diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 00000000000..e617573a381 --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,23 @@ +name: Security Audit +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' + +jobs: + audit: + runs-on: ubuntu-latest + permissions: + issues: write + checks: write + steps: + - uses: actions/checkout@v3 + - uses: rustsec/audit-check@v1.4.1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ignore: "RUSTSEC-2021-0145" + # RUSTSEC-2021-0145 pertains `atty`, which is a depencency of + # `criterion`. While the latter removed the depencency in its + # newest version, it would also require a higher `rustc`. We + # therefore avoid bumping it to allow benchmarking with our + # `rustc` 1.63 MSRV. 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..f3108ae437b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,348 @@ +# 0.0.123 - May 08, 2024 - "BOLT12 Dust Sweeping" + +## API Updates + + * To reduce risk of force-closures and improve HTLC reliability the default + dust exposure limit has been increased to + `MaxDustHTLCExposure::FeeRateMultiplier(10_000)`. Users with existing + channels might want to consider using + `ChannelManager::update_channel_config` to apply the new default (#3045). + * `ChainMonitor::archive_fully_resolved_channel_monitors` is now provided to + remove from memory `ChannelMonitor`s that have been fully resolved on-chain + and are now not needed. It uses the new `Persist::archive_persisted_channel` + to inform the storage layer that such a monitor should be archived (#2964). + * An `OutputSweeper` is now provided which will automatically sweep + `SpendableOutputDescriptor`s, retrying until the sweep confirms (#2825). + * After initiating an outbound channel, a peer disconnection no longer results + in immediate channel closure. Rather, if the peer is reconnected before the + channel times out LDK will automatically retry opening it (#2725). + * `PaymentPurpose` now has separate variants for BOLT12 payments, which + include fields from the `invoice_request` as well as the `OfferId` (#2970). + * `ChannelDetails` now includes a list of in-flight HTLCs (#2442). + * `Event::PaymentForwarded` now includes `skimmed_fee_msat` (#2858). + * The `hashbrown` dependency has been upgraded and the use of `ahash` as the + no-std hash table hash function has been removed. As a consequence, LDK's + `Hash{Map,Set}`s no longer feature several constructors when LDK is built + with no-std; see the `util::hash_tables` module instead. On platforms that + `getrandom` supports, setting the `possiblyrandom/getrandom` feature flag + will ensure hash tables are resistant to HashDoS attacks, though the + `possiblyrandom` crate should detect most common platforms (#2810, #2891). + * `ChannelMonitor`-originated requests to the `ChannelSigner` can now fail and + be retried using `ChannelMonitor::signer_unblocked` (#2816). + * `SpendableOutputDescriptor::to_psbt_input` now includes the `witness_script` + where available as well as new proprietary data which can be used to + re-derive some spending keys from the base key (#2761, #3004). + * `OutPoint::to_channel_id` has been removed in favor of + `ChannelId::v1_from_funding_outpoint` in preparation for v2 channels with a + different `ChannelId` derivation scheme (#2797). + * `PeerManager::get_peer_node_ids` has been replaced with `list_peers` and + `peer_by_node_id`, which provide more details (#2905). + * `Bolt11Invoice::get_payee_pub_key` is now provided (#2909). + * `Default[Message]Router` now take an `entropy_source` argument (#2847). + * `ClosureReason::HTLCsTimedOut` has been separated out from + `ClosureReason::HolderForceClosed` as it is the most common case (#2887). + * `ClosureReason::CooperativeClosure` is now split into + `{Counterparty,Locally}Initiated` variants (#2863). + * `Event::ChannelPending::channel_type` is now provided (#2872). + * `PaymentForwarded::{prev,next}_user_channel_id` are now provided (#2924). + * Channel init messages have been refactored towards V2 channels (#2871). + * `BumpTransactionEvent` now contains the channel and counterparty (#2873). + * `util::scid_utils` is now public, with some trivial utilities to examine + short channel ids (#2694). + * `DirectedChannelInfo::{source,target}` are now public (#2870). + * Bounds in `lightning-background-processor` were simplified by using + `AChannelManager` (#2963). + * The `Persist` impl for `KVStore` no longer requires `Sized`, allowing for + the use of `dyn KVStore` as `Persist` (#2883, #2976). + * `From` is now implemented for `PaymentHash` (#2918). + * `NodeId::from_slice` is now provided (#2942). + * `ChannelManager` deserialization may now fail with `DangerousValue` when + LDK's persistence API was violated (#2974). + +## Bug Fixes + * Excess fees on counterparty commitment transactions are now included in the + dust exposure calculation. This lines behavior up with some cases where + transaction fees can be burnt, making them effectively dust exposure (#3045). + * `Future`s used as an `std::...::Future` could grow in size unbounded if it + was never woken. For those not using async persistence and using the async + `lightning-background-processor`, this could cause a memory leak in the + `ChainMonitor` (#2894). + * Inbound channel requests that fail in + `ChannelManager::accept_inbound_channel` would previously have stalled from + the peer's perspective as no `error` message was sent (#2953). + * Blinded path construction has been tuned to select paths more likely to + succeed, improving BOLT12 payment reliability (#2911, #2912). + * After a reorg, `lightning-transaction-sync` could have failed to follow a + transaction that LDK needed information about (#2946). + * `RecipientOnionFields`' `custom_tlvs` are now propagated to recipients when + paying with blinded paths (#2975). + * `Event::ChannelClosed` is now properly generated and peers are properly + notified for all channels that as a part of a batch channel open fail to be + funded (#3029). + * In cases where user event processing is substantially delayed such that we + complete multiple round-trips with our peers before a `PaymentSent` event is + handled and then restart without persisting the `ChannelManager` after having + persisted a `ChannelMonitor[Update]`, on startup we may have `Err`d trying to + deserialize the `ChannelManager` (#3021). + * If a peer has relatively high latency, `PeerManager` may have failed to + establish a connection (#2993). + * `ChannelUpdate` messages broadcasted for our own channel closures are now + slightly more robust (#2731). + * Deserializing malformed BOLT11 invoices may have resulted in an integer + overflow panic in debug builds (#3032). + * In exceedingly rare cases (no cases of this are known), LDK may have created + an invalid serialization for a `ChannelManager` (#2998). + * Message processing latency handling BOLT12 payments has been reduced (#2881). + * Latency in processing `Event::SpendableOutputs` may be reduced (#3033). + +## Node Compatibility + * LDK's blinded paths were inconsistent with other implementations in several + ways, which have been addressed (#2856, #2936, #2945). + * LDK's messaging blinded paths now support the latest features which some + nodes may begin relying on soon (#2961). + * LDK's BOLT12 structs have been updated to support some last-minute changes to + the spec (#3017, #3018). + * CLN v24.02 requires the `gossip_queries` feature for all peers, however LDK + by default does not set it for those not using a `P2PGossipSync` (e.g. those + using RGS). This change was reverted in CLN v24.02.2 however for now LDK + always sets the `gossip_queries` feature. This change is expected to be + reverted in a future LDK release (#2959). + +## Security +0.0.123 fixes a denial-of-service vulnerability which we believe to be reachable +from untrusted input when parsing invalid BOLT11 invoices containing non-ASCII +characters. + * BOLT11 invoices with non-ASCII characters in the human-readable-part may + cause an out-of-bounds read attempt leading to a panic (#3054). Note that all + BOLT11 invoices containing non-ASCII characters are invalid. + +In total, this release features 150 files changed, 19307 insertions, 6306 +deletions in 360 commits since 0.0.121 from 17 authors, in alphabetical order: + + * Arik Sosman + * Duncan Dean + * Elias Rohrer + * Evan Feenstra + * Jeffrey Czyz + * Keyue Bao + * Matt Corallo + * Orbital + * Sergi Delgado Segura + * Valentine Wallace + * Willem Van Lint + * Wilmer Paulino + * benthecarman + * jbesraa + * olegkubrakov + * optout + * shaavan + + +# 0.0.122 - Apr 09, 2024 - "That Which Is Untested Is Broken" + +## Bug Fixes + * `Route` objects did not successfully round-trip through de/serialization + since LDK 0.0.117, which has now been fixed (#2897). + * Correct deserialization of unknown future enum variants. This ensures + downgrades from future versions of LDK do not result in read failures or + corrupt reads in cases where enums are written (#2969). + * When hitting lnd bug 6039, our workaround previously resulted in + `ChannelManager` persistences on every round-trip with our peer. These + useless persistences are now skipped (#2937). + +In total, this release features 4 files changed, 99 insertions, 55 +deletions in 6 commits from 1 author, in alphabetical order: + * Matt Corallo + + +# 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 + * José 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 e795ecb9fba..4b8e044d1d3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,10 +88,11 @@ 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.41.1 (enforced by -our GitHub Actions). Also, the compatibility for LDK object serialization is -currently ensured back to and including crate version 0.0.99 (see the -[changelog](CHANGELOG.md)). +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 +upgrades or downgrades should be mentioned in the [changelog](CHANGELOG.md). Commits should cover both the issue fixed and the solution's rationale. These [guidelines](https://chris.beams.io/posts/git-commit/) should be kept in mind. @@ -119,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 @@ -147,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 a4ab59b5383..f8de40f3193 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/check-cfg-flags.py b/ci/check-cfg-flags.py new file mode 100755 index 00000000000..fd514da657b --- /dev/null +++ b/ci/check-cfg-flags.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +# Rust is fairly relaxed in checking the validity of arguments passed to #[cfg]. +# While it should probably be more strict when checking features, it cannot be +# strict when checking loose cfg tags, because those can be anything and are +# simply passed to rustc via unconstrained arguments. +# +# Thus, we do it for rustc manually, but scanning all our source and checking +# that all our cfg tags match a known cfg tag. +import sys, glob, re + +def check_feature(feature): + if feature == "std": + pass + elif feature == "no-std": + pass + elif feature == "possiblyrandom": + pass + elif feature == "getrandom": + pass + elif feature == "hashbrown": + pass + elif feature == "backtrace": + pass + elif feature == "grind_signatures": + pass + elif feature == "unsafe_revoked_tx_signing": + pass + elif feature == "futures": + pass + elif feature == "tokio": + pass + elif feature == "rest-client": + pass + elif feature == "rpc-client": + pass + elif feature == "serde": + pass + elif feature == "esplora-blocking": + pass + elif feature == "esplora-async": + pass + elif feature == "async-interface": + pass + elif feature == "electrum": + pass + elif feature == "time": + pass + elif feature == "_test_utils": + pass + elif feature == "_test_vectors": + pass + elif feature == "afl": + pass + elif feature == "honggfuzz": + pass + elif feature == "libfuzzer_fuzz": + pass + elif feature == "stdin_fuzz": + pass + elif feature == "max_level_off": + pass + elif feature == "max_level_error": + pass + elif feature == "max_level_warn": + pass + elif feature == "max_level_info": + pass + elif feature == "max_level_debug": + pass + elif feature == "max_level_trace": + pass + else: + print("Bad feature: " + feature) + assert False + +def check_target_os(os): + if os == "windows": + pass + else: + assert False + +def check_cfg_tag(cfg): + if cfg == "fuzzing": + pass + elif cfg == "test": + pass + elif cfg == "debug_assertions": + pass + elif cfg == "c_bindings": + pass + elif cfg == "ldk_bench": + pass + elif cfg == "taproot": + pass + elif cfg == "async_signing": + pass + elif cfg == "require_route_graph_test": + pass + elif cfg == "dual_funding": + pass + elif cfg == "splicing": + pass + else: + print("Bad cfg tag: " + cfg) + assert False + +def check_cfg_args(cfg): + if cfg.startswith("all(") or cfg.startswith("any(") or cfg.startswith("not("): + brackets = 1 + pos = 4 + while pos < len(cfg): + if cfg[pos] == "(": + brackets += 1 + elif cfg[pos] == ")": + brackets -= 1 + if brackets == 0: + check_cfg_args(cfg[4:pos]) + if pos + 1 != len(cfg): + assert cfg[pos + 1] == "," + check_cfg_args(cfg[pos + 2:].strip()) + return + pos += 1 + assert False + assert(cfg.endswith(")")) + check_cfg_args(cfg[4:len(cfg)-1]) + else: + parts = [part.strip() for part in cfg.split(",", 1)] + if len(parts) > 1: + for part in parts: + check_cfg_args(part) + elif cfg.startswith("feature") or cfg.startswith("target_os") or cfg.startswith("target_pointer_width"): + arg = cfg + if cfg.startswith("feature"): + arg = arg[7:].strip() + elif cfg.startswith("target_os"): + arg = arg[9:].strip() + else: + arg = arg[20:].strip() + assert arg.startswith("=") + arg = arg[1:].strip() + assert arg.startswith("\"") + assert arg.endswith("\"") + arg = arg[1:len(arg)-1] + assert not "\"" in arg + if cfg.startswith("feature"): + check_feature(arg) + elif cfg.startswith("target_os"): + check_target_os(arg) + else: + assert arg == "32" or arg == "64" + else: + check_cfg_tag(cfg.strip()) + +cfg_regex = re.compile("#\[cfg\((.*)\)\]") +for path in glob.glob(sys.path[0] + "/../**/*.rs", recursive = True): + with open(path, "r") as file: + while True: + line = file.readline() + if not line: + break + if "#[cfg(" in line: + if not line.strip().startswith("//"): + cfg_part = cfg_regex.match(line.strip()).group(1) + check_cfg_args(cfg_part) diff --git a/ci/ci-tests.sh b/ci/ci-tests.sh index 00fc54774d3..0dc654d8bed 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 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 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 +PIN_RELEASE_DEPS # pin the release dependencies in our main workspace -# 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 +# 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 syn crate depends on too-new proc-macro2 starting with v2.0.33, i.e., has MSRV of 1.56 -if [ "$RUSTC_MINOR_VERSION" -lt 56 ]; then - SYN_2_DEP=$(grep -o '"syn 2.*' Cargo.lock | tr -d '",' | tr ' ' ':') - cargo update -p "$SYN_2_DEP" --precise "2.0.32" --verbose -fi +# The 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 proc-macro2 crate switched to Rust edition 2021 starting with v1.0.66, i.e., has MSRV of 1.56 -[ "$RUSTC_MINOR_VERSION" -lt 56 ] && cargo update -p proc-macro2 --precise "1.0.65" --verbose - -# The memchr crate switched to an MSRV of 1.60 starting with v2.6.0 -[ "$RUSTC_MINOR_VERSION" -lt 60 ] && cargo update -p memchr --precise "2.5.0" --verbose +# 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,15 +87,20 @@ 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 cargo check --verbose --color always --features esplora-async cargo test --verbose --color always --features esplora-async-https cargo check --verbose --color always --features esplora-async-https + cargo test --verbose --color always --features electrum + cargo check --verbose --color always --features electrum popd fi @@ -76,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 @@ -123,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 @@ -150,7 +171,11 @@ 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 cfg-flag builds" +RUSTFLAGS="--cfg=taproot" cargo test --verbose --color always -p lightning +[ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean +RUSTFLAGS="--cfg=async_signing" cargo test --verbose --color always -p lightning +[ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean +RUSTFLAGS="--cfg=dual_funding" cargo test --verbose --color always -p lightning +[ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean +RUSTFLAGS="--cfg=splicing" cargo test --verbose --color always -p lightning diff --git a/ci/rustfmt.sh b/ci/rustfmt.sh new file mode 100755 index 00000000000..a1fdf6bd7f3 --- /dev/null +++ b/ci/rustfmt.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -eox pipefail + +# Generate initial exclusion list +#find . -name '*.rs' -type f |sort >rustfmt_excluded_files + +# The +rustversion syntax only works with rustup-installed rust toolchains, +# not with any distro-provided ones. Thus, we check for a rustup install and +# only pass +1.63.0 if we find one. +VERS="" +[ "$(which rustup)" != "" ] && VERS="+1.63.0" + +# Run fmt +TMP_FILE=$(mktemp) +find . -name '*.rs' -type f |sort >$TMP_FILE +for file in $(comm -23 $TMP_FILE rustfmt_excluded_files); do + echo "Checking formatting of $file" + rustfmt $VERS --check $file +done diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 5daebb35066..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. @@ -20,9 +20,8 @@ stdin_fuzz = [] [dependencies] lightning = { path = "../lightning", features = ["regex", "hashbrown", "_test_utils"] } lightning-rapid-gossip-sync = { path = "../lightning-rapid-gossip-sync" } -bitcoin = { version = "0.29.0", features = ["secp-lowmemory"] } -hex = "0.3" -hashbrown = "0.8" +bitcoin = { version = "0.30.2", features = ["secp-lowmemory"] } +hex = { package = "hex-conservative", version = "0.1.1", default-features = false } afl = { version = "0.12", optional = true } honggfuzz = { version = "0.5", optional = true, default-features = false } diff --git a/fuzz/README.md b/fuzz/README.md index bfc8fa5f4bf..987288b5d93 100644 --- a/fuzz/README.md +++ b/fuzz/README.md @@ -1,22 +1,23 @@ # Fuzzing -Fuzz tests generate a ton of random parameter arguments to the program and then validate that none cause it to crash. +Fuzz tests generate a ton of random parameter arguments to the program and then validate that none +cause it to crash. ## How does it work? -Typically, Travis CI will run `travis-fuzz.sh` on one of the environments the automated tests are configured for. -This is the most time-consuming component of the continuous integration workflow, so it is recommended that you detect -issues locally, and Travis merely acts as a sanity check. Fuzzing is further only effective with -a lot of CPU time, indicating that if crash scenarios are discovered on Travis with its low -runtime constraints, the crash is caused relatively easily. +Typically, CI will run `ci-fuzz.sh` on one of the environments the automated tests are +configured for. Fuzzing is further only effective with a lot of CPU time, indicating that if crash +scenarios are discovered on CI with its low runtime constraints, the crash is caused relatively +easily. ## How do I run fuzz tests locally? -You typically won't need to run the entire combination of different fuzzing tools. For local execution, `honggfuzz` -should be more than sufficient. +We support multiple fuzzing engines such as `honggfuzz`, `libFuzzer` and `AFL`. You typically won't +need to run the entire suite of different fuzzing tools. For local execution, `honggfuzz`should be +more than sufficient. ### Setup - +#### Honggfuzz To install `honggfuzz`, simply run ```shell @@ -31,9 +32,18 @@ cargo update -p honggfuzz --precise "0.5.52" cargo install --force honggfuzz --version "0.5.52" ``` +#### cargo-fuzz / libFuzzer +To install `cargo-fuzz`, simply run + +```shell +cargo update +cargo install --force cargo-fuzz +``` + ### Execution -To run the Hongg fuzzer, do +#### Honggfuzz +To run fuzzing using `honggfuzz`, do ```shell export CPU_COUNT=1 # replace as needed @@ -46,19 +56,39 @@ cargo hfuzz run $TARGET (Or, for a prettier output, replace the last line with `cargo --color always hfuzz run $TARGET`.) +#### cargo-fuzz / libFuzzer +To run fuzzing using `cargo-fuzz / libFuzzer`, run + +```shell +rustup install nightly # Note: libFuzzer requires a nightly version of rust. +cargo +nightly fuzz run --features "libfuzzer_fuzz" msg_ping_target +``` +Note: If you encounter a `SIGKILL` during run/build check for OOM in kernel logs and consider +increasing RAM size for VM. + +If you wish to just generate fuzzing binary executables for `libFuzzer` and not run them: +```shell +cargo +nightly fuzz build --features "libfuzzer_fuzz" msg_ping_target +# Generates binary artifact in path ./target/aarch64-unknown-linux-gnu/release/msg_ping_target +# Exact path depends on your system architecture. +``` +You can upload the build artifact generated above to `ClusterFuzz` for distributed fuzzing. + +### List Fuzzing Targets To see a list of available fuzzing targets, run: ```shell ls ./src/bin/ ``` -## A fuzz test failed on Travis, what do I do? +## A fuzz test failed, what do I do? -You're trying to create a PR, but need to find the underlying cause of that pesky fuzz failure blocking the merge? +You're trying to create a PR, but need to find the underlying cause of that pesky fuzz failure +blocking the merge? Worry not, for this is easily traced. -If your Travis output log looks like this: +If your output log looks like this: ``` Size:639 (i,b,hw,ed,ip,cmp): 0/0/0/0/0/1, Tot:0/0/0/2036/5/28604 @@ -66,13 +96,13 @@ Seen a crash. Terminating all fuzzing threads … # a lot of lines in between -<0x0000555555565559> [func:UNKNOWN file: line:0 module:/home/travis/build/rust-bitcoin/rust-lightning/fuzz/hfuzz_target/x86_64-unknown-linux-gnu/release/full_stack_target] +<0x0000555555565559> [func:UNKNOWN file: line:0 module:./rust-lightning/fuzz/hfuzz_target/x86_64-unknown-linux-gnu/release/full_stack_target] <0x0000000000000000> [func:UNKNOWN file: line:0 module:UNKNOWN] ===================================================================== 2d3136383734090101010101010101010101010101010101010101010101 010101010100040101010101010101010101010103010101010100010101 0069d07c319a4961 -The command "if [ "$(rustup show | grep default | grep stable)" != "" ]; then cd fuzz && cargo test --verbose && ./travis-fuzz.sh; fi" exited with 1. +The command "if [ "$(rustup show | grep default | grep stable)" != "" ]; then cd fuzz && cargo test --verbose && ./ci-fuzz.sh; fi" exited with 1. ``` Note that the penultimate stack trace line ends in `release/full_stack_target]`. That indicates that diff --git a/fuzz/src/bin/gen_target.sh b/fuzz/src/bin/gen_target.sh index 2fa7debdf46..62381622f6b 100755 --- a/fuzz/src/bin/gen_target.sh +++ b/fuzz/src/bin/gen_target.sh @@ -70,3 +70,9 @@ GEN_TEST msg_tx_signatures msg_targets:: GEN_TEST msg_tx_init_rbf msg_targets:: GEN_TEST msg_tx_ack_rbf msg_targets:: GEN_TEST msg_tx_abort msg_targets:: + +GEN_TEST msg_stfu msg_targets:: + +GEN_TEST msg_splice msg_targets:: +GEN_TEST msg_splice_ack msg_targets:: +GEN_TEST msg_splice_locked msg_targets:: diff --git a/fuzz/src/bin/msg_splice_ack_target.rs b/fuzz/src/bin/msg_splice_ack_target.rs new file mode 100644 index 00000000000..cff5675e191 --- /dev/null +++ b/fuzz/src/bin/msg_splice_ack_target.rs @@ -0,0 +1,113 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This file is auto-generated by gen_target.sh based on target_template.txt +// To modify it, modify target_template.txt and run gen_target.sh instead. + +#![cfg_attr(feature = "libfuzzer_fuzz", no_main)] + +#[cfg(not(fuzzing))] +compile_error!("Fuzz targets need cfg=fuzzing"); + +extern crate lightning_fuzz; +use lightning_fuzz::msg_targets::msg_splice_ack::*; + +#[cfg(feature = "afl")] +#[macro_use] extern crate afl; +#[cfg(feature = "afl")] +fn main() { + fuzz!(|data| { + msg_splice_ack_run(data.as_ptr(), data.len()); + }); +} + +#[cfg(feature = "honggfuzz")] +#[macro_use] extern crate honggfuzz; +#[cfg(feature = "honggfuzz")] +fn main() { + loop { + fuzz!(|data| { + msg_splice_ack_run(data.as_ptr(), data.len()); + }); + } +} + +#[cfg(feature = "libfuzzer_fuzz")] +#[macro_use] extern crate libfuzzer_sys; +#[cfg(feature = "libfuzzer_fuzz")] +fuzz_target!(|data: &[u8]| { + msg_splice_ack_run(data.as_ptr(), data.len()); +}); + +#[cfg(feature = "stdin_fuzz")] +fn main() { + use std::io::Read; + + let mut data = Vec::with_capacity(8192); + std::io::stdin().read_to_end(&mut data).unwrap(); + msg_splice_ack_run(data.as_ptr(), data.len()); +} + +#[test] +fn run_test_cases() { + use std::fs; + use std::io::Read; + use lightning_fuzz::utils::test_logger::StringBuffer; + + use std::sync::{atomic, Arc}; + { + let data: Vec = vec![0]; + msg_splice_ack_run(data.as_ptr(), data.len()); + } + let mut threads = Vec::new(); + let threads_running = Arc::new(atomic::AtomicUsize::new(0)); + if let Ok(tests) = fs::read_dir("test_cases/msg_splice_ack") { + for test in tests { + let mut data: Vec = Vec::new(); + let path = test.unwrap().path(); + fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap(); + threads_running.fetch_add(1, atomic::Ordering::AcqRel); + + let thread_count_ref = Arc::clone(&threads_running); + let main_thread_ref = std::thread::current(); + threads.push((path.file_name().unwrap().to_str().unwrap().to_string(), + std::thread::spawn(move || { + let string_logger = StringBuffer::new(); + + let panic_logger = string_logger.clone(); + let res = if ::std::panic::catch_unwind(move || { + msg_splice_ack_test(&data, panic_logger); + }).is_err() { + Some(string_logger.into_string()) + } else { None }; + thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel); + main_thread_ref.unpark(); + res + }) + )); + while threads_running.load(atomic::Ordering::Acquire) > 32 { + std::thread::park(); + } + } + } + let mut failed_outputs = Vec::new(); + for (test, thread) in threads.drain(..) { + if let Some(output) = thread.join().unwrap() { + println!("\nOutput of {}:\n{}\n", test, output); + failed_outputs.push(test); + } + } + if !failed_outputs.is_empty() { + println!("Test cases which failed: "); + for case in failed_outputs { + println!("{}", case); + } + panic!(); + } +} diff --git a/fuzz/src/bin/msg_splice_locked_target.rs b/fuzz/src/bin/msg_splice_locked_target.rs new file mode 100644 index 00000000000..ea5a49bc16d --- /dev/null +++ b/fuzz/src/bin/msg_splice_locked_target.rs @@ -0,0 +1,113 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This file is auto-generated by gen_target.sh based on target_template.txt +// To modify it, modify target_template.txt and run gen_target.sh instead. + +#![cfg_attr(feature = "libfuzzer_fuzz", no_main)] + +#[cfg(not(fuzzing))] +compile_error!("Fuzz targets need cfg=fuzzing"); + +extern crate lightning_fuzz; +use lightning_fuzz::msg_targets::msg_splice_locked::*; + +#[cfg(feature = "afl")] +#[macro_use] extern crate afl; +#[cfg(feature = "afl")] +fn main() { + fuzz!(|data| { + msg_splice_locked_run(data.as_ptr(), data.len()); + }); +} + +#[cfg(feature = "honggfuzz")] +#[macro_use] extern crate honggfuzz; +#[cfg(feature = "honggfuzz")] +fn main() { + loop { + fuzz!(|data| { + msg_splice_locked_run(data.as_ptr(), data.len()); + }); + } +} + +#[cfg(feature = "libfuzzer_fuzz")] +#[macro_use] extern crate libfuzzer_sys; +#[cfg(feature = "libfuzzer_fuzz")] +fuzz_target!(|data: &[u8]| { + msg_splice_locked_run(data.as_ptr(), data.len()); +}); + +#[cfg(feature = "stdin_fuzz")] +fn main() { + use std::io::Read; + + let mut data = Vec::with_capacity(8192); + std::io::stdin().read_to_end(&mut data).unwrap(); + msg_splice_locked_run(data.as_ptr(), data.len()); +} + +#[test] +fn run_test_cases() { + use std::fs; + use std::io::Read; + use lightning_fuzz::utils::test_logger::StringBuffer; + + use std::sync::{atomic, Arc}; + { + let data: Vec = vec![0]; + msg_splice_locked_run(data.as_ptr(), data.len()); + } + let mut threads = Vec::new(); + let threads_running = Arc::new(atomic::AtomicUsize::new(0)); + if let Ok(tests) = fs::read_dir("test_cases/msg_splice_locked") { + for test in tests { + let mut data: Vec = Vec::new(); + let path = test.unwrap().path(); + fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap(); + threads_running.fetch_add(1, atomic::Ordering::AcqRel); + + let thread_count_ref = Arc::clone(&threads_running); + let main_thread_ref = std::thread::current(); + threads.push((path.file_name().unwrap().to_str().unwrap().to_string(), + std::thread::spawn(move || { + let string_logger = StringBuffer::new(); + + let panic_logger = string_logger.clone(); + let res = if ::std::panic::catch_unwind(move || { + msg_splice_locked_test(&data, panic_logger); + }).is_err() { + Some(string_logger.into_string()) + } else { None }; + thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel); + main_thread_ref.unpark(); + res + }) + )); + while threads_running.load(atomic::Ordering::Acquire) > 32 { + std::thread::park(); + } + } + } + let mut failed_outputs = Vec::new(); + for (test, thread) in threads.drain(..) { + if let Some(output) = thread.join().unwrap() { + println!("\nOutput of {}:\n{}\n", test, output); + failed_outputs.push(test); + } + } + if !failed_outputs.is_empty() { + println!("Test cases which failed: "); + for case in failed_outputs { + println!("{}", case); + } + panic!(); + } +} diff --git a/fuzz/src/bin/msg_splice_target.rs b/fuzz/src/bin/msg_splice_target.rs new file mode 100644 index 00000000000..bff4dffbbc2 --- /dev/null +++ b/fuzz/src/bin/msg_splice_target.rs @@ -0,0 +1,113 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This file is auto-generated by gen_target.sh based on target_template.txt +// To modify it, modify target_template.txt and run gen_target.sh instead. + +#![cfg_attr(feature = "libfuzzer_fuzz", no_main)] + +#[cfg(not(fuzzing))] +compile_error!("Fuzz targets need cfg=fuzzing"); + +extern crate lightning_fuzz; +use lightning_fuzz::msg_targets::msg_splice::*; + +#[cfg(feature = "afl")] +#[macro_use] extern crate afl; +#[cfg(feature = "afl")] +fn main() { + fuzz!(|data| { + msg_splice_run(data.as_ptr(), data.len()); + }); +} + +#[cfg(feature = "honggfuzz")] +#[macro_use] extern crate honggfuzz; +#[cfg(feature = "honggfuzz")] +fn main() { + loop { + fuzz!(|data| { + msg_splice_run(data.as_ptr(), data.len()); + }); + } +} + +#[cfg(feature = "libfuzzer_fuzz")] +#[macro_use] extern crate libfuzzer_sys; +#[cfg(feature = "libfuzzer_fuzz")] +fuzz_target!(|data: &[u8]| { + msg_splice_run(data.as_ptr(), data.len()); +}); + +#[cfg(feature = "stdin_fuzz")] +fn main() { + use std::io::Read; + + let mut data = Vec::with_capacity(8192); + std::io::stdin().read_to_end(&mut data).unwrap(); + msg_splice_run(data.as_ptr(), data.len()); +} + +#[test] +fn run_test_cases() { + use std::fs; + use std::io::Read; + use lightning_fuzz::utils::test_logger::StringBuffer; + + use std::sync::{atomic, Arc}; + { + let data: Vec = vec![0]; + msg_splice_run(data.as_ptr(), data.len()); + } + let mut threads = Vec::new(); + let threads_running = Arc::new(atomic::AtomicUsize::new(0)); + if let Ok(tests) = fs::read_dir("test_cases/msg_splice") { + for test in tests { + let mut data: Vec = Vec::new(); + let path = test.unwrap().path(); + fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap(); + threads_running.fetch_add(1, atomic::Ordering::AcqRel); + + let thread_count_ref = Arc::clone(&threads_running); + let main_thread_ref = std::thread::current(); + threads.push((path.file_name().unwrap().to_str().unwrap().to_string(), + std::thread::spawn(move || { + let string_logger = StringBuffer::new(); + + let panic_logger = string_logger.clone(); + let res = if ::std::panic::catch_unwind(move || { + msg_splice_test(&data, panic_logger); + }).is_err() { + Some(string_logger.into_string()) + } else { None }; + thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel); + main_thread_ref.unpark(); + res + }) + )); + while threads_running.load(atomic::Ordering::Acquire) > 32 { + std::thread::park(); + } + } + } + let mut failed_outputs = Vec::new(); + for (test, thread) in threads.drain(..) { + if let Some(output) = thread.join().unwrap() { + println!("\nOutput of {}:\n{}\n", test, output); + failed_outputs.push(test); + } + } + if !failed_outputs.is_empty() { + println!("Test cases which failed: "); + for case in failed_outputs { + println!("{}", case); + } + panic!(); + } +} diff --git a/fuzz/src/bin/msg_stfu_target.rs b/fuzz/src/bin/msg_stfu_target.rs new file mode 100644 index 00000000000..1cabf8ded48 --- /dev/null +++ b/fuzz/src/bin/msg_stfu_target.rs @@ -0,0 +1,113 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This file is auto-generated by gen_target.sh based on target_template.txt +// To modify it, modify target_template.txt and run gen_target.sh instead. + +#![cfg_attr(feature = "libfuzzer_fuzz", no_main)] + +#[cfg(not(fuzzing))] +compile_error!("Fuzz targets need cfg=fuzzing"); + +extern crate lightning_fuzz; +use lightning_fuzz::msg_targets::msg_stfu::*; + +#[cfg(feature = "afl")] +#[macro_use] extern crate afl; +#[cfg(feature = "afl")] +fn main() { + fuzz!(|data| { + msg_stfu_run(data.as_ptr(), data.len()); + }); +} + +#[cfg(feature = "honggfuzz")] +#[macro_use] extern crate honggfuzz; +#[cfg(feature = "honggfuzz")] +fn main() { + loop { + fuzz!(|data| { + msg_stfu_run(data.as_ptr(), data.len()); + }); + } +} + +#[cfg(feature = "libfuzzer_fuzz")] +#[macro_use] extern crate libfuzzer_sys; +#[cfg(feature = "libfuzzer_fuzz")] +fuzz_target!(|data: &[u8]| { + msg_stfu_run(data.as_ptr(), data.len()); +}); + +#[cfg(feature = "stdin_fuzz")] +fn main() { + use std::io::Read; + + let mut data = Vec::with_capacity(8192); + std::io::stdin().read_to_end(&mut data).unwrap(); + msg_stfu_run(data.as_ptr(), data.len()); +} + +#[test] +fn run_test_cases() { + use std::fs; + use std::io::Read; + use lightning_fuzz::utils::test_logger::StringBuffer; + + use std::sync::{atomic, Arc}; + { + let data: Vec = vec![0]; + msg_stfu_run(data.as_ptr(), data.len()); + } + let mut threads = Vec::new(); + let threads_running = Arc::new(atomic::AtomicUsize::new(0)); + if let Ok(tests) = fs::read_dir("test_cases/msg_stfu") { + for test in tests { + let mut data: Vec = Vec::new(); + let path = test.unwrap().path(); + fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap(); + threads_running.fetch_add(1, atomic::Ordering::AcqRel); + + let thread_count_ref = Arc::clone(&threads_running); + let main_thread_ref = std::thread::current(); + threads.push((path.file_name().unwrap().to_str().unwrap().to_string(), + std::thread::spawn(move || { + let string_logger = StringBuffer::new(); + + let panic_logger = string_logger.clone(); + let res = if ::std::panic::catch_unwind(move || { + msg_stfu_test(&data, panic_logger); + }).is_err() { + Some(string_logger.into_string()) + } else { None }; + thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel); + main_thread_ref.unpark(); + res + }) + )); + while threads_running.load(atomic::Ordering::Acquire) > 32 { + std::thread::park(); + } + } + } + let mut failed_outputs = Vec::new(); + for (test, thread) in threads.drain(..) { + if let Some(output) = thread.join().unwrap() { + println!("\nOutput of {}:\n{}\n", test, output); + failed_outputs.push(test); + } + } + if !failed_outputs.is_empty() { + println!("Test cases which failed: "); + for case in failed_outputs { + println!("{}", case); + } + panic!(); + } +} diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index dddd97cae45..b3cf867d6e3 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -20,9 +20,9 @@ use bitcoin::blockdata::constants::genesis_block; use bitcoin::blockdata::transaction::{Transaction, TxOut}; -use bitcoin::blockdata::script::{Builder, Script}; +use bitcoin::blockdata::script::{Builder, ScriptBuf}; use bitcoin::blockdata::opcodes; -use bitcoin::blockdata::locktime::PackedLockTime; +use bitcoin::blockdata::locktime::absolute::LockTime; use bitcoin::network::constants::Network; use bitcoin::hashes::Hash as TraitImport; @@ -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; @@ -80,9 +83,8 @@ impl FeeEstimator for FuzzEstimator { // always return a HighPriority feerate here which is >= the maximum Normal feerate and a // Background feerate which is <= the minimum Normal feerate. match conf_target { - ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => MAX_FEE * 10, ConfirmationTarget::OnChainSweep => MAX_FEE, - ConfirmationTarget::ChannelCloseMinimum|ConfirmationTarget::AnchorChannelFee|ConfirmationTarget::MinAllowedAnchorChannelRemoteFee|ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => 253, + ConfirmationTarget::ChannelCloseMinimum|ConfirmationTarget::AnchorChannelFee|ConfirmationTarget::MinAllowedAnchorChannelRemoteFee|ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee|ConfirmationTarget::OutputSpendingFee => 253, ConfirmationTarget::NonAnchorChannelFee => cmp::min(self.ret_val.load(atomic::Ordering::Acquire), MAX_FEE), } } @@ -100,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 {} @@ -134,7 +157,7 @@ impl TestChainMonitor { logger, keys, persister, - latest_monitors: Mutex::new(HashMap::new()), + latest_monitors: Mutex::new(new_hash_map()), } } } @@ -150,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(); } } @@ -231,14 +251,16 @@ impl NodeSigner for KeyProvider { } impl SignerProvider for KeyProvider { - type Signer = TestChannelSigner; + type EcdsaSigner = TestChannelSigner; + #[cfg(taproot)] + type TaprootSigner = TestChannelSigner; fn generate_channel_keys_id(&self, _inbound: bool, _channel_value_satoshis: u64, _user_channel_id: u128) -> [u8; 32] { let id = self.rand_bytes_id.fetch_add(1, atomic::Ordering::Relaxed) as u8; [id; 32] } - fn derive_channel_signer(&self, channel_value_satoshis: u64, channel_keys_id: [u8; 32]) -> Self::Signer { + fn derive_channel_signer(&self, channel_value_satoshis: u64, channel_keys_id: [u8; 32]) -> Self::EcdsaSigner { let secp_ctx = Secp256k1::signing_only(); let id = channel_keys_id[0]; let keys = InMemorySigner::new( @@ -257,7 +279,7 @@ impl SignerProvider for KeyProvider { TestChannelSigner::new_with_revoked(keys, revoked_commitment, false) } - fn read_chan_signer(&self, buffer: &[u8]) -> Result { + fn read_chan_signer(&self, buffer: &[u8]) -> Result { let mut reader = std::io::Cursor::new(buffer); let inner: InMemorySigner = ReadableArgs::read(&mut reader, self)?; @@ -267,14 +289,15 @@ impl SignerProvider for KeyProvider { inner, state, disable_revocation_policy_check: false, + available: Arc::new(Mutex::new(true)), }) } - fn get_destination_script(&self) -> Result { + fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Result { let secp_ctx = Secp256k1::signing_only(); let channel_monitor_claim_key = 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, 2, self.node_secret[31]]).unwrap(); let our_channel_monitor_claim_key_hash = WPubkeyHash::hash(&PublicKey::from_secret_key(&secp_ctx, &channel_monitor_claim_key).serialize()); - Ok(Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0).push_slice(&our_channel_monitor_claim_key_hash[..]).into_script()) + Ok(Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0).push_slice(our_channel_monitor_claim_key_hash).into_script()) } fn get_shutdown_scriptpubkey(&self) -> Result { @@ -343,7 +366,7 @@ type ChanMan<'a> = ChannelManager, Arc, A fn get_payment_secret_hash(dest: &ChanMan, payment_id: &mut u8) -> Option<(PaymentSecret, PaymentHash)> { let mut payment_hash; for _ in 0..256 { - payment_hash = PaymentHash(Sha256::hash(&[*payment_id; 1]).into_inner()); + payment_hash = PaymentHash(Sha256::hash(&[*payment_id; 1]).to_byte_array()); if let Ok(payment_secret) = dest.create_inbound_payment_for_hash(payment_hash, None, 3600, None) { return Some((payment_secret, payment_hash)); } @@ -441,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) @@ -482,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); } @@ -525,7 +548,7 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { features: $source.init_features(), networks: None, remote_network_address: None }, false).unwrap(); - $source.create_channel($dest.get_our_node_id(), 100_000, 42, 0, None).unwrap(); + $source.create_channel($dest.get_our_node_id(), 100_000, 42, 0, None, None).unwrap(); let open_channel = { let events = $source.get_and_clear_pending_msg_events(); assert_eq!(events.len(), 1); @@ -565,7 +588,7 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { let events = $source.get_and_clear_pending_events(); assert_eq!(events.len(), 1); if let events::Event::FundingGenerationReady { ref temporary_channel_id, ref channel_value_satoshis, ref output_script, .. } = events[0] { - let tx = Transaction { version: $chan_id, lock_time: PackedLockTime::ZERO, input: Vec::new(), output: vec![TxOut { + let tx = Transaction { version: $chan_id, lock_time: LockTime::ZERO, input: Vec::new(), output: vec![TxOut { value: *channel_value_satoshis, script_pubkey: output_script.clone(), }]}; funding_output = OutPoint { txid: tx.txid(), index: 0 }; @@ -955,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 @@ -977,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 { @@ -1258,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 9ed58dd949d..d07def30ff9 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -15,18 +15,20 @@ use bitcoin::blockdata::constants::genesis_block; use bitcoin::blockdata::transaction::{Transaction, TxOut}; -use bitcoin::blockdata::script::{Builder, Script}; +use bitcoin::blockdata::script::{Builder, ScriptBuf}; use bitcoin::blockdata::opcodes; -use bitcoin::blockdata::locktime::PackedLockTime; +use bitcoin::blockdata::locktime::absolute::LockTime; use bitcoin::consensus::encode::deserialize; use bitcoin::network::constants::Network; -use bitcoin::hashes::Hash as TraitImport; -use bitcoin::hashes::HashEngine as TraitImportEngine; +use bitcoin::hashes::hex::FromHex; +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}; @@ -35,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}; @@ -73,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) | @@ -80,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, @@ -115,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, @@ -141,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 { @@ -209,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, @@ -221,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; @@ -339,7 +356,9 @@ impl NodeSigner for KeyProvider { } impl SignerProvider for KeyProvider { - type Signer = TestChannelSigner; + type EcdsaSigner = TestChannelSigner; + #[cfg(taproot)] + type TaprootSigner = TestChannelSigner; fn generate_channel_keys_id(&self, inbound: bool, _channel_value_satoshis: u64, _user_channel_id: u128) -> [u8; 32] { let ctr = self.counter.fetch_add(1, Ordering::Relaxed) as u8; @@ -347,7 +366,7 @@ impl SignerProvider for KeyProvider { [ctr; 32] } - fn derive_channel_signer(&self, channel_value_satoshis: u64, channel_keys_id: [u8; 32]) -> Self::Signer { + fn derive_channel_signer(&self, channel_value_satoshis: u64, channel_keys_id: [u8; 32]) -> Self::EcdsaSigner { let secp_ctx = Secp256k1::signing_only(); let ctr = channel_keys_id[0]; let (inbound, state) = self.signer_state.borrow().get(&ctr).unwrap().clone(); @@ -391,11 +410,11 @@ impl SignerProvider for KeyProvider { )) } - fn get_destination_script(&self) -> Result { + fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Result { let secp_ctx = Secp256k1::signing_only(); - let channel_monitor_claim_key = SecretKey::from_slice(&hex::decode("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()[..]).unwrap(); + let channel_monitor_claim_key = SecretKey::from_slice(&>::from_hex("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()[..]).unwrap(); let our_channel_monitor_claim_key_hash = WPubkeyHash::hash(&PublicKey::from_secret_key(&secp_ctx, &channel_monitor_claim_key).serialize()); - Ok(Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0).push_slice(&our_channel_monitor_claim_key_hash[..]).into_script()) + Ok(Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0).push_slice(our_channel_monitor_claim_key_hash).into_script()) } fn get_shutdown_scriptpubkey(&self) -> Result { @@ -407,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), @@ -426,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)) { @@ -435,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]; @@ -450,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 { @@ -480,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 pending_funding_generation: Vec<(ChannelId, PublicKey, u64, Script)> = Vec::new(); - let mut pending_funding_signatures = HashMap::new(); + 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 = new_hash_map(); loop { match get_slice!(1)[0] { @@ -530,18 +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)); - let mut sha = Sha256::engine(); - sha.input(&payment_hash.0[..]); - payment_hash.0 = Sha256::from_engine(sha).into_inner(); + 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; @@ -549,21 +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)); - let mut sha = Sha256::engine(); - sha.input(&payment_hash.0[..]); - payment_hash.0 = Sha256::from_engine(sha).into_inner(); + 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]; @@ -571,7 +607,7 @@ pub fn do_test(data: &[u8], logger: &Arc) { let their_key = get_pubkey!(); let chan_value = slice_to_be24(get_slice!(3)) as u64; let push_msat_value = slice_to_be24(get_slice!(3)) as u64; - if channelmanager.create_channel(their_key, chan_value, push_msat_value, 0, None).is_err() { return; } + if channelmanager.create_channel(their_key, chan_value, push_msat_value, 0, None, None).is_err() { return; } }, 6 => { let mut channels = channelmanager.list_channels(); @@ -603,9 +639,7 @@ pub fn do_test(data: &[u8], logger: &Arc) { }, 16 => { let payment_preimage = PaymentPreimage(keys_manager.get_secure_random_bytes()); - let mut sha = Sha256::engine(); - sha.input(&payment_preimage.0[..]); - let payment_hash = PaymentHash(Sha256::from_engine(sha).into_inner()); + let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0[..]).to_byte_array()); // Note that this may fail - our hashes may collide and we'll end up trying to // double-register the same payment_hash. let _ = channelmanager.create_inbound_payment_for_hash(payment_hash, None, 1, None); @@ -616,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: PackedLockTime::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 => { @@ -658,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 { @@ -686,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(); @@ -702,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); + } + }, _ => {}, } } @@ -721,6 +814,7 @@ pub extern "C" fn full_stack_run(data: *const u8, datalen: usize) { #[cfg(test)] mod tests { + use bitcoin::hashes::hex::FromHex; use lightning::util::logger::{Logger, Record}; use std::collections::HashMap; use std::sync::{Arc, Mutex}; @@ -730,12 +824,18 @@ mod tests { pub lines: Mutex>, } impl Logger for TrackingLogger { - fn log(&self, record: &Record) { + fn log(&self, record: Record) { *self.lines.lock().unwrap().entry((record.module_path.to_string(), format!("{}", record.args))).or_insert(0) += 1; println!("{:<5} [{} : {}, {}] {}", record.level.to_string(), record.module_path, record.file, record.line, record.args); } } + 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 @@ -744,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 @@ -755,285 +852,465 @@ 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 - // - // 00fd00fd - 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); + // Two feerate requests to check the dust exposure on the initial commitment tx + ext_from_hex("00fd00fd", &mut test); + + // inbound read from peer id 1 of len 18 + ext_from_hex("030112", &mut test); + // 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); + + // Two feerate requests to check dust exposure + ext_from_hex("00fd00fd", &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); + // Two feerate requests to check dust exposure + ext_from_hex("00fd00fd", &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); + + // Two feerate requests to check dust exposure + ext_from_hex("00fd00fd", &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); + + // Two feerate requests to check dust exposure + ext_from_hex("00fd00fd", &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); + + // Two feerate requests to check dust exposure + ext_from_hex("00fd00fd", &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); + // Two feerate requests to check dust exposure + ext_from_hex("00fd00fd", &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(&::hex::decode("01000000000000000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000000020300320003000000000000000000000000000000000000000000000000000000000000000203000000000000000000000000000000030012001003000000000000000000000000000000030020001000021aaa0008aaaaaaaaaaaa9aaa030000000000000000000000000000000300120147030000000000000000000000000000000300fe00206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679000000000000c35000000000000000000000000000000162ffffffffffffffff00000000000002220000000000000000000000fd000601e3030000000000000000000000000000000000000000000000000000000000000001030000000000000000000000000000000000000000000000000000000000000002030000000000000000000000000000000000000000000000000000000000000003030000000000000000000000000000000000000000000000000000000000000004030059030000000000000000000000000000000000000000000000000000000000000005020900000000000000000000000000000000000000000000000000000000000000010000010210000300000000000000000000000000000000fd00fd0300120084030000000000000000000000000000000300940022ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb1819096793d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000210100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000c005e020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0150c3000000000000220020ae00000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c000003001200430300000000000000000000000000000003005300243d0000000000000000000000000000000000000000000000000000000000000002080000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000010301320003000000000000000000000000000000000000000000000000000000000000000703000000000000000000000000000000030142000302000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030112001001000000000000000000000000000000030120001000021aaa0008aaaaaaaaaaaa9aaa01000000000000000000000000000000050103020000000000000000000000000000000000000000000000000000000000000000c3500003e800fd0301120112010000000000000000000000000000000301ff00210000000000000000000000000000000000000000000000000000000000000e05000000000000016200000000004c4b4000000000000003e800000000000003e80000000203f000050300000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000002000300000000000000000000000000000000000000000000000000000000000003000300000000000000000000000000000000000000000000000000000000000004000300000000000000000000000000000000000000000000000000000000000005000266000000000000000000000000000003012300000000000000000000000000000000000000010000000000000000000000000000000a00fd00fd03011200620100000000000000000000000000000003017200233a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c0001000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000b03011200430100000000000000000000000000000003015300243a000000000000000000000000000000000000000000000000000000000000000267000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000020b00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd03011200640100000000000000000000000000000003017400843a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000660000000000000000000000000000000000000000000000000000000000000002640000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112004a0100000000000000000000000000000003015a00823a000000000000000000000000000000000000000000000000000000000000000000000000000000ff008888888888888888888888888888888888888888888888888888888888880100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a0000000000000000000000000000000000000000000000000000000000000067000000000000000000000000000000000000000000000000000000000000000265000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000010000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000020a000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000b00000000000000000000000000000000000000000000000000000000000000020d00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd03011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000002700000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112002c0100000000000000000000000000000003013c00833a00000000000000000000000000000000000000000000000000000000000000000000000000000100000100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a000000000000000000000000000000000000000000000000000000000000006500000000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000703001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000020c000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000032010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d00000000000000000000000000000000000000000000000000000000000000000000000000000200000000000b0838ff00000000000000000000000000000000000000000000000000000000000000000003f0000300000000000000000000000000000000000000000000000000000000000005551202030927c00401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff53000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200a4030000000000000000000000000000000300b400843d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007501000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006705000000000000000000000000000000000000000000000000000000000000060300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000d00000000000000000000000000000000000000000000000000000000000000020f00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd0c007d02000000013a000000000000000000000000000000000000000000000000000000000000000000000000000000800258020000000000002200204b0000000000000000000000000000000000000000000000000000000000000014c00000000000001600142800000000000000000000000000000000000000050000200c005e0200000001730000000000000000000000000000000000000000000000000000000000000000000000000000000001a701000000000000220020b200000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c000007").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 @@ -1047,4 +1324,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/msg_targets/gen_target.sh b/fuzz/src/msg_targets/gen_target.sh index d89df19d99a..cb24aa919db 100755 --- a/fuzz/src/msg_targets/gen_target.sh +++ b/fuzz/src/msg_targets/gen_target.sh @@ -58,3 +58,9 @@ GEN_TEST lightning::ln::msgs::TxSignatures test_msg_simple "" GEN_TEST lightning::ln::msgs::TxInitRbf test_msg_simple "" GEN_TEST lightning::ln::msgs::TxAckRbf test_msg_simple "" GEN_TEST lightning::ln::msgs::TxAbort test_msg_simple "" + +GEN_TEST lightning::ln::msgs::Stfu test_msg_simple "" + +GEN_TEST lightning::ln::msgs::Splice test_msg_simple "" +GEN_TEST lightning::ln::msgs::SpliceAck test_msg_simple "" +GEN_TEST lightning::ln::msgs::SpliceLocked test_msg_simple "" diff --git a/fuzz/src/msg_targets/mod.rs b/fuzz/src/msg_targets/mod.rs index 302dda440cb..837bee65dec 100644 --- a/fuzz/src/msg_targets/mod.rs +++ b/fuzz/src/msg_targets/mod.rs @@ -41,3 +41,7 @@ pub mod msg_tx_signatures; pub mod msg_tx_init_rbf; pub mod msg_tx_ack_rbf; pub mod msg_tx_abort; +pub mod msg_stfu; +pub mod msg_splice; +pub mod msg_splice_ack; +pub mod msg_splice_locked; diff --git a/fuzz/src/msg_targets/msg_splice.rs b/fuzz/src/msg_targets/msg_splice.rs new file mode 100644 index 00000000000..e6a18d2561c --- /dev/null +++ b/fuzz/src/msg_targets/msg_splice.rs @@ -0,0 +1,25 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This file is auto-generated by gen_target.sh based on msg_target_template.txt +// To modify it, modify msg_target_template.txt and run gen_target.sh instead. + +use crate::msg_targets::utils::VecWriter; +use crate::utils::test_logger; + +#[inline] +pub fn msg_splice_test(data: &[u8], _out: Out) { + test_msg_simple!(lightning::ln::msgs::Splice, data); +} + +#[no_mangle] +pub extern "C" fn msg_splice_run(data: *const u8, datalen: usize) { + let data = unsafe { std::slice::from_raw_parts(data, datalen) }; + test_msg_simple!(lightning::ln::msgs::Splice, data); +} diff --git a/fuzz/src/msg_targets/msg_splice_ack.rs b/fuzz/src/msg_targets/msg_splice_ack.rs new file mode 100644 index 00000000000..cc77c198bb7 --- /dev/null +++ b/fuzz/src/msg_targets/msg_splice_ack.rs @@ -0,0 +1,25 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This file is auto-generated by gen_target.sh based on msg_target_template.txt +// To modify it, modify msg_target_template.txt and run gen_target.sh instead. + +use crate::msg_targets::utils::VecWriter; +use crate::utils::test_logger; + +#[inline] +pub fn msg_splice_ack_test(data: &[u8], _out: Out) { + test_msg_simple!(lightning::ln::msgs::SpliceAck, data); +} + +#[no_mangle] +pub extern "C" fn msg_splice_ack_run(data: *const u8, datalen: usize) { + let data = unsafe { std::slice::from_raw_parts(data, datalen) }; + test_msg_simple!(lightning::ln::msgs::SpliceAck, data); +} diff --git a/fuzz/src/msg_targets/msg_splice_locked.rs b/fuzz/src/msg_targets/msg_splice_locked.rs new file mode 100644 index 00000000000..39e340fd40b --- /dev/null +++ b/fuzz/src/msg_targets/msg_splice_locked.rs @@ -0,0 +1,25 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This file is auto-generated by gen_target.sh based on msg_target_template.txt +// To modify it, modify msg_target_template.txt and run gen_target.sh instead. + +use crate::msg_targets::utils::VecWriter; +use crate::utils::test_logger; + +#[inline] +pub fn msg_splice_locked_test(data: &[u8], _out: Out) { + test_msg_simple!(lightning::ln::msgs::SpliceLocked, data); +} + +#[no_mangle] +pub extern "C" fn msg_splice_locked_run(data: *const u8, datalen: usize) { + let data = unsafe { std::slice::from_raw_parts(data, datalen) }; + test_msg_simple!(lightning::ln::msgs::SpliceLocked, data); +} diff --git a/fuzz/src/msg_targets/msg_stfu.rs b/fuzz/src/msg_targets/msg_stfu.rs new file mode 100644 index 00000000000..be2029194a7 --- /dev/null +++ b/fuzz/src/msg_targets/msg_stfu.rs @@ -0,0 +1,25 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This file is auto-generated by gen_target.sh based on msg_target_template.txt +// To modify it, modify msg_target_template.txt and run gen_target.sh instead. + +use crate::msg_targets::utils::VecWriter; +use crate::utils::test_logger; + +#[inline] +pub fn msg_stfu_test(data: &[u8], _out: Out) { + test_msg_simple!(lightning::ln::msgs::Stfu, data); +} + +#[no_mangle] +pub extern "C" fn msg_stfu_run(data: *const u8, datalen: usize) { + let data = unsafe { std::slice::from_raw_parts(data, datalen) }; + test_msg_simple!(lightning::ln::msgs::Stfu, data); +} 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 fcc8dc3cad2..91fcb9bf2d4 100644 --- a/fuzz/src/onion_message.rs +++ b/fuzz/src/onion_message.rs @@ -1,20 +1,24 @@ // Imports that need to be added manually use bitcoin::bech32::u5; -use bitcoin::blockdata::script::Script; -use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey}; +use bitcoin::blockdata::script::ScriptBuf; +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, EmptyNodeIdLookUp}; +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; @@ -32,16 +36,28 @@ pub fn do_test(data: &[u8], logger: &L) { node_secret: secret, counter: AtomicU64::new(0), }; + let node_id_lookup = EmptyNodeIdLookUp {}; let message_router = TestMessageRouter {}; let offers_msg_handler = TestOffersMessageHandler {}; let custom_msg_handler = TestCustomMessageHandler {}; let onion_messenger = OnionMessenger::new( - &keys_manager, &keys_manager, logger, &message_router, &offers_msg_handler, - &custom_msg_handler + &keys_manager, &keys_manager, logger, &node_id_lookup, &message_router, + &offers_msg_handler, &custom_msg_handler ); - let mut pk = [2; 33]; pk[1] = 0xff; - let peer_node_id_not_used = PublicKey::from_slice(&pk).unwrap(); - onion_messenger.handle_onion_message(&peer_node_id_not_used, &msg); + + let peer_node_id = { + let mut secret_bytes = [0; 32]; + secret_bytes[31] = 2; + let secret = SecretKey::from_slice(&secret_bytes).unwrap(); + PublicKey::from_secret_key(&Secp256k1::signing_only(), &secret) + }; + + let mut features = InitFeatures::empty(); + features.set_onion_messages_optional(); + let init = msgs::Init { features, networks: None, remote_network_address: None }; + + onion_messenger.peer_connected(&peer_node_id, &init, false).unwrap(); + onion_messenger.handle_onion_message(&peer_node_id, &msg); } } @@ -67,8 +83,15 @@ impl MessageRouter for TestMessageRouter { Ok(OnionMessagePath { intermediate_nodes: vec![], destination, + first_node_addresses: None, }) } + + fn create_blinded_paths( + &self, _recipient: PublicKey, _peers: Vec, _secp_ctx: &Secp256k1, + ) -> Result, ()> { + unreachable!() + } } struct TestOffersMessageHandler {} @@ -79,6 +102,7 @@ impl OffersMessageHandler for TestOffersMessageHandler { } } +#[derive(Debug)] struct TestCustomMessage {} const CUSTOM_MESSAGE_TYPE: u64 = 4242; @@ -177,23 +201,26 @@ impl NodeSigner for KeyProvider { } impl SignerProvider for KeyProvider { - type Signer = TestChannelSigner; + type EcdsaSigner = TestChannelSigner; + #[cfg(taproot)] + type TaprootSigner = TestChannelSigner; fn generate_channel_keys_id(&self, _inbound: bool, _channel_value_satoshis: u64, _user_channel_id: u128) -> [u8; 32] { unreachable!() } - fn derive_channel_signer(&self, _channel_value_satoshis: u64, _channel_keys_id: [u8; 32]) -> Self::Signer { + fn derive_channel_signer(&self, _channel_value_satoshis: u64, _channel_keys_id: [u8; 32]) -> Self::EcdsaSigner { unreachable!() } fn read_chan_signer(&self, _data: &[u8]) -> Result { unreachable!() } - fn get_destination_script(&self) -> Result { unreachable!() } + fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Result { unreachable!() } fn get_shutdown_scriptpubkey(&self) -> Result { unreachable!() } } #[cfg(test)] mod tests { + use bitcoin::hashes::hex::FromHex; use lightning::util::logger::{Logger, Record}; use std::collections::HashMap; use std::sync::Mutex; @@ -203,7 +230,7 @@ mod tests { pub lines: Mutex>, } impl Logger for TrackingLogger { - fn log(&self, record: &Record) { + fn log(&self, record: Record) { *self.lines.lock().unwrap().entry((record.module_path.to_string(), format!("{}", record.args))).or_insert(0) += 1; println!("{:<5} [{} : {}, {}] {}", record.level.to_string(), record.module_path, record.file, record.line, record.args); } @@ -211,25 +238,171 @@ mod tests { #[test] fn test_no_onion_message_breakage() { - let two_unblinded_hops_om = "020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000000000000000000000000000000000000000000000000000e0135043304210202020202020202020202020202020202020202020202020202020202020202026d000000000000000000000000000000eb0000000000000000000000000000000000000000000000000000000000000036041096000000000000000000000000000000fd1092202a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000"; + let one_hop_om = "\ + 020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000\ + 000000000000000000000000000000000000000000000000e01ae0276020000000000000000000000000000\ + 000000000000000000000000000000000002020000000000000000000000000000000000000000000000000\ + 000000000000e0101022a0000000000000000000000000000014551231950b75fc4402da1732fc9bebf0010\ + 9500000000000000000000000000000004106d000000000000000000000000000000fd1092202a2a2a2a2a2\ + a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a0000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000005600000000000000000000000000000000000000000000\ + 000000000000000000"; + let logger = TrackingLogger { lines: Mutex::new(HashMap::new()) }; + super::do_test(&>::from_hex(one_hop_om).unwrap(), &logger); + { + let log_entries = logger.lines.lock().unwrap(); + assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), + "Received an onion message with path_id None and a reply_path: Custom(TestCustomMessage)" + .to_string())), Some(&1)); + assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), + "Constructing onion message when responding to Custom onion message with path_id None: TestCustomMessage".to_string())), Some(&1)); + assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), + "Buffered onion message when responding to Custom onion message with path_id None".to_string())), Some(&1)); + } + + let two_unblinded_hops_om = "\ + 020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000\ + 000000000000000000000000000000000000000000000000e01350433042102020202020202020202020202\ + 02020202020202020202020202020202020202026d000000000000000000000000000000eb0000000000000\ + 000000000000000000000000000000000000000000000000036041096000000000000000000000000000000\ + fd1092202a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000004800000000000000000000000000000000000000000000\ + 000000000000000000"; let logger = TrackingLogger { lines: Mutex::new(HashMap::new()) }; - super::do_test(&::hex::decode(two_unblinded_hops_om).unwrap(), &logger); + super::do_test(&>::from_hex(two_unblinded_hops_om).unwrap(), &logger); { let log_entries = logger.lines.lock().unwrap(); assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), "Forwarding an onion message to peer 020202020202020202020202020202020202020202020202020202020202020202".to_string())), Some(&1)); } - let two_unblinded_two_blinded_om = "020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000000000000000000000000000000000000000000000000000e0135043304210202020202020202020202020202020202020202020202020202020202020202026d0000000000000000000000000000009e0000000000000000000000000000000000000000000000000000000000000058045604210203030303030303030303030303030303030303030303030303030303030303020821020000000000000000000000000000000000000000000000000000000000000e0196000000000000000000000000000000e9000000000000000000000000000000000000000000000000000000000000003504330421020404040404040404040404040404040404040404040404040404040404040402ca00000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000003604103f000000000000000000000000000000fd1092202a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000"; + let two_unblinded_two_blinded_om = "\ + 020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000\ + 000000000000000000000000000000000000000000000000e01350433042102020202020202020202020202\ + 02020202020202020202020202020202020202026d0000000000000000000000000000009e0000000000000\ + 000000000000000000000000000000000000000000000000058045604210203030303030303030303030303\ + 030303030303030303030303030303030303020821020000000000000000000000000000000000000000000\ + 000000000000000000e0196000000000000000000000000000000e900000000000000000000000000000000\ + 000000000000000000000000000000350433042102040404040404040404040404040404040404040404040\ + 4040404040404040402ca000000000000000000000000000000420000000000000000000000000000000000\ + 00000000000000000000000000003604103f000000000000000000000000000000fd1092202a2a2a2a2a2a2\ + a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000004800000000000000000000000000000000000000000000\ + 000000000000000000"; let logger = TrackingLogger { lines: Mutex::new(HashMap::new()) }; - super::do_test(&::hex::decode(two_unblinded_two_blinded_om).unwrap(), &logger); + super::do_test(&>::from_hex(two_unblinded_two_blinded_om).unwrap(), &logger); { let log_entries = logger.lines.lock().unwrap(); assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), "Forwarding an onion message to peer 020202020202020202020202020202020202020202020202020202020202020202".to_string())), Some(&1)); } - let three_blinded_om = "020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000000000000000000000000000000000000000000000000000e0135043304210202020202020202020202020202020202020202020202020202020202020202026d000000000000000000000000000000b20000000000000000000000000000000000000000000000000000000000000035043304210203030303030303030303030303030303030303030303030303030303030303029600000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000003604104e000000000000000000000000000000fd1092202a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000"; + let three_blinded_om = "\ + 020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000\ + 000000000000000000000000000000000000000000000000e01350433042102020202020202020202020202\ + 02020202020202020202020202020202020202026d000000000000000000000000000000b20000000000000\ + 000000000000000000000000000000000000000000000000035043304210203030303030303030303030303\ + 030303030303030303030303030303030303029600000000000000000000000000000033000000000000000\ + 000000000000000000000000000000000000000000000003604104e000000000000000000000000000000fd\ + 1092202a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a00000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000004800000000000000000000000000000000000000000000\ + 000000000000000000"; let logger = TrackingLogger { lines: Mutex::new(HashMap::new()) }; - super::do_test(&::hex::decode(three_blinded_om).unwrap(), &logger); + super::do_test(&>::from_hex(three_blinded_om).unwrap(), &logger); { let log_entries = logger.lines.lock().unwrap(); assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), "Forwarding an onion message to peer 020202020202020202020202020202020202020202020202020202020202020202".to_string())), Some(&1)); diff --git a/fuzz/src/peer_crypt.rs b/fuzz/src/peer_crypt.rs index f6df392fcef..41d8c093606 100644 --- a/fuzz/src/peer_crypt.rs +++ b/fuzz/src/peer_crypt.rs @@ -7,7 +7,7 @@ // You may not use this file except in accordance with one or both of these // licenses. -use lightning::ln::peer_channel_encryptor::PeerChannelEncryptor; +use lightning::ln::peer_channel_encryptor::{PeerChannelEncryptor, MessageBuf}; use lightning::util::test_utils::TestNodeSigner; use bitcoin::secp256k1::{Secp256k1, PublicKey, SecretKey}; @@ -74,15 +74,17 @@ pub fn do_test(data: &[u8]) { assert!(crypter.is_ready_for_encryption()); crypter }; + let mut buf = [0; 65536 + 16]; loop { if get_slice!(1)[0] == 0 { - crypter.encrypt_buffer(get_slice!(slice_to_be16(get_slice!(2)))); + crypter.encrypt_buffer(MessageBuf::from_encoded(&get_slice!(slice_to_be16(get_slice!(2))))); } else { let len = match crypter.decrypt_length_header(get_slice!(16+2)) { Ok(len) => len, Err(_) => return, }; - match crypter.decrypt_message(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..afe028131c1 100644 --- a/fuzz/src/router.rs +++ b/fuzz/src/router.rs @@ -11,7 +11,7 @@ use bitcoin::blockdata::constants::ChainHash; use bitcoin::blockdata::script::Builder; use bitcoin::blockdata::transaction::TxOut; -use lightning::blinded_path::{BlindedHop, BlindedPath}; +use lightning::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use lightning::chain::transaction::OutPoint; use lightning::ln::ChannelId; use lightning::ln::channelmanager::{self, ChannelDetails, ChannelCounterparty}; @@ -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}; @@ -157,6 +157,7 @@ pub fn do_test(data: &[u8], out: Out) { msgs::DecodeError::ShortRead => panic!("We picked the length..."), msgs::DecodeError::Io(e) => panic!("{:?}", e), msgs::DecodeError::UnsupportedCompression => return, + msgs::DecodeError::DangerousValue => return, } } }} @@ -198,7 +199,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 +209,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 +244,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 +259,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 +280,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 +300,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 => { @@ -358,14 +363,14 @@ pub fn do_test(data: &[u8], out: Out) { }); } (payinfo, BlindedPath { - introduction_node_id: hop.src_node_id, + introduction_node: IntroductionNode::NodeId(hop.src_node_id), blinding_point: dummy_pk, blinded_hops, }) }).collect(); let mut features = Bolt12InvoiceFeatures::empty(); features.set_basic_mpp_optional(); - find_routes!(first_hops, vec![dummy_pk].iter(), |final_amt, _, _| { + 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/fuzz/src/utils/test_logger.rs b/fuzz/src/utils/test_logger.rs index f8c96f99bd1..5e5817e23f1 100644 --- a/fuzz/src/utils/test_logger.rs +++ b/fuzz/src/utils/test_logger.rs @@ -56,7 +56,7 @@ impl<'a, Out: Output> Write for LockedWriteAdapter<'a, Out> { } impl Logger for TestLogger { - fn log(&self, record: &Record) { + fn log(&self, record: Record) { write!(LockedWriteAdapter(&self.out), "{:<5} {} [{} : {}] {}\n", record.level.to_string(), self.id, record.module_path, record.line, record.args) .unwrap(); diff --git a/fuzz/src/utils/test_persister.rs b/fuzz/src/utils/test_persister.rs index 89de25aa5e6..f9a03d16178 100644 --- a/fuzz/src/utils/test_persister.rs +++ b/fuzz/src/utils/test_persister.rs @@ -17,4 +17,7 @@ impl chainmonitor::Persist for TestPersister { fn update_persisted_channel(&self, _funding_txo: OutPoint, _update: Option<&channelmonitor::ChannelMonitorUpdate>, _data: &channelmonitor::ChannelMonitor, _update_id: MonitorUpdateId) -> chain::ChannelMonitorUpdateStatus { self.update_ret.lock().unwrap().clone() } + + fn archive_persisted_channel(&self, _: OutPoint) { + } } diff --git a/fuzz/targets.h b/fuzz/targets.h index cad0ac4d822..841ed55cea3 100644 --- a/fuzz/targets.h +++ b/fuzz/targets.h @@ -57,3 +57,7 @@ void msg_tx_signatures_run(const unsigned char* data, size_t data_len); void msg_tx_init_rbf_run(const unsigned char* data, size_t data_len); void msg_tx_ack_rbf_run(const unsigned char* data, size_t data_len); void msg_tx_abort_run(const unsigned char* data, size_t data_len); +void msg_stfu_run(const unsigned char* data, size_t data_len); +void msg_splice_run(const unsigned char* data, size_t data_len); +void msg_splice_ack_run(const unsigned char* data, size_t data_len); +void msg_splice_locked_run(const unsigned char* data, size_t data_len); diff --git a/lightning-background-processor/Cargo.toml b/lightning-background-processor/Cargo.toml index 59f77ac5fa4..b6fe9c4bfe7 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.123" 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 @@ -21,12 +21,12 @@ no-std = ["bitcoin/no-std", "lightning/no-std", "lightning-rapid-gossip-sync/no- default = ["std"] [dependencies] -bitcoin = { version = "0.29.0", 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 } +bitcoin = { version = "0.30.2", default-features = false } +lightning = { version = "0.0.123", path = "../lightning", default-features = false } +lightning-rapid-gossip-sync = { version = "0.0.123", 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.123", path = "../lightning", features = ["_test_utils"] } +lightning-invoice = { version = "0.31.0", path = "../lightning-invoice" } +lightning-persister = { version = "0.0.123", path = "../lightning-persister" } diff --git a/lightning-background-processor/src/lib.rs b/lightning-background-processor/src/lib.rs index aa6d0b0615e..ad1056ea922 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))] @@ -25,15 +24,17 @@ extern crate lightning_rapid_gossip_sync; use lightning::chain; use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; 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::ln::channelmanager::ChannelManager; +use lightning::events::EventHandler; +#[cfg(any(feature = "std", feature = "futures"))] +use lightning::events::EventsProvider; + +use lightning::ln::channelmanager::AChannelManager; +use lightning::ln::msgs::OnionMessageHandler; use lightning::ln::peer_handler::APeerManager; use lightning::routing::gossip::{NetworkGraph, P2PGossipSync}; use lightning::routing::utxo::UtxoLookup; -use lightning::routing::router::Router; use lightning::routing::scoring::{ScoreUpdate, WriteableScore}; use lightning::util::logger::Logger; use lightning::util::persist::Persister; @@ -78,6 +79,8 @@ use alloc::vec::Vec; /// However, as long as [`ChannelMonitor`] backups are sound, no funds besides those used for /// unilateral chain closure fees are at risk. /// +/// [`ChannelManager`]: lightning::ln::channelmanager::ChannelManager +/// [`ChannelManager::timer_tick_occurred`]: lightning::ln::channelmanager::ChannelManager::timer_tick_occurred /// [`ChannelMonitor`]: lightning::chain::channelmonitor::ChannelMonitor /// [`Event`]: lightning::events::Event /// [`PeerManager::timer_tick_occurred`]: lightning::ln::peer_handler::PeerManager::timer_tick_occurred @@ -104,11 +107,16 @@ const PING_TIMER: u64 = 30; #[cfg(test)] const PING_TIMER: u64 = 1; +#[cfg(not(test))] +const ONION_MESSAGE_HANDLER_TIMER: u64 = 10; +#[cfg(test)] +const ONION_MESSAGE_HANDLER_TIMER: u64 = 1; + /// Prune the network graph of stale entries hourly. 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; @@ -239,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, } @@ -270,27 +278,31 @@ fn update_scorer<'a, S: 'static + Deref + Send + Sync, SC: 'a + Wri } macro_rules! define_run_body { - ($persister: ident, $chain_monitor: ident, $process_chain_monitor_events: expr, - $channel_manager: ident, $process_channel_manager_events: expr, - $gossip_sync: ident, $peer_manager: ident, $logger: ident, $scorer: ident, - $loop_exit_check: expr, $await: expr, $get_timer: expr, $timer_elapsed: expr, - $check_slow_await: expr) - => { { + ( + $persister: ident, $chain_monitor: ident, $process_chain_monitor_events: expr, + $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, $time_fetch: expr, + ) => { { log_trace!($logger, "Calling ChannelManager's timer_tick_occurred on startup"); - $channel_manager.timer_tick_occurred(); + $channel_manager.get_cm().timer_tick_occurred(); log_trace!($logger, "Rebroadcasting monitor's pending claims on startup"); $chain_monitor.rebroadcast_pending_claims(); let mut last_freshness_call = $get_timer(FRESHNESS_TIMER); + let mut last_onion_message_handler_call = $get_timer(ONION_MESSAGE_HANDLER_TIMER); let mut last_ping_call = $get_timer(PING_TIMER); let mut last_prune_call = $get_timer(FIRST_NETWORK_PRUNE_TIMER); 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; $process_chain_monitor_events; + $process_onion_message_handler_events; // Note that the PeerManager::process_events may block on ChannelManager's locks, // hence it comes last here. When the ChannelManager finishes whatever it's doing, @@ -324,16 +336,21 @@ macro_rules! define_run_body { break; } - if $channel_manager.get_and_clear_needs_persistence() { + if $channel_manager.get_cm().get_and_clear_needs_persistence() { log_trace!($logger, "Persisting ChannelManager..."); - $persister.persist_manager(&*$channel_manager)?; + $persister.persist_manager(&$channel_manager)?; log_trace!($logger, "Done persisting ChannelManager."); } if $timer_elapsed(&mut last_freshness_call, FRESHNESS_TIMER) { log_trace!($logger, "Calling ChannelManager's timer_tick_occurred"); - $channel_manager.timer_tick_occurred(); + $channel_manager.get_cm().timer_tick_occurred(); last_freshness_call = $get_timer(FRESHNESS_TIMER); } + if $timer_elapsed(&mut last_onion_message_handler_call, ONION_MESSAGE_HANDLER_TIMER) { + log_trace!($logger, "Calling OnionMessageHandler's timer_tick_occurred"); + $peer_manager.onion_message_handler().timer_tick_occurred(); + last_onion_message_handler_call = $get_timer(ONION_MESSAGE_HANDLER_TIMER); + } if await_slow { // On various platforms, we may be starved of CPU cycles for several reasons. // E.g. on iOS, if we've been in the background, we will be entirely paused. @@ -370,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."); } @@ -389,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) } @@ -409,7 +440,7 @@ macro_rules! define_run_body { // After we exit, ensure we persist the ChannelManager one final time - this avoids // some races where users quit while channel updates were in-flight, with // ChannelMonitor update(s) persisted without a corresponding ChannelManager update. - $persister.persist_manager(&*$channel_manager)?; + $persister.persist_manager(&$channel_manager)?; // Persist Scorer on exit if let Some(ref scorer) = $scorer { @@ -497,52 +528,75 @@ 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 { +/// # struct Logger {} +/// # impl lightning::util::logger::Logger for Logger { +/// # fn log(&self, _record: lightning::util::logger::Record) {} +/// # } +/// # struct Store {} +/// # impl lightning::util::persist::KVStore for Store { /// # fn read(&self, primary_namespace: &str, secondary_namespace: &str, key: &str) -> io::Result> { Ok(Vec::new()) } /// # fn write(&self, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: &[u8]) -> io::Result<()> { Ok(()) } /// # fn remove(&self, primary_namespace: &str, secondary_namespace: &str, key: &str, lazy: bool) -> io::Result<()> { Ok(()) } /// # fn list(&self, primary_namespace: &str, secondary_namespace: &str) -> io::Result> { Ok(Vec::new()) } /// # } -/// # struct MyEventHandler {} -/// # impl MyEventHandler { +/// # struct EventHandler {} +/// # impl EventHandler { /// # async fn handle_event(&self, _: lightning::events::Event) {} /// # } /// # #[derive(Eq, PartialEq, Clone, Hash)] -/// # struct MySocketDescriptor {} -/// # impl lightning::ln::peer_handler::SocketDescriptor for MySocketDescriptor { +/// # struct SocketDescriptor {} +/// # impl lightning::ln::peer_handler::SocketDescriptor for SocketDescriptor { /// # fn send_data(&mut self, _data: &[u8], _resume_read: bool) -> usize { 0 } /// # fn disconnect_socket(&mut self) {} /// # } -/// # type MyBroadcaster = dyn lightning::chain::chaininterface::BroadcasterInterface + Send + Sync; -/// # type MyFeeEstimator = dyn lightning::chain::chaininterface::FeeEstimator + Send + Sync; -/// # type MyNodeSigner = dyn lightning::sign::NodeSigner + Send + Sync; -/// # type MyUtxoLookup = dyn lightning::routing::utxo::UtxoLookup + Send + Sync; -/// # type MyFilter = dyn lightning::chain::Filter + Send + Sync; -/// # type MyLogger = dyn lightning::util::logger::Logger + Send + Sync; -/// # type MyChainMonitor = lightning::chain::chainmonitor::ChainMonitor, Arc, Arc, Arc, Arc>; -/// # type MyPeerManager = lightning::ln::peer_handler::SimpleArcPeerManager, MyLogger>; -/// # type MyNetworkGraph = lightning::routing::gossip::NetworkGraph>; -/// # type MyGossipSync = lightning::routing::gossip::P2PGossipSync, Arc, Arc>; -/// # type MyChannelManager = lightning::ln::channelmanager::SimpleArcChannelManager; -/// # type MyScorer = RwLock, Arc>>; -/// -/// # async fn setup_background_processing(my_persister: Arc, my_event_handler: Arc, my_chain_monitor: Arc, my_channel_manager: Arc, my_gossip_sync: Arc, my_logger: Arc, my_scorer: Arc, my_peer_manager: Arc) { -/// let background_persister = Arc::clone(&my_persister); -/// let background_event_handler = Arc::clone(&my_event_handler); -/// let background_chain_mon = Arc::clone(&my_chain_monitor); -/// let background_chan_man = Arc::clone(&my_channel_manager); -/// let background_gossip_sync = GossipSync::p2p(Arc::clone(&my_gossip_sync)); -/// let background_peer_man = Arc::clone(&my_peer_manager); -/// let background_logger = Arc::clone(&my_logger); -/// let background_scorer = Arc::clone(&my_scorer); +/// # type ChainMonitor = lightning::chain::chainmonitor::ChainMonitor, Arc, Arc, Arc, Arc>; +/// # type NetworkGraph = lightning::routing::gossip::NetworkGraph>; +/// # type P2PGossipSync
    = lightning::routing::gossip::P2PGossipSync, Arc
      , Arc>; +/// # type ChannelManager = lightning::ln::channelmanager::SimpleArcChannelManager, B, FE, Logger>; +/// # type Scorer = RwLock, Arc>>; +/// # type PeerManager = lightning::ln::peer_handler::SimpleArcPeerManager, B, FE, Arc
        , Logger>; +/// # +/// # struct Node< +/// # B: lightning::chain::chaininterface::BroadcasterInterface + Send + Sync + 'static, +/// # F: lightning::chain::Filter + Send + Sync + 'static, +/// # FE: lightning::chain::chaininterface::FeeEstimator + Send + Sync + 'static, +/// # UL: lightning::routing::utxo::UtxoLookup + Send + Sync + 'static, +/// # > { +/// # peer_manager: Arc>, +/// # event_handler: Arc, +/// # channel_manager: Arc>, +/// # chain_monitor: Arc>, +/// # gossip_sync: Arc>, +/// # persister: Arc, +/// # logger: Arc, +/// # scorer: Arc, +/// # } +/// # +/// # async fn setup_background_processing< +/// # B: lightning::chain::chaininterface::BroadcasterInterface + Send + Sync + 'static, +/// # F: lightning::chain::Filter + Send + Sync + 'static, +/// # FE: lightning::chain::chaininterface::FeeEstimator + Send + Sync + 'static, +/// # UL: lightning::routing::utxo::UtxoLookup + Send + Sync + 'static, +/// # >(node: Node) { +/// let background_persister = Arc::clone(&node.persister); +/// let background_event_handler = Arc::clone(&node.event_handler); +/// let background_chain_mon = Arc::clone(&node.chain_monitor); +/// let background_chan_man = Arc::clone(&node.channel_manager); +/// let background_gossip_sync = GossipSync::p2p(Arc::clone(&node.gossip_sync)); +/// let background_peer_man = Arc::clone(&node.peer_manager); +/// let background_logger = Arc::clone(&node.logger); +/// let background_scorer = Arc::clone(&node.scorer); /// /// // Setup the sleeper. /// let (stop_sender, stop_receiver) = tokio::sync::watch::channel(()); @@ -571,9 +625,10 @@ use core::task; /// Some(background_scorer), /// sleeper, /// mobile_interruptable_platform, -/// ) -/// .await -/// .expect("Failed to process events"); +/// || Some(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap()) +/// ) +/// .await +/// .expect("Failed to process events"); /// }); /// /// // Stop the background processing. @@ -586,47 +641,39 @@ pub async fn process_events_async< 'a, UL: 'static + Deref + Send + Sync, CF: 'static + Deref + Send + Sync, - CW: 'static + Deref + Send + Sync, T: 'static + Deref + Send + Sync, - ES: 'static + Deref + Send + Sync, - NS: 'static + Deref + Send + Sync, - SP: 'static + Deref + Send + Sync, F: 'static + Deref + Send + Sync, - R: 'static + Deref + Send + Sync, G: 'static + Deref> + Send + Sync, L: 'static + Deref + Send + Sync, P: 'static + Deref + Send + Sync, EventHandlerFuture: core::future::Future, EventHandler: Fn(Event) -> EventHandlerFuture, PS: 'static + Deref + Send, - M: 'static + Deref::Signer, CF, T, F, L, P>> + Send + Sync, - CM: 'static + Deref> + Send + Sync, + M: 'static + Deref::Signer, CF, T, F, L, P>> + Send + Sync, + CM: 'static + Deref + Send + Sync, PGS: 'static + Deref> + Send + Sync, RGS: 'static + Deref> + Send, - APM: APeerManager + Send + Sync, - PM: 'static + Deref + Send + Sync, + PM: 'static + Deref + Send + Sync, 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, CF::Target: 'static + chain::Filter, - CW::Target: 'static + chain::Watch<::Signer>, 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, - P::Target: 'static + Persist<::Signer>, - PS::Target: 'static + Persister<'a, CW, T, ES, NS, SP, F, R, L, SC>, + P::Target: 'static + Persist<::Signer>, + PS::Target: 'static + Persister<'a, CM, L, SC>, + CM::Target: AChannelManager + Send + Sync, + PM::Target: APeerManager + Send + Sync, { let mut should_break = false; let async_event_handler = |event| { @@ -635,27 +682,32 @@ 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) + } } } } event_handler(event).await; } }; - define_run_body!(persister, - chain_monitor, chain_monitor.process_pending_events_async(async_event_handler).await, - channel_manager, channel_manager.process_pending_events_async(async_event_handler).await, - gossip_sync, peer_manager, logger, scorer, should_break, { + define_run_body!( + persister, chain_monitor, + chain_monitor.process_pending_events_async(async_event_handler).await, + channel_manager, channel_manager.get_cm().process_pending_events_async(async_event_handler).await, + peer_manager, process_onion_message_handler_events_async(&peer_manager, async_event_handler).await, + gossip_sync, logger, scorer, should_break, { let fut = Selector { - a: channel_manager.get_event_or_persistence_needed_future(), + a: channel_manager.get_cm().get_event_or_persistence_needed_future(), b: chain_monitor.get_update_future(), c: sleeper(if mobile_interruptable_platform { Duration::from_millis(100) } else { Duration::from_secs(FASTEST_TIMER) }), }; @@ -673,7 +725,27 @@ where task::Poll::Ready(exit) => { should_break = exit; true }, task::Poll::Pending => false, } - }, mobile_interruptable_platform) + }, mobile_interruptable_platform, fetch_time, + ) +} + +#[cfg(feature = "futures")] +async fn process_onion_message_handler_events_async< + EventHandlerFuture: core::future::Future, + EventHandler: Fn(Event) -> EventHandlerFuture, + PM: 'static + Deref + Send + Sync, +>( + peer_manager: &PM, handler: EventHandler +) +where + PM::Target: APeerManager + Send + Sync, +{ + let events = core::cell::RefCell::new(Vec::new()); + peer_manager.onion_message_handler().process_pending_events(&|e| events.borrow_mut().push(e)); + + for event in events.into_inner() { + handler(event).await + } } #[cfg(feature = "std")] @@ -726,24 +798,18 @@ impl BackgroundProcessor { 'a, UL: 'static + Deref + Send + Sync, CF: 'static + Deref + Send + Sync, - CW: 'static + Deref + Send + Sync, T: 'static + Deref + Send + Sync, - ES: 'static + Deref + Send + Sync, - NS: 'static + Deref + Send + Sync, - SP: 'static + Deref + Send + Sync, F: 'static + Deref + Send + Sync, - R: 'static + Deref + Send + Sync, G: 'static + Deref> + Send + Sync, L: 'static + Deref + Send + Sync, P: 'static + Deref + Send + Sync, EH: 'static + EventHandler + Send, PS: 'static + Deref + Send, - M: 'static + Deref::Signer, CF, T, F, L, P>> + Send + Sync, - CM: 'static + Deref> + Send + Sync, + M: 'static + Deref::Signer, CF, T, F, L, P>> + Send + Sync, + CM: 'static + Deref + Send + Sync, PGS: 'static + Deref> + Send + Sync, RGS: 'static + Deref> + Send, - APM: APeerManager + Send + Sync, - PM: 'static + Deref + Send + Sync, + PM: 'static + Deref + Send + Sync, S: 'static + Deref + Send + Sync, SC: for <'b> WriteableScore<'b>, >( @@ -753,16 +819,13 @@ impl BackgroundProcessor { where UL::Target: 'static + UtxoLookup, CF::Target: 'static + chain::Filter, - CW::Target: 'static + chain::Watch<::Signer>, 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, - P::Target: 'static + Persist<::Signer>, - PS::Target: 'static + Persister<'a, CW, T, ES, NS, SP, F, R, L, SC>, + P::Target: 'static + Persist<::Signer>, + PS::Target: 'static + Persister<'a, CM, L, SC>, + CM::Target: AChannelManager + Send + Sync, + PM::Target: APeerManager + Send + Sync, { let stop_thread = Arc::new(AtomicBool::new(false)); let stop_thread_clone = stop_thread.clone(); @@ -773,7 +836,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) @@ -782,14 +848,23 @@ impl BackgroundProcessor { } event_handler.handle_event(event); }; - define_run_body!(persister, chain_monitor, chain_monitor.process_pending_events(&event_handler), - channel_manager, channel_manager.process_pending_events(&event_handler), - gossip_sync, peer_manager, logger, scorer, stop_thread.load(Ordering::Acquire), + define_run_body!( + persister, chain_monitor, chain_monitor.process_pending_events(&event_handler), + channel_manager, channel_manager.get_cm().process_pending_events(&event_handler), + peer_manager, + 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_cm().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) } } @@ -844,27 +919,29 @@ impl Drop for BackgroundProcessor { #[cfg(all(feature = "std", test))] mod tests { + use bitcoin::{ScriptBuf, Txid}; use bitcoin::blockdata::constants::{genesis_block, ChainHash}; - use bitcoin::blockdata::locktime::PackedLockTime; + use bitcoin::blockdata::locktime::absolute::LockTime; use bitcoin::blockdata::transaction::{Transaction, TxOut}; + use bitcoin::hashes::Hash; use bitcoin::network::constants::Network; use bitcoin::secp256k1::{SecretKey, PublicKey, Secp256k1}; - use lightning::chain::{BestBlock, Confirm, chainmonitor}; + use lightning::chain::{BestBlock, Confirm, chainmonitor, Filter}; use lightning::chain::channelmonitor::ANTI_REORG_DELAY; - use lightning::sign::{InMemorySigner, KeysManager}; + use lightning::sign::{InMemorySigner, KeysManager, ChangeDestinationSource}; 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::types::{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::router::{DefaultRouter, Path, RouteHop}; + 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; use lightning::util::ser::Writeable; use lightning::util::test_utils; @@ -872,6 +949,7 @@ mod tests { CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE, CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE, CHANNEL_MANAGER_PERSISTENCE_KEY, NETWORK_GRAPH_PERSISTENCE_PRIMARY_NAMESPACE, NETWORK_GRAPH_PERSISTENCE_SECONDARY_NAMESPACE, NETWORK_GRAPH_PERSISTENCE_KEY, SCORER_PERSISTENCE_PRIMARY_NAMESPACE, SCORER_PERSISTENCE_SECONDARY_NAMESPACE, SCORER_PERSISTENCE_KEY}; + use lightning::util::sweep::{OutputSweeper, OutputSpendStatus}; use lightning_persister::fs_store::FilesystemStore; use std::collections::VecDeque; use std::{fs, env}; @@ -910,6 +988,7 @@ mod tests { Arc>>, Arc, + Arc, Arc>, (), TestScorer> @@ -933,6 +1012,9 @@ mod tests { logger: Arc, best_block: BestBlock, scorer: Arc>, + sweeper: Arc, Arc, + Arc, Arc, Arc, + Arc, Arc>>, } impl Node { @@ -1071,12 +1153,12 @@ mod tests { impl ScoreLookUp for TestScorer { type ScoreParams = (); fn channel_penalty_msat( - &self, _short_channel_id: u64, _source: &NodeId, _target: &NodeId, _usage: ChannelUsage, _score_params: &Self::ScoreParams + &self, _candidate: &CandidateRouteHop, _usage: ChannelUsage, _score_params: &Self::ScoreParams ) -> u64 { unimplemented!(); } } impl ScoreUpdate for TestScorer { - fn payment_path_failed(&mut self, actual_path: &Path, actual_short_channel_id: u64) { + 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 } => { @@ -1096,7 +1178,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, .. } => { @@ -1115,7 +1197,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, .. } => { @@ -1133,7 +1215,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, .. } => { @@ -1151,6 +1233,7 @@ mod tests { } } } + fn time_passed(&mut self, _: Duration) {} } #[cfg(c_bindings)] @@ -1170,6 +1253,14 @@ mod tests { } } + struct TestWallet {} + + impl ChangeDestinationSource for TestWallet { + fn get_change_destination_script(&self) -> Result { + Ok(ScriptBuf::new()) + } + } + fn get_full_filepath(filepath: String, filename: String) -> String { let mut path = PathBuf::from(filepath); path.push(filename); @@ -1188,8 +1279,10 @@ 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); @@ -1198,6 +1291,9 @@ mod tests { let best_block = BestBlock::from_network(network); let params = ChainParameters { network, best_block }; let manager = Arc::new(ChannelManager::new(fee_estimator.clone(), chain_monitor.clone(), tx_broadcaster.clone(), router.clone(), logger.clone(), keys_manager.clone(), keys_manager.clone(), keys_manager.clone(), UserConfig::default(), params, genesis_block.header.time)); + let wallet = Arc::new(TestWallet {}); + let sweeper = Arc::new(OutputSweeper::new(best_block, Arc::clone(&tx_broadcaster), Arc::clone(&fee_estimator), + None::>, Arc::clone(&keys_manager), wallet, Arc::clone(&kv_store), Arc::clone(&logger))); let p2p_gossip_sync = Arc::new(P2PGossipSync::new(network_graph.clone(), Some(chain_source.clone()), logger.clone())); let rapid_gossip_sync = Arc::new(RapidGossipSync::new(network_graph.clone(), logger.clone())); let msg_handler = MessageHandler { @@ -1206,7 +1302,7 @@ mod tests { onion_message_handler: IgnoringMessageHandler{}, custom_message_handler: IgnoringMessageHandler{} }; let peer_manager = Arc::new(PeerManager::new(msg_handler, 0, &seed, logger.clone(), keys_manager.clone())); - let node = Node { node: manager, p2p_gossip_sync, rapid_gossip_sync, peer_manager, chain_monitor, kv_store, tx_broadcaster, network_graph, logger, best_block, scorer }; + let node = Node { node: manager, p2p_gossip_sync, rapid_gossip_sync, peer_manager, chain_monitor, kv_store, tx_broadcaster, network_graph, logger, best_block, scorer, sweeper }; nodes.push(node); } @@ -1241,7 +1337,7 @@ mod tests { macro_rules! begin_open_channel { ($node_a: expr, $node_b: expr, $channel_value: expr) => {{ - $node_a.node.create_channel($node_b.node.get_our_node_id(), $channel_value, 100, 42, None).unwrap(); + $node_a.node.create_channel($node_b.node.get_our_node_id(), $channel_value, 100, 42, None, None).unwrap(); $node_b.node.handle_open_channel(&$node_a.node.get_our_node_id(), &get_event_msg!($node_a, MessageSendEvent::SendOpenChannel, $node_b.node.get_our_node_id())); $node_a.node.handle_accept_channel(&$node_b.node.get_our_node_id(), &get_event_msg!($node_b, MessageSendEvent::SendAcceptChannel, $node_a.node.get_our_node_id())); }} @@ -1254,7 +1350,7 @@ mod tests { assert_eq!(channel_value_satoshis, $channel_value); assert_eq!(user_channel_id, 42); - let tx = Transaction { version: 1 as i32, lock_time: PackedLockTime(0), input: Vec::new(), output: vec![TxOut { + let tx = Transaction { version: 1 as i32, lock_time: LockTime::ZERO, input: Vec::new(), output: vec![TxOut { value: channel_value_satoshis, script_pubkey: output_script.clone(), }]}; (temporary_channel_id, tx) @@ -1266,8 +1362,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); @@ -1275,15 +1371,40 @@ mod tests { 1 => { node.node.transactions_confirmed(&header, &txdata, height); node.chain_monitor.transactions_confirmed(&header, &txdata, height); + node.sweeper.transactions_confirmed(&header, &txdata, height); }, x if x == depth => { + // We need the TestBroadcaster to know about the new height so that it doesn't think + // we're violating the time lock requirements of transactions broadcasted at that + // point. + node.tx_broadcaster.blocks.lock().unwrap().push((genesis_block(Network::Bitcoin), height)); node.node.best_block_updated(&header, height); node.chain_monitor.best_block_updated(&header, height); + node.sweeper.best_block_updated(&header, height); }, _ => {}, } } } + + fn advance_chain(node: &mut Node, num_blocks: u32) { + for i in 1..=num_blocks { + let prev_blockhash = node.best_block.block_hash; + let height = node.best_block.height + 1; + let header = create_dummy_header(prev_blockhash, height); + node.best_block = BestBlock::new(header.block_hash(), height); + if i == num_blocks { + // We need the TestBroadcaster to know about the new height so that it doesn't think + // we're violating the time lock requirements of transactions broadcasted at that + // point. + node.tx_broadcaster.blocks.lock().unwrap().push((genesis_block(Network::Bitcoin), height)); + node.node.best_block_updated(&header, height); + node.chain_monitor.best_block_updated(&header, height); + node.sweeper.best_block_updated(&header, height); + } + } + } + fn confirm_transaction(node: &mut Node, tx: &Transaction) { confirm_transaction_depth(node, tx, ANTI_REORG_DELAY); } @@ -1339,7 +1460,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()); @@ -1362,9 +1483,11 @@ mod tests { #[test] fn test_timer_tick_called() { - // Test that `ChannelManager::timer_tick_occurred` is called every `FRESHNESS_TIMER`, - // `ChainMonitor::rebroadcast_pending_claims` is called every `REBROADCAST_TIMER`, and - // `PeerManager::timer_tick_occurred` every `PING_TIMER`. + // Test that: + // - `ChannelManager::timer_tick_occurred` is called every `FRESHNESS_TIMER`, + // - `ChainMonitor::rebroadcast_pending_claims` is called every `REBROADCAST_TIMER`, + // - `PeerManager::timer_tick_occurred` is called every `PING_TIMER`, and + // - `OnionMessageHandler::timer_tick_occurred` is called every `ONION_MESSAGE_HANDLER_TIMER`. let (_, nodes) = create_nodes(1, "test_timer_tick_called"); let data_dir = nodes[0].kv_store.get_data_dir(); let persister = Arc::new(Persister::new(data_dir)); @@ -1375,9 +1498,11 @@ mod tests { let desired_log_1 = "Calling ChannelManager's timer_tick_occurred".to_string(); let desired_log_2 = "Calling PeerManager's timer_tick_occurred".to_string(); let desired_log_3 = "Rebroadcasting monitor's pending claims".to_string(); - if log_entries.get(&("lightning_background_processor".to_string(), desired_log_1)).is_some() && - log_entries.get(&("lightning_background_processor".to_string(), desired_log_2)).is_some() && - log_entries.get(&("lightning_background_processor".to_string(), desired_log_3)).is_some() { + let desired_log_4 = "Calling OnionMessageHandler's timer_tick_occurred".to_string(); + if log_entries.get(&("lightning_background_processor", desired_log_1)).is_some() && + log_entries.get(&("lightning_background_processor", desired_log_2)).is_some() && + log_entries.get(&("lightning_background_processor", desired_log_3)).is_some() && + log_entries.get(&("lightning_background_processor", desired_log_4)).is_some() { break } } @@ -1424,7 +1549,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"), @@ -1511,6 +1636,9 @@ mod tests { let _as_channel_update = get_event_msg!(nodes[0], MessageSendEvent::SendChannelUpdate, nodes[1].node.get_our_node_id()); nodes[1].node.handle_channel_ready(&nodes[0].node.get_our_node_id(), &as_funding); let _bs_channel_update = get_event_msg!(nodes[1], MessageSendEvent::SendChannelUpdate, nodes[0].node.get_our_node_id()); + let broadcast_funding = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().pop().unwrap(); + assert_eq!(broadcast_funding.txid(), funding_tx.txid()); + assert!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().is_empty()); if !std::thread::panicking() { bg_processor.stop().unwrap(); @@ -1536,10 +1664,95 @@ mod tests { .recv_timeout(Duration::from_secs(EVENT_DEADLINE)) .expect("Events not handled within deadline"); match event { - Event::SpendableOutputs { .. } => {}, + Event::SpendableOutputs { outputs, channel_id } => { + nodes[0].sweeper.track_spendable_outputs(outputs, channel_id, false, Some(153)).unwrap(); + }, _ => panic!("Unexpected event: {:?}", event), } + // Check we don't generate an initial sweeping tx until we reach the required height. + assert_eq!(nodes[0].sweeper.tracked_spendable_outputs().len(), 1); + let tracked_output = nodes[0].sweeper.tracked_spendable_outputs().first().unwrap().clone(); + if let Some(sweep_tx_0) = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().pop() { + assert!(!tracked_output.is_spent_in(&sweep_tx_0)); + match tracked_output.status { + OutputSpendStatus::PendingInitialBroadcast { delayed_until_height } => { + assert_eq!(delayed_until_height, Some(153)); + } + _ => panic!("Unexpected status"), + } + } + + advance_chain(&mut nodes[0], 3); + + // Check we generate an initial sweeping tx. + assert_eq!(nodes[0].sweeper.tracked_spendable_outputs().len(), 1); + let tracked_output = nodes[0].sweeper.tracked_spendable_outputs().first().unwrap().clone(); + let sweep_tx_0 = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().pop().unwrap(); + match tracked_output.status { + OutputSpendStatus::PendingFirstConfirmation { latest_spending_tx, .. } => { + assert_eq!(sweep_tx_0.txid(), latest_spending_tx.txid()); + } + _ => panic!("Unexpected status"), + } + + // Check we regenerate and rebroadcast the sweeping tx each block. + advance_chain(&mut nodes[0], 1); + assert_eq!(nodes[0].sweeper.tracked_spendable_outputs().len(), 1); + let tracked_output = nodes[0].sweeper.tracked_spendable_outputs().first().unwrap().clone(); + let sweep_tx_1 = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().pop().unwrap(); + match tracked_output.status { + OutputSpendStatus::PendingFirstConfirmation { latest_spending_tx, .. } => { + assert_eq!(sweep_tx_1.txid(), latest_spending_tx.txid()); + } + _ => panic!("Unexpected status"), + } + assert_ne!(sweep_tx_0, sweep_tx_1); + + advance_chain(&mut nodes[0], 1); + assert_eq!(nodes[0].sweeper.tracked_spendable_outputs().len(), 1); + let tracked_output = nodes[0].sweeper.tracked_spendable_outputs().first().unwrap().clone(); + let sweep_tx_2 = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().pop().unwrap(); + match tracked_output.status { + OutputSpendStatus::PendingFirstConfirmation { latest_spending_tx, .. } => { + assert_eq!(sweep_tx_2.txid(), latest_spending_tx.txid()); + } + _ => panic!("Unexpected status"), + } + assert_ne!(sweep_tx_0, sweep_tx_2); + assert_ne!(sweep_tx_1, sweep_tx_2); + + // Check we still track the spendable outputs up to ANTI_REORG_DELAY confirmations. + confirm_transaction_depth(&mut nodes[0], &sweep_tx_2, 5); + assert_eq!(nodes[0].sweeper.tracked_spendable_outputs().len(), 1); + let tracked_output = nodes[0].sweeper.tracked_spendable_outputs().first().unwrap().clone(); + match tracked_output.status { + OutputSpendStatus::PendingThresholdConfirmations { latest_spending_tx, .. } => { + assert_eq!(sweep_tx_2.txid(), latest_spending_tx.txid()); + } + _ => panic!("Unexpected status"), + } + + // Check we still see the transaction as confirmed if we unconfirm any untracked + // transaction. (We previously had a bug that would mark tracked transactions as + // unconfirmed if any transaction at an unknown block height would be unconfirmed.) + let unconf_txid = Txid::from_slice(&[0; 32]).unwrap(); + nodes[0].sweeper.transaction_unconfirmed(&unconf_txid); + + assert_eq!(nodes[0].sweeper.tracked_spendable_outputs().len(), 1); + let tracked_output = nodes[0].sweeper.tracked_spendable_outputs().first().unwrap().clone(); + match tracked_output.status { + OutputSpendStatus::PendingThresholdConfirmations { latest_spending_tx, .. } => { + assert_eq!(sweep_tx_2.txid(), latest_spending_tx.txid()); + } + _ => panic!("Unexpected status"), + } + + // Check we stop tracking the spendable outputs when one of the txs reaches + // ANTI_REORG_DELAY confirmations. + confirm_transaction_depth(&mut nodes[0], &sweep_tx_0, ANTI_REORG_DELAY); + assert_eq!(nodes[0].sweeper.tracked_spendable_outputs().len(), 0); + if !std::thread::panicking() { bg_processor.stop().unwrap(); } @@ -1555,8 +1768,8 @@ mod tests { loop { let log_entries = nodes[0].logger.lines.lock().unwrap(); - let expected_log = "Persisting scorer".to_string(); - if log_entries.get(&("lightning_background_processor".to_string(), expected_log)).is_some() { + let expected_log = "Calling time_passed and persisting scorer".to_string(); + if log_entries.get(&("lightning_background_processor", expected_log)).is_some() { break } } @@ -1580,7 +1793,7 @@ mod tests { $sleep; let log_entries = $nodes[0].logger.lines.lock().unwrap(); let loop_counter = "Calling ChannelManager's timer_tick_occurred".to_string(); - if *log_entries.get(&("lightning_background_processor".to_string(), loop_counter)) + if *log_entries.get(&("lightning_background_processor", loop_counter)) .unwrap_or(&0) > 1 { // Wait until the loop has gone around at least twice. @@ -1654,7 +1867,7 @@ mod tests { _ = exit_receiver.changed() => true, } }) - }, false, + }, false, || Some(Duration::from_secs(1696300000)), ); let t1 = tokio::spawn(bp_future); @@ -1792,7 +2005,7 @@ mod tests { let log_entries = nodes[0].logger.lines.lock().unwrap(); let expected_log = "Persisting scorer after update".to_string(); - assert_eq!(*log_entries.get(&("lightning_background_processor".to_string(), expected_log)).unwrap(), 5); + assert_eq!(*log_entries.get(&("lightning_background_processor", expected_log)).unwrap(), 5); } #[tokio::test] @@ -1829,7 +2042,7 @@ mod tests { _ = exit_receiver.changed() => true, } }) - }, false, + }, false, || Some(Duration::ZERO), ); let t1 = tokio::spawn(bp_future); let t2 = tokio::spawn(async move { @@ -1838,7 +2051,7 @@ mod tests { let log_entries = nodes[0].logger.lines.lock().unwrap(); let expected_log = "Persisting scorer after update".to_string(); - assert_eq!(*log_entries.get(&("lightning_background_processor".to_string(), expected_log)).unwrap(), 5); + assert_eq!(*log_entries.get(&("lightning_background_processor", expected_log)).unwrap(), 5); }); let (r1, r2) = tokio::join!(t1, t2); diff --git a/lightning-block-sync/Cargo.toml b/lightning-block-sync/Cargo.toml index d9bb4ee7b1e..e9d5c569766 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.123" 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 @@ -18,12 +18,13 @@ rest-client = [ "serde_json", "chunked_transfer" ] rpc-client = [ "serde_json", "chunked_transfer" ] [dependencies] -bitcoin = "0.29.0" -lightning = { version = "0.0.118", path = "../lightning" } -tokio = { version = "1.0", features = [ "io-util", "net", "time" ], optional = true } +bitcoin = "0.30.2" +hex = { package = "hex-conservative", version = "0.1.1", default-features = false } +lightning = { version = "0.0.123", 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.123", 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 bf9e9577619..ed811d2cc0c 100644 --- a/lightning-block-sync/src/convert.rs +++ b/lightning-block-sync/src/convert.rs @@ -1,8 +1,8 @@ use crate::http::{BinaryResponse, JsonResponse}; -use crate::utils::hex_to_uint256; +use crate::utils::hex_to_work; use crate::{BlockHeaderData, BlockSourceError}; -use bitcoin::blockdata::block::{Block, BlockHeader}; +use bitcoin::blockdata::block::{Block, Header}; use bitcoin::consensus::encode; use bitcoin::hash_types::{BlockHash, TxMerkleNode, Txid}; use bitcoin::hashes::hex::FromHex; @@ -88,17 +88,21 @@ impl TryFrom for BlockHeaderData { } } Ok(BlockHeaderData { - header: BlockHeader { - version: get_field!("version", as_i64).try_into().map_err(|_| ())?, + header: Header { + version: bitcoin::blockdata::block::Version::from_consensus( + get_field!("version", as_i64).try_into().map_err(|_| ())? + ), prev_blockhash: if let Some(hash_str) = response.get("previousblockhash") { - BlockHash::from_hex(hash_str.as_str().ok_or(())?).map_err(|_| ())? + BlockHash::from_str(hash_str.as_str().ok_or(())?).map_err(|_| ())? } else { BlockHash::all_zeros() }, - merkle_root: TxMerkleNode::from_hex(get_field!("merkleroot", as_str)).map_err(|_| ())?, + merkle_root: TxMerkleNode::from_str(get_field!("merkleroot", as_str)).map_err(|_| ())?, time: get_field!("time", as_u64).try_into().map_err(|_| ())?, - bits: u32::from_be_bytes(<[u8; 4]>::from_hex(get_field!("bits", as_str)).map_err(|_| ())?), + bits: bitcoin::CompactTarget::from_consensus( + u32::from_be_bytes(<[u8; 4]>::from_hex(get_field!("bits", as_str)).map_err(|_| ())?) + ), nonce: get_field!("nonce", as_u64).try_into().map_err(|_| ())?, }, - chainwork: hex_to_uint256(get_field!("chainwork", as_str)).map_err(|_| ())?, + chainwork: hex_to_work(get_field!("chainwork", as_str)).map_err(|_| ())?, height: get_field!("height", as_u64).try_into().map_err(|_| ())?, }) } @@ -132,7 +136,7 @@ impl TryInto<(BlockHash, Option)> for JsonResponse { } let hash = match &self.0["bestblockhash"] { - serde_json::Value::String(hex_data) => match BlockHash::from_hex(&hex_data) { + serde_json::Value::String(hex_data) => match BlockHash::from_str(&hex_data) { Err(_) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid hex data")), Ok(block_hash) => block_hash, }, @@ -158,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() )) } } @@ -260,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; @@ -288,8 +277,8 @@ pub(crate) mod tests { use super::*; use bitcoin::blockdata::constants::genesis_block; use bitcoin::hashes::Hash; - use bitcoin::hashes::hex::ToHex; use bitcoin::network::constants::Network; + use hex::DisplayHex; use serde_json::value::Number; use serde_json::Value; @@ -298,14 +287,14 @@ pub(crate) mod tests { fn from(data: BlockHeaderData) -> Self { let BlockHeaderData { chainwork, height, header } = data; serde_json::json!({ - "chainwork": chainwork.to_string()["0x".len()..], + "chainwork": chainwork.to_be_bytes().as_hex().to_string(), "height": height, - "version": header.version, - "merkleroot": header.merkle_root.to_hex(), + "version": header.version.to_consensus(), + "merkleroot": header.merkle_root.to_string(), "time": header.time, "nonce": header.nonce, - "bits": header.bits.to_hex(), - "previousblockhash": header.prev_blockhash.to_hex(), + "bits": header.bits.to_consensus().to_be_bytes().as_hex().to_string(), + "previousblockhash": header.prev_blockhash.to_string(), }) } } @@ -394,7 +383,7 @@ pub(crate) mod tests { #[test] fn into_block_header_from_json_response_with_valid_header_array() { let genesis_block = genesis_block(Network::Bitcoin); - let best_block_header = BlockHeader { + let best_block_header = Header { prev_blockhash: genesis_block.block_hash(), ..genesis_block.header }; @@ -541,7 +530,7 @@ pub(crate) mod tests { fn into_block_hash_from_json_response_without_height() { let block = genesis_block(Network::Bitcoin); let response = JsonResponse(serde_json::json!({ - "bestblockhash": block.block_hash().to_hex(), + "bestblockhash": block.block_hash().to_string(), })); match TryInto::<(BlockHash, Option)>::try_into(response) { Err(e) => panic!("Unexpected error: {:?}", e), @@ -556,7 +545,7 @@ pub(crate) mod tests { fn into_block_hash_from_json_response_with_unexpected_blocks_type() { let block = genesis_block(Network::Bitcoin); let response = JsonResponse(serde_json::json!({ - "bestblockhash": block.block_hash().to_hex(), + "bestblockhash": block.block_hash().to_string(), "blocks": "foo", })); match TryInto::<(BlockHash, Option)>::try_into(response) { @@ -572,7 +561,7 @@ pub(crate) mod tests { fn into_block_hash_from_json_response_with_invalid_height() { let block = genesis_block(Network::Bitcoin); let response = JsonResponse(serde_json::json!({ - "bestblockhash": block.block_hash().to_hex(), + "bestblockhash": block.block_hash().to_string(), "blocks": std::u64::MAX, })); match TryInto::<(BlockHash, Option)>::try_into(response) { @@ -588,7 +577,7 @@ pub(crate) mod tests { fn into_block_hash_from_json_response_with_height() { let block = genesis_block(Network::Bitcoin); let response = JsonResponse(serde_json::json!({ - "bestblockhash": block.block_hash().to_hex(), + "bestblockhash": block.block_hash().to_string(), "blocks": 1, })); match TryInto::<(BlockHash, Option)>::try_into(response) { @@ -618,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"), } @@ -630,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"), } @@ -646,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 3b6e9f68376..9cd4049679c 100644 --- a/lightning-block-sync/src/gossip.rs +++ b/lightning-block-sync/src/gossip.rs @@ -9,10 +9,7 @@ use bitcoin::blockdata::constants::ChainHash; use bitcoin::blockdata::transaction::{TxOut, OutPoint}; use bitcoin::hash_types::BlockHash; -use lightning::sign::NodeSigner; - -use lightning::ln::peer_handler::{CustomMessageHandler, PeerManager, SocketDescriptor}; -use lightning::ln::msgs::{ChannelMessageHandler, OnionMessageHandler}; +use lightning::ln::peer_handler::APeerManager; use lightning::routing::gossip::{NetworkGraph, P2PGossipSync}; use lightning::routing::utxo::{UtxoFuture, UtxoLookup, UtxoResult, UtxoLookupError}; @@ -135,21 +132,12 @@ impl< pub struct GossipVerifier where Blocks::Target: UtxoSource, L::Target: Logger, - CM::Target: ChannelMessageHandler, - OM::Target: OnionMessageHandler, - CMH::Target: CustomMessageHandler, - NS::Target: NodeSigner, { source: Blocks, - peer_manager: Arc>, Self, L>>, OM, L, CMH, NS>>, + peer_manager_wake: Arc, gossiper: Arc>, Self, L>>, spawn: S, block_cache: Arc>>, @@ -160,26 +148,20 @@ const BLOCK_CACHE_SIZE: usize = 5; impl GossipVerifier where +> GossipVerifier where Blocks::Target: UtxoSource, L::Target: Logger, - CM::Target: ChannelMessageHandler, - OM::Target: OnionMessageHandler, - CMH::Target: CustomMessageHandler, - NS::Target: NodeSigner, { /// Constructs a new [`GossipVerifier`]. /// /// This is expected to be given to a [`P2PGossipSync`] (initially constructed with `None` for /// the UTXO lookup) via [`P2PGossipSync::add_utxo_lookup`]. - pub fn new(source: Blocks, spawn: S, gossiper: Arc>, Self, L>>, peer_manager: Arc>, Self, L>>, OM, L, CMH, NS>>) -> Self { + pub fn new( + source: Blocks, spawn: S, gossiper: Arc>, Self, L>>, peer_manager: APM + ) -> Self where APM::Target: APeerManager { + let peer_manager_wake = Arc::new(move || peer_manager.as_ref().process_events()); Self { - source, spawn, gossiper, peer_manager, + source, spawn, gossiper, peer_manager_wake, block_cache: Arc::new(Mutex::new(VecDeque::with_capacity(BLOCK_CACHE_SIZE))), } } @@ -269,18 +251,9 @@ impl Deref for GossipVerifier where +> Deref for GossipVerifier where Blocks::Target: UtxoSource, L::Target: Logger, - CM::Target: ChannelMessageHandler, - OM::Target: OnionMessageHandler, - CMH::Target: CustomMessageHandler, - NS::Target: NodeSigner, { type Target = Self; fn deref(&self) -> &Self { self } @@ -290,18 +263,9 @@ impl UtxoLookup for GossipVerifier where +> UtxoLookup for GossipVerifier where Blocks::Target: UtxoSource, L::Target: Logger, - CM::Target: ChannelMessageHandler, - OM::Target: OnionMessageHandler, - CMH::Target: CustomMessageHandler, - NS::Target: NodeSigner, { fn get_utxo(&self, _chain_hash: &ChainHash, short_channel_id: u64) -> UtxoResult { let res = UtxoFuture::new(); @@ -309,11 +273,11 @@ 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/init.rs b/lightning-block-sync/src/init.rs index 5423bba5182..ba93d12f5bc 100644 --- a/lightning-block-sync/src/init.rs +++ b/lightning-block-sync/src/init.rs @@ -4,7 +4,7 @@ use crate::{BlockSource, BlockSourceResult, Cache, ChainNotifier}; use crate::poll::{ChainPoller, Validate, ValidatedBlockHeader}; -use bitcoin::blockdata::block::BlockHeader; +use bitcoin::blockdata::block::Header; use bitcoin::hash_types::BlockHash; use bitcoin::network::constants::Network; @@ -69,10 +69,10 @@ BlockSourceResult where B::Target: BlockSource { /// R: Router, /// L: Logger, /// C: chain::Filter, -/// P: chainmonitor::Persist, +/// P: chainmonitor::Persist, /// >( /// block_source: &B, -/// chain_monitor: &ChainMonitor, +/// chain_monitor: &ChainMonitor, /// config: UserConfig, /// entropy_source: &ES, /// node_signer: &NS, @@ -85,7 +85,7 @@ BlockSourceResult where B::Target: BlockSource { /// ) { /// // Read a serialized channel monitor paired with the block hash when it was persisted. /// let serialized_monitor = "..."; -/// let (monitor_block_hash, mut monitor) = <(BlockHash, ChannelMonitor)>::read( +/// let (monitor_block_hash, mut monitor) = <(BlockHash, ChannelMonitor)>::read( /// &mut Cursor::new(&serialized_monitor), (entropy_source, signer_provider)).unwrap(); /// /// // Read the channel manager paired with the block hash when it was persisted. @@ -103,7 +103,7 @@ BlockSourceResult where B::Target: BlockSource { /// config, /// vec![&mut monitor], /// ); -/// <(BlockHash, ChannelManager<&ChainMonitor, &T, &ES, &NS, &SP, &F, &R, &L>)>::read( +/// <(BlockHash, ChannelManager<&ChainMonitor, &T, &ES, &NS, &SP, &F, &R, &L>)>::read( /// &mut Cursor::new(&serialized_manager), read_args).unwrap() /// }; /// @@ -211,11 +211,11 @@ impl<'a, C: Cache> Cache for ReadOnlyCache<'a, C> { struct DynamicChainListener<'a, L: chain::Listen + ?Sized>(&'a L); impl<'a, L: chain::Listen + ?Sized> chain::Listen for DynamicChainListener<'a, L> { - fn filtered_block_connected(&self, _header: &BlockHeader, _txdata: &chain::transaction::TransactionData, _height: u32) { + fn filtered_block_connected(&self, _header: &Header, _txdata: &chain::transaction::TransactionData, _height: u32) { unreachable!() } - fn block_disconnected(&self, header: &BlockHeader, height: u32) { + fn block_disconnected(&self, header: &Header, height: u32) { self.0.block_disconnected(header, height) } } @@ -234,7 +234,7 @@ impl<'a, L: chain::Listen + ?Sized> chain::Listen for ChainListenerSet<'a, L> { } } - fn filtered_block_connected(&self, header: &BlockHeader, txdata: &chain::transaction::TransactionData, height: u32) { + fn filtered_block_connected(&self, header: &Header, txdata: &chain::transaction::TransactionData, height: u32) { for (starting_height, chain_listener) in self.0.iter() { if height > *starting_height { chain_listener.filtered_block_connected(header, txdata, height); @@ -242,7 +242,7 @@ impl<'a, L: chain::Listen + ?Sized> chain::Listen for ChainListenerSet<'a, L> { } } - fn block_disconnected(&self, _header: &BlockHeader, _height: u32) { + fn block_disconnected(&self, _header: &Header, _height: u32) { unreachable!() } } @@ -252,8 +252,6 @@ mod tests { use crate::test_utils::{Blockchain, MockChainListener}; use super::*; - use bitcoin::network::constants::Network; - #[tokio::test] async fn sync_from_same_chain() { let chain = Blockchain::default().with_height(4); diff --git a/lightning-block-sync/src/lib.rs b/lightning-block-sync/src/lib.rs index 3561a1b5d76..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)] @@ -47,9 +46,9 @@ mod utils; use crate::poll::{ChainTip, Poll, ValidatedBlockHeader}; -use bitcoin::blockdata::block::{Block, BlockHeader}; +use bitcoin::blockdata::block::{Block, Header}; use bitcoin::hash_types::BlockHash; -use bitcoin::util::uint::Uint256; +use bitcoin::pow::Work; use lightning::chain; use lightning::chain::Listen; @@ -147,14 +146,13 @@ impl BlockSourceError { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct BlockHeaderData { /// The block header itself. - pub header: BlockHeader, + pub header: Header, /// The block height where the genesis block has height 0. pub height: u32, - /// The total chain work in expected number of double-SHA256 hashes required to build a chain - /// of equivalent weight. - pub chainwork: Uint256, + /// The total chain work required to build a chain of equivalent weight. + pub chainwork: Work, } /// A block including either all its transactions or only the block header. @@ -166,7 +164,7 @@ pub enum BlockData { /// A block containing all its transactions. FullBlock(Block), /// A block header for when the block does not contain any pertinent transactions. - HeaderOnly(BlockHeader), + HeaderOnly(Header), } /// A lightweight client for keeping a listener in sync with the chain, allowing for Simplified diff --git a/lightning-block-sync/src/poll.rs b/lightning-block-sync/src/poll.rs index e7171cf3656..dcc19a4969d 100644 --- a/lightning-block-sync/src/poll.rs +++ b/lightning-block-sync/src/poll.rs @@ -60,7 +60,7 @@ impl Validate for BlockHeaderData { fn validate(self, block_hash: BlockHash) -> BlockSourceResult { let pow_valid_block_hash = self.header - .validate_pow(&self.header.target()) + .validate_pow(self.header.target()) .map_err(BlockSourceError::persistent)?; if pow_valid_block_hash != block_hash { @@ -81,7 +81,7 @@ impl Validate for BlockData { }; let pow_valid_block_hash = header - .validate_pow(&header.target()) + .validate_pow(header.target()) .map_err(BlockSourceError::persistent)?; if pow_valid_block_hash != block_hash { @@ -138,8 +138,8 @@ impl ValidatedBlockHeader { if self.height % 2016 == 0 { let target = self.header.target(); let previous_target = previous_header.header.target(); - let min_target = previous_target >> 2; - let max_target = previous_target << 2; + let min_target = previous_target.min_difficulty_transition_threshold(); + let max_target = previous_target.max_difficulty_transition_threshold(); if target > max_target || target < min_target { return Err(BlockSourceError::persistent("invalid difficulty transition")) } @@ -262,7 +262,6 @@ mod tests { use crate::*; use crate::test_utils::Blockchain; use super::*; - use bitcoin::util::uint::Uint256; #[tokio::test] async fn poll_empty_chain() { @@ -302,7 +301,7 @@ mod tests { // Invalidate the tip by changing its target. chain.blocks.last_mut().unwrap().header.bits = - BlockHeader::compact_target_from_u256(&Uint256::from_be_bytes([0; 32])); + bitcoin::Target::from_be_bytes([0x01; 32]).to_compact_lossy(); let poller = ChainPoller::new(&chain, Network::Bitcoin); match poller.poll_chain_tip(best_known_chain_tip).await { diff --git a/lightning-block-sync/src/rest.rs b/lightning-block-sync/src/rest.rs index 5690da12ea0..74a460a7ab5 100644 --- a/lightning-block-sync/src/rest.rs +++ b/lightning-block-sync/src/rest.rs @@ -8,7 +8,6 @@ use crate::convert::GetUtxosResponse; use bitcoin::OutPoint; use bitcoin::hash_types::BlockHash; -use bitcoin::hashes::hex::ToHex; use std::convert::TryFrom; use std::convert::TryInto; @@ -44,14 +43,14 @@ impl RestClient { impl BlockSource for RestClient { fn get_header<'a>(&'a self, header_hash: &'a BlockHash, _height: Option) -> AsyncBlockSourceResult<'a, BlockHeaderData> { Box::pin(async move { - let resource_path = format!("headers/1/{}.json", header_hash.to_hex()); + let resource_path = format!("headers/1/{}.json", header_hash.to_string()); Ok(self.request_resource::(&resource_path).await?) }) } fn get_block<'a>(&'a self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, BlockData> { Box::pin(async move { - let resource_path = format!("block/{}.bin", header_hash.to_hex()); + let resource_path = format!("block/{}.bin", header_hash.to_string()); Ok(BlockData::FullBlock(self.request_resource::(&resource_path).await?)) }) } @@ -73,7 +72,7 @@ impl UtxoSource for RestClient { fn is_output_unspent<'a>(&'a self, outpoint: OutPoint) -> AsyncBlockSourceResult<'a, bool> { Box::pin(async move { - let resource_path = format!("getutxos/{}-{}.json", outpoint.txid.to_hex(), outpoint.vout); + let resource_path = format!("getutxos/{}-{}.json", outpoint.txid.to_string(), outpoint.vout); let utxo_result = self.request_resource::(&resource_path).await?; Ok(utxo_result.hit_bitmap_nonempty) @@ -145,7 +144,7 @@ mod tests { )); let client = RestClient::new(server.endpoint()).unwrap(); - let outpoint = OutPoint::new(bitcoin::Txid::from_inner([0; 32]), 0); + let outpoint = OutPoint::new(bitcoin::Txid::from_byte_array([0; 32]), 0); let unspent_output = client.is_output_unspent(outpoint).await.unwrap(); assert_eq!(unspent_output, false); } @@ -159,7 +158,7 @@ mod tests { )); let client = RestClient::new(server.endpoint()).unwrap(); - let outpoint = OutPoint::new(bitcoin::Txid::from_inner([0; 32]), 0); + let outpoint = OutPoint::new(bitcoin::Txid::from_byte_array([0; 32]), 0); let unspent_output = client.is_output_unspent(outpoint).await.unwrap(); assert_eq!(unspent_output, true); } diff --git a/lightning-block-sync/src/rpc.rs b/lightning-block-sync/src/rpc.rs index 0ad94040aca..d296088ae7e 100644 --- a/lightning-block-sync/src/rpc.rs +++ b/lightning-block-sync/src/rpc.rs @@ -6,7 +6,6 @@ use crate::http::{HttpClient, HttpEndpoint, HttpError, JsonResponse}; use crate::gossip::UtxoSource; use bitcoin::hash_types::BlockHash; -use bitcoin::hashes::hex::ToHex; use bitcoin::OutPoint; use std::sync::Mutex; @@ -120,14 +119,14 @@ impl RpcClient { impl BlockSource for RpcClient { fn get_header<'a>(&'a self, header_hash: &'a BlockHash, _height: Option) -> AsyncBlockSourceResult<'a, BlockHeaderData> { Box::pin(async move { - let header_hash = serde_json::json!(header_hash.to_hex()); + let header_hash = serde_json::json!(header_hash.to_string()); Ok(self.call_method("getblockheader", &[header_hash]).await?) }) } fn get_block<'a>(&'a self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, BlockData> { Box::pin(async move { - let header_hash = serde_json::json!(header_hash.to_hex()); + let header_hash = serde_json::json!(header_hash.to_string()); let verbosity = serde_json::json!(0); Ok(BlockData::FullBlock(self.call_method("getblock", &[header_hash, verbosity]).await?)) }) @@ -150,7 +149,7 @@ impl UtxoSource for RpcClient { fn is_output_unspent<'a>(&'a self, outpoint: OutPoint) -> AsyncBlockSourceResult<'a, bool> { Box::pin(async move { - let txid_param = serde_json::json!(outpoint.txid.to_hex()); + let txid_param = serde_json::json!(outpoint.txid.to_string()); let vout_param = serde_json::json!(outpoint.vout); let include_mempool = serde_json::json!(false); let utxo_opt: serde_json::Value = self.call_method( @@ -275,7 +274,7 @@ mod tests { let response = serde_json::json!({ "result": null }); let server = HttpServer::responding_with_ok(MessageBody::Content(response)); let client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap(); - let outpoint = OutPoint::new(bitcoin::Txid::from_inner([0; 32]), 0); + let outpoint = OutPoint::new(bitcoin::Txid::from_byte_array([0; 32]), 0); let unspent_output = client.is_output_unspent(outpoint).await.unwrap(); assert_eq!(unspent_output, false); } @@ -285,7 +284,7 @@ mod tests { let response = serde_json::json!({ "result": {"bestblock": 1, "confirmations": 42}}); let server = HttpServer::responding_with_ok(MessageBody::Content(response)); let client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap(); - let outpoint = OutPoint::new(bitcoin::Txid::from_inner([0; 32]), 0); + let outpoint = OutPoint::new(bitcoin::Txid::from_byte_array([0; 32]), 0); let unspent_output = client.is_output_unspent(outpoint).await.unwrap(); assert_eq!(unspent_output, true); } diff --git a/lightning-block-sync/src/test_utils.rs b/lightning-block-sync/src/test_utils.rs index 597d2a85fd5..b6fa6617c84 100644 --- a/lightning-block-sync/src/test_utils.rs +++ b/lightning-block-sync/src/test_utils.rs @@ -1,13 +1,12 @@ use crate::{AsyncBlockSourceResult, BlockData, BlockHeaderData, BlockSource, BlockSourceError, UnboundedCache}; use crate::poll::{Validate, ValidatedBlockHeader}; -use bitcoin::blockdata::block::{Block, BlockHeader}; +use bitcoin::blockdata::block::{Block, Header, Version}; use bitcoin::blockdata::constants::genesis_block; -use bitcoin::hash_types::BlockHash; +use bitcoin::blockdata::locktime::absolute::LockTime; +use bitcoin::hash_types::{BlockHash, TxMerkleNode}; use bitcoin::network::constants::Network; -use bitcoin::util::uint::Uint256; -use bitcoin::util::hash::bitcoin_merkle_root; -use bitcoin::{PackedLockTime, Transaction}; +use bitcoin::Transaction; use lightning::chain; @@ -35,7 +34,7 @@ impl Blockchain { pub fn with_height(mut self, height: usize) -> Self { self.blocks.reserve_exact(height); - let bits = BlockHeader::compact_target_from_u256(&Uint256::from_be_bytes([0xff; 32])); + let bits = bitcoin::Target::from_be_bytes([0xff; 32]).to_compact_lossy(); for i in 1..=height { let prev_block = &self.blocks[i - 1]; let prev_blockhash = prev_block.block_hash(); @@ -46,16 +45,16 @@ impl Blockchain { // but that's OK because those tests don't trigger the check. let coinbase = Transaction { version: 0, - lock_time: PackedLockTime::ZERO, + lock_time: LockTime::ZERO, input: vec![], output: vec![] }; - let merkle_root = bitcoin_merkle_root(vec![coinbase.txid().as_hash()].into_iter()).unwrap(); + let merkle_root = TxMerkleNode::from_raw_hash(coinbase.txid().to_raw_hash()); self.blocks.push(Block { - header: BlockHeader { - version: 0, + header: Header { + version: Version::NO_SOFT_FORK_SIGNALLING, prev_blockhash, - merkle_root: merkle_root.into(), + merkle_root, time, bits, nonce: 0, @@ -103,8 +102,12 @@ impl Blockchain { fn at_height_unvalidated(&self, height: usize) -> BlockHeaderData { assert!(!self.blocks.is_empty()); assert!(height < self.blocks.len()); + let mut total_work = self.blocks[0].header.work(); + for i in 1..=height { + total_work = total_work + self.blocks[i].header.work(); + } BlockHeaderData { - chainwork: self.blocks[0].header.work() + Uint256::from_u64(height as u64).unwrap(), + chainwork: total_work, height: height as u32, header: self.blocks[height].header, } @@ -188,8 +191,8 @@ impl BlockSource for Blockchain { pub struct NullChainListener; impl chain::Listen for NullChainListener { - fn filtered_block_connected(&self, _header: &BlockHeader, _txdata: &chain::transaction::TransactionData, _height: u32) {} - fn block_disconnected(&self, _header: &BlockHeader, _height: u32) {} + fn filtered_block_connected(&self, _header: &Header, _txdata: &chain::transaction::TransactionData, _height: u32) {} + fn block_disconnected(&self, _header: &Header, _height: u32) {} } pub struct MockChainListener { @@ -236,7 +239,7 @@ impl chain::Listen for MockChainListener { } } - fn filtered_block_connected(&self, header: &BlockHeader, _txdata: &chain::transaction::TransactionData, height: u32) { + fn filtered_block_connected(&self, header: &Header, _txdata: &chain::transaction::TransactionData, height: u32) { match self.expected_filtered_blocks_connected.borrow_mut().pop_front() { None => { panic!("Unexpected filtered block connected: {:?}", header.block_hash()); @@ -248,7 +251,7 @@ impl chain::Listen for MockChainListener { } } - fn block_disconnected(&self, header: &BlockHeader, height: u32) { + fn block_disconnected(&self, header: &Header, height: u32) { match self.expected_blocks_disconnected.borrow_mut().pop_front() { None => { panic!("Unexpected block disconnected: {:?}", header.block_hash()); diff --git a/lightning-block-sync/src/utils.rs b/lightning-block-sync/src/utils.rs index 96a2e578877..b841f5f9d41 100644 --- a/lightning-block-sync/src/utils.rs +++ b/lightning-block-sync/src/utils.rs @@ -1,54 +1,54 @@ use bitcoin::hashes::hex::FromHex; -use bitcoin::util::uint::Uint256; +use bitcoin::pow::Work; -pub fn hex_to_uint256(hex: &str) -> Result { +pub fn hex_to_work(hex: &str) -> Result { let bytes = <[u8; 32]>::from_hex(hex)?; - Ok(Uint256::from_be_bytes(bytes)) + Ok(Work::from_be_bytes(bytes)) } #[cfg(test)] mod tests { use super::*; - use bitcoin::util::uint::Uint256; + use bitcoin::pow::Work; #[test] - fn hex_to_uint256_empty_str() { - assert!(hex_to_uint256("").is_err()); + fn hex_to_work_empty_str() { + assert!(hex_to_work("").is_err()); } #[test] - fn hex_to_uint256_too_short_str() { + fn hex_to_work_too_short_str() { let hex = String::from_utf8(vec![b'0'; 32]).unwrap(); - assert_eq!(hex_to_uint256(&hex), Err(bitcoin::hashes::hex::Error::InvalidLength(64, 32))); + assert_eq!(hex_to_work(&hex), Err(bitcoin::hashes::hex::Error::InvalidLength(64, 32))); } #[test] - fn hex_to_uint256_too_long_str() { + fn hex_to_work_too_long_str() { let hex = String::from_utf8(vec![b'0'; 128]).unwrap(); - assert_eq!(hex_to_uint256(&hex), Err(bitcoin::hashes::hex::Error::InvalidLength(64, 128))); + assert_eq!(hex_to_work(&hex), Err(bitcoin::hashes::hex::Error::InvalidLength(64, 128))); } #[test] - fn hex_to_uint256_odd_length_str() { + fn hex_to_work_odd_length_str() { let hex = String::from_utf8(vec![b'0'; 65]).unwrap(); - assert_eq!(hex_to_uint256(&hex), Err(bitcoin::hashes::hex::Error::OddLengthString(65))); + assert_eq!(hex_to_work(&hex), Err(bitcoin::hashes::hex::Error::OddLengthString(65))); } #[test] - fn hex_to_uint256_invalid_char() { + fn hex_to_work_invalid_char() { let hex = String::from_utf8(vec![b'G'; 64]).unwrap(); - assert_eq!(hex_to_uint256(&hex), Err(bitcoin::hashes::hex::Error::InvalidChar(b'G'))); + assert_eq!(hex_to_work(&hex), Err(bitcoin::hashes::hex::Error::InvalidChar(b'G'))); } #[test] - fn hex_to_uint256_lowercase_str() { - let hex: String = std::iter::repeat("0123456789abcdef").take(4).collect(); - assert_eq!(hex_to_uint256(&hex).unwrap(), Uint256([0x0123456789abcdefu64; 4])); + fn hex_to_work_lowercase_str() { + let hex: String = std::iter::repeat("1a").take(32).collect(); + assert_eq!(hex_to_work(&hex).unwrap(), Work::from_be_bytes([0x1a; 32])); } #[test] - fn hex_to_uint256_uppercase_str() { - let hex: String = std::iter::repeat("0123456789ABCDEF").take(4).collect(); - assert_eq!(hex_to_uint256(&hex).unwrap(), Uint256([0x0123456789abcdefu64; 4])); + fn hex_to_work_uppercase_str() { + let hex: String = std::iter::repeat("1A").take(32).collect(); + assert_eq!(hex_to_work(&hex).unwrap(), Work::from_be_bytes([0x1A; 32])); } } diff --git a/lightning-custom-message/Cargo.toml b/lightning-custom-message/Cargo.toml index 2efce6bcd2c..0ef2213d2b1 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.123" authors = ["Jeffrey Czyz"] license = "MIT OR Apache-2.0" repository = "https://github.com/lightningdevkit/rust-lightning" @@ -14,5 +14,5 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] -bitcoin = "0.29.0" -lightning = { version = "0.0.118", path = "../lightning" } +bitcoin = "0.30.2" +lightning = { version = "0.0.123", path = "../lightning" } diff --git a/lightning-custom-message/src/lib.rs b/lightning-custom-message/src/lib.rs index a0a70c9de03..e2000bc5804 100644 --- a/lightning-custom-message/src/lib.rs +++ b/lightning-custom-message/src/lib.rs @@ -12,6 +12,7 @@ //! `Foo` and `Bar` messages, and further composing it with a handler for `Baz` messages. //! //!``` +//! # fn main() {} // Avoid #[macro_export] generating an in-function warning //! # extern crate bitcoin; //! extern crate lightning; //! #[macro_use] @@ -167,7 +168,6 @@ //! # } //! } //! -//! # fn main() { //! // The first crate may define a handler composing `FooHandler` and `BarHandler` and export the //! // corresponding message type ids as a macro to use in further composition. //! @@ -207,7 +207,6 @@ //! macro_rules! foo_bar_baz_type_ids { //! () => { foo_bar_type_ids!() | baz_type_id!() } //! } -//! # } //!``` //! //! [BOLT 1]: https://github.com/lightning/bolts/blob/master/01-messaging.md diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml index 6a77d0dee01..1c4e2faef4d 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.31.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,25 +16,24 @@ 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", "lightning/std", "bech32/std"] [dependencies] bech32 = { version = "0.9.0", default-features = false } -lightning = { version = "0.0.118", path = "../lightning", default-features = false } -secp256k1 = { version = "0.24.0", default-features = false, features = ["recovery", "alloc"] } -num-traits = { version = "0.2.8", default-features = false } -bitcoin_hashes = { version = "0.11", default-features = false } -hashbrown = { version = "0.8", optional = true } -bitcoin = { version = "0.29.0", default-features = false } +lightning = { version = "0.0.123", path = "../lightning", default-features = false } +secp256k1 = { version = "0.27.0", default-features = false, features = ["recovery", "alloc"] } +serde = { version = "1.0.118", optional = true } +bitcoin = { version = "0.30.2", default-features = false } # RGB and related -rgb-lib = { version = "0.3.0-alpha.3", features = [ +rgb-lib = { version = "0.3.0-alpha.4", features = [ "electrum", "esplora", ] } [dev-dependencies] -lightning = { version = "0.0.118", path = "../lightning", default-features = false, features = ["_test_utils"] } -hex = "0.4" +lightning = { version = "0.0.123", 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 aad6140ac02..ae56e51d2ff 100644 --- a/lightning-invoice/src/de.rs +++ b/lightning-invoice/src/de.rs @@ -1,5 +1,6 @@ #[cfg(feature = "std")] use std::error; +#[cfg(not(feature = "std"))] use core::convert::TryFrom; use core::fmt; use core::fmt::{Display, Formatter}; @@ -10,17 +11,15 @@ use core::str::FromStr; use bech32::{u5, FromBase32}; use bitcoin::{PubkeyHash, ScriptHash}; -use bitcoin::util::address::WitnessVersion; -use bitcoin_hashes::Hash; -use bitcoin_hashes::sha256; +use bitcoin::address::WitnessVersion; +use bitcoin::hashes::Hash; +use bitcoin::hashes::sha256; use rgb_lib::ContractId; use crate::prelude::*; -use lightning::ln::PaymentSecret; +use lightning::ln::types::PaymentSecret; use lightning::routing::gossip::RoutingFees; use lightning::routing::router::{RouteHint, RouteHintHop}; -use num_traits::{CheckedAdd, CheckedMul}; - use secp256k1::ecdsa::{RecoveryId, RecoverableSignature}; use secp256k1::PublicKey; @@ -45,7 +44,11 @@ mod hrp_sm { } impl States { - fn next_state(&self, read_symbol: char) -> Result { + fn next_state(&self, read_byte: u8) -> Result { + let read_symbol = match char::from_u32(read_byte.into()) { + Some(symb) if symb.is_ascii() => symb, + _ => return Err(super::Bolt11ParseError::MalformedHRP), + }; match *self { States::Start => { if read_symbol == 'l' { @@ -121,7 +124,7 @@ mod hrp_sm { *range = Some(new_range); } - fn step(&mut self, c: char) -> Result<(), super::Bolt11ParseError> { + fn step(&mut self, c: u8) -> Result<(), super::Bolt11ParseError> { let next_state = self.state.next_state(c)?; match next_state { States::ParseCurrencyPrefix => { @@ -160,7 +163,7 @@ mod hrp_sm { pub fn parse_hrp(input: &str) -> Result<(&str, &str, &str), super::Bolt11ParseError> { let mut sm = StateMachine::new(); - for c in input.chars() { + for c in input.bytes() { sm.step(c)?; } @@ -357,7 +360,7 @@ impl FromBase32 for PositiveTimestamp { if b32.len() != 7 { return Err(Bolt11ParseError::InvalidSliceLength("PositiveTimestamp::from_base32()".into())); } - let timestamp: u64 = parse_int_be(b32, 32) + let timestamp: u64 = parse_u64_be(b32) .expect("7*5bit < 64bit, no overflow possible"); match PositiveTimestamp::from_unix_timestamp(timestamp) { Ok(t) => Ok(t), @@ -383,16 +386,17 @@ impl FromBase32 for Bolt11InvoiceSignature { } } -pub(crate) fn parse_int_be(digits: &[U], base: T) -> Option - where T: CheckedAdd + CheckedMul + From + Default, - U: Into + Copy -{ - digits.iter().fold(Some(Default::default()), |acc, b| - acc - .and_then(|x| x.checked_mul(&base)) - .and_then(|x| x.checked_add(&(Into::::into(*b)).into())) - ) -} +macro_rules! define_parse_int_be { ($name: ident, $ty: ty) => { + fn $name(digits: &[u5]) -> Option<$ty> { + digits.iter().fold(Some(Default::default()), |acc, b| + acc + .and_then(|x| x.checked_mul(32)) + .and_then(|x| x.checked_add((Into::::into(*b)).into())) + ) + } +} } +define_parse_int_be!(parse_u16_be, u16); +define_parse_int_be!(parse_u64_be, u64); fn parse_tagged_parts(data: &[u5]) -> Result, Bolt11ParseError> { let mut parts = Vec::::new(); @@ -405,7 +409,7 @@ fn parse_tagged_parts(data: &[u5]) -> Result, Bolt11ParseErr // Ignore tag at data[0], it will be handled in the TaggedField parsers and // parse the length to find the end of the tagged field's data - let len = parse_int_be(&data[1..3], 32).expect("can't overflow"); + let len = parse_u16_be(&data[1..3]).expect("can't overflow") as usize; let last_element = 3 + len; if data.len() < last_element { @@ -522,7 +526,7 @@ impl FromBase32 for ExpiryTime { type Err = Bolt11ParseError; fn from_base32(field_data: &[u5]) -> Result { - match parse_int_be::(field_data, 32) + match parse_u64_be(field_data) .map(ExpiryTime::from_seconds) { Some(t) => Ok(t), @@ -535,7 +539,7 @@ impl FromBase32 for MinFinalCltvExpiryDelta { type Err = Bolt11ParseError; fn from_base32(field_data: &[u5]) -> Result { - let expiry = parse_int_be::(field_data, 32); + let expiry = parse_u64_be(field_data); if let Some(expiry) = expiry { Ok(MinFinalCltvExpiryDelta(expiry)) } else { @@ -569,14 +573,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)) } @@ -607,12 +611,12 @@ impl FromBase32 for PrivateRoute { let hop = RouteHintHop { src_node_id: PublicKey::from_slice(&hop_bytes[0..33])?, - short_channel_id: parse_int_be(&channel_id, 256).expect("short chan ID slice too big?"), + short_channel_id: u64::from_be_bytes(channel_id), fees: RoutingFees { - base_msat: parse_int_be(&hop_bytes[41..45], 256).expect("slice too big?"), - proportional_millionths: parse_int_be(&hop_bytes[45..49], 256).expect("slice too big?"), + base_msat: u32::from_be_bytes(hop_bytes[41..45].try_into().expect("slice too big?")), + proportional_millionths: u32::from_be_bytes(hop_bytes[45..49].try_into().expect("slice too big?")), }, - cltv_expiry_delta: parse_int_be(&hop_bytes[49..51], 256).expect("slice too big?"), + cltv_expiry_delta: u16::from_be_bytes(hop_bytes[49..51].try_into().expect("slice too big?")), htlc_minimum_msat: None, htlc_maximum_msat: None, }; @@ -628,7 +632,7 @@ impl FromBase32 for RgbAmount { type Err = Bolt11ParseError; fn from_base32(field_data: &[u5]) -> Result { - let rgb_amount = parse_int_be::(field_data, 32); + let rgb_amount = parse_u64_be(field_data); if let Some(rgb_amount) = rgb_amount { Ok(RgbAmount(rgb_amount)) } else { @@ -760,8 +764,8 @@ mod test { use crate::de::Bolt11ParseError; use secp256k1::PublicKey; use bech32::u5; - use bitcoin_hashes::hex::FromHex; - use bitcoin_hashes::sha256; + use bitcoin::hashes::sha256; + use std::str::FromStr; const CHARSET_REV: [i8; 128] = [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, @@ -795,12 +799,16 @@ mod test { #[test] fn test_parse_int_from_bytes_be() { - use crate::de::parse_int_be; - - assert_eq!(parse_int_be::(&[1, 2, 3, 4], 256), Some(16909060)); - assert_eq!(parse_int_be::(&[1, 3], 32), Some(35)); - assert_eq!(parse_int_be::(&[255, 255, 255, 255], 256), Some(4294967295)); - assert_eq!(parse_int_be::(&[1, 0, 0, 0, 0], 256), None); + use crate::de::parse_u16_be; + + assert_eq!(parse_u16_be(&[ + u5::try_from_u8(1).unwrap(), u5::try_from_u8(2).unwrap(), + u5::try_from_u8(3).unwrap(), u5::try_from_u8(4).unwrap()] + ), Some(34916)); + assert_eq!(parse_u16_be(&[ + u5::try_from_u8(2).unwrap(), u5::try_from_u8(0).unwrap(), + u5::try_from_u8(0).unwrap(), u5::try_from_u8(0).unwrap()] + ), None); } #[test] @@ -812,7 +820,7 @@ mod test { "qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypq".as_bytes() ); - let hash = sha256::Hash::from_hex( + let hash = sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap(); let expected = Ok(Sha256(hash)); @@ -889,8 +897,8 @@ mod test { use crate::Fallback; use bech32::FromBase32; use bitcoin::{PubkeyHash, ScriptHash}; - use bitcoin::util::address::WitnessVersion; - use bitcoin_hashes::Hash; + use bitcoin::address::WitnessVersion; + use bitcoin::hashes::Hash; let cases = vec![ ( @@ -950,7 +958,6 @@ mod test { use lightning::routing::router::{RouteHint, RouteHintHop}; use crate::PrivateRoute; use bech32::FromBase32; - use crate::de::parse_int_be; let input = from_bech32( "q20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqa\ @@ -966,7 +973,7 @@ mod test { 0x7e, 0x14, 0x8f, 0x78, 0xc7, 0x72, 0x55 ][..] ).unwrap(), - short_channel_id: parse_int_be(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08], 256).expect("short chan ID slice too big?"), + short_channel_id: 0x0102030405060708, fees: RoutingFees { base_msat: 1, proportional_millionths: 20, @@ -983,7 +990,7 @@ mod test { 0x7e, 0x14, 0x8f, 0x78, 0xc7, 0x72, 0x55 ][..] ).unwrap(), - short_channel_id: parse_int_be(&[0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a], 256).expect("short chan ID slice too big?"), + short_channel_id: 0x030405060708090a, fees: RoutingFees { base_msat: 2, proportional_millionths: 30, @@ -1022,7 +1029,7 @@ mod test { data: RawDataPart { timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), tagged_fields: vec ! [ - PaymentHash(Sha256(sha256::Hash::from_hex( + PaymentHash(Sha256(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap())).into(), Description(crate::Description::new("coffee beans".to_owned()).unwrap()).into(), @@ -1069,7 +1076,7 @@ mod test { data: RawDataPart { timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), tagged_fields: vec ! [ - PaymentHash(Sha256(sha256::Hash::from_hex( + PaymentHash(Sha256(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap())).into(), Description( diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index 5bd2c242135..fcbdffc650d 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,9 +30,7 @@ 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; extern crate alloc; #[cfg(any(test, feature = "std"))] @@ -46,8 +43,8 @@ use std::time::SystemTime; use bech32::u5; use bitcoin::{Address, Network, PubkeyHash, ScriptHash}; -use bitcoin::util::address::{Payload, WitnessVersion}; -use bitcoin_hashes::{Hash, sha256}; +use bitcoin::address::{Payload, WitnessProgram, WitnessVersion}; +use bitcoin::hashes::{Hash, sha256}; use lightning::ln::features::Bolt11InvoiceFeatures; use lightning::util::invoice::construct_invoice_preimage; @@ -69,39 +66,26 @@ use core::str; use serde::{Deserialize, Deserializer,Serialize, Serializer, de::Error}; #[doc(no_inline)] -pub use lightning::ln::PaymentSecret; +pub use lightning::ln::types::PaymentSecret; #[doc(no_inline)] pub use lightning::routing::router::{RouteHint, RouteHintHop}; #[doc(no_inline)] pub use lightning::routing::gossip::RoutingFees; +use lightning::util::string::UntrustedString; 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(feature = "std")] -mod sync; - -/// 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)] @@ -172,15 +156,15 @@ 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; /// -/// use lightning::ln::PaymentSecret; +/// use lightning::ln::types::PaymentSecret; /// /// use lightning_invoice::{Currency, InvoiceBuilder}; /// @@ -269,6 +253,15 @@ pub enum Bolt11InvoiceDescription<'f> { Hash(&'f Sha256), } +impl<'f> Display for Bolt11InvoiceDescription<'f> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Bolt11InvoiceDescription::Direct(desc) => write!(f, "{}", desc.0), + Bolt11InvoiceDescription::Hash(hash) => write!(f, "{}", hash.0), + } + } +} + /// Represents a signed [`RawBolt11Invoice`] with cached hash. The signature is not checked and may be /// invalid. /// @@ -403,6 +396,10 @@ impl From for Currency { Network::Testnet => Currency::BitcoinTestnet, Network::Regtest => Currency::Regtest, Network::Signet => Currency::Signet, + _ => { + debug_assert!(false, "Need to handle new rust-bitcoin network type"); + Currency::Regtest + }, } } } @@ -472,8 +469,8 @@ impl Sha256 { /// /// # Invariants /// The description can be at most 639 __bytes__ long -#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct Description(String); +#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Default)] +pub struct Description(UntrustedString); /// Payee public key #[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] @@ -559,7 +556,7 @@ impl InvoiceBuilder Self { - let amount = amount_msat * 10; // Invoices are denominated in "pico BTC" + let amount = match amount_msat.checked_mul(10) { // Invoices are denominated in "pico BTC" + Some(amt) => amt, + None => { + self.error = Some(CreationError::InvalidAmount); + return self + } + }; let biggest_possible_si_prefix = SiPrefix::values_desc() .iter() .find(|prefix| amount % prefix.multiplier() == 0) @@ -699,7 +702,7 @@ impl InvoiceBui pub fn invoice_description(self, description: Bolt11InvoiceDescription) -> InvoiceBuilder { match description { Bolt11InvoiceDescription::Direct(desc) => { - self.description(desc.clone().into_inner()) + self.description(desc.clone().into_inner().0) } Bolt11InvoiceDescription::Hash(hash) => { self.description_hash(hash.0) @@ -1105,9 +1108,10 @@ impl RawBolt11Invoice { find_all_extract!(self.known_tagged_fields(), TaggedField::PrivateRoute(ref x), x).collect() } + /// Returns `None` if no amount is set or on overflow. pub fn amount_pico_btc(&self) -> Option { - self.hrp.raw_amount.map(|v| { - v * self.hrp.si_prefix.as_ref().map_or(1_000_000_000_000, |si| { si.multiplier() }) + self.hrp.raw_amount.and_then(|v| { + v.checked_mul(self.hrp.si_prefix.as_ref().map_or(1_000_000_000_000, |si| { si.multiplier() })) }) } @@ -1168,6 +1172,12 @@ impl PositiveTimestamp { } } +impl From for Duration { + fn from(val: PositiveTimestamp) -> Self { + val.0 + } +} + #[cfg(feature = "std")] impl From for SystemTime { fn from(val: PositiveTimestamp) -> Self { @@ -1378,6 +1388,15 @@ impl Bolt11Invoice { self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0 } + /// Recover the payee's public key if one was included in the invoice, otherwise return the + /// recovered public key from the signature + pub fn get_payee_pub_key(&self) -> 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 { @@ -1445,10 +1464,13 @@ impl Bolt11Invoice { /// Returns a list of all fallback addresses as [`Address`]es pub fn fallback_addresses(&self) -> Vec
        { - self.fallbacks().iter().map(|fallback| { + self.fallbacks().iter().filter_map(|fallback| { let payload = match fallback { Fallback::SegWitProgram { version, program } => { - Payload::WitnessProgram { version: *version, program: program.to_vec() } + match WitnessProgram::new(*version, program.clone()) { + Ok(witness_program) => Payload::WitnessProgram(witness_program), + Err(_) => return None, + } } Fallback::PubKeyHash(pkh) => { Payload::PubkeyHash(*pkh) @@ -1458,7 +1480,7 @@ impl Bolt11Invoice { } }; - Address { payload, network: self.network() } + Some(Address::new(self.network(), payload)) }).collect() } @@ -1548,27 +1570,19 @@ impl Description { if description.len() > 639 { Err(CreationError::DescriptionTooLong) } else { - Ok(Description(description)) + Ok(Description(UntrustedString(description))) } } - /// Returns the underlying description [`String`] - pub fn into_inner(self) -> String { + /// Returns the underlying description [`UntrustedString`] + pub fn into_inner(self) -> UntrustedString { self.0 } } -impl From for String { - fn from(val: Description) -> Self { - val.into_inner() - } -} - -impl Deref for Description { - type Target = str; - - fn deref(&self) -> &str { - &self.0 +impl Display for Description { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) } } @@ -1793,9 +1807,9 @@ impl<'de> Deserialize<'de> for Bolt11Invoice { #[cfg(test)] mod test { - use bitcoin::Script; - use bitcoin_hashes::hex::FromHex; - use bitcoin_hashes::sha256; + use bitcoin::ScriptBuf; + use bitcoin::hashes::sha256; + use std::str::FromStr; #[test] fn test_system_time_bounds_assumptions() { @@ -1819,7 +1833,7 @@ mod test { data: RawDataPart { timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), tagged_fields: vec![ - PaymentHash(crate::Sha256(sha256::Hash::from_hex( + PaymentHash(crate::Sha256(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap())).into(), Description(crate::Description::new( @@ -1857,7 +1871,7 @@ mod test { data: RawDataPart { timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), tagged_fields: vec ! [ - PaymentHash(Sha256(sha256::Hash::from_hex( + PaymentHash(Sha256(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap())).into(), Description( @@ -1913,11 +1927,11 @@ mod test { use lightning::ln::features::Bolt11InvoiceFeatures; use secp256k1::Secp256k1; use secp256k1::SecretKey; - use crate::{Bolt11Invoice, RawBolt11Invoice, RawHrp, RawDataPart, Currency, Sha256, PositiveTimestamp, + use crate::{Bolt11Invoice, RawBolt11Invoice, RawHrp, RawDataPart, Currency, Sha256, PositiveTimestamp, Bolt11SemanticError}; let private_key = SecretKey::from_slice(&[42; 32]).unwrap(); - let payment_secret = lightning::ln::PaymentSecret([21; 32]); + let payment_secret = lightning::ln::types::PaymentSecret([21; 32]); let invoice_template = RawBolt11Invoice { hrp: RawHrp { currency: Currency::Bitcoin, @@ -1927,7 +1941,7 @@ mod test { data: RawDataPart { timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), tagged_fields: vec ! [ - PaymentHash(Sha256(sha256::Hash::from_hex( + PaymentHash(Sha256(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap())).into(), Description( @@ -2088,7 +2102,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(); @@ -2104,7 +2118,7 @@ mod test { let route_1 = RouteHint(vec![ RouteHintHop { src_node_id: public_key, - short_channel_id: de::parse_int_be(&[123; 8], 256).expect("short chan ID slice too big?"), + short_channel_id: u64::from_be_bytes([123; 8]), fees: RoutingFees { base_msat: 2, proportional_millionths: 1, @@ -2115,7 +2129,7 @@ mod test { }, RouteHintHop { src_node_id: public_key, - short_channel_id: de::parse_int_be(&[42; 8], 256).expect("short chan ID slice too big?"), + short_channel_id: u64::from_be_bytes([42; 8]), fees: RoutingFees { base_msat: 3, proportional_millionths: 2, @@ -2140,7 +2154,7 @@ mod test { }, RouteHintHop { src_node_id: public_key, - short_channel_id: de::parse_int_be(&[1; 8], 256).expect("short chan ID slice too big?"), + short_channel_id: u64::from_be_bytes([1; 8]), fees: RoutingFees { base_msat: 5, proportional_millionths: 4, @@ -2177,14 +2191,14 @@ 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)); assert_eq!(invoice.expiry_time(), Duration::from_secs(54321)); assert_eq!(invoice.min_final_cltv_expiry_delta(), 144); assert_eq!(invoice.fallbacks(), vec![&Fallback::PubKeyHash(PubkeyHash::from_slice(&[0;20]).unwrap())]); - let address = Address::from_script(&Script::new_p2pkh(&PubkeyHash::from_slice(&[0;20]).unwrap()), Network::Testnet).unwrap(); + let address = Address::from_script(&ScriptBuf::new_p2pkh(&PubkeyHash::from_slice(&[0;20]).unwrap()), Network::Testnet).unwrap(); assert_eq!(invoice.fallback_addresses(), vec![address]); assert_eq!(invoice.private_routes(), vec![&PrivateRoute(route_1), &PrivateRoute(route_2)]); assert_eq!( diff --git a/lightning-invoice/src/payment.rs b/lightning-invoice/src/payment.rs index 89842591fde..a8ffce7bbd9 100644 --- a/lightning-invoice/src/payment.rs +++ b/lightning-invoice/src/payment.rs @@ -10,302 +10,87 @@ //! Convenient utilities for paying Lightning invoices. use crate::Bolt11Invoice; -use crate::prelude::*; +use bitcoin::hashes::Hash; -use bitcoin_hashes::Hash; +use lightning::ln::types::PaymentHash; +use lightning::ln::channelmanager::RecipientOnionFields; +use lightning::routing::router::{PaymentParameters, RouteParameters}; -use lightning::chain; -use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; -use lightning::sign::{NodeSigner, SignerProvider, EntropySource}; -use lightning::ln::PaymentHash; -use lightning::ln::channelmanager::{AChannelManager, ChannelManager, PaymentId, Retry, RetryableSendFailure, RecipientOnionFields, ProbeSendFailure}; -use lightning::routing::router::{PaymentParameters, RouteParameters, Router}; -use lightning::util::logger::Logger; - -use core::fmt::Debug; -use core::ops::Deref; -use core::time::Duration; - -/// Pays the given [`Bolt11Invoice`], retrying if needed based on [`Retry`]. -/// -/// [`Bolt11Invoice::payment_hash`] is used as the [`PaymentId`], which ensures idempotency as long -/// as the payment is still pending. If the payment succeeds, you must ensure that a second payment -/// with the same [`PaymentHash`] is never sent. -/// -/// If you wish to use a different payment idempotency token, see [`pay_invoice_with_id`]. -pub fn pay_invoice( - invoice: &Bolt11Invoice, retry_strategy: Retry, channelmanager: C -) -> Result -where C::Target: AChannelManager, -{ - let payment_id = PaymentId(invoice.payment_hash().into_inner()); - pay_invoice_with_id(invoice, payment_id, retry_strategy, channelmanager.get_cm()) - .map(|()| payment_id) -} - -/// Pays the given [`Bolt11Invoice`] with a custom idempotency key, retrying if needed based on -/// [`Retry`]. +/// Builds the necessary parameters to pay or pre-flight probe the given zero-amount +/// [`Bolt11Invoice`] using [`ChannelManager::send_payment`] or +/// [`ChannelManager::send_preflight_probes`]. /// -/// Note that idempotency is only guaranteed as long as the payment is still pending. Once the -/// payment completes or fails, no idempotency guarantees are made. +/// Prior to paying, you must ensure that the [`Bolt11Invoice::payment_hash`] is unique and the +/// same [`PaymentHash`] has never been paid before. /// -/// You should ensure that the [`Bolt11Invoice::payment_hash`] is unique and the same -/// [`PaymentHash`] has never been paid before. +/// Will always succeed unless the invoice has an amount specified, in which case +/// [`payment_parameters_from_invoice`] should be used. /// -/// See [`pay_invoice`] for a variant which uses the [`PaymentHash`] for the idempotency token. -pub fn pay_invoice_with_id( - invoice: &Bolt11Invoice, payment_id: PaymentId, retry_strategy: Retry, channelmanager: C -) -> Result<(), PaymentError> -where C::Target: AChannelManager, -{ - let amt_msat = invoice.amount_milli_satoshis().ok_or(PaymentError::Invoice("amount missing"))?; - pay_invoice_using_amount(invoice, amt_msat, payment_id, retry_strategy, channelmanager.get_cm()) -} - -/// Pays the given zero-value [`Bolt11Invoice`] using the given amount, retrying if needed based on -/// [`Retry`]. -/// -/// [`Bolt11Invoice::payment_hash`] is used as the [`PaymentId`], which ensures idempotency as long -/// as the payment is still pending. If the payment succeeds, you must ensure that a second payment -/// with the same [`PaymentHash`] is never sent. -/// -/// If you wish to use a different payment idempotency token, see -/// [`pay_zero_value_invoice_with_id`]. -pub fn pay_zero_value_invoice( - invoice: &Bolt11Invoice, amount_msats: u64, retry_strategy: Retry, channelmanager: C -) -> Result -where C::Target: AChannelManager, -{ - let payment_id = PaymentId(invoice.payment_hash().into_inner()); - pay_zero_value_invoice_with_id(invoice, amount_msats, payment_id, retry_strategy, - channelmanager) - .map(|()| payment_id) +/// [`ChannelManager::send_payment`]: lightning::ln::channelmanager::ChannelManager::send_payment +/// [`ChannelManager::send_preflight_probes`]: lightning::ln::channelmanager::ChannelManager::send_preflight_probes +pub fn payment_parameters_from_zero_amount_invoice(invoice: &Bolt11Invoice, amount_msat: u64) +-> Result<(PaymentHash, RecipientOnionFields, RouteParameters), ()> { + if invoice.amount_milli_satoshis().is_some() { + Err(()) + } else { + Ok(params_from_invoice(invoice, amount_msat)) + } } -/// Pays the given zero-value [`Bolt11Invoice`] using the given amount and custom idempotency key, -/// retrying if needed based on [`Retry`]. +/// Builds the necessary parameters to pay or pre-flight probe the given [`Bolt11Invoice`] using +/// [`ChannelManager::send_payment`] or [`ChannelManager::send_preflight_probes`]. /// -/// Note that idempotency is only guaranteed as long as the payment is still pending. Once the -/// payment completes or fails, no idempotency guarantees are made. +/// Prior to paying, you must ensure that the [`Bolt11Invoice::payment_hash`] is unique and the +/// same [`PaymentHash`] has never been paid before. /// -/// You should ensure that the [`Bolt11Invoice::payment_hash`] is unique and the same -/// [`PaymentHash`] has never been paid before. +/// Will always succeed unless the invoice has no amount specified, in which case +/// [`payment_parameters_from_zero_amount_invoice`] should be used. /// -/// See [`pay_zero_value_invoice`] for a variant which uses the [`PaymentHash`] for the -/// idempotency token. -pub fn pay_zero_value_invoice_with_id( - invoice: &Bolt11Invoice, amount_msats: u64, payment_id: PaymentId, retry_strategy: Retry, - channelmanager: C -) -> Result<(), PaymentError> -where C::Target: AChannelManager, -{ - if invoice.amount_milli_satoshis().is_some() { - Err(PaymentError::Invoice("amount unexpected")) +/// [`ChannelManager::send_payment`]: lightning::ln::channelmanager::ChannelManager::send_payment +/// [`ChannelManager::send_preflight_probes`]: lightning::ln::channelmanager::ChannelManager::send_preflight_probes +pub fn payment_parameters_from_invoice(invoice: &Bolt11Invoice) +-> Result<(PaymentHash, RecipientOnionFields, RouteParameters), ()> { + if let Some(amount_msat) = invoice.amount_milli_satoshis() { + Ok(params_from_invoice(invoice, amount_msat)) } else { - pay_invoice_using_amount(invoice, amount_msats, payment_id, retry_strategy, - channelmanager.get_cm()) + Err(()) } } -fn pay_invoice_using_amount( - invoice: &Bolt11Invoice, amount_msats: u64, payment_id: PaymentId, retry_strategy: Retry, - payer: P -) -> Result<(), PaymentError> where P::Target: Payer { - let payment_hash = PaymentHash((*invoice.payment_hash()).into_inner()); +fn params_from_invoice(invoice: &Bolt11Invoice, amount_msat: u64) +-> (PaymentHash, RecipientOnionFields, RouteParameters) { + let payment_hash = PaymentHash((*invoice.payment_hash()).to_byte_array()); + let mut recipient_onion = RecipientOnionFields::secret_only(*invoice.payment_secret()); recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone()); - let mut payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key(), - invoice.min_final_cltv_expiry_delta() as u32) - .with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs()) - .with_route_hints(invoice.route_hints()).unwrap(); - if let Some(features) = invoice.features() { - payment_params = payment_params.with_bolt11_features(features.clone()).unwrap(); - } - let route_params = RouteParameters::from_payment_params_and_value(payment_params, amount_msats); - - payer.send_payment(payment_hash, recipient_onion, payment_id, route_params, retry_strategy) -} - -/// Sends payment probes over all paths of a route that would be used to pay the given invoice. -/// -/// See [`ChannelManager::send_preflight_probes`] for more information. -pub fn preflight_probe_invoice( - invoice: &Bolt11Invoice, channelmanager: C, liquidity_limit_multiplier: Option, -) -> Result, ProbingError> -where C::Target: AChannelManager, -{ - let amount_msat = if let Some(invoice_amount_msat) = invoice.amount_milli_satoshis() { - invoice_amount_msat - } else { - return Err(ProbingError::Invoice("Failed to send probe as no amount was given in the invoice.")); - }; let mut payment_params = PaymentParameters::from_node_id( - invoice.recover_payee_pub_key(), - invoice.min_final_cltv_expiry_delta() as u32, - ) - .with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs()) - .with_route_hints(invoice.route_hints()) - .unwrap(); - - if let Some(features) = invoice.features() { - payment_params = payment_params.with_bolt11_features(features.clone()).unwrap(); - } - let route_params = RouteParameters::from_payment_params_and_value(payment_params, amount_msat); - - channelmanager.get_cm().send_preflight_probes(route_params, liquidity_limit_multiplier) - .map_err(ProbingError::Sending) -} - -/// Sends payment probes over all paths of a route that would be used to pay the given zero-value -/// invoice using the given amount. -/// -/// See [`ChannelManager::send_preflight_probes`] for more information. -pub fn preflight_probe_zero_value_invoice( - invoice: &Bolt11Invoice, amount_msat: u64, channelmanager: C, - liquidity_limit_multiplier: Option, -) -> Result, ProbingError> -where C::Target: AChannelManager, -{ - if invoice.amount_milli_satoshis().is_some() { - return Err(ProbingError::Invoice("amount unexpected")); + invoice.recover_payee_pub_key(), + invoice.min_final_cltv_expiry_delta() as u32 + ) + .with_route_hints(invoice.route_hints()).unwrap(); + if let Some(expiry) = invoice.expires_at() { + payment_params = payment_params.with_expiry_time(expiry.as_secs()); } - - let mut payment_params = PaymentParameters::from_node_id( - invoice.recover_payee_pub_key(), - invoice.min_final_cltv_expiry_delta() as u32, - ) - .with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs()) - .with_route_hints(invoice.route_hints()) - .unwrap(); - if let Some(features) = invoice.features() { payment_params = payment_params.with_bolt11_features(features.clone()).unwrap(); } - let route_params = RouteParameters::from_payment_params_and_value(payment_params, amount_msat); - - channelmanager.get_cm().send_preflight_probes(route_params, liquidity_limit_multiplier) - .map_err(ProbingError::Sending) -} -fn expiry_time_from_unix_epoch(invoice: &Bolt11Invoice) -> Duration { - invoice.signed_invoice.raw_invoice.data.timestamp.0 + invoice.expiry_time() -} - -/// An error that may occur when making a payment. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum PaymentError { - /// An error resulting from the provided [`Bolt11Invoice`] or payment hash. - Invoice(&'static str), - /// An error occurring when sending a payment. - Sending(RetryableSendFailure), -} - -/// An error that may occur when sending a payment probe. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum ProbingError { - /// An error resulting from the provided [`Bolt11Invoice`]. - Invoice(&'static str), - /// An error occurring when sending a payment probe. - Sending(ProbeSendFailure), -} - -/// A trait defining behavior of a [`Bolt11Invoice`] payer. -/// -/// Useful for unit testing internal methods. -trait Payer { - /// Sends a payment over the Lightning Network using the given [`Route`]. - /// - /// [`Route`]: lightning::routing::router::Route - fn send_payment( - &self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, - payment_id: PaymentId, route_params: RouteParameters, retry_strategy: Retry - ) -> Result<(), PaymentError>; -} - -impl Payer for ChannelManager -where - M::Target: chain::Watch<::Signer>, - T::Target: BroadcasterInterface, - ES::Target: EntropySource, - NS::Target: NodeSigner, - SP::Target: SignerProvider, - F::Target: FeeEstimator, - R::Target: Router, - L::Target: Logger, -{ - fn send_payment( - &self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, - payment_id: PaymentId, route_params: RouteParameters, retry_strategy: Retry - ) -> Result<(), PaymentError> { - self.send_payment(payment_hash, recipient_onion, payment_id, route_params, retry_strategy) - .map_err(PaymentError::Sending) - } + let route_params = RouteParameters::from_payment_params_and_value(payment_params, amount_msat); + (payment_hash, recipient_onion, route_params) } #[cfg(test)] mod tests { use super::*; use crate::{InvoiceBuilder, Currency}; - use bitcoin_hashes::sha256::Hash as Sha256; - use lightning::events::Event; - use lightning::ln::msgs::ChannelMessageHandler; - use lightning::ln::{PaymentPreimage, PaymentSecret}; - use lightning::ln::functional_test_utils::*; - use secp256k1::{SecretKey, Secp256k1}; - use std::collections::VecDeque; - use std::time::{SystemTime, Duration}; - - struct TestPayer { - expectations: core::cell::RefCell>, - } - - impl TestPayer { - fn new() -> Self { - Self { - expectations: core::cell::RefCell::new(VecDeque::new()), - } - } - - fn expect_send(self, value_msat: Amount) -> Self { - self.expectations.borrow_mut().push_back(value_msat); - self - } - - fn check_value_msats(&self, actual_value_msats: Amount) { - let expected_value_msats = self.expectations.borrow_mut().pop_front(); - if let Some(expected_value_msats) = expected_value_msats { - assert_eq!(actual_value_msats, expected_value_msats); - } else { - panic!("Unexpected amount: {:?}", actual_value_msats); - } - } - } - - #[derive(Clone, Debug, PartialEq, Eq)] - struct Amount(u64); // msat - - impl Payer for TestPayer { - fn send_payment( - &self, _payment_hash: PaymentHash, _recipient_onion: RecipientOnionFields, - _payment_id: PaymentId, route_params: RouteParameters, _retry_strategy: Retry - ) -> Result<(), PaymentError> { - self.check_value_msats(Amount(route_params.final_value_msat)); - Ok(()) - } - } - - impl Drop for TestPayer { - fn drop(&mut self) { - if std::thread::panicking() { - return; - } - - if !self.expectations.borrow().is_empty() { - panic!("Unsatisfied payment expectations: {:?}", self.expectations.borrow()); - } - } - } + use bitcoin::hashes::sha256::Hash as Sha256; + use lightning::ln::types::PaymentSecret; + use lightning::routing::router::Payee; + use secp256k1::{SecretKey, PublicKey, Secp256k1}; + use core::time::Duration; + #[cfg(feature = "std")] + use std::time::SystemTime; fn duration_since_epoch() -> Duration { #[cfg(feature = "std")] @@ -316,11 +101,14 @@ mod tests { duration_since_epoch } - fn invoice(payment_preimage: PaymentPreimage) -> Bolt11Invoice { - let payment_hash = Sha256::hash(&payment_preimage.0); + #[test] + fn invoice_test() { + let payment_hash = Sha256::hash(&[0; 32]); let private_key = SecretKey::from_slice(&[42; 32]).unwrap(); + let secp_ctx = Secp256k1::new(); + let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key); - InvoiceBuilder::new(Currency::Bitcoin) + let invoice = InvoiceBuilder::new(Currency::Bitcoin) .description("test".into()) .payment_hash(payment_hash) .payment_secret(PaymentSecret([0; 32])) @@ -328,69 +116,63 @@ mod tests { .min_final_cltv_expiry_delta(144) .amount_milli_satoshis(128) .build_signed(|hash| { - Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key) + secp_ctx.sign_ecdsa_recoverable(hash, &private_key) }) - .unwrap() + .unwrap(); + + assert!(payment_parameters_from_zero_amount_invoice(&invoice, 42).is_err()); + + let (hash, onion, params) = payment_parameters_from_invoice(&invoice).unwrap(); + assert_eq!(&hash.0[..], &payment_hash[..]); + assert_eq!(onion.payment_secret, Some(PaymentSecret([0; 32]))); + assert_eq!(params.final_value_msat, 128); + match params.payment_params.payee { + Payee::Clear { node_id, .. } => { + assert_eq!(node_id, public_key); + }, + _ => panic!(), + } } - fn zero_value_invoice(payment_preimage: PaymentPreimage) -> Bolt11Invoice { - let payment_hash = Sha256::hash(&payment_preimage.0); + #[test] + fn zero_value_invoice_test() { + let payment_hash = Sha256::hash(&[0; 32]); let private_key = SecretKey::from_slice(&[42; 32]).unwrap(); + let secp_ctx = Secp256k1::new(); + let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key); - InvoiceBuilder::new(Currency::Bitcoin) + let invoice = InvoiceBuilder::new(Currency::Bitcoin) .description("test".into()) .payment_hash(payment_hash) .payment_secret(PaymentSecret([0; 32])) .duration_since_epoch(duration_since_epoch()) .min_final_cltv_expiry_delta(144) .build_signed(|hash| { - Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key) + secp_ctx.sign_ecdsa_recoverable(hash, &private_key) }) - .unwrap() - } + .unwrap(); - #[test] - fn pays_invoice() { - let payment_id = PaymentId([42; 32]); - let payment_preimage = PaymentPreimage([1; 32]); - let invoice = invoice(payment_preimage); - let final_value_msat = invoice.amount_milli_satoshis().unwrap(); - - let payer = TestPayer::new().expect_send(Amount(final_value_msat)); - pay_invoice_using_amount(&invoice, final_value_msat, payment_id, Retry::Attempts(0), &payer).unwrap(); - } + assert!(payment_parameters_from_invoice(&invoice).is_err()); - #[test] - fn pays_zero_value_invoice() { - let payment_id = PaymentId([42; 32]); - let payment_preimage = PaymentPreimage([1; 32]); - let invoice = zero_value_invoice(payment_preimage); - let amt_msat = 10_000; - - let payer = TestPayer::new().expect_send(Amount(amt_msat)); - pay_invoice_using_amount(&invoice, amt_msat, payment_id, Retry::Attempts(0), &payer).unwrap(); - } - - #[test] - fn fails_paying_zero_value_invoice_with_amount() { - let chanmon_cfgs = create_chanmon_cfgs(1); - let node_cfgs = create_node_cfgs(1, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(1, &node_cfgs, &[None]); - let nodes = create_network(1, &node_cfgs, &node_chanmgrs); - - let payment_preimage = PaymentPreimage([1; 32]); - let invoice = invoice(payment_preimage); - let amt_msat = 10_000; - - match pay_zero_value_invoice(&invoice, amt_msat, Retry::Attempts(0), nodes[0].node) { - Err(PaymentError::Invoice("amount unexpected")) => {}, - _ => panic!() + let (hash, onion, params) = payment_parameters_from_zero_amount_invoice(&invoice, 42).unwrap(); + assert_eq!(&hash.0[..], &payment_hash[..]); + assert_eq!(onion.payment_secret, Some(PaymentSecret([0; 32]))); + assert_eq!(params.final_value_msat, 42); + match params.payment_params.payee { + Payee::Clear { node_id, .. } => { + assert_eq!(node_id, public_key); + }, + _ => panic!(), } } #[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); @@ -418,7 +200,8 @@ mod tests { }) .unwrap(); - pay_invoice(&invoice, Retry::Attempts(0), nodes[0].node).unwrap(); + let (hash, onion, params) = payment_parameters_from_invoice(&invoice).unwrap(); + nodes[0].node.send_payment(hash, onion, PaymentId(hash.0), params, Retry::Attempts(0)).unwrap(); check_added_monitors(&nodes[0], 1); let send_event = SendEvent::from_node(&nodes[0]); nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &send_event.msgs[0]); diff --git a/lightning-invoice/src/ser.rs b/lightning-invoice/src/ser.rs index 9801fee4b3d..ebd394e849f 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 @@ -279,13 +279,13 @@ impl Base32Len for Sha256 { impl ToBase32 for Description { fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - self.as_bytes().write_base32(writer) + self.0.0.as_bytes().write_base32(writer) } } impl Base32Len for Description { fn base32_len(&self) -> usize { - self.0.as_bytes().base32_len() + self.0.0.as_bytes().base32_len() } } diff --git a/lightning-invoice/src/sync.rs b/lightning-invoice/src/sync.rs deleted file mode 100644 index 897c8543993..00000000000 --- a/lightning-invoice/src/sync.rs +++ /dev/null @@ -1,21 +0,0 @@ -use core::cell::RefMut; -use core::ops::{Deref, DerefMut}; - -#[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() - } -} diff --git a/lightning-invoice/src/utils.rs b/lightning-invoice/src/utils.rs index 44928c6209f..4e8ca0c70e7 100644 --- a/lightning-invoice/src/utils.rs +++ b/lightning-invoice/src/utils.rs @@ -4,21 +4,23 @@ 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}; -use lightning::ln::{PaymentHash, PaymentSecret}; +use lightning::ln::types::{PaymentHash, PaymentSecret}; use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, MIN_FINAL_CLTV_EXPIRY_DELTA}; use lightning::ln::channelmanager::{PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA}; use lightning::ln::inbound_payment::{create, create_from_hash, ExpandedKey}; use lightning::routing::gossip::RoutingFees; use lightning::routing::router::{RouteHint, RouteHintHop, Router}; -use lightning::util::logger::Logger; +use lightning::util::logger::{Logger, Record}; use rgb_lib::ContractId; use secp256k1::PublicKey; +use alloc::collections::{btree_map, BTreeMap}; use core::ops::Deref; use core::time::Duration; +#[cfg(not(feature = "std"))] use core::iter::Iterator; /// Utility to create an invoice that can be paid to one of multiple nodes, or a "phantom invoice." @@ -159,7 +161,7 @@ where let invoice = match description { Bolt11InvoiceDescription::Direct(description) => { - InvoiceBuilder::new(network).description(description.0.clone()) + InvoiceBuilder::new(network).description(description.0.0.clone()) } Bolt11InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0), }; @@ -336,7 +338,7 @@ pub fn create_invoice_from_channelmanager, contract_id: Option, amt_rgb: Option, ) -> Result> where - M::Target: chain::Watch<::Signer>, + M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, ES::Target: EntropySource, NS::Target: NodeSigner, @@ -377,7 +379,7 @@ pub fn create_invoice_from_channelmanager_with_description_hash, contract_id: Option, amt_rgb: Option, ) -> Result> where - M::Target: chain::Watch<::Signer>, + M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, ES::Target: EntropySource, NS::Target: NodeSigner, @@ -407,7 +409,7 @@ pub fn create_invoice_from_channelmanager_with_description_hash_and_duration_sin duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option, contract_id: Option, amt_rgb: Option, ) -> Result> where - M::Target: chain::Watch<::Signer>, + M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, ES::Target: EntropySource, NS::Target: NodeSigner, @@ -432,7 +434,7 @@ pub fn create_invoice_from_channelmanager_and_duration_since_epoch, contract_id: Option, amt_rgb: Option, ) -> Result> where - M::Target: chain::Watch<::Signer>, + M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, ES::Target: EntropySource, NS::Target: NodeSigner, @@ -456,7 +458,7 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch, contract_id: Option, amt_rgb: Option, ) -> Result> where - M::Target: chain::Watch<::Signer>, + M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, ES::Target: EntropySource, NS::Target: NodeSigner, @@ -489,7 +491,7 @@ pub fn create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_ invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, min_final_cltv_expiry_delta: Option, contract_id: Option, amt_rgb: Option, ) -> Result> where - M::Target: chain::Watch<::Signer>, + M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, ES::Target: EntropySource, NS::Target: NodeSigner, @@ -519,7 +521,7 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_has payment_secret: PaymentSecret, min_final_cltv_expiry_delta: Option, contract_id: Option, amt_rgb: Option, ) -> Result> where - M::Target: chain::Watch<::Signer>, + M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, ES::Target: EntropySource, NS::Target: NodeSigner, @@ -539,7 +541,7 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_has let invoice = match description { Bolt11InvoiceDescription::Direct(description) => { - InvoiceBuilder::new(network).description(description.0.clone()) + InvoiceBuilder::new(network).description(description.0.0.clone()) } Bolt11InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0), }; @@ -610,7 +612,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; @@ -633,6 +635,7 @@ where log_trace!(logger, "Considering {} channels for invoice route hints", channels.len()); for channel in channels.into_iter().filter(|chan| chan.is_channel_ready) { + let logger = WithChannelDetails::from(logger, &channel); if channel.get_inbound_payment_scid().is_none() || channel.counterparty.forwarding_info.is_none() { log_trace!(logger, "Ignoring channel {} for invoice route hints", &channel.channel_id); continue; @@ -670,7 +673,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. @@ -703,7 +706,7 @@ where channel.inbound_capacity_msat); } } - hash_map::Entry::Vacant(entry) => { + btree_map::Entry::Vacant(entry) => { entry.insert(channel); } } @@ -717,6 +720,7 @@ where .into_iter() .map(|(_, channel)| channel) .filter(|channel| { + let logger = WithChannelDetails::from(logger, &channel); let has_enough_capacity = channel.inbound_capacity_msat >= min_inbound_capacity; let include_channel = if has_pub_unconf_chan { // If we have a public channel, but it doesn't have enough confirmations to (yet) @@ -797,16 +801,39 @@ fn prefer_current_channel(min_inbound_capacity_msat: Option, current_channe current_channel > candidate_channel } +/// Adds relevant context to a [`Record`] before passing it to the wrapped [`Logger`]. +struct WithChannelDetails<'a, 'b, L: Deref> where L::Target: Logger { + /// The logger to delegate to after adding context to the record. + logger: &'a L, + /// The [`ChannelDetails`] for adding relevant context to the logged record. + details: &'b ChannelDetails +} + +impl<'a, 'b, L: Deref> Logger for WithChannelDetails<'a, 'b, L> where L::Target: Logger { + fn log(&self, mut record: Record) { + record.peer_id = Some(self.details.counterparty.node_id); + record.channel_id = Some(self.details.channel_id); + self.logger.log(record) + } +} + +impl<'a, 'b, L: Deref> WithChannelDetails<'a, 'b, L> where L::Target: Logger { + fn from(logger: &'a L, details: &'b ChannelDetails) -> Self { + Self { logger, details } + } +} + #[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::types::PaymentHash; + #[cfg(feature = "std")] + use lightning::ln::types::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; @@ -815,6 +842,7 @@ mod test { use lightning::util::config::UserConfig; use crate::utils::{create_invoice_from_channelmanager_and_duration_since_epoch, rotate_through_iterators}; use std::collections::HashSet; + use lightning::util::string::UntrustedString; #[test] fn test_prefer_current_channel() { @@ -859,7 +887,7 @@ mod test { assert_eq!(invoice.amount_pico_btc(), Some(100_000)); // If no `min_final_cltv_expiry_delta` is specified, then it should be `MIN_FINAL_CLTV_EXPIRY_DELTA`. assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description("test".to_string()))); + assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description(UntrustedString("test".to_string())))); assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into())); // Invoice SCIDs should always use inbound SCID aliases over the real channel ID, if one is @@ -879,8 +907,7 @@ mod test { let route_params = RouteParameters::from_payment_params_and_value( payment_params, invoice.amount_milli_satoshis().unwrap()); let payment_event = { - let mut payment_hash = PaymentHash([0; 32]); - payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]); + let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array()); nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(*invoice.payment_secret()), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap(); @@ -970,7 +997,7 @@ mod test { ).unwrap(); assert_eq!(invoice.amount_pico_btc(), Some(100_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description("test".to_string()))); + assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description(UntrustedString("test".to_string())))); assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&payment_hash.0[..]).unwrap()); } @@ -1148,7 +1175,7 @@ mod test { // is never handled, the `channel.counterparty.forwarding_info` is never assigned. let mut private_chan_cfg = UserConfig::default(); private_chan_cfg.channel_handshake_config.announced_channel = false; - let temporary_channel_id = nodes[2].node.create_channel(nodes[0].node.get_our_node_id(), 1_000_000, 500_000_000, 42, Some(private_chan_cfg)).unwrap(); + let temporary_channel_id = nodes[2].node.create_channel(nodes[0].node.get_our_node_id(), 1_000_000, 500_000_000, 42, None, Some(private_chan_cfg)).unwrap(); let open_channel = get_event_msg!(nodes[2], MessageSendEvent::SendOpenChannel, nodes[0].node.get_our_node_id()); nodes[0].node.handle_open_channel(&nodes[2].node.get_our_node_id(), &open_channel); let accept_channel = get_event_msg!(nodes[0], MessageSendEvent::SendAcceptChannel, nodes[2].node.get_our_node_id()); @@ -1277,6 +1304,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]; @@ -1301,7 +1331,7 @@ mod test { let user_payment_preimage = PaymentPreimage([1; 32]); let payment_hash = if user_generated_pmt_hash { - Some(PaymentHash(Sha256::hash(&user_payment_preimage.0[..]).into_inner())) + Some(PaymentHash(Sha256::hash(&user_payment_preimage.0[..]).to_byte_array())) } else { None }; @@ -1314,7 +1344,7 @@ mod test { route_hints, nodes[1].keys_manager, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, None, Duration::from_secs(genesis_timestamp) ).unwrap(); - let (payment_hash, payment_secret) = (PaymentHash(invoice.payment_hash().into_inner()), *invoice.payment_secret()); + let (payment_hash, payment_secret) = (PaymentHash(invoice.payment_hash().to_byte_array()), *invoice.payment_secret()); let payment_preimage = if user_generated_pmt_hash { user_payment_preimage } else { @@ -1322,7 +1352,7 @@ mod test { }; assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description("test".to_string()))); + assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description(UntrustedString("test".to_string())))); assert_eq!(invoice.route_hints().len(), 2); assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into())); assert!(!invoice.features().unwrap().supports_basic_mpp()); @@ -1334,8 +1364,7 @@ mod test { let params = RouteParameters::from_payment_params_and_value( payment_params, invoice.amount_milli_satoshis().unwrap()); let (payment_event, fwd_idx) = { - let mut payment_hash = PaymentHash([0; 32]); - payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]); + let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array()); nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(*invoice.payment_secret()), PaymentId(payment_hash.0), params, Retry::Attempts(0)).unwrap(); @@ -1460,7 +1489,7 @@ mod test { nodes[2].node.get_phantom_route_hints(), ]; let user_payment_preimage = PaymentPreimage([1; 32]); - let payment_hash = Some(PaymentHash(Sha256::hash(&user_payment_preimage.0[..]).into_inner())); + let payment_hash = Some(PaymentHash(Sha256::hash(&user_payment_preimage.0[..]).to_byte_array())); let non_default_invoice_expiry_secs = 4200; let min_final_cltv_expiry_delta = Some(100); let duration_since_epoch = Duration::from_secs(1234567); @@ -1554,7 +1583,7 @@ mod test { // is never handled, the `channel.counterparty.forwarding_info` is never assigned. let mut private_chan_cfg = UserConfig::default(); private_chan_cfg.channel_handshake_config.announced_channel = false; - let temporary_channel_id = nodes[1].node.create_channel(nodes[3].node.get_our_node_id(), 1_000_000, 500_000_000, 42, Some(private_chan_cfg)).unwrap(); + let temporary_channel_id = nodes[1].node.create_channel(nodes[3].node.get_our_node_id(), 1_000_000, 500_000_000, 42, None, Some(private_chan_cfg)).unwrap(); let open_channel = get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, nodes[3].node.get_our_node_id()); nodes[3].node.handle_open_channel(&nodes[1].node.get_our_node_id(), &open_channel); let accept_channel = get_event_msg!(nodes[3], MessageSendEvent::SendAcceptChannel, nodes[1].node.get_our_node_id()); diff --git a/lightning-invoice/tests/ser_de.rs b/lightning-invoice/tests/ser_de.rs index e21b82eae3c..e5e311fe669 100644 --- a/lightning-invoice/tests/ser_de.rs +++ b/lightning-invoice/tests/ser_de.rs @@ -1,17 +1,13 @@ extern crate bech32; -extern crate bitcoin_hashes; extern crate lightning; extern crate lightning_invoice; extern crate secp256k1; extern crate hex; -use bitcoin::util::address::WitnessVersion; +use bitcoin::address::WitnessVersion; use bitcoin::{PubkeyHash, ScriptHash}; -use bitcoin_hashes::hex::FromHex; -use bitcoin_hashes::{sha256, Hash}; -use lightning::ln::PaymentSecret; -use lightning::routing::gossip::RoutingFees; -use lightning::routing::router::{RouteHint, RouteHintHop}; +use bitcoin::hashes::hex::FromHex; +use bitcoin::hashes::{sha256, Hash}; use lightning_invoice::*; use secp256k1::PublicKey; use secp256k1::ecdsa::{RecoverableSignature, RecoveryId}; @@ -26,7 +22,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { InvoiceBuilder::new(Currency::Bitcoin) .duration_since_epoch(Duration::from_secs(1496314658)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .description("Please consider supporting this project".to_owned()) @@ -34,7 +30,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("8d3ce9e28357337f62da0162d9454df827f83cfe499aeb1c1db349d4d81127425e434ca29929406c23bba1ae8ac6ca32880b38d4bf6ff874024cac34ba9625f1").unwrap(), + &>::from_hex("8d3ce9e28357337f62da0162d9454df827f83cfe499aeb1c1db349d4d81127425e434ca29929406c23bba1ae8ac6ca32880b38d4bf6ff874024cac34ba9625f1").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -47,7 +43,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .amount_milli_satoshis(250_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .description("1 cup coffee".to_owned()) @@ -56,7 +52,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("e59e3ffbd3945e4334879158d31e89b076dff54f3fa7979ae79df2db9dcaf5896cbfe1a478b8d2307e92c88139464cb7e6ef26e414c4abe33337961ddc5e8ab1").unwrap(), + &>::from_hex("e59e3ffbd3945e4334879158d31e89b076dff54f3fa7979ae79df2db9dcaf5896cbfe1a478b8d2307e92c88139464cb7e6ef26e414c4abe33337961ddc5e8ab1").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -69,7 +65,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .amount_milli_satoshis(250_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .description("ナンセンス 1杯".to_owned()) @@ -78,7 +74,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("bae41ef385e0fc972977c7ea42b12cbd76577d2412919da8a8a22f9577b6507710c0e96dd78c821dea16453037f717f44aa7e3d196ebb18fbb97307dcb7336c3").unwrap(), + &>::from_hex("bae41ef385e0fc972977c7ea42b12cbd76577d2412919da8a8a22f9577b6507710c0e96dd78c821dea16453037f717f44aa7e3d196ebb18fbb97307dcb7336c3").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -92,14 +88,14 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .duration_since_epoch(Duration::from_secs(1496314658)) .description_hash(sha256::Hash::hash(b"One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .build_raw() .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("f67a5f696648fa4fb102e1a07b230e54722f8e024cee71e80b4847ac191da3fb2d2cdb28cc32344d7e9a9cf5c9b6a0ee0582ae46e9938b9c81e344a4dbb5289d").unwrap(), + &>::from_hex("f67a5f696648fa4fb102e1a07b230e54722f8e024cee71e80b4847ac191da3fb2d2cdb28cc32344d7e9a9cf5c9b6a0ee0582ae46e9938b9c81e344a4dbb5289d").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -113,7 +109,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .duration_since_epoch(Duration::from_secs(1496314658)) .description_hash(sha256::Hash::hash(b"One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .fallback(Fallback::PubKeyHash(PubkeyHash::from_slice(&[49, 114, 181, 101, 79, 102, 131, 200, 251, 20, 105, 89, 211, 71, 206, 48, 60, 174, 76, 167]).unwrap())) @@ -121,7 +117,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("6ca95a74dc32e69ced6175b15a5cc56a92bf19f5dace0f134b7d94d464b9f5cf6090a18d48b243f289394d17bdf89466d8e6b37df5981f696bc3dd5986e1bee1").unwrap(), + &>::from_hex("6ca95a74dc32e69ced6175b15a5cc56a92bf19f5dace0f134b7d94d464b9f5cf6090a18d48b243f289394d17bdf89466d8e6b37df5981f696bc3dd5986e1bee1").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -135,12 +131,12 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .duration_since_epoch(Duration::from_secs(1496314658)) .description_hash(sha256::Hash::hash(b"One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .fallback(Fallback::PubKeyHash(PubkeyHash::from_slice(&[4, 182, 31, 125, 193, 234, 13, 201, 148, 36, 70, 76, 196, 6, 77, 197, 100, 217, 30, 137]).unwrap())) .private_route(RouteHint(vec![RouteHintHop { - src_node_id: PublicKey::from_slice(&hex::decode( + src_node_id: PublicKey::from_slice(&>::from_hex( "029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255" ).unwrap()).unwrap(), short_channel_id: (66051 << 40) | (263430 << 16) | 1800, @@ -148,7 +144,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { cltv_expiry_delta: 3, htlc_maximum_msat: None, htlc_minimum_msat: None, }, RouteHintHop { - src_node_id: PublicKey::from_slice(&hex::decode( + src_node_id: PublicKey::from_slice(&>::from_hex( "039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255" ).unwrap()).unwrap(), short_channel_id: (197637 << 40) | (395016 << 16) | 2314, @@ -160,7 +156,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("6a6586db4e8f6d40e3a5bb92e4df5110c627e9ce493af237e20a046b4e86ea200178c59564ecf892f33a9558bf041b6ad2cb8292d7a6c351fbb7f2ae2d16b54e").unwrap(), + &>::from_hex("6a6586db4e8f6d40e3a5bb92e4df5110c627e9ce493af237e20a046b4e86ea200178c59564ecf892f33a9558bf041b6ad2cb8292d7a6c351fbb7f2ae2d16b54e").unwrap(), RecoveryId::from_i32(0).unwrap() ) }).unwrap(), @@ -174,7 +170,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .duration_since_epoch(Duration::from_secs(1496314658)) .description_hash(sha256::Hash::hash(b"One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .fallback(Fallback::ScriptHash(ScriptHash::from_slice(&[143, 85, 86, 59, 154, 25, 243, 33, 194, 17, 233, 185, 243, 140, 223, 104, 110, 160, 120, 69]).unwrap())) @@ -182,7 +178,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("16810439d1a9bfd5a65acc61340dc92448bb2d456a80b58ce012b73cb5202438020500c9ab7ef5573a4d174c811f669885ae27f895bb3a3be52c243589f87518").unwrap(), + &>::from_hex("16810439d1a9bfd5a65acc61340dc92448bb2d456a80b58ce012b73cb5202438020500c9ab7ef5573a4d174c811f669885ae27f895bb3a3be52c243589f87518").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -196,7 +192,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .duration_since_epoch(Duration::from_secs(1496314658)) .description_hash(sha256::Hash::hash(b"One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .fallback(Fallback::SegWitProgram { version: WitnessVersion::V0, @@ -206,7 +202,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("5a8bd7b97c1cc9055ee60cf2356621f8752248e037a953886a1782b44a58f5ff2d94e6bc89b7b514541a3603bb33722b6c08aa1a3639d34becc549a99fea6eae").unwrap(), + &>::from_hex("5a8bd7b97c1cc9055ee60cf2356621f8752248e037a953886a1782b44a58f5ff2d94e6bc89b7b514541a3603bb33722b6c08aa1a3639d34becc549a99fea6eae").unwrap(), RecoveryId::from_i32(0).unwrap() ) }).unwrap(), @@ -220,7 +216,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .duration_since_epoch(Duration::from_secs(1496314658)) .description_hash(sha256::Hash::hash(b"One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .fallback(Fallback::SegWitProgram { version: WitnessVersion::V0, @@ -230,7 +226,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("2b3ec248f80301a421817369194f012cdd8af8df1c279981420f9e901e20fa3309d791e11355e609b59ce4a220852a0cd55ab862b1785a83b206c90fa74d01c8").unwrap(), + &>::from_hex("2b3ec248f80301a421817369194f012cdd8af8df1c279981420f9e901e20fa3309d791e11355e609b59ce4a220852a0cd55ab862b1785a83b206c90fa74d01c8").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -243,14 +239,14 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .amount_milli_satoshis(967878534) .duration_since_epoch(Duration::from_secs(1572468703)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "462264ede7e14047e9b249da94fefc47f41f7d02ee9b091815a5506bc8abf75f" ).unwrap()) .expiry_time(Duration::from_secs(604800)) .min_final_cltv_expiry_delta(10) .description("Blockstream Store: 88.85 USD for Blockstream Ledger Nano S x 1, \"Back In My Day\" Sticker x 2, \"I Got Lightning Working\" Sticker x 2 and 1 more items".to_owned()) .private_route(RouteHint(vec![RouteHintHop { - src_node_id: PublicKey::from_slice(&hex::decode( + src_node_id: PublicKey::from_slice(&>::from_hex( "03d06758583bb5154774a6eb221b1276c9e82d65bbaceca806d90e20c108f4b1c7" ).unwrap()).unwrap(), short_channel_id: (589390 << 40) | (3312 << 16) | 1, @@ -262,7 +258,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("1b1160cf6186b55722c1ac7ea502086baaccaabdc76b326e666b7f309d972b15069bfca11cd365304b36f48230cc12f3f13a017aab65f7c165a169df32282a58").unwrap(), + &>::from_hex("1b1160cf6186b55722c1ac7ea502086baaccaabdc76b326e666b7f309d972b15069bfca11cd365304b36f48230cc12f3f13a017aab65f7c165a169df32282a58").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -275,7 +271,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .amount_milli_satoshis(2_500_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .description("coffee beans".to_owned()) @@ -283,7 +279,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("5755469bf4b8e6b6ae7a1308d5f9bad5c82812e0855cd24fac242aa323fa820c5c551ede4faeabcb7fb6d5a464ad0e35c86f615589ee0e0c250c216a662198c1").unwrap(), + &>::from_hex("5755469bf4b8e6b6ae7a1308d5f9bad5c82812e0855cd24fac242aa323fa820c5c551ede4faeabcb7fb6d5a464ad0e35c86f615589ee0e0c250c216a662198c1").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -296,7 +292,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .amount_milli_satoshis(2_500_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .description("coffee beans".to_owned()) @@ -304,7 +300,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("5755469bf4b8e6b6ae7a1308d5f9bad5c82812e0855cd24fac242aa323fa820c5c551ede4faeabcb7fb6d5a464ad0e35c86f615589ee0e0c250c216a662198c1").unwrap(), + &>::from_hex("5755469bf4b8e6b6ae7a1308d5f9bad5c82812e0855cd24fac242aa323fa820c5c551ede4faeabcb7fb6d5a464ad0e35c86f615589ee0e0c250c216a662198c1").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -317,7 +313,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .amount_milli_satoshis(2_500_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .description("coffee beans".to_owned()) @@ -325,7 +321,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("150a5252308f25bc2641a186de87470189bb003774326beee33b9a2a720d1584386631c5dda6fc3195f97464bfc93d2574868eadd767d6da1078329c4349c837").unwrap(), + &>::from_hex("150a5252308f25bc2641a186de87470189bb003774326beee33b9a2a720d1584386631c5dda6fc3195f97464bfc93d2574868eadd767d6da1078329c4349c837").unwrap(), RecoveryId::from_i32(0).unwrap() ) }).unwrap(), @@ -337,13 +333,13 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { InvoiceBuilder::new(Currency::Bitcoin) .amount_milli_satoshis(1_000_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .description("payment metadata inside".to_owned()) - .payment_metadata(hex::decode("01fafaf0").unwrap()) + .payment_metadata(>::from_hex("01fafaf0").unwrap()) .require_payment_metadata() - .payee_pub_key(PublicKey::from_slice(&hex::decode( + .payee_pub_key(PublicKey::from_slice(&>::from_hex( "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" ).unwrap()).unwrap()) .payment_secret(PaymentSecret([0x11; 32])) @@ -351,7 +347,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("2150ed137ddb54f9736c6a0290ded709d22bddb7261d1d6518dffb467c6b1eef02afc182491bdacd00b65c83554c914a1c53c61b0a4ef04eccccdfb4365ed259").unwrap(), + &>::from_hex("2150ed137ddb54f9736c6a0290ded709d22bddb7261d1d6518dffb467c6b1eef02afc182491bdacd00b65c83554c914a1c53c61b0a4ef04eccccdfb4365ed259").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -363,18 +359,18 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { InvoiceBuilder::new(Currency::Bitcoin) .amount_milli_satoshis(1_000_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .description("payment metadata inside".to_owned()) - .payment_metadata(hex::decode("01fafaf0").unwrap()) + .payment_metadata(>::from_hex("01fafaf0").unwrap()) .require_payment_metadata() .payment_secret(PaymentSecret([0x11; 32])) .build_raw() .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("f5d27be7d9c27d3aa521bc35d77cabd6bda18f1f61716445b19e27e4e17a887508ea8de5a8e1d94f561248f65434e61a221160dac1f1991b9c0f1057b269d898").unwrap(), + &>::from_hex("f5d27be7d9c27d3aa521bc35d77cabd6bda18f1f61716445b19e27e4e17a887508ea8de5a8e1d94f561248f65434e61a221160dac1f1991b9c0f1057b269d898").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), diff --git a/lightning-net-tokio/Cargo.toml b/lightning-net-tokio/Cargo.toml index 6cd3a1fa382..478986bb837 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.123" authors = ["Matt Corallo"] license = "MIT OR Apache-2.0" repository = "https://github.com/lightningdevkit/rust-lightning/" @@ -8,17 +8,17 @@ 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 rustdoc-args = ["--cfg", "docsrs"] [dependencies] -bitcoin = "0.29.0" -lightning = { version = "0.0.118", path = "../lightning" } -tokio = { version = "1.0", features = [ "rt", "sync", "net", "time" ] } +bitcoin = "0.30.2" +lightning = { version = "0.0.123", 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.123", path = "../lightning", features = ["_test_utils"] } diff --git a/lightning-net-tokio/src/lib.rs b/lightning-net-tokio/src/lib.rs index bac18b2b398..6d001ca67fd 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))] @@ -209,7 +208,12 @@ impl Connection { break Disconnect::CloseConnection; } }, - SelectorOutput::B(_) => {}, + SelectorOutput::B(some) => { + // The mpsc Receiver should only return `None` if the write side has been + // dropped, but that shouldn't be possible since its referenced by the Self in + // `us`. + debug_assert!(some.is_some()); + }, SelectorOutput::C(res) => { if res.is_err() { break Disconnect::PeerDisconnected; } match reader.try_read(&mut buf) { @@ -422,7 +426,11 @@ const SOCK_WAKER_VTABLE: task::RawWakerVTable = task::RawWakerVTable::new(clone_socket_waker, wake_socket_waker, wake_socket_waker_by_ref, drop_socket_waker); fn clone_socket_waker(orig_ptr: *const ()) -> task::RawWaker { - write_avail_to_waker(orig_ptr as *const mpsc::Sender<()>) + let new_waker = unsafe { Arc::from_raw(orig_ptr as *const mpsc::Sender<()>) }; + let res = write_avail_to_waker(&new_waker); + // Don't decrement the refcount when dropping new_waker by turning it back `into_raw`. + let _ = Arc::into_raw(new_waker); + res } // When waking, an error should be fine. Most likely we got two send_datas in a row, both of which // failed to fully write, but we only need to call write_buffer_space_avail() once. Otherwise, the @@ -435,16 +443,15 @@ fn wake_socket_waker(orig_ptr: *const ()) { } fn wake_socket_waker_by_ref(orig_ptr: *const ()) { let sender_ptr = orig_ptr as *const mpsc::Sender<()>; - let sender = unsafe { (*sender_ptr).clone() }; + let sender = unsafe { &*sender_ptr }; let _ = sender.try_send(()); } fn drop_socket_waker(orig_ptr: *const ()) { - let _orig_box = unsafe { Box::from_raw(orig_ptr as *mut mpsc::Sender<()>) }; - // _orig_box is now dropped + let _orig_arc = unsafe { Arc::from_raw(orig_ptr as *mut mpsc::Sender<()>) }; + // _orig_arc is now dropped } -fn write_avail_to_waker(sender: *const mpsc::Sender<()>) -> task::RawWaker { - let new_box = Box::leak(Box::new(unsafe { (*sender).clone() })); - let new_ptr = new_box as *const mpsc::Sender<()>; +fn write_avail_to_waker(sender: &Arc>) -> task::RawWaker { + let new_ptr = Arc::into_raw(Arc::clone(&sender)); task::RawWaker::new(new_ptr as *const (), &SOCK_WAKER_VTABLE) } @@ -452,12 +459,20 @@ fn write_avail_to_waker(sender: *const mpsc::Sender<()>) -> task::RawWaker { /// type in the template of PeerHandler. pub struct SocketDescriptor { conn: Arc>, + // We store a copy of the mpsc::Sender to wake the read task in an Arc here. While we can + // simply clone the sender and store a copy in each waker, that would require allocating for + // each waker. Instead, we can simply `Arc::clone`, creating a new reference and store the + // pointer in the waker. + write_avail_sender: Arc>, id: u64, } impl SocketDescriptor { fn new(conn: Arc>) -> Self { - let id = conn.lock().unwrap().id; - Self { conn, id } + let (id, write_avail_sender) = { + let us = conn.lock().unwrap(); + (us.id, Arc::new(us.write_avail.clone())) + }; + Self { conn, id, write_avail_sender } } } impl peer_handler::SocketDescriptor for SocketDescriptor { @@ -480,7 +495,7 @@ impl peer_handler::SocketDescriptor for SocketDescriptor { let _ = us.read_waker.try_send(()); } if data.is_empty() { return 0; } - let waker = unsafe { task::Waker::from_raw(write_avail_to_waker(&us.write_avail)) }; + let waker = unsafe { task::Waker::from_raw(write_avail_to_waker(&self.write_avail_sender)) }; let mut ctx = task::Context::from_waker(&waker); let mut written_len = 0; loop { @@ -492,6 +507,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, } }, @@ -522,6 +540,7 @@ impl Clone for SocketDescriptor { Self { conn: Arc::clone(&self.conn), id: self.id, + write_avail_sender: Arc::clone(&self.write_avail_sender), } } } @@ -542,7 +561,6 @@ mod tests { use lightning::ln::features::*; use lightning::ln::msgs::*; use lightning::ln::peer_handler::{MessageHandler, PeerManager}; - use lightning::ln::features::NodeFeatures; use lightning::routing::gossip::NodeId; use lightning::events::*; use lightning::util::test_utils::TestNodeSigner; @@ -559,7 +577,7 @@ mod tests { pub struct TestLogger(); impl lightning::util::logger::Logger for TestLogger { - fn log(&self, record: &lightning::util::logger::Record) { + fn log(&self, record: lightning::util::logger::Record) { println!("{:<5} [{} : {}, {}] {}", record.level.to_string(), record.module_path, record.file, record.line, record.args); } } @@ -605,6 +623,13 @@ mod tests { fn handle_channel_update(&self, _their_node_id: &PublicKey, _msg: &ChannelUpdate) {} fn handle_open_channel_v2(&self, _their_node_id: &PublicKey, _msg: &OpenChannelV2) {} fn handle_accept_channel_v2(&self, _their_node_id: &PublicKey, _msg: &AcceptChannelV2) {} + fn handle_stfu(&self, _their_node_id: &PublicKey, _msg: &Stfu) {} + #[cfg(splicing)] + fn handle_splice(&self, _their_node_id: &PublicKey, _msg: &Splice) {} + #[cfg(splicing)] + fn handle_splice_ack(&self, _their_node_id: &PublicKey, _msg: &SpliceAck) {} + #[cfg(splicing)] + fn handle_splice_locked(&self, _their_node_id: &PublicKey, _msg: &SpliceLocked) {} fn handle_tx_add_input(&self, _their_node_id: &PublicKey, _msg: &TxAddInput) {} fn handle_tx_add_output(&self, _their_node_id: &PublicKey, _msg: &TxAddOutput) {} fn handle_tx_remove_input(&self, _their_node_id: &PublicKey, _msg: &TxRemoveInput) {} diff --git a/lightning-persister/Cargo.toml b/lightning-persister/Cargo.toml index 525b9507329..a826f8ca51e 100644 --- a/lightning-persister/Cargo.toml +++ b/lightning-persister/Cargo.toml @@ -1,21 +1,21 @@ [package] name = "lightning-persister" -version = "0.0.118" +version = "0.0.123" 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 rustdoc-args = ["--cfg", "docsrs"] [dependencies] -bitcoin = "0.29.0" -lightning = { version = "0.0.118", path = "../lightning" } +bitcoin = "0.30.2" +lightning = { version = "0.0.123", 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"] } -bitcoin = { version = "0.29.0", default-features = false } +lightning = { version = "0.0.123", 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 c665d8083cb..364a3ee706f 100644 --- a/lightning-persister/src/fs_store.rs +++ b/lightning-persister/src/fs_store.rs @@ -369,7 +369,6 @@ mod tests { use super::*; use crate::test_utils::{do_read_write_remove_list_persist, do_test_store}; - use bitcoin::hashes::hex::FromHex; use bitcoin::Txid; use lightning::chain::ChannelMonitorUpdateStatus; @@ -380,12 +379,7 @@ mod tests { use lightning::ln::functional_test_utils::*; use lightning::util::test_utils; use lightning::util::persist::read_channel_monitors; - use std::fs; - #[cfg(target_os = "windows")] - use { - lightning::get_event_msg, - lightning::ln::msgs::ChannelMessageHandler, - }; + use std::str::FromStr; impl Drop for FilesystemStore { fn drop(&mut self) { @@ -455,7 +449,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 @@ -466,7 +460,7 @@ mod tests { fs::set_permissions(path, perms).unwrap(); let test_txo = OutPoint { - txid: Txid::from_hex("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(), + txid: Txid::from_str("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(), index: 0 }; match store.persist_new_channel(test_txo, &added_monitors[0].1, update_id.2) { @@ -494,7 +488,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 @@ -503,7 +497,7 @@ mod tests { let store = FilesystemStore::new(":<>/".into()); let test_txo = OutPoint { - txid: Txid::from_hex("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(), + txid: Txid::from_str("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(), index: 0 }; match store.persist_new_channel(test_txo, &added_monitors[0].1, update_id.2) { 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 7853c32e70c..b7843a59db6 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.123" 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 } -bitcoin = { version = "0.29.0", default-features = false } +lightning = { version = "0.0.123", 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.123", 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 5a61be7990e..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)] @@ -49,7 +48,7 @@ //! # use lightning::util::logger::{Logger, Record}; //! # struct FakeLogger {} //! # impl Logger for FakeLogger { -//! # fn log(&self, record: &Record) { } +//! # fn log(&self, record: Record) { } //! # } //! # let logger = FakeLogger {}; //! @@ -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..b3fae0ffd8b 100644 --- a/lightning-rapid-gossip-sync/src/processing.rs +++ b/lightning-rapid-gossip-sync/src/processing.rs @@ -14,13 +14,12 @@ 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}; -#[cfg(not(feature = "std"))] +#[cfg(all(not(feature = "std"), not(test)))] use alloc::{vec::Vec, borrow::ToOwned}; /// The purpose of this prefix is to identify the serialization format, should other rapid gossip @@ -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 24f9c687a08..9bf2b6e37c1 100644 --- a/lightning-transaction-sync/Cargo.toml +++ b/lightning-transaction-sync/Cargo.toml @@ -1,35 +1,41 @@ [package] name = "lightning-transaction-sync" -version = "0.0.118" +version = "0.0.123" 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", "reqwest/rustls-tls"] +esplora-async-https = ["esplora-async", "esplora-client/async-https-rustls"] esplora-blocking = ["esplora-client/blocking"] +electrum = ["electrum-client"] async-interface = [] [dependencies] -lightning = { version = "0.0.118", path = "../lightning", default-features = false } -bitcoin = { version = "0.29.0", default-features = false } +lightning = { version = "0.0.123", 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 } -esplora-client = { version = "0.4", default-features = false, optional = true } -reqwest = { version = "0.11", optional = true, default-features = false, features = ["json"] } +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"] } -electrsd = { version = "0.22.0", features = ["legacy", "esplora_a33e97e1", "bitcoind_23_0"] } -electrum-client = "0.12.0" -tokio = { version = "1.14.0", features = ["full"] } +lightning = { version = "0.0.123", 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/common.rs b/lightning-transaction-sync/src/common.rs index a6ee61e90f2..c635f7385c6 100644 --- a/lightning-transaction-sync/src/common.rs +++ b/lightning-transaction-sync/src/common.rs @@ -1,5 +1,7 @@ -use lightning::chain::WatchedOutput; -use bitcoin::{Txid, BlockHash, Transaction, BlockHeader, OutPoint}; +use lightning::chain::{Confirm, WatchedOutput}; +use lightning::chain::channelmonitor::ANTI_REORG_DELAY; +use bitcoin::{Txid, BlockHash, Transaction, OutPoint}; +use bitcoin::block::Header; use std::collections::{HashSet, HashMap}; @@ -12,6 +14,9 @@ pub(crate) struct SyncState { // Outputs that were previously processed, but must not be forgotten yet as // as we still need to monitor any spends on-chain. pub watched_outputs: HashMap, + // Outputs for which we previously saw a spend on-chain but kept around until the spends reach + // sufficient depth. + pub outputs_spends_pending_threshold_conf: Vec<(Txid, u32, OutPoint, WatchedOutput)>, // The tip hash observed during our last sync. pub last_sync_hash: Option, // Indicates whether we need to resync, e.g., after encountering an error. @@ -23,10 +28,63 @@ impl SyncState { Self { watched_transactions: HashSet::new(), watched_outputs: HashMap::new(), + outputs_spends_pending_threshold_conf: Vec::new(), last_sync_hash: None, pending_sync: false, } } + pub fn sync_unconfirmed_transactions( + &mut self, confirmables: &Vec<&(dyn Confirm + Sync + Send)>, + unconfirmed_txs: Vec, + ) { + for txid in unconfirmed_txs { + for c in confirmables { + c.transaction_unconfirmed(&txid); + } + + self.watched_transactions.insert(txid); + + // If a previously-confirmed output spend is unconfirmed, re-add the watched output to + // the tracking map. + self.outputs_spends_pending_threshold_conf.retain(|(conf_txid, _, prev_outpoint, output)| { + if txid == *conf_txid { + self.watched_outputs.insert(*prev_outpoint, output.clone()); + false + } else { + true + } + }) + } + } + + pub fn sync_confirmed_transactions( + &mut self, confirmables: &Vec<&(dyn Confirm + Sync + Send)>, + confirmed_txs: Vec + ) { + for ctx in confirmed_txs { + for c in confirmables { + c.transactions_confirmed( + &ctx.block_header, + &[(ctx.pos, &ctx.tx)], + ctx.block_height, + ); + } + + self.watched_transactions.remove(&ctx.tx.txid()); + + for input in &ctx.tx.input { + if let Some(output) = self.watched_outputs.remove(&input.previous_output) { + self.outputs_spends_pending_threshold_conf.push((ctx.tx.txid(), ctx.block_height, input.previous_output, output)); + } + } + } + } + + pub fn prune_output_spends(&mut self, cur_height: u32) { + self.outputs_spends_pending_threshold_conf.retain(|(_, conf_height, _, _)| { + cur_height < conf_height + ANTI_REORG_DELAY - 1 + }); + } } @@ -67,9 +125,11 @@ impl FilterQueue { } } +#[derive(Debug)] pub(crate) struct ConfirmedTx { pub tx: Transaction, - pub block_header: BlockHeader, + pub txid: Txid, + pub block_header: Header, pub block_height: u32, pub pos: usize, } diff --git a/lightning-transaction-sync/src/electrum.rs b/lightning-transaction-sync/src/electrum.rs new file mode 100644 index 00000000000..046f698d4a2 --- /dev/null +++ b/lightning-transaction-sync/src/electrum.rs @@ -0,0 +1,494 @@ +use crate::common::{ConfirmedTx, SyncState, FilterQueue}; +use crate::error::{TxSyncError, InternalError}; + +use electrum_client::Client as ElectrumClient; +use electrum_client::ElectrumApi; +use electrum_client::GetMerkleRes; + +use lightning::util::logger::Logger; +use lightning::{log_error, log_debug, log_trace}; +use lightning::chain::WatchedOutput; +use lightning::chain::{Confirm, Filter}; + +use bitcoin::{BlockHash, Script, Transaction, Txid}; +use bitcoin::block::Header; +use bitcoin::hash_types::TxMerkleNode; +use bitcoin::hashes::Hash; +use bitcoin::hashes::sha256d::Hash as Sha256d; + +use std::ops::Deref; +use std::sync::Mutex; +use std::collections::HashSet; +use std::time::Instant; + +/// Synchronizes LDK with a given Electrum server. +/// +/// Needs to be registered with a [`ChainMonitor`] via the [`Filter`] interface to be informed of +/// transactions and outputs to monitor for on-chain confirmation, unconfirmation, and +/// reconfirmation. +/// +/// Note that registration via [`Filter`] needs to happen before any calls to +/// [`Watch::watch_channel`] to ensure we get notified of the items to monitor. +/// +/// [`ChainMonitor`]: lightning::chain::chainmonitor::ChainMonitor +/// [`Watch::watch_channel`]: lightning::chain::Watch::watch_channel +/// [`Filter`]: lightning::chain::Filter +pub struct ElectrumSyncClient +where + L::Target: Logger, +{ + sync_state: Mutex, + queue: Mutex, + client: ElectrumClient, + logger: L, +} + +impl ElectrumSyncClient +where + L::Target: Logger, +{ + /// Returns a new [`ElectrumSyncClient`] object. + pub fn new(server_url: String, logger: L) -> Result { + let client = ElectrumClient::new(&server_url).map_err(|e| { + log_error!(logger, "Failed to connect to electrum server '{}': {}", server_url, e); + e + })?; + + Self::from_client(client, logger) + } + + /// Returns a new [`ElectrumSyncClient`] object using the given Electrum client. + pub fn from_client(client: ElectrumClient, logger: L) -> Result { + let sync_state = Mutex::new(SyncState::new()); + let queue = Mutex::new(FilterQueue::new()); + + Ok(Self { + sync_state, + queue, + client, + logger, + }) + } + + /// Synchronizes the given `confirmables` via their [`Confirm`] interface implementations. This + /// method should be called regularly to keep LDK up-to-date with current chain data. + /// + /// For example, instances of [`ChannelManager`] and [`ChainMonitor`] can be informed about the + /// newest on-chain activity related to the items previously registered via the [`Filter`] + /// interface. + /// + /// [`Confirm`]: lightning::chain::Confirm + /// [`ChainMonitor`]: lightning::chain::chainmonitor::ChainMonitor + /// [`ChannelManager`]: lightning::ln::channelmanager::ChannelManager + /// [`Filter`]: lightning::chain::Filter + pub fn sync(&self, confirmables: Vec<&(dyn Confirm + Sync + Send)>) -> Result<(), TxSyncError> { + // This lock makes sure we're syncing once at a time. + 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; + + // Clear any header notifications we might have gotten to keep the queue count low. + while let Some(_) = self.client.block_headers_pop()? {} + + let tip_notification = self.client.block_headers_subscribe()?; + let mut tip_header = tip_notification.header; + let mut tip_height = tip_notification.height as u32; + + loop { + let pending_registrations = self.queue.lock().unwrap().process_queues(&mut sync_state); + let tip_is_new = Some(tip_header.block_hash()) != sync_state.last_sync_hash; + + // We loop until any registered transactions have been processed at least once, or the + // tip hasn't been updated during the last iteration. + if !sync_state.pending_sync && !pending_registrations && !tip_is_new { + // Nothing to do. + break; + } else { + // Update the known tip to the newest one. + if tip_is_new { + // First check for any unconfirmed transactions and act on it immediately. + match self.get_unconfirmed_transactions(&confirmables) { + Ok(unconfirmed_txs) => { + // Double-check the tip hash. If it changed, a reorg happened since + // we started syncing and we need to restart last-minute. + match self.check_update_tip(&mut tip_header, &mut tip_height) { + Ok(false) => { + num_unconfirmed += unconfirmed_txs.len(); + sync_state.sync_unconfirmed_transactions( + &confirmables, + unconfirmed_txs + ); + } + Ok(true) => { + log_debug!(self.logger, + "Encountered inconsistency during transaction sync, restarting."); + sync_state.pending_sync = true; + continue; + } + Err(err) => { + // (Semi-)permanent failure, retry later. + log_error!(self.logger, + "Failed during transaction sync, aborting. Synced so far: {} confirmed, {} unconfirmed.", + num_confirmed, + num_unconfirmed + ); + sync_state.pending_sync = true; + return Err(TxSyncError::from(err)); + } + } + }, + Err(err) => { + // (Semi-)permanent failure, retry later. + log_error!(self.logger, + "Failed during transaction sync, aborting. Synced so far: {} confirmed, {} unconfirmed.", + num_confirmed, + num_unconfirmed + ); + sync_state.pending_sync = true; + return Err(TxSyncError::from(err)); + } + } + + // Update the best block. + for c in &confirmables { + c.best_block_updated(&tip_header, tip_height); + } + + // Prune any sufficiently confirmed output spends + sync_state.prune_output_spends(tip_height); + } + + match self.get_confirmed_transactions(&sync_state) { + Ok(confirmed_txs) => { + // Double-check the tip hash. If it changed, a reorg happened since + // we started syncing and we need to restart last-minute. + match self.check_update_tip(&mut tip_header, &mut tip_height) { + Ok(false) => { + num_confirmed += confirmed_txs.len(); + sync_state.sync_confirmed_transactions( + &confirmables, + confirmed_txs + ); + } + Ok(true) => { + log_debug!(self.logger, + "Encountered inconsistency during transaction sync, restarting."); + sync_state.pending_sync = true; + continue; + } + Err(err) => { + // (Semi-)permanent failure, retry later. + log_error!(self.logger, + "Failed during transaction sync, aborting. Synced so far: {} confirmed, {} unconfirmed.", + num_confirmed, + num_unconfirmed + ); + sync_state.pending_sync = true; + return Err(TxSyncError::from(err)); + } + } + } + Err(InternalError::Inconsistency) => { + // Immediately restart syncing when we encounter any inconsistencies. + log_debug!(self.logger, + "Encountered inconsistency during transaction sync, restarting."); + sync_state.pending_sync = true; + continue; + } + Err(err) => { + // (Semi-)permanent failure, retry later. + log_error!(self.logger, + "Failed during transaction sync, aborting. Synced so far: {} confirmed, {} unconfirmed.", + num_confirmed, + num_unconfirmed + ); + sync_state.pending_sync = true; + return Err(TxSyncError::from(err)); + } + } + sync_state.last_sync_hash = Some(tip_header.block_hash()); + 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(()) + } + + fn check_update_tip(&self, cur_tip_header: &mut Header, cur_tip_height: &mut u32) + -> Result + { + let check_notification = self.client.block_headers_subscribe()?; + let check_tip_hash = check_notification.header.block_hash(); + + // Restart if either the tip changed or we got some divergent tip + // change notification since we started. In the latter case we + // make sure we clear the queue before continuing. + let mut restart_sync = check_tip_hash != cur_tip_header.block_hash(); + while let Some(queued_notif) = self.client.block_headers_pop()? { + if queued_notif.header.block_hash() != check_tip_hash { + restart_sync = true + } + } + + if restart_sync { + *cur_tip_header = check_notification.header; + *cur_tip_height = check_notification.height as u32; + Ok(true) + } else { + Ok(false) + } + } + + fn get_confirmed_transactions( + &self, sync_state: &SyncState, + ) -> Result, InternalError> { + + // First, check the confirmation status of registered transactions as well as the + // status of dependent transactions of registered outputs. + let mut confirmed_txs: Vec = Vec::new(); + let mut watched_script_pubkeys = Vec::with_capacity( + sync_state.watched_transactions.len() + sync_state.watched_outputs.len()); + let mut watched_txs = Vec::with_capacity(sync_state.watched_transactions.len()); + + for txid in &sync_state.watched_transactions { + match self.client.transaction_get(&txid) { + Ok(tx) => { + watched_txs.push((txid, tx.clone())); + if let Some(tx_out) = tx.output.first() { + // We watch an arbitrary output of the transaction of interest in order to + // retrieve the associated script history, before narrowing down our search + // through `filter`ing by `txid` below. + watched_script_pubkeys.push(tx_out.script_pubkey.clone()); + } else { + debug_assert!(false, "Failed due to retrieving invalid tx data."); + log_error!(self.logger, "Failed due to retrieving invalid tx data."); + return Err(InternalError::Failed); + } + } + Err(electrum_client::Error::Protocol(_)) => { + // We couldn't find the tx, do nothing. + } + Err(e) => { + log_error!(self.logger, "Failed to look up transaction {}: {}.", txid, e); + return Err(InternalError::Failed); + } + } + } + + let num_tx_lookups = watched_script_pubkeys.len(); + debug_assert_eq!(num_tx_lookups, watched_txs.len()); + + for output in sync_state.watched_outputs.values() { + watched_script_pubkeys.push(output.script_pubkey.clone()); + } + + let num_output_spend_lookups = watched_script_pubkeys.len() - num_tx_lookups; + debug_assert_eq!(num_output_spend_lookups, sync_state.watched_outputs.len()); + + match self.client.batch_script_get_history(watched_script_pubkeys.iter().map(|s| s.deref())) + { + Ok(results) => { + let (tx_results, output_results) = results.split_at(num_tx_lookups); + debug_assert_eq!(num_output_spend_lookups, output_results.len()); + + for (i, script_history) in tx_results.iter().enumerate() { + let (txid, tx) = &watched_txs[i]; + if confirmed_txs.iter().any(|ctx| ctx.txid == **txid) { + continue; + } + let mut filtered_history = script_history.iter().filter(|h| h.tx_hash == **txid); + if let Some(history) = filtered_history.next() + { + let prob_conf_height = history.height as u32; + let confirmed_tx = self.get_confirmed_tx(tx, prob_conf_height)?; + confirmed_txs.push(confirmed_tx); + } + debug_assert!(filtered_history.next().is_none()); + } + + for (watched_output, script_history) in sync_state.watched_outputs.values() + .zip(output_results) + { + for possible_output_spend in script_history { + if possible_output_spend.height <= 0 { + continue; + } + + let txid = possible_output_spend.tx_hash; + if confirmed_txs.iter().any(|ctx| ctx.txid == txid) { + continue; + } + + match self.client.transaction_get(&txid) { + Ok(tx) => { + let mut is_spend = false; + for txin in &tx.input { + let watched_outpoint = watched_output.outpoint + .into_bitcoin_outpoint(); + if txin.previous_output == watched_outpoint { + is_spend = true; + break; + } + } + + if !is_spend { + continue; + } + + let prob_conf_height = possible_output_spend.height as u32; + let confirmed_tx = self.get_confirmed_tx(&tx, prob_conf_height)?; + confirmed_txs.push(confirmed_tx); + } + Err(e) => { + log_trace!(self.logger, + "Inconsistency: Tx {} was unconfirmed during syncing: {}", + txid, e); + return Err(InternalError::Inconsistency); + } + } + } + } + } + Err(e) => { + log_error!(self.logger, "Failed to look up script histories: {}.", e); + return Err(InternalError::Failed); + } + } + + // Sort all confirmed transactions first by block height, then by in-block + // position, and finally feed them to the interface in order. + confirmed_txs.sort_unstable_by(|tx1, tx2| { + tx1.block_height.cmp(&tx2.block_height).then_with(|| tx1.pos.cmp(&tx2.pos)) + }); + + Ok(confirmed_txs) + } + + fn get_unconfirmed_transactions( + &self, confirmables: &Vec<&(dyn Confirm + Sync + Send)>, + ) -> Result, InternalError> { + // Query the interface for relevant txids and check whether the relevant blocks are still + // in the best chain, mark them unconfirmed otherwise + let relevant_txids = confirmables + .iter() + .flat_map(|c| c.get_relevant_txids()) + .collect::)>>(); + + let mut unconfirmed_txs = Vec::new(); + + for (txid, conf_height, block_hash_opt) in relevant_txids { + if let Some(block_hash) = block_hash_opt { + let block_header = self.client.block_header(conf_height as usize)?; + if block_header.block_hash() == block_hash { + // Skip if the tx is still confirmed in the block in question. + continue; + } + + unconfirmed_txs.push(txid); + } else { + log_error!(self.logger, + "Untracked confirmation of funding transaction. Please ensure none of your channels had been created with LDK prior to version 0.0.113!"); + panic!("Untracked confirmation of funding transaction. Please ensure none of your channels had been created with LDK prior to version 0.0.113!"); + } + } + Ok(unconfirmed_txs) + } + + fn get_confirmed_tx(&self, tx: &Transaction, prob_conf_height: u32) + -> Result + { + let txid = tx.txid(); + match self.client.transaction_get_merkle(&txid, prob_conf_height as usize) { + Ok(merkle_res) => { + debug_assert_eq!(prob_conf_height, merkle_res.block_height as u32); + match self.client.block_header(prob_conf_height as usize) { + Ok(block_header) => { + let pos = merkle_res.pos; + if !self.validate_merkle_proof(&txid, + &block_header.merkle_root, merkle_res)? + { + log_trace!(self.logger, + "Inconsistency: Block {} was unconfirmed during syncing.", + block_header.block_hash()); + return Err(InternalError::Inconsistency); + } + let confirmed_tx = ConfirmedTx { + tx: tx.clone(), + txid, + block_header, block_height: prob_conf_height, + pos, + }; + Ok(confirmed_tx) + } + Err(e) => { + log_error!(self.logger, + "Failed to retrieve block header for height {}: {}.", + prob_conf_height, e); + Err(InternalError::Failed) + } + } + } + Err(e) => { + log_trace!(self.logger, + "Inconsistency: Tx {} was unconfirmed during syncing: {}", + txid, e); + Err(InternalError::Inconsistency) + } + } + } + + /// Returns a reference to the underlying Electrum client. + pub fn client(&self) -> &ElectrumClient { + &self.client + } + + fn validate_merkle_proof(&self, txid: &Txid, merkle_root: &TxMerkleNode, + merkle_res: GetMerkleRes) -> Result + { + let mut index = merkle_res.pos; + let mut cur = txid.to_raw_hash(); + for mut bytes in merkle_res.merkle { + bytes.reverse(); + // unwrap() safety: `bytes` has len 32 so `from_slice` can never fail. + let next_hash = Sha256d::from_slice(&bytes).unwrap(); + let (left, right) = if index % 2 == 0 { + (cur, next_hash) + } else { + (next_hash, cur) + }; + + let data = [&left[..], &right[..]].concat(); + cur = Sha256d::hash(&data); + index /= 2; + } + + Ok(cur == merkle_root.to_raw_hash()) + } +} + +impl Filter for ElectrumSyncClient +where + L::Target: Logger, +{ + fn register_tx(&self, txid: &Txid, _script_pubkey: &Script) { + let mut locked_queue = self.queue.lock().unwrap(); + locked_queue.transactions.insert(*txid); + } + + fn register_output(&self, output: WatchedOutput) { + let mut locked_queue = self.queue.lock().unwrap(); + locked_queue.outputs.insert(output.outpoint.into_bitcoin_outpoint(), output); + } +} diff --git a/lightning-transaction-sync/src/error.rs b/lightning-transaction-sync/src/error.rs index 73d9de70169..d1f4a319e86 100644 --- a/lightning-transaction-sync/src/error.rs +++ b/lightning-transaction-sync/src/error.rs @@ -18,7 +18,6 @@ impl fmt::Display for TxSyncError { } #[derive(Debug)] -#[cfg(any(feature = "esplora-blocking", feature = "esplora-async"))] pub(crate) enum InternalError { /// A transaction sync failed and needs to be retried eventually. Failed, @@ -26,7 +25,6 @@ pub(crate) enum InternalError { Inconsistency, } -#[cfg(any(feature = "esplora-blocking", feature = "esplora-async"))] impl fmt::Display for InternalError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { @@ -38,9 +36,14 @@ impl fmt::Display for InternalError { } } -#[cfg(any(feature = "esplora-blocking", feature = "esplora-async"))] impl std::error::Error for InternalError {} +impl From for TxSyncError { + fn from(_e: InternalError) -> Self { + Self::Failed + } +} + #[cfg(any(feature = "esplora-blocking", feature = "esplora-async"))] impl From for TxSyncError { fn from(_e: esplora_client::Error) -> Self { @@ -55,9 +58,16 @@ impl From for InternalError { } } -#[cfg(any(feature = "esplora-blocking", feature = "esplora-async"))] -impl From for TxSyncError { - fn from(_e: InternalError) -> Self { +#[cfg(feature = "electrum")] +impl From for InternalError { + fn from(_e: electrum_client::Error) -> Self { + Self::Failed + } +} + +#[cfg(feature = "electrum")] +impl From for TxSyncError { + fn from(_e: electrum_client::Error) -> Self { Self::Failed } } diff --git a/lightning-transaction-sync/src/esplora.rs b/lightning-transaction-sync/src/esplora.rs index f68be08a5a6..538918ada95 100644 --- a/lightning-transaction-sync/src/esplora.rs +++ b/lightning-transaction-sync/src/esplora.rs @@ -2,7 +2,7 @@ use crate::error::{TxSyncError, InternalError}; use crate::common::{SyncState, FilterQueue, ConfirmedTx}; use lightning::util::logger::Logger; -use lightning::{log_error, log_info, log_debug, log_trace}; +use lightning::{log_error, log_debug, log_trace}; use lightning::chain::WatchedOutput; use lightning::chain::{Confirm, Filter}; @@ -89,7 +89,11 @@ where #[cfg(feature = "async-interface")] let mut sync_state = self.sync_state.lock().await; - log_info!(self.logger, "Starting transaction sync."); + log_trace!(self.logger, "Starting transaction sync."); + #[cfg(feature = "time")] + let start_time = std::time::Instant::now(); + let mut num_confirmed = 0; + let mut num_unconfirmed = 0; let mut tip_hash = maybe_await!(self.client.get_tip_hash())?; @@ -110,23 +114,46 @@ where Ok(unconfirmed_txs) => { // Double-check the tip hash. If it changed, a reorg happened since // we started syncing and we need to restart last-minute. - let check_tip_hash = maybe_await!(self.client.get_tip_hash())?; - if check_tip_hash != tip_hash { - tip_hash = check_tip_hash; - continue; + match maybe_await!(self.client.get_tip_hash()) { + Ok(check_tip_hash) => { + if check_tip_hash != tip_hash { + tip_hash = check_tip_hash; + + log_debug!(self.logger, "Encountered inconsistency during transaction sync, restarting."); + sync_state.pending_sync = true; + continue; + } + num_unconfirmed += unconfirmed_txs.len(); + sync_state.sync_unconfirmed_transactions( + &confirmables, + unconfirmed_txs + ); + } + Err(err) => { + // (Semi-)permanent failure, retry later. + log_error!(self.logger, + "Failed during transaction sync, aborting. Synced so far: {} confirmed, {} unconfirmed.", + num_confirmed, + num_unconfirmed + ); + sync_state.pending_sync = true; + return Err(TxSyncError::from(err)); + } } - - self.sync_unconfirmed_transactions(&mut sync_state, &confirmables, unconfirmed_txs); }, Err(err) => { // (Semi-)permanent failure, retry later. - log_error!(self.logger, "Failed during transaction sync, aborting."); + log_error!(self.logger, + "Failed during transaction sync, aborting. Synced so far: {} confirmed, {} unconfirmed.", + num_confirmed, + num_unconfirmed + ); sync_state.pending_sync = true; return Err(TxSyncError::from(err)); } } - match maybe_await!(self.sync_best_block_updated(&confirmables, &tip_hash)) { + match maybe_await!(self.sync_best_block_updated(&confirmables, &mut sync_state, &tip_hash)) { Ok(()) => {} Err(InternalError::Inconsistency) => { // Immediately restart syncing when we encounter any inconsistencies. @@ -136,6 +163,11 @@ where } Err(err) => { // (Semi-)permanent failure, retry later. + log_error!(self.logger, + "Failed during transaction sync, aborting. Synced so far: {} confirmed, {} unconfirmed.", + num_confirmed, + num_unconfirmed + ); sync_state.pending_sync = true; return Err(TxSyncError::from(err)); } @@ -146,17 +178,33 @@ where Ok(confirmed_txs) => { // Double-check the tip hash. If it changed, a reorg happened since // we started syncing and we need to restart last-minute. - let check_tip_hash = maybe_await!(self.client.get_tip_hash())?; - if check_tip_hash != tip_hash { - tip_hash = check_tip_hash; - continue; + match maybe_await!(self.client.get_tip_hash()) { + Ok(check_tip_hash) => { + if check_tip_hash != tip_hash { + tip_hash = check_tip_hash; + + log_debug!(self.logger, + "Encountered inconsistency during transaction sync, restarting."); + sync_state.pending_sync = true; + continue; + } + num_confirmed += confirmed_txs.len(); + sync_state.sync_confirmed_transactions( + &confirmables, + confirmed_txs + ); + } + Err(err) => { + // (Semi-)permanent failure, retry later. + log_error!(self.logger, + "Failed during transaction sync, aborting. Synced so far: {} confirmed, {} unconfirmed.", + num_confirmed, + num_unconfirmed + ); + sync_state.pending_sync = true; + return Err(TxSyncError::from(err)); + } } - - self.sync_confirmed_transactions( - &mut sync_state, - &confirmables, - confirmed_txs, - ); } Err(InternalError::Inconsistency) => { // Immediately restart syncing when we encounter any inconsistencies. @@ -166,7 +214,11 @@ where } Err(err) => { // (Semi-)permanent failure, retry later. - log_error!(self.logger, "Failed during transaction sync, aborting."); + log_error!(self.logger, + "Failed during transaction sync, aborting. Synced so far: {} confirmed, {} unconfirmed.", + num_confirmed, + num_unconfirmed + ); sync_state.pending_sync = true; return Err(TxSyncError::from(err)); } @@ -175,13 +227,18 @@ where sync_state.pending_sync = false; } } - log_info!(self.logger, "Finished transaction sync."); + #[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(()) } #[maybe_async] fn sync_best_block_updated( - &self, confirmables: &Vec<&(dyn Confirm + Sync + Send)>, tip_hash: &BlockHash, + &self, confirmables: &Vec<&(dyn Confirm + Sync + Send)>, sync_state: &mut SyncState, tip_hash: &BlockHash, ) -> Result<(), InternalError> { // Inform the interface of the new block. @@ -192,6 +249,9 @@ where for c in confirmables { c.best_block_updated(&tip_header, tip_height); } + + // Prune any sufficiently confirmed output spends + sync_state.prune_output_spends(tip_height); } } else { return Err(InternalError::Inconsistency); @@ -199,26 +259,6 @@ where Ok(()) } - fn sync_confirmed_transactions( - &self, sync_state: &mut SyncState, confirmables: &Vec<&(dyn Confirm + Sync + Send)>, confirmed_txs: Vec, - ) { - for ctx in confirmed_txs { - for c in confirmables { - c.transactions_confirmed( - &ctx.block_header, - &[(ctx.pos, &ctx.tx)], - ctx.block_height, - ); - } - - sync_state.watched_transactions.remove(&ctx.tx.txid()); - - for input in &ctx.tx.input { - sync_state.watched_outputs.remove(&input.previous_output); - } - } - } - #[maybe_async] fn get_confirmed_transactions( &self, sync_state: &SyncState, @@ -227,10 +267,13 @@ where // First, check the confirmation status of registered transactions as well as the // status of dependent transactions of registered outputs. - let mut confirmed_txs = Vec::new(); + let mut confirmed_txs: Vec = Vec::new(); for txid in &sync_state.watched_transactions { - if let Some(confirmed_tx) = maybe_await!(self.get_confirmed_tx(&txid, None, None))? { + if confirmed_txs.iter().any(|ctx| ctx.txid == *txid) { + continue; + } + if let Some(confirmed_tx) = maybe_await!(self.get_confirmed_tx(*txid, None, None))? { confirmed_txs.push(confirmed_tx); } } @@ -241,9 +284,19 @@ where { if let Some(spending_txid) = output_status.txid { if let Some(spending_tx_status) = output_status.status { + if confirmed_txs.iter().any(|ctx| ctx.txid == spending_txid) { + if spending_tx_status.confirmed { + // Skip inserting duplicate ConfirmedTx entry + continue; + } else { + log_trace!(self.logger, "Inconsistency: Detected previously-confirmed Tx {} as unconfirmed", spending_txid); + return Err(InternalError::Inconsistency); + } + } + if let Some(confirmed_tx) = maybe_await!(self .get_confirmed_tx( - &spending_txid, + spending_txid, spending_tx_status.block_hash, spending_tx_status.block_height, ))? @@ -266,7 +319,7 @@ where #[maybe_async] fn get_confirmed_tx( - &self, txid: &Txid, expected_block_hash: Option, known_block_height: Option, + &self, txid: Txid, expected_block_hash: Option, known_block_height: Option, ) -> Result, InternalError> { if let Some(merkle_block) = maybe_await!(self.client.get_merkle_block(&txid))? { let block_header = merkle_block.header; @@ -281,21 +334,27 @@ where let mut matches = Vec::new(); let mut indexes = Vec::new(); let _ = merkle_block.txn.extract_matches(&mut matches, &mut indexes); - if indexes.len() != 1 || matches.len() != 1 || matches[0] != *txid { + if indexes.len() != 1 || matches.len() != 1 || matches[0] != txid { log_error!(self.logger, "Retrieved Merkle block for txid {} doesn't match expectations. This should not happen. Please verify server integrity.", txid); return Err(InternalError::Failed); } - let pos = *indexes.get(0).ok_or(InternalError::Failed)? as usize; + // unwrap() safety: len() > 0 is checked above + let pos = *indexes.first().unwrap() as usize; if let Some(tx) = maybe_await!(self.client.get_tx(&txid))? { + if tx.txid() != txid { + log_error!(self.logger, "Retrieved transaction for txid {} doesn't match expectations. This should not happen. Please verify server integrity.", txid); + return Err(InternalError::Failed); + } + if let Some(block_height) = known_block_height { // We can take a shortcut here if a previous call already gave us the height. - return Ok(Some(ConfirmedTx { tx, block_header, pos, block_height })); + return Ok(Some(ConfirmedTx { tx, txid, block_header, pos, block_height })); } let block_status = maybe_await!(self.client.get_block_status(&block_hash))?; if let Some(block_height) = block_status.height { - return Ok(Some(ConfirmedTx { tx, block_header, pos, block_height })); + return Ok(Some(ConfirmedTx { tx, txid, block_header, pos, block_height })); } else { // If any previously-confirmed block suddenly is no longer confirmed, we found // an inconsistency and should start over. @@ -316,11 +375,11 @@ where let relevant_txids = confirmables .iter() .flat_map(|c| c.get_relevant_txids()) - .collect::)>>(); + .collect::)>>(); let mut unconfirmed_txs = Vec::new(); - for (txid, block_hash_opt) in relevant_txids { + for (txid, _conf_height, block_hash_opt) in relevant_txids { if let Some(block_hash) = block_hash_opt { let block_status = maybe_await!(self.client.get_block_status(&block_hash))?; if block_status.in_best_chain { @@ -337,18 +396,6 @@ where Ok(unconfirmed_txs) } - fn sync_unconfirmed_transactions( - &self, sync_state: &mut SyncState, confirmables: &Vec<&(dyn Confirm + Sync + Send)>, unconfirmed_txs: Vec, - ) { - for txid in unconfirmed_txs { - for c in confirmables { - c.transaction_unconfirmed(&txid); - } - - sync_state.watched_transactions.insert(txid); - } - } - /// Returns a reference to the underlying esplora client. pub fn client(&self) -> &EsploraClientType { &self.client diff --git a/lightning-transaction-sync/src/lib.rs b/lightning-transaction-sync/src/lib.rs index ca3ce3f8ad6..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)] @@ -74,11 +73,17 @@ extern crate bdk_macros; #[cfg(any(feature = "esplora-blocking", feature = "esplora-async"))] mod esplora; -#[cfg(any(feature = "esplora-blocking", feature = "esplora-async"))] -mod common; +#[cfg(any(feature = "electrum"))] +mod electrum; +#[cfg(any(feature = "esplora-blocking", feature = "esplora-async", feature = "electrum"))] +mod common; +#[cfg(any(feature = "esplora-blocking", feature = "esplora-async", feature = "electrum"))] mod error; +#[cfg(any(feature = "esplora-blocking", feature = "esplora-async", feature = "electrum"))] pub use error::TxSyncError; #[cfg(any(feature = "esplora-blocking", feature = "esplora-async"))] pub use esplora::EsploraSyncClient; +#[cfg(feature = "electrum")] +pub use electrum::ElectrumSyncClient; diff --git a/lightning-transaction-sync/tests/integration_tests.rs b/lightning-transaction-sync/tests/integration_tests.rs index 617b1213e89..48044b236bf 100644 --- a/lightning-transaction-sync/tests/integration_tests.rs +++ b/lightning-transaction-sync/tests/integration_tests.rs @@ -1,16 +1,21 @@ -#![cfg(any(feature = "esplora-blocking", feature = "esplora-async"))] +#![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; -use lightning::chain::{Confirm, Filter}; -use lightning::chain::transaction::TransactionData; -use lightning::util::logger::{Logger, Record}; +#[cfg(feature = "electrum")] +use lightning_transaction_sync::ElectrumSyncClient; +use lightning::chain::{Confirm, Filter, WatchedOutput}; +use lightning::chain::transaction::{OutPoint, TransactionData}; +use lightning::util::test_utils::TestLogger; use electrsd::{bitcoind, bitcoind::BitcoinD, ElectrsD}; -use bitcoin::{Amount, Txid, BlockHash, BlockHeader}; +use bitcoin::{Amount, Txid, BlockHash}; +use bitcoin::blockdata::block::Header; use bitcoin::blockdata::constants::genesis_block; use bitcoin::network::constants::Network; use electrsd::bitcoind::bitcoincore_rpc::bitcoincore_rpc_json::AddressType; use bitcoind::bitcoincore_rpc::RpcApi; -use electrum_client::ElectrumApi; +use bdk_macros::maybe_await; use std::env; use std::sync::Mutex; @@ -42,21 +47,23 @@ pub fn generate_blocks_and_wait(bitcoind: &BitcoinD, electrsd: &ElectrsD, num: u let address = bitcoind .client .get_new_address(Some("test"), Some(AddressType::Legacy)) - .expect("failed to get new address"); + .expect("failed to get new address") + .assume_checked(); // TODO: expect this Result once the WouldBlock issue is resolved upstream. let _block_hashes_res = bitcoind.client.generate_to_address(num as u64, &address); wait_for_block(electrsd, cur_height as usize + num); } pub fn wait_for_block(electrsd: &ElectrsD, min_height: usize) { - let mut header = match electrsd.client.block_headers_subscribe() { + use electrsd::electrum_client::ElectrumApi; + let mut header = match electrsd.client.block_headers_subscribe_raw() { Ok(header) => header, Err(_) => { // While subscribing should succeed the first time around, we ran into some cases where // it didn't. Since we can't proceed without subscribing, we try again after a delay // and panic if it still fails. std::thread::sleep(Duration::from_secs(1)); - electrsd.client.block_headers_subscribe().expect("failed to subscribe to block headers") + electrsd.client.block_headers_subscribe_raw().expect("failed to subscribe to block headers") } }; loop { @@ -66,7 +73,7 @@ pub fn wait_for_block(electrsd: &ElectrsD, min_height: usize) { header = exponential_backoff_poll(|| { electrsd.trigger().expect("failed to trigger electrsd"); electrsd.client.ping().expect("failed to ping electrsd"); - electrsd.client.block_headers_pop().expect("failed to pop block header") + electrsd.client.block_headers_pop_raw().expect("failed to pop block header") }); } } @@ -119,7 +126,7 @@ impl TestConfirmable { } impl Confirm for TestConfirmable { - fn transactions_confirmed(&self, header: &BlockHeader, txdata: &TransactionData<'_>, height: u32) { + fn transactions_confirmed(&self, header: &Header, txdata: &TransactionData<'_>, height: u32) { for (_, tx) in txdata { let txid = tx.txid(); let block_hash = header.block_hash(); @@ -135,25 +142,140 @@ impl Confirm for TestConfirmable { self.events.lock().unwrap().push(TestConfirmableEvent::Unconfirmed(*txid)); } - fn best_block_updated(&self, header: &BlockHeader, height: u32) { + fn best_block_updated(&self, header: &Header, height: u32) { let block_hash = header.block_hash(); *self.best_block.lock().unwrap() = (block_hash, height); self.events.lock().unwrap().push(TestConfirmableEvent::BestBlockUpdated(block_hash, height)); } - fn get_relevant_txids(&self) -> Vec<(Txid, Option)> { - self.confirmed_txs.lock().unwrap().iter().map(|(&txid, (hash, _))| (txid, Some(*hash))).collect::>() + fn get_relevant_txids(&self) -> Vec<(Txid, u32, Option)> { + self.confirmed_txs.lock().unwrap().iter().map(|(&txid, (hash, height))| (txid, *height, Some(*hash))).collect::>() } } -pub struct TestLogger {} +macro_rules! test_syncing { + ($tx_sync: expr, $confirmable: expr, $bitcoind: expr, $electrsd: expr) => {{ + // Check we pick up on new best blocks + assert_eq!($confirmable.best_block.lock().unwrap().1, 0); + + maybe_await!($tx_sync.sync(vec![&$confirmable])).unwrap(); + assert_eq!($confirmable.best_block.lock().unwrap().1, 102); + + let events = std::mem::take(&mut *$confirmable.events.lock().unwrap()); + assert_eq!(events.len(), 1); + + // Check registered confirmed transactions are marked confirmed + let new_address = $bitcoind.client.get_new_address(Some("test"), + Some(AddressType::Legacy)).unwrap().assume_checked(); + let txid = $bitcoind.client.send_to_address(&new_address, Amount::from_sat(5000), None, None, + None, None, None, None).unwrap(); + let second_txid = $bitcoind.client.send_to_address(&new_address, Amount::from_sat(5000), None, + None, None, None, None, None).unwrap(); + $tx_sync.register_tx(&txid, &new_address.payload.script_pubkey()); + + maybe_await!($tx_sync.sync(vec![&$confirmable])).unwrap(); + + let events = std::mem::take(&mut *$confirmable.events.lock().unwrap()); + assert_eq!(events.len(), 0); + assert!($confirmable.confirmed_txs.lock().unwrap().is_empty()); + assert!($confirmable.unconfirmed_txs.lock().unwrap().is_empty()); + + generate_blocks_and_wait(&$bitcoind, &$electrsd, 1); + maybe_await!($tx_sync.sync(vec![&$confirmable])).unwrap(); + + let events = std::mem::take(&mut *$confirmable.events.lock().unwrap()); + assert_eq!(events.len(), 2); + assert!($confirmable.confirmed_txs.lock().unwrap().contains_key(&txid)); + assert!($confirmable.unconfirmed_txs.lock().unwrap().is_empty()); + + // Now take an arbitrary output of the second transaction and check we'll confirm its spend. + let tx_res = $bitcoind.client.get_transaction(&second_txid, None).unwrap(); + let block_hash = tx_res.info.blockhash.unwrap(); + let tx = tx_res.transaction().unwrap(); + let prev_outpoint = tx.input.first().unwrap().previous_output; + let prev_tx = $bitcoind.client.get_transaction(&prev_outpoint.txid, None).unwrap().transaction() + .unwrap(); + let prev_script_pubkey = prev_tx.output[prev_outpoint.vout as usize].script_pubkey.clone(); + let output = WatchedOutput { + block_hash: Some(block_hash), + outpoint: OutPoint { txid: prev_outpoint.txid, index: prev_outpoint.vout as u16 }, + script_pubkey: prev_script_pubkey + }; + + $tx_sync.register_output(output); + maybe_await!($tx_sync.sync(vec![&$confirmable])).unwrap(); + + let events = std::mem::take(&mut *$confirmable.events.lock().unwrap()); + assert_eq!(events.len(), 1); + assert!($confirmable.confirmed_txs.lock().unwrap().contains_key(&second_txid)); + assert_eq!($confirmable.confirmed_txs.lock().unwrap().len(), 2); + assert!($confirmable.unconfirmed_txs.lock().unwrap().is_empty()); + + // Check previously confirmed transactions are marked unconfirmed when they are reorged. + let best_block_hash = $bitcoind.client.get_best_block_hash().unwrap(); + $bitcoind.client.invalidate_block(&best_block_hash).unwrap(); + + // We're getting back to the previous height with a new tip, but best block shouldn't change. + generate_blocks_and_wait(&$bitcoind, &$electrsd, 1); + assert_ne!($bitcoind.client.get_best_block_hash().unwrap(), best_block_hash); + maybe_await!($tx_sync.sync(vec![&$confirmable])).unwrap(); + let events = std::mem::take(&mut *$confirmable.events.lock().unwrap()); + assert_eq!(events.len(), 0); + + // Now we're surpassing previous height, getting new tip. + generate_blocks_and_wait(&$bitcoind, &$electrsd, 1); + assert_ne!($bitcoind.client.get_best_block_hash().unwrap(), best_block_hash); + maybe_await!($tx_sync.sync(vec![&$confirmable])).unwrap(); + + // Transactions still confirmed but under new tip. + assert!($confirmable.confirmed_txs.lock().unwrap().contains_key(&txid)); + assert!($confirmable.confirmed_txs.lock().unwrap().contains_key(&second_txid)); + assert!($confirmable.unconfirmed_txs.lock().unwrap().is_empty()); + + // Check we got unconfirmed, then reconfirmed in the meantime. + let mut seen_txids = HashSet::new(); + let events = std::mem::take(&mut *$confirmable.events.lock().unwrap()); + assert_eq!(events.len(), 5); + + match events[0] { + TestConfirmableEvent::Unconfirmed(t) => { + assert!(t == txid || t == second_txid); + assert!(seen_txids.insert(t)); + }, + _ => panic!("Unexpected event"), + } -impl Logger for TestLogger { - fn log(&self, record: &Record) { - println!("{} -- {}", - record.level, - record.args); - } + match events[1] { + TestConfirmableEvent::Unconfirmed(t) => { + assert!(t == txid || t == second_txid); + assert!(seen_txids.insert(t)); + }, + _ => panic!("Unexpected event"), + } + + match events[2] { + TestConfirmableEvent::BestBlockUpdated(..) => {}, + _ => panic!("Unexpected event"), + } + + match events[3] { + TestConfirmableEvent::Confirmed(t, _, _) => { + assert!(t == txid || t == second_txid); + assert!(seen_txids.remove(&t)); + }, + _ => panic!("Unexpected event"), + } + + match events[4] { + TestConfirmableEvent::Confirmed(t, _, _) => { + assert!(t == txid || t == second_txid); + assert!(seen_txids.remove(&t)); + }, + _ => panic!("Unexpected event"), + } + + assert_eq!(seen_txids.len(), 0); + }}; } #[test] @@ -161,82 +283,12 @@ impl Logger for TestLogger { fn test_esplora_syncs() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); generate_blocks_and_wait(&bitcoind, &electrsd, 101); - let mut logger = TestLogger {}; + let mut logger = TestLogger::new(); let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); let tx_sync = EsploraSyncClient::new(esplora_url, &mut logger); let confirmable = TestConfirmable::new(); - // Check we pick up on new best blocks - assert_eq!(confirmable.best_block.lock().unwrap().1, 0); - - tx_sync.sync(vec![&confirmable]).unwrap(); - assert_eq!(confirmable.best_block.lock().unwrap().1, 102); - - let events = std::mem::take(&mut *confirmable.events.lock().unwrap()); - assert_eq!(events.len(), 1); - - // Check registered confirmed transactions are marked confirmed - let new_address = bitcoind.client.get_new_address(Some("test"), Some(AddressType::Legacy)).unwrap(); - let txid = bitcoind.client.send_to_address(&new_address, Amount::from_sat(5000), None, None, None, None, None, None).unwrap(); - tx_sync.register_tx(&txid, &new_address.script_pubkey()); - - tx_sync.sync(vec![&confirmable]).unwrap(); - - let events = std::mem::take(&mut *confirmable.events.lock().unwrap()); - assert_eq!(events.len(), 0); - assert!(confirmable.confirmed_txs.lock().unwrap().is_empty()); - assert!(confirmable.unconfirmed_txs.lock().unwrap().is_empty()); - - generate_blocks_and_wait(&bitcoind, &electrsd, 1); - tx_sync.sync(vec![&confirmable]).unwrap(); - - let events = std::mem::take(&mut *confirmable.events.lock().unwrap()); - assert_eq!(events.len(), 2); - assert!(confirmable.confirmed_txs.lock().unwrap().contains_key(&txid)); - assert!(confirmable.unconfirmed_txs.lock().unwrap().is_empty()); - - // Check previously confirmed transactions are marked unconfirmed when they are reorged. - let best_block_hash = bitcoind.client.get_best_block_hash().unwrap(); - bitcoind.client.invalidate_block(&best_block_hash).unwrap(); - - // We're getting back to the previous height with a new tip, but best block shouldn't change. - generate_blocks_and_wait(&bitcoind, &electrsd, 1); - assert_ne!(bitcoind.client.get_best_block_hash().unwrap(), best_block_hash); - tx_sync.sync(vec![&confirmable]).unwrap(); - let events = std::mem::take(&mut *confirmable.events.lock().unwrap()); - assert_eq!(events.len(), 0); - - // Now we're surpassing previous height, getting new tip. - generate_blocks_and_wait(&bitcoind, &electrsd, 1); - assert_ne!(bitcoind.client.get_best_block_hash().unwrap(), best_block_hash); - tx_sync.sync(vec![&confirmable]).unwrap(); - - // Transaction still confirmed but under new tip. - assert!(confirmable.confirmed_txs.lock().unwrap().contains_key(&txid)); - assert!(confirmable.unconfirmed_txs.lock().unwrap().is_empty()); - - // Check we got unconfirmed, then reconfirmed in the meantime. - let events = std::mem::take(&mut *confirmable.events.lock().unwrap()); - assert_eq!(events.len(), 3); - - match events[0] { - TestConfirmableEvent::Unconfirmed(t) => { - assert_eq!(t, txid); - }, - _ => panic!("Unexpected event"), - } - - match events[1] { - TestConfirmableEvent::BestBlockUpdated(..) => {}, - _ => panic!("Unexpected event"), - } - - match events[2] { - TestConfirmableEvent::Confirmed(t, _, _) => { - assert_eq!(t, txid); - }, - _ => panic!("Unexpected event"), - } + test_syncing!(tx_sync, confirmable, bitcoind, electrsd); } #[tokio::test] @@ -244,80 +296,22 @@ fn test_esplora_syncs() { async fn test_esplora_syncs() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); generate_blocks_and_wait(&bitcoind, &electrsd, 101); - let mut logger = TestLogger {}; + let mut logger = TestLogger::new(); let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); let tx_sync = EsploraSyncClient::new(esplora_url, &mut logger); let confirmable = TestConfirmable::new(); - // Check we pick up on new best blocks - assert_eq!(confirmable.best_block.lock().unwrap().1, 0); - - tx_sync.sync(vec![&confirmable]).await.unwrap(); - assert_eq!(confirmable.best_block.lock().unwrap().1, 102); - - let events = std::mem::take(&mut *confirmable.events.lock().unwrap()); - assert_eq!(events.len(), 1); - - // Check registered confirmed transactions are marked confirmed - let new_address = bitcoind.client.get_new_address(Some("test"), Some(AddressType::Legacy)).unwrap(); - let txid = bitcoind.client.send_to_address(&new_address, Amount::from_sat(5000), None, None, None, None, None, None).unwrap(); - tx_sync.register_tx(&txid, &new_address.script_pubkey()); - - tx_sync.sync(vec![&confirmable]).await.unwrap(); - - let events = std::mem::take(&mut *confirmable.events.lock().unwrap()); - assert_eq!(events.len(), 0); - assert!(confirmable.confirmed_txs.lock().unwrap().is_empty()); - assert!(confirmable.unconfirmed_txs.lock().unwrap().is_empty()); - - generate_blocks_and_wait(&bitcoind, &electrsd, 1); - tx_sync.sync(vec![&confirmable]).await.unwrap(); - - let events = std::mem::take(&mut *confirmable.events.lock().unwrap()); - assert_eq!(events.len(), 2); - assert!(confirmable.confirmed_txs.lock().unwrap().contains_key(&txid)); - assert!(confirmable.unconfirmed_txs.lock().unwrap().is_empty()); - - // Check previously confirmed transactions are marked unconfirmed when they are reorged. - let best_block_hash = bitcoind.client.get_best_block_hash().unwrap(); - bitcoind.client.invalidate_block(&best_block_hash).unwrap(); - - // We're getting back to the previous height with a new tip, but best block shouldn't change. - generate_blocks_and_wait(&bitcoind, &electrsd, 1); - assert_ne!(bitcoind.client.get_best_block_hash().unwrap(), best_block_hash); - tx_sync.sync(vec![&confirmable]).await.unwrap(); - let events = std::mem::take(&mut *confirmable.events.lock().unwrap()); - assert_eq!(events.len(), 0); - - // Now we're surpassing previous height, getting new tip. - generate_blocks_and_wait(&bitcoind, &electrsd, 1); - assert_ne!(bitcoind.client.get_best_block_hash().unwrap(), best_block_hash); - tx_sync.sync(vec![&confirmable]).await.unwrap(); - - // Transaction still confirmed but under new tip. - assert!(confirmable.confirmed_txs.lock().unwrap().contains_key(&txid)); - assert!(confirmable.unconfirmed_txs.lock().unwrap().is_empty()); - - // Check we got unconfirmed, then reconfirmed in the meantime. - let events = std::mem::take(&mut *confirmable.events.lock().unwrap()); - assert_eq!(events.len(), 3); - - match events[0] { - TestConfirmableEvent::Unconfirmed(t) => { - assert_eq!(t, txid); - }, - _ => panic!("Unexpected event"), - } - - match events[1] { - TestConfirmableEvent::BestBlockUpdated(..) => {}, - _ => panic!("Unexpected event"), - } + test_syncing!(tx_sync, confirmable, bitcoind, electrsd); +} - match events[2] { - TestConfirmableEvent::Confirmed(t, _, _) => { - assert_eq!(t, txid); - }, - _ => panic!("Unexpected event"), - } +#[test] +#[cfg(feature = "electrum")] +fn test_electrum_syncs() { + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + generate_blocks_and_wait(&bitcoind, &electrsd, 101); + let mut logger = TestLogger::new(); + let electrum_url = format!("tcp://{}", electrsd.electrum_url); + let tx_sync = ElectrumSyncClient::new(electrum_url, &mut logger).unwrap(); + let confirmable = TestConfirmable::new(); + test_syncing!(tx_sync, confirmable, bitcoind, electrsd); } diff --git a/lightning/Cargo.toml b/lightning/Cargo.toml index 54418a9c01b..85bdab6df2f 100644 --- a/lightning/Cargo.toml +++ b/lightning/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lightning" -version = "0.0.118" +version = "0.0.123" 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 @@ -40,22 +40,23 @@ grind_signatures = [] default = ["std", "grind_signatures"] [dependencies] -bitcoin = { package = "bitcoin", version = "0.29.0", default-features = false, features = [ +bitcoin = { version = "0.30.2", default-features = false, features = [ "base64", "secp-recovery", "serde", ] } - -hashbrown = { version = "0.8", optional = true } +hashbrown = { version = "0.13", optional = true, default-features = false } +possiblyrandom = { version = "0.2", 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 } # RGB and related futures = "0.3" -hex = "0.4" -rgb-lib = { version = "0.3.0-alpha.3", features = [ +rgb-lib = { version = "0.3.0-alpha.4", features = [ "electrum", "esplora", ] } @@ -69,11 +70,10 @@ tokio = { version = "1.14.1", features = [ ] } [dev-dependencies] -hex = "0.4" regex = "1.5.6" [dev-dependencies.bitcoin] -version = "0.29.0" +version = "0.30.2" default-features = false features = ["bitcoinconsensus", "secp-recovery"] @@ -81,4 +81,4 @@ features = ["bitcoinconsensus", "secp-recovery"] criterion = { version = "0.4", optional = true, default-features = false } [target.'cfg(taproot)'.dependencies] -musig2 = { git = "https://github.com/arik-so/rust-musig2", rev = "27797d7" } +musig2 = { git = "https://github.com/arik-so/rust-musig2", rev = "cff11e3" } diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index d2e81444ef6..9a282a5f87d 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -1,14 +1,16 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; -use crate::blinded_path::{BlindedHop, BlindedPath}; +#[allow(unused_imports)] +use crate::prelude::*; + +use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode, NextMessageHop, NodeIdLookUp}; use crate::blinded_path::utils; use crate::io; use crate::io::Cursor; use crate::ln::onion_utils; -use crate::onion_message::ControlTlvs; -use crate::prelude::*; +use crate::onion_message::packet::ControlTlvs; 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; @@ -17,8 +19,8 @@ use core::ops::Deref; /// TLVs to encode in an intermediate onion message packet's hop data. When provided in a blinded /// route, they are encoded into [`BlindedHop::encrypted_payload`]. pub(crate) struct ForwardTlvs { - /// The node id of the next hop in the onion message's path. - pub(crate) next_node_id: PublicKey, + /// The next hop in the onion message's path. + pub(crate) next_hop: NextMessageHop, /// Senders to a blinded path use this value to concatenate the route they find to the /// introduction node with the blinded path. pub(crate) next_blinding_override: Option, @@ -34,9 +36,14 @@ pub(crate) struct ReceiveTlvs { impl Writeable for ForwardTlvs { fn write(&self, writer: &mut W) -> Result<(), io::Error> { + let (next_node_id, short_channel_id) = match self.next_hop { + NextMessageHop::NodeId(pubkey) => (Some(pubkey), None), + NextMessageHop::ShortChannelId(scid) => (None, Some(scid)), + }; // TODO: write padding encode_tlv_stream!(writer, { - (4, self.next_node_id, required), + (2, short_channel_id, option), + (4, next_node_id, option), (8, self.next_blinding_override, option) }); Ok(()) @@ -59,9 +66,8 @@ pub(super) fn blinded_hops( ) -> Result, secp256k1::Error> { let blinded_tlvs = unblinded_path.iter() .skip(1) // The first node's TLVs contains the next node's pubkey - .map(|pk| { - ControlTlvs::Forward(ForwardTlvs { next_node_id: *pk, next_blinding_override: None }) - }) + .map(|pk| ForwardTlvs { next_hop: NextMessageHop::NodeId(*pk), next_blinding_override: None }) + .map(|tlvs| ControlTlvs::Forward(tlvs)) .chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs { path_id: None }))); utils::construct_blinded_hops(secp_ctx, unblinded_path.iter(), blinded_tlvs, session_priv) @@ -69,18 +75,30 @@ pub(super) fn blinded_hops( // Advance the blinded onion message path by one hop, so make the second hop into the new // introduction node. -pub(crate) fn advance_path_by_one( - path: &mut BlindedPath, node_signer: &NS, secp_ctx: &Secp256k1 -) -> Result<(), ()> where NS::Target: NodeSigner { +pub(crate) fn advance_path_by_one( + path: &mut BlindedPath, node_signer: &NS, node_id_lookup: &NL, secp_ctx: &Secp256k1 +) -> Result<(), ()> +where + NS::Target: NodeSigner, + NL::Target: NodeIdLookUp, + T: secp256k1::Signing + secp256k1::Verification, +{ let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &path.blinding_point, None)?; let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes()); let encrypted_control_tlvs = path.blinded_hops.remove(0).encrypted_payload; let mut s = Cursor::new(&encrypted_control_tlvs); let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64); match ChaChaPolyReadAdapter::read(&mut reader, rho) { - Ok(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(ForwardTlvs { - mut next_node_id, next_blinding_override, - })}) => { + Ok(ChaChaPolyReadAdapter { + readable: ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override }) + }) => { + let next_node_id = match next_hop { + NextMessageHop::NodeId(pubkey) => pubkey, + NextMessageHop::ShortChannelId(scid) => match node_id_lookup.next_node_id(scid) { + Some(pubkey) => pubkey, + None => return Err(()), + }, + }; let mut new_blinding_point = match next_blinding_override { Some(blinding_point) => blinding_point, None => { @@ -89,7 +107,7 @@ pub(crate) fn advance_path_by_one Err(()) diff --git a/lightning/src/blinded_path/mod.rs b/lightning/src/blinded_path/mod.rs index d75b4f25b36..3dc6b121b00 100644 --- a/lightning/src/blinded_path/mod.rs +++ b/lightning/src/blinded_path/mod.rs @@ -17,22 +17,34 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; use crate::ln::msgs::DecodeError; use crate::offers::invoice::BlindedPayInfo; +use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph}; use crate::sign::EntropySource; use crate::util::ser::{Readable, Writeable, Writer}; use crate::io; use crate::prelude::*; +/// The next hop to forward an onion message along its path. +/// +/// Note that payment blinded paths always specify their next hop using an explicit node id. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub enum NextMessageHop { + /// The node id of the next hop. + NodeId(PublicKey), + /// The short channel id leading to the next hop. + ShortChannelId(u64), +} + /// Onion messages and payments can be sent and received to blinded paths, which serve to hide the /// identity of the recipient. #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct BlindedPath { /// To send to a blinded path, the sender first finds a route to the unblinded - /// `introduction_node_id`, which can unblind its [`encrypted_payload`] to find out the onion + /// `introduction_node`, which can unblind its [`encrypted_payload`] to find out the onion /// message or payment's next hop and forward it along. /// /// [`encrypted_payload`]: BlindedHop::encrypted_payload - pub introduction_node_id: PublicKey, + pub introduction_node: IntroductionNode, /// Used by the introduction node to decrypt its [`encrypted_payload`] to forward the onion /// message or payment. /// @@ -42,6 +54,52 @@ pub struct BlindedPath { pub blinded_hops: Vec, } +/// The unblinded node in a [`BlindedPath`]. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub enum IntroductionNode { + /// The node id of the introduction node. + NodeId(PublicKey), + /// The short channel id of the channel leading to the introduction node. The [`Direction`] + /// identifies which side of the channel is the introduction node. + DirectedShortChannelId(Direction, u64), +} + +/// The side of a channel that is the [`IntroductionNode`] in a [`BlindedPath`]. [BOLT 7] defines +/// which nodes is which in the [`ChannelAnnouncement`] message. +/// +/// [BOLT 7]: https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#the-channel_announcement-message +/// [`ChannelAnnouncement`]: crate::ln::msgs::ChannelAnnouncement +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub enum Direction { + /// The lesser node id when compared lexicographically in ascending order. + NodeOne, + /// The greater node id when compared lexicographically in ascending order. + NodeTwo, +} + +/// An interface for looking up the node id of a channel counterparty for the purpose of forwarding +/// an [`OnionMessage`]. +/// +/// [`OnionMessage`]: crate::ln::msgs::OnionMessage +pub trait NodeIdLookUp { + /// Returns the node id of the forwarding node's channel counterparty with `short_channel_id`. + /// + /// Here, the forwarding node is referring to the node of the [`OnionMessenger`] parameterized + /// by the [`NodeIdLookUp`] and the counterparty to one of that node's peers. + /// + /// [`OnionMessenger`]: crate::onion_message::messenger::OnionMessenger + fn next_node_id(&self, short_channel_id: u64) -> Option; +} + +/// A [`NodeIdLookUp`] that always returns `None`. +pub struct EmptyNodeIdLookUp {} + +impl NodeIdLookUp for EmptyNodeIdLookUp { + fn next_node_id(&self, _short_channel_id: u64) -> Option { + None + } +} + /// An encrypted payload and node id corresponding to a hop in a payment or onion message path, to /// be encoded in the sender's onion packet. These hops cannot be identified by outside observers /// and thus can be used to hide the identity of the recipient. @@ -74,10 +132,10 @@ impl BlindedPath { if node_pks.is_empty() { return Err(()) } let blinding_secret_bytes = entropy_source.get_secure_random_bytes(); let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted"); - let introduction_node_id = node_pks[0]; + let introduction_node = IntroductionNode::NodeId(node_pks[0]); Ok(BlindedPath { - introduction_node_id, + introduction_node, blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret), blinded_hops: message::blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?, }) @@ -85,14 +143,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,28 +164,64 @@ 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 introduction_node = IntroductionNode::NodeId( + intermediate_nodes.first().map_or(payee_node_id, |n| n.node_id) + ); 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), + introduction_node, blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret), blinded_hops: payment::blinded_hops( secp_ctx, intermediate_nodes, payee_node_id, payee_tlvs, &blinding_secret ).map_err(|_| ())?, })) } + + /// Returns the introduction [`NodeId`] of the blinded path, if it is publicly reachable (i.e., + /// it is found in the network graph). + pub fn public_introduction_node_id<'a>( + &self, network_graph: &'a ReadOnlyNetworkGraph + ) -> Option<&'a NodeId> { + match &self.introduction_node { + IntroductionNode::NodeId(pubkey) => { + let node_id = NodeId::from_pubkey(pubkey); + network_graph.nodes().get_key_value(&node_id).map(|(key, _)| key) + }, + IntroductionNode::DirectedShortChannelId(direction, scid) => { + network_graph + .channel(*scid) + .map(|c| match direction { + Direction::NodeOne => &c.node_one, + Direction::NodeTwo => &c.node_two, + }) + }, + } + } } impl Writeable for BlindedPath { fn write(&self, w: &mut W) -> Result<(), io::Error> { - self.introduction_node_id.write(w)?; + match &self.introduction_node { + IntroductionNode::NodeId(pubkey) => pubkey.write(w)?, + IntroductionNode::DirectedShortChannelId(direction, scid) => { + match direction { + Direction::NodeOne => 0u8.write(w)?, + Direction::NodeTwo => 1u8.write(w)?, + } + scid.write(w)?; + }, + } + self.blinding_point.write(w)?; (self.blinded_hops.len() as u8).write(w)?; for hop in &self.blinded_hops { @@ -138,7 +233,17 @@ impl Writeable for BlindedPath { impl Readable for BlindedPath { fn read(r: &mut R) -> Result { - let introduction_node_id = Readable::read(r)?; + let mut first_byte: u8 = Readable::read(r)?; + let introduction_node = match first_byte { + 0 => IntroductionNode::DirectedShortChannelId(Direction::NodeOne, Readable::read(r)?), + 1 => IntroductionNode::DirectedShortChannelId(Direction::NodeTwo, Readable::read(r)?), + 2|3 => { + use io::Read; + let mut pubkey_read = core::slice::from_mut(&mut first_byte).chain(r.by_ref()); + IntroductionNode::NodeId(Readable::read(&mut pubkey_read)?) + }, + _ => return Err(DecodeError::InvalidValue), + }; let blinding_point = Readable::read(r)?; let num_hops: u8 = Readable::read(r)?; if num_hops == 0 { return Err(DecodeError::InvalidValue) } @@ -147,7 +252,7 @@ impl Readable for BlindedPath { blinded_hops.push(Readable::read(r)?); } Ok(BlindedPath { - introduction_node_id, + introduction_node, blinding_point, blinded_hops, }) @@ -159,3 +264,25 @@ impl_writeable!(BlindedHop, { encrypted_payload }); +impl Direction { + /// Returns the [`NodeId`] from the inputs corresponding to the direction. + pub fn select_node_id<'a>(&self, node_a: &'a NodeId, node_b: &'a NodeId) -> &'a NodeId { + match self { + Direction::NodeOne => core::cmp::min(node_a, node_b), + Direction::NodeTwo => core::cmp::max(node_a, node_b), + } + } + + /// Returns the [`PublicKey`] from the inputs corresponding to the direction. + pub fn select_pubkey<'a>(&self, node_a: &'a PublicKey, node_b: &'a PublicKey) -> &'a PublicKey { + let (node_one, node_two) = if NodeId::from_pubkey(node_a) < NodeId::from_pubkey(node_b) { + (node_a, node_b) + } else { + (node_b, node_a) + }; + match self { + Direction::NodeOne => node_one, + Direction::NodeTwo => node_two, + } + } +} diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index 6e923580362..dfe89e0ebc6 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -7,14 +7,17 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; use crate::blinded_path::BlindedHop; use crate::blinded_path::utils; use crate::io; -use crate::ln::PaymentSecret; +use crate::ln::types::PaymentSecret; +use crate::ln::channelmanager::CounterpartyForwardingInfo; use crate::ln::features::BlindedHopFeatures; use crate::ln::msgs::DecodeError; use crate::offers::invoice::BlindedPayInfo; -use crate::prelude::*; -use crate::util::ser::{Readable, Writeable, Writer}; +use crate::offers::invoice_request::InvoiceRequestFields; +use crate::offers::offer::OfferId; +use crate::util::ser::{HighZeroBytesDroppedBigSize, Readable, Writeable, Writer}; -use core::convert::TryFrom; +#[allow(unused_imports)] +use crate::prelude::*; /// An intermediate node, its outbound channel, and relay parameters. #[derive(Clone, Debug)] @@ -52,12 +55,13 @@ pub struct ReceiveTlvs { pub payment_secret: PaymentSecret, /// Constraints for the receiver of this payment. pub payment_constraints: PaymentConstraints, + /// Context for the receiver of this payment. + pub payment_context: PaymentContext, } /// Data to construct a [`BlindedHop`] for sending a payment over. /// /// [`BlindedHop`]: crate::blinded_path::BlindedHop -#[allow(dead_code)] pub(crate) enum BlindedPaymentTlvs { /// This blinded payment data is for a forwarding node. Forward(ForwardTlvs), @@ -97,13 +101,97 @@ pub struct PaymentConstraints { pub htlc_minimum_msat: u64, } +/// The context of an inbound payment, which is included in a [`BlindedPath`] via [`ReceiveTlvs`] +/// and surfaced in [`PaymentPurpose`]. +/// +/// [`BlindedPath`]: crate::blinded_path::BlindedPath +/// [`PaymentPurpose`]: crate::events::PaymentPurpose +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum PaymentContext { + /// The payment context was unknown. + Unknown(UnknownPaymentContext), + + /// The payment was made for an invoice requested from a BOLT 12 [`Offer`]. + /// + /// [`Offer`]: crate::offers::offer::Offer + Bolt12Offer(Bolt12OfferContext), + + /// The payment was made for an invoice sent for a BOLT 12 [`Refund`]. + /// + /// [`Refund`]: crate::offers::refund::Refund + Bolt12Refund(Bolt12RefundContext), +} + +// Used when writing PaymentContext in Event::PaymentClaimable to avoid cloning. +pub(crate) enum PaymentContextRef<'a> { + Bolt12Offer(&'a Bolt12OfferContext), + Bolt12Refund(&'a Bolt12RefundContext), +} + +/// An unknown payment context. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct UnknownPaymentContext(()); + +/// The context of a payment made for an invoice requested from a BOLT 12 [`Offer`]. +/// +/// [`Offer`]: crate::offers::offer::Offer +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Bolt12OfferContext { + /// The identifier of the [`Offer`]. + /// + /// [`Offer`]: crate::offers::offer::Offer + pub offer_id: OfferId, + + /// Fields from an [`InvoiceRequest`] sent for a [`Bolt12Invoice`]. + /// + /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest + /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice + pub invoice_request: InvoiceRequestFields, +} + +/// The context of a payment made for an invoice sent for a BOLT 12 [`Refund`]. +/// +/// [`Refund`]: crate::offers::refund::Refund +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Bolt12RefundContext {} + +impl PaymentContext { + pub(crate) fn unknown() -> Self { + PaymentContext::Unknown(UnknownPaymentContext(())) + } +} + +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> { + let features_opt = + if self.features == BlindedHopFeatures::empty() { None } + else { Some(&self.features) }; encode_tlv_stream!(w, { (2, self.short_channel_id, required), (10, self.payment_relay, required), (12, self.payment_constraints, required), - (14, self.features, required) + (14, features_opt, option) }); Ok(()) } @@ -113,27 +201,13 @@ impl Writeable for ReceiveTlvs { fn write(&self, w: &mut W) -> Result<(), io::Error> { encode_tlv_stream!(w, { (12, self.payment_constraints, required), - (65536, self.payment_secret, required) + (65536, self.payment_secret, required), + (65537, self.payment_context, required) }); Ok(()) } } -// This will be removed once we support forwarding blinded HTLCs, because we'll always read a -// `BlindedPaymentTlvs` instead. -impl Readable for ReceiveTlvs { - fn read(r: &mut R) -> Result { - _init_and_read_tlv_stream!(r, { - (12, payment_constraints, required), - (65536, payment_secret, required), - }); - Ok(Self { - payment_secret: payment_secret.0.unwrap(), - payment_constraints: payment_constraints.0.unwrap() - }) - } -} - impl<'a> Writeable for BlindedPaymentTlvsRef<'a> { fn write(&self, w: &mut W) -> Result<(), io::Error> { // TODO: write padding @@ -154,22 +228,26 @@ impl Readable for BlindedPaymentTlvs { (12, payment_constraints, required), (14, features, option), (65536, payment_secret, option), + (65537, payment_context, (default_value, PaymentContext::unknown())), }); let _padding: Option = _padding; if let Some(short_channel_id) = scid { - if payment_secret.is_some() { return Err(DecodeError::InvalidValue) } + if payment_secret.is_some() { + return Err(DecodeError::InvalidValue) + } Ok(BlindedPaymentTlvs::Forward(ForwardTlvs { short_channel_id, payment_relay: payment_relay.ok_or(DecodeError::InvalidValue)?, payment_constraints: payment_constraints.0.unwrap(), - features: features.ok_or(DecodeError::InvalidValue)?, + features: features.unwrap_or_else(BlindedHopFeatures::empty), })) } else { if payment_relay.is_some() || features.is_some() { return Err(DecodeError::InvalidValue) } Ok(BlindedPaymentTlvs::Receive(ReceiveTlvs { payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?, payment_constraints: payment_constraints.0.unwrap(), + payment_context: payment_context.0.unwrap(), })) } } @@ -188,7 +266,7 @@ pub(super) fn blinded_hops( } /// `None` if underflow occurs. -fn amt_to_forward_msat(inbound_amt_msat: u64, payment_relay: &PaymentRelay) -> Option { +pub(crate) fn amt_to_forward_msat(inbound_amt_msat: u64, payment_relay: &PaymentRelay) -> Option { let inbound_amt = inbound_amt_msat as u128; let base = payment_relay.fee_base_msat as u128; let prop = payment_relay.fee_proportional_millionths as u128; @@ -209,11 +287,12 @@ fn amt_to_forward_msat(inbound_amt_msat: u64, payment_relay: &PaymentRelay) -> O } 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. @@ -269,23 +348,86 @@ pub(super) fn compute_payinfo( }) } -impl_writeable_msg!(PaymentRelay, { - cltv_expiry_delta, - fee_proportional_millionths, - fee_base_msat -}, {}); +impl Writeable for PaymentRelay { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.cltv_expiry_delta.write(w)?; + self.fee_proportional_millionths.write(w)?; + HighZeroBytesDroppedBigSize(self.fee_base_msat).write(w) + } +} +impl Readable for PaymentRelay { + fn read(r: &mut R) -> Result { + let cltv_expiry_delta: u16 = Readable::read(r)?; + let fee_proportional_millionths: u32 = Readable::read(r)?; + let fee_base_msat: HighZeroBytesDroppedBigSize = Readable::read(r)?; + Ok(Self { cltv_expiry_delta, fee_proportional_millionths, fee_base_msat: fee_base_msat.0 }) + } +} + +impl Writeable for PaymentConstraints { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.max_cltv_expiry.write(w)?; + HighZeroBytesDroppedBigSize(self.htlc_minimum_msat).write(w) + } +} +impl Readable for PaymentConstraints { + fn read(r: &mut R) -> Result { + let max_cltv_expiry: u32 = Readable::read(r)?; + let htlc_minimum_msat: HighZeroBytesDroppedBigSize = Readable::read(r)?; + Ok(Self { max_cltv_expiry, htlc_minimum_msat: htlc_minimum_msat.0 }) + } +} + +impl_writeable_tlv_based_enum!(PaymentContext, + ; + (0, Unknown), + (1, Bolt12Offer), + (2, Bolt12Refund), +); + +impl<'a> Writeable for PaymentContextRef<'a> { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + match self { + PaymentContextRef::Bolt12Offer(context) => { + 1u8.write(w)?; + context.write(w)?; + }, + PaymentContextRef::Bolt12Refund(context) => { + 2u8.write(w)?; + context.write(w)?; + }, + } + + Ok(()) + } +} + +impl Writeable for UnknownPaymentContext { + fn write(&self, _w: &mut W) -> Result<(), io::Error> { + Ok(()) + } +} + +impl Readable for UnknownPaymentContext { + fn read(_r: &mut R) -> Result { + Ok(UnknownPaymentContext(())) + } +} + +impl_writeable_tlv_based!(Bolt12OfferContext, { + (0, offer_id, required), + (2, invoice_request, required), +}); -impl_writeable_msg!(PaymentConstraints, { - max_cltv_expiry, - htlc_minimum_msat -}, {}); +impl_writeable_tlv_based!(Bolt12RefundContext, {}); #[cfg(test)] mod tests { use bitcoin::secp256k1::PublicKey; - use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, ReceiveTlvs, PaymentConstraints, PaymentRelay}; - use crate::ln::PaymentSecret; + use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, ReceiveTlvs, PaymentConstraints, PaymentContext, PaymentRelay}; + use crate::ln::types::PaymentSecret; use crate::ln::features::BlindedHopFeatures; + use crate::ln::functional_test_utils::TEST_FINAL_CLTV; #[test] fn compute_payinfo() { @@ -331,12 +473,13 @@ mod tests { max_cltv_expiry: 0, htlc_minimum_msat: 1, }, + payment_context: PaymentContext::unknown(), }; 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); } @@ -349,11 +492,12 @@ mod tests { max_cltv_expiry: 0, htlc_minimum_msat: 1, }, + payment_context: PaymentContext::unknown(), }; - 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); } @@ -402,9 +546,10 @@ mod tests { max_cltv_expiry: 0, htlc_minimum_msat: 3, }, + payment_context: PaymentContext::unknown(), }; 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); } @@ -452,12 +597,13 @@ mod tests { max_cltv_expiry: 0, htlc_minimum_msat: 1, }, + payment_context: PaymentContext::unknown(), }; 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); } @@ -506,9 +652,10 @@ mod tests { max_cltv_expiry: 0, htlc_minimum_msat: 1, }, + payment_context: PaymentContext::unknown(), }; - 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 c62b4e6c261..7e43f314536 100644 --- a/lightning/src/blinded_path/utils.rs +++ b/lightning/src/blinded_path/utils.rs @@ -18,11 +18,13 @@ 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::util::ser::{Readable, VecWriter, Writeable}; +use crate::onion_message::messenger::Destination; +use crate::crypto::streams::ChaChaPolyWriteAdapter; +use crate::util::ser::{Readable, Writeable}; use crate::io; + +#[allow(unused_imports)] use crate::prelude::*; // TODO: DRY with onion_utils::construct_onion_keys_callback @@ -49,7 +51,7 @@ where let hop_pk_blinding_factor = { let mut hmac = HmacEngine::::new(b"blinded_node_id"); hmac.input(encrypted_data_ss.as_ref()); - Hmac::from_engine(hmac).into_inner() + Hmac::from_engine(hmac).to_byte_array() }; $pk.mul_tweak(secp_ctx, &Scalar::from_be_bytes(hop_pk_blinding_factor).unwrap())? }; @@ -70,7 +72,7 @@ where let mut sha = Sha256::engine(); sha.input(&msg_blinding_point.serialize()[..]); sha.input(encrypted_data_ss.as_ref()); - Sha256::from_engine(sha).into_inner() + Sha256::from_engine(sha).to_byte_array() }; msg_blinding_point_priv = msg_blinding_point_priv.mul_tweak(&Scalar::from_be_bytes(msg_blinding_point_blinding_factor).unwrap())?; @@ -80,7 +82,7 @@ where let mut sha = Sha256::engine(); sha.input(&onion_packet_pubkey.serialize()[..]); sha.input(onion_packet_ss.as_ref()); - Sha256::from_engine(sha).into_inner() + Sha256::from_engine(sha).to_byte_array() }; onion_packet_pubkey_priv = onion_packet_pubkey_priv.mul_tweak(&Scalar::from_be_bytes(onion_packet_pubkey_blinding_factor).unwrap())?; onion_packet_pubkey = PublicKey::from_secret_key(secp_ctx, &onion_packet_pubkey_priv); @@ -129,10 +131,8 @@ where /// Encrypt TLV payload to be used as a [`crate::blinded_path::BlindedHop::encrypted_payload`]. fn encrypt_payload(payload: P, encrypted_tlvs_rho: [u8; 32]) -> Vec { - let mut writer = VecWriter(Vec::new()); let write_adapter = ChaChaPolyWriteAdapter::new(encrypted_tlvs_rho, &payload); - write_adapter.write(&mut writer).expect("In-memory writes cannot fail"); - writer.0 + write_adapter.encode() } /// Blinded path encrypted payloads may be padded to ensure they are equal length. diff --git a/lightning/src/chain/chaininterface.rs b/lightning/src/chain/chaininterface.rs index 73707f05236..9909e115ed6 100644 --- a/lightning/src/chain/chaininterface.rs +++ b/lightning/src/chain/chaininterface.rs @@ -14,7 +14,8 @@ //! disconnections, transaction broadcasting, and feerate information requests. use core::{cmp, ops::Deref}; -use core::convert::TryInto; + +use crate::prelude::*; use bitcoin::blockdata::transaction::Transaction; @@ -40,7 +41,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]); } @@ -53,23 +54,6 @@ pub enum ConfirmationTarget { /// to low hundreds of blocks to get our transaction on-chain, but we shouldn't risk too low a /// fee - this should be a relatively high priority feerate. OnChainSweep, - /// The highest feerate we will allow our channel counterparty to have in a non-anchor channel. - /// - /// This is the feerate on the transaction which we (or our counterparty) will broadcast in - /// order to close the channel unilaterally. Because our counterparty must ensure they can - /// always broadcast the latest state, this value being too low will cause immediate - /// force-closures. - /// - /// Allowing this value to be too high can allow our counterparty to burn our HTLC outputs to - /// dust, which can result in HTLCs failing or force-closures (when the dust HTLCs exceed - /// [`ChannelConfig::max_dust_htlc_exposure`]). - /// - /// Because most nodes use a feerate estimate which is based on a relatively high priority - /// transaction entering the current mempool, setting this to a small multiple of your current - /// high priority feerate estimate should suffice. - /// - /// [`ChannelConfig::max_dust_htlc_exposure`]: crate::util::config::ChannelConfig::max_dust_htlc_exposure - MaxAllowedNonAnchorChannelRemoteFee, /// This is the lowest feerate we will allow our channel counterparty to have in an anchor /// channel in order to close the channel if a channel party goes away. /// @@ -140,6 +124,17 @@ pub enum ConfirmationTarget { /// /// [`ChannelManager::close_channel_with_feerate_and_script`]: crate::ln::channelmanager::ChannelManager::close_channel_with_feerate_and_script ChannelCloseMinimum, + /// The feerate [`OutputSweeper`] will use on transactions spending + /// [`SpendableOutputDescriptor`]s after a channel closure. + /// + /// Generally spending these outputs is safe as long as they eventually confirm, so a value + /// (slightly above) the mempool minimum should suffice. However, as this value will influence + /// how long funds will be unavailable after channel closure, [`FeeEstimator`] implementors + /// might want to choose a higher feerate to regain control over funds faster. + /// + /// [`OutputSweeper`]: crate::util::sweep::OutputSweeper + /// [`SpendableOutputDescriptor`]: crate::sign::SpendableOutputDescriptor + OutputSpendingFee, } /// A trait which should be implemented to provide feerate information on a number of time @@ -152,6 +147,10 @@ pub enum ConfirmationTarget { /// /// Note that all of the functions implemented here *must* be reentrant-safe (obviously - they're /// called from inside the library in response to chain events, P2P events, or timer events). +/// +/// LDK may generate a substantial number of fee-estimation calls in some cases. You should +/// pre-calculate and cache the fee estimate results to ensure you don't substantially slow HTLC +/// handling. pub trait FeeEstimator { /// Gets estimated satoshis of fee required per 1000 Weight-Units. /// diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index e87d082d9a7..ccb30e132c2 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -23,26 +23,26 @@ //! events. The remote server would make use of [`ChainMonitor`] for block processing and for //! servicing [`ChannelMonitor`] updates from the client. -use bitcoin::blockdata::block::BlockHeader; +use bitcoin::blockdata::block::Header; use bitcoin::hash_types::{Txid, BlockHash}; use crate::chain; use crate::chain::{ChannelMonitorUpdateStatus, Filter, WatchedOutput}; use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; -use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, Balance, MonitorEvent, TransactionOutputs, LATENCY_GRACE_PERIOD_BLOCKS}; +use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, Balance, MonitorEvent, TransactionOutputs, WithChannelMonitor, LATENCY_GRACE_PERIOD_BLOCKS}; use crate::chain::transaction::{OutPoint, TransactionData}; -use crate::sign::WriteableEcdsaChannelSigner; +use crate::ln::types::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,12 @@ 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; + /// Prevents the channel monitor from being loaded on startup. + /// + /// Archiving the data in a backup location (rather than deleting it fully) is useful for + /// hedging against data loss in case of unexpected failure. + fn archive_persisted_channel(&self, channel_funding_outpoint: OutPoint); } struct MonitorHolder { @@ -287,10 +292,12 @@ 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, + /// A [`Notifier`] used to wake up the background processor in case we have any [`Event`]s for + /// it to give to users (or [`MonitorEvent`]s for `ChannelManager` to process). event_notifier: Notifier, } @@ -312,12 +319,12 @@ where C::Target: chain::Filter, /// updated `txdata`. /// /// Calls which represent a new blockchain tip height should set `best_height`. - fn process_chain_data(&self, header: &BlockHeader, best_height: Option, txdata: &TransactionData, process: FN) + fn process_chain_data(&self, header: &Header, best_height: Option, txdata: &TransactionData, process: FN) where 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) { @@ -355,15 +362,17 @@ where C::Target: chain::Filter, } fn update_monitor_with_chain_data( - &self, header: &BlockHeader, best_height: Option, txdata: &TransactionData, + &self, header: &Header, best_height: Option, txdata: &TransactionData, process: FN, funding_outpoint: &OutPoint, monitor_state: &MonitorHolder ) -> Result<(), ()> where FN: Fn(&ChannelMonitor, &TransactionData) -> Vec { let monitor = &monitor_state.monitor; + let logger = WithChannelMonitor::from(&self.logger, &monitor); let mut txn_outputs; { txn_outputs = process(monitor, txdata); + let chain_sync_update_id = self.sync_persistence_id.get_increment(); let update_id = MonitorUpdateId { - contents: UpdateOrigin::ChainSync(self.sync_persistence_id.get_increment()), + contents: UpdateOrigin::ChainSync(chain_sync_update_id), }; let mut pending_monitor_updates = monitor_state.pending_monitor_updates.lock().unwrap(); if let Some(height) = best_height { @@ -375,12 +384,18 @@ where C::Target: chain::Filter, } } - log_trace!(self.logger, "Syncing Channel Monitor for channel {}", log_funding_info!(monitor)); + log_trace!(logger, "Syncing Channel Monitor for channel {} for block-data update_id {}", + log_funding_info!(monitor), + chain_sync_update_id + ); match self.persister.update_persisted_channel(*funding_outpoint, None, monitor, update_id) { ChannelMonitorUpdateStatus::Completed => - log_trace!(self.logger, "Finished syncing Channel Monitor for channel {}", log_funding_info!(monitor)), + log_trace!(logger, "Finished syncing Channel Monitor for channel {} for block-data update_id {}", + log_funding_info!(monitor), + chain_sync_update_id + ), ChannelMonitorUpdateStatus::InProgress => { - log_debug!(self.logger, "Channel Monitor sync for channel {} in progress, holding events until completion!", log_funding_info!(monitor)); + log_debug!(logger, "Channel Monitor sync for channel {} in progress, holding events until completion!", log_funding_info!(monitor)); pending_monitor_updates.push(update_id); }, ChannelMonitorUpdateStatus::UnrecoverableError => { @@ -401,7 +416,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); } } } @@ -417,7 +433,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, @@ -469,20 +485,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)] @@ -524,7 +543,7 @@ where C::Target: chain::Filter, pending_monitor_updates.retain(|update_id| *update_id != completed_update_id); match completed_update_id { - MonitorUpdateId { contents: UpdateOrigin::OffChain(_) } => { + MonitorUpdateId { contents: UpdateOrigin::OffChain(completed_update_id) } => { // Note that we only check for `UpdateOrigin::OffChain` failures here - if // we're being told that a `UpdateOrigin::OffChain` monitor update completed, // we only care about ensuring we don't tell the `ChannelManager` to restore @@ -535,18 +554,37 @@ where C::Target: chain::Filter, // `MonitorEvent`s from the monitor back to the `ChannelManager` until they // complete. let monitor_is_pending_updates = monitor_data.has_pending_offchain_updates(&pending_monitor_updates); + log_debug!(self.logger, "Completed off-chain monitor update {} for channel with funding outpoint {:?}, {}", + completed_update_id, + funding_txo, + if monitor_is_pending_updates { + "still have pending off-chain updates" + } else { + "all off-chain updates complete, returning a MonitorEvent" + }); if monitor_is_pending_updates { // If there are still monitor updates pending, we cannot yet construct a // 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())); }, - MonitorUpdateId { contents: UpdateOrigin::ChainSync(_) } => { - if !monitor_data.has_pending_chainsync_updates(&pending_monitor_updates) { + MonitorUpdateId { contents: UpdateOrigin::ChainSync(completed_update_id) } => { + let monitor_has_pending_updates = + monitor_data.has_pending_chainsync_updates(&pending_monitor_updates); + log_debug!(self.logger, "Completed chain sync monitor update {} for channel with funding outpoint {:?}, {}", + completed_update_id, + funding_txo, + if monitor_has_pending_updates { + "still have pending chain sync updates" + } else { + "all chain sync updates complete, releasing pending MonitorEvents" + }); + if !monitor_has_pending_updates { monitor_data.last_chain_persist_height.store(self.highest_chain_height.load(Ordering::Acquire), Ordering::Release); // The next time release_pending_monitor_events is called, any events for this // ChannelMonitor will be returned. @@ -563,9 +601,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(); @@ -620,10 +663,66 @@ where C::Target: chain::Filter, let monitors = self.monitors.read().unwrap(); for (_, monitor_holder) in &*monitors { monitor_holder.monitor.rebroadcast_pending_claims( - &*self.broadcaster, &*self.fee_estimator, &*self.logger + &*self.broadcaster, &*self.fee_estimator, &self.logger ) } } + + /// 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 + ) + } + } + } + + /// Archives fully resolved channel monitors by calling [`Persist::archive_persisted_channel`]. + /// + /// This is useful for pruning fully resolved monitors from the monitor set and primary + /// storage so they are not kept in memory and reloaded on restart. + /// + /// Should be called occasionally (once every handful of blocks or on startup). + /// + /// Depending on the implementation of [`Persist::archive_persisted_channel`] the monitor + /// data could be moved to an archive location or removed entirely. + pub fn archive_fully_resolved_channel_monitors(&self) { + let mut have_monitors_to_prune = false; + for (_, monitor_holder) in self.monitors.read().unwrap().iter() { + let logger = WithChannelMonitor::from(&self.logger, &monitor_holder.monitor); + if monitor_holder.monitor.is_fully_resolved(&logger) { + have_monitors_to_prune = true; + } + } + if have_monitors_to_prune { + let mut monitors = self.monitors.write().unwrap(); + monitors.retain(|funding_txo, monitor_holder| { + let logger = WithChannelMonitor::from(&self.logger, &monitor_holder.monitor); + if monitor_holder.monitor.is_fully_resolved(&logger) { + log_info!(logger, + "Archiving fully resolved ChannelMonitor for funding txo {}", + funding_txo + ); + self.persister.archive_persisted_channel(*funding_txo); + false + } else { + true + } + }); + } + } } impl @@ -635,20 +734,22 @@ where L::Target: Logger, P::Target: Persist, { - fn filtered_block_connected(&self, header: &BlockHeader, txdata: &TransactionData, height: u32) { + fn filtered_block_connected(&self, header: &Header, txdata: &TransactionData, height: u32) { log_debug!(self.logger, "New best block {} at height {} provided via block_connected", header.block_hash(), height); self.process_chain_data(header, Some(height), &txdata, |monitor, txdata| { monitor.block_connected( - header, txdata, height, &*self.broadcaster, &*self.fee_estimator, &*self.logger) + header, txdata, height, &*self.broadcaster, &*self.fee_estimator, &self.logger) }); + // Assume we may have some new events and wake the event processor + self.event_notifier.notify(); } - fn block_disconnected(&self, header: &BlockHeader, height: u32) { + fn block_disconnected(&self, header: &Header, height: u32) { let monitor_states = self.monitors.read().unwrap(); log_debug!(self.logger, "Latest block {} at height {} removed via block_disconnected", header.block_hash(), height); for monitor_state in monitor_states.values() { monitor_state.monitor.block_disconnected( - header, height, &*self.broadcaster, &*self.fee_estimator, &*self.logger); + header, height, &*self.broadcaster, &*self.fee_estimator, &self.logger); } } } @@ -662,42 +763,47 @@ where L::Target: Logger, P::Target: Persist, { - fn transactions_confirmed(&self, header: &BlockHeader, txdata: &TransactionData, height: u32) { + fn transactions_confirmed(&self, header: &Header, txdata: &TransactionData, height: u32) { log_debug!(self.logger, "{} provided transactions confirmed at height {} in block {}", txdata.len(), height, header.block_hash()); self.process_chain_data(header, None, txdata, |monitor, txdata| { monitor.transactions_confirmed( - header, txdata, height, &*self.broadcaster, &*self.fee_estimator, &*self.logger) + header, txdata, height, &*self.broadcaster, &*self.fee_estimator, &self.logger) }); + // Assume we may have some new events and wake the event processor + self.event_notifier.notify(); } fn transaction_unconfirmed(&self, txid: &Txid) { log_debug!(self.logger, "Transaction {} reorganized out of chain", txid); let monitor_states = self.monitors.read().unwrap(); for monitor_state in monitor_states.values() { - monitor_state.monitor.transaction_unconfirmed(txid, &*self.broadcaster, &*self.fee_estimator, &*self.logger); + monitor_state.monitor.transaction_unconfirmed(txid, &*self.broadcaster, &*self.fee_estimator, &self.logger); } } - fn best_block_updated(&self, header: &BlockHeader, height: u32) { + fn best_block_updated(&self, header: &Header, height: u32) { log_debug!(self.logger, "New best block {} at height {} provided via best_block_updated", header.block_hash(), height); self.process_chain_data(header, Some(height), &[], |monitor, txdata| { // While in practice there shouldn't be any recursive calls when given empty txdata, // it's still possible if a chain::Filter implementation returns a transaction. debug_assert!(txdata.is_empty()); monitor.best_block_updated( - header, height, &*self.broadcaster, &*self.fee_estimator, &*self.logger) + header, height, &*self.broadcaster, &*self.fee_estimator, &self.logger + ) }); + // Assume we may have some new events and wake the event processor + self.event_notifier.notify(); } - fn get_relevant_txids(&self) -> Vec<(Txid, Option)> { + fn get_relevant_txids(&self) -> Vec<(Txid, u32, Option)> { let mut txids = Vec::new(); let monitor_states = self.monitors.read().unwrap(); for monitor_state in monitor_states.values() { txids.append(&mut monitor_state.monitor.get_relevant_txids()); } - txids.sort_unstable(); - txids.dedup(); + txids.sort_unstable_by(|a, b| a.0.cmp(&b.0).then(b.1.cmp(&a.1))); + txids.dedup_by_key(|(txid, _, _)| *txid); txids } } @@ -711,34 +817,35 @@ where C::Target: chain::Filter, P::Target: Persist, { fn watch_channel(&self, funding_outpoint: OutPoint, monitor: ChannelMonitor) -> Result { + let logger = WithChannelMonitor::from(&self.logger, &monitor); let mut monitors = self.monitors.write().unwrap(); let entry = match monitors.entry(funding_outpoint) { hash_map::Entry::Occupied(_) => { - log_error!(self.logger, "Failed to add new channel data: channel monitor for given outpoint is already present"); + log_error!(logger, "Failed to add new channel data: channel monitor for given outpoint is already present"); return Err(()); }, hash_map::Entry::Vacant(e) => e, }; - log_trace!(self.logger, "Got new ChannelMonitor for channel {}", log_funding_info!(monitor)); + log_trace!(logger, "Got new ChannelMonitor for channel {}", log_funding_info!(monitor)); let update_id = MonitorUpdateId::from_new_monitor(&monitor); let mut pending_monitor_updates = Vec::new(); let persist_res = self.persister.persist_new_channel(funding_outpoint, &monitor, update_id); match persist_res { ChannelMonitorUpdateStatus::InProgress => { - log_info!(self.logger, "Persistence of new ChannelMonitor for channel {} in progress", log_funding_info!(monitor)); + log_info!(logger, "Persistence of new ChannelMonitor for channel {} in progress", log_funding_info!(monitor)); pending_monitor_updates.push(update_id); }, ChannelMonitorUpdateStatus::Completed => { - log_info!(self.logger, "Persistence of new ChannelMonitor for channel {} completed", log_funding_info!(monitor)); + log_info!(logger, "Persistence of new ChannelMonitor for channel {} completed", log_funding_info!(monitor)); }, ChannelMonitorUpdateStatus::UnrecoverableError => { let err_str = "ChannelMonitor[Update] persistence failed unrecoverably. This indicates we cannot continue normal operation and must shut down."; - log_error!(self.logger, "{}", err_str); + log_error!(logger, "{}", err_str); panic!("{}", err_str); }, } 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, @@ -749,11 +856,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(); - let ret = match monitors.get(&funding_txo) { + 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 @@ -765,7 +876,8 @@ where C::Target: chain::Filter, }, Some(monitor_state) => { let monitor = &monitor_state.monitor; - log_trace!(self.logger, "Updating ChannelMonitor for channel {}", log_funding_info!(monitor)); + let logger = WithChannelMonitor::from(&self.logger, &monitor); + log_trace!(logger, "Updating ChannelMonitor to id {} for channel {}", update.update_id, log_funding_info!(monitor)); let update_res = monitor.update_monitor(update, &self.broadcaster, &self.fee_estimator, &self.logger); let update_id = MonitorUpdateId::from_monitor_update(update); @@ -776,7 +888,7 @@ where C::Target: chain::Filter, // We don't want to persist a `monitor_update` which results in a failure to apply later // while reading `channel_monitor` with updates from storage. Instead, we should persist // the entire `channel_monitor` here. - log_warn!(self.logger, "Failed to update ChannelMonitor for channel {}. Going ahead and persisting the entire ChannelMonitor", log_funding_info!(monitor)); + log_warn!(logger, "Failed to update ChannelMonitor for channel {}. Going ahead and persisting the entire ChannelMonitor", log_funding_info!(monitor)); self.persister.update_persisted_channel(funding_txo, None, monitor, update_id) } else { self.persister.update_persisted_channel(funding_txo, Some(update), monitor, update_id) @@ -784,12 +896,29 @@ where C::Target: chain::Filter, match persist_res { ChannelMonitorUpdateStatus::InProgress => { pending_monitor_updates.push(update_id); - log_debug!(self.logger, "Persistence of ChannelMonitorUpdate for channel {} in progress", log_funding_info!(monitor)); + log_debug!(logger, + "Persistence of ChannelMonitorUpdate id {:?} for channel {} in progress", + update_id, + log_funding_info!(monitor) + ); }, ChannelMonitorUpdateStatus::Completed => { - log_debug!(self.logger, "Persistence of ChannelMonitorUpdate for channel {} completed", log_funding_info!(monitor)); + log_debug!(logger, + "Persistence of ChannelMonitorUpdate id {:?} for channel {} completed", + update_id, + log_funding_info!(monitor) + ); + }, + ChannelMonitorUpdateStatus::UnrecoverableError => { + // Take the monitors lock for writing so that we poison it and any future + // operations going forward fail immediately. + core::mem::drop(pending_monitor_updates); + core::mem::drop(monitors); + let _poison = self.monitors.write().unwrap(); + let err_str = "ChannelMonitor[Update] persistence failed unrecoverably. This indicates we cannot continue normal operation and must shut down."; + log_error!(logger, "{}", err_str); + panic!("{}", err_str); }, - ChannelMonitorUpdateStatus::UnrecoverableError => { /* we'll panic in a moment */ }, } if update_res.is_err() { ChannelMonitorUpdateStatus::InProgress @@ -797,39 +926,26 @@ where C::Target: chain::Filter, persist_res } } - }; - if let ChannelMonitorUpdateStatus::UnrecoverableError = ret { - // Take the monitors lock for writing so that we poison it and any future - // operations going forward fail immediately. - core::mem::drop(monitors); - let _poison = self.monitors.write().unwrap(); - let err_str = "ChannelMonitor[Update] persistence failed unrecoverably. This indicates we cannot continue normal operation and must shut down."; - log_error!(self.logger, "{}", err_str); - panic!("{}", err_str); } - ret } - fn release_pending_monitor_events(&self) -> Vec<(OutPoint, Vec, 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); let is_pending_monitor_update = monitor_state.has_pending_chainsync_updates(&monitor_state.pending_monitor_updates.lock().unwrap()); - if is_pending_monitor_update && - monitor_state.last_chain_persist_height.load(Ordering::Acquire) + LATENCY_GRACE_PERIOD_BLOCKS as usize - > self.highest_chain_height.load(Ordering::Acquire) - { - log_debug!(self.logger, "A Channel Monitor sync is still in progress, refusing to provide monitor events!"); - } else { + if !is_pending_monitor_update || monitor_state.last_chain_persist_height.load(Ordering::Acquire) + LATENCY_GRACE_PERIOD_BLOCKS as usize <= self.highest_chain_height.load(Ordering::Acquire) { if is_pending_monitor_update { - log_error!(self.logger, "A ChannelMonitor sync took longer than {} blocks to complete.", LATENCY_GRACE_PERIOD_BLOCKS); - log_error!(self.logger, " To avoid funds-loss, we are allowing monitor updates to be released."); - log_error!(self.logger, " This may cause duplicate payment events to be generated."); + log_error!(logger, "A ChannelMonitor sync took longer than {} blocks to complete.", LATENCY_GRACE_PERIOD_BLOCKS); + log_error!(logger, " To avoid funds-loss, we are allowing monitor updates to be released."); + log_error!(logger, " This may cause duplicate payment events to be generated."); } 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 98a0d7aa8b5..9410431156d 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -20,9 +20,9 @@ //! security-domain-separated system design, you should consider having multiple paths for //! ChannelMonitors to get out of the HSM and onto monitoring devices. -use bitcoin::blockdata::block::BlockHeader; +use bitcoin::blockdata::block::Header; use bitcoin::blockdata::transaction::{OutPoint as BitcoinOutPoint, TxOut, Transaction}; -use bitcoin::blockdata::script::Script; +use bitcoin::blockdata::script::{Script, ScriptBuf}; use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; @@ -30,33 +30,35 @@ use bitcoin::hash_types::{Txid, BlockHash}; use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature}; use bitcoin::secp256k1::{SecretKey, PublicKey}; -use bitcoin::{secp256k1, EcdsaSighashType}; +use bitcoin::secp256k1; +use bitcoin::sighash::EcdsaSighashType; use crate::ln::channel::INITIAL_COMMITMENT_NUMBER; -use crate::ln::{PaymentHash, PaymentPreimage}; +use crate::ln::types::{PaymentHash, PaymentPreimage, ChannelId}; use crate::ln::msgs::DecodeError; -use crate::ln::chan_utils; -use crate::ln::chan_utils::{CommitmentTransaction, CounterpartyCommitmentSecrets, HTLCOutputInCommitment, HTLCClaim, ChannelTransactionParameters, HolderCommitmentTransaction, TxCreationKeys}; +use crate::ln::channel_keys::{DelayedPaymentKey, DelayedPaymentBasepoint, HtlcBasepoint, HtlcKey, RevocationKey, RevocationBasepoint}; +use crate::ln::chan_utils::{self,CommitmentTransaction, CounterpartyCommitmentSecrets, HTLCOutputInCommitment, HTLCClaim, ChannelTransactionParameters, HolderCommitmentTransaction, TxCreationKeys}; use crate::ln::channelmanager::{HTLCSource, SentHTLCId}; use crate::chain; 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, WriteableEcdsaChannelSigner, SignerProvider, EntropySource}; -use crate::chain::onchaintx::{ClaimEvent, OnchainTxHandler}; +use crate::sign::{ChannelDerivationParameters, HTLCDescriptor, SpendableOutputDescriptor, StaticPaymentOutputDescriptor, DelayedPaymentOutputDescriptor, ecdsa::WriteableEcdsaChannelSigner, SignerProvider, EntropySource}; +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; +use crate::util::logger::{Logger, Record}; use crate::util::ser::{Readable, ReadableArgs, RequiredWrapper, MaybeReadable, UpgradableRequired, Writer, Writeable, U48}; use crate::util::byte_utils; -use crate::events::{Event, EventHandler}; +use crate::events::{ClosureReason, Event, EventHandler}; use crate::events::bump_transaction::{AnchorDescriptor, BumpTransactionEvent}; +#[allow(unused_imports)] use crate::prelude::*; + use core::{cmp, mem}; use std::path::PathBuf; use crate::io::{self, Error}; -use core::convert::TryInto; use core::ops::Deref; use crate::sync::{Mutex, LockTestExt}; @@ -71,6 +73,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 +98,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 +123,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 +141,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 }) } } @@ -133,7 +157,19 @@ pub enum MonitorEvent { /// A monitor event containing an HTLCUpdate. HTLCEvent(HTLCUpdate), - /// A monitor event that the Channel's commitment transaction was confirmed. + /// Indicates we broadcasted the channel's latest commitment transaction and thus closed the + /// channel. Holds information about the channel and why it was closed. + HolderForceClosedWithInfo { + /// The reason the channel was closed. + reason: ClosureReason, + /// The funding outpoint of the channel. + outpoint: OutPoint, + /// The channel ID of the channel. + channel_id: ChannelId, + }, + + /// Indicates we broadcasted the channel's latest commitment transaction and thus closed the + /// channel. HolderForceClosed(OutPoint), /// Indicates a [`ChannelMonitor`] update has completed. See @@ -143,6 +179,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`]. /// @@ -157,6 +195,12 @@ impl_writeable_tlv_based_enum_upgradable!(MonitorEvent, (0, Completed) => { (0, funding_txo, required), (2, monitor_update_id, required), + (4, channel_id, required), + }, + (5, HolderForceClosedWithInfo) => { + (0, reason, upgradable_required), + (2, outpoint, required), + (4, channel_id, required), }, ; (2, HTLCEvent), @@ -239,10 +283,10 @@ pub(crate) const HTLC_FAIL_BACK_BUFFER: u32 = CLTV_CLAIM_BUFFER + LATENCY_GRACE_ struct HolderSignedTx { /// txid of the transaction in tx, just used to make comparison faster txid: Txid, - revocation_key: PublicKey, - a_htlc_key: PublicKey, - b_htlc_key: PublicKey, - delayed_payment_key: PublicKey, + revocation_key: RevocationKey, + a_htlc_key: HtlcKey, + b_htlc_key: HtlcKey, + delayed_payment_key: DelayedPaymentKey, per_commitment_point: PublicKey, htlc_outputs: Vec<(HTLCOutputInCommitment, Option, Option)>, to_self_value_sat: u64, @@ -279,8 +323,8 @@ impl HolderSignedTx { /// justice or 2nd-stage preimage/timeout transactions. #[derive(Clone, PartialEq, Eq)] struct CounterpartyCommitmentParameters { - counterparty_delayed_payment_base_key: PublicKey, - counterparty_htlc_base_key: PublicKey, + counterparty_delayed_payment_base_key: DelayedPaymentBasepoint, + counterparty_htlc_base_key: HtlcBasepoint, on_counterparty_tx_csv: u16, } @@ -363,7 +407,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() } } @@ -520,7 +564,7 @@ pub(crate) enum ChannelMonitorUpdateStep { should_broadcast: bool, }, ShutdownScript { - scriptpubkey: Script, + scriptpubkey: ScriptBuf, }, } @@ -754,19 +798,20 @@ pub(crate) struct ChannelMonitorImpl { latest_update_id: u64, commitment_transaction_number_obscure_factor: u64, - destination_script: Script, - broadcasted_holder_revokable_script: Option<(Script, PublicKey, PublicKey)>, - counterparty_payment_script: Script, - shutdown_script: Option