- name: Checkout source code
uses: actions/checkout@v3
- name: Install Rust ${{ matrix.toolchain }} toolchain
- uses: actions-rs/toolchain@v1
- with:
- toolchain: ${{ matrix.toolchain }}
- override: true
- profile: minimal
+ run: |
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ matrix.toolchain }}
+ rustup override set ${{ matrix.toolchain }}
- name: Install no-std-check dependencies for ARM Embedded
if: "matrix.platform == 'ubuntu-latest'"
run: |
- name: Checkout source code
uses: actions/checkout@v3
- name: Install Rust ${{ env.TOOLCHAIN }} toolchain
- uses: actions-rs/toolchain@v1
- with:
- toolchain: ${{ env.TOOLCHAIN }}
- override: true
- profile: minimal
+ run: |
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ env.TOOLCHAIN }}
+ rustup override set ${{ env.TOOLCHAIN }}
- name: Cache routing graph snapshot
id: cache-graph
uses: actions/cache@v3
check_commits:
runs-on: ubuntu-latest
env:
- TOOLCHAIN: 1.57.0
+ TOOLCHAIN: stable
steps:
- name: Checkout source code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install Rust ${{ env.TOOLCHAIN }} toolchain
- uses: actions-rs/toolchain@v1
- with:
- toolchain: ${{ env.TOOLCHAIN }}
- override: true
- profile: minimal
+ run: |
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ env.TOOLCHAIN }}
+ rustup override set ${{ env.TOOLCHAIN }}
- name: Fetch full tree and rebase on upstream
run: |
git remote add upstream https://github.com/lightningdevkit/rust-lightning
with:
fetch-depth: 0
- name: Install Rust ${{ env.TOOLCHAIN }} toolchain
- uses: actions-rs/toolchain@v1
- with:
- toolchain: ${{ env.TOOLCHAIN }}
- override: true
- profile: minimal
+ run: |
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ env.TOOLCHAIN }}
+ rustup override set ${{ env.TOOLCHAIN }}
- name: Run cargo check for release build.
run: |
cargo check --release
cargo check --no-default-features --features=no-std --release
cargo check --no-default-features --features=futures --release
cargo doc --release
- RUSTDOCFLAGS="--cfg=anchors" cargo doc --release
- name: Run cargo check for Taproot build.
run: |
cargo check --release
cargo check --no-default-features --features=futures --release
cargo doc --release
env:
- RUSTFLAGS: '--cfg=anchors --cfg=taproot'
- RUSTDOCFLAGS: '--cfg=anchors --cfg=taproot'
+ RUSTFLAGS: '--cfg=taproot'
+ RUSTDOCFLAGS: '--cfg=taproot'
fuzz:
runs-on: ubuntu-latest
env:
- TOOLCHAIN: stable
+ TOOLCHAIN: 1.58
steps:
- name: Checkout source code
uses: actions/checkout@v3
- - name: Install Rust 1.58 toolchain
- uses: actions-rs/toolchain@v1
- with:
- toolchain: 1.58
- override: true
- profile: minimal
+ - name: Install Rust ${{ env.TOOLCHAIN }} toolchain
+ run: |
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ env.TOOLCHAIN }}
+ rustup override set ${{ env.TOOLCHAIN }}
- name: Install dependencies for honggfuzz
run: |
sudo apt-get update
- name: Checkout source code
uses: actions/checkout@v3
- name: Install Rust ${{ env.TOOLCHAIN }} toolchain
- uses: actions-rs/toolchain@v1
- with:
- toolchain: ${{ env.TOOLCHAIN }}
- override: true
- profile: minimal
+ run: |
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ env.TOOLCHAIN }}
+ rustup override set ${{ env.TOOLCHAIN }}
- name: Install clippy
run: |
rustup component add clippy
+# 0.0.116rc1 - Jul 14, 2023 - "Anchoring the Roadmap"
+
+## API Updates
+
+ * Support for zero-HTLC-fee anchor output channels has been added and is now
+ considered beta (#2367). Users who set
+ `ChannelHandshakeConfig::negotiate_anchors_zero_fee_htlc_tx` should be
+ prepared to handle the new `Event::BumpTransaction`, e.g. via the
+ `BumpTransactionEventHandler` (#2089). Users who set the same and wish to
+ accept inbound anchor-based channels must do so manually by setting
+ `UserConfig::manually_accept_inbound_channels` (#2368).
+ * Support forwarding and accepting HTLCs with a reduced amount has been added,
+ to support LSPs skimming a fee on the penultimate hop (#2319).
+ * BOLT11 and BOLT12 Invoice and related types have been renamed to include a
+ BOLTNN prefix, ensuring uniqueness in `lightning{,-invoice}` crates (#2416).
+ * `Score`rs now have an associated type which represents a parameter passed
+ when calculating penalties. This allows for the same `Score`r to be used with
+ different penalty calculation parameters (#2237).
+ * `DefaultRouter` is no longer restrained to a `Mutex`-wrapped `Score`,
+ allowing it to be used in `no-std` builds (#2383).
+ * `CustomMessageHandler::provided_{node,init}_features` and various custom
+ feature bit methods on `*Features` were added (#2204).
+ * Keysend/push payments using MPP are now supported when receiving if
+ `UserConfig::accept_mpp_keysend` is set and when sending if specified in the
+ `PaymentParameters`. Note that not all recipients support this (#2156).
+ * A new `ConfirmationTarget::MempoolMinimum` has been added (#2415).
+ * `SpendableOutputDescriptor::to_psbt_input` was added (#2286).
+ * `ChannelManager::update_partial_channel_config` was added (#2330).
+ * `ChannelDetails::channel_shutdown_state` was added (#2347).
+ * The shutdown script can now be provided at shutdown time via
+ `ChannelManager::close_channel_with_feerate_and_script` (#2219).
+ * `BroadcasterInterface` now takes multiple transactions at once. While not
+ available today, in the future single calls should be passed to a full node
+ via a single batch/package transaction acceptance API (#2272).
+ * `Balance::claimable_amount_satoshis` was added (#2333).
+ * `payment_{hash,preimage}` have been added to some `Balance` variants (#2217).
+ * The `lightning::chain::keysinterface` is now `lightning::sign` (#2246).
+ * Routing to a blinded path has been implemented, though sending to such a
+ route is not yet supported in `ChannelManager` (#2120).
+ * `OffersMessageHandler` was added for offers-related onion messages (#2294).
+ * The `CustomMessageHandler` parameter to `PeerManager` has moved to
+ `MessageHandler` from `PeerManager::new` explicitly (#2249).
+ * Various P2P messages for dual funding channel establishment have been added,
+ though handling for them is not yet in `ChannelManager` (#1794)
+ * Script-fetching methods in `sign` interfaces can now return errors, see docs
+ for the implications of failing (#2213).
+ * The `data_loss_protect` option is now required when reading
+ `channel_reestablish` messages, as many others have done (#2253).
+ * `InFlightHtlcs::add_inflight_htlc` has been added (#2042).
+ * The `init` message `networks` field is now written and checked (#2329).
+ * `PeerManager` generics have been simplified with the introduction of the
+ `APeerManager` trait (#2249).
+ * `ParitalOrd` and `Ord` are now implemented for `Invoice` (#2279).
+ * `ParitalEq` and `Debug` are now implemented for `InMemorySigner` (#2328).
+ * `ParitalEq` and `Eq` are now implemented for `PaymentError` (#2316).
+ * `NetworkGraph::update_channel_from_announcement_no_lookup` was added (#2222).
+ * `lightning::routing::gossip::verify_{channel,node}_announcement` was added
+ (#2307).
+
+## Backwards Compatibility
+ * `PaymentParameters` written with blinded path info using LDK 0.0.115 will not
+ be readable in LDK 0.0.116, and vice versa.
+ * Forwarding less than `Event::HTLCIntercepted::expected_outbound_amount_msat`
+ in `ChannelManager::forward_intercepted_htlc` may prevent the
+ `ChannelManager` from being read by LDK prior to 0.0.116 (#2319)
+ * Setting `ChannelConfig::accept_underpaying_htlcs` may prevent the
+ `ChannelManager` from being read by LDK prior to 0.0.116 and un-setting the
+ parameter between restarts may lead to payment failures (#2319).
+ * `ChannelManager::create_inbound_payment{,_for_hash}_legacy` has been removed,
+ removing the ability to create inbound payments which are claimable after
+ downgrade to LDK 0.0.103 and prior. In the future handling such payments will
+ also be removed (#2351).
+ * Some fields required by LDK 0.0.103 and earlier are no longer written, thus
+ deserializing objects written by 0.0.116 with 0.0.103 may now fail (#2351).
+
+## Bug Fixes
+ * `ChannelDetails::next_outbound_htlc_limit_msat` was made substantially more
+ accurate and a corresponding `next_outbound_htlc_minimum_msat` was added.
+ This resolves issues where unpayable routes were generated due to
+ overestimation of the amount which is payable over one of our channels as
+ the first hop (#2312).
+ * A rare case where delays in processing `Event`s generated by
+ `ChannelMonitor`s could lead to loss of those events in case of an untimely
+ crash. This could lead to the loss of an `Event::SpendableOutputs` (#2369).
+ * Fixed a regression in 0.0.115 which caused `PendingHTLCsForwardable` events
+ to be missed when processing phantom node receives. This caused such
+ payments to be delayed until a further, unrelated HTLC came in (#2395).
+ * Peers which are unresponsive to channel messages for several timer ticks are
+ now disconnected to allow for on-reconnection state machine reset. This
+ works around some issues in LND prior to 16.3 which can cause channels to
+ hang and eventually force-close (#2293).
+ * `ChannelManager::new` now requires the current time (either from a recent
+ block header or the system clock), ensuring invoices created immediately
+ after startup aren't already expired (#2372).
+ * Resolved an issue where reading a `ProbabilisticScorer` on some platforms
+ (e.g. iOS) can lead to a panic (#2322).
+ * `ChannelConfig::max_dust_htlc_exposure` is now allowed to scale based on
+ current fees, and the default has been updated to do so. This substantially
+ reduces the chance of force-closure due to dust exposure. Note that existing
+ channels will retain their current value and you may wish to update the
+ value on your existing channels on upgrade (#2354).
+ * `PeerManager::process_events` no longer blocks in any case. This fixes a bug
+ where reentrancy from `PeerManager` into user code which eventually calls
+ `process_events` could lead to a deadlock (#2280).
+ * The persist timing of network graph and scoring in
+ `lightning-background-processor` has been tweaked to provide more reliable
+ persistence after updates to either (#2226).
+ * The number of route hints added to BOLT 11 invoices by the
+ `lightning-invoice::utils` builders has been reduced to three to ensure
+ invoices can be represented in scan-able QR codes (#2044).
+ * Fixed sending large onion messages, which would previously have resulted in
+ an HMAC error on the second hop (#2277).
+ * Fixed a memory leak that may occur when a `ChannelManager` or
+ `ChannelMonitor` is `drop`ed (#2233).
+ * A potential deadlock in calling `NetworkGraph::eq` was resolved (#2284).
+ * Fixed an overflow which prevented disconnecting peers in some minor cases
+ with more than 31 peers (#2245).
+ * Gossip messages with an unknown chain hash are now ignored (#2230).
+ * Rapid Gossip Sync processing now fails on an unknown chain hash (#2324).
+ * `RouteHintHop::htlc_maximum_msat` is now enforced. Note that BOLT11 route
+ hints do not have such a field so this code is generally unused (#2305).
+
# 0.0.115 - Apr 24, 2023 - "Rebroadcast the Bugfixes"
## API Updates
"lightning-custom-message",
"lightning-transaction-sync",
"no-std-check",
+ "msrv-no-dev-deps-check",
"bench",
]
cargo doc --document-private-items
cd fuzz && RUSTFLAGS="--cfg=fuzzing" cargo check --features=stdin_fuzz
cd ../lightning && cargo check --no-default-features --features=no-std
+cd .. && RUSTC_BOOTSTRAP=1 RUSTFLAGS="--cfg=c_bindings" cargo check -Z avoid-dev-deps
RUSTC_MINOR_VERSION=$(rustc --version | awk '{ split($2,a,"."); print a[2] }')
HOST_PLATFORM="$(rustc --version --verbose | grep "host:" | awk '{ print $2 }')"
-# Tokio MSRV on versions 1.17 through 1.26 is rustc 1.49. Above 1.26 MSRV is 1.56.
-[ "$RUSTC_MINOR_VERSION" -lt 49 ] && cargo update -p tokio --precise "1.14.1" --verbose
-[[ "$RUSTC_MINOR_VERSION" -gt 48 && "$RUSTC_MINOR_VERSION" -lt 56 ]] && cargo update -p tokio --precise "1.25.1" --verbose
+# Some crates require pinning to meet our MSRV even for our downstream users,
+# which we do here.
+# Further crates which appear only as dev-dependencies are pinned further down.
+function PIN_RELEASE_DEPS {
+ # Tokio MSRV on versions 1.17 through 1.26 is rustc 1.49. Above 1.26 MSRV is 1.56.
+ [ "$RUSTC_MINOR_VERSION" -lt 49 ] && cargo update -p tokio --precise "1.14.1" --verbose
+ [[ "$RUSTC_MINOR_VERSION" -gt 48 && "$RUSTC_MINOR_VERSION" -lt 56 ]] && cargo update -p tokio --precise "1.25.1" --verbose
-# Sadly the log crate is always a dependency of tokio until 1.20, and has no reasonable MSRV guarantees
-[ "$RUSTC_MINOR_VERSION" -lt 49 ] && cargo update -p log --precise "0.4.18" --verbose
+ # Sadly the log crate is always a dependency of tokio until 1.20, and has no reasonable MSRV guarantees
+ [ "$RUSTC_MINOR_VERSION" -lt 49 ] && cargo update -p log --precise "0.4.18" --verbose
+
+ # The serde_json crate switched to Rust edition 2021 starting with v1.0.101, i.e., has MSRV of 1.56
+ [ "$RUSTC_MINOR_VERSION" -lt 56 ] && cargo update -p serde_json --precise "1.0.100" --verbose
+
+ return 0 # Don't fail the script if our rustc is higher than the last check
+}
+
+PIN_RELEASE_DEPS # pin the release dependencies in our main workspace
+
+# The addr2line v0.20 crate (a dependency of `backtrace` starting with 0.3.68) relies on 1.55+
+[ "$RUSTC_MINOR_VERSION" -lt 55 ] && cargo update -p backtrace --precise "0.3.67" --verbose
+
+# The quote crate switched to Rust edition 2021 starting with v1.0.31, i.e., has MSRV of 1.56
+[ "$RUSTC_MINOR_VERSION" -lt 56 ] && cargo update -p quote --precise "1.0.30" --verbose
+
+# The 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
[ "$LDK_COVERAGE_BUILD" != "" ] && export RUSTFLAGS="-C link-dead-code"
echo -e "\n\nTesting no-std build on a downstream no-std crate"
# check no-std compatibility across dependencies
pushd no-std-check
-cargo check --verbose --color always --features lightning-transaction-sync
+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
+ cargo check --verbose --color always
+fi
+popd
+
+# Test that we can build downstream code with only the "release pins".
+pushd msrv-no-dev-deps-check
+PIN_RELEASE_DEPS
+cargo check
popd
if [ -f "$(which arm-none-eabi-gcc)" ]; then
popd
fi
-echo -e "\n\nTest anchors builds"
-pushd lightning
-RUSTFLAGS="$RUSTFLAGS --cfg=anchors" cargo test --verbose --color always -p lightning
echo -e "\n\nTest Taproot builds"
-RUSTFLAGS="$RUSTFLAGS --cfg=anchors --cfg=taproot" cargo test --verbose --color always -p lightning
+pushd lightning
+RUSTFLAGS="$RUSTFLAGS --cfg=taproot" cargo test --verbose --color always -p lightning
popd
use crate::utils::test_logger;
use core::convert::TryFrom;
-use lightning::offers::parse::{Bech32Encode, ParseError};
+use lightning::offers::parse::{Bech32Encode, Bolt12ParseError};
#[inline]
pub fn do_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
}
impl TryFrom<Vec<u8>> for Bytes {
- type Error = ParseError;
- fn try_from(data: Vec<u8>) -> Result<Self, ParseError> {
+ type Error = Bolt12ParseError;
+ fn try_from(data: Vec<u8>) -> Result<Self, Bolt12ParseError> {
Ok(Bytes(data))
}
}
// Background feerate which is <= the minimum Normal feerate.
match conf_target {
ConfirmationTarget::HighPriority => MAX_FEE,
- ConfirmationTarget::Background => 253,
+ ConfirmationTarget::Background|ConfirmationTarget::MempoolMinimum => 253,
ConfirmationTarget::Normal => cmp::min(self.ret_val.load(atomic::Ordering::Acquire), MAX_FEE),
}
}
impl Router for FuzzRouter {
fn find_route(
&self, _payer: &PublicKey, _params: &RouteParameters, _first_hops: Option<&[&ChannelDetails]>,
- _inflight_htlcs: &InFlightHtlcs
+ _inflight_htlcs: InFlightHtlcs
) -> Result<Route, msgs::LightningError> {
Err(msgs::LightningError {
err: String::from("Not implemented"),
config.channel_config.forwarding_fee_proportional_millionths = 0;
config.channel_handshake_config.announced_channel = true;
let network = Network::Bitcoin;
+ let best_block_timestamp = genesis_block(network).header.time;
let params = ChainParameters {
network,
best_block: BestBlock::from_network(network),
};
- (ChannelManager::new($fee_estimator.clone(), monitor.clone(), broadcast.clone(), &router, Arc::clone(&logger), keys_manager.clone(), keys_manager.clone(), keys_manager.clone(), config, params),
+ (ChannelManager::new($fee_estimator.clone(), monitor.clone(), broadcast.clone(), &router, Arc::clone(&logger), keys_manager.clone(), keys_manager.clone(), keys_manager.clone(), config, params, best_block_timestamp),
monitor, keys_manager)
} }
}
use lightning::routing::gossip::{P2PGossipSync, NetworkGraph};
use lightning::routing::utxo::UtxoLookup;
use lightning::routing::router::{InFlightHtlcs, PaymentParameters, Route, RouteParameters, Router};
-use lightning::util::config::UserConfig;
+use lightning::util::config::{UserConfig, MaxDustHTLCExposure};
use lightning::util::errors::APIError;
use lightning::util::enforcing_trait_impls::{EnforcingSigner, EnforcementState};
use lightning::util::logger::Logger;
impl Router for FuzzRouter {
fn find_route(
&self, _payer: &PublicKey, _params: &RouteParameters, _first_hops: Option<&[&ChannelDetails]>,
- _inflight_htlcs: &InFlightHtlcs
+ _inflight_htlcs: InFlightHtlcs
) -> Result<Route, msgs::LightningError> {
Err(msgs::LightningError {
err: String::from("Not implemented"),
});
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 {
network,
best_block: BestBlock::from_network(network),
};
- let channelmanager = Arc::new(ChannelManager::new(fee_est.clone(), monitor.clone(), broadcast.clone(), &router, Arc::clone(&logger), keys_manager.clone(), keys_manager.clone(), keys_manager.clone(), config, params));
+ let channelmanager = Arc::new(ChannelManager::new(fee_est.clone(), monitor.clone(), broadcast.clone(), &router, Arc::clone(&logger), keys_manager.clone(), keys_manager.clone(), keys_manager.clone(), config, params, best_block_timestamp));
// Adding new calls to `EntropySource::get_secure_random_bytes` during startup can change all the
// keys subsequently generated in this test. Rather than regenerating all the messages manually,
// it's easier to just increment the counter here so the keys don't change.
//
// 0a - create the funding transaction (client should send funding_created now)
//
+ // 00fd00fd - Two feerate requests (calculating max dust exposure) (all returning min feerate) (gonna be ingested by FuzzEstimator)
+ //
// 030112 - inbound read from peer id 1 of len 18
// 0062 01000000000000000000000000000000 - message header indicating message length 98
// 030172 - inbound read from peer id 1 of len 114
// 0300c1 - inbound read from peer id 0 of len 193
// ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ab00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000 - end of update_add_htlc from 0 to 1 via client and mac
//
+ // 00fd - One feerate request (calculating max dust exposure) (all returning min feerate) (gonna be ingested by FuzzEstimator)
+ //
// 030012 - inbound read from peer id 0 of len 18
// 0064 03000000000000000000000000000000 - message header indicating message length 100
// 030074 - inbound read from peer id 0 of len 116
// 07 - process the now-pending HTLC forward
// - client now sends id 1 update_add_htlc and commitment_signed (CHECK 7: UpdateHTLCs event for node 03020000 with 1 HTLCs for channel 3f000000)
//
+ // 00fd00fd - Two feerate requests (calculating max dust exposure) (all returning min feerate) (gonna be ingested by FuzzEstimator)
+ //
// - we respond with commitment_signed then revoke_and_ack (a weird, but valid, order)
// 030112 - inbound read from peer id 1 of len 18
// 0064 01000000000000000000000000000000 - message header indicating message length 100
// 0300c1 - inbound read from peer id 0 of len 193
// ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ab00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000 - end of update_add_htlc from 0 to 1 via client and mac
//
+ // 00fd - One feerate request (calculating max dust exposure) (all returning min feerate) (gonna be ingested by FuzzEstimator)
+ //
// - now respond to the update_fulfill_htlc+commitment_signed messages the client sent to peer 0
// 030012 - inbound read from peer id 0 of len 18
// 0063 03000000000000000000000000000000 - message header indicating message length 99
// - client now sends id 1 update_add_htlc and commitment_signed (CHECK 7 duplicate)
// - we respond with revoke_and_ack, then commitment_signed, then update_fail_htlc
//
+ // 00fd00fd - Two feerate requests (calculating max dust exposure) (all returning min feerate) (gonna be ingested by FuzzEstimator)
+ //
// 030112 - inbound read from peer id 1 of len 18
// 0064 01000000000000000000000000000000 - message header indicating message length 100
// 030174 - inbound read from peer id 1 of len 116
// 0300c1 - inbound read from peer id 0 of len 193
// ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 5300000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000 - end of update_add_htlc from 0 to 1 via client and mac
//
+ // 00fd - One feerate request (calculating max dust exposure) (all returning min feerate) (gonna be ingested by FuzzEstimator)
+ //
// 030012 - inbound read from peer id 0 of len 18
// 00a4 03000000000000000000000000000000 - message header indicating message length 164
// 0300b4 - inbound read from peer id 0 of len 180
// 07 - process the now-pending HTLC forward
// - client now sends id 1 update_add_htlc and commitment_signed (CHECK 7 duplicate)
//
+ // 00fd00fd - Two feerate requests (calculating max dust exposure) (all returning min feerate) (gonna be ingested by FuzzEstimator)
+ //
// 0c007d - connect a block with one transaction of len 125
// 02000000013a000000000000000000000000000000000000000000000000000000000000000000000000000000800258020000000000002200204b0000000000000000000000000000000000000000000000000000000000000014c0000000000000160014280000000000000000000000000000000000000005000020 - the commitment transaction for channel 3f00000000000000000000000000000000000000000000000000000000000000
//
// - client now fails the HTLC backwards as it was unable to extract the payment preimage (CHECK 9 duplicate and CHECK 10)
let logger = Arc::new(TrackingLogger { lines: Mutex::new(HashMap::new()) });
- super::do_test(&::hex::decode("01000000000000000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000000020300320003000000000000000000000000000000000000000000000000000000000000000203000000000000000000000000000000030012001003000000000000000000000000000000030020001000021aaa0008aaaaaaaaaaaa9aaa030000000000000000000000000000000300120147030000000000000000000000000000000300fe00207500000000000000000000000000000000000000000000000000000000000000ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679000000000000c35000000000000000000000000000000162ffffffffffffffff00000000000002220000000000000000000000fd000601e3030000000000000000000000000000000000000000000000000000000000000001030000000000000000000000000000000000000000000000000000000000000002030000000000000000000000000000000000000000000000000000000000000003030000000000000000000000000000000000000000000000000000000000000004030059030000000000000000000000000000000000000000000000000000000000000005020900000000000000000000000000000000000000000000000000000000000000010000010210000300000000000000000000000000000000fd00fd0300120084030000000000000000000000000000000300940022ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb1819096793d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000210100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000c005e020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0150c3000000000000220020ae00000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c000003001200430300000000000000000000000000000003005300243d0000000000000000000000000000000000000000000000000000000000000002080000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000010301320003000000000000000000000000000000000000000000000000000000000000000703000000000000000000000000000000030142000302000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030112001001000000000000000000000000000000030120001000021aaa0008aaaaaaaaaaaa9aaa01000000000000000000000000000000050103020000000000000000000000000000000000000000000000000000000000000000c3500003e800fd0301120112010000000000000000000000000000000301ff00210000000000000000000000000000000000000000000000000000000000000e05000000000000016200000000004c4b4000000000000003e800000000000003e80000000203f000050300000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000002000300000000000000000000000000000000000000000000000000000000000003000300000000000000000000000000000000000000000000000000000000000004000300000000000000000000000000000000000000000000000000000000000005000266000000000000000000000000000003012300000000000000000000000000000000000000010000000000000000000000000000000a03011200620100000000000000000000000000000003017200233a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c0001000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000b03011200430100000000000000000000000000000003015300243a000000000000000000000000000000000000000000000000000000000000000267000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000020b00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000703011200640100000000000000000000000000000003017400843a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000660000000000000000000000000000000000000000000000000000000000000002640000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112004a0100000000000000000000000000000003015a00823a000000000000000000000000000000000000000000000000000000000000000000000000000000ff008888888888888888888888888888888888888888888888888888888888880100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a0000000000000000000000000000000000000000000000000000000000000067000000000000000000000000000000000000000000000000000000000000000265000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000010000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000020a000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000b00000000000000000000000000000000000000000000000000000000000000020d00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000703011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000002700000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112002c0100000000000000000000000000000003013c00833a00000000000000000000000000000000000000000000000000000000000000000000000000000100000100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a000000000000000000000000000000000000000000000000000000000000006500000000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000703001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000020c000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000032010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d00000000000000000000000000000000000000000000000000000000000000000000000000000200000000000b0838ff00000000000000000000000000000000000000000000000000000000000000000003f0000300000000000000000000000000000000000000000000000000000000000005551202030927c00401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff53000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200a4030000000000000000000000000000000300b400843d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007501000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006705000000000000000000000000000000000000000000000000000000000000060300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000d00000000000000000000000000000000000000000000000000000000000000020f0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000070c007d02000000013a000000000000000000000000000000000000000000000000000000000000000000000000000000800258020000000000002200204b0000000000000000000000000000000000000000000000000000000000000014c00000000000001600142800000000000000000000000000000000000000050000200c005e0200000001730000000000000000000000000000000000000000000000000000000000000000000000000000000001a701000000000000220020b200000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c000007").unwrap(), &(Arc::clone(&logger) as Arc<dyn Logger>));
+ super::do_test(&::hex::decode("01000000000000000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000000020300320003000000000000000000000000000000000000000000000000000000000000000203000000000000000000000000000000030012001003000000000000000000000000000000030020001000021aaa0008aaaaaaaaaaaa9aaa030000000000000000000000000000000300120147030000000000000000000000000000000300fe00207500000000000000000000000000000000000000000000000000000000000000ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679000000000000c35000000000000000000000000000000162ffffffffffffffff00000000000002220000000000000000000000fd000601e3030000000000000000000000000000000000000000000000000000000000000001030000000000000000000000000000000000000000000000000000000000000002030000000000000000000000000000000000000000000000000000000000000003030000000000000000000000000000000000000000000000000000000000000004030059030000000000000000000000000000000000000000000000000000000000000005020900000000000000000000000000000000000000000000000000000000000000010000010210000300000000000000000000000000000000fd00fd0300120084030000000000000000000000000000000300940022ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb1819096793d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000210100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000c005e020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0150c3000000000000220020ae00000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c000003001200430300000000000000000000000000000003005300243d0000000000000000000000000000000000000000000000000000000000000002080000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000010301320003000000000000000000000000000000000000000000000000000000000000000703000000000000000000000000000000030142000302000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030112001001000000000000000000000000000000030120001000021aaa0008aaaaaaaaaaaa9aaa01000000000000000000000000000000050103020000000000000000000000000000000000000000000000000000000000000000c3500003e800fd0301120112010000000000000000000000000000000301ff00210000000000000000000000000000000000000000000000000000000000000e05000000000000016200000000004c4b4000000000000003e800000000000003e80000000203f000050300000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000002000300000000000000000000000000000000000000000000000000000000000003000300000000000000000000000000000000000000000000000000000000000004000300000000000000000000000000000000000000000000000000000000000005000266000000000000000000000000000003012300000000000000000000000000000000000000010000000000000000000000000000000a00fd00fd03011200620100000000000000000000000000000003017200233a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c0001000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000b03011200430100000000000000000000000000000003015300243a000000000000000000000000000000000000000000000000000000000000000267000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000020b00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd03011200640100000000000000000000000000000003017400843a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000660000000000000000000000000000000000000000000000000000000000000002640000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112004a0100000000000000000000000000000003015a00823a000000000000000000000000000000000000000000000000000000000000000000000000000000ff008888888888888888888888888888888888888888888888888888888888880100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a0000000000000000000000000000000000000000000000000000000000000067000000000000000000000000000000000000000000000000000000000000000265000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000010000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000020a000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000b00000000000000000000000000000000000000000000000000000000000000020d00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd03011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000002700000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112002c0100000000000000000000000000000003013c00833a00000000000000000000000000000000000000000000000000000000000000000000000000000100000100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a000000000000000000000000000000000000000000000000000000000000006500000000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000703001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000020c000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000032010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d00000000000000000000000000000000000000000000000000000000000000000000000000000200000000000b0838ff00000000000000000000000000000000000000000000000000000000000000000003f0000300000000000000000000000000000000000000000000000000000000000005551202030927c00401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff53000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200a4030000000000000000000000000000000300b400843d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007501000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006705000000000000000000000000000000000000000000000000000000000000060300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000d00000000000000000000000000000000000000000000000000000000000000020f00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd0c007d02000000013a000000000000000000000000000000000000000000000000000000000000000000000000000000800258020000000000002200204b0000000000000000000000000000000000000000000000000000000000000014c00000000000001600142800000000000000000000000000000000000000050000200c005e0200000001730000000000000000000000000000000000000000000000000000000000000000000000000000000001a701000000000000220020b200000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c000007").unwrap(), &(Arc::clone(&logger) as Arc<dyn Logger>));
let log_entries = logger.lines.lock().unwrap();
assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling SendAcceptChannel event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000002 for channel ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679".to_string())), Some(&1)); // 1
// licenses.
use crate::utils::test_logger;
-use lightning::offers::invoice::Invoice;
+use lightning::offers::invoice::Bolt12Invoice;
use lightning::util::ser::Writeable;
use std::convert::TryFrom;
#[inline]
pub fn do_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
- if let Ok(invoice) = Invoice::try_from(data.to_vec()) {
+ if let Ok(invoice) = Bolt12Invoice::try_from(data.to_vec()) {
let mut bytes = Vec::with_capacity(data.len());
invoice.write(&mut bytes).unwrap();
assert_eq!(data, bytes);
use lightning::sign::EntropySource;
use lightning::ln::PaymentHash;
use lightning::ln::features::BlindedHopFeatures;
-use lightning::offers::invoice::{BlindedPayInfo, UnsignedInvoice};
+use lightning::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice};
use lightning::offers::invoice_request::InvoiceRequest;
-use lightning::offers::parse::SemanticError;
+use lightning::offers::parse::Bolt12SemanticError;
use lightning::util::ser::Writeable;
#[inline]
fn build_response<'a, T: secp256k1::Signing + secp256k1::Verification>(
invoice_request: &'a InvoiceRequest, secp_ctx: &Secp256k1<T>
-) -> Result<UnsignedInvoice<'a>, SemanticError> {
+) -> Result<UnsignedBolt12Invoice<'a>, Bolt12SemanticError> {
let entropy_source = Randomness {};
let paths = vec![
BlindedPath::new_for_message(&[pubkey(43), pubkey(44), pubkey(42)], &entropy_source, secp_ctx).unwrap(),
},
];
- let payment_paths = paths.into_iter().zip(payinfo.into_iter()).collect();
+ let payment_paths = payinfo.into_iter().zip(paths.into_iter()).collect();
let payment_hash = PaymentHash([42; 32]);
invoice_request.respond_with(payment_paths, payment_hash)?.build()
}
use core::convert::{Infallible, TryFrom};
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
use lightning::offers::offer::{Amount, Offer, Quantity};
-use lightning::offers::parse::SemanticError;
+use lightning::offers::parse::Bolt12SemanticError;
use lightning::util::ser::Writeable;
#[inline]
fn build_response<'a>(
offer: &'a Offer, pubkey: PublicKey
-) -> Result<UnsignedInvoiceRequest<'a>, SemanticError> {
+) -> Result<UnsignedInvoiceRequest<'a>, Bolt12SemanticError> {
let mut builder = offer.request_invoice(vec![42; 64], pubkey)?;
builder = match offer.amount() {
None => builder.amount_msats(1000).unwrap(),
Some(Amount::Bitcoin { amount_msats }) => builder.amount_msats(amount_msats + 1)?,
- Some(Amount::Currency { .. }) => return Err(SemanticError::UnsupportedCurrency),
+ Some(Amount::Currency { .. }) => return Err(Bolt12SemanticError::UnsupportedCurrency),
};
builder = match offer.supported_quantity() {
use lightning::sign::EntropySource;
use lightning::ln::PaymentHash;
use lightning::ln::features::BlindedHopFeatures;
-use lightning::offers::invoice::{BlindedPayInfo, UnsignedInvoice};
-use lightning::offers::parse::SemanticError;
+use lightning::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice};
+use lightning::offers::parse::Bolt12SemanticError;
use lightning::offers::refund::Refund;
use lightning::util::ser::Writeable;
fn build_response<'a, T: secp256k1::Signing + secp256k1::Verification>(
refund: &'a Refund, signing_pubkey: PublicKey, secp_ctx: &Secp256k1<T>
-) -> Result<UnsignedInvoice<'a>, SemanticError> {
+) -> Result<UnsignedBolt12Invoice<'a>, Bolt12SemanticError> {
let entropy_source = Randomness {};
let paths = vec![
BlindedPath::new_for_message(&[pubkey(43), pubkey(44), pubkey(42)], &entropy_source, secp_ctx).unwrap(),
},
];
- let payment_paths = paths.into_iter().zip(payinfo.into_iter()).collect();
+ let payment_paths = payinfo.into_iter().zip(paths.into_iter()).collect();
let payment_hash = PaymentHash([42; 32]);
refund.respond_with(payment_paths, payment_hash, signing_pubkey)?.build()
}
inbound_htlc_maximum_msat: None,
config: None,
feerate_sat_per_1000_weight: None,
+ channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown),
});
}
Some(&first_hops_vec[..])
[package]
name = "lightning-background-processor"
-version = "0.0.115"
+version = "0.0.116-rc1"
authors = ["Valentine Wallace <vwallace@protonmail.com>"]
license = "MIT OR Apache-2.0"
repository = "http://github.com/lightningdevkit/rust-lightning"
[dependencies]
bitcoin = { version = "0.29.0", default-features = false }
-lightning = { version = "0.0.115", path = "../lightning", default-features = false }
-lightning-rapid-gossip-sync = { version = "0.0.115", path = "../lightning-rapid-gossip-sync", default-features = false }
+lightning = { version = "0.0.116-rc1", path = "../lightning", default-features = false }
+lightning-rapid-gossip-sync = { version = "0.0.116-rc1", 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.115", path = "../lightning", features = ["_test_utils"] }
-lightning-invoice = { version = "0.23.0", path = "../lightning-invoice" }
-lightning-persister = { version = "0.0.115", path = "../lightning-persister" }
+lightning = { version = "0.0.116-rc1", path = "../lightning", features = ["_test_utils"] }
+lightning-invoice = { version = "0.24.0-rc1", path = "../lightning-invoice" }
+lightning-persister = { version = "0.0.116-rc1", path = "../lightning-persister" }
/// # 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 MyMessageRouter = dyn lightning::onion_message::MessageRouter + Send + Sync;
/// # type MyChainMonitor = lightning::chain::chainmonitor::ChainMonitor<lightning::sign::InMemorySigner, Arc<MyFilter>, Arc<MyBroadcaster>, Arc<MyFeeEstimator>, Arc<MyLogger>, Arc<MyPersister>>;
-/// # type MyPeerManager = lightning::ln::peer_handler::SimpleArcPeerManager<MySocketDescriptor, MyChainMonitor, MyBroadcaster, MyFeeEstimator, MyUtxoLookup, MyLogger, MyMessageRouter>;
+/// # type MyPeerManager = lightning::ln::peer_handler::SimpleArcPeerManager<MySocketDescriptor, MyChainMonitor, MyBroadcaster, MyFeeEstimator, MyUtxoLookup, MyLogger>;
/// # type MyNetworkGraph = lightning::routing::gossip::NetworkGraph<Arc<MyLogger>>;
/// # type MyGossipSync = lightning::routing::gossip::P2PGossipSync<Arc<MyNetworkGraph>, Arc<MyUtxoLookup>, Arc<MyLogger>>;
/// # type MyChannelManager = lightning::ln::channelmanager::SimpleArcChannelManager<MyChainMonitor, MyBroadcaster, MyFeeEstimator, MyLogger>;
fn disconnect_socket(&mut self) {}
}
- type ChannelManager = channelmanager::ChannelManager<Arc<ChainMonitor>, Arc<test_utils::TestBroadcaster>, Arc<KeysManager>, Arc<KeysManager>, Arc<KeysManager>, Arc<test_utils::TestFeeEstimator>, Arc<DefaultRouter<Arc<NetworkGraph<Arc<test_utils::TestLogger>>>, Arc<test_utils::TestLogger>, Arc<Mutex<TestScorer>>, (), TestScorer>>, Arc<test_utils::TestLogger>>;
+ type ChannelManager =
+ channelmanager::ChannelManager<
+ Arc<ChainMonitor>,
+ Arc<test_utils::TestBroadcaster>,
+ Arc<KeysManager>,
+ Arc<KeysManager>,
+ Arc<KeysManager>,
+ Arc<test_utils::TestFeeEstimator>,
+ Arc<DefaultRouter<
+ Arc<NetworkGraph<Arc<test_utils::TestLogger>>>,
+ Arc<test_utils::TestLogger>,
+ Arc<Mutex<TestScorer>>,
+ (),
+ TestScorer>
+ >,
+ Arc<test_utils::TestLogger>>;
type ChainMonitor = chainmonitor::ChainMonitor<InMemorySigner, Arc<test_utils::TestChainSource>, Arc<test_utils::TestBroadcaster>, Arc<test_utils::TestFeeEstimator>, Arc<test_utils::TestLogger>, Arc<FilesystemPersister>>;
let chain_monitor = Arc::new(chainmonitor::ChainMonitor::new(Some(chain_source.clone()), tx_broadcaster.clone(), logger.clone(), fee_estimator.clone(), persister.clone()));
let best_block = BestBlock::from_network(network);
let params = ChainParameters { network, best_block };
- let manager = Arc::new(ChannelManager::new(fee_estimator.clone(), chain_monitor.clone(), tx_broadcaster.clone(), router.clone(), logger.clone(), keys_manager.clone(), keys_manager.clone(), keys_manager.clone(), UserConfig::default(), params));
+ 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 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 {
[package]
name = "lightning-block-sync"
-version = "0.0.115"
+version = "0.0.116-rc1"
authors = ["Jeffrey Czyz", "Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "http://github.com/lightningdevkit/rust-lightning"
[dependencies]
bitcoin = "0.29.0"
-lightning = { version = "0.0.115", path = "../lightning" }
+lightning = { version = "0.0.116-rc1", path = "../lightning" }
tokio = { version = "1.0", features = [ "io-util", "net", "time" ], optional = true }
serde_json = { version = "1.0", optional = true }
chunked_transfer = { version = "1.4", optional = true }
[dev-dependencies]
-lightning = { version = "0.0.115", path = "../lightning", features = ["_test_utils"] }
+lightning = { version = "0.0.116-rc1", path = "../lightning", features = ["_test_utils"] }
tokio = { version = "1.14", features = [ "macros", "rt" ] }
[package]
name = "lightning-custom-message"
-version = "0.0.115"
+version = "0.0.116-rc1"
authors = ["Jeffrey Czyz"]
license = "MIT OR Apache-2.0"
repository = "http://github.com/lightningdevkit/rust-lightning"
[dependencies]
bitcoin = "0.29.0"
-lightning = { version = "0.0.115", path = "../lightning" }
+lightning = { version = "0.0.116-rc1", path = "../lightning" }
[package]
name = "lightning-invoice"
description = "Data structures to parse and serialize BOLT11 lightning invoices"
-version = "0.23.0"
+version = "0.24.0-rc1"
authors = ["Sebastian Geisler <sgeisler@wh2.tu-dresden.de>"]
documentation = "https://docs.rs/lightning-invoice/"
license = "MIT OR Apache-2.0"
[dependencies]
bech32 = { version = "0.9.0", default-features = false }
-lightning = { version = "0.0.115", path = "../lightning", default-features = false }
+lightning = { version = "0.0.116-rc1", path = "../lightning", default-features = false }
secp256k1 = { version = "0.24.0", default-features = false, features = ["recovery", "alloc"] }
num-traits = { version = "0.2.8", default-features = false }
bitcoin_hashes = { version = "0.11", default-features = false }
bitcoin = { version = "0.29.0", default-features = false }
[dev-dependencies]
-lightning = { version = "0.0.115", path = "../lightning", default-features = false, features = ["_test_utils"] }
+lightning = { version = "0.0.116-rc1", path = "../lightning", default-features = false, features = ["_test_utils"] }
hex = "0.4"
serde_json = { version = "1"}
use secp256k1::ecdsa::{RecoveryId, RecoverableSignature};
use secp256k1::PublicKey;
-use super::{Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, InvoiceSignature, PositiveTimestamp,
- SemanticError, PrivateRoute, ParseError, ParseOrSemanticError, Description, RawTaggedField, Currency, RawHrp, SiPrefix, RawInvoice,
- constants, SignedRawInvoice, RawDataPart, InvoiceFeatures};
+use super::{Bolt11Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, Bolt11InvoiceSignature, PositiveTimestamp,
+ Bolt11SemanticError, PrivateRoute, Bolt11ParseError, ParseOrSemanticError, Description, RawTaggedField, Currency, RawHrp, SiPrefix, RawBolt11Invoice,
+ constants, SignedRawBolt11Invoice, RawDataPart, Bolt11InvoiceFeatures};
use self::hrp_sm::parse_hrp;
}
impl States {
- fn next_state(&self, read_symbol: char) -> Result<States, super::ParseError> {
+ fn next_state(&self, read_symbol: char) -> Result<States, super::Bolt11ParseError> {
match *self {
States::Start => {
if read_symbol == 'l' {
Ok(States::ParseL)
} else {
- Err(super::ParseError::MalformedHRP)
+ Err(super::Bolt11ParseError::MalformedHRP)
}
}
States::ParseL => {
if read_symbol == 'n' {
Ok(States::ParseN)
} else {
- Err(super::ParseError::MalformedHRP)
+ Err(super::Bolt11ParseError::MalformedHRP)
}
},
States::ParseN => {
} else if ['m', 'u', 'n', 'p'].contains(&read_symbol) {
Ok(States::ParseAmountSiPrefix)
} else {
- Err(super::ParseError::UnknownSiPrefix)
+ Err(super::Bolt11ParseError::UnknownSiPrefix)
}
},
- States::ParseAmountSiPrefix => Err(super::ParseError::MalformedHRP),
+ States::ParseAmountSiPrefix => Err(super::Bolt11ParseError::MalformedHRP),
}
}
*range = Some(new_range);
}
- fn step(&mut self, c: char) -> Result<(), super::ParseError> {
+ fn step(&mut self, c: char) -> Result<(), super::Bolt11ParseError> {
let next_state = self.state.next_state(c)?;
match next_state {
States::ParseCurrencyPrefix => {
}
}
- pub fn parse_hrp(input: &str) -> Result<(&str, &str, &str), super::ParseError> {
+ pub fn parse_hrp(input: &str) -> Result<(&str, &str, &str), super::Bolt11ParseError> {
let mut sm = StateMachine::new();
for c in input.chars() {
sm.step(c)?;
}
if !sm.is_final() {
- return Err(super::ParseError::MalformedHRP);
+ return Err(super::Bolt11ParseError::MalformedHRP);
}
let currency = sm.currency_prefix().clone()
impl FromStr for super::Currency {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_str(currency_prefix: &str) -> Result<Self, ParseError> {
+ fn from_str(currency_prefix: &str) -> Result<Self, Bolt11ParseError> {
match currency_prefix {
"bc" => Ok(Currency::Bitcoin),
"tb" => Ok(Currency::BitcoinTestnet),
"bcrt" => Ok(Currency::Regtest),
"sb" => Ok(Currency::Simnet),
"tbs" => Ok(Currency::Signet),
- _ => Err(ParseError::UnknownCurrency)
+ _ => Err(Bolt11ParseError::UnknownCurrency)
}
}
}
impl FromStr for SiPrefix {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_str(currency_prefix: &str) -> Result<Self, ParseError> {
+ fn from_str(currency_prefix: &str) -> Result<Self, Bolt11ParseError> {
use crate::SiPrefix::*;
match currency_prefix {
"m" => Ok(Milli),
"u" => Ok(Micro),
"n" => Ok(Nano),
"p" => Ok(Pico),
- _ => Err(ParseError::UnknownSiPrefix)
+ _ => Err(Bolt11ParseError::UnknownSiPrefix)
}
}
}
/// ```
-/// use lightning_invoice::Invoice;
+/// use lightning_invoice::Bolt11Invoice;
///
///
/// let invoice = "lnbc100p1psj9jhxdqud3jxktt5w46x7unfv9kz6mn0v3jsnp4q0d3p2sfluzdx45tqcs\
/// 8s0gyuxjjgux34w75dnc6xp2l35j7es3jd4ugt3lu0xzre26yg5m7ke54n2d5sym4xcmxtl8238xxvw5h5h5\
/// j5r6drg6k6zcqj0fcwg";
///
-/// assert!(invoice.parse::<Invoice>().is_ok());
+/// assert!(invoice.parse::<Bolt11Invoice>().is_ok());
/// ```
-impl FromStr for Invoice {
+impl FromStr for Bolt11Invoice {
type Err = ParseOrSemanticError;
fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
- let signed = s.parse::<SignedRawInvoice>()?;
- Ok(Invoice::from_signed(signed)?)
+ let signed = s.parse::<SignedRawBolt11Invoice>()?;
+ Ok(Bolt11Invoice::from_signed(signed)?)
}
}
/// 8s0gyuxjjgux34w75dnc6xp2l35j7es3jd4ugt3lu0xzre26yg5m7ke54n2d5sym4xcmxtl8238xxvw5h5h5\
/// j5r6drg6k6zcqj0fcwg";
///
-/// let parsed_1 = invoice.parse::<Invoice>();
+/// let parsed_1 = invoice.parse::<Bolt11Invoice>();
///
-/// let parsed_2 = match invoice.parse::<SignedRawInvoice>() {
-/// Ok(signed) => match Invoice::from_signed(signed) {
+/// let parsed_2 = match invoice.parse::<SignedRawBolt11Invoice>() {
+/// Ok(signed) => match Bolt11Invoice::from_signed(signed) {
/// Ok(invoice) => Ok(invoice),
/// Err(e) => Err(ParseOrSemanticError::SemanticError(e)),
/// },
/// assert!(parsed_1.is_ok());
/// assert_eq!(parsed_1, parsed_2);
/// ```
-impl FromStr for SignedRawInvoice {
- type Err = ParseError;
+impl FromStr for SignedRawBolt11Invoice {
+ type Err = Bolt11ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (hrp, data, var) = bech32::decode(s)?;
if var == bech32::Variant::Bech32m {
// Consider Bech32m addresses to be "Invalid Checksum", since that is what we'd get if
// we didn't support Bech32m (which lightning does not use).
- return Err(ParseError::Bech32Error(bech32::Error::InvalidChecksum));
+ return Err(Bolt11ParseError::Bech32Error(bech32::Error::InvalidChecksum));
}
if data.len() < 104 {
- return Err(ParseError::TooShortDataPart);
+ return Err(Bolt11ParseError::TooShortDataPart);
}
let raw_hrp: RawHrp = hrp.parse()?;
let data_part = RawDataPart::from_base32(&data[..data.len()-104])?;
- Ok(SignedRawInvoice {
- raw_invoice: RawInvoice {
+ Ok(SignedRawBolt11Invoice {
+ raw_invoice: RawBolt11Invoice {
hrp: raw_hrp,
data: data_part,
},
- hash: RawInvoice::hash_from_parts(
+ hash: RawBolt11Invoice::hash_from_parts(
hrp.as_bytes(),
&data[..data.len()-104]
),
- signature: InvoiceSignature::from_base32(&data[data.len()-104..])?,
+ signature: Bolt11InvoiceSignature::from_base32(&data[data.len()-104..])?,
})
}
}
impl FromStr for RawHrp {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
fn from_str(hrp: &str) -> Result<Self, <Self as FromStr>::Err> {
let parts = parse_hrp(hrp)?;
let si: SiPrefix = parts.2.parse()?;
if let Some(amt) = amount {
if amt.checked_mul(si.multiplier()).is_none() {
- return Err(ParseError::IntegerOverflowError);
+ return Err(Bolt11ParseError::IntegerOverflowError);
}
}
Some(si)
}
impl FromBase32 for RawDataPart {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
fn from_base32(data: &[u5]) -> Result<Self, Self::Err> {
if data.len() < 7 { // timestamp length
- return Err(ParseError::TooShortDataPart);
+ return Err(Bolt11ParseError::TooShortDataPart);
}
let timestamp = PositiveTimestamp::from_base32(&data[0..7])?;
}
impl FromBase32 for PositiveTimestamp {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
fn from_base32(b32: &[u5]) -> Result<Self, Self::Err> {
if b32.len() != 7 {
- return Err(ParseError::InvalidSliceLength("PositiveTimestamp::from_base32()".into()));
+ return Err(Bolt11ParseError::InvalidSliceLength("PositiveTimestamp::from_base32()".into()));
}
let timestamp: u64 = parse_int_be(b32, 32)
.expect("7*5bit < 64bit, no overflow possible");
}
}
-impl FromBase32 for InvoiceSignature {
- type Err = ParseError;
+impl FromBase32 for Bolt11InvoiceSignature {
+ type Err = Bolt11ParseError;
fn from_base32(signature: &[u5]) -> Result<Self, Self::Err> {
if signature.len() != 104 {
- return Err(ParseError::InvalidSliceLength("InvoiceSignature::from_base32()".into()));
+ return Err(Bolt11ParseError::InvalidSliceLength("Bolt11InvoiceSignature::from_base32()".into()));
}
let recoverable_signature_bytes = Vec::<u8>::from_base32(signature)?;
let signature = &recoverable_signature_bytes[0..64];
let recovery_id = RecoveryId::from_i32(recoverable_signature_bytes[64] as i32)?;
- Ok(InvoiceSignature(RecoverableSignature::from_compact(
+ Ok(Bolt11InvoiceSignature(RecoverableSignature::from_compact(
signature,
recovery_id
)?))
)
}
-fn parse_tagged_parts(data: &[u5]) -> Result<Vec<RawTaggedField>, ParseError> {
+fn parse_tagged_parts(data: &[u5]) -> Result<Vec<RawTaggedField>, Bolt11ParseError> {
let mut parts = Vec::<RawTaggedField>::new();
let mut data = data;
while !data.is_empty() {
if data.len() < 3 {
- return Err(ParseError::UnexpectedEndOfTaggedFields);
+ return Err(Bolt11ParseError::UnexpectedEndOfTaggedFields);
}
// Ignore tag at data[0], it will be handled in the TaggedField parsers and
let last_element = 3 + len;
if data.len() < last_element {
- return Err(ParseError::UnexpectedEndOfTaggedFields);
+ return Err(Bolt11ParseError::UnexpectedEndOfTaggedFields);
}
// Get the tagged field's data slice
Ok(field) => {
parts.push(RawTaggedField::KnownSemantics(field))
},
- Err(ParseError::Skip)|Err(ParseError::Bech32Error(bech32::Error::InvalidLength)) => {
+ Err(Bolt11ParseError::Skip)|Err(Bolt11ParseError::Bech32Error(bech32::Error::InvalidLength)) => {
parts.push(RawTaggedField::UnknownSemantics(field.into()))
},
Err(e) => {return Err(e)}
}
impl FromBase32 for TaggedField {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_base32(field: &[u5]) -> Result<TaggedField, ParseError> {
+ fn from_base32(field: &[u5]) -> Result<TaggedField, Bolt11ParseError> {
if field.len() < 3 {
- return Err(ParseError::UnexpectedEndOfTaggedFields);
+ return Err(Bolt11ParseError::UnexpectedEndOfTaggedFields);
}
let tag = field[0];
constants::TAG_PAYMENT_METADATA =>
Ok(TaggedField::PaymentMetadata(Vec::<u8>::from_base32(field_data)?)),
constants::TAG_FEATURES =>
- Ok(TaggedField::Features(InvoiceFeatures::from_base32(field_data)?)),
+ Ok(TaggedField::Features(Bolt11InvoiceFeatures::from_base32(field_data)?)),
_ => {
// "A reader MUST skip over unknown fields"
- Err(ParseError::Skip)
+ Err(Bolt11ParseError::Skip)
}
}
}
}
impl FromBase32 for Sha256 {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<Sha256, ParseError> {
+ fn from_base32(field_data: &[u5]) -> Result<Sha256, Bolt11ParseError> {
if field_data.len() != 52 {
// "A reader MUST skip over […] a p, [or] h […] field that does not have data_length 52 […]."
- Err(ParseError::Skip)
+ Err(Bolt11ParseError::Skip)
} else {
Ok(Sha256(sha256::Hash::from_slice(&Vec::<u8>::from_base32(field_data)?)
.expect("length was checked before (52 u5 -> 32 u8)")))
}
impl FromBase32 for Description {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<Description, ParseError> {
+ fn from_base32(field_data: &[u5]) -> Result<Description, Bolt11ParseError> {
let bytes = Vec::<u8>::from_base32(field_data)?;
let description = String::from(str::from_utf8(&bytes)?);
Ok(Description::new(description).expect(
}
impl FromBase32 for PayeePubKey {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<PayeePubKey, ParseError> {
+ fn from_base32(field_data: &[u5]) -> Result<PayeePubKey, Bolt11ParseError> {
if field_data.len() != 53 {
// "A reader MUST skip over […] a n […] field that does not have data_length 53 […]."
- Err(ParseError::Skip)
+ Err(Bolt11ParseError::Skip)
} else {
let data_bytes = Vec::<u8>::from_base32(field_data)?;
let pub_key = PublicKey::from_slice(&data_bytes)?;
}
impl FromBase32 for ExpiryTime {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<ExpiryTime, ParseError> {
+ fn from_base32(field_data: &[u5]) -> Result<ExpiryTime, Bolt11ParseError> {
match parse_int_be::<u64, u5>(field_data, 32)
.map(ExpiryTime::from_seconds)
{
Some(t) => Ok(t),
- None => Err(ParseError::IntegerOverflowError),
+ None => Err(Bolt11ParseError::IntegerOverflowError),
}
}
}
impl FromBase32 for MinFinalCltvExpiryDelta {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<MinFinalCltvExpiryDelta, ParseError> {
+ fn from_base32(field_data: &[u5]) -> Result<MinFinalCltvExpiryDelta, Bolt11ParseError> {
let expiry = parse_int_be::<u64, u5>(field_data, 32);
if let Some(expiry) = expiry {
Ok(MinFinalCltvExpiryDelta(expiry))
} else {
- Err(ParseError::IntegerOverflowError)
+ Err(Bolt11ParseError::IntegerOverflowError)
}
}
}
impl FromBase32 for Fallback {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<Fallback, ParseError> {
+ fn from_base32(field_data: &[u5]) -> Result<Fallback, Bolt11ParseError> {
if field_data.is_empty() {
- return Err(ParseError::UnexpectedEndOfTaggedFields);
+ return Err(Bolt11ParseError::UnexpectedEndOfTaggedFields);
}
let version = field_data[0];
match version.to_u8() {
0..=16 => {
if bytes.len() < 2 || bytes.len() > 40 {
- return Err(ParseError::InvalidSegWitProgramLength);
+ return Err(Bolt11ParseError::InvalidSegWitProgramLength);
}
let version = WitnessVersion::try_from(version).expect("0 through 16 are valid SegWit versions");
Ok(Fallback::SegWitProgram {
17 => {
let pkh = match PubkeyHash::from_slice(&bytes) {
Ok(pkh) => pkh,
- Err(bitcoin_hashes::Error::InvalidLength(_, _)) => return Err(ParseError::InvalidPubKeyHashLength),
+ Err(bitcoin_hashes::Error::InvalidLength(_, _)) => return Err(Bolt11ParseError::InvalidPubKeyHashLength),
};
Ok(Fallback::PubKeyHash(pkh))
}
18 => {
let sh = match ScriptHash::from_slice(&bytes) {
Ok(sh) => sh,
- Err(bitcoin_hashes::Error::InvalidLength(_, _)) => return Err(ParseError::InvalidScriptHashLength),
+ Err(bitcoin_hashes::Error::InvalidLength(_, _)) => return Err(Bolt11ParseError::InvalidScriptHashLength),
};
Ok(Fallback::ScriptHash(sh))
}
- _ => Err(ParseError::Skip)
+ _ => Err(Bolt11ParseError::Skip)
}
}
}
impl FromBase32 for PrivateRoute {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<PrivateRoute, ParseError> {
+ fn from_base32(field_data: &[u5]) -> Result<PrivateRoute, Bolt11ParseError> {
let bytes = Vec::<u8>::from_base32(field_data)?;
if bytes.len() % 51 != 0 {
- return Err(ParseError::UnexpectedEndOfTaggedFields);
+ return Err(Bolt11ParseError::UnexpectedEndOfTaggedFields);
}
let mut route_hops = Vec::<RouteHintHop>::new();
}
}
-impl Display for ParseError {
+impl Display for Bolt11ParseError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match *self {
// TODO: find a way to combine the first three arms (e as error::Error?)
- ParseError::Bech32Error(ref e) => {
+ Bolt11ParseError::Bech32Error(ref e) => {
write!(f, "Invalid bech32: {}", e)
}
- ParseError::ParseAmountError(ref e) => {
+ Bolt11ParseError::ParseAmountError(ref e) => {
write!(f, "Invalid amount in hrp ({})", e)
}
- ParseError::MalformedSignature(ref e) => {
+ Bolt11ParseError::MalformedSignature(ref e) => {
write!(f, "Invalid secp256k1 signature: {}", e)
}
- ParseError::DescriptionDecodeError(ref e) => {
+ Bolt11ParseError::DescriptionDecodeError(ref e) => {
write!(f, "Description is not a valid utf-8 string: {}", e)
}
- ParseError::InvalidSliceLength(ref function) => {
+ Bolt11ParseError::InvalidSliceLength(ref function) => {
write!(f, "Slice in function {} had the wrong length", function)
}
- ParseError::BadPrefix => f.write_str("did not begin with 'ln'"),
- ParseError::UnknownCurrency => f.write_str("currency code unknown"),
- ParseError::UnknownSiPrefix => f.write_str("unknown SI prefix"),
- ParseError::MalformedHRP => f.write_str("malformed human readable part"),
- ParseError::TooShortDataPart => {
+ Bolt11ParseError::BadPrefix => f.write_str("did not begin with 'ln'"),
+ Bolt11ParseError::UnknownCurrency => f.write_str("currency code unknown"),
+ Bolt11ParseError::UnknownSiPrefix => f.write_str("unknown SI prefix"),
+ Bolt11ParseError::MalformedHRP => f.write_str("malformed human readable part"),
+ Bolt11ParseError::TooShortDataPart => {
f.write_str("data part too short (should be at least 111 bech32 chars long)")
},
- ParseError::UnexpectedEndOfTaggedFields => {
+ Bolt11ParseError::UnexpectedEndOfTaggedFields => {
f.write_str("tagged fields part ended unexpectedly")
},
- ParseError::PaddingError => f.write_str("some data field had bad padding"),
- ParseError::IntegerOverflowError => {
+ Bolt11ParseError::PaddingError => f.write_str("some data field had bad padding"),
+ Bolt11ParseError::IntegerOverflowError => {
f.write_str("parsed integer doesn't fit into receiving type")
},
- ParseError::InvalidSegWitProgramLength => {
+ Bolt11ParseError::InvalidSegWitProgramLength => {
f.write_str("fallback SegWit program is too long or too short")
},
- ParseError::InvalidPubKeyHashLength => {
+ Bolt11ParseError::InvalidPubKeyHashLength => {
f.write_str("fallback public key hash has a length unequal 20 bytes")
},
- ParseError::InvalidScriptHashLength => {
+ Bolt11ParseError::InvalidScriptHashLength => {
f.write_str("fallback script hash has a length unequal 32 bytes")
},
- ParseError::InvalidRecoveryId => {
+ Bolt11ParseError::InvalidRecoveryId => {
f.write_str("recovery id is out of range (should be in [0,3])")
},
- ParseError::Skip => {
+ Bolt11ParseError::Skip => {
f.write_str("the tagged field has to be skipped because of an unexpected, but allowed property")
},
}
}
#[cfg(feature = "std")]
-impl error::Error for ParseError {}
+impl error::Error for Bolt11ParseError {}
#[cfg(feature = "std")]
impl error::Error for ParseOrSemanticError {}
macro_rules! from_error {
($my_error:expr, $extern_error:ty) => {
- impl From<$extern_error> for ParseError {
+ impl From<$extern_error> for Bolt11ParseError {
fn from(e: $extern_error) -> Self {
$my_error(e)
}
}
}
-from_error!(ParseError::MalformedSignature, secp256k1::Error);
-from_error!(ParseError::ParseAmountError, ParseIntError);
-from_error!(ParseError::DescriptionDecodeError, str::Utf8Error);
+from_error!(Bolt11ParseError::MalformedSignature, secp256k1::Error);
+from_error!(Bolt11ParseError::ParseAmountError, ParseIntError);
+from_error!(Bolt11ParseError::DescriptionDecodeError, str::Utf8Error);
-impl From<bech32::Error> for ParseError {
+impl From<bech32::Error> for Bolt11ParseError {
fn from(e: bech32::Error) -> Self {
match e {
- bech32::Error::InvalidPadding => ParseError::PaddingError,
- _ => ParseError::Bech32Error(e)
+ bech32::Error::InvalidPadding => Bolt11ParseError::PaddingError,
+ _ => Bolt11ParseError::Bech32Error(e)
}
}
}
-impl From<ParseError> for ParseOrSemanticError {
- fn from(e: ParseError) -> Self {
+impl From<Bolt11ParseError> for ParseOrSemanticError {
+ fn from(e: Bolt11ParseError) -> Self {
ParseOrSemanticError::ParseError(e)
}
}
-impl From<crate::SemanticError> for ParseOrSemanticError {
- fn from(e: SemanticError) -> Self {
+impl From<crate::Bolt11SemanticError> for ParseOrSemanticError {
+ fn from(e: Bolt11SemanticError) -> Self {
ParseOrSemanticError::SemanticError(e)
}
}
#[cfg(test)]
mod test {
- use crate::de::ParseError;
+ use crate::de::Bolt11ParseError;
use secp256k1::PublicKey;
use bech32::u5;
use bitcoin_hashes::hex::FromHex;
assert_eq!("bcrt".parse::<Currency>(), Ok(Currency::Regtest));
assert_eq!("sb".parse::<Currency>(), Ok(Currency::Simnet));
assert_eq!("tbs".parse::<Currency>(), Ok(Currency::Signet));
- assert_eq!("something_else".parse::<Currency>(), Err(ParseError::UnknownCurrency))
+ assert_eq!("something_else".parse::<Currency>(), Err(Bolt11ParseError::UnknownCurrency))
}
#[test]
let input_unexpected_length = from_bech32(
"qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypyq".as_bytes()
);
- assert_eq!(Sha256::from_base32(&input_unexpected_length), Err(ParseError::Skip));
+ assert_eq!(Sha256::from_base32(&input_unexpected_length), Err(Bolt11ParseError::Skip));
}
#[test]
let input_unexpected_length = from_bech32(
"q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhvq".as_bytes()
);
- assert_eq!(PayeePubKey::from_base32(&input_unexpected_length), Err(ParseError::Skip));
+ assert_eq!(PayeePubKey::from_base32(&input_unexpected_length), Err(Bolt11ParseError::Skip));
}
#[test]
assert_eq!(ExpiryTime::from_base32(&input), expected);
let input_too_large = from_bech32("sqqqqqqqqqqqq".as_bytes());
- assert_eq!(ExpiryTime::from_base32(&input_too_large), Err(ParseError::IntegerOverflowError));
+ assert_eq!(ExpiryTime::from_base32(&input_too_large), Err(Bolt11ParseError::IntegerOverflowError));
}
#[test]
),
(
vec![u5::try_from_u8(21).unwrap(); 41],
- Err(ParseError::Skip)
+ Err(Bolt11ParseError::Skip)
),
(
vec![],
- Err(ParseError::UnexpectedEndOfTaggedFields)
+ Err(Bolt11ParseError::UnexpectedEndOfTaggedFields)
),
(
vec![u5::try_from_u8(1).unwrap(); 81],
- Err(ParseError::InvalidSegWitProgramLength)
+ Err(Bolt11ParseError::InvalidSegWitProgramLength)
),
(
vec![u5::try_from_u8(17).unwrap(); 1],
- Err(ParseError::InvalidPubKeyHashLength)
+ Err(Bolt11ParseError::InvalidPubKeyHashLength)
),
(
vec![u5::try_from_u8(18).unwrap(); 1],
- Err(ParseError::InvalidScriptHashLength)
+ Err(Bolt11ParseError::InvalidScriptHashLength)
)
];
assert_eq!(
PrivateRoute::from_base32(&[u5::try_from_u8(0).unwrap(); 40][..]),
- Err(ParseError::UnexpectedEndOfTaggedFields)
+ Err(Bolt11ParseError::UnexpectedEndOfTaggedFields)
);
}
#[test]
fn test_payment_secret_and_features_de_and_ser() {
- use lightning::ln::features::InvoiceFeatures;
+ use lightning::ln::features::Bolt11InvoiceFeatures;
use secp256k1::ecdsa::{RecoveryId, RecoverableSignature};
use crate::TaggedField::*;
- use crate::{SiPrefix, SignedRawInvoice, InvoiceSignature, RawInvoice, RawHrp, RawDataPart,
+ use crate::{SiPrefix, SignedRawBolt11Invoice, Bolt11InvoiceSignature, RawBolt11Invoice, RawHrp, RawDataPart,
Currency, Sha256, PositiveTimestamp};
// Feature bits 9, 15, and 99 are set.
- let expected_features = InvoiceFeatures::from_le_bytes(vec![0, 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8]);
+ let expected_features = Bolt11InvoiceFeatures::from_le_bytes(vec![0, 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8]);
let invoice_str = "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqpqsq67gye39hfg3zd8rgc80k32tvy9xk2xunwm5lzexnvpx6fd77en8qaq424dxgt56cag2dpt359k3ssyhetktkpqh24jqnjyw6uqd08sgptq44qu";
- let invoice = SignedRawInvoice {
- raw_invoice: RawInvoice {
+ let invoice = SignedRawBolt11Invoice {
+ raw_invoice: RawBolt11Invoice {
hrp: RawHrp {
currency: Currency::Bitcoin,
raw_amount: Some(25),
hash: [0xb1, 0x96, 0x46, 0xc3, 0xbc, 0x56, 0x76, 0x1d, 0x20, 0x65, 0x6e, 0x0e, 0x32,
0xec, 0xd2, 0x69, 0x27, 0xb7, 0x62, 0x6e, 0x2a, 0x8b, 0xe6, 0x97, 0x71, 0x9f,
0xf8, 0x7e, 0x44, 0x54, 0x55, 0xb9],
- signature: InvoiceSignature(RecoverableSignature::from_compact(
+ signature: Bolt11InvoiceSignature(RecoverableSignature::from_compact(
&[0xd7, 0x90, 0x4c, 0xc4, 0xb7, 0x4a, 0x22, 0x26, 0x9c, 0x68, 0xc1, 0xdf, 0x68,
0xa9, 0x6c, 0x21, 0x4d, 0x65, 0x1b, 0x93, 0x76, 0xe9, 0xf1, 0x64, 0xd3, 0x60,
0x4d, 0xa4, 0xb7, 0xde, 0xcc, 0xce, 0x0e, 0x82, 0xaa, 0xab, 0x4c, 0x85, 0xd3,
fn test_raw_signed_invoice_deserialization() {
use crate::TaggedField::*;
use secp256k1::ecdsa::{RecoveryId, RecoverableSignature};
- use crate::{SignedRawInvoice, InvoiceSignature, RawInvoice, RawHrp, RawDataPart, Currency, Sha256,
+ use crate::{SignedRawBolt11Invoice, Bolt11InvoiceSignature, RawBolt11Invoice, RawHrp, RawDataPart, Currency, Sha256,
PositiveTimestamp};
assert_eq!(
"lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmw\
wd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d73gafnh3cax9rn449d9p5uxz9\
ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ecky03ylcqca784w".parse(),
- Ok(SignedRawInvoice {
- raw_invoice: RawInvoice {
+ Ok(SignedRawBolt11Invoice {
+ raw_invoice: RawBolt11Invoice {
hrp: RawHrp {
currency: Currency::Bitcoin,
raw_amount: None,
0x7b, 0x1d, 0x85, 0x8d, 0xb1, 0xd1, 0xf7, 0xab, 0x71, 0x37, 0xdc, 0xb7,
0x83, 0x5d, 0xb2, 0xec, 0xd5, 0x18, 0xe1, 0xc9
],
- signature: InvoiceSignature(RecoverableSignature::from_compact(
+ signature: Bolt11InvoiceSignature(RecoverableSignature::from_compact(
& [
0x38u8, 0xec, 0x68, 0x91, 0x34, 0x5e, 0x20, 0x41, 0x45, 0xbe, 0x8a,
0x3a, 0x99, 0xde, 0x38, 0xe9, 0x8a, 0x39, 0xd6, 0xa5, 0x69, 0x43,
//! invoices and functions to create, encode and decode these. If you just want to use the standard
//! en-/decoding functionality this should get you started:
//!
-//! * For parsing use `str::parse::<Invoice>(&self)` (see [`Invoice::from_str`])
+//! * For parsing use `str::parse::<Bolt11Invoice>(&self)` (see [`Bolt11Invoice::from_str`])
//! * For constructing invoices use the [`InvoiceBuilder`]
//! * For serializing invoices use the [`Display`]/[`ToString`] traits
//!
-//! [`Invoice::from_str`]: crate::Invoice#impl-FromStr
+//! [`Bolt11Invoice::from_str`]: crate::Bolt11Invoice#impl-FromStr
#[cfg(not(any(feature = "std", feature = "no-std")))]
compile_error!("at least one of the `std` or `no-std` features must be enabled");
use bitcoin::util::address::{Payload, WitnessVersion};
use bitcoin_hashes::{Hash, sha256};
use lightning::ln::PaymentSecret;
-use lightning::ln::features::InvoiceFeatures;
+use lightning::ln::features::Bolt11InvoiceFeatures;
#[cfg(any(doc, test))]
use lightning::routing::gossip::RoutingFees;
use lightning::routing::router::RouteHint;
/// reasons, but should generally result in an "invalid BOLT11 invoice" message for the user.
#[allow(missing_docs)]
#[derive(PartialEq, Eq, Debug, Clone)]
-pub enum ParseError {
+pub enum Bolt11ParseError {
Bech32Error(bech32::Error),
ParseAmountError(ParseIntError),
MalformedSignature(secp256k1::Error),
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum ParseOrSemanticError {
/// The invoice couldn't be decoded
- ParseError(ParseError),
+ ParseError(Bolt11ParseError),
/// The invoice could be decoded but violates the BOLT11 standard
- SemanticError(crate::SemanticError),
+ SemanticError(crate::Bolt11SemanticError),
}
/// The number of bits used to represent timestamps as defined in BOLT 11.
/// [`MIN_FINAL_CLTV_EXPIRY_DELTA`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA
pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA: u64 = 18;
-/// Builder for [`Invoice`]s. It's the most convenient and advised way to use this library. It ensures
-/// that only a semantically and syntactically correct Invoice can be built using it.
+/// Builder for [`Bolt11Invoice`]s. It's the most convenient and advised way to use this library. It
+/// ensures that only a semantically and syntactically correct invoice can be built using it.
///
/// ```
/// extern crate secp256k1;
/// Represents a syntactically and semantically correct lightning BOLT11 invoice.
///
-/// There are three ways to construct an `Invoice`:
+/// There are three ways to construct a `Bolt11Invoice`:
/// 1. using [`InvoiceBuilder`]
-/// 2. using [`Invoice::from_signed`]
-/// 3. using `str::parse::<Invoice>(&str)` (see [`Invoice::from_str`])
+/// 2. using [`Bolt11Invoice::from_signed`]
+/// 3. using `str::parse::<Bolt11Invoice>(&str)` (see [`Bolt11Invoice::from_str`])
///
-/// [`Invoice::from_str`]: crate::Invoice#impl-FromStr
+/// [`Bolt11Invoice::from_str`]: crate::Bolt11Invoice#impl-FromStr
#[derive(Eq, PartialEq, Debug, Clone, Hash, Ord, PartialOrd)]
-pub struct Invoice {
- signed_invoice: SignedRawInvoice,
+pub struct Bolt11Invoice {
+ signed_invoice: SignedRawBolt11Invoice,
}
/// Represents the description of an invoice which has to be either a directly included string or
/// This is not exported to bindings users as we don't have a good way to map the reference lifetimes making this
/// practically impossible to use safely in languages like C.
#[derive(Eq, PartialEq, Debug, Clone, Ord, PartialOrd)]
-pub enum InvoiceDescription<'f> {
+pub enum Bolt11InvoiceDescription<'f> {
/// Reference to the directly supplied description in the invoice
Direct(&'f Description),
Hash(&'f Sha256),
}
-/// Represents a signed [`RawInvoice`] with cached hash. The signature is not checked and may be
+/// Represents a signed [`RawBolt11Invoice`] with cached hash. The signature is not checked and may be
/// invalid.
///
/// # Invariants
-/// The hash has to be either from the deserialized invoice or from the serialized [`RawInvoice`].
+/// The hash has to be either from the deserialized invoice or from the serialized [`RawBolt11Invoice`].
#[derive(Eq, PartialEq, Debug, Clone, Hash, Ord, PartialOrd)]
-pub struct SignedRawInvoice {
- /// The rawInvoice that the signature belongs to
- raw_invoice: RawInvoice,
+pub struct SignedRawBolt11Invoice {
+ /// The raw invoice that the signature belongs to
+ raw_invoice: RawBolt11Invoice,
- /// Hash of the [`RawInvoice`] that will be used to check the signature.
+ /// Hash of the [`RawBolt11Invoice`] that will be used to check the signature.
///
- /// * if the `SignedRawInvoice` was deserialized the hash is of from the original encoded form,
+ /// * if the `SignedRawBolt11Invoice` was deserialized the hash is of from the original encoded form,
/// since it's not guaranteed that encoding it again will lead to the same result since integers
/// could have been encoded with leading zeroes etc.
- /// * if the `SignedRawInvoice` was constructed manually the hash will be the calculated hash
- /// from the [`RawInvoice`]
+ /// * if the `SignedRawBolt11Invoice` was constructed manually the hash will be the calculated hash
+ /// from the [`RawBolt11Invoice`]
hash: [u8; 32],
/// signature of the payment request
- signature: InvoiceSignature,
+ signature: Bolt11InvoiceSignature,
}
-/// Represents an syntactically correct [`Invoice`] for a payment on the lightning network,
+/// Represents an syntactically correct [`Bolt11Invoice`] for a payment on the lightning network,
/// but without the signature information.
/// Decoding and encoding should not lead to information loss but may lead to different hashes.
///
-/// For methods without docs see the corresponding methods in [`Invoice`].
+/// For methods without docs see the corresponding methods in [`Bolt11Invoice`].
#[derive(Eq, PartialEq, Debug, Clone, Hash, Ord, PartialOrd)]
-pub struct RawInvoice {
+pub struct RawBolt11Invoice {
/// human readable part
pub hrp: RawHrp,
pub data: RawDataPart,
}
-/// Data of the [`RawInvoice`] that is encoded in the human readable part.
+/// Data of the [`RawBolt11Invoice`] that is encoded in the human readable part.
///
/// This is not exported to bindings users as we don't yet support `Option<Enum>`
#[derive(Eq, PartialEq, Debug, Clone, Hash, Ord, PartialOrd)]
pub si_prefix: Option<SiPrefix>,
}
-/// Data of the [`RawInvoice`] that is encoded in the data part
+/// Data of the [`RawBolt11Invoice`] that is encoded in the data part
#[derive(Eq, PartialEq, Debug, Clone, Hash, Ord, PartialOrd)]
pub struct RawDataPart {
/// generation time of the invoice
PrivateRoute(PrivateRoute),
PaymentSecret(PaymentSecret),
PaymentMetadata(Vec<u8>),
- Features(InvoiceFeatures),
+ Features(Bolt11InvoiceFeatures),
}
/// SHA-256 hash
/// Recoverable signature
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
-pub struct InvoiceSignature(pub RecoverableSignature);
+pub struct Bolt11InvoiceSignature(pub RecoverableSignature);
-impl PartialOrd for InvoiceSignature {
+impl PartialOrd for Bolt11InvoiceSignature {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.0.serialize_compact().1.partial_cmp(&other.0.serialize_compact().1)
}
}
-impl Ord for InvoiceSignature {
+impl Ord for Bolt11InvoiceSignature {
fn cmp(&self, other: &Self) -> Ordering {
self.0.serialize_compact().1.cmp(&other.0.serialize_compact().1)
}
}
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, tb::True, C, S, M> {
- /// Builds a [`RawInvoice`] if no [`CreationError`] occurred while construction any of the
+ /// Builds a [`RawBolt11Invoice`] if no [`CreationError`] occurred while construction any of the
/// fields.
- pub fn build_raw(self) -> Result<RawInvoice, CreationError> {
+ pub fn build_raw(self) -> Result<RawBolt11Invoice, CreationError> {
// If an error occurred at any time before, return it now
if let Some(e) = self.error {
tagged_fields,
};
- Ok(RawInvoice {
+ Ok(RawBolt11Invoice {
hrp,
data,
})
}
/// Set the description or description hash. This function is only available if no description (hash) was set.
- pub fn invoice_description(self, description: InvoiceDescription) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
+ pub fn invoice_description(self, description: Bolt11InvoiceDescription) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
match description {
- InvoiceDescription::Direct(desc) => {
+ Bolt11InvoiceDescription::Direct(desc) => {
self.description(desc.clone().into_inner())
}
- InvoiceDescription::Hash(hash) => {
+ Bolt11InvoiceDescription::Hash(hash) => {
self.description_hash(hash.0)
}
}
}
self.tagged_fields.push(TaggedField::PaymentSecret(payment_secret));
if !found_features {
- let mut features = InvoiceFeatures::empty();
+ let mut features = Bolt11InvoiceFeatures::empty();
features.set_variable_length_onion_required();
features.set_payment_secret_required();
self.tagged_fields.push(TaggedField::Features(features));
}
}
if !found_features {
- let mut features = InvoiceFeatures::empty();
+ let mut features = Bolt11InvoiceFeatures::empty();
features.set_payment_metadata_optional();
self.tagged_fields.push(TaggedField::Features(features));
}
/// Builds and signs an invoice using the supplied `sign_function`. This function MAY NOT fail
/// and MUST produce a recoverable signature valid for the given hash and if applicable also for
/// the included payee public key.
- pub fn build_signed<F>(self, sign_function: F) -> Result<Invoice, CreationError>
+ pub fn build_signed<F>(self, sign_function: F) -> Result<Bolt11Invoice, CreationError>
where F: FnOnce(&Message) -> RecoverableSignature
{
let invoice = self.try_build_signed::<_, ()>(|hash| {
/// Builds and signs an invoice using the supplied `sign_function`. This function MAY fail with
/// an error of type `E` and MUST produce a recoverable signature valid for the given hash and
/// if applicable also for the included payee public key.
- pub fn try_build_signed<F, E>(self, sign_function: F) -> Result<Invoice, SignOrCreationError<E>>
+ pub fn try_build_signed<F, E>(self, sign_function: F) -> Result<Bolt11Invoice, SignOrCreationError<E>>
where F: FnOnce(&Message) -> Result<RecoverableSignature, E>
{
let raw = match self.build_raw() {
Err(e) => return Err(SignOrCreationError::SignError(e)),
};
- let invoice = Invoice {
+ let invoice = Bolt11Invoice {
signed_invoice: signed,
};
}
-impl SignedRawInvoice {
- /// Disassembles the `SignedRawInvoice` into its three parts:
+impl SignedRawBolt11Invoice {
+ /// Disassembles the `SignedRawBolt11Invoice` into its three parts:
/// 1. raw invoice
/// 2. hash of the raw invoice
/// 3. signature
- pub fn into_parts(self) -> (RawInvoice, [u8; 32], InvoiceSignature) {
+ pub fn into_parts(self) -> (RawBolt11Invoice, [u8; 32], Bolt11InvoiceSignature) {
(self.raw_invoice, self.hash, self.signature)
}
- /// The [`RawInvoice`] which was signed.
- pub fn raw_invoice(&self) -> &RawInvoice {
+ /// The [`RawBolt11Invoice`] which was signed.
+ pub fn raw_invoice(&self) -> &RawBolt11Invoice {
&self.raw_invoice
}
- /// The hash of the [`RawInvoice`] that was signed.
+ /// The hash of the [`RawBolt11Invoice`] that was signed.
pub fn signable_hash(&self) -> &[u8; 32] {
&self.hash
}
/// Signature for the invoice.
- pub fn signature(&self) -> &InvoiceSignature {
+ pub fn signature(&self) -> &Bolt11InvoiceSignature {
&self.signature
}
}
#[allow(missing_docs)]
-impl RawInvoice {
+impl RawBolt11Invoice {
/// Hash the HRP as bytes and signatureless data part.
fn hash_from_parts(hrp_bytes: &[u8], data_without_signature: &[u5]) -> [u8; 32] {
let preimage = construct_invoice_preimage(hrp_bytes, data_without_signature);
hash
}
- /// Calculate the hash of the encoded `RawInvoice` which should be signed.
+ /// Calculate the hash of the encoded `RawBolt11Invoice` which should be signed.
pub fn signable_hash(&self) -> [u8; 32] {
use bech32::ToBase32;
- RawInvoice::hash_from_parts(
+ RawBolt11Invoice::hash_from_parts(
self.hrp.to_string().as_bytes(),
&self.data.to_base32()
)
}
/// Signs the invoice using the supplied `sign_method`. This function MAY fail with an error of
- /// type `E`. Since the signature of a [`SignedRawInvoice`] is not required to be valid there
+ /// type `E`. Since the signature of a [`SignedRawBolt11Invoice`] is not required to be valid there
/// are no constraints regarding the validity of the produced signature.
///
/// This is not exported to bindings users as we don't currently support passing function pointers into methods
/// explicitly.
- pub fn sign<F, E>(self, sign_method: F) -> Result<SignedRawInvoice, E>
+ pub fn sign<F, E>(self, sign_method: F) -> Result<SignedRawBolt11Invoice, E>
where F: FnOnce(&Message) -> Result<RecoverableSignature, E>
{
let raw_hash = self.signable_hash();
.expect("Hash is 32 bytes long, same as MESSAGE_SIZE");
let signature = sign_method(&hash)?;
- Ok(SignedRawInvoice {
+ Ok(SignedRawBolt11Invoice {
raw_invoice: self,
hash: raw_hash,
- signature: InvoiceSignature(signature),
+ signature: Bolt11InvoiceSignature(signature),
})
}
find_extract!(self.known_tagged_fields(), TaggedField::PaymentMetadata(ref x), x)
}
- pub fn features(&self) -> Option<&InvoiceFeatures> {
+ pub fn features(&self) -> Option<&Bolt11InvoiceFeatures> {
find_extract!(self.known_tagged_fields(), TaggedField::Features(ref x), x)
}
}
}
-impl Invoice {
- /// The hash of the [`RawInvoice`] that was signed.
+impl Bolt11Invoice {
+ /// The hash of the [`RawBolt11Invoice`] that was signed.
pub fn signable_hash(&self) -> [u8; 32] {
self.signed_invoice.hash
}
- /// Transform the `Invoice` into it's unchecked version
- pub fn into_signed_raw(self) -> SignedRawInvoice {
+ /// Transform the `Bolt11Invoice` into its unchecked version.
+ pub fn into_signed_raw(self) -> SignedRawBolt11Invoice {
self.signed_invoice
}
/// Check that all mandatory fields are present
- fn check_field_counts(&self) -> Result<(), SemanticError> {
+ fn check_field_counts(&self) -> Result<(), Bolt11SemanticError> {
// "A writer MUST include exactly one p field […]."
let payment_hash_cnt = self.tagged_fields().filter(|&tf| match *tf {
TaggedField::PaymentHash(_) => true,
_ => false,
}).count();
if payment_hash_cnt < 1 {
- return Err(SemanticError::NoPaymentHash);
+ return Err(Bolt11SemanticError::NoPaymentHash);
} else if payment_hash_cnt > 1 {
- return Err(SemanticError::MultiplePaymentHashes);
+ return Err(Bolt11SemanticError::MultiplePaymentHashes);
}
// "A writer MUST include either exactly one d or exactly one h field."
_ => false,
}).count();
if description_cnt < 1 {
- return Err(SemanticError::NoDescription);
+ return Err(Bolt11SemanticError::NoDescription);
} else if description_cnt > 1 {
- return Err(SemanticError::MultipleDescriptions);
+ return Err(Bolt11SemanticError::MultipleDescriptions);
}
self.check_payment_secret()?;
}
/// Checks that there is exactly one payment secret field
- fn check_payment_secret(&self) -> Result<(), SemanticError> {
+ fn check_payment_secret(&self) -> Result<(), Bolt11SemanticError> {
// "A writer MUST include exactly one `s` field."
let payment_secret_count = self.tagged_fields().filter(|&tf| match *tf {
TaggedField::PaymentSecret(_) => true,
_ => false,
}).count();
if payment_secret_count < 1 {
- return Err(SemanticError::NoPaymentSecret);
+ return Err(Bolt11SemanticError::NoPaymentSecret);
} else if payment_secret_count > 1 {
- return Err(SemanticError::MultiplePaymentSecrets);
+ return Err(Bolt11SemanticError::MultiplePaymentSecrets);
}
Ok(())
}
/// Check that amount is a whole number of millisatoshis
- fn check_amount(&self) -> Result<(), SemanticError> {
+ fn check_amount(&self) -> Result<(), Bolt11SemanticError> {
if let Some(amount_pico_btc) = self.amount_pico_btc() {
if amount_pico_btc % 10 != 0 {
- return Err(SemanticError::ImpreciseAmount);
+ return Err(Bolt11SemanticError::ImpreciseAmount);
}
}
Ok(())
}
/// Check that feature bits are set as required
- fn check_feature_bits(&self) -> Result<(), SemanticError> {
+ fn check_feature_bits(&self) -> Result<(), Bolt11SemanticError> {
self.check_payment_secret()?;
// "A writer MUST set an s field if and only if the payment_secret feature is set."
_ => false,
});
match features {
- None => Err(SemanticError::InvalidFeatures),
+ None => Err(Bolt11SemanticError::InvalidFeatures),
Some(TaggedField::Features(features)) => {
if features.requires_unknown_bits() {
- Err(SemanticError::InvalidFeatures)
+ Err(Bolt11SemanticError::InvalidFeatures)
} else if !features.supports_payment_secret() {
- Err(SemanticError::InvalidFeatures)
+ Err(Bolt11SemanticError::InvalidFeatures)
} else {
Ok(())
}
}
/// Check that the invoice is signed correctly and that key recovery works
- pub fn check_signature(&self) -> Result<(), SemanticError> {
+ pub fn check_signature(&self) -> Result<(), Bolt11SemanticError> {
match self.signed_invoice.recover_payee_pub_key() {
Err(secp256k1::Error::InvalidRecoveryId) =>
- return Err(SemanticError::InvalidRecoveryId),
+ return Err(Bolt11SemanticError::InvalidRecoveryId),
Err(secp256k1::Error::InvalidSignature) =>
- return Err(SemanticError::InvalidSignature),
+ return Err(Bolt11SemanticError::InvalidSignature),
Err(e) => panic!("no other error may occur, got {:?}", e),
Ok(_) => {},
}
if !self.signed_invoice.check_signature() {
- return Err(SemanticError::InvalidSignature);
+ return Err(Bolt11SemanticError::InvalidSignature);
}
Ok(())
}
- /// Constructs an `Invoice` from a [`SignedRawInvoice`] by checking all its invariants.
+ /// Constructs a `Bolt11Invoice` from a [`SignedRawBolt11Invoice`] by checking all its invariants.
/// ```
/// use lightning_invoice::*;
///
/// 8s0gyuxjjgux34w75dnc6xp2l35j7es3jd4ugt3lu0xzre26yg5m7ke54n2d5sym4xcmxtl8238xxvw5h5h5\
/// j5r6drg6k6zcqj0fcwg";
///
- /// let signed = invoice.parse::<SignedRawInvoice>().unwrap();
+ /// let signed = invoice.parse::<SignedRawBolt11Invoice>().unwrap();
///
- /// assert!(Invoice::from_signed(signed).is_ok());
+ /// assert!(Bolt11Invoice::from_signed(signed).is_ok());
/// ```
- pub fn from_signed(signed_invoice: SignedRawInvoice) -> Result<Self, SemanticError> {
- let invoice = Invoice {
+ pub fn from_signed(signed_invoice: SignedRawBolt11Invoice) -> Result<Self, Bolt11SemanticError> {
+ let invoice = Bolt11Invoice {
signed_invoice,
};
invoice.check_field_counts()?;
Ok(invoice)
}
- /// Returns the `Invoice`'s timestamp (should equal its creation time)
+ /// Returns the `Bolt11Invoice`'s timestamp (should equal its creation time)
#[cfg(feature = "std")]
pub fn timestamp(&self) -> SystemTime {
self.signed_invoice.raw_invoice().data.timestamp.as_time()
}
- /// Returns the `Invoice`'s timestamp as a duration since the Unix epoch
+ /// Returns the `Bolt11Invoice`'s timestamp as a duration since the Unix epoch
pub fn duration_since_epoch(&self) -> Duration {
self.signed_invoice.raw_invoice().data.timestamp.0
}
- /// Returns an iterator over all tagged fields of this Invoice.
+ /// Returns an iterator over all tagged fields of this `Bolt11Invoice`.
///
/// This is not exported to bindings users as there is not yet a manual mapping for a FilterMap
pub fn tagged_fields(&self)
/// Return the description or a hash of it for longer ones
///
- /// This is not exported to bindings users because we don't yet export InvoiceDescription
- pub fn description(&self) -> InvoiceDescription {
+ /// This is not exported to bindings users because we don't yet export Bolt11InvoiceDescription
+ pub fn description(&self) -> Bolt11InvoiceDescription {
if let Some(direct) = self.signed_invoice.description() {
- return InvoiceDescription::Direct(direct);
+ return Bolt11InvoiceDescription::Direct(direct);
} else if let Some(hash) = self.signed_invoice.description_hash() {
- return InvoiceDescription::Hash(hash);
+ return Bolt11InvoiceDescription::Hash(hash);
}
unreachable!("ensured by constructor");
}
}
/// Get the invoice features if they were included in the invoice
- pub fn features(&self) -> Option<&InvoiceFeatures> {
+ pub fn features(&self) -> Option<&Bolt11InvoiceFeatures> {
self.signed_invoice.features()
}
}
}
-impl Deref for InvoiceSignature {
+impl Deref for Bolt11InvoiceSignature {
type Target = RecoverableSignature;
fn deref(&self) -> &RecoverableSignature {
}
}
-impl Deref for SignedRawInvoice {
- type Target = RawInvoice;
+impl Deref for SignedRawBolt11Invoice {
+ type Target = RawBolt11Invoice;
- fn deref(&self) -> &RawInvoice {
+ fn deref(&self) -> &RawBolt11Invoice {
&self.raw_invoice
}
}
-/// Errors that may occur when constructing a new [`RawInvoice`] or [`Invoice`]
+/// Errors that may occur when constructing a new [`RawBolt11Invoice`] or [`Bolt11Invoice`]
#[derive(Eq, PartialEq, Debug, Clone)]
pub enum CreationError {
/// The supplied description string was longer than 639 __bytes__ (see [`Description::new`])
#[cfg(feature = "std")]
impl std::error::Error for CreationError { }
-/// Errors that may occur when converting a [`RawInvoice`] to an [`Invoice`]. They relate to the
-/// requirements sections in BOLT #11
+/// Errors that may occur when converting a [`RawBolt11Invoice`] to a [`Bolt11Invoice`]. They relate to
+/// the requirements sections in BOLT #11
#[derive(Eq, PartialEq, Debug, Clone)]
-pub enum SemanticError {
+pub enum Bolt11SemanticError {
/// The invoice is missing the mandatory payment hash
NoPaymentHash,
ImpreciseAmount,
}
-impl Display for SemanticError {
+impl Display for Bolt11SemanticError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
- SemanticError::NoPaymentHash => f.write_str("The invoice is missing the mandatory payment hash"),
- SemanticError::MultiplePaymentHashes => f.write_str("The invoice has multiple payment hashes which isn't allowed"),
- SemanticError::NoDescription => f.write_str("No description or description hash are part of the invoice"),
- SemanticError::MultipleDescriptions => f.write_str("The invoice contains multiple descriptions and/or description hashes which isn't allowed"),
- SemanticError::NoPaymentSecret => f.write_str("The invoice is missing the mandatory payment secret"),
- SemanticError::MultiplePaymentSecrets => f.write_str("The invoice contains multiple payment secrets"),
- SemanticError::InvalidFeatures => f.write_str("The invoice's features are invalid"),
- SemanticError::InvalidRecoveryId => f.write_str("The recovery id doesn't fit the signature/pub key"),
- SemanticError::InvalidSignature => f.write_str("The invoice's signature is invalid"),
- SemanticError::ImpreciseAmount => f.write_str("The invoice's amount was not a whole number of millisatoshis"),
+ Bolt11SemanticError::NoPaymentHash => f.write_str("The invoice is missing the mandatory payment hash"),
+ Bolt11SemanticError::MultiplePaymentHashes => f.write_str("The invoice has multiple payment hashes which isn't allowed"),
+ Bolt11SemanticError::NoDescription => f.write_str("No description or description hash are part of the invoice"),
+ Bolt11SemanticError::MultipleDescriptions => f.write_str("The invoice contains multiple descriptions and/or description hashes which isn't allowed"),
+ Bolt11SemanticError::NoPaymentSecret => f.write_str("The invoice is missing the mandatory payment secret"),
+ Bolt11SemanticError::MultiplePaymentSecrets => f.write_str("The invoice contains multiple payment secrets"),
+ Bolt11SemanticError::InvalidFeatures => f.write_str("The invoice's features are invalid"),
+ Bolt11SemanticError::InvalidRecoveryId => f.write_str("The recovery id doesn't fit the signature/pub key"),
+ Bolt11SemanticError::InvalidSignature => f.write_str("The invoice's signature is invalid"),
+ Bolt11SemanticError::ImpreciseAmount => f.write_str("The invoice's amount was not a whole number of millisatoshis"),
}
}
}
#[cfg(feature = "std")]
-impl std::error::Error for SemanticError { }
+impl std::error::Error for Bolt11SemanticError { }
/// When signing using a fallible method either an user-supplied `SignError` or a [`CreationError`]
/// may occur.
}
#[cfg(feature = "serde")]
-impl Serialize for Invoice {
+impl Serialize for Bolt11Invoice {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
serializer.serialize_str(self.to_string().as_str())
}
}
#[cfg(feature = "serde")]
-impl<'de> Deserialize<'de> for Invoice {
- fn deserialize<D>(deserializer: D) -> Result<Invoice, D::Error> where D: Deserializer<'de> {
+impl<'de> Deserialize<'de> for Bolt11Invoice {
+ fn deserialize<D>(deserializer: D) -> Result<Bolt11Invoice, D::Error> where D: Deserializer<'de> {
let bolt11 = String::deserialize(deserializer)?
- .parse::<Invoice>()
+ .parse::<Bolt11Invoice>()
.map_err(|e| D::Error::custom(format_args!("{:?}", e)))?;
Ok(bolt11)
#[test]
fn test_calc_invoice_hash() {
- use crate::{RawInvoice, RawHrp, RawDataPart, Currency, PositiveTimestamp};
+ use crate::{RawBolt11Invoice, RawHrp, RawDataPart, Currency, PositiveTimestamp};
use crate::TaggedField::*;
- let invoice = RawInvoice {
+ let invoice = RawBolt11Invoice {
hrp: RawHrp {
currency: Currency::Bitcoin,
raw_amount: None,
use secp256k1::Secp256k1;
use secp256k1::ecdsa::{RecoveryId, RecoverableSignature};
use secp256k1::{SecretKey, PublicKey};
- use crate::{SignedRawInvoice, InvoiceSignature, RawInvoice, RawHrp, RawDataPart, Currency, Sha256,
+ use crate::{SignedRawBolt11Invoice, Bolt11InvoiceSignature, RawBolt11Invoice, RawHrp, RawDataPart, Currency, Sha256,
PositiveTimestamp};
- let invoice = SignedRawInvoice {
- raw_invoice: RawInvoice {
+ let invoice = SignedRawBolt11Invoice {
+ raw_invoice: RawBolt11Invoice {
hrp: RawHrp {
currency: Currency::Bitcoin,
raw_amount: None,
0x7b, 0x1d, 0x85, 0x8d, 0xb1, 0xd1, 0xf7, 0xab, 0x71, 0x37, 0xdc, 0xb7,
0x83, 0x5d, 0xb2, 0xec, 0xd5, 0x18, 0xe1, 0xc9
],
- signature: InvoiceSignature(RecoverableSignature::from_compact(
+ signature: Bolt11InvoiceSignature(RecoverableSignature::from_compact(
& [
0x38u8, 0xec, 0x68, 0x91, 0x34, 0x5e, 0x20, 0x41, 0x45, 0xbe, 0x8a,
0x3a, 0x99, 0xde, 0x38, 0xe9, 0x8a, 0x39, 0xd6, 0xa5, 0x69, 0x43,
#[test]
fn test_check_feature_bits() {
use crate::TaggedField::*;
- use lightning::ln::features::InvoiceFeatures;
+ use lightning::ln::features::Bolt11InvoiceFeatures;
use secp256k1::Secp256k1;
use secp256k1::SecretKey;
- use crate::{RawInvoice, RawHrp, RawDataPart, Currency, Sha256, PositiveTimestamp, Invoice,
- SemanticError};
+ use crate::{Bolt11Invoice, RawBolt11Invoice, RawHrp, RawDataPart, Currency, Sha256, PositiveTimestamp,
+ Bolt11SemanticError};
let private_key = SecretKey::from_slice(&[42; 32]).unwrap();
let payment_secret = lightning::ln::PaymentSecret([21; 32]);
- let invoice_template = RawInvoice {
+ let invoice_template = RawBolt11Invoice {
hrp: RawHrp {
currency: Currency::Bitcoin,
raw_amount: None,
invoice.data.tagged_fields.push(PaymentSecret(payment_secret).into());
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)))
}.unwrap();
- assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::InvalidFeatures));
+ assert_eq!(Bolt11Invoice::from_signed(invoice), Err(Bolt11SemanticError::InvalidFeatures));
// Missing feature bits
let invoice = {
let mut invoice = invoice_template.clone();
invoice.data.tagged_fields.push(PaymentSecret(payment_secret).into());
- invoice.data.tagged_fields.push(Features(InvoiceFeatures::empty()).into());
+ invoice.data.tagged_fields.push(Features(Bolt11InvoiceFeatures::empty()).into());
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)))
}.unwrap();
- assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::InvalidFeatures));
+ assert_eq!(Bolt11Invoice::from_signed(invoice), Err(Bolt11SemanticError::InvalidFeatures));
- let mut payment_secret_features = InvoiceFeatures::empty();
+ let mut payment_secret_features = Bolt11InvoiceFeatures::empty();
payment_secret_features.set_payment_secret_required();
// Including payment secret and feature bits
invoice.data.tagged_fields.push(Features(payment_secret_features.clone()).into());
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)))
}.unwrap();
- assert!(Invoice::from_signed(invoice).is_ok());
+ assert!(Bolt11Invoice::from_signed(invoice).is_ok());
// No payment secret or features
let invoice = {
let invoice = invoice_template.clone();
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)))
}.unwrap();
- assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret));
+ assert_eq!(Bolt11Invoice::from_signed(invoice), Err(Bolt11SemanticError::NoPaymentSecret));
// No payment secret or feature bits
let invoice = {
let mut invoice = invoice_template.clone();
- invoice.data.tagged_fields.push(Features(InvoiceFeatures::empty()).into());
+ invoice.data.tagged_fields.push(Features(Bolt11InvoiceFeatures::empty()).into());
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)))
}.unwrap();
- assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret));
+ assert_eq!(Bolt11Invoice::from_signed(invoice), Err(Bolt11SemanticError::NoPaymentSecret));
// Missing payment secret
let invoice = {
invoice.data.tagged_fields.push(Features(payment_secret_features).into());
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)))
}.unwrap();
- assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret));
+ assert_eq!(Bolt11Invoice::from_signed(invoice), Err(Bolt11SemanticError::NoPaymentSecret));
// Multiple payment secrets
let invoice = {
invoice.data.tagged_fields.push(PaymentSecret(payment_secret).into());
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)))
}.unwrap();
- assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::MultiplePaymentSecrets));
+ assert_eq!(Bolt11Invoice::from_signed(invoice), Err(Bolt11SemanticError::MultiplePaymentSecrets));
}
#[test]
assert_eq!(invoice.private_routes(), vec![&PrivateRoute(route_1), &PrivateRoute(route_2)]);
assert_eq!(
invoice.description(),
- InvoiceDescription::Hash(&Sha256(sha256::Hash::from_slice(&[3;32][..]).unwrap()))
+ Bolt11InvoiceDescription::Hash(&Sha256(sha256::Hash::from_slice(&[3;32][..]).unwrap()))
);
assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&[21;32][..]).unwrap());
assert_eq!(invoice.payment_secret(), &PaymentSecret([42; 32]));
- let mut expected_features = InvoiceFeatures::empty();
+ let mut expected_features = Bolt11InvoiceFeatures::empty();
expected_features.set_variable_length_onion_required();
expected_features.set_payment_secret_required();
expected_features.set_basic_mpp_optional();
Ok(secp_ctx.sign_ecdsa_recoverable(hash, &privkey))
})
.unwrap();
- let invoice = Invoice::from_signed(signed_invoice).unwrap();
+ let invoice = Bolt11Invoice::from_signed(signed_invoice).unwrap();
assert_eq!(invoice.min_final_cltv_expiry_delta(), DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA);
assert_eq!(invoice.expiry_time(), Duration::from_secs(DEFAULT_EXPIRY_TIME));
Ok(secp_ctx.sign_ecdsa_recoverable(hash, &privkey))
})
.unwrap();
- let invoice = Invoice::from_signed(signed_invoice).unwrap();
+ let invoice = Bolt11Invoice::from_signed(signed_invoice).unwrap();
assert!(invoice.would_expire(Duration::from_secs(1234567 + DEFAULT_EXPIRY_TIME + 1)));
}
p62g49p7569ev48cmulecsxe59lvaw3wlxm7r982zxa9zzj7z5l0cqqxusqqyqqqqlgqqqqqzsqygarl9fh3\
8s0gyuxjjgux34w75dnc6xp2l35j7es3jd4ugt3lu0xzre26yg5m7ke54n2d5sym4xcmxtl8238xxvw5h5h5\
j5r6drg6k6zcqj0fcwg";
- let invoice = invoice_str.parse::<super::Invoice>().unwrap();
+ let invoice = invoice_str.parse::<super::Bolt11Invoice>().unwrap();
let serialized_invoice = serde_json::to_string(&invoice).unwrap();
- let deserialized_invoice: super::Invoice = serde_json::from_str(serialized_invoice.as_str()).unwrap();
+ let deserialized_invoice: super::Bolt11Invoice = serde_json::from_str(serialized_invoice.as_str()).unwrap();
assert_eq!(invoice, deserialized_invoice);
assert_eq!(invoice_str, deserialized_invoice.to_string().as_str());
assert_eq!(invoice_str, serialized_invoice.as_str().trim_matches('\"'));
//! Convenient utilities for paying Lightning invoices and sending spontaneous payments.
-use crate::Invoice;
+use crate::Bolt11Invoice;
use bitcoin_hashes::Hash;
use core::ops::Deref;
use core::time::Duration;
-/// Pays the given [`Invoice`], retrying if needed based on [`Retry`].
+/// Pays the given [`Bolt11Invoice`], retrying if needed based on [`Retry`].
///
-/// [`Invoice::payment_hash`] is used as the [`PaymentId`], which ensures idempotency as long
+/// [`Bolt11Invoice::payment_hash`] is used as the [`PaymentId`], which ensures idempotency as long
/// as the payment is still pending. If the payment succeeds, you must ensure that a second payment
/// with the same [`PaymentHash`] is never sent.
///
/// If you wish to use a different payment idempotency token, see [`pay_invoice_with_id`].
pub fn pay_invoice<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
- invoice: &Invoice, retry_strategy: Retry,
+ invoice: &Bolt11Invoice, retry_strategy: Retry,
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>
) -> Result<PaymentId, PaymentError>
where
.map(|()| payment_id)
}
-/// Pays the given [`Invoice`] with a custom idempotency key, retrying if needed based on [`Retry`].
+/// Pays the given [`Bolt11Invoice`] with a custom idempotency key, retrying if needed based on
+/// [`Retry`].
///
/// Note that idempotency is only guaranteed as long as the payment is still pending. Once the
/// payment completes or fails, no idempotency guarantees are made.
///
-/// You should ensure that the [`Invoice::payment_hash`] is unique and the same [`PaymentHash`]
-/// has never been paid before.
+/// You should ensure that the [`Bolt11Invoice::payment_hash`] is unique and the same
+/// [`PaymentHash`] has never been paid before.
///
/// See [`pay_invoice`] for a variant which uses the [`PaymentHash`] for the idempotency token.
pub fn pay_invoice_with_id<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
- invoice: &Invoice, payment_id: PaymentId, retry_strategy: Retry,
+ invoice: &Bolt11Invoice, payment_id: PaymentId, retry_strategy: Retry,
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>
) -> Result<(), PaymentError>
where
pay_invoice_using_amount(invoice, amt_msat, payment_id, retry_strategy, channelmanager)
}
-/// Pays the given zero-value [`Invoice`] using the given amount, retrying if needed based on
+/// Pays the given zero-value [`Bolt11Invoice`] using the given amount, retrying if needed based on
/// [`Retry`].
///
-/// [`Invoice::payment_hash`] is used as the [`PaymentId`], which ensures idempotency as long
+/// [`Bolt11Invoice::payment_hash`] is used as the [`PaymentId`], which ensures idempotency as long
/// as the payment is still pending. If the payment succeeds, you must ensure that a second payment
/// with the same [`PaymentHash`] is never sent.
///
/// If you wish to use a different payment idempotency token, see
/// [`pay_zero_value_invoice_with_id`].
pub fn pay_zero_value_invoice<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
- invoice: &Invoice, amount_msats: u64, retry_strategy: Retry,
+ invoice: &Bolt11Invoice, amount_msats: u64, retry_strategy: Retry,
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>
) -> Result<PaymentId, PaymentError>
where
.map(|()| payment_id)
}
-/// Pays the given zero-value [`Invoice`] using the given amount and custom idempotency key,
+/// Pays the given zero-value [`Bolt11Invoice`] using the given amount and custom idempotency key,
/// retrying if needed based on [`Retry`].
///
/// Note that idempotency is only guaranteed as long as the payment is still pending. Once the
/// payment completes or fails, no idempotency guarantees are made.
///
-/// You should ensure that the [`Invoice::payment_hash`] is unique and the same [`PaymentHash`]
-/// has never been paid before.
+/// You should ensure that the [`Bolt11Invoice::payment_hash`] is unique and the same
+/// [`PaymentHash`] has never been paid before.
///
/// See [`pay_zero_value_invoice`] for a variant which uses the [`PaymentHash`] for the
/// idempotency token.
pub fn pay_zero_value_invoice_with_id<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
- invoice: &Invoice, amount_msats: u64, payment_id: PaymentId, retry_strategy: Retry,
+ invoice: &Bolt11Invoice, amount_msats: u64, payment_id: PaymentId, retry_strategy: Retry,
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>
) -> Result<(), PaymentError>
where
}
fn pay_invoice_using_amount<P: Deref>(
- invoice: &Invoice, amount_msats: u64, payment_id: PaymentId, retry_strategy: Retry,
+ invoice: &Bolt11Invoice, amount_msats: u64, payment_id: PaymentId, retry_strategy: Retry,
payer: P
) -> Result<(), PaymentError> where P::Target: Payer {
let payment_hash = PaymentHash((*invoice.payment_hash()).into_inner());
payer.send_payment(payment_hash, recipient_onion, payment_id, route_params, retry_strategy)
}
-fn expiry_time_from_unix_epoch(invoice: &Invoice) -> Duration {
+fn expiry_time_from_unix_epoch(invoice: &Bolt11Invoice) -> Duration {
invoice.signed_invoice.raw_invoice.data.timestamp.0 + invoice.expiry_time()
}
/// An error that may occur when making a payment.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PaymentError {
- /// An error resulting from the provided [`Invoice`] or payment hash.
+ /// An error resulting from the provided [`Bolt11Invoice`] or payment hash.
Invoice(&'static str),
/// An error occurring when sending a payment.
Sending(RetryableSendFailure),
}
-/// A trait defining behavior of an [`Invoice`] payer.
+/// A trait defining behavior of a [`Bolt11Invoice`] payer.
///
/// Useful for unit testing internal methods.
trait Payer {
duration_since_epoch
}
- fn invoice(payment_preimage: PaymentPreimage) -> Invoice {
+ fn invoice(payment_preimage: PaymentPreimage) -> Bolt11Invoice {
let payment_hash = Sha256::hash(&payment_preimage.0);
let private_key = SecretKey::from_slice(&[42; 32]).unwrap();
.unwrap()
}
- fn zero_value_invoice(payment_preimage: PaymentPreimage) -> Invoice {
+ fn zero_value_invoice(payment_preimage: PaymentPreimage) -> Bolt11Invoice {
let payment_hash = Sha256::hash(&payment_preimage.0);
let private_key = SecretKey::from_slice(&[42; 32]).unwrap();
use bech32::{ToBase32, u5, WriteBase32, Base32Len};
use crate::prelude::*;
-use super::{Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, InvoiceSignature, PositiveTimestamp,
- PrivateRoute, Description, RawTaggedField, Currency, RawHrp, SiPrefix, constants, SignedRawInvoice, RawDataPart};
+use super::{Bolt11Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, Bolt11InvoiceSignature, PositiveTimestamp,
+ PrivateRoute, Description, RawTaggedField, Currency, RawHrp, SiPrefix, constants, SignedRawBolt11Invoice, RawDataPart};
/// Converts a stream of bytes written to it to base32. On finalization the according padding will
/// be applied. That means the results of writing two data blocks with one or two `BytesToBase32`
}
}
-impl Display for Invoice {
+impl Display for Bolt11Invoice {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
self.signed_invoice.fmt(f)
}
}
-impl Display for SignedRawInvoice {
+impl Display for SignedRawBolt11Invoice {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
let hrp = self.raw_invoice.hrp.to_string();
let mut data = self.raw_invoice.data.to_base32();
}
}
-impl ToBase32 for InvoiceSignature {
+impl ToBase32 for Bolt11InvoiceSignature {
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
let mut converter = BytesToBase32::new(writer);
let (recovery_id, signature) = self.0.serialize_compact();
//! Convenient utilities to create an invoice.
-use crate::{CreationError, Currency, Invoice, InvoiceBuilder, SignOrCreationError};
+use crate::{Bolt11Invoice, CreationError, Currency, InvoiceBuilder, SignOrCreationError};
-use crate::{prelude::*, Description, InvoiceDescription, Sha256};
+use crate::{prelude::*, Description, Bolt11InvoiceDescription, Sha256};
use bech32::ToBase32;
use bitcoin_hashes::Hash;
use lightning::chain;
amt_msat: Option<u64>, payment_hash: Option<PaymentHash>, description: String,
invoice_expiry_delta_secs: u32, phantom_route_hints: Vec<PhantomRouteHints>, entropy_source: ES,
node_signer: NS, logger: L, network: Currency, min_final_cltv_expiry_delta: Option<u16>, duration_since_epoch: Duration,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
ES::Target: EntropySource,
NS::Target: NodeSigner,
L::Target: Logger,
{
let description = Description::new(description).map_err(SignOrCreationError::CreationError)?;
- let description = InvoiceDescription::Direct(&description,);
+ let description = Bolt11InvoiceDescription::Direct(&description,);
_create_phantom_invoice::<ES, NS, L>(
amt_msat, payment_hash, description, invoice_expiry_delta_secs, phantom_route_hints,
entropy_source, node_signer, logger, network, min_final_cltv_expiry_delta, duration_since_epoch,
amt_msat: Option<u64>, payment_hash: Option<PaymentHash>, invoice_expiry_delta_secs: u32,
description_hash: Sha256, phantom_route_hints: Vec<PhantomRouteHints>, entropy_source: ES,
node_signer: NS, logger: L, network: Currency, min_final_cltv_expiry_delta: Option<u16>, duration_since_epoch: Duration,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
ES::Target: EntropySource,
NS::Target: NodeSigner,
L::Target: Logger,
{
_create_phantom_invoice::<ES, NS, L>(
- amt_msat, payment_hash, InvoiceDescription::Hash(&description_hash),
+ amt_msat, payment_hash, Bolt11InvoiceDescription::Hash(&description_hash),
invoice_expiry_delta_secs, phantom_route_hints, entropy_source, node_signer, logger, network,
min_final_cltv_expiry_delta, duration_since_epoch,
)
const MAX_CHANNEL_HINTS: usize = 3;
fn _create_phantom_invoice<ES: Deref, NS: Deref, L: Deref>(
- amt_msat: Option<u64>, payment_hash: Option<PaymentHash>, description: InvoiceDescription,
+ amt_msat: Option<u64>, payment_hash: Option<PaymentHash>, description: Bolt11InvoiceDescription,
invoice_expiry_delta_secs: u32, phantom_route_hints: Vec<PhantomRouteHints>, entropy_source: ES,
node_signer: NS, logger: L, network: Currency, min_final_cltv_expiry_delta: Option<u16>, duration_since_epoch: Duration,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
ES::Target: EntropySource,
NS::Target: NodeSigner,
}
let invoice = match description {
- InvoiceDescription::Direct(description) => {
+ Bolt11InvoiceDescription::Direct(description) => {
InvoiceBuilder::new(network).description(description.0.clone())
}
- InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0),
+ Bolt11InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0),
};
// If we ever see performance here being too slow then we should probably take this ExpandedKey as a parameter instead.
let data_without_signature = raw_invoice.data.to_base32();
let signed_raw_invoice = raw_invoice.sign(|_| node_signer.sign_invoice(hrp_bytes, &data_without_signature, Recipient::PhantomNode));
match signed_raw_invoice {
- Ok(inv) => Ok(Invoice::from_signed(inv).unwrap()),
+ Ok(inv) => Ok(Bolt11Invoice::from_signed(inv).unwrap()),
Err(e) => Err(SignOrCreationError::SignError(e))
}
}
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description: String, invoice_expiry_delta_secs: u32,
min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description_hash: Sha256,
invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description_hash: Sha256,
duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
{
_create_invoice_from_channelmanager_and_duration_since_epoch(
channelmanager, node_signer, logger, network, amt_msat,
- InvoiceDescription::Hash(&description_hash),
+ Bolt11InvoiceDescription::Hash(&description_hash),
duration_since_epoch, invoice_expiry_delta_secs, min_final_cltv_expiry_delta,
)
}
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description: String, duration_since_epoch: Duration,
invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
{
_create_invoice_from_channelmanager_and_duration_since_epoch(
channelmanager, node_signer, logger, network, amt_msat,
- InvoiceDescription::Direct(
+ Bolt11InvoiceDescription::Direct(
&Description::new(description).map_err(SignOrCreationError::CreationError)?,
),
duration_since_epoch, invoice_expiry_delta_secs, min_final_cltv_expiry_delta,
fn _create_invoice_from_channelmanager_and_duration_since_epoch<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
- network: Currency, amt_msat: Option<u64>, description: InvoiceDescription,
+ network: Currency, amt_msat: Option<u64>, description: Bolt11InvoiceDescription,
duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description: String, duration_since_epoch: Duration,
invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?;
_create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash(
channelmanager, node_signer, logger, network, amt_msat,
- InvoiceDescription::Direct(
+ Bolt11InvoiceDescription::Direct(
&Description::new(description).map_err(SignOrCreationError::CreationError)?,
),
duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret,
fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
- network: Currency, amt_msat: Option<u64>, description: InvoiceDescription, duration_since_epoch: Duration,
- invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, payment_secret: PaymentSecret,
- min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+ network: Currency, amt_msat: Option<u64>, description: Bolt11InvoiceDescription,
+ duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, payment_hash: PaymentHash,
+ payment_secret: PaymentSecret, min_final_cltv_expiry_delta: Option<u16>,
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
log_trace!(logger, "Creating invoice with payment hash {}", log_bytes!(payment_hash.0));
let invoice = match description {
- InvoiceDescription::Direct(description) => {
+ Bolt11InvoiceDescription::Direct(description) => {
InvoiceBuilder::new(network).description(description.0.clone())
}
- InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0),
+ Bolt11InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0),
};
let mut invoice = invoice
let data_without_signature = raw_invoice.data.to_base32();
let signed_raw_invoice = raw_invoice.sign(|_| node_signer.sign_invoice(hrp_bytes, &data_without_signature, Recipient::Node));
match signed_raw_invoice {
- Ok(inv) => Ok(Invoice::from_signed(inv).unwrap()),
+ Ok(inv) => Ok(Bolt11Invoice::from_signed(inv).unwrap()),
Err(e) => Err(SignOrCreationError::SignError(e))
}
}
#[cfg(test)]
mod test {
+ use core::cell::RefCell;
use core::time::Duration;
- use crate::{Currency, Description, InvoiceDescription, SignOrCreationError, CreationError};
+ use crate::{Currency, Description, Bolt11InvoiceDescription, SignOrCreationError, CreationError};
use bitcoin_hashes::{Hash, sha256};
use bitcoin_hashes::sha256::Hash as Sha256;
use lightning::sign::PhantomKeysManager;
- use lightning::events::{MessageSendEvent, MessageSendEventsProvider, Event};
+ use lightning::events::{MessageSendEvent, MessageSendEventsProvider, Event, EventsProvider};
use lightning::ln::{PaymentPreimage, PaymentHash};
use lightning::ln::channelmanager::{PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY_DELTA, PaymentId, RecipientOnionFields, Retry};
use lightning::ln::functional_test_utils::*;
assert_eq!(invoice.amount_pico_btc(), Some(100_000));
// If no `min_final_cltv_expiry_delta` is specified, then it should be `MIN_FINAL_CLTV_EXPIRY_DELTA`.
assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
- assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));
+ assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description("test".to_string())));
assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into()));
// Invoice SCIDs should always use inbound SCID aliases over the real channel ID, if one is
).unwrap();
assert_eq!(invoice.amount_pico_btc(), Some(100_000));
assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
- assert_eq!(invoice.description(), InvoiceDescription::Hash(&crate::Sha256(Sha256::hash("Testing description_hash".as_bytes()))));
+ assert_eq!(invoice.description(), Bolt11InvoiceDescription::Hash(&crate::Sha256(Sha256::hash("Testing description_hash".as_bytes()))));
}
#[test]
).unwrap();
assert_eq!(invoice.amount_pico_btc(), Some(100_000));
assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
- assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));
+ assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description("test".to_string())));
assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&payment_hash.0[..]).unwrap());
}
} else {
None
};
+ let genesis_timestamp = bitcoin::blockdata::constants::genesis_block(bitcoin::Network::Testnet).header.time as u64;
let non_default_invoice_expiry_secs = 4200;
let invoice =
crate::utils::create_phantom_invoice::<&test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestLogger>(
Some(payment_amt), payment_hash, "test".to_string(), non_default_invoice_expiry_secs,
route_hints, nodes[1].keys_manager, nodes[1].keys_manager, nodes[1].logger,
- Currency::BitcoinTestnet, None, Duration::from_secs(1234567)
+ 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_preimage = if user_generated_pmt_hash {
};
assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
- assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));
+ assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description("test".to_string())));
assert_eq!(invoice.route_hints().len(), 2);
assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into()));
assert!(!invoice.features().unwrap().supports_basic_mpp());
// Note that we have to "forward pending HTLCs" twice before we see the PaymentClaimable as
// this "emulates" the payment taking two hops, providing some privacy to make phantom node
// payments "look real" by taking more time.
- expect_pending_htlcs_forwardable_ignore!(nodes[fwd_idx]);
- nodes[fwd_idx].node.process_pending_htlc_forwards();
- expect_pending_htlcs_forwardable_ignore!(nodes[fwd_idx]);
- nodes[fwd_idx].node.process_pending_htlc_forwards();
+ let other_events = RefCell::new(Vec::new());
+ let forward_event_handler = |event: Event| {
+ if let Event::PendingHTLCsForwardable { .. } = event {
+ nodes[fwd_idx].node.process_pending_htlc_forwards();
+ } else {
+ other_events.borrow_mut().push(event);
+ }
+ };
+ nodes[fwd_idx].node.process_pending_events(&forward_event_handler);
+ nodes[fwd_idx].node.process_pending_events(&forward_event_handler);
let payment_preimage_opt = if user_generated_pmt_hash { None } else { Some(payment_preimage) };
- expect_payment_claimable!(&nodes[fwd_idx], payment_hash, payment_secret, payment_amt, payment_preimage_opt, invoice.recover_payee_pub_key());
+ assert_eq!(other_events.borrow().len(), 1);
+ check_payment_claimable(&other_events.borrow()[0], payment_hash, payment_secret, payment_amt, payment_preimage_opt, invoice.recover_payee_pub_key());
do_claim_payment_along_route(&nodes[0], &[&vec!(&nodes[fwd_idx])[..]], false, payment_preimage);
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 2);
assert_eq!(invoice.amount_pico_btc(), Some(200_000));
assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into()));
- assert_eq!(invoice.description(), InvoiceDescription::Hash(&crate::Sha256(Sha256::hash("Description hash phantom invoice".as_bytes()))));
+ assert_eq!(invoice.description(), Bolt11InvoiceDescription::Hash(&crate::Sha256(Sha256::hash("Description hash phantom invoice".as_bytes()))));
}
#[test]
use std::time::Duration;
use std::str::FromStr;
-fn get_test_tuples() -> Vec<(String, SignedRawInvoice, bool, bool)> {
+fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> {
vec![
(
"lnbc1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq9qrsgq357wnc5r2ueh7ck6q93dj32dlqnls087fxdwk8qakdyafkq3yap9us6v52vjjsrvywa6rt52cm9r9zqt8r2t7mlcwspyetp5h2tztugp9lfyql".to_owned(),
fn invoice_deserialize() {
for (serialized, deserialized, ignore_feature_diff, ignore_unknown_fields) in get_test_tuples() {
eprintln!("Testing invoice {}...", serialized);
- let parsed = serialized.parse::<SignedRawInvoice>().unwrap();
+ let parsed = serialized.parse::<SignedRawBolt11Invoice>().unwrap();
let (parsed_invoice, _, parsed_sig) = parsed.into_parts();
let (deserialized_invoice, _, deserialized_sig) = deserialized.into_parts();
}
assert_eq!(deserialized_hunks, parsed_hunks);
- Invoice::from_signed(serialized.parse::<SignedRawInvoice>().unwrap()).unwrap();
+ Bolt11Invoice::from_signed(serialized.parse::<SignedRawBolt11Invoice>().unwrap()).unwrap();
}
}
#[test]
fn test_bolt_invalid_invoices() {
// Tests the BOLT 11 invalid invoice test vectors
- assert_eq!(Invoice::from_str(
+ assert_eq!(Bolt11Invoice::from_str(
"lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q4psqqqqqqqqqqqqqqqqsgqtqyx5vggfcsll4wu246hz02kp85x4katwsk9639we5n5yngc3yhqkm35jnjw4len8vrnqnf5ejh0mzj9n3vz2px97evektfm2l6wqccp3y7372"
- ), Err(ParseOrSemanticError::SemanticError(SemanticError::InvalidFeatures)));
- assert_eq!(Invoice::from_str(
+ ), Err(ParseOrSemanticError::SemanticError(Bolt11SemanticError::InvalidFeatures)));
+ assert_eq!(Bolt11Invoice::from_str(
"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrnt"
- ), Err(ParseOrSemanticError::ParseError(ParseError::Bech32Error(bech32::Error::InvalidChecksum))));
- assert_eq!(Invoice::from_str(
+ ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(bech32::Error::InvalidChecksum))));
+ assert_eq!(Bolt11Invoice::from_str(
"pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny"
- ), Err(ParseOrSemanticError::ParseError(ParseError::Bech32Error(bech32::Error::MissingSeparator))));
- assert_eq!(Invoice::from_str(
+ ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(bech32::Error::MissingSeparator))));
+ assert_eq!(Bolt11Invoice::from_str(
"LNBC2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny"
- ), Err(ParseOrSemanticError::ParseError(ParseError::Bech32Error(bech32::Error::MixedCase))));
- assert_eq!(Invoice::from_str(
+ ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(bech32::Error::MixedCase))));
+ assert_eq!(Bolt11Invoice::from_str(
"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqwgt7mcn5yqw3yx0w94pswkpq6j9uh6xfqqqtsk4tnarugeektd4hg5975x9am52rz4qskukxdmjemg92vvqz8nvmsye63r5ykel43pgz7zq0g2"
- ), Err(ParseOrSemanticError::SemanticError(SemanticError::InvalidSignature)));
- assert_eq!(Invoice::from_str(
+ ), Err(ParseOrSemanticError::SemanticError(Bolt11SemanticError::InvalidSignature)));
+ assert_eq!(Bolt11Invoice::from_str(
"lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6na6hlh"
- ), Err(ParseOrSemanticError::ParseError(ParseError::TooShortDataPart)));
- assert_eq!(Invoice::from_str(
+ ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::TooShortDataPart)));
+ assert_eq!(Bolt11Invoice::from_str(
"lnbc2500x1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqrrzc4cvfue4zp3hggxp47ag7xnrlr8vgcmkjxk3j5jqethnumgkpqp23z9jclu3v0a7e0aruz366e9wqdykw6dxhdzcjjhldxq0w6wgqcnu43j"
- ), Err(ParseOrSemanticError::ParseError(ParseError::UnknownSiPrefix)));
- assert_eq!(Invoice::from_str(
+ ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::UnknownSiPrefix)));
+ assert_eq!(Bolt11Invoice::from_str(
"lnbc2500000001p1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgq0lzc236j96a95uv0m3umg28gclm5lqxtqqwk32uuk4k6673k6n5kfvx3d2h8s295fad45fdhmusm8sjudfhlf6dcsxmfvkeywmjdkxcp99202x"
- ), Err(ParseOrSemanticError::SemanticError(SemanticError::ImpreciseAmount)));
+ ), Err(ParseOrSemanticError::SemanticError(Bolt11SemanticError::ImpreciseAmount)));
}
[package]
name = "lightning-net-tokio"
-version = "0.0.115"
+version = "0.0.116-rc1"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
[dependencies]
bitcoin = "0.29.0"
-lightning = { version = "0.0.115", path = "../lightning" }
-tokio = { version = "1.0", features = [ "io-util", "macros", "rt", "sync", "net", "time" ] }
+lightning = { version = "0.0.116-rc1", path = "../lightning" }
+tokio = { version = "1.0", features = [ "io-util", "rt", "sync", "net", "time" ] }
[dev-dependencies]
tokio = { version = "1.14", features = [ "io-util", "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] }
-lightning = { version = "0.0.115", path = "../lightning", features = ["_test_utils"] }
+lightning = { version = "0.0.116-rc1", path = "../lightning", features = ["_test_utils"] }
use lightning::ln::msgs::NetAddress;
use std::ops::Deref;
-use std::task;
+use std::task::{self, Poll};
+use std::future::Future;
use std::net::SocketAddr;
use std::net::TcpStream as StdTcpStream;
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;
+use std::pin::Pin;
use std::hash::Hash;
static ID_COUNTER: AtomicU64 = AtomicU64::new(0);
+// We only need to select over multiple futures in one place, and taking on the full `tokio/macros`
+// dependency tree in order to do so (which has broken our MSRV before) is excessive. Instead, we
+// define a trivial two- and three- select macro with the specific types we need and just use that.
+
+pub(crate) enum SelectorOutput {
+ A(Option<()>), B(Option<()>), C(tokio::io::Result<usize>),
+}
+
+pub(crate) struct TwoSelector<
+ A: Future<Output=Option<()>> + Unpin, B: Future<Output=Option<()>> + Unpin
+> {
+ pub a: A,
+ pub b: B,
+}
+
+impl<
+ A: Future<Output=Option<()>> + Unpin, B: Future<Output=Option<()>> + Unpin
+> Future for TwoSelector<A, B> {
+ type Output = SelectorOutput;
+ fn poll(mut self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll<SelectorOutput> {
+ match Pin::new(&mut self.a).poll(ctx) {
+ Poll::Ready(res) => { return Poll::Ready(SelectorOutput::A(res)); },
+ Poll::Pending => {},
+ }
+ match Pin::new(&mut self.b).poll(ctx) {
+ Poll::Ready(res) => { return Poll::Ready(SelectorOutput::B(res)); },
+ Poll::Pending => {},
+ }
+ Poll::Pending
+ }
+}
+
+pub(crate) struct ThreeSelector<
+ A: Future<Output=Option<()>> + Unpin, B: Future<Output=Option<()>> + Unpin, C: Future<Output=tokio::io::Result<usize>> + Unpin
+> {
+ pub a: A,
+ pub b: B,
+ pub c: C,
+}
+
+impl<
+ A: Future<Output=Option<()>> + Unpin, B: Future<Output=Option<()>> + Unpin, C: Future<Output=tokio::io::Result<usize>> + Unpin
+> Future for ThreeSelector<A, B, C> {
+ type Output = SelectorOutput;
+ fn poll(mut self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll<SelectorOutput> {
+ match Pin::new(&mut self.a).poll(ctx) {
+ Poll::Ready(res) => { return Poll::Ready(SelectorOutput::A(res)); },
+ Poll::Pending => {},
+ }
+ match Pin::new(&mut self.b).poll(ctx) {
+ Poll::Ready(res) => { return Poll::Ready(SelectorOutput::B(res)); },
+ Poll::Pending => {},
+ }
+ match Pin::new(&mut self.c).poll(ctx) {
+ Poll::Ready(res) => { return Poll::Ready(SelectorOutput::C(res)); },
+ Poll::Pending => {},
+ }
+ Poll::Pending
+ }
+}
+
/// Connection contains all our internal state for a connection - we hold a reference to the
/// Connection object (in an Arc<Mutex<>>) in each SocketDescriptor we create as well as in the
/// read future (which is returned by schedule_read).
}
us_lock.read_paused
};
- tokio::select! {
- v = write_avail_receiver.recv() => {
+ // TODO: Drop the Box'ing of the futures once Rust has pin-on-stack support.
+ let select_result = if read_paused {
+ TwoSelector {
+ a: Box::pin(write_avail_receiver.recv()),
+ b: Box::pin(read_wake_receiver.recv()),
+ }.await
+ } else {
+ ThreeSelector {
+ a: Box::pin(write_avail_receiver.recv()),
+ b: Box::pin(read_wake_receiver.recv()),
+ c: Box::pin(reader.read(&mut buf)),
+ }.await
+ };
+ match select_result {
+ SelectorOutput::A(v) => {
assert!(v.is_some()); // We can't have dropped the sending end, its in the us Arc!
if peer_manager.as_ref().write_buffer_space_avail(&mut our_descriptor).is_err() {
break Disconnect::CloseConnection;
}
},
- _ = read_wake_receiver.recv() => {},
- read = reader.read(&mut buf), if !read_paused => match read {
- Ok(0) => break Disconnect::PeerDisconnected,
- Ok(len) => {
- let read_res = peer_manager.as_ref().read_event(&mut our_descriptor, &buf[0..len]);
- let mut us_lock = us.lock().unwrap();
- match read_res {
- Ok(pause_read) => {
- if pause_read {
- us_lock.read_paused = true;
- }
- },
- Err(_) => break Disconnect::CloseConnection,
- }
- },
- Err(_) => break Disconnect::PeerDisconnected,
+ SelectorOutput::B(_) => {},
+ SelectorOutput::C(read) => {
+ match read {
+ Ok(0) => break Disconnect::PeerDisconnected,
+ Ok(len) => {
+ let read_res = peer_manager.as_ref().read_event(&mut our_descriptor, &buf[0..len]);
+ let mut us_lock = us.lock().unwrap();
+ match read_res {
+ Ok(pause_read) => {
+ if pause_read {
+ us_lock.read_paused = true;
+ }
+ },
+ Err(_) => break Disconnect::CloseConnection,
+ }
+ },
+ Err(_) => break Disconnect::PeerDisconnected,
+ }
},
}
let _ = event_waker.try_send(());
[package]
name = "lightning-persister"
-version = "0.0.115"
+version = "0.0.116-rc1"
authors = ["Valentine Wallace", "Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
[dependencies]
bitcoin = "0.29.0"
-lightning = { version = "0.0.115", path = "../lightning" }
+lightning = { version = "0.0.116-rc1", path = "../lightning" }
libc = "0.2"
[target.'cfg(windows)'.dependencies]
criterion = { version = "0.4", optional = true, default-features = false }
[dev-dependencies]
-lightning = { version = "0.0.115", path = "../lightning", features = ["_test_utils"] }
+lightning = { version = "0.0.116-rc1", path = "../lightning", features = ["_test_utils"] }
[package]
name = "lightning-rapid-gossip-sync"
-version = "0.0.115"
+version = "0.0.116-rc1"
authors = ["Arik Sosman <git@arik.io>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
std = ["lightning/std"]
[dependencies]
-lightning = { version = "0.0.115", path = "../lightning", default-features = false }
+lightning = { version = "0.0.116-rc1", path = "../lightning", default-features = false }
bitcoin = { version = "0.29.0", default-features = false }
[target.'cfg(ldk_bench)'.dependencies]
criterion = { version = "0.4", optional = true, default-features = false }
[dev-dependencies]
-lightning = { version = "0.0.115", path = "../lightning", features = ["_test_utils"] }
+lightning = { version = "0.0.116-rc1", path = "../lightning", features = ["_test_utils"] }
[package]
name = "lightning-transaction-sync"
-version = "0.0.115"
+version = "0.0.116-rc1"
authors = ["Elias Rohrer"]
license = "MIT OR Apache-2.0"
repository = "http://github.com/lightningdevkit/rust-lightning"
async-interface = []
[dependencies]
-lightning = { version = "0.0.115", path = "../lightning", default-features = false }
+lightning = { version = "0.0.116-rc1", path = "../lightning", default-features = false }
bitcoin = { version = "0.29.0", default-features = false }
bdk-macros = "0.6"
futures = { version = "0.3", optional = true }
reqwest = { version = "0.11", optional = true, default-features = false, features = ["json"] }
[dev-dependencies]
-lightning = { version = "0.0.115", path = "../lightning", features = ["std"] }
+lightning = { version = "0.0.116-rc1", path = "../lightning", features = ["std"] }
electrsd = { version = "0.22.0", features = ["legacy", "esplora_a33e97e1", "bitcoind_23_0"] }
electrum-client = "0.12.0"
tokio = { version = "1.14.0", features = ["full"] }
[package]
name = "lightning"
-version = "0.0.115"
+version = "0.0.116-rc1"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
//! disconnections, transaction broadcasting, and feerate information requests.
use core::{cmp, ops::Deref};
+use core::convert::TryInto;
use bitcoin::blockdata::transaction::Transaction;
+// TODO: Define typed abstraction over feerates to handle their conversions.
+pub(crate) fn compute_feerate_sat_per_1000_weight(fee_sat: u64, weight: u64) -> u32 {
+ (fee_sat * 1000 / weight).try_into().unwrap_or(u32::max_value())
+}
+pub(crate) const fn fee_for_weight(feerate_sat_per_1000_weight: u32, weight: u64) -> u64 {
+ ((feerate_sat_per_1000_weight as u64 * weight) + 1000 - 1) / 1000
+}
+
/// An interface to send a transaction to the Bitcoin network.
pub trait BroadcasterInterface {
/// Sends a list of transactions out to (hopefully) be mined.
fn broadcast_transactions(&self, txs: &[&Transaction]);
}
-/// An enum that represents the speed at which we want a transaction to confirm used for feerate
+/// An enum that represents the priority at which we want a transaction to confirm used for feerate
/// estimation.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum ConfirmationTarget {
- /// We are happy with this transaction confirming slowly when feerate drops some.
+ /// We'd like a transaction to confirm in the future, but don't want to commit most of the fees
+ /// required to do so yet. The remaining fees will come via a Child-Pays-For-Parent (CPFP) fee
+ /// bump of the transaction.
+ ///
+ /// The feerate returned should be the absolute minimum feerate required to enter most node
+ /// mempools across the network. Note that if you are not able to obtain this feerate estimate,
+ /// you should likely use the furthest-out estimate allowed by your fee estimator.
+ MempoolMinimum,
+ /// We are happy with a transaction confirming slowly, at least within a day or so worth of
+ /// blocks.
Background,
- /// We'd like this transaction to confirm without major delay, but 12-18 blocks is fine.
+ /// We'd like a transaction to confirm without major delayed, i.e., within the next 12-24 blocks.
Normal,
- /// We'd like this transaction to confirm in the next few blocks.
+ /// We'd like a transaction to confirm in the next few blocks.
HighPriority,
}
/// A trait which should be implemented to provide feerate information on a number of time
/// horizons.
///
+/// If access to a local mempool is not feasible, feerate estimates should be fetched from a set of
+/// third-parties hosting them. Note that this enables them to affect the propagation of your
+/// pre-signed transactions at any time and therefore endangers the safety of channels funds. It
+/// should be considered carefully as a deployment.
+///
/// Note that all of the functions implemented here *must* be reentrant-safe (obviously - they're
/// called from inside the library in response to chain events, P2P events, or timer events).
pub trait FeeEstimator {
self.event_notifier.notify();
}
- #[cfg(any(test, fuzzing, feature = "_test_utils"))]
+ #[cfg(any(test, feature = "_test_utils"))]
pub fn get_and_clear_pending_events(&self) -> Vec<events::Event> {
use crate::events::EventsProvider;
let events = core::cell::RefCell::new(Vec::new());
pub async fn process_pending_events_async<Future: core::future::Future, H: Fn(Event) -> Future>(
&self, handler: H
) {
- let mut pending_events = Vec::new();
- for monitor_state in self.monitors.read().unwrap().values() {
- pending_events.append(&mut monitor_state.monitor.get_and_clear_pending_events());
- }
- for event in pending_events {
- handler(event).await;
+ // Sadly we can't hold the monitors read lock through an async call. Thus we have to do a
+ // crazy dance to process a monitor's events then only remove them once we've done so.
+ let mons_to_process = self.monitors.read().unwrap().keys().cloned().collect::<Vec<_>>();
+ for funding_txo in mons_to_process {
+ let mut ev;
+ super::channelmonitor::process_events_body!(
+ self.monitors.read().unwrap().get(&funding_txo).map(|m| &m.monitor), ev, handler(ev).await);
}
}
L::Target: Logger,
P::Target: Persist<ChannelSigner>,
{
- #[cfg(not(anchors))]
- /// Processes [`SpendableOutputs`] events produced from each [`ChannelMonitor`] upon maturity.
- ///
- /// An [`EventHandler`] may safely call back to the provider, though this shouldn't be needed in
- /// order to handle these events.
- ///
- /// [`SpendableOutputs`]: events::Event::SpendableOutputs
- fn process_pending_events<H: Deref>(&self, handler: H) where H::Target: EventHandler {
- let mut pending_events = Vec::new();
- for monitor_state in self.monitors.read().unwrap().values() {
- pending_events.append(&mut monitor_state.monitor.get_and_clear_pending_events());
- }
- for event in pending_events {
- handler.handle_event(event);
- }
- }
- #[cfg(anchors)]
/// Processes [`SpendableOutputs`] events produced from each [`ChannelMonitor`] upon maturity.
///
/// For channels featuring anchor outputs, this method will also process [`BumpTransaction`]
/// events produced from each [`ChannelMonitor`] while there is a balance to claim onchain
/// within each channel. As the confirmation of a commitment transaction may be critical to the
- /// safety of funds, this method must be invoked frequently, ideally once for every chain tip
- /// update (block connected or disconnected).
+ /// safety of funds, we recommend invoking this every 30 seconds, or lower if running in an
+ /// environment with spotty connections, like on mobile.
///
/// An [`EventHandler`] may safely call back to the provider, though this shouldn't be needed in
/// order to handle these events.
/// [`SpendableOutputs`]: events::Event::SpendableOutputs
/// [`BumpTransaction`]: events::Event::BumpTransaction
fn process_pending_events<H: Deref>(&self, handler: H) where H::Target: EventHandler {
- let mut pending_events = Vec::new();
for monitor_state in self.monitors.read().unwrap().values() {
- pending_events.append(&mut monitor_state.monitor.get_and_clear_pending_events());
- }
- for event in pending_events {
- handler.handle_event(event);
+ monitor_state.monitor.process_pending_events(&handler);
}
}
}
use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator, LowerBoundedFeeEstimator};
use crate::chain::transaction::{OutPoint, TransactionData};
use crate::sign::{SpendableOutputDescriptor, StaticPaymentOutputDescriptor, DelayedPaymentOutputDescriptor, WriteableEcdsaChannelSigner, SignerProvider, EntropySource};
-#[cfg(anchors)]
-use crate::chain::onchaintx::ClaimEvent;
-use crate::chain::onchaintx::OnchainTxHandler;
+use crate::chain::onchaintx::{ClaimEvent, OnchainTxHandler};
use crate::chain::package::{CounterpartyOfferedHTLCOutput, CounterpartyReceivedHTLCOutput, HolderFundingOutput, HolderHTLCOutput, PackageSolvingData, PackageTemplate, RevokedOutput, RevokedHTLCOutput};
use crate::chain::Filter;
use crate::util::logger::Logger;
use crate::util::ser::{Readable, ReadableArgs, RequiredWrapper, MaybeReadable, UpgradableRequired, Writer, Writeable, U48};
use crate::util::byte_utils;
-use crate::events::Event;
-#[cfg(anchors)]
-use crate::events::bump_transaction::{AnchorDescriptor, HTLCDescriptor, BumpTransactionEvent};
+use crate::events::{Event, EventHandler};
+use crate::events::bump_transaction::{ChannelDerivationParameters, AnchorDescriptor, HTLCDescriptor, BumpTransactionEvent};
use crate::prelude::*;
use core::{cmp, mem};
(8, delayed_payment_key, required),
(10, per_commitment_point, required),
(12, feerate_per_kw, required),
- (14, htlc_outputs, vec_type)
+ (14, htlc_outputs, required_vec)
});
-#[cfg(anchors)]
impl HolderSignedTx {
fn non_dust_htlcs(&self) -> Vec<HTLCOutputInCommitment> {
self.htlc_outputs.iter().filter_map(|(htlc, _, _)| {
impl_writeable_tlv_based_enum_upgradable!(ChannelMonitorUpdateStep,
(0, LatestHolderCommitmentTXInfo) => {
(0, commitment_tx, required),
- (1, claimed_htlcs, vec_type),
- (2, htlc_outputs, vec_type),
+ (1, claimed_htlcs, optional_vec),
+ (2, htlc_outputs, required_vec),
(4, nondust_htlc_sources, optional_vec),
},
(1, LatestCounterpartyCommitmentTXInfo) => {
(0, commitment_txid, required),
(2, commitment_number, required),
(4, their_per_commitment_point, required),
- (6, htlc_outputs, vec_type),
+ (6, htlc_outputs, required_vec),
},
(2, PaymentPreimage) => {
(0, payment_preimage, required),
/// You MUST ensure that no ChannelMonitors for a given channel anywhere contain out-of-date
/// information and are actively monitoring the chain.
///
-/// Pending Events or updated HTLCs which have not yet been read out by
-/// get_and_clear_pending_monitor_events or get_and_clear_pending_events are serialized to disk and
-/// reloaded at deserialize-time. Thus, you must ensure that, when handling events, all events
-/// gotten are fully handled before re-serializing the new state.
-///
/// Note that the deserializer is only implemented for (BlockHash, ChannelMonitor), which
/// tells you the last block hash which was block_connect()ed. You MUST rescan any blocks along
/// the "reorg path" (ie disconnecting blocks until you find a common ancestor from both the
#[cfg(test)]
pub(crate) inner: Mutex<ChannelMonitorImpl<Signer>>,
#[cfg(not(test))]
- inner: Mutex<ChannelMonitorImpl<Signer>>,
+ pub(super) inner: Mutex<ChannelMonitorImpl<Signer>>,
}
#[derive(PartialEq)]
// we further MUST NOT generate events during block/transaction-disconnection.
pending_monitor_events: Vec<MonitorEvent>,
- pending_events: Vec<Event>,
+ pub(super) pending_events: Vec<Event>,
+ pub(super) is_processing_pending_events: bool,
// Used to track on-chain events (i.e., transactions part of channels confirmed on chain) on
// which to take actions once they reach enough confirmations. Each entry includes the
write_tlv_fields!(writer, {
(1, self.funding_spend_confirmed, option),
- (3, self.htlcs_resolved_on_chain, vec_type),
- (5, self.pending_monitor_events, vec_type),
+ (3, self.htlcs_resolved_on_chain, required_vec),
+ (5, self.pending_monitor_events, required_vec),
(7, self.funding_spend_seen, required),
(9, self.counterparty_node_id, option),
(11, self.confirmed_commitment_tx_counterparty_output, option),
- (13, self.spendable_txids_confirmed, vec_type),
+ (13, self.spendable_txids_confirmed, required_vec),
(15, self.counterparty_fulfilled_htlcs, required),
});
}
}
+macro_rules! _process_events_body {
+ ($self_opt: expr, $event_to_handle: expr, $handle_event: expr) => {
+ loop {
+ let (pending_events, repeated_events);
+ if let Some(us) = $self_opt {
+ let mut inner = us.inner.lock().unwrap();
+ if inner.is_processing_pending_events {
+ break;
+ }
+ inner.is_processing_pending_events = true;
+
+ pending_events = inner.pending_events.clone();
+ repeated_events = inner.get_repeated_events();
+ } else { break; }
+ let num_events = pending_events.len();
+
+ for event in pending_events.into_iter().chain(repeated_events.into_iter()) {
+ $event_to_handle = event;
+ $handle_event;
+ }
+
+ if let Some(us) = $self_opt {
+ let mut inner = us.inner.lock().unwrap();
+ inner.pending_events.drain(..num_events);
+ inner.is_processing_pending_events = false;
+ if !inner.pending_events.is_empty() {
+ // If there's more events to process, go ahead and do so.
+ continue;
+ }
+ }
+ break;
+ }
+ }
+}
+pub(super) use _process_events_body as process_events_body;
+
impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
/// For lockorder enforcement purposes, we need to have a single site which constructs the
/// `inner` mutex, otherwise cases where we lock two monitors at the same time (eg in our
payment_preimages: HashMap::new(),
pending_monitor_events: Vec::new(),
pending_events: Vec::new(),
+ is_processing_pending_events: false,
onchain_events_awaiting_threshold_conf: Vec::new(),
outputs_to_watch,
self.inner.lock().unwrap().get_and_clear_pending_monitor_events()
}
- /// Gets the list of pending events which were generated by previous actions, clearing the list
- /// in the process.
+ /// Processes [`SpendableOutputs`] events produced from each [`ChannelMonitor`] upon maturity.
+ ///
+ /// For channels featuring anchor outputs, this method will also process [`BumpTransaction`]
+ /// events produced from each [`ChannelMonitor`] while there is a balance to claim onchain
+ /// within each channel. As the confirmation of a commitment transaction may be critical to the
+ /// safety of funds, we recommend invoking this every 30 seconds, or lower if running in an
+ /// environment with spotty connections, like on mobile.
///
- /// This is called by the [`EventsProvider::process_pending_events`] implementation for
- /// [`ChainMonitor`].
+ /// An [`EventHandler`] may safely call back to the provider, though this shouldn't be needed in
+ /// order to handle these events.
///
- /// [`EventsProvider::process_pending_events`]: crate::events::EventsProvider::process_pending_events
- /// [`ChainMonitor`]: crate::chain::chainmonitor::ChainMonitor
+ /// [`SpendableOutputs`]: crate::events::Event::SpendableOutputs
+ /// [`BumpTransaction`]: crate::events::Event::BumpTransaction
+ pub fn process_pending_events<H: Deref>(&self, handler: &H) where H::Target: EventHandler {
+ let mut ev;
+ process_events_body!(Some(self), ev, handler.handle_event(ev));
+ }
+
+ /// Processes any events asynchronously.
+ ///
+ /// See [`Self::process_pending_events`] for more information.
+ pub async fn process_pending_events_async<Future: core::future::Future, H: Fn(Event) -> Future>(
+ &self, handler: &H
+ ) {
+ let mut ev;
+ process_events_body!(Some(self), ev, { handler(ev).await });
+ }
+
+ #[cfg(test)]
pub fn get_and_clear_pending_events(&self) -> Vec<Event> {
- self.inner.lock().unwrap().get_and_clear_pending_events()
+ let mut ret = Vec::new();
+ let mut lck = self.inner.lock().unwrap();
+ mem::swap(&mut ret, &mut lck.pending_events);
+ ret.append(&mut lck.get_repeated_events());
+ ret
}
pub(crate) fn get_min_seen_secret(&self) -> u64 {
debug_assert!(htlc_input_idx_opt.is_some());
BitcoinOutPoint::new(*txid, htlc_input_idx_opt.unwrap_or(0))
} else {
- debug_assert!(!self.onchain_tx_handler.opt_anchors());
+ debug_assert!(!self.onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx());
BitcoinOutPoint::new(*txid, 0)
}
} else {
// If the channel supports anchor outputs, we'll need to emit an external
// event to be consumed such that a child transaction is broadcast with a
// high enough feerate for the parent commitment transaction to confirm.
- if self.onchain_tx_handler.opt_anchors() {
+ if self.onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx() {
let funding_output = HolderFundingOutput::build(
self.funding_redeemscript.clone(), self.channel_value_satoshis,
- self.onchain_tx_handler.opt_anchors(),
+ self.onchain_tx_handler.channel_type_features().clone(),
);
let best_block_height = self.best_block.height();
let commitment_package = PackageTemplate::build_package(
ret
}
- pub fn get_and_clear_pending_events(&mut self) -> Vec<Event> {
- let mut ret = Vec::new();
- mem::swap(&mut ret, &mut self.pending_events);
- #[cfg(anchors)]
- for claim_event in self.onchain_tx_handler.get_and_clear_pending_claim_events().drain(..) {
+ /// Gets the set of events that are repeated regularly (e.g. those which RBF bump
+ /// transactions). We're okay if we lose these on restart as they'll be regenerated for us at
+ /// some regular interval via [`ChannelMonitor::rebroadcast_pending_claims`].
+ pub(super) fn get_repeated_events(&mut self) -> Vec<Event> {
+ let pending_claim_events = self.onchain_tx_handler.get_and_clear_pending_claim_events();
+ let mut ret = Vec::with_capacity(pending_claim_events.len());
+ for (claim_id, claim_event) in pending_claim_events {
match claim_event {
ClaimEvent::BumpCommitment {
package_target_feerate_sat_per_1000_weight, commitment_tx, anchor_output_idx,
let commitment_tx_fee_satoshis = self.channel_value_satoshis -
commitment_tx.output.iter().fold(0u64, |sum, output| sum + output.value);
ret.push(Event::BumpTransaction(BumpTransactionEvent::ChannelClose {
+ claim_id,
package_target_feerate_sat_per_1000_weight,
commitment_tx,
commitment_tx_fee_satoshis,
anchor_descriptor: AnchorDescriptor {
- channel_keys_id: self.channel_keys_id,
- channel_value_satoshis: self.channel_value_satoshis,
+ channel_derivation_parameters: ChannelDerivationParameters {
+ keys_id: self.channel_keys_id,
+ value_satoshis: self.channel_value_satoshis,
+ transaction_parameters: self.onchain_tx_handler.channel_transaction_parameters.clone(),
+ },
outpoint: BitcoinOutPoint {
txid: commitment_txid,
vout: anchor_output_idx,
let mut htlc_descriptors = Vec::with_capacity(htlcs.len());
for htlc in htlcs {
htlc_descriptors.push(HTLCDescriptor {
- channel_keys_id: self.channel_keys_id,
- channel_value_satoshis: self.channel_value_satoshis,
- channel_parameters: self.onchain_tx_handler.channel_transaction_parameters.clone(),
+ channel_derivation_parameters: ChannelDerivationParameters {
+ keys_id: self.channel_keys_id,
+ value_satoshis: self.channel_value_satoshis,
+ transaction_parameters: self.onchain_tx_handler.channel_transaction_parameters.clone(),
+ },
commitment_txid: htlc.commitment_txid,
per_commitment_number: htlc.per_commitment_number,
+ per_commitment_point: self.onchain_tx_handler.signer.get_per_commitment_point(
+ htlc.per_commitment_number, &self.onchain_tx_handler.secp_ctx,
+ ),
htlc: htlc.htlc,
preimage: htlc.preimage,
counterparty_sig: htlc.counterparty_sig,
});
}
ret.push(Event::BumpTransaction(BumpTransactionEvent::HTLCResolution {
+ claim_id,
target_feerate_sat_per_1000_weight,
htlc_descriptors,
tx_lock_time,
// First, process non-htlc outputs (to_holder & to_counterparty)
for (idx, outp) in tx.output.iter().enumerate() {
if outp.script_pubkey == revokeable_p2wsh {
- let revk_outp = RevokedOutput::build(per_commitment_point, self.counterparty_commitment_params.counterparty_delayed_payment_base_key, self.counterparty_commitment_params.counterparty_htlc_base_key, per_commitment_key, outp.value, self.counterparty_commitment_params.on_counterparty_tx_csv, self.onchain_tx_handler.opt_anchors());
+ let revk_outp = RevokedOutput::build(per_commitment_point, self.counterparty_commitment_params.counterparty_delayed_payment_base_key, self.counterparty_commitment_params.counterparty_htlc_base_key, per_commitment_key, outp.value, self.counterparty_commitment_params.on_counterparty_tx_csv, self.onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx());
let justice_package = PackageTemplate::build_package(commitment_txid, idx as u32, PackageSolvingData::RevokedOutput(revk_outp), height + self.counterparty_commitment_params.on_counterparty_tx_csv as u32, height);
claimable_outpoints.push(justice_package);
to_counterparty_output_info =
return (claimable_outpoints, (commitment_txid, watch_outputs),
to_counterparty_output_info);
}
- let revk_htlc_outp = RevokedHTLCOutput::build(per_commitment_point, self.counterparty_commitment_params.counterparty_delayed_payment_base_key, self.counterparty_commitment_params.counterparty_htlc_base_key, per_commitment_key, htlc.amount_msat / 1000, htlc.clone(), self.onchain_tx_handler.channel_transaction_parameters.opt_anchors.is_some());
+ let revk_htlc_outp = RevokedHTLCOutput::build(per_commitment_point, self.counterparty_commitment_params.counterparty_delayed_payment_base_key, self.counterparty_commitment_params.counterparty_htlc_base_key, per_commitment_key, htlc.amount_msat / 1000, htlc.clone(), &self.onchain_tx_handler.channel_transaction_parameters.channel_type_features);
let justice_package = PackageTemplate::build_package(commitment_txid, transaction_output_index, PackageSolvingData::RevokedHTLCOutput(revk_htlc_outp), htlc.cltv_expiry, height);
claimable_outpoints.push(justice_package);
}
CounterpartyOfferedHTLCOutput::build(*per_commitment_point,
self.counterparty_commitment_params.counterparty_delayed_payment_base_key,
self.counterparty_commitment_params.counterparty_htlc_base_key,
- preimage.unwrap(), htlc.clone(), self.onchain_tx_handler.opt_anchors()))
+ preimage.unwrap(), htlc.clone(), self.onchain_tx_handler.channel_type_features().clone()))
} else {
PackageSolvingData::CounterpartyReceivedHTLCOutput(
CounterpartyReceivedHTLCOutput::build(*per_commitment_point,
self.counterparty_commitment_params.counterparty_delayed_payment_base_key,
self.counterparty_commitment_params.counterparty_htlc_base_key,
- htlc.clone(), self.onchain_tx_handler.opt_anchors()))
+ htlc.clone(), self.onchain_tx_handler.channel_type_features().clone()))
};
let counterparty_package = PackageTemplate::build_package(commitment_txid, transaction_output_index, counterparty_htlc_outp, htlc.cltv_expiry, 0);
claimable_outpoints.push(counterparty_package);
if let Some(transaction_output_index) = htlc.transaction_output_index {
let htlc_output = if htlc.offered {
let htlc_output = HolderHTLCOutput::build_offered(
- htlc.amount_msat, htlc.cltv_expiry, self.onchain_tx_handler.opt_anchors()
+ htlc.amount_msat, htlc.cltv_expiry, self.onchain_tx_handler.channel_type_features().clone()
);
htlc_output
} else {
continue;
};
let htlc_output = HolderHTLCOutput::build_accepted(
- payment_preimage, htlc.amount_msat, self.onchain_tx_handler.opt_anchors()
+ payment_preimage, htlc.amount_msat, self.onchain_tx_handler.channel_type_features().clone()
);
htlc_output
};
let mut holder_transactions = vec![commitment_tx];
// When anchor outputs are present, the HTLC transactions are only valid once the commitment
// transaction confirms.
- if self.onchain_tx_handler.opt_anchors() {
+ if self.onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx() {
return holder_transactions;
}
for htlc in self.current_holder_commitment_tx.htlc_outputs.iter() {
let mut holder_transactions = vec![commitment_tx];
// When anchor outputs are present, the HTLC transactions are only final once the commitment
// transaction confirms due to the CSV 1 encumberance.
- if self.onchain_tx_handler.opt_anchors() {
+ if self.onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx() {
return holder_transactions;
}
for htlc in self.current_holder_commitment_tx.htlc_outputs.iter() {
let should_broadcast = self.should_broadcast_holder_commitment_txn(logger);
if should_broadcast {
- let funding_outp = HolderFundingOutput::build(self.funding_redeemscript.clone(), self.channel_value_satoshis, self.onchain_tx_handler.opt_anchors());
+ let funding_outp = HolderFundingOutput::build(self.funding_redeemscript.clone(), self.channel_value_satoshis, self.onchain_tx_handler.channel_type_features().clone());
let commitment_package = PackageTemplate::build_package(self.funding_info.0.txid.clone(), self.funding_info.0.index as u32, PackageSolvingData::HolderFundingOutput(funding_outp), self.best_block.height(), self.best_block.height());
claimable_outpoints.push(commitment_package);
self.pending_monitor_events.push(MonitorEvent::CommitmentTxConfirmed(self.funding_info.0));
// We can't broadcast our HTLC transactions while the commitment transaction is
// unconfirmed. We'll delay doing so until we detect the confirmed commitment in
// `transactions_confirmed`.
- if !self.onchain_tx_handler.opt_anchors() {
+ if !self.onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx() {
// Because we're broadcasting a commitment transaction, we should construct the package
// assuming it gets confirmed in the next block. Sadly, we have code which considers
// "not yet confirmed" things as discardable, so we cannot do that here.
let mut counterparty_fulfilled_htlcs = Some(HashMap::new());
read_tlv_fields!(reader, {
(1, funding_spend_confirmed, option),
- (3, htlcs_resolved_on_chain, vec_type),
- (5, pending_monitor_events, vec_type),
+ (3, htlcs_resolved_on_chain, optional_vec),
+ (5, pending_monitor_events, optional_vec),
(7, funding_spend_seen, option),
(9, counterparty_node_id, option),
(11, confirmed_commitment_tx_counterparty_output, option),
- (13, spendable_txids_confirmed, vec_type),
+ (13, spendable_txids_confirmed, optional_vec),
(15, counterparty_fulfilled_htlcs, option),
});
payment_preimages,
pending_monitor_events: pending_monitor_events.unwrap(),
pending_events,
+ is_processing_pending_events: false,
onchain_events_awaiting_threshold_conf,
outputs_to_watch,
use crate::sync::{Arc, Mutex};
use crate::io;
use bitcoin::{PackedLockTime, Sequence, Witness};
+ use crate::ln::features::ChannelTypeFeatures;
use crate::prelude::*;
fn do_test_funding_spend_refuses_updates(use_local_txn: bool) {
selected_contest_delay: 67,
}),
funding_outpoint: Some(funding_outpoint),
- opt_anchors: None,
- opt_non_zero_fee_anchors: None,
+ channel_type_features: ChannelTypeFeatures::only_static_remote_key()
};
// Prune with one old state and a holder commitment tx holding a few overlaps with the
// old state.
let txid = Txid::from_hex("56944c5d3f98413ef45cf54545538103cc9f298e0575820ad3591376e2e0f65d").unwrap();
// Justice tx with 1 to_holder, 2 revoked offered HTLCs, 1 revoked received HTLCs
- for &opt_anchors in [false, true].iter() {
+ for channel_type_features in [ChannelTypeFeatures::only_static_remote_key(), ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies()].iter() {
let mut claim_tx = Transaction { version: 0, lock_time: PackedLockTime::ZERO, input: Vec::new(), output: Vec::new() };
let mut sum_actual_sigs = 0;
for i in 0..4 {
value: 0,
});
let base_weight = claim_tx.weight();
- let inputs_weight = vec![WEIGHT_REVOKED_OUTPUT, weight_revoked_offered_htlc(opt_anchors), weight_revoked_offered_htlc(opt_anchors), weight_revoked_received_htlc(opt_anchors)];
+ let inputs_weight = vec![WEIGHT_REVOKED_OUTPUT, weight_revoked_offered_htlc(channel_type_features), weight_revoked_offered_htlc(channel_type_features), weight_revoked_received_htlc(channel_type_features)];
let mut inputs_total_weight = 2; // count segwit flags
{
let mut sighash_parts = sighash::SighashCache::new(&mut claim_tx);
for (idx, inp) in inputs_weight.iter().enumerate() {
- sign_input!(sighash_parts, idx, 0, inp, sum_actual_sigs, opt_anchors);
+ sign_input!(sighash_parts, idx, 0, inp, sum_actual_sigs, channel_type_features);
inputs_total_weight += inp;
}
}
}
// Claim tx with 1 offered HTLCs, 3 received HTLCs
- for &opt_anchors in [false, true].iter() {
+ for channel_type_features in [ChannelTypeFeatures::only_static_remote_key(), ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies()].iter() {
let mut claim_tx = Transaction { version: 0, lock_time: PackedLockTime::ZERO, input: Vec::new(), output: Vec::new() };
let mut sum_actual_sigs = 0;
for i in 0..4 {
value: 0,
});
let base_weight = claim_tx.weight();
- let inputs_weight = vec![weight_offered_htlc(opt_anchors), weight_received_htlc(opt_anchors), weight_received_htlc(opt_anchors), weight_received_htlc(opt_anchors)];
+ let inputs_weight = vec![weight_offered_htlc(channel_type_features), weight_received_htlc(channel_type_features), weight_received_htlc(channel_type_features), weight_received_htlc(channel_type_features)];
let mut inputs_total_weight = 2; // count segwit flags
{
let mut sighash_parts = sighash::SighashCache::new(&mut claim_tx);
for (idx, inp) in inputs_weight.iter().enumerate() {
- sign_input!(sighash_parts, idx, 0, inp, sum_actual_sigs, opt_anchors);
+ sign_input!(sighash_parts, idx, 0, inp, sum_actual_sigs, channel_type_features);
inputs_total_weight += inp;
}
}
}
// Justice tx with 1 revoked HTLC-Success tx output
- for &opt_anchors in [false, true].iter() {
+ for channel_type_features in [ChannelTypeFeatures::only_static_remote_key(), ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies()].iter() {
let mut claim_tx = Transaction { version: 0, lock_time: PackedLockTime::ZERO, input: Vec::new(), output: Vec::new() };
let mut sum_actual_sigs = 0;
claim_tx.input.push(TxIn {
{
let mut sighash_parts = sighash::SighashCache::new(&mut claim_tx);
for (idx, inp) in inputs_weight.iter().enumerate() {
- sign_input!(sighash_parts, idx, 0, inp, sum_actual_sigs, opt_anchors);
+ sign_input!(sighash_parts, idx, 0, inp, sum_actual_sigs, channel_type_features);
inputs_total_weight += inp;
}
}
self.1.block_disconnected(header, height);
}
}
+
+/// A unique identifier to track each pending output claim within a [`ChannelMonitor`].
+///
+/// This is not exported to bindings users as we just use [u8; 32] directly.
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
+pub struct ClaimId(pub [u8; 32]);
//! OnchainTxHandler objects are fully-part of ChannelMonitor and encapsulates all
//! building, tracking, bumping and notifications functions.
-#[cfg(anchors)]
use bitcoin::PackedLockTime;
use bitcoin::blockdata::transaction::Transaction;
use bitcoin::blockdata::transaction::OutPoint as BitcoinOutPoint;
use bitcoin::blockdata::script::Script;
-
+use bitcoin::hashes::{Hash, HashEngine};
+use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hash_types::{Txid, BlockHash};
-
use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature};
use bitcoin::secp256k1;
+use crate::chain::chaininterface::compute_feerate_sat_per_1000_weight;
use crate::sign::{ChannelSigner, EntropySource, SignerProvider};
use crate::ln::msgs::DecodeError;
use crate::ln::PaymentPreimage;
-#[cfg(anchors)]
-use crate::ln::chan_utils::{self, HTLCOutputInCommitment};
-use crate::ln::chan_utils::{ChannelTransactionParameters, HolderCommitmentTransaction};
-#[cfg(anchors)]
-use crate::chain::chaininterface::ConfirmationTarget;
-use crate::chain::chaininterface::{FeeEstimator, BroadcasterInterface, LowerBoundedFeeEstimator};
+use crate::ln::chan_utils::{self, ChannelTransactionParameters, HTLCOutputInCommitment, HolderCommitmentTransaction};
+use crate::chain::ClaimId;
+use crate::chain::chaininterface::{ConfirmationTarget, FeeEstimator, BroadcasterInterface, LowerBoundedFeeEstimator};
use crate::chain::channelmonitor::{ANTI_REORG_DELAY, CLTV_SHARED_CLAIM_BUFFER};
use crate::sign::WriteableEcdsaChannelSigner;
-#[cfg(anchors)]
-use crate::chain::package::PackageSolvingData;
-use crate::chain::package::PackageTemplate;
+use crate::chain::package::{PackageSolvingData, PackageTemplate};
use crate::util::logger::Logger;
use crate::util::ser::{Readable, ReadableArgs, MaybeReadable, UpgradableRequired, Writer, Writeable, VecWriter};
use core::cmp;
use core::ops::Deref;
use core::mem::replace;
-#[cfg(anchors)]
use core::mem::swap;
-use bitcoin::hashes::Hash;
+use crate::ln::features::ChannelTypeFeatures;
const MAX_ALLOC_SIZE: usize = 64*1024;
/// transaction has met [`ANTI_REORG_DELAY`] confirmations, we consider it final and remove the
/// pending request.
Claim {
- package_id: PackageID,
+ claim_id: ClaimId,
},
/// The counterparty has claimed an outpoint from one of our pending requests through a
/// different transaction than ours. If our transaction was attempting to claim multiple
impl_writeable_tlv_based_enum_upgradable!(OnchainEvent,
(0, Claim) => {
- (0, package_id, required),
+ (0, claim_id, required),
},
(1, ContentiousOutpoint) => {
(0, package, required),
}
}
-#[cfg(anchors)]
/// The claim commonly referred to as the pre-signed second-stage HTLC transaction.
pub(crate) struct ExternalHTLCClaim {
pub(crate) commitment_txid: Txid,
// Represents the different types of claims for which events are yielded externally to satisfy said
// claims.
-#[cfg(anchors)]
pub(crate) enum ClaimEvent {
/// Event yielded to signal that the commitment transaction fee must be bumped to claim any
/// encumbered funds and proceed to HTLC resolution, if any HTLCs exist.
pub(crate) enum OnchainClaim {
/// A finalized transaction pending confirmation spending the output to claim.
Tx(Transaction),
- #[cfg(anchors)]
/// An event yielded externally to signal additional inputs must be added to a transaction
/// pending confirmation spending the output to claim.
Event(ClaimEvent),
}
-/// An internal identifier to track pending package claims within the `OnchainTxHandler`.
-type PackageID = [u8; 32];
-
/// OnchainTxHandler receives claiming requests, aggregates them if it's sound, broadcast and
/// do RBF bumping if possible.
pub struct OnchainTxHandler<ChannelSigner: WriteableEcdsaChannelSigner> {
// us and is immutable until all outpoint of the claimable set are post-anti-reorg-delay solved.
// Entry is cache of elements need to generate a bumped claiming transaction (see ClaimTxBumpMaterial)
#[cfg(test)] // Used in functional_test to verify sanitization
- pub(crate) pending_claim_requests: HashMap<PackageID, PackageTemplate>,
+ pub(crate) pending_claim_requests: HashMap<ClaimId, PackageTemplate>,
#[cfg(not(test))]
- pending_claim_requests: HashMap<PackageID, PackageTemplate>,
+ pending_claim_requests: HashMap<ClaimId, PackageTemplate>,
// Used to track external events that need to be forwarded to the `ChainMonitor`. This `Vec`
// essentially acts as an insertion-ordered `HashMap` – there should only ever be one occurrence
- // of a `PackageID`, which tracks its latest `ClaimEvent`, i.e., if a pending claim exists, and
+ // of a `ClaimId`, which tracks its latest `ClaimEvent`, i.e., if a pending claim exists, and
// a new block has been connected, resulting in a new claim, the previous will be replaced with
// the new.
//
// - A channel has been force closed by broadcasting the holder's latest commitment transaction
// - A block being connected/disconnected
// - Learning the preimage for an HTLC we can claim onchain
- #[cfg(anchors)]
- pending_claim_events: Vec<(PackageID, ClaimEvent)>,
+ pending_claim_events: Vec<(ClaimId, ClaimEvent)>,
// Used to link outpoints claimed in a connected block to a pending claim request. The keys
// represent the outpoints that our `ChannelMonitor` has detected we have keys/scripts to
// [`ANTI_REORG_DELAY`]. The initial confirmation block height is used to remove the entry if
// the block gets disconnected.
#[cfg(test)] // Used in functional_test to verify sanitization
- pub claimable_outpoints: HashMap<BitcoinOutPoint, (PackageID, u32)>,
+ pub claimable_outpoints: HashMap<BitcoinOutPoint, (ClaimId, u32)>,
#[cfg(not(test))]
- claimable_outpoints: HashMap<BitcoinOutPoint, (PackageID, u32)>,
+ claimable_outpoints: HashMap<BitcoinOutPoint, (ClaimId, u32)>,
locktimed_packages: BTreeMap<u32, Vec<PackageTemplate>>,
locktimed_packages,
pending_claim_requests,
onchain_events_awaiting_threshold_conf,
- #[cfg(anchors)]
pending_claim_events: Vec::new(),
secp_ctx,
})
claimable_outpoints: HashMap::new(),
locktimed_packages: BTreeMap::new(),
onchain_events_awaiting_threshold_conf: Vec::new(),
- #[cfg(anchors)]
pending_claim_events: Vec::new(),
secp_ctx,
}
self.holder_commitment.to_broadcaster_value_sat()
}
- #[cfg(anchors)]
- pub(crate) fn get_and_clear_pending_claim_events(&mut self) -> Vec<ClaimEvent> {
+ pub(crate) fn get_and_clear_pending_claim_events(&mut self) -> Vec<(ClaimId, ClaimEvent)> {
let mut events = Vec::new();
swap(&mut events, &mut self.pending_claim_events);
- events.into_iter().map(|(_, event)| event).collect()
+ events
}
/// Triggers rebroadcasts/fee-bumps of pending claims from a force-closed channel. This is
L::Target: Logger,
{
let mut bump_requests = Vec::with_capacity(self.pending_claim_requests.len());
- for (package_id, request) in self.pending_claim_requests.iter() {
+ for (claim_id, request) in self.pending_claim_requests.iter() {
let inputs = request.outpoints();
log_info!(logger, "Triggering rebroadcast/fee-bump for request with inputs {:?}", inputs);
- bump_requests.push((*package_id, request.clone()));
+ bump_requests.push((*claim_id, request.clone()));
}
- for (package_id, request) in bump_requests {
+ for (claim_id, request) in bump_requests {
self.generate_claim(current_height, &request, false /* force_feerate_bump */, fee_estimator, logger)
.map(|(_, new_feerate, claim)| {
let mut bumped_feerate = false;
- if let Some(mut_request) = self.pending_claim_requests.get_mut(&package_id) {
+ if let Some(mut_request) = self.pending_claim_requests.get_mut(&claim_id) {
bumped_feerate = request.previous_feerate() > new_feerate;
mut_request.set_feerate(new_feerate);
}
log_info!(logger, "{} onchain {}", log_start, log_tx!(tx));
broadcaster.broadcast_transactions(&[&tx]);
},
- #[cfg(anchors)]
OnchainClaim::Event(event) => {
let log_start = if bumped_feerate { "Yielding fee-bumped" } else { "Replaying" };
log_info!(logger, "{} onchain event to spend inputs {:?}", log_start,
#[cfg(debug_assertions)] {
debug_assert!(request.requires_external_funding());
let num_existing = self.pending_claim_events.iter()
- .filter(|entry| entry.0 == package_id).count();
+ .filter(|entry| entry.0 == claim_id).count();
assert!(num_existing == 0 || num_existing == 1);
}
- self.pending_claim_events.retain(|event| event.0 != package_id);
- self.pending_claim_events.push((package_id, event));
+ self.pending_claim_events.retain(|event| event.0 != claim_id);
+ self.pending_claim_events.push((claim_id, event));
}
}
});
// transaction is reorged out.
let mut all_inputs_have_confirmed_spend = true;
for outpoint in request_outpoints.iter() {
- if let Some((request_package_id, _)) = self.claimable_outpoints.get(*outpoint) {
+ if let Some((request_claim_id, _)) = self.claimable_outpoints.get(*outpoint) {
// We check for outpoint spends within claims individually rather than as a set
// since requests can have outpoints split off.
if !self.onchain_events_awaiting_threshold_conf.iter()
- .any(|event_entry| if let OnchainEvent::Claim { package_id } = event_entry.event {
- *request_package_id == package_id
+ .any(|event_entry| if let OnchainEvent::Claim { claim_id } = event_entry.event {
+ *request_claim_id == claim_id
} else {
// The onchain event is not a claim, keep seeking until we find one.
false
// didn't receive confirmation of it before, or not enough reorg-safe depth on top of it).
let new_timer = cached_request.get_height_timer(cur_height);
if cached_request.is_malleable() {
- #[cfg(anchors)]
- { // Attributes are not allowed on if expressions on our current MSRV of 1.41.
- if cached_request.requires_external_funding() {
- let target_feerate_sat_per_1000_weight = cached_request.compute_package_feerate(
- fee_estimator, ConfirmationTarget::HighPriority, force_feerate_bump
- );
- if let Some(htlcs) = cached_request.construct_malleable_package_with_external_funding(self) {
- return Some((
- new_timer,
- target_feerate_sat_per_1000_weight as u64,
- OnchainClaim::Event(ClaimEvent::BumpHTLC {
- target_feerate_sat_per_1000_weight,
- htlcs,
- tx_lock_time: PackedLockTime(cached_request.package_locktime(cur_height)),
- }),
- ));
- } else {
- return None;
- }
+ if cached_request.requires_external_funding() {
+ let target_feerate_sat_per_1000_weight = cached_request.compute_package_feerate(
+ fee_estimator, ConfirmationTarget::HighPriority, force_feerate_bump
+ );
+ if let Some(htlcs) = cached_request.construct_malleable_package_with_external_funding(self) {
+ return Some((
+ new_timer,
+ target_feerate_sat_per_1000_weight as u64,
+ OnchainClaim::Event(ClaimEvent::BumpHTLC {
+ target_feerate_sat_per_1000_weight,
+ htlcs,
+ tx_lock_time: PackedLockTime(cached_request.package_locktime(cur_height)),
+ }),
+ ));
+ } else {
+ return None;
}
}
// Untractable packages cannot have their fees bumped through Replace-By-Fee. Some
// packages may support fee bumping through Child-Pays-For-Parent, indicated by those
// which require external funding.
- #[cfg(not(anchors))]
- let inputs = cached_request.inputs();
- #[cfg(anchors)]
let mut inputs = cached_request.inputs();
debug_assert_eq!(inputs.len(), 1);
let tx = match cached_request.finalize_untractable_package(self, logger) {
if !cached_request.requires_external_funding() {
return Some((new_timer, 0, OnchainClaim::Tx(tx)));
}
- #[cfg(anchors)]
return inputs.find_map(|input| match input {
// Commitment inputs with anchors support are the only untractable inputs supported
// thus far that require external funding.
- PackageSolvingData::HolderFundingOutput(..) => {
+ PackageSolvingData::HolderFundingOutput(output) => {
debug_assert_eq!(tx.txid(), self.holder_commitment.trust().txid(),
"Holder commitment transaction mismatch");
+
+ let conf_target = ConfirmationTarget::HighPriority;
+ let package_target_feerate_sat_per_1000_weight = cached_request
+ .compute_package_feerate(fee_estimator, conf_target, force_feerate_bump);
+ if let Some(input_amount_sat) = output.funding_amount {
+ let fee_sat = input_amount_sat - tx.output.iter().map(|output| output.value).sum::<u64>();
+ let commitment_tx_feerate_sat_per_1000_weight =
+ compute_feerate_sat_per_1000_weight(fee_sat, tx.weight() as u64);
+ if commitment_tx_feerate_sat_per_1000_weight >= package_target_feerate_sat_per_1000_weight {
+ log_debug!(logger, "Pre-signed {} already has feerate {} sat/kW above required {} sat/kW",
+ log_tx!(tx), commitment_tx_feerate_sat_per_1000_weight,
+ package_target_feerate_sat_per_1000_weight);
+ return Some((new_timer, 0, OnchainClaim::Tx(tx.clone())));
+ }
+ }
+
// We'll locate an anchor output we can spend within the commitment transaction.
let funding_pubkey = &self.channel_transaction_parameters.holder_pubkeys.funding_pubkey;
match chan_utils::get_anchor_output(&tx, funding_pubkey) {
Some((idx, _)) => {
// TODO: Use a lower confirmation target when both our and the
// counterparty's latest commitment don't have any HTLCs present.
- let conf_target = ConfirmationTarget::HighPriority;
- let package_target_feerate_sat_per_1000_weight = cached_request
- .compute_package_feerate(fee_estimator, conf_target, force_feerate_bump);
Some((
new_timer,
package_target_feerate_sat_per_1000_weight as u64,
) {
req.set_timer(new_timer);
req.set_feerate(new_feerate);
- let package_id = match claim {
+ // Once a pending claim has an id assigned, it remains fixed until the claim is
+ // satisfied, regardless of whether the claim switches between different variants of
+ // `OnchainClaim`.
+ let claim_id = match claim {
OnchainClaim::Tx(tx) => {
log_info!(logger, "Broadcasting onchain {}", log_tx!(tx));
broadcaster.broadcast_transactions(&[&tx]);
- tx.txid().into_inner()
+ ClaimId(tx.txid().into_inner())
},
- #[cfg(anchors)]
OnchainClaim::Event(claim_event) => {
log_info!(logger, "Yielding onchain event to spend inputs {:?}", req.outpoints());
- let package_id = match claim_event {
- ClaimEvent::BumpCommitment { ref commitment_tx, .. } => commitment_tx.txid().into_inner(),
+ let claim_id = match claim_event {
+ ClaimEvent::BumpCommitment { ref commitment_tx, .. } =>
+ // For commitment claims, we can just use their txid as it should
+ // already be unique.
+ ClaimId(commitment_tx.txid().into_inner()),
ClaimEvent::BumpHTLC { ref htlcs, .. } => {
- // Use the same construction as a lightning channel id to generate
- // the package id for this request based on the first HTLC. It
- // doesn't matter what we use as long as it's unique per request.
- let mut package_id = [0; 32];
- package_id[..].copy_from_slice(&htlcs[0].commitment_txid[..]);
- let htlc_output_index = htlcs[0].htlc.transaction_output_index.unwrap();
- package_id[30] ^= ((htlc_output_index >> 8) & 0xff) as u8;
- package_id[31] ^= ((htlc_output_index >> 0) & 0xff) as u8;
- package_id
+ // For HTLC claims, commit to the entire set of HTLC outputs to
+ // claim, which will always be unique per request. Once a claim ID
+ // is generated, it is assigned and remains unchanged, even if the
+ // underlying set of HTLCs changes.
+ let mut engine = Sha256::engine();
+ for htlc in htlcs {
+ engine.input(&htlc.commitment_txid.into_inner());
+ engine.input(&htlc.htlc.transaction_output_index.unwrap().to_be_bytes());
+ }
+ ClaimId(Sha256::from_engine(engine).into_inner())
},
};
- debug_assert_eq!(self.pending_claim_events.iter().filter(|entry| entry.0 == package_id).count(), 0);
- self.pending_claim_events.push((package_id, claim_event));
- package_id
+ debug_assert!(self.pending_claim_requests.get(&claim_id).is_none());
+ debug_assert_eq!(self.pending_claim_events.iter().filter(|entry| entry.0 == claim_id).count(), 0);
+ self.pending_claim_events.push((claim_id, claim_event));
+ claim_id
},
};
+ debug_assert!(self.pending_claim_requests.get(&claim_id).is_none());
for k in req.outpoints() {
log_info!(logger, "Registering claiming request for {}:{}", k.txid, k.vout);
- self.claimable_outpoints.insert(k.clone(), (package_id, conf_height));
+ self.claimable_outpoints.insert(k.clone(), (claim_id, conf_height));
}
- self.pending_claim_requests.insert(package_id, req);
+ self.pending_claim_requests.insert(claim_id, req);
}
}
}
// Scan all input to verify is one of the outpoint spent is of interest for us
let mut claimed_outputs_material = Vec::new();
for inp in &tx.input {
- if let Some((package_id, _)) = self.claimable_outpoints.get(&inp.previous_output) {
+ if let Some((claim_id, _)) = self.claimable_outpoints.get(&inp.previous_output) {
// If outpoint has claim request pending on it...
- if let Some(request) = self.pending_claim_requests.get_mut(package_id) {
+ if let Some(request) = self.pending_claim_requests.get_mut(claim_id) {
//... we need to verify equality between transaction outpoints and claim request
// outpoints to know if transaction is the original claim or a bumped one issued
// by us.
txid: tx.txid(),
height: conf_height,
block_hash: Some(conf_hash),
- event: OnchainEvent::Claim { package_id: *package_id }
+ event: OnchainEvent::Claim { claim_id: *claim_id }
};
if !self.onchain_events_awaiting_threshold_conf.contains(&entry) {
self.onchain_events_awaiting_threshold_conf.push(entry);
}
//TODO: recompute soonest_timelock to avoid wasting a bit on fees
if at_least_one_drop {
- bump_candidates.insert(*package_id, request.clone());
+ bump_candidates.insert(*claim_id, request.clone());
// If we have any pending claim events for the request being updated
// that have yet to be consumed, we'll remove them since they will
// end up producing an invalid transaction by double spending
// input(s) that already have a confirmed spend. If such spend is
// reorged out of the chain, then we'll attempt to re-spend the
// inputs once we see it.
- #[cfg(anchors)] {
- #[cfg(debug_assertions)] {
- let existing = self.pending_claim_events.iter()
- .filter(|entry| entry.0 == *package_id).count();
- assert!(existing == 0 || existing == 1);
- }
- self.pending_claim_events.retain(|entry| entry.0 != *package_id);
+ #[cfg(debug_assertions)] {
+ let existing = self.pending_claim_events.iter()
+ .filter(|entry| entry.0 == *claim_id).count();
+ assert!(existing == 0 || existing == 1);
}
+ self.pending_claim_events.retain(|entry| entry.0 != *claim_id);
}
}
break; //No need to iterate further, either tx is our or their
for entry in onchain_events_awaiting_threshold_conf {
if entry.has_reached_confirmation_threshold(cur_height) {
match entry.event {
- OnchainEvent::Claim { package_id } => {
+ OnchainEvent::Claim { claim_id } => {
// We may remove a whole set of claim outpoints here, as these one may have
// been aggregated in a single tx and claimed so atomically
- if let Some(request) = self.pending_claim_requests.remove(&package_id) {
+ if let Some(request) = self.pending_claim_requests.remove(&claim_id) {
for outpoint in request.outpoints() {
log_debug!(logger, "Removing claim tracking for {} due to maturation of claim package {}.",
- outpoint, log_bytes!(package_id));
+ outpoint, log_bytes!(claim_id.0));
self.claimable_outpoints.remove(outpoint);
}
- #[cfg(anchors)] {
- #[cfg(debug_assertions)] {
- let num_existing = self.pending_claim_events.iter()
- .filter(|entry| entry.0 == package_id).count();
- assert!(num_existing == 0 || num_existing == 1);
- }
- self.pending_claim_events.retain(|(id, _)| *id != package_id);
+ #[cfg(debug_assertions)] {
+ let num_existing = self.pending_claim_events.iter()
+ .filter(|entry| entry.0 == claim_id).count();
+ assert!(num_existing == 0 || num_existing == 1);
}
+ self.pending_claim_events.retain(|(id, _)| *id != claim_id);
}
},
OnchainEvent::ContentiousOutpoint { package } => {
}
// Check if any pending claim request must be rescheduled
- for (package_id, request) in self.pending_claim_requests.iter() {
+ for (claim_id, request) in self.pending_claim_requests.iter() {
if cur_height >= request.timer() {
- bump_candidates.insert(*package_id, request.clone());
+ bump_candidates.insert(*claim_id, request.clone());
}
}
// Build, bump and rebroadcast tx accordingly
log_trace!(logger, "Bumping {} candidates", bump_candidates.len());
- for (package_id, request) in bump_candidates.iter() {
+ for (claim_id, request) in bump_candidates.iter() {
if let Some((new_timer, new_feerate, bump_claim)) = self.generate_claim(
cur_height, &request, true /* force_feerate_bump */, &*fee_estimator, &*logger,
) {
log_info!(logger, "Broadcasting RBF-bumped onchain {}", log_tx!(bump_tx));
broadcaster.broadcast_transactions(&[&bump_tx]);
},
- #[cfg(anchors)]
OnchainClaim::Event(claim_event) => {
log_info!(logger, "Yielding RBF-bumped onchain event to spend inputs {:?}", request.outpoints());
#[cfg(debug_assertions)] {
let num_existing = self.pending_claim_events.iter().
- filter(|entry| entry.0 == *package_id).count();
+ filter(|entry| entry.0 == *claim_id).count();
assert!(num_existing == 0 || num_existing == 1);
}
- self.pending_claim_events.retain(|event| event.0 != *package_id);
- self.pending_claim_events.push((*package_id, claim_event));
+ self.pending_claim_events.retain(|event| event.0 != *claim_id);
+ self.pending_claim_events.push((*claim_id, claim_event));
},
}
- if let Some(request) = self.pending_claim_requests.get_mut(package_id) {
+ if let Some(request) = self.pending_claim_requests.get_mut(claim_id) {
request.set_timer(new_timer);
request.set_feerate(new_feerate);
}
self.onchain_events_awaiting_threshold_conf.push(entry);
}
}
- for ((_package_id, _), ref mut request) in bump_candidates.iter_mut() {
+ for ((_claim_id, _), ref mut request) in bump_candidates.iter_mut() {
// `height` is the height being disconnected, so our `current_height` is 1 lower.
let current_height = height - 1;
if let Some((new_timer, new_feerate, bump_claim)) = self.generate_claim(
log_info!(logger, "Broadcasting onchain {}", log_tx!(bump_tx));
broadcaster.broadcast_transactions(&[&bump_tx]);
},
- #[cfg(anchors)]
OnchainClaim::Event(claim_event) => {
log_info!(logger, "Yielding onchain event after reorg to spend inputs {:?}", request.outpoints());
#[cfg(debug_assertions)] {
let num_existing = self.pending_claim_events.iter()
- .filter(|entry| entry.0 == *_package_id).count();
+ .filter(|entry| entry.0 == *_claim_id).count();
assert!(num_existing == 0 || num_existing == 1);
}
- self.pending_claim_events.retain(|event| event.0 != *_package_id);
- self.pending_claim_events.push((*_package_id, claim_event));
+ self.pending_claim_events.retain(|event| event.0 != *_claim_id);
+ self.pending_claim_events.push((*_claim_id, claim_event));
},
}
}
}
//TODO: getting lastest holder transactions should be infallible and result in us "force-closing the channel", but we may
- // have empty holder commitment transaction if a ChannelMonitor is asked to force-close just after Channel::get_outbound_funding_created,
+ // have empty holder commitment transaction if a ChannelMonitor is asked to force-close just after OutboundV1Channel::get_funding_created,
// before providing a initial commitment transaction. For outbound channel, init ChannelMonitor at Channel::funding_signed, there is nothing
// to monitor before.
pub(crate) fn get_fully_signed_holder_tx(&mut self, funding_redeemscript: &Script) -> Transaction {
htlc_tx
}
- #[cfg(anchors)]
pub(crate) fn generate_external_htlc_claim(
&self, outp: &::bitcoin::OutPoint, preimage: &Option<PaymentPreimage>
) -> Option<ExternalHTLCClaim> {
.or_else(|| self.prev_holder_commitment.as_ref().map(|c| find_htlc(c)).flatten())
}
- pub(crate) fn opt_anchors(&self) -> bool {
- self.channel_transaction_parameters.opt_anchors.is_some()
+ pub(crate) fn channel_type_features(&self) -> &ChannelTypeFeatures {
+ &self.channel_transaction_parameters.channel_type_features
}
#[cfg(any(test,feature = "unsafe_revoked_tx_signing"))]
use crate::ln::msgs::DecodeError;
use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, MIN_RELAY_FEE_SAT_PER_1000_WEIGHT};
use crate::sign::WriteableEcdsaChannelSigner;
-#[cfg(anchors)]
-use crate::chain::onchaintx::ExternalHTLCClaim;
-use crate::chain::onchaintx::OnchainTxHandler;
+use crate::chain::onchaintx::{ExternalHTLCClaim, OnchainTxHandler};
use crate::util::logger::Logger;
-use crate::util::ser::{Readable, Writer, Writeable};
+use crate::util::ser::{Readable, Writer, Writeable, RequiredWrapper};
use crate::io;
use crate::prelude::*;
use core::cmp;
-#[cfg(anchors)]
use core::convert::TryInto;
use core::mem;
use core::ops::Deref;
use bitcoin::{PackedLockTime, Sequence, Witness};
+use crate::ln::features::ChannelTypeFeatures;
use super::chaininterface::LowerBoundedFeeEstimator;
const MAX_ALLOC_SIZE: usize = 64*1024;
-pub(crate) fn weight_revoked_offered_htlc(opt_anchors: bool) -> u64 {
+pub(crate) fn weight_revoked_offered_htlc(channel_type_features: &ChannelTypeFeatures) -> u64 {
// number_of_witness_elements + sig_length + revocation_sig + pubkey_length + revocationpubkey + witness_script_length + witness_script
const WEIGHT_REVOKED_OFFERED_HTLC: u64 = 1 + 1 + 73 + 1 + 33 + 1 + 133;
const WEIGHT_REVOKED_OFFERED_HTLC_ANCHORS: u64 = WEIGHT_REVOKED_OFFERED_HTLC + 3; // + OP_1 + OP_CSV + OP_DROP
- if opt_anchors { WEIGHT_REVOKED_OFFERED_HTLC_ANCHORS } else { WEIGHT_REVOKED_OFFERED_HTLC }
+ if channel_type_features.supports_anchors_zero_fee_htlc_tx() { WEIGHT_REVOKED_OFFERED_HTLC_ANCHORS } else { WEIGHT_REVOKED_OFFERED_HTLC }
}
-pub(crate) fn weight_revoked_received_htlc(opt_anchors: bool) -> u64 {
+pub(crate) fn weight_revoked_received_htlc(channel_type_features: &ChannelTypeFeatures) -> u64 {
// number_of_witness_elements + sig_length + revocation_sig + pubkey_length + revocationpubkey + witness_script_length + witness_script
const WEIGHT_REVOKED_RECEIVED_HTLC: u64 = 1 + 1 + 73 + 1 + 33 + 1 + 139;
const WEIGHT_REVOKED_RECEIVED_HTLC_ANCHORS: u64 = WEIGHT_REVOKED_RECEIVED_HTLC + 3; // + OP_1 + OP_CSV + OP_DROP
- if opt_anchors { WEIGHT_REVOKED_RECEIVED_HTLC_ANCHORS } else { WEIGHT_REVOKED_RECEIVED_HTLC }
+ if channel_type_features.supports_anchors_zero_fee_htlc_tx() { WEIGHT_REVOKED_RECEIVED_HTLC_ANCHORS } else { WEIGHT_REVOKED_RECEIVED_HTLC }
}
-pub(crate) fn weight_offered_htlc(opt_anchors: bool) -> u64 {
+pub(crate) fn weight_offered_htlc(channel_type_features: &ChannelTypeFeatures) -> u64 {
// number_of_witness_elements + sig_length + counterpartyhtlc_sig + preimage_length + preimage + witness_script_length + witness_script
const WEIGHT_OFFERED_HTLC: u64 = 1 + 1 + 73 + 1 + 32 + 1 + 133;
const WEIGHT_OFFERED_HTLC_ANCHORS: u64 = WEIGHT_OFFERED_HTLC + 3; // + OP_1 + OP_CSV + OP_DROP
- if opt_anchors { WEIGHT_OFFERED_HTLC_ANCHORS } else { WEIGHT_OFFERED_HTLC }
+ if channel_type_features.supports_anchors_zero_fee_htlc_tx() { WEIGHT_OFFERED_HTLC_ANCHORS } else { WEIGHT_OFFERED_HTLC }
}
-pub(crate) fn weight_received_htlc(opt_anchors: bool) -> u64 {
+pub(crate) fn weight_received_htlc(channel_type_features: &ChannelTypeFeatures) -> u64 {
// number_of_witness_elements + sig_length + counterpartyhtlc_sig + empty_vec_length + empty_vec + witness_script_length + witness_script
const WEIGHT_RECEIVED_HTLC: u64 = 1 + 1 + 73 + 1 + 1 + 1 + 139;
const WEIGHT_RECEIVED_HTLC_ANCHORS: u64 = WEIGHT_RECEIVED_HTLC + 3; // + OP_1 + OP_CSV + OP_DROP
- if opt_anchors { WEIGHT_RECEIVED_HTLC_ANCHORS } else { WEIGHT_RECEIVED_HTLC }
+ if channel_type_features.supports_anchors_zero_fee_htlc_tx() { WEIGHT_RECEIVED_HTLC_ANCHORS } else { WEIGHT_RECEIVED_HTLC }
+}
+
+/// Verifies deserializable channel type features
+pub(crate) fn verify_channel_type_features(channel_type_features: &Option<ChannelTypeFeatures>, additional_permitted_features: Option<&ChannelTypeFeatures>) -> Result<(), DecodeError> {
+ if let Some(features) = channel_type_features.as_ref() {
+ if features.requires_unknown_bits() {
+ return Err(DecodeError::UnknownRequiredFeature);
+ }
+
+ let mut supported_feature_set = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
+ supported_feature_set.set_scid_privacy_required();
+ supported_feature_set.set_zero_conf_required();
+
+ // allow the passing of an additional necessary permitted flag
+ if let Some(additional_permitted_features) = additional_permitted_features {
+ supported_feature_set |= additional_permitted_features;
+ }
+
+ if !features.is_subset(&supported_feature_set) {
+ return Err(DecodeError::UnknownRequiredFeature);
+ }
+ }
+
+ Ok(())
}
// number_of_witness_elements + sig_length + revocation_sig + true_length + op_true + witness_script_length + witness_script
}
impl RevokedHTLCOutput {
- pub(crate) fn build(per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: PublicKey, counterparty_htlc_base_key: PublicKey, per_commitment_key: SecretKey, amount: u64, htlc: HTLCOutputInCommitment, opt_anchors: bool) -> Self {
- let weight = if htlc.offered { weight_revoked_offered_htlc(opt_anchors) } else { weight_revoked_received_htlc(opt_anchors) };
+ pub(crate) fn build(per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: PublicKey, counterparty_htlc_base_key: PublicKey, per_commitment_key: SecretKey, amount: u64, htlc: HTLCOutputInCommitment, channel_type_features: &ChannelTypeFeatures) -> Self {
+ let weight = if htlc.offered { weight_revoked_offered_htlc(channel_type_features) } else { weight_revoked_received_htlc(channel_type_features) };
RevokedHTLCOutput {
per_commitment_point,
counterparty_delayed_payment_base_key,
/// witnessScript.
///
/// The preimage is used as part of the witness.
+///
+/// Note that on upgrades, some features of existing outputs may be missed.
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct CounterpartyOfferedHTLCOutput {
per_commitment_point: PublicKey,
counterparty_htlc_base_key: PublicKey,
preimage: PaymentPreimage,
htlc: HTLCOutputInCommitment,
- opt_anchors: Option<()>,
+ channel_type_features: ChannelTypeFeatures,
}
impl CounterpartyOfferedHTLCOutput {
- pub(crate) fn build(per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: PublicKey, counterparty_htlc_base_key: PublicKey, preimage: PaymentPreimage, htlc: HTLCOutputInCommitment, opt_anchors: bool) -> Self {
+ pub(crate) fn build(per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: PublicKey, counterparty_htlc_base_key: PublicKey, preimage: PaymentPreimage, htlc: HTLCOutputInCommitment, channel_type_features: ChannelTypeFeatures) -> Self {
CounterpartyOfferedHTLCOutput {
per_commitment_point,
counterparty_delayed_payment_base_key,
counterparty_htlc_base_key,
preimage,
htlc,
- opt_anchors: if opt_anchors { Some(()) } else { None },
+ channel_type_features,
}
}
+}
- fn opt_anchors(&self) -> bool {
- self.opt_anchors.is_some()
+impl Writeable for CounterpartyOfferedHTLCOutput {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ let legacy_deserialization_prevention_marker = chan_utils::legacy_deserialization_prevention_marker_for_channel_type_features(&self.channel_type_features);
+ write_tlv_fields!(writer, {
+ (0, self.per_commitment_point, required),
+ (2, self.counterparty_delayed_payment_base_key, required),
+ (4, self.counterparty_htlc_base_key, required),
+ (6, self.preimage, required),
+ (8, self.htlc, required),
+ (10, legacy_deserialization_prevention_marker, option),
+ (11, self.channel_type_features, required),
+ });
+ Ok(())
}
}
-impl_writeable_tlv_based!(CounterpartyOfferedHTLCOutput, {
- (0, per_commitment_point, required),
- (2, counterparty_delayed_payment_base_key, required),
- (4, counterparty_htlc_base_key, required),
- (6, preimage, required),
- (8, htlc, required),
- (10, opt_anchors, option),
-});
+impl Readable for CounterpartyOfferedHTLCOutput {
+ fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
+ let mut per_commitment_point = RequiredWrapper(None);
+ let mut counterparty_delayed_payment_base_key = RequiredWrapper(None);
+ let mut counterparty_htlc_base_key = RequiredWrapper(None);
+ let mut preimage = RequiredWrapper(None);
+ let mut htlc = RequiredWrapper(None);
+ let mut _legacy_deserialization_prevention_marker: Option<()> = None;
+ let mut channel_type_features = None;
+
+ read_tlv_fields!(reader, {
+ (0, per_commitment_point, required),
+ (2, counterparty_delayed_payment_base_key, required),
+ (4, counterparty_htlc_base_key, required),
+ (6, preimage, required),
+ (8, htlc, required),
+ (10, _legacy_deserialization_prevention_marker, option),
+ (11, channel_type_features, option),
+ });
+
+ verify_channel_type_features(&channel_type_features, None)?;
+
+ Ok(Self {
+ per_commitment_point: per_commitment_point.0.unwrap(),
+ counterparty_delayed_payment_base_key: counterparty_delayed_payment_base_key.0.unwrap(),
+ counterparty_htlc_base_key: counterparty_htlc_base_key.0.unwrap(),
+ preimage: preimage.0.unwrap(),
+ htlc: htlc.0.unwrap(),
+ channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key())
+ })
+ }
+}
/// A struct to describe a HTLC output on a counterparty commitment transaction.
///
/// HTLCOutputInCommitment (hash, timelock, directon) and pubkeys are used to generate a suitable
/// witnessScript.
+///
+/// Note that on upgrades, some features of existing outputs may be missed.
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct CounterpartyReceivedHTLCOutput {
per_commitment_point: PublicKey,
counterparty_delayed_payment_base_key: PublicKey,
counterparty_htlc_base_key: PublicKey,
htlc: HTLCOutputInCommitment,
- opt_anchors: Option<()>,
+ channel_type_features: ChannelTypeFeatures,
}
impl CounterpartyReceivedHTLCOutput {
- pub(crate) fn build(per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: PublicKey, counterparty_htlc_base_key: PublicKey, htlc: HTLCOutputInCommitment, opt_anchors: bool) -> Self {
+ pub(crate) fn build(per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: PublicKey, counterparty_htlc_base_key: PublicKey, htlc: HTLCOutputInCommitment, channel_type_features: ChannelTypeFeatures) -> Self {
CounterpartyReceivedHTLCOutput {
per_commitment_point,
counterparty_delayed_payment_base_key,
counterparty_htlc_base_key,
htlc,
- opt_anchors: if opt_anchors { Some(()) } else { None },
+ channel_type_features
}
}
+}
- fn opt_anchors(&self) -> bool {
- self.opt_anchors.is_some()
+impl Writeable for CounterpartyReceivedHTLCOutput {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ let legacy_deserialization_prevention_marker = chan_utils::legacy_deserialization_prevention_marker_for_channel_type_features(&self.channel_type_features);
+ write_tlv_fields!(writer, {
+ (0, self.per_commitment_point, required),
+ (2, self.counterparty_delayed_payment_base_key, required),
+ (4, self.counterparty_htlc_base_key, required),
+ (6, self.htlc, required),
+ (8, legacy_deserialization_prevention_marker, option),
+ (9, self.channel_type_features, required),
+ });
+ Ok(())
}
}
-impl_writeable_tlv_based!(CounterpartyReceivedHTLCOutput, {
- (0, per_commitment_point, required),
- (2, counterparty_delayed_payment_base_key, required),
- (4, counterparty_htlc_base_key, required),
- (6, htlc, required),
- (8, opt_anchors, option),
-});
+impl Readable for CounterpartyReceivedHTLCOutput {
+ fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
+ let mut per_commitment_point = RequiredWrapper(None);
+ let mut counterparty_delayed_payment_base_key = RequiredWrapper(None);
+ let mut counterparty_htlc_base_key = RequiredWrapper(None);
+ let mut htlc = RequiredWrapper(None);
+ let mut _legacy_deserialization_prevention_marker: Option<()> = None;
+ let mut channel_type_features = None;
+
+ read_tlv_fields!(reader, {
+ (0, per_commitment_point, required),
+ (2, counterparty_delayed_payment_base_key, required),
+ (4, counterparty_htlc_base_key, required),
+ (6, htlc, required),
+ (8, _legacy_deserialization_prevention_marker, option),
+ (9, channel_type_features, option),
+ });
+
+ verify_channel_type_features(&channel_type_features, None)?;
+
+ Ok(Self {
+ per_commitment_point: per_commitment_point.0.unwrap(),
+ counterparty_delayed_payment_base_key: counterparty_delayed_payment_base_key.0.unwrap(),
+ counterparty_htlc_base_key: counterparty_htlc_base_key.0.unwrap(),
+ htlc: htlc.0.unwrap(),
+ channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key())
+ })
+ }
+}
/// A struct to describe a HTLC output on holder commitment transaction.
///
/// Either offered or received, the amount is always used as part of the bip143 sighash.
/// Preimage is only included as part of the witness in former case.
+///
+/// Note that on upgrades, some features of existing outputs may be missed.
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct HolderHTLCOutput {
preimage: Option<PaymentPreimage>,
amount_msat: u64,
/// Defaults to 0 for HTLC-Success transactions, which have no expiry
cltv_expiry: u32,
- opt_anchors: Option<()>,
+ channel_type_features: ChannelTypeFeatures,
}
impl HolderHTLCOutput {
- pub(crate) fn build_offered(amount_msat: u64, cltv_expiry: u32, opt_anchors: bool) -> Self {
+ pub(crate) fn build_offered(amount_msat: u64, cltv_expiry: u32, channel_type_features: ChannelTypeFeatures) -> Self {
HolderHTLCOutput {
preimage: None,
amount_msat,
cltv_expiry,
- opt_anchors: if opt_anchors { Some(()) } else { None } ,
+ channel_type_features,
}
}
- pub(crate) fn build_accepted(preimage: PaymentPreimage, amount_msat: u64, opt_anchors: bool) -> Self {
+ pub(crate) fn build_accepted(preimage: PaymentPreimage, amount_msat: u64, channel_type_features: ChannelTypeFeatures) -> Self {
HolderHTLCOutput {
preimage: Some(preimage),
amount_msat,
cltv_expiry: 0,
- opt_anchors: if opt_anchors { Some(()) } else { None } ,
+ channel_type_features,
}
}
+}
- fn opt_anchors(&self) -> bool {
- self.opt_anchors.is_some()
+impl Writeable for HolderHTLCOutput {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ let legacy_deserialization_prevention_marker = chan_utils::legacy_deserialization_prevention_marker_for_channel_type_features(&self.channel_type_features);
+ write_tlv_fields!(writer, {
+ (0, self.amount_msat, required),
+ (2, self.cltv_expiry, required),
+ (4, self.preimage, option),
+ (6, legacy_deserialization_prevention_marker, option),
+ (7, self.channel_type_features, required),
+ });
+ Ok(())
}
}
-impl_writeable_tlv_based!(HolderHTLCOutput, {
- (0, amount_msat, required),
- (2, cltv_expiry, required),
- (4, preimage, option),
- (6, opt_anchors, option)
-});
+impl Readable for HolderHTLCOutput {
+ fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
+ let mut amount_msat = RequiredWrapper(None);
+ let mut cltv_expiry = RequiredWrapper(None);
+ let mut preimage = None;
+ let mut _legacy_deserialization_prevention_marker: Option<()> = None;
+ let mut channel_type_features = None;
+
+ read_tlv_fields!(reader, {
+ (0, amount_msat, required),
+ (2, cltv_expiry, required),
+ (4, preimage, option),
+ (6, _legacy_deserialization_prevention_marker, option),
+ (7, channel_type_features, option),
+ });
+
+ verify_channel_type_features(&channel_type_features, None)?;
+
+ Ok(Self {
+ amount_msat: amount_msat.0.unwrap(),
+ cltv_expiry: cltv_expiry.0.unwrap(),
+ preimage,
+ channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key())
+ })
+ }
+}
/// A struct to describe the channel output on the funding transaction.
///
/// witnessScript is used as part of the witness redeeming the funding utxo.
+///
+/// Note that on upgrades, some features of existing outputs may be missed.
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct HolderFundingOutput {
funding_redeemscript: Script,
- funding_amount: Option<u64>,
- opt_anchors: Option<()>,
+ pub(crate) funding_amount: Option<u64>,
+ channel_type_features: ChannelTypeFeatures,
}
impl HolderFundingOutput {
- pub(crate) fn build(funding_redeemscript: Script, funding_amount: u64, opt_anchors: bool) -> Self {
+ pub(crate) fn build(funding_redeemscript: Script, funding_amount: u64, channel_type_features: ChannelTypeFeatures) -> Self {
HolderFundingOutput {
funding_redeemscript,
funding_amount: Some(funding_amount),
- opt_anchors: if opt_anchors { Some(()) } else { None },
+ channel_type_features,
}
}
+}
- fn opt_anchors(&self) -> bool {
- self.opt_anchors.is_some()
+impl Writeable for HolderFundingOutput {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ let legacy_deserialization_prevention_marker = chan_utils::legacy_deserialization_prevention_marker_for_channel_type_features(&self.channel_type_features);
+ write_tlv_fields!(writer, {
+ (0, self.funding_redeemscript, required),
+ (1, self.channel_type_features, required),
+ (2, legacy_deserialization_prevention_marker, option),
+ (3, self.funding_amount, option),
+ });
+ Ok(())
}
}
-impl_writeable_tlv_based!(HolderFundingOutput, {
- (0, funding_redeemscript, required),
- (2, opt_anchors, option),
- (3, funding_amount, option),
-});
+impl Readable for HolderFundingOutput {
+ fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
+ let mut funding_redeemscript = RequiredWrapper(None);
+ let mut _legacy_deserialization_prevention_marker: Option<()> = None;
+ let mut channel_type_features = None;
+ let mut funding_amount = None;
+
+ read_tlv_fields!(reader, {
+ (0, funding_redeemscript, required),
+ (1, channel_type_features, option),
+ (2, _legacy_deserialization_prevention_marker, option),
+ (3, funding_amount, option)
+ });
+
+ verify_channel_type_features(&channel_type_features, None)?;
+
+ Ok(Self {
+ funding_redeemscript: funding_redeemscript.0.unwrap(),
+ channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key()),
+ funding_amount
+ })
+ }
+}
/// A wrapper encapsulating all in-protocol differing outputs types.
///
PackageSolvingData::CounterpartyOfferedHTLCOutput(ref outp) => outp.htlc.amount_msat / 1000,
PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => outp.htlc.amount_msat / 1000,
PackageSolvingData::HolderHTLCOutput(ref outp) => {
- debug_assert!(outp.opt_anchors());
+ debug_assert!(outp.channel_type_features.supports_anchors_zero_fee_htlc_tx());
outp.amount_msat / 1000
},
PackageSolvingData::HolderFundingOutput(ref outp) => {
- debug_assert!(outp.opt_anchors());
+ debug_assert!(outp.channel_type_features.supports_anchors_zero_fee_htlc_tx());
outp.funding_amount.unwrap()
}
};
match self {
PackageSolvingData::RevokedOutput(ref outp) => outp.weight as usize,
PackageSolvingData::RevokedHTLCOutput(ref outp) => outp.weight as usize,
- PackageSolvingData::CounterpartyOfferedHTLCOutput(ref outp) => weight_offered_htlc(outp.opt_anchors()) as usize,
- PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => weight_received_htlc(outp.opt_anchors()) as usize,
+ PackageSolvingData::CounterpartyOfferedHTLCOutput(ref outp) => weight_offered_htlc(&outp.channel_type_features) as usize,
+ PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => weight_received_htlc(&outp.channel_type_features) as usize,
PackageSolvingData::HolderHTLCOutput(ref outp) => {
- debug_assert!(outp.opt_anchors());
+ debug_assert!(outp.channel_type_features.supports_anchors_zero_fee_htlc_tx());
if outp.preimage.is_none() {
- weight_offered_htlc(true) as usize
+ weight_offered_htlc(&outp.channel_type_features) as usize
} else {
- weight_received_htlc(true) as usize
+ weight_received_htlc(&outp.channel_type_features) as usize
}
},
// Since HolderFundingOutput maps to an untractable package that is already signed, its
},
PackageSolvingData::RevokedHTLCOutput(ref outp) => {
let chan_keys = TxCreationKeys::derive_new(&onchain_handler.secp_ctx, &outp.per_commitment_point, &outp.counterparty_delayed_payment_base_key, &outp.counterparty_htlc_base_key, &onchain_handler.signer.pubkeys().revocation_basepoint, &onchain_handler.signer.pubkeys().htlc_basepoint);
- let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&outp.htlc, onchain_handler.opt_anchors(), &chan_keys.broadcaster_htlc_key, &chan_keys.countersignatory_htlc_key, &chan_keys.revocation_key);
+ let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&outp.htlc, &onchain_handler.channel_type_features(), &chan_keys.broadcaster_htlc_key, &chan_keys.countersignatory_htlc_key, &chan_keys.revocation_key);
//TODO: should we panic on signer failure ?
if let Ok(sig) = onchain_handler.signer.sign_justice_revoked_htlc(&bumped_tx, i, outp.amount, &outp.per_commitment_key, &outp.htlc, &onchain_handler.secp_ctx) {
let mut ser_sig = sig.serialize_der().to_vec();
},
PackageSolvingData::CounterpartyOfferedHTLCOutput(ref outp) => {
let chan_keys = TxCreationKeys::derive_new(&onchain_handler.secp_ctx, &outp.per_commitment_point, &outp.counterparty_delayed_payment_base_key, &outp.counterparty_htlc_base_key, &onchain_handler.signer.pubkeys().revocation_basepoint, &onchain_handler.signer.pubkeys().htlc_basepoint);
- let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&outp.htlc, onchain_handler.opt_anchors(), &chan_keys.broadcaster_htlc_key, &chan_keys.countersignatory_htlc_key, &chan_keys.revocation_key);
+ let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&outp.htlc, &onchain_handler.channel_type_features(), &chan_keys.broadcaster_htlc_key, &chan_keys.countersignatory_htlc_key, &chan_keys.revocation_key);
if let Ok(sig) = onchain_handler.signer.sign_counterparty_htlc_transaction(&bumped_tx, i, &outp.htlc.amount_msat / 1000, &outp.per_commitment_point, &outp.htlc, &onchain_handler.secp_ctx) {
let mut ser_sig = sig.serialize_der().to_vec();
},
PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => {
let chan_keys = TxCreationKeys::derive_new(&onchain_handler.secp_ctx, &outp.per_commitment_point, &outp.counterparty_delayed_payment_base_key, &outp.counterparty_htlc_base_key, &onchain_handler.signer.pubkeys().revocation_basepoint, &onchain_handler.signer.pubkeys().htlc_basepoint);
- let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&outp.htlc, onchain_handler.opt_anchors(), &chan_keys.broadcaster_htlc_key, &chan_keys.countersignatory_htlc_key, &chan_keys.revocation_key);
+ let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&outp.htlc, &onchain_handler.channel_type_features(), &chan_keys.broadcaster_htlc_key, &chan_keys.countersignatory_htlc_key, &chan_keys.revocation_key);
if let Ok(sig) = onchain_handler.signer.sign_counterparty_htlc_transaction(&bumped_tx, i, &outp.htlc.amount_msat / 1000, &outp.per_commitment_point, &outp.htlc, &onchain_handler.secp_ctx) {
let mut ser_sig = sig.serialize_der().to_vec();
fn get_finalized_tx<Signer: WriteableEcdsaChannelSigner>(&self, outpoint: &BitcoinOutPoint, onchain_handler: &mut OnchainTxHandler<Signer>) -> Option<Transaction> {
match self {
PackageSolvingData::HolderHTLCOutput(ref outp) => {
- debug_assert!(!outp.opt_anchors());
+ debug_assert!(!outp.channel_type_features.supports_anchors_zero_fee_htlc_tx());
return onchain_handler.get_fully_signed_htlc_tx(outpoint, &outp.preimage);
}
PackageSolvingData::HolderFundingOutput(ref outp) => {
PackageSolvingData::RevokedHTLCOutput(..) => { (PackageMalleability::Malleable, true) },
PackageSolvingData::CounterpartyOfferedHTLCOutput(..) => { (PackageMalleability::Malleable, true) },
PackageSolvingData::CounterpartyReceivedHTLCOutput(..) => { (PackageMalleability::Malleable, false) },
- PackageSolvingData::HolderHTLCOutput(ref outp) => if outp.opt_anchors() {
+ PackageSolvingData::HolderHTLCOutput(ref outp) => if outp.channel_type_features.supports_anchors_zero_fee_htlc_tx() {
(PackageMalleability::Malleable, outp.preimage.is_some())
} else {
(PackageMalleability::Untractable, false)
let output_weight = (8 + 1 + destination_script.len()) * WITNESS_SCALE_FACTOR;
inputs_weight + witnesses_weight + transaction_weight + output_weight
}
- #[cfg(anchors)]
pub(crate) fn construct_malleable_package_with_external_funding<Signer: WriteableEcdsaChannelSigner>(
&self, onchain_handler: &mut OnchainTxHandler<Signer>,
) -> Option<Vec<ExternalHTLCClaim>> {
for (previous_output, input) in &self.inputs {
match input {
PackageSolvingData::HolderHTLCOutput(ref outp) => {
- debug_assert!(outp.opt_anchors());
+ debug_assert!(outp.channel_type_features.supports_anchors_zero_fee_htlc_tx());
onchain_handler.generate_external_htlc_claim(&previous_output, &outp.preimage).map(|htlc| {
htlcs.get_or_insert_with(|| Vec::with_capacity(self.inputs.len())).push(htlc);
});
None
}
- #[cfg(anchors)]
/// Computes a feerate based on the given confirmation target. If a previous feerate was used,
/// the new feerate is below it, and `force_feerate_bump` is set, we'll use a 25% increase of
/// the previous feerate instead of the new feerate.
/// attached to help the spending transaction reach confirmation.
pub(crate) fn requires_external_funding(&self) -> bool {
self.inputs.iter().find(|input| match input.1 {
- PackageSolvingData::HolderFundingOutput(ref outp) => outp.opt_anchors(),
- PackageSolvingData::HolderHTLCOutput(ref outp) => outp.opt_anchors(),
+ PackageSolvingData::HolderFundingOutput(ref outp) => outp.channel_type_features.supports_anchors_zero_fee_htlc_tx(),
+ PackageSolvingData::HolderHTLCOutput(ref outp) => outp.channel_type_features.supports_anchors_zero_fee_htlc_tx(),
_ => false,
}).is_some()
}
use bitcoin::secp256k1::{PublicKey,SecretKey};
use bitcoin::secp256k1::Secp256k1;
+ use crate::ln::features::ChannelTypeFeatures;
macro_rules! dumb_revk_output {
($secp_ctx: expr, $is_counterparty_balance_on_anchors: expr) => {
() => {
{
let preimage = PaymentPreimage([2;32]);
- PackageSolvingData::HolderHTLCOutput(HolderHTLCOutput::build_accepted(preimage, 0, false))
+ PackageSolvingData::HolderHTLCOutput(HolderHTLCOutput::build_accepted(preimage, 0, ChannelTypeFeatures::only_static_remote_key()))
}
}
}
let txid = Txid::from_hex("c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e").unwrap();
let secp_ctx = Secp256k1::new();
let revk_outp = dumb_revk_output!(secp_ctx, false);
- let counterparty_outp = dumb_counterparty_output!(secp_ctx, 0, false);
+ let counterparty_outp = dumb_counterparty_output!(secp_ctx, 0, ChannelTypeFeatures::only_static_remote_key());
let mut revoked_package = PackageTemplate::build_package(txid, 0, revk_outp, 1000, 100);
let counterparty_package = PackageTemplate::build_package(txid, 1, counterparty_outp, 1000, 100);
fn test_package_amounts() {
let txid = Txid::from_hex("c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e").unwrap();
let secp_ctx = Secp256k1::new();
- let counterparty_outp = dumb_counterparty_output!(secp_ctx, 1_000_000, false);
+ let counterparty_outp = dumb_counterparty_output!(secp_ctx, 1_000_000, ChannelTypeFeatures::only_static_remote_key());
let package = PackageTemplate::build_package(txid, 0, counterparty_outp, 1000, 100);
assert_eq!(package.package_amount(), 1000);
}
{
- for &opt_anchors in [false, true].iter() {
- let counterparty_outp = dumb_counterparty_output!(secp_ctx, 1_000_000, opt_anchors);
+ for channel_type_features in [ChannelTypeFeatures::only_static_remote_key(), ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies()].iter() {
+ let counterparty_outp = dumb_counterparty_output!(secp_ctx, 1_000_000, channel_type_features.clone());
let package = PackageTemplate::build_package(txid, 0, counterparty_outp, 1000, 100);
- assert_eq!(package.package_weight(&Script::new()), weight_sans_output + weight_received_htlc(opt_anchors) as usize);
+ assert_eq!(package.package_weight(&Script::new()), weight_sans_output + weight_received_htlc(channel_type_features) as usize);
}
}
{
- for &opt_anchors in [false, true].iter() {
- let counterparty_outp = dumb_counterparty_offered_output!(secp_ctx, 1_000_000, opt_anchors);
+ for channel_type_features in [ChannelTypeFeatures::only_static_remote_key(), ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies()].iter() {
+ let counterparty_outp = dumb_counterparty_offered_output!(secp_ctx, 1_000_000, channel_type_features.clone());
let package = PackageTemplate::build_package(txid, 0, counterparty_outp, 1000, 100);
- assert_eq!(package.package_weight(&Script::new()), weight_sans_output + weight_offered_htlc(opt_anchors) as usize);
+ assert_eq!(package.package_weight(&Script::new()), weight_sans_output + weight_offered_htlc(channel_type_features) as usize);
}
}
}
// You may not use this file except in accordance with one or both of these
// licenses.
-//! Utitilies for bumping transactions originating from [`super::Event`]s.
+//! Utilities for bumping transactions originating from [`Event`]s.
+//!
+//! [`Event`]: crate::events::Event
-use crate::ln::PaymentPreimage;
+use alloc::collections::BTreeMap;
+use core::ops::Deref;
+
+use crate::chain::chaininterface::{BroadcasterInterface, compute_feerate_sat_per_1000_weight, fee_for_weight, FEERATE_FLOOR_SATS_PER_KW};
+use crate::chain::ClaimId;
+use crate::io_extras::sink;
+use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI;
use crate::ln::chan_utils;
-use crate::ln::chan_utils::{ChannelTransactionParameters, HTLCOutputInCommitment};
+use crate::ln::chan_utils::{
+ ANCHOR_INPUT_WITNESS_WEIGHT, HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT,
+ HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT, ChannelTransactionParameters, HTLCOutputInCommitment
+};
+use crate::ln::features::ChannelTypeFeatures;
+use crate::ln::PaymentPreimage;
+use crate::prelude::*;
+use crate::sign::{ChannelSigner, EcdsaChannelSigner, SignerProvider, WriteableEcdsaChannelSigner};
+use crate::sync::Mutex;
+use crate::util::logger::Logger;
-use bitcoin::{OutPoint, PackedLockTime, Script, Transaction, Txid, TxIn, TxOut, Witness};
+use bitcoin::{OutPoint, PackedLockTime, PubkeyHash, Sequence, Script, Transaction, Txid, TxIn, TxOut, Witness, WPubkeyHash};
+use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR;
+use bitcoin::consensus::Encodable;
use bitcoin::secp256k1;
use bitcoin::secp256k1::{PublicKey, Secp256k1};
use bitcoin::secp256k1::ecdsa::Signature;
+const EMPTY_SCRIPT_SIG_WEIGHT: u64 = 1 /* empty script_sig */ * WITNESS_SCALE_FACTOR as u64;
+
+const BASE_INPUT_SIZE: u64 = 32 /* txid */ + 4 /* vout */ + 4 /* sequence */;
+
+const BASE_INPUT_WEIGHT: u64 = BASE_INPUT_SIZE * WITNESS_SCALE_FACTOR as u64;
+
+/// The parameters required to derive a channel signer via [`SignerProvider`].
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct ChannelDerivationParameters {
+ /// The value in satoshis of the channel we're attempting to spend the anchor output of.
+ pub value_satoshis: u64,
+ /// The unique identifier to re-derive the signer for the associated channel.
+ pub keys_id: [u8; 32],
+ /// The necessary channel parameters that need to be provided to the re-derived signer through
+ /// [`ChannelSigner::provide_channel_parameters`].
+ ///
+ /// [`ChannelSigner::provide_channel_parameters`]: crate::sign::ChannelSigner::provide_channel_parameters
+ pub transaction_parameters: ChannelTransactionParameters,
+}
+
/// A descriptor used to sign for a commitment transaction's anchor output.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AnchorDescriptor {
- /// A unique identifier used along with `channel_value_satoshis` to re-derive the
- /// [`InMemorySigner`] required to sign `input`.
- ///
- /// [`InMemorySigner`]: crate::sign::InMemorySigner
- pub channel_keys_id: [u8; 32],
- /// The value in satoshis of the channel we're attempting to spend the anchor output of. This is
- /// used along with `channel_keys_id` to re-derive the [`InMemorySigner`] required to sign
- /// `input`.
- ///
- /// [`InMemorySigner`]: crate::sign::InMemorySigner
- pub channel_value_satoshis: u64,
+ /// The parameters required to derive the signer for the anchor input.
+ pub channel_derivation_parameters: ChannelDerivationParameters,
/// The transaction input's outpoint corresponding to the commitment transaction's anchor
/// output.
pub outpoint: OutPoint,
}
+impl AnchorDescriptor {
+ /// Returns the UTXO to be spent by the anchor input, which can be obtained via
+ /// [`Self::unsigned_tx_input`].
+ pub fn previous_utxo(&self) -> TxOut {
+ TxOut {
+ script_pubkey: self.witness_script().to_v0_p2wsh(),
+ value: ANCHOR_OUTPUT_VALUE_SATOSHI,
+ }
+ }
+
+ /// Returns the unsigned transaction input spending the anchor output in the commitment
+ /// transaction.
+ pub fn unsigned_tx_input(&self) -> TxIn {
+ TxIn {
+ previous_output: self.outpoint.clone(),
+ script_sig: Script::new(),
+ sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
+ witness: Witness::new(),
+ }
+ }
+
+ /// Returns the witness script of the anchor output in the commitment transaction.
+ pub fn witness_script(&self) -> Script {
+ let channel_params = self.channel_derivation_parameters.transaction_parameters.as_holder_broadcastable();
+ chan_utils::get_anchor_redeemscript(&channel_params.broadcaster_pubkeys().funding_pubkey)
+ }
+
+ /// Returns the fully signed witness required to spend the anchor output in the commitment
+ /// transaction.
+ pub fn tx_input_witness(&self, signature: &Signature) -> Witness {
+ let channel_params = self.channel_derivation_parameters.transaction_parameters.as_holder_broadcastable();
+ chan_utils::build_anchor_input_witness(&channel_params.broadcaster_pubkeys().funding_pubkey, signature)
+ }
+
+ /// Derives the channel signer required to sign the anchor input.
+ pub fn derive_channel_signer<S: WriteableEcdsaChannelSigner, SP: Deref>(&self, signer_provider: &SP) -> S
+ where
+ SP::Target: SignerProvider<Signer = S>
+ {
+ let mut signer = signer_provider.derive_channel_signer(
+ self.channel_derivation_parameters.value_satoshis,
+ self.channel_derivation_parameters.keys_id,
+ );
+ signer.provide_channel_parameters(&self.channel_derivation_parameters.transaction_parameters);
+ signer
+ }
+}
+
/// A descriptor used to sign for a commitment transaction's HTLC output.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HTLCDescriptor {
- /// A unique identifier used along with `channel_value_satoshis` to re-derive the
- /// [`InMemorySigner`] required to sign `input`.
- ///
- /// [`InMemorySigner`]: crate::sign::InMemorySigner
- pub channel_keys_id: [u8; 32],
- /// The value in satoshis of the channel we're attempting to spend the anchor output of. This is
- /// used along with `channel_keys_id` to re-derive the [`InMemorySigner`] required to sign
- /// `input`.
- ///
- /// [`InMemorySigner`]: crate::sign::InMemorySigner
- pub channel_value_satoshis: u64,
- /// The necessary channel parameters that need to be provided to the re-derived
- /// [`InMemorySigner`] through [`ChannelSigner::provide_channel_parameters`].
- ///
- /// [`InMemorySigner`]: crate::sign::InMemorySigner
- /// [`ChannelSigner::provide_channel_parameters`]: crate::sign::ChannelSigner::provide_channel_parameters
- pub channel_parameters: ChannelTransactionParameters,
+ /// The parameters required to derive the signer for the HTLC input.
+ pub channel_derivation_parameters: ChannelDerivationParameters,
/// The txid of the commitment transaction in which the HTLC output lives.
pub commitment_txid: Txid,
/// The number of the commitment transaction in which the HTLC output lives.
pub per_commitment_number: u64,
+ /// The key tweak corresponding to the number of the commitment transaction in which the HTLC
+ /// output lives. This tweak is applied to all the basepoints for both parties in the channel to
+ /// arrive at unique keys per commitment.
+ ///
+ /// See <https://github.com/lightning/bolts/blob/master/03-transactions.md#keys> for more info.
+ pub per_commitment_point: PublicKey,
/// The details of the HTLC as it appears in the commitment transaction.
pub htlc: HTLCOutputInCommitment,
/// The preimage, if `Some`, to claim the HTLC output with. If `None`, the timeout path must be
}
impl HTLCDescriptor {
+ /// Returns the outpoint of the HTLC output in the commitment transaction. This is the outpoint
+ /// being spent by the HTLC input in the HTLC transaction.
+ pub fn outpoint(&self) -> OutPoint {
+ OutPoint {
+ txid: self.commitment_txid,
+ vout: self.htlc.transaction_output_index.unwrap(),
+ }
+ }
+
+ /// Returns the UTXO to be spent by the HTLC input, which can be obtained via
+ /// [`Self::unsigned_tx_input`].
+ pub fn previous_utxo<C: secp256k1::Signing + secp256k1::Verification>(&self, secp: &Secp256k1<C>) -> TxOut {
+ TxOut {
+ script_pubkey: self.witness_script(secp).to_v0_p2wsh(),
+ value: self.htlc.amount_msat / 1000,
+ }
+ }
+
/// Returns the unsigned transaction input spending the HTLC output in the commitment
/// transaction.
pub fn unsigned_tx_input(&self) -> TxIn {
- chan_utils::build_htlc_input(&self.commitment_txid, &self.htlc, true /* opt_anchors */)
+ chan_utils::build_htlc_input(&self.commitment_txid, &self.htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies())
}
/// Returns the delayed output created as a result of spending the HTLC output in the commitment
/// transaction.
- pub fn tx_output<C: secp256k1::Signing + secp256k1::Verification>(
- &self, per_commitment_point: &PublicKey, secp: &Secp256k1<C>
- ) -> TxOut {
- let channel_params = self.channel_parameters.as_holder_broadcastable();
+ pub fn tx_output<C: secp256k1::Signing + secp256k1::Verification>(&self, secp: &Secp256k1<C>) -> TxOut {
+ let channel_params = self.channel_derivation_parameters.transaction_parameters.as_holder_broadcastable();
let broadcaster_keys = channel_params.broadcaster_pubkeys();
let counterparty_keys = channel_params.countersignatory_pubkeys();
let broadcaster_delayed_key = chan_utils::derive_public_key(
- secp, per_commitment_point, &broadcaster_keys.delayed_payment_basepoint
+ secp, &self.per_commitment_point, &broadcaster_keys.delayed_payment_basepoint
);
let counterparty_revocation_key = chan_utils::derive_public_revocation_key(
- secp, per_commitment_point, &counterparty_keys.revocation_basepoint
+ secp, &self.per_commitment_point, &counterparty_keys.revocation_basepoint
);
chan_utils::build_htlc_output(
- 0 /* feerate_per_kw */, channel_params.contest_delay(), &self.htlc, true /* opt_anchors */,
- false /* use_non_zero_fee_anchors */, &broadcaster_delayed_key, &counterparty_revocation_key
+ 0 /* feerate_per_kw */, channel_params.contest_delay(), &self.htlc,
+ &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &broadcaster_delayed_key, &counterparty_revocation_key
)
}
/// Returns the witness script of the HTLC output in the commitment transaction.
- pub fn witness_script<C: secp256k1::Signing + secp256k1::Verification>(
- &self, per_commitment_point: &PublicKey, secp: &Secp256k1<C>
- ) -> Script {
- let channel_params = self.channel_parameters.as_holder_broadcastable();
+ pub fn witness_script<C: secp256k1::Signing + secp256k1::Verification>(&self, secp: &Secp256k1<C>) -> Script {
+ let channel_params = self.channel_derivation_parameters.transaction_parameters.as_holder_broadcastable();
let broadcaster_keys = channel_params.broadcaster_pubkeys();
let counterparty_keys = channel_params.countersignatory_pubkeys();
let broadcaster_htlc_key = chan_utils::derive_public_key(
- secp, per_commitment_point, &broadcaster_keys.htlc_basepoint
+ secp, &self.per_commitment_point, &broadcaster_keys.htlc_basepoint
);
let counterparty_htlc_key = chan_utils::derive_public_key(
- secp, per_commitment_point, &counterparty_keys.htlc_basepoint
+ secp, &self.per_commitment_point, &counterparty_keys.htlc_basepoint
);
let counterparty_revocation_key = chan_utils::derive_public_revocation_key(
- secp, per_commitment_point, &counterparty_keys.revocation_basepoint
+ secp, &self.per_commitment_point, &counterparty_keys.revocation_basepoint
);
chan_utils::get_htlc_redeemscript_with_explicit_keys(
- &self.htlc, true /* opt_anchors */, &broadcaster_htlc_key, &counterparty_htlc_key,
+ &self.htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &broadcaster_htlc_key, &counterparty_htlc_key,
&counterparty_revocation_key,
)
}
/// transaction.
pub fn tx_input_witness(&self, signature: &Signature, witness_script: &Script) -> Witness {
chan_utils::build_htlc_input_witness(
- signature, &self.counterparty_sig, &self.preimage, witness_script, true /* opt_anchors */
+ signature, &self.counterparty_sig, &self.preimage, witness_script, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies() /* opt_anchors */
)
}
+
+ /// Derives the channel signer required to sign the HTLC input.
+ pub fn derive_channel_signer<S: WriteableEcdsaChannelSigner, SP: Deref>(&self, signer_provider: &SP) -> S
+ where
+ SP::Target: SignerProvider<Signer = S>
+ {
+ let mut signer = signer_provider.derive_channel_signer(
+ self.channel_derivation_parameters.value_satoshis,
+ self.channel_derivation_parameters.keys_id,
+ );
+ signer.provide_channel_parameters(&self.channel_derivation_parameters.transaction_parameters);
+ signer
+ }
}
/// Represents the different types of transactions, originating from LDK, to be bumped.
/// broadcast first, as the child anchor transaction depends on it.
///
/// The consumer should be able to sign for any of the additional inputs included within the
- /// child anchor transaction. To sign its anchor input, an [`InMemorySigner`] should be
- /// re-derived through [`KeysManager::derive_channel_keys`] with the help of
- /// [`AnchorDescriptor::channel_keys_id`] and [`AnchorDescriptor::channel_value_satoshis`]. The
- /// anchor input signature can be computed with [`EcdsaChannelSigner::sign_holder_anchor_input`],
- /// which can then be provided to [`build_anchor_input_witness`] along with the `funding_pubkey`
- /// to obtain the full witness required to spend.
+ /// child anchor transaction. To sign its anchor input, an [`EcdsaChannelSigner`] should be
+ /// re-derived through [`AnchorDescriptor::derive_channel_signer`]. The anchor input signature
+ /// can be computed with [`EcdsaChannelSigner::sign_holder_anchor_input`], which can then be
+ /// provided to [`build_anchor_input_witness`] along with the `funding_pubkey` to obtain the
+ /// full witness required to spend.
///
/// It is possible to receive more than one instance of this event if a valid child anchor
/// transaction is never broadcast or is but not with a sufficient fee to be mined. Care should
/// an empty `pending_htlcs`), confirmation of the commitment transaction can be considered to
/// be not urgent.
///
- /// [`InMemorySigner`]: crate::sign::InMemorySigner
- /// [`KeysManager::derive_channel_keys`]: crate::sign::KeysManager::derive_channel_keys
+ /// [`EcdsaChannelSigner`]: crate::sign::EcdsaChannelSigner
/// [`EcdsaChannelSigner::sign_holder_anchor_input`]: crate::sign::EcdsaChannelSigner::sign_holder_anchor_input
/// [`build_anchor_input_witness`]: crate::ln::chan_utils::build_anchor_input_witness
ChannelClose {
+ /// The unique identifier for the claim of the anchor output in the commitment transaction.
+ ///
+ /// The identifier must map to the set of external UTXOs assigned to the claim, such that
+ /// they can be reused when a new claim with the same identifier needs to be made, resulting
+ /// in a fee-bumping attempt.
+ claim_id: ClaimId,
/// The target feerate that the transaction package, which consists of the commitment
/// transaction and the to-be-crafted child anchor transaction, must meet.
package_target_feerate_sat_per_1000_weight: u32,
/// broadcast by the consumer of the event.
///
/// The consumer should be able to sign for any of the non-HTLC inputs added to the resulting
- /// HTLC transaction. To sign HTLC inputs, an [`InMemorySigner`] should be re-derived through
- /// [`KeysManager::derive_channel_keys`] with the help of `channel_keys_id` and
- /// `channel_value_satoshis`. Each HTLC input's signature can be computed with
- /// [`EcdsaChannelSigner::sign_holder_htlc_transaction`], which can then be provided to
- /// [`HTLCDescriptor::tx_input_witness`] to obtain the fully signed witness required to spend.
+ /// HTLC transaction. To sign HTLC inputs, an [`EcdsaChannelSigner`] should be re-derived
+ /// through [`HTLCDescriptor::derive_channel_signer`]. Each HTLC input's signature can be
+ /// computed with [`EcdsaChannelSigner::sign_holder_htlc_transaction`], which can then be
+ /// provided to [`HTLCDescriptor::tx_input_witness`] to obtain the fully signed witness required
+ /// to spend.
///
/// It is possible to receive more than one instance of this event if a valid HTLC transaction
/// is never broadcast or is but not with a sufficient fee to be mined. Care should be taken by
/// longer able to commit external confirmed funds to the HTLC transaction or the fee committed
/// to the HTLC transaction is greater in value than the HTLCs being claimed.
///
- /// [`InMemorySigner`]: crate::sign::InMemorySigner
- /// [`KeysManager::derive_channel_keys`]: crate::sign::KeysManager::derive_channel_keys
+ /// [`EcdsaChannelSigner`]: crate::sign::EcdsaChannelSigner
/// [`EcdsaChannelSigner::sign_holder_htlc_transaction`]: crate::sign::EcdsaChannelSigner::sign_holder_htlc_transaction
/// [`HTLCDescriptor::tx_input_witness`]: HTLCDescriptor::tx_input_witness
HTLCResolution {
+ /// The unique identifier for the claim of the HTLCs in the confirmed commitment
+ /// transaction.
+ ///
+ /// The identifier must map to the set of external UTXOs assigned to the claim, such that
+ /// they can be reused when a new claim with the same identifier needs to be made, resulting
+ /// in a fee-bumping attempt.
+ claim_id: ClaimId,
/// The target feerate that the resulting HTLC transaction must meet.
target_feerate_sat_per_1000_weight: u32,
/// The set of pending HTLCs on the confirmed commitment that need to be claimed, preferably
tx_lock_time: PackedLockTime,
},
}
+
+/// An input that must be included in a transaction when performing coin selection through
+/// [`CoinSelectionSource::select_confirmed_utxos`]. It is guaranteed to be a SegWit input, so it
+/// must have an empty [`TxIn::script_sig`] when spent.
+#[derive(Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
+pub struct Input {
+ /// The unique identifier of the input.
+ pub outpoint: OutPoint,
+ /// The UTXO being spent by the input.
+ pub previous_utxo: TxOut,
+ /// The upper-bound weight consumed by the input's full [`TxIn::script_sig`] and
+ /// [`TxIn::witness`], each with their lengths included, required to satisfy the output's
+ /// script.
+ pub satisfaction_weight: u64,
+}
+
+/// An unspent transaction output that is available to spend resulting from a successful
+/// [`CoinSelection`] attempt.
+#[derive(Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
+pub struct Utxo {
+ /// The unique identifier of the output.
+ pub outpoint: OutPoint,
+ /// The output to spend.
+ pub output: TxOut,
+ /// The upper-bound weight consumed by the input's full [`TxIn::script_sig`] and [`TxIn::witness`], each
+ /// with their lengths included, required to satisfy the output's script. The weight consumed by
+ /// the input's `script_sig` must account for [`WITNESS_SCALE_FACTOR`].
+ pub satisfaction_weight: u64,
+}
+
+impl Utxo {
+ const P2WPKH_WITNESS_WEIGHT: u64 = 1 /* num stack items */ +
+ 1 /* sig length */ +
+ 73 /* sig including sighash flag */ +
+ 1 /* pubkey length */ +
+ 33 /* pubkey */;
+
+ /// Returns a `Utxo` with the `satisfaction_weight` estimate for a legacy P2PKH output.
+ pub fn new_p2pkh(outpoint: OutPoint, value: u64, pubkey_hash: &PubkeyHash) -> Self {
+ let script_sig_size = 1 /* script_sig length */ +
+ 1 /* OP_PUSH73 */ +
+ 73 /* sig including sighash flag */ +
+ 1 /* OP_PUSH33 */ +
+ 33 /* pubkey */;
+ Self {
+ outpoint,
+ output: TxOut {
+ value,
+ script_pubkey: Script::new_p2pkh(pubkey_hash),
+ },
+ satisfaction_weight: script_sig_size * WITNESS_SCALE_FACTOR as u64 + 1 /* empty witness */,
+ }
+ }
+
+ /// Returns a `Utxo` with the `satisfaction_weight` estimate for a P2WPKH nested in P2SH output.
+ pub fn new_nested_p2wpkh(outpoint: OutPoint, value: u64, pubkey_hash: &WPubkeyHash) -> Self {
+ let script_sig_size = 1 /* script_sig length */ +
+ 1 /* OP_0 */ +
+ 1 /* OP_PUSH20 */ +
+ 20 /* pubkey_hash */;
+ Self {
+ outpoint,
+ output: TxOut {
+ value,
+ script_pubkey: Script::new_p2sh(&Script::new_v0_p2wpkh(pubkey_hash).script_hash()),
+ },
+ satisfaction_weight: script_sig_size * WITNESS_SCALE_FACTOR as u64 + Self::P2WPKH_WITNESS_WEIGHT,
+ }
+ }
+
+ /// Returns a `Utxo` with the `satisfaction_weight` estimate for a SegWit v0 P2WPKH output.
+ pub fn new_v0_p2wpkh(outpoint: OutPoint, value: u64, pubkey_hash: &WPubkeyHash) -> Self {
+ Self {
+ outpoint,
+ output: TxOut {
+ value,
+ script_pubkey: Script::new_v0_p2wpkh(pubkey_hash),
+ },
+ satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT + Self::P2WPKH_WITNESS_WEIGHT,
+ }
+ }
+}
+
+/// The result of a successful coin selection attempt for a transaction requiring additional UTXOs
+/// to cover its fees.
+#[derive(Clone, Debug)]
+pub struct CoinSelection {
+ /// The set of UTXOs (with at least 1 confirmation) to spend and use within a transaction
+ /// requiring additional fees.
+ pub confirmed_utxos: Vec<Utxo>,
+ /// An additional output tracking whether any change remained after coin selection. This output
+ /// should always have a value above dust for its given `script_pubkey`. It should not be
+ /// spent until the transaction it belongs to confirms to ensure mempool descendant limits are
+ /// not met. This implies no other party should be able to spend it except us.
+ pub change_output: Option<TxOut>,
+}
+
+/// An abstraction over a bitcoin wallet that can perform coin selection over a set of UTXOs and can
+/// sign for them. The coin selection method aims to mimic Bitcoin Core's `fundrawtransaction` RPC,
+/// which most wallets should be able to satisfy. Otherwise, consider implementing [`WalletSource`],
+/// which can provide a default implementation of this trait when used with [`Wallet`].
+pub trait CoinSelectionSource {
+ /// Performs coin selection of a set of UTXOs, with at least 1 confirmation each, that are
+ /// available to spend. Implementations are free to pick their coin selection algorithm of
+ /// choice, as long as the following requirements are met:
+ ///
+ /// 1. `must_spend` contains a set of [`Input`]s that must be included in the transaction
+ /// throughout coin selection, but must not be returned as part of the result.
+ /// 2. `must_pay_to` contains a set of [`TxOut`]s that must be included in the transaction
+ /// throughout coin selection. In some cases, like when funding an anchor transaction, this
+ /// set is empty. Implementations should ensure they handle this correctly on their end,
+ /// e.g., Bitcoin Core's `fundrawtransaction` RPC requires at least one output to be
+ /// provided, in which case a zero-value empty OP_RETURN output can be used instead.
+ /// 3. Enough inputs must be selected/contributed for the resulting transaction (including the
+ /// inputs and outputs noted above) to meet `target_feerate_sat_per_1000_weight`.
+ ///
+ /// Implementations must take note that [`Input::satisfaction_weight`] only tracks the weight of
+ /// the input's `script_sig` and `witness`. Some wallets, like Bitcoin Core's, may require
+ /// providing the full input weight. Failing to do so may lead to underestimating fee bumps and
+ /// delaying block inclusion.
+ ///
+ /// The `claim_id` must map to the set of external UTXOs assigned to the claim, such that they
+ /// can be re-used within new fee-bumped iterations of the original claiming transaction,
+ /// ensuring that claims don't double spend each other. If a specific `claim_id` has never had a
+ /// transaction associated with it, and all of the available UTXOs have already been assigned to
+ /// other claims, implementations must be willing to double spend their UTXOs. The choice of
+ /// which UTXOs to double spend is left to the implementation, but it must strive to keep the
+ /// set of other claims being double spent to a minimum.
+ fn select_confirmed_utxos(
+ &self, claim_id: ClaimId, must_spend: Vec<Input>, must_pay_to: &[TxOut],
+ target_feerate_sat_per_1000_weight: u32,
+ ) -> Result<CoinSelection, ()>;
+ /// Signs and provides the full witness for all inputs within the transaction known to the
+ /// trait (i.e., any provided via [`CoinSelectionSource::select_confirmed_utxos`]).
+ fn sign_tx(&self, tx: Transaction) -> Result<Transaction, ()>;
+}
+
+/// An alternative to [`CoinSelectionSource`] that can be implemented and used along [`Wallet`] to
+/// provide a default implementation to [`CoinSelectionSource`].
+pub trait WalletSource {
+ /// Returns all UTXOs, with at least 1 confirmation each, that are available to spend.
+ fn list_confirmed_utxos(&self) -> Result<Vec<Utxo>, ()>;
+ /// Returns a script to use for change above dust resulting from a successful coin selection
+ /// attempt.
+ fn get_change_script(&self) -> Result<Script, ()>;
+ /// Signs and provides the full [`TxIn::script_sig`] and [`TxIn::witness`] for all inputs within
+ /// the transaction known to the wallet (i.e., any provided via
+ /// [`WalletSource::list_confirmed_utxos`]).
+ fn sign_tx(&self, tx: Transaction) -> Result<Transaction, ()>;
+}
+
+/// A wrapper over [`WalletSource`] that implements [`CoinSelection`] by preferring UTXOs that would
+/// avoid conflicting double spends. If not enough UTXOs are available to do so, conflicting double
+/// spends may happen.
+pub struct Wallet<W: Deref, L: Deref>
+where
+ W::Target: WalletSource,
+ L::Target: Logger
+{
+ source: W,
+ logger: L,
+ // TODO: Do we care about cleaning this up once the UTXOs have a confirmed spend? We can do so
+ // by checking whether any UTXOs that exist in the map are no longer returned in
+ // `list_confirmed_utxos`.
+ locked_utxos: Mutex<HashMap<OutPoint, ClaimId>>,
+}
+
+impl<W: Deref, L: Deref> Wallet<W, L>
+where
+ W::Target: WalletSource,
+ L::Target: Logger
+{
+ /// Returns a new instance backed by the given [`WalletSource`] that serves as an implementation
+ /// of [`CoinSelectionSource`].
+ pub fn new(source: W, logger: L) -> Self {
+ Self { source, logger, locked_utxos: Mutex::new(HashMap::new()) }
+ }
+
+ /// Performs coin selection on the set of UTXOs obtained from
+ /// [`WalletSource::list_confirmed_utxos`]. Its algorithm can be described as "smallest
+ /// above-dust-after-spend first", with a slight twist: we may skip UTXOs that are above dust at
+ /// the target feerate after having spent them in a separate claim transaction if
+ /// `force_conflicting_utxo_spend` is unset to avoid producing conflicting transactions. If
+ /// `tolerate_high_network_feerates` is set, we'll attempt to spend UTXOs that contribute at
+ /// least 1 satoshi at the current feerate, otherwise, we'll only attempt to spend those which
+ /// contribute at least twice their fee.
+ fn select_confirmed_utxos_internal(
+ &self, utxos: &[Utxo], claim_id: ClaimId, force_conflicting_utxo_spend: bool,
+ tolerate_high_network_feerates: bool, target_feerate_sat_per_1000_weight: u32,
+ preexisting_tx_weight: u64, target_amount_sat: u64,
+ ) -> Result<CoinSelection, ()> {
+ let mut locked_utxos = self.locked_utxos.lock().unwrap();
+ let mut eligible_utxos = utxos.iter().filter_map(|utxo| {
+ if let Some(utxo_claim_id) = locked_utxos.get(&utxo.outpoint) {
+ if *utxo_claim_id != claim_id && !force_conflicting_utxo_spend {
+ log_trace!(self.logger, "Skipping UTXO {} to prevent conflicting spend", utxo.outpoint);
+ return None;
+ }
+ }
+ let fee_to_spend_utxo = fee_for_weight(
+ target_feerate_sat_per_1000_weight, BASE_INPUT_WEIGHT as u64 + utxo.satisfaction_weight,
+ );
+ let should_spend = if tolerate_high_network_feerates {
+ utxo.output.value > fee_to_spend_utxo
+ } else {
+ utxo.output.value >= fee_to_spend_utxo * 2
+ };
+ if should_spend {
+ Some((utxo, fee_to_spend_utxo))
+ } else {
+ log_trace!(self.logger, "Skipping UTXO {} due to dust proximity after spend", utxo.outpoint);
+ None
+ }
+ }).collect::<Vec<_>>();
+ eligible_utxos.sort_unstable_by_key(|(utxo, _)| utxo.output.value);
+
+ let mut selected_amount = 0;
+ let mut total_fees = fee_for_weight(target_feerate_sat_per_1000_weight, preexisting_tx_weight);
+ let mut selected_utxos = Vec::new();
+ for (utxo, fee_to_spend_utxo) in eligible_utxos {
+ if selected_amount >= target_amount_sat + total_fees {
+ break;
+ }
+ selected_amount += utxo.output.value;
+ total_fees += fee_to_spend_utxo;
+ selected_utxos.push(utxo.clone());
+ }
+ if selected_amount < target_amount_sat + total_fees {
+ log_debug!(self.logger, "Insufficient funds to meet target feerate {} sat/kW",
+ target_feerate_sat_per_1000_weight);
+ return Err(());
+ }
+ for utxo in &selected_utxos {
+ locked_utxos.insert(utxo.outpoint, claim_id);
+ }
+ core::mem::drop(locked_utxos);
+
+ let remaining_amount = selected_amount - target_amount_sat - total_fees;
+ let change_script = self.source.get_change_script()?;
+ let change_output_fee = fee_for_weight(
+ target_feerate_sat_per_1000_weight,
+ (8 /* value */ + change_script.consensus_encode(&mut sink()).unwrap() as u64) *
+ WITNESS_SCALE_FACTOR as u64,
+ );
+ let change_output_amount = remaining_amount.saturating_sub(change_output_fee);
+ let change_output = if change_output_amount < change_script.dust_value().to_sat() {
+ log_debug!(self.logger, "Coin selection attempt did not yield change output");
+ None
+ } else {
+ Some(TxOut { script_pubkey: change_script, value: change_output_amount })
+ };
+
+ Ok(CoinSelection {
+ confirmed_utxos: selected_utxos,
+ change_output,
+ })
+ }
+}
+
+impl<W: Deref, L: Deref> CoinSelectionSource for Wallet<W, L>
+where
+ W::Target: WalletSource,
+ L::Target: Logger
+{
+ fn select_confirmed_utxos(
+ &self, claim_id: ClaimId, must_spend: Vec<Input>, must_pay_to: &[TxOut],
+ target_feerate_sat_per_1000_weight: u32,
+ ) -> Result<CoinSelection, ()> {
+ let utxos = self.source.list_confirmed_utxos()?;
+ // TODO: Use fee estimation utils when we upgrade to bitcoin v0.30.0.
+ const BASE_TX_SIZE: u64 = 4 /* version */ + 1 /* input count */ + 1 /* output count */ + 4 /* locktime */;
+ let total_output_size: u64 = must_pay_to.iter().map(|output|
+ 8 /* value */ + 1 /* script len */ + output.script_pubkey.len() as u64
+ ).sum();
+ let total_satisfaction_weight: u64 = must_spend.iter().map(|input| input.satisfaction_weight).sum();
+ let total_input_weight = (BASE_INPUT_WEIGHT * must_spend.len() as u64) + total_satisfaction_weight;
+
+ let preexisting_tx_weight = 2 /* segwit marker & flag */ + total_input_weight +
+ ((BASE_TX_SIZE + total_output_size) * WITNESS_SCALE_FACTOR as u64);
+ let target_amount_sat = must_pay_to.iter().map(|output| output.value).sum();
+ let do_coin_selection = |force_conflicting_utxo_spend: bool, tolerate_high_network_feerates: bool| {
+ log_debug!(self.logger, "Attempting coin selection targeting {} sat/kW (force_conflicting_utxo_spend = {}, tolerate_high_network_feerates = {})",
+ target_feerate_sat_per_1000_weight, force_conflicting_utxo_spend, tolerate_high_network_feerates);
+ self.select_confirmed_utxos_internal(
+ &utxos, claim_id, force_conflicting_utxo_spend, tolerate_high_network_feerates,
+ target_feerate_sat_per_1000_weight, preexisting_tx_weight, target_amount_sat,
+ )
+ };
+ do_coin_selection(false, false)
+ .or_else(|_| do_coin_selection(false, true))
+ .or_else(|_| do_coin_selection(true, false))
+ .or_else(|_| do_coin_selection(true, true))
+ }
+
+ fn sign_tx(&self, tx: Transaction) -> Result<Transaction, ()> {
+ self.source.sign_tx(tx)
+ }
+}
+
+/// A handler for [`Event::BumpTransaction`] events that sources confirmed UTXOs from a
+/// [`CoinSelectionSource`] to fee bump transactions via Child-Pays-For-Parent (CPFP) or
+/// Replace-By-Fee (RBF).
+///
+/// [`Event::BumpTransaction`]: crate::events::Event::BumpTransaction
+pub struct BumpTransactionEventHandler<B: Deref, C: Deref, SP: Deref, L: Deref>
+where
+ B::Target: BroadcasterInterface,
+ C::Target: CoinSelectionSource,
+ SP::Target: SignerProvider,
+ L::Target: Logger,
+{
+ broadcaster: B,
+ utxo_source: C,
+ signer_provider: SP,
+ logger: L,
+ secp: Secp256k1<secp256k1::All>,
+}
+
+impl<B: Deref, C: Deref, SP: Deref, L: Deref> BumpTransactionEventHandler<B, C, SP, L>
+where
+ B::Target: BroadcasterInterface,
+ C::Target: CoinSelectionSource,
+ SP::Target: SignerProvider,
+ L::Target: Logger,
+{
+ /// Returns a new instance capable of handling [`Event::BumpTransaction`] events.
+ ///
+ /// [`Event::BumpTransaction`]: crate::events::Event::BumpTransaction
+ pub fn new(broadcaster: B, utxo_source: C, signer_provider: SP, logger: L) -> Self {
+ Self {
+ broadcaster,
+ utxo_source,
+ signer_provider,
+ logger,
+ secp: Secp256k1::new(),
+ }
+ }
+
+ /// Updates a transaction with the result of a successful coin selection attempt.
+ fn process_coin_selection(&self, tx: &mut Transaction, mut coin_selection: CoinSelection) {
+ for utxo in coin_selection.confirmed_utxos.drain(..) {
+ tx.input.push(TxIn {
+ previous_output: utxo.outpoint,
+ script_sig: Script::new(),
+ sequence: Sequence::ZERO,
+ witness: Witness::new(),
+ });
+ }
+ if let Some(change_output) = coin_selection.change_output.take() {
+ tx.output.push(change_output);
+ } else if tx.output.is_empty() {
+ // We weren't provided a change output, likely because the input set was a perfect
+ // match, but we still need to have at least one output in the transaction for it to be
+ // considered standard. We choose to go with an empty OP_RETURN as it is the cheapest
+ // way to include a dummy output.
+ log_debug!(self.logger, "Including dummy OP_RETURN output since an output is needed and a change output was not provided");
+ tx.output.push(TxOut {
+ value: 0,
+ script_pubkey: Script::new_op_return(&[]),
+ });
+ }
+ }
+
+ /// Handles a [`BumpTransactionEvent::ChannelClose`] event variant by producing a fully-signed
+ /// transaction spending an anchor output of the commitment transaction to bump its fee and
+ /// broadcasts them to the network as a package.
+ fn handle_channel_close(
+ &self, claim_id: ClaimId, package_target_feerate_sat_per_1000_weight: u32,
+ commitment_tx: &Transaction, commitment_tx_fee_sat: u64, anchor_descriptor: &AnchorDescriptor,
+ ) -> Result<(), ()> {
+ // Our commitment transaction already has fees allocated to it, so we should take them into
+ // account. We compute its feerate and subtract it from the package target, using the result
+ // as the target feerate for our anchor transaction. Unfortunately, this results in users
+ // overpaying by a small margin since we don't yet know the anchor transaction size, and
+ // avoiding the small overpayment only makes our API even more complex.
+ let commitment_tx_sat_per_1000_weight: u32 = compute_feerate_sat_per_1000_weight(
+ commitment_tx_fee_sat, commitment_tx.weight() as u64,
+ );
+ let anchor_target_feerate_sat_per_1000_weight = core::cmp::max(
+ package_target_feerate_sat_per_1000_weight - commitment_tx_sat_per_1000_weight,
+ FEERATE_FLOOR_SATS_PER_KW,
+ );
+
+ log_debug!(self.logger, "Peforming coin selection for anchor transaction targeting {} sat/kW",
+ anchor_target_feerate_sat_per_1000_weight);
+ let must_spend = vec![Input {
+ outpoint: anchor_descriptor.outpoint,
+ previous_utxo: anchor_descriptor.previous_utxo(),
+ satisfaction_weight: commitment_tx.weight() as u64 + ANCHOR_INPUT_WITNESS_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT,
+ }];
+ let coin_selection = self.utxo_source.select_confirmed_utxos(
+ claim_id, must_spend, &[], anchor_target_feerate_sat_per_1000_weight,
+ )?;
+
+ let mut anchor_tx = Transaction {
+ version: 2,
+ lock_time: PackedLockTime::ZERO, // TODO: Use next best height.
+ input: vec![anchor_descriptor.unsigned_tx_input()],
+ output: vec![],
+ };
+ #[cfg(debug_assertions)]
+ let total_satisfaction_weight =
+ coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum::<u64>() +
+ ANCHOR_INPUT_WITNESS_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT;
+
+ self.process_coin_selection(&mut anchor_tx, coin_selection);
+ let anchor_txid = anchor_tx.txid();
+
+ debug_assert_eq!(anchor_tx.output.len(), 1);
+ #[cfg(debug_assertions)]
+ let unsigned_tx_weight = anchor_tx.weight() as u64 - (anchor_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT);
+
+ log_debug!(self.logger, "Signing anchor transaction {}", anchor_txid);
+ anchor_tx = self.utxo_source.sign_tx(anchor_tx)?;
+
+ let signer = anchor_descriptor.derive_channel_signer(&self.signer_provider);
+ let anchor_sig = signer.sign_holder_anchor_input(&anchor_tx, 0, &self.secp)?;
+ anchor_tx.input[0].witness = anchor_descriptor.tx_input_witness(&anchor_sig);
+
+ #[cfg(debug_assertions)] {
+ let signed_tx_weight = anchor_tx.weight() as u64;
+ let expected_signed_tx_weight = unsigned_tx_weight + total_satisfaction_weight;
+ // Our estimate should be within a 1% error margin of the actual weight and we should
+ // never underestimate.
+ assert!(expected_signed_tx_weight >= signed_tx_weight &&
+ expected_signed_tx_weight - (expected_signed_tx_weight / 100) <= signed_tx_weight);
+ }
+
+ log_info!(self.logger, "Broadcasting anchor transaction {} to bump channel close with txid {}",
+ anchor_txid, commitment_tx.txid());
+ self.broadcaster.broadcast_transactions(&[&commitment_tx, &anchor_tx]);
+ Ok(())
+ }
+
+ /// Handles a [`BumpTransactionEvent::HTLCResolution`] event variant by producing a
+ /// fully-signed, fee-bumped HTLC transaction that is broadcast to the network.
+ fn handle_htlc_resolution(
+ &self, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32,
+ htlc_descriptors: &[HTLCDescriptor], tx_lock_time: PackedLockTime,
+ ) -> Result<(), ()> {
+ let mut htlc_tx = Transaction {
+ version: 2,
+ lock_time: tx_lock_time,
+ input: vec![],
+ output: vec![],
+ };
+ let mut must_spend = Vec::with_capacity(htlc_descriptors.len());
+ for htlc_descriptor in htlc_descriptors {
+ let htlc_input = htlc_descriptor.unsigned_tx_input();
+ must_spend.push(Input {
+ outpoint: htlc_input.previous_output.clone(),
+ previous_utxo: htlc_descriptor.previous_utxo(&self.secp),
+ satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT + if htlc_descriptor.preimage.is_some() {
+ HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT
+ } else {
+ HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT
+ },
+ });
+ htlc_tx.input.push(htlc_input);
+ let htlc_output = htlc_descriptor.tx_output(&self.secp);
+ htlc_tx.output.push(htlc_output);
+ }
+
+ log_debug!(self.logger, "Peforming coin selection for HTLC transaction targeting {} sat/kW",
+ target_feerate_sat_per_1000_weight);
+ #[cfg(debug_assertions)]
+ let must_spend_satisfaction_weight =
+ must_spend.iter().map(|input| input.satisfaction_weight).sum::<u64>();
+ let coin_selection = self.utxo_source.select_confirmed_utxos(
+ claim_id, must_spend, &htlc_tx.output, target_feerate_sat_per_1000_weight,
+ )?;
+ #[cfg(debug_assertions)]
+ let total_satisfaction_weight =
+ coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum::<u64>() +
+ must_spend_satisfaction_weight;
+ self.process_coin_selection(&mut htlc_tx, coin_selection);
+
+ #[cfg(debug_assertions)]
+ let unsigned_tx_weight = htlc_tx.weight() as u64 - (htlc_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT);
+
+ log_debug!(self.logger, "Signing HTLC transaction {}", htlc_tx.txid());
+ htlc_tx = self.utxo_source.sign_tx(htlc_tx)?;
+
+ let mut signers = BTreeMap::new();
+ for (idx, htlc_descriptor) in htlc_descriptors.iter().enumerate() {
+ let signer = signers.entry(htlc_descriptor.channel_derivation_parameters.keys_id)
+ .or_insert_with(|| htlc_descriptor.derive_channel_signer(&self.signer_provider));
+ let htlc_sig = signer.sign_holder_htlc_transaction(&htlc_tx, idx, htlc_descriptor, &self.secp)?;
+ let witness_script = htlc_descriptor.witness_script(&self.secp);
+ htlc_tx.input[idx].witness = htlc_descriptor.tx_input_witness(&htlc_sig, &witness_script);
+ }
+
+ #[cfg(debug_assertions)] {
+ let signed_tx_weight = htlc_tx.weight() as u64;
+ let expected_signed_tx_weight = unsigned_tx_weight + total_satisfaction_weight;
+ // Our estimate should be within a 1% error margin of the actual weight and we should
+ // never underestimate.
+ assert!(expected_signed_tx_weight >= signed_tx_weight &&
+ expected_signed_tx_weight - (expected_signed_tx_weight / 100) <= signed_tx_weight);
+ }
+
+ log_info!(self.logger, "Broadcasting {}", log_tx!(htlc_tx));
+ self.broadcaster.broadcast_transactions(&[&htlc_tx]);
+ Ok(())
+ }
+
+ /// Handles all variants of [`BumpTransactionEvent`].
+ pub fn handle_event(&self, event: &BumpTransactionEvent) {
+ match event {
+ BumpTransactionEvent::ChannelClose {
+ claim_id, package_target_feerate_sat_per_1000_weight, commitment_tx,
+ commitment_tx_fee_satoshis, anchor_descriptor, ..
+ } => {
+ log_info!(self.logger, "Handling channel close bump (claim_id = {}, commitment_txid = {})",
+ log_bytes!(claim_id.0), commitment_tx.txid());
+ if let Err(_) = self.handle_channel_close(
+ *claim_id, *package_target_feerate_sat_per_1000_weight, commitment_tx,
+ *commitment_tx_fee_satoshis, anchor_descriptor,
+ ) {
+ log_error!(self.logger, "Failed bumping commitment transaction fee for {}",
+ commitment_tx.txid());
+ }
+ }
+ BumpTransactionEvent::HTLCResolution {
+ claim_id, target_feerate_sat_per_1000_weight, htlc_descriptors, tx_lock_time,
+ } => {
+ log_info!(self.logger, "Handling HTLC bump (claim_id = {}, htlcs_to_claim = {})",
+ log_bytes!(claim_id.0), log_iter!(htlc_descriptors.iter().map(|d| d.outpoint())));
+ if let Err(_) = self.handle_htlc_resolution(
+ *claim_id, *target_feerate_sat_per_1000_weight, htlc_descriptors, *tx_lock_time,
+ ) {
+ log_error!(self.logger, "Failed bumping HTLC transaction fee for commitment {}",
+ htlc_descriptors[0].commitment_txid);
+ }
+ }
+ }
+ }
+}
//! future, as well as generate and broadcast funding transactions handle payment preimages and a
//! few other things.
-#[cfg(anchors)]
pub mod bump_transaction;
-#[cfg(anchors)]
pub use bump_transaction::BumpTransactionEvent;
use crate::sign::SpendableOutputDescriptor;
use crate::routing::router::{BlindedTail, Path, RouteHop, RouteParameters};
use bitcoin::{PackedLockTime, Transaction, OutPoint};
-#[cfg(anchors)]
-use bitcoin::{Txid, TxIn, TxOut, Witness};
use bitcoin::blockdata::script::Script;
use bitcoin::hashes::Hash;
use bitcoin::hashes::sha256::Hash as Sha256;
);
#[derive(Clone, Debug, PartialEq, Eq)]
-/// The reason the channel was closed. See individual variants more details.
+/// The reason the channel was closed. See individual variants for more details.
pub enum ClosureReason {
/// Closure generated from receiving a peer error message.
///
///
/// [`ChannelMonitor`]: crate::chain::channelmonitor::ChannelMonitor
/// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
- OutdatedChannelManager
+ OutdatedChannelManager,
+ /// The counterparty requested a cooperative close of a channel that had not been funded yet.
+ /// The channel has been immediately closed.
+ CounterpartyCoopClosedUnfundedChannel,
}
impl core::fmt::Display for ClosureReason {
},
ClosureReason::DisconnectedPeer => f.write_str("the peer disconnected prior to the channel being funded"),
ClosureReason::OutdatedChannelManager => f.write_str("the ChannelManager read from disk was stale compared to ChannelMonitor(s)"),
+ ClosureReason::CounterpartyCoopClosedUnfundedChannel => f.write_str("the peer requested the unfunded channel be closed"),
}
}
}
(8, ProcessingError) => { (1, err, required) },
(10, DisconnectedPeer) => {},
(12, OutdatedChannelManager) => {},
+ (13, CounterpartyCoopClosedUnfundedChannel) => {},
);
/// Intended destination of a failed HTLC as indicated in [`Event::HTLCHandlingFailed`].
///
/// Payments received on LDK versions prior to 0.0.115 will have this field unset.
onion_fields: Option<RecipientOnionFields>,
- /// The value, in thousandths of a satoshi, that this payment is for.
+ /// The value, in thousandths of a satoshi, that this payment is claimable for. May be greater
+ /// than the invoice amount.
+ ///
+ /// May be less than the invoice amount if [`ChannelConfig::accept_underpaying_htlcs`] is set
+ /// and the previous hop took an extra fee.
+ ///
+ /// # Note
+ /// If [`ChannelConfig::accept_underpaying_htlcs`] is set and you claim without verifying this
+ /// field, you may lose money!
+ ///
+ /// [`ChannelConfig::accept_underpaying_htlcs`]: crate::util::config::ChannelConfig::accept_underpaying_htlcs
amount_msat: u64,
+ /// The value, in thousands of a satoshi, that was skimmed off of this payment as an extra fee
+ /// taken by our channel counterparty.
+ ///
+ /// Will always be 0 unless [`ChannelConfig::accept_underpaying_htlcs`] is set.
+ ///
+ /// [`ChannelConfig::accept_underpaying_htlcs`]: crate::util::config::ChannelConfig::accept_underpaying_htlcs
+ counterparty_skimmed_fee_msat: u64,
/// Information for claiming this received payment, based on whether the purpose of the
/// payment is to pay an invoice or to send a spontaneous payment.
purpose: PaymentPurpose,
/// The payment hash of the claimed payment. Note that LDK will not stop you from
/// registering duplicate payment hashes for inbound payments.
payment_hash: PaymentHash,
- /// The value, in thousandths of a satoshi, that this payment is for.
+ /// The value, in thousandths of a satoshi, that this payment is for. May be greater than the
+ /// invoice amount.
amount_msat: u64,
/// The purpose of the claimed payment, i.e. whether the payment was for an invoice or a
/// spontaneous payment.
inbound_amount_msat: u64,
/// How many msats the payer intended to route to the next node. Depending on the reason you are
/// intercepting this payment, you might take a fee by forwarding less than this amount.
+ /// Forwarding less than this amount may break compatibility with LDK versions prior to 0.0.116.
///
/// Note that LDK will NOT check that expected fees were factored into this value. You MUST
/// check that whatever fee you want has been included here or subtract it as required. Further,
/// Destination of the HTLC that failed to be processed.
failed_next_destination: HTLCDestination,
},
- #[cfg(anchors)]
/// Indicates that a transaction originating from LDK needs to have its fee bumped. This event
/// requires confirmed external funds to be readily available to spend.
///
- /// LDK does not currently generate this event. It is limited to the scope of channels with
- /// anchor outputs, which will be introduced in a future release.
+ /// LDK does not currently generate this event unless the
+ /// [`ChannelHandshakeConfig::negotiate_anchors_zero_fee_htlc_tx`] config flag is set to true.
+ /// It is limited to the scope of channels with anchor outputs.
+ ///
+ /// [`ChannelHandshakeConfig::negotiate_anchors_zero_fee_htlc_tx`]: crate::util::config::ChannelHandshakeConfig::negotiate_anchors_zero_fee_htlc_tx
BumpTransaction(BumpTransactionEvent),
}
// We never write out FundingGenerationReady events as, upon disconnection, peers
// drop any channels which have not yet exchanged funding_signed.
},
- &Event::PaymentClaimable { ref payment_hash, ref amount_msat, ref purpose,
- ref receiver_node_id, ref via_channel_id, ref via_user_channel_id,
+ &Event::PaymentClaimable { ref payment_hash, ref amount_msat, counterparty_skimmed_fee_msat,
+ ref purpose, ref receiver_node_id, ref via_channel_id, ref via_user_channel_id,
ref claim_deadline, ref onion_fields
} => {
1u8.write(writer)?;
payment_preimage = Some(*preimage);
}
}
+ let skimmed_fee_opt = if counterparty_skimmed_fee_msat == 0 { None }
+ else { Some(counterparty_skimmed_fee_msat) };
write_tlv_fields!(writer, {
(0, payment_hash, required),
(1, receiver_node_id, option),
(3, via_channel_id, option),
(4, amount_msat, required),
(5, via_user_channel_id, option),
- (6, 0u64, required), // user_payment_id required for compatibility with 0.0.103 and earlier
+ // Type 6 was `user_payment_id` on 0.0.103 and earlier
(7, claim_deadline, option),
(8, payment_preimage, option),
(9, onion_fields, option),
+ (10, skimmed_fee_opt, option),
});
},
&Event::PaymentSent { ref payment_id, ref payment_preimage, ref payment_hash, ref fee_paid_msat } => {
(2, payment_failed_permanently, required),
(3, false, required), // all_paths_failed in LDK versions prior to 0.0.114
(4, path.blinded_tail, option),
- (5, path.hops, vec_type),
+ (5, path.hops, required_vec),
(7, short_channel_id, option),
(9, None::<RouteParameters>, option), // retry in LDK versions prior to 0.0.115
(11, payment_id, option),
write_tlv_fields!(writer, {
(0, payment_id, required),
(2, payment_hash, option),
- (4, path.hops, vec_type),
+ (4, path.hops, required_vec),
(6, path.blinded_tail, option),
})
},
write_tlv_fields!(writer, {
(0, payment_id, required),
(2, payment_hash, required),
- (4, path.hops, vec_type),
+ (4, path.hops, required_vec),
(6, path.blinded_tail, option),
})
},
write_tlv_fields!(writer, {
(0, payment_id, required),
(2, payment_hash, required),
- (4, path.hops, vec_type),
+ (4, path.hops, required_vec),
(6, short_channel_id, option),
(8, path.blinded_tail, option),
})
(2, failed_next_destination, required),
})
},
- #[cfg(anchors)]
&Event::BumpTransaction(ref event)=> {
27u8.write(writer)?;
match event {
let mut payment_preimage = None;
let mut payment_secret = None;
let mut amount_msat = 0;
+ let mut counterparty_skimmed_fee_msat_opt = None;
let mut receiver_node_id = None;
- let mut _user_payment_id = None::<u64>; // For compatibility with 0.0.103 and earlier
+ let mut _user_payment_id = None::<u64>; // Used in 0.0.103 and earlier, no longer written in 0.0.116+.
let mut via_channel_id = None;
let mut claim_deadline = None;
let mut via_user_channel_id = None;
(7, claim_deadline, option),
(8, payment_preimage, option),
(9, onion_fields, option),
+ (10, counterparty_skimmed_fee_msat_opt, option),
});
let purpose = match payment_secret {
Some(secret) => PaymentPurpose::InvoicePayment {
receiver_node_id,
payment_hash,
amount_msat,
+ counterparty_skimmed_fee_msat: counterparty_skimmed_fee_msat_opt.unwrap_or(0),
purpose,
via_channel_id,
via_user_channel_id,
(1, network_update, upgradable_option),
(2, payment_failed_permanently, required),
(4, blinded_tail, option),
- (5, path, vec_type),
+ // Added as a part of LDK 0.0.101 and always filled in since.
+ // Defaults to an empty Vec, though likely should have been `Option`al.
+ (5, path, optional_vec),
(7, short_channel_id, option),
(11, payment_id, option),
(13, failure_opt, upgradable_option),
_init_and_read_tlv_fields!(reader, {
(0, payment_id, required),
(2, payment_hash, option),
- (4, path, vec_type),
+ (4, path, required_vec),
(6, blinded_tail, option),
});
Ok(Some(Event::PaymentPathSuccessful {
payment_id: payment_id.0.unwrap(),
payment_hash,
- path: Path { hops: path.unwrap(), blinded_tail },
+ path: Path { hops: path, blinded_tail },
}))
};
f()
_init_and_read_tlv_fields!(reader, {
(0, payment_id, required),
(2, payment_hash, required),
- (4, path, vec_type),
+ (4, path, required_vec),
(6, blinded_tail, option),
});
Ok(Some(Event::ProbeSuccessful {
payment_id: payment_id.0.unwrap(),
payment_hash: payment_hash.0.unwrap(),
- path: Path { hops: path.unwrap(), blinded_tail },
+ path: Path { hops: path, blinded_tail },
}))
};
f()
_init_and_read_tlv_fields!(reader, {
(0, payment_id, required),
(2, payment_hash, required),
- (4, path, vec_type),
+ (4, path, required_vec),
(6, short_channel_id, option),
(8, blinded_tail, option),
});
Ok(Some(Event::ProbeFailed {
payment_id: payment_id.0.unwrap(),
payment_hash: payment_hash.0.unwrap(),
- path: Path { hops: path.unwrap(), blinded_tail },
+ path: Path { hops: path, blinded_tail },
short_channel_id,
}))
};
//! * `max_level_trace`
#![cfg_attr(not(any(test, fuzzing, feature = "_test_utils")), deny(missing_docs))]
-#![cfg_attr(not(any(test, fuzzing, feature = "_test_utils")), forbid(unsafe_code))]
+#![cfg_attr(not(any(test, feature = "_test_utils")), forbid(unsafe_code))]
// Prefix these with `rustdoc::` when we update our MSRV to be >= 1.52 to remove warnings.
#![deny(broken_intra_doc_links)]
extern crate core;
#[cfg(any(test, feature = "_test_utils"))] extern crate hex;
-#[cfg(any(test, fuzzing, feature = "_test_utils"))] extern crate regex;
+#[cfg(any(test, feature = "_test_utils"))] extern crate regex;
#[cfg(not(feature = "std"))] extern crate core2;
use crate::sign::EntropySource;
use crate::ln::{PaymentHash, PaymentPreimage};
use crate::ln::msgs::DecodeError;
-use crate::util::ser::{Readable, Writeable, Writer};
+use crate::util::ser::{Readable, RequiredWrapper, Writeable, Writer};
use crate::util::transaction_utils;
use bitcoin::secp256k1::{SecretKey, PublicKey, Scalar};
use crate::ln::channel::{INITIAL_COMMITMENT_NUMBER, ANCHOR_OUTPUT_VALUE_SATOSHI};
use core::ops::Deref;
use crate::chain;
+use crate::ln::features::ChannelTypeFeatures;
use crate::util::crypto::{sign, sign_with_aux_rand};
/// Maximum number of one-way in-flight HTLC (protocol-level value).
/// This is the maximum post-anchor value.
pub const MAX_ACCEPTED_HTLC_SCRIPT_WEIGHT: usize = 143;
+/// The upper bound weight of an anchor input.
+pub const ANCHOR_INPUT_WITNESS_WEIGHT: u64 = 116;
+/// The upper bound weight of an HTLC timeout input from a commitment transaction with anchor
+/// outputs.
+pub const HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT: u64 = 288;
+/// The upper bound weight of an HTLC success input from a commitment transaction with anchor
+/// outputs.
+pub const HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT: u64 = 327;
+
/// Gets the weight for an HTLC-Success transaction.
#[inline]
-pub fn htlc_success_tx_weight(opt_anchors: bool) -> u64 {
+pub fn htlc_success_tx_weight(channel_type_features: &ChannelTypeFeatures) -> u64 {
const HTLC_SUCCESS_TX_WEIGHT: u64 = 703;
const HTLC_SUCCESS_ANCHOR_TX_WEIGHT: u64 = 706;
- if opt_anchors { HTLC_SUCCESS_ANCHOR_TX_WEIGHT } else { HTLC_SUCCESS_TX_WEIGHT }
+ if channel_type_features.supports_anchors_zero_fee_htlc_tx() { HTLC_SUCCESS_ANCHOR_TX_WEIGHT } else { HTLC_SUCCESS_TX_WEIGHT }
}
/// Gets the weight for an HTLC-Timeout transaction.
#[inline]
-pub fn htlc_timeout_tx_weight(opt_anchors: bool) -> u64 {
+pub fn htlc_timeout_tx_weight(channel_type_features: &ChannelTypeFeatures) -> u64 {
const HTLC_TIMEOUT_TX_WEIGHT: u64 = 663;
const HTLC_TIMEOUT_ANCHOR_TX_WEIGHT: u64 = 666;
- if opt_anchors { HTLC_TIMEOUT_ANCHOR_TX_WEIGHT } else { HTLC_TIMEOUT_TX_WEIGHT }
+ if channel_type_features.supports_anchors_zero_fee_htlc_tx() { HTLC_TIMEOUT_ANCHOR_TX_WEIGHT } else { HTLC_TIMEOUT_TX_WEIGHT }
}
/// Describes the type of HTLC claim as determined by analyzing the witness.
});
#[inline]
-pub(crate) fn get_htlc_redeemscript_with_explicit_keys(htlc: &HTLCOutputInCommitment, opt_anchors: bool, broadcaster_htlc_key: &PublicKey, countersignatory_htlc_key: &PublicKey, revocation_key: &PublicKey) -> Script {
+pub(crate) fn get_htlc_redeemscript_with_explicit_keys(htlc: &HTLCOutputInCommitment, channel_type_features: &ChannelTypeFeatures, broadcaster_htlc_key: &PublicKey, countersignatory_htlc_key: &PublicKey, revocation_key: &PublicKey) -> Script {
let payment_hash160 = Ripemd160::hash(&htlc.payment_hash.0[..]).into_inner();
if htlc.offered {
let mut bldr = Builder::new().push_opcode(opcodes::all::OP_DUP)
.push_opcode(opcodes::all::OP_EQUALVERIFY)
.push_opcode(opcodes::all::OP_CHECKSIG)
.push_opcode(opcodes::all::OP_ENDIF);
- if opt_anchors {
+ if channel_type_features.supports_anchors_zero_fee_htlc_tx() {
bldr = bldr.push_opcode(opcodes::all::OP_PUSHNUM_1)
.push_opcode(opcodes::all::OP_CSV)
.push_opcode(opcodes::all::OP_DROP);
.push_opcode(opcodes::all::OP_DROP)
.push_opcode(opcodes::all::OP_CHECKSIG)
.push_opcode(opcodes::all::OP_ENDIF);
- if opt_anchors {
+ if channel_type_features.supports_anchors_zero_fee_htlc_tx() {
bldr = bldr.push_opcode(opcodes::all::OP_PUSHNUM_1)
.push_opcode(opcodes::all::OP_CSV)
.push_opcode(opcodes::all::OP_DROP);
/// Gets the witness redeemscript for an HTLC output in a commitment transaction. Note that htlc
/// does not need to have its previous_output_index filled.
#[inline]
-pub fn get_htlc_redeemscript(htlc: &HTLCOutputInCommitment, opt_anchors: bool, keys: &TxCreationKeys) -> Script {
- get_htlc_redeemscript_with_explicit_keys(htlc, opt_anchors, &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key)
+pub fn get_htlc_redeemscript(htlc: &HTLCOutputInCommitment, channel_type_features: &ChannelTypeFeatures, keys: &TxCreationKeys) -> Script {
+ get_htlc_redeemscript_with_explicit_keys(htlc, channel_type_features, &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key)
}
/// Gets the redeemscript for a funding output from the two funding public keys.
///
/// Panics if htlc.transaction_output_index.is_none() (as such HTLCs do not appear in the
/// commitment transaction).
-pub fn build_htlc_transaction(commitment_txid: &Txid, feerate_per_kw: u32, contest_delay: u16, htlc: &HTLCOutputInCommitment, opt_anchors: bool, use_non_zero_fee_anchors: bool, broadcaster_delayed_payment_key: &PublicKey, revocation_key: &PublicKey) -> Transaction {
+pub fn build_htlc_transaction(commitment_txid: &Txid, feerate_per_kw: u32, contest_delay: u16, htlc: &HTLCOutputInCommitment, channel_type_features: &ChannelTypeFeatures, broadcaster_delayed_payment_key: &PublicKey, revocation_key: &PublicKey) -> Transaction {
let mut txins: Vec<TxIn> = Vec::new();
- txins.push(build_htlc_input(commitment_txid, htlc, opt_anchors));
+ txins.push(build_htlc_input(commitment_txid, htlc, channel_type_features));
let mut txouts: Vec<TxOut> = Vec::new();
txouts.push(build_htlc_output(
- feerate_per_kw, contest_delay, htlc, opt_anchors, use_non_zero_fee_anchors,
+ feerate_per_kw, contest_delay, htlc, channel_type_features,
broadcaster_delayed_payment_key, revocation_key
));
}
}
-pub(crate) fn build_htlc_input(commitment_txid: &Txid, htlc: &HTLCOutputInCommitment, opt_anchors: bool) -> TxIn {
+pub(crate) fn build_htlc_input(commitment_txid: &Txid, htlc: &HTLCOutputInCommitment, channel_type_features: &ChannelTypeFeatures) -> TxIn {
TxIn {
previous_output: OutPoint {
txid: commitment_txid.clone(),
vout: htlc.transaction_output_index.expect("Can't build an HTLC transaction for a dust output"),
},
script_sig: Script::new(),
- sequence: Sequence(if opt_anchors { 1 } else { 0 }),
+ sequence: Sequence(if channel_type_features.supports_anchors_zero_fee_htlc_tx() { 1 } else { 0 }),
witness: Witness::new(),
}
}
pub(crate) fn build_htlc_output(
- feerate_per_kw: u32, contest_delay: u16, htlc: &HTLCOutputInCommitment, opt_anchors: bool,
- use_non_zero_fee_anchors: bool, broadcaster_delayed_payment_key: &PublicKey, revocation_key: &PublicKey
+ feerate_per_kw: u32, contest_delay: u16, htlc: &HTLCOutputInCommitment, channel_type_features: &ChannelTypeFeatures, broadcaster_delayed_payment_key: &PublicKey, revocation_key: &PublicKey
) -> TxOut {
let weight = if htlc.offered {
- htlc_timeout_tx_weight(opt_anchors)
+ htlc_timeout_tx_weight(channel_type_features)
} else {
- htlc_success_tx_weight(opt_anchors)
+ htlc_success_tx_weight(channel_type_features)
};
- let output_value = if opt_anchors && !use_non_zero_fee_anchors {
+ let output_value = if channel_type_features.supports_anchors_zero_fee_htlc_tx() && !channel_type_features.supports_anchors_nonzero_fee_htlc_tx() {
htlc.amount_msat / 1000
} else {
let total_fee = feerate_per_kw as u64 * weight / 1000;
/// Returns the witness required to satisfy and spend a HTLC input.
pub fn build_htlc_input_witness(
local_sig: &Signature, remote_sig: &Signature, preimage: &Option<PaymentPreimage>,
- redeem_script: &Script, opt_anchors: bool,
+ redeem_script: &Script, channel_type_features: &ChannelTypeFeatures,
) -> Witness {
- let remote_sighash_type = if opt_anchors {
+ let remote_sighash_type = if channel_type_features.supports_anchors_zero_fee_htlc_tx() {
EcdsaSighashType::SinglePlusAnyoneCanPay
} else {
EcdsaSighashType::All
witness
}
+/// Pre-anchors channel type features did not use to get serialized in the following six structs:
+/// — [`ChannelTransactionParameters`]
+/// — [`CommitmentTransaction`]
+/// — [`CounterpartyOfferedHTLCOutput`]
+/// — [`CounterpartyReceivedHTLCOutput`]
+/// — [`HolderHTLCOutput`]
+/// — [`HolderFundingOutput`]
+///
+/// To ensure a forwards-compatible serialization, we use odd TLV fields. However, if new features
+/// are used that could break security, where old signers should be prevented from handling the
+/// serialized data, an optional even-field TLV will be used as a stand-in to break compatibility.
+///
+/// This method determines whether or not that option needs to be set based on the chanenl type
+/// features, and returns it.
+///
+/// [`CounterpartyOfferedHTLCOutput`]: crate::chain::package::CounterpartyOfferedHTLCOutput
+/// [`CounterpartyReceivedHTLCOutput`]: crate::chain::package::CounterpartyReceivedHTLCOutput
+/// [`HolderHTLCOutput`]: crate::chain::package::HolderHTLCOutput
+/// [`HolderFundingOutput`]: crate::chain::package::HolderFundingOutput
+pub(crate) fn legacy_deserialization_prevention_marker_for_channel_type_features(features: &ChannelTypeFeatures) -> Option<()> {
+ let mut legacy_version_bit_set = ChannelTypeFeatures::only_static_remote_key();
+ legacy_version_bit_set.set_scid_privacy_required();
+ legacy_version_bit_set.set_zero_conf_required();
+
+ if features.is_subset(&legacy_version_bit_set) {
+ None
+ } else {
+ Some(())
+ }
+}
+
/// Gets the witnessScript for the to_remote output when anchors are enabled.
#[inline]
pub fn get_to_countersignatory_with_anchors_redeemscript(payment_point: &PublicKey) -> Script {
.into_script()
}
-#[cfg(anchors)]
/// Locates the output with an anchor script paying to `funding_pubkey` within `commitment_tx`.
pub(crate) fn get_anchor_output<'a>(commitment_tx: &'a Transaction, funding_pubkey: &PublicKey) -> Option<(u32, &'a TxOut)> {
let anchor_script = chan_utils::get_anchor_redeemscript(funding_pubkey).to_v0_p2wsh();
pub counterparty_parameters: Option<CounterpartyChannelTransactionParameters>,
/// The late-bound funding outpoint
pub funding_outpoint: Option<chain::transaction::OutPoint>,
- /// Are anchors (zero fee HTLC transaction variant) used for this channel. Boolean is
- /// serialization backwards-compatible.
- pub opt_anchors: Option<()>,
- /// Are non-zero-fee anchors are enabled (used in conjuction with opt_anchors)
- /// It is intended merely for backwards compatibility with signers that need it.
- /// There is no support for this feature in LDK channel negotiation.
- pub opt_non_zero_fee_anchors: Option<()>,
+ /// This channel's type, as negotiated during channel open. For old objects where this field
+ /// wasn't serialized, it will default to static_remote_key at deserialization.
+ pub channel_type_features: ChannelTypeFeatures
}
/// Late-bound per-channel counterparty data used to build transactions.
(2, selected_contest_delay, required),
});
-impl_writeable_tlv_based!(ChannelTransactionParameters, {
- (0, holder_pubkeys, required),
- (2, holder_selected_contest_delay, required),
- (4, is_outbound_from_holder, required),
- (6, counterparty_parameters, option),
- (8, funding_outpoint, option),
- (10, opt_anchors, option),
- (12, opt_non_zero_fee_anchors, option),
-});
+impl Writeable for ChannelTransactionParameters {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ let legacy_deserialization_prevention_marker = legacy_deserialization_prevention_marker_for_channel_type_features(&self.channel_type_features);
+ write_tlv_fields!(writer, {
+ (0, self.holder_pubkeys, required),
+ (2, self.holder_selected_contest_delay, required),
+ (4, self.is_outbound_from_holder, required),
+ (6, self.counterparty_parameters, option),
+ (8, self.funding_outpoint, option),
+ (10, legacy_deserialization_prevention_marker, option),
+ (11, self.channel_type_features, required),
+ });
+ Ok(())
+ }
+}
+
+impl Readable for ChannelTransactionParameters {
+ fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
+ let mut holder_pubkeys = RequiredWrapper(None);
+ let mut holder_selected_contest_delay = RequiredWrapper(None);
+ let mut is_outbound_from_holder = RequiredWrapper(None);
+ let mut counterparty_parameters = None;
+ let mut funding_outpoint = None;
+ let mut _legacy_deserialization_prevention_marker: Option<()> = None;
+ let mut channel_type_features = None;
+
+ read_tlv_fields!(reader, {
+ (0, holder_pubkeys, required),
+ (2, holder_selected_contest_delay, required),
+ (4, is_outbound_from_holder, required),
+ (6, counterparty_parameters, option),
+ (8, funding_outpoint, option),
+ (10, _legacy_deserialization_prevention_marker, option),
+ (11, channel_type_features, option),
+ });
+
+ let mut additional_features = ChannelTypeFeatures::empty();
+ additional_features.set_anchors_nonzero_fee_htlc_tx_required();
+ chain::package::verify_channel_type_features(&channel_type_features, Some(&additional_features))?;
+
+ Ok(Self {
+ holder_pubkeys: holder_pubkeys.0.unwrap(),
+ holder_selected_contest_delay: holder_selected_contest_delay.0.unwrap(),
+ is_outbound_from_holder: is_outbound_from_holder.0.unwrap(),
+ counterparty_parameters,
+ funding_outpoint,
+ channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key())
+ })
+ }
+}
/// Static channel fields used to build transactions given per-commitment fields, organized by
/// broadcaster/countersignatory.
}
/// Whether to use anchors for this channel
- pub fn opt_anchors(&self) -> bool {
- self.inner.opt_anchors.is_some()
+ pub fn channel_type_features(&self) -> &ChannelTypeFeatures {
+ &self.inner.channel_type_features
}
}
(0, inner, required),
(2, counterparty_sig, required),
(4, holder_sig_first, required),
- (6, counterparty_htlc_sigs, vec_type),
+ (6, counterparty_htlc_sigs, required_vec),
});
impl HolderCommitmentTransaction {
is_outbound_from_holder: false,
counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: channel_pubkeys.clone(), selected_contest_delay: 0 }),
funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }),
- opt_anchors: None,
- opt_non_zero_fee_anchors: None,
+ channel_type_features: ChannelTypeFeatures::only_static_remote_key(),
};
let mut counterparty_htlc_sigs = Vec::new();
for _ in 0..htlcs.len() {
counterparty_htlc_sigs.push(dummy_sig);
}
- let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, false, dummy_key.clone(), dummy_key.clone(), keys, 0, htlcs, &channel_parameters.as_counterparty_broadcastable());
+ let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, dummy_key.clone(), dummy_key.clone(), keys, 0, htlcs, &channel_parameters.as_counterparty_broadcastable());
htlcs.sort_by_key(|htlc| htlc.0.transaction_output_index);
HolderCommitmentTransaction {
inner,
to_countersignatory_value_sat: u64,
feerate_per_kw: u32,
htlcs: Vec<HTLCOutputInCommitment>,
- // A boolean that is serialization backwards-compatible
- opt_anchors: Option<()>,
- // Whether non-zero-fee anchors should be used
- opt_non_zero_fee_anchors: Option<()>,
+ // Note that on upgrades, some features of existing outputs may be missed.
+ channel_type_features: ChannelTypeFeatures,
// A cache of the parties' pubkeys required to construct the transaction, see doc for trust()
keys: TxCreationKeys,
// For access to the pre-built transaction, see doc for trust()
self.to_countersignatory_value_sat == o.to_countersignatory_value_sat &&
self.feerate_per_kw == o.feerate_per_kw &&
self.htlcs == o.htlcs &&
- self.opt_anchors == o.opt_anchors &&
+ self.channel_type_features == o.channel_type_features &&
self.keys == o.keys;
if eq {
debug_assert_eq!(self.built.transaction, o.built.transaction);
}
}
-impl_writeable_tlv_based!(CommitmentTransaction, {
- (0, commitment_number, required),
- (2, to_broadcaster_value_sat, required),
- (4, to_countersignatory_value_sat, required),
- (6, feerate_per_kw, required),
- (8, keys, required),
- (10, built, required),
- (12, htlcs, vec_type),
- (14, opt_anchors, option),
- (16, opt_non_zero_fee_anchors, option),
-});
+impl Writeable for CommitmentTransaction {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ let legacy_deserialization_prevention_marker = legacy_deserialization_prevention_marker_for_channel_type_features(&self.channel_type_features);
+ write_tlv_fields!(writer, {
+ (0, self.commitment_number, required),
+ (2, self.to_broadcaster_value_sat, required),
+ (4, self.to_countersignatory_value_sat, required),
+ (6, self.feerate_per_kw, required),
+ (8, self.keys, required),
+ (10, self.built, required),
+ (12, self.htlcs, required_vec),
+ (14, legacy_deserialization_prevention_marker, option),
+ (15, self.channel_type_features, required),
+ });
+ Ok(())
+ }
+}
+
+impl Readable for CommitmentTransaction {
+ fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
+ _init_and_read_tlv_fields!(reader, {
+ (0, commitment_number, required),
+ (2, to_broadcaster_value_sat, required),
+ (4, to_countersignatory_value_sat, required),
+ (6, feerate_per_kw, required),
+ (8, keys, required),
+ (10, built, required),
+ (12, htlcs, required_vec),
+ (14, _legacy_deserialization_prevention_marker, option),
+ (15, channel_type_features, option),
+ });
+
+ let mut additional_features = ChannelTypeFeatures::empty();
+ additional_features.set_anchors_nonzero_fee_htlc_tx_required();
+ chain::package::verify_channel_type_features(&channel_type_features, Some(&additional_features))?;
+
+ Ok(Self {
+ commitment_number: commitment_number.0.unwrap(),
+ to_broadcaster_value_sat: to_broadcaster_value_sat.0.unwrap(),
+ to_countersignatory_value_sat: to_countersignatory_value_sat.0.unwrap(),
+ feerate_per_kw: feerate_per_kw.0.unwrap(),
+ keys: keys.0.unwrap(),
+ built: built.0.unwrap(),
+ htlcs,
+ channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key())
+ })
+ }
+}
impl CommitmentTransaction {
/// Construct an object of the class while assigning transaction output indices to HTLCs.
/// Only include HTLCs that are above the dust limit for the channel.
///
/// This is not exported to bindings users due to the generic though we likely should expose a version without
- pub fn new_with_auxiliary_htlc_data<T>(commitment_number: u64, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, opt_anchors: bool, broadcaster_funding_key: PublicKey, countersignatory_funding_key: PublicKey, keys: TxCreationKeys, feerate_per_kw: u32, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters) -> CommitmentTransaction {
+ pub fn new_with_auxiliary_htlc_data<T>(commitment_number: u64, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, broadcaster_funding_key: PublicKey, countersignatory_funding_key: PublicKey, keys: TxCreationKeys, feerate_per_kw: u32, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters) -> CommitmentTransaction {
// Sort outputs and populate output indices while keeping track of the auxiliary data
- let (outputs, htlcs) = Self::internal_build_outputs(&keys, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs_with_aux, channel_parameters, opt_anchors, &broadcaster_funding_key, &countersignatory_funding_key).unwrap();
+ let (outputs, htlcs) = Self::internal_build_outputs(&keys, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs_with_aux, channel_parameters, &broadcaster_funding_key, &countersignatory_funding_key).unwrap();
let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(commitment_number, channel_parameters);
let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs);
to_countersignatory_value_sat,
feerate_per_kw,
htlcs,
- opt_anchors: if opt_anchors { Some(()) } else { None },
+ channel_type_features: channel_parameters.channel_type_features().clone(),
keys,
built: BuiltCommitmentTransaction {
transaction,
txid
},
- opt_non_zero_fee_anchors: None,
}
}
///
/// This is not exported to bindings users due to move, and also not likely to be useful for binding users
pub fn with_non_zero_fee_anchors(mut self) -> Self {
- self.opt_non_zero_fee_anchors = Some(());
+ self.channel_type_features.set_anchors_nonzero_fee_htlc_tx_required();
self
}
let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(self.commitment_number, channel_parameters);
let mut htlcs_with_aux = self.htlcs.iter().map(|h| (h.clone(), ())).collect();
- let (outputs, _) = Self::internal_build_outputs(keys, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, &mut htlcs_with_aux, channel_parameters, self.opt_anchors.is_some(), broadcaster_funding_key, countersignatory_funding_key)?;
+ let (outputs, _) = Self::internal_build_outputs(keys, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, &mut htlcs_with_aux, channel_parameters, broadcaster_funding_key, countersignatory_funding_key)?;
let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs);
let txid = transaction.txid();
// - initial sorting of outputs / HTLCs in the constructor, in which case T is auxiliary data the
// caller needs to have sorted together with the HTLCs so it can keep track of the output index
// - building of a bitcoin transaction during a verify() call, in which case T is just ()
- fn internal_build_outputs<T>(keys: &TxCreationKeys, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, opt_anchors: bool, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> Result<(Vec<TxOut>, Vec<HTLCOutputInCommitment>), ()> {
+ fn internal_build_outputs<T>(keys: &TxCreationKeys, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> Result<(Vec<TxOut>, Vec<HTLCOutputInCommitment>), ()> {
let countersignatory_pubkeys = channel_parameters.countersignatory_pubkeys();
let contest_delay = channel_parameters.contest_delay();
let mut txouts: Vec<(TxOut, Option<&mut HTLCOutputInCommitment>)> = Vec::new();
if to_countersignatory_value_sat > 0 {
- let script = if opt_anchors {
+ let script = if channel_parameters.channel_type_features().supports_anchors_zero_fee_htlc_tx() {
get_to_countersignatory_with_anchors_redeemscript(&countersignatory_pubkeys.payment_point).to_v0_p2wsh()
} else {
Payload::p2wpkh(&BitcoinPublicKey::new(countersignatory_pubkeys.payment_point)).unwrap().script_pubkey()
));
}
- if opt_anchors {
+ if channel_parameters.channel_type_features().supports_anchors_zero_fee_htlc_tx() {
if to_broadcaster_value_sat > 0 || !htlcs_with_aux.is_empty() {
let anchor_script = get_anchor_redeemscript(broadcaster_funding_key);
txouts.push((
let mut htlcs = Vec::with_capacity(htlcs_with_aux.len());
for (htlc, _) in htlcs_with_aux {
- let script = chan_utils::get_htlc_redeemscript(&htlc, opt_anchors, &keys);
+ let script = chan_utils::get_htlc_redeemscript(&htlc, &channel_parameters.channel_type_features(), &keys);
let txout = TxOut {
script_pubkey: script.to_v0_p2wsh(),
value: htlc.amount_msat / 1000,
}
/// Should anchors be used.
- pub fn opt_anchors(&self) -> bool {
- self.opt_anchors.is_some()
+ pub fn channel_type_features(&self) -> &ChannelTypeFeatures {
+ &self.inner.channel_type_features
}
/// Get a signature for each HTLC which was included in the commitment transaction (ie for
for this_htlc in inner.htlcs.iter() {
assert!(this_htlc.transaction_output_index.is_some());
- let htlc_tx = build_htlc_transaction(&txid, inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc, self.opt_anchors(), self.opt_non_zero_fee_anchors.is_some(), &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
+ let htlc_tx = build_htlc_transaction(&txid, inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc, &self.channel_type_features, &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
- let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc, self.opt_anchors(), &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key);
+ let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc, &self.channel_type_features, &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key);
let sighash = hash_to_message!(&sighash::SighashCache::new(&htlc_tx).segwit_signature_hash(0, &htlc_redeemscript, this_htlc.amount_msat / 1000, EcdsaSighashType::All).unwrap()[..]);
ret.push(sign_with_aux_rand(secp_ctx, &sighash, &holder_htlc_key, entropy_source));
// Further, we should never be provided the preimage for an HTLC-Timeout transaction.
if this_htlc.offered && preimage.is_some() { unreachable!(); }
- let mut htlc_tx = build_htlc_transaction(&txid, inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc, self.opt_anchors(), self.opt_non_zero_fee_anchors.is_some(), &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
+ let mut htlc_tx = build_htlc_transaction(&txid, inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc, &self.channel_type_features, &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
- let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc, self.opt_anchors(), &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key);
+ let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc, &self.channel_type_features, &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key);
htlc_tx.input[0].witness = chan_utils::build_htlc_input_witness(
- signature, counterparty_signature, preimage, &htlc_redeemscript, self.opt_anchors(),
+ signature, counterparty_signature, preimage, &htlc_redeemscript, &self.channel_type_features,
);
htlc_tx
}
use bitcoin::hashes::hex::ToHex;
use bitcoin::util::address::Payload;
use bitcoin::PublicKey as BitcoinPublicKey;
+ use crate::ln::features::ChannelTypeFeatures;
#[test]
fn test_anchors() {
is_outbound_from_holder: false,
counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys.clone(), selected_contest_delay: 0 }),
funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }),
- opt_anchors: None,
- opt_non_zero_fee_anchors: None,
+ channel_type_features: ChannelTypeFeatures::only_static_remote_key(),
};
let mut htlcs_with_aux: Vec<(_, ())> = Vec::new();
// Generate broadcaster and counterparty outputs
let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
0, 1000, 2000,
- false,
holder_pubkeys.funding_pubkey,
counterparty_pubkeys.funding_pubkey,
keys.clone(), 1,
assert_eq!(tx.built.transaction.output[1].script_pubkey, Payload::p2wpkh(&BitcoinPublicKey::new(counterparty_pubkeys.payment_point)).unwrap().script_pubkey());
// Generate broadcaster and counterparty outputs as well as two anchors
+ channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
0, 1000, 2000,
- true,
holder_pubkeys.funding_pubkey,
counterparty_pubkeys.funding_pubkey,
keys.clone(), 1,
// Generate broadcaster output and anchor
let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
0, 3000, 0,
- true,
holder_pubkeys.funding_pubkey,
counterparty_pubkeys.funding_pubkey,
keys.clone(), 1,
// Generate counterparty output and anchor
let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
0, 0, 3000,
- true,
holder_pubkeys.funding_pubkey,
counterparty_pubkeys.funding_pubkey,
keys.clone(), 1,
};
// Generate broadcaster output and received and offered HTLC outputs, w/o anchors
+ channel_parameters.channel_type_features = ChannelTypeFeatures::only_static_remote_key();
let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
0, 3000, 0,
- false,
holder_pubkeys.funding_pubkey,
counterparty_pubkeys.funding_pubkey,
keys.clone(), 1,
&channel_parameters.as_holder_broadcastable()
);
assert_eq!(tx.built.transaction.output.len(), 3);
- assert_eq!(tx.built.transaction.output[0].script_pubkey, get_htlc_redeemscript(&received_htlc, false, &keys).to_v0_p2wsh());
- assert_eq!(tx.built.transaction.output[1].script_pubkey, get_htlc_redeemscript(&offered_htlc, false, &keys).to_v0_p2wsh());
- assert_eq!(get_htlc_redeemscript(&received_htlc, false, &keys).to_v0_p2wsh().to_hex(),
+ assert_eq!(tx.built.transaction.output[0].script_pubkey, get_htlc_redeemscript(&received_htlc, &ChannelTypeFeatures::only_static_remote_key(), &keys).to_v0_p2wsh());
+ assert_eq!(tx.built.transaction.output[1].script_pubkey, get_htlc_redeemscript(&offered_htlc, &ChannelTypeFeatures::only_static_remote_key(), &keys).to_v0_p2wsh());
+ assert_eq!(get_htlc_redeemscript(&received_htlc, &ChannelTypeFeatures::only_static_remote_key(), &keys).to_v0_p2wsh().to_hex(),
"0020e43a7c068553003fe68fcae424fb7b28ec5ce48cd8b6744b3945631389bad2fb");
- assert_eq!(get_htlc_redeemscript(&offered_htlc, false, &keys).to_v0_p2wsh().to_hex(),
+ assert_eq!(get_htlc_redeemscript(&offered_htlc, &ChannelTypeFeatures::only_static_remote_key(), &keys).to_v0_p2wsh().to_hex(),
"0020215d61bba56b19e9eadb6107f5a85d7f99c40f65992443f69229c290165bc00d");
// Generate broadcaster output and received and offered HTLC outputs, with anchors
- channel_parameters.opt_anchors = Some(());
+ channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
0, 3000, 0,
- true,
holder_pubkeys.funding_pubkey,
counterparty_pubkeys.funding_pubkey,
keys.clone(), 1,
&channel_parameters.as_holder_broadcastable()
);
assert_eq!(tx.built.transaction.output.len(), 5);
- assert_eq!(tx.built.transaction.output[2].script_pubkey, get_htlc_redeemscript(&received_htlc, true, &keys).to_v0_p2wsh());
- assert_eq!(tx.built.transaction.output[3].script_pubkey, get_htlc_redeemscript(&offered_htlc, true, &keys).to_v0_p2wsh());
- assert_eq!(get_htlc_redeemscript(&received_htlc, true, &keys).to_v0_p2wsh().to_hex(),
+ assert_eq!(tx.built.transaction.output[2].script_pubkey, get_htlc_redeemscript(&received_htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &keys).to_v0_p2wsh());
+ assert_eq!(tx.built.transaction.output[3].script_pubkey, get_htlc_redeemscript(&offered_htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &keys).to_v0_p2wsh());
+ assert_eq!(get_htlc_redeemscript(&received_htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &keys).to_v0_p2wsh().to_hex(),
"0020b70d0649c72b38756885c7a30908d912a7898dd5d79457a7280b8e9a20f3f2bc");
- assert_eq!(get_htlc_redeemscript(&offered_htlc, true, &keys).to_v0_p2wsh().to_hex(),
+ assert_eq!(get_htlc_redeemscript(&offered_htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &keys).to_v0_p2wsh().to_hex(),
"002087a3faeb1950a469c0e2db4a79b093a41b9526e5a6fc6ef5cb949bde3be379c7");
}
RecipientOnionFields::secret_only(payment_secret_2), PaymentId(payment_hash_2.0)).unwrap();
check_added_monitors!(nodes[0], 0);
+ let chan_0_monitor_serialized = get_monitor!(nodes[0], chan_id).encode();
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
nodes[0].node.claim_funds(payment_preimage_0);
// disconnect the peers. Note that the fuzzer originally found this issue because
// deserializing a ChannelManager in this state causes an assertion failure.
if reload_a {
- let chan_0_monitor_serialized = get_monitor!(nodes[0], chan_id).encode();
reload_node!(nodes[0], &nodes[0].node.encode(), &[&chan_0_monitor_serialized], persister, new_chain_monitor, nodes_0_deserialized);
+ persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
} else {
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
}
assert_eq!(pending_cs.commitment_signed, cs);
} else { panic!(); }
- // There should be no monitor updates as we are still pending awaiting a failed one.
- check_added_monitors!(nodes[0], 0);
- check_added_monitors!(nodes[1], 0);
+ if reload_a {
+ // The two pending monitor updates were replayed (but are still pending).
+ check_added_monitors(&nodes[0], 2);
+ } else {
+ // There should be no monitor updates as we are still pending awaiting a failed one.
+ check_added_monitors(&nodes[0], 0);
+ }
+ check_added_monitors(&nodes[1], 0);
}
// If we finish updating the monitor, we should free the holding cell right away (this did
use crate::ln::msgs;
use crate::ln::msgs::DecodeError;
use crate::ln::script::{self, ShutdownScript};
-use crate::ln::channelmanager::{self, CounterpartyForwardingInfo, PendingHTLCStatus, HTLCSource, SentHTLCId, HTLCFailureMsg, PendingHTLCInfo, RAACommitmentOrder, BREAKDOWN_TIMEOUT, MIN_CLTV_EXPIRY_DELTA, MAX_LOCAL_BREAKDOWN_TIMEOUT};
+use crate::ln::channelmanager::{self, CounterpartyForwardingInfo, PendingHTLCStatus, HTLCSource, SentHTLCId, HTLCFailureMsg, PendingHTLCInfo, RAACommitmentOrder, BREAKDOWN_TIMEOUT, MIN_CLTV_EXPIRY_DELTA, MAX_LOCAL_BREAKDOWN_TIMEOUT, ChannelShutdownState};
use crate::ln::chan_utils::{CounterpartyCommitmentSecrets, TxCreationKeys, HTLCOutputInCommitment, htlc_success_tx_weight, htlc_timeout_tx_weight, make_funding_redeemscript, ChannelPublicKeys, CommitmentTransaction, HolderCommitmentTransaction, ChannelTransactionParameters, CounterpartyChannelTransactionParameters, MAX_HTLCS, get_commitment_transaction_number_obscure_factor, ClosingTransaction};
use crate::ln::chan_utils;
use crate::ln::onion_utils::HTLCFailReason;
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer, VecWriter};
use crate::util::logger::Logger;
use crate::util::errors::APIError;
-use crate::util::config::{UserConfig, ChannelConfig, LegacyChannelConfig, ChannelHandshakeConfig, ChannelHandshakeLimits};
+use crate::util::config::{UserConfig, ChannelConfig, LegacyChannelConfig, ChannelHandshakeConfig, ChannelHandshakeLimits, MaxDustHTLCExposure};
use crate::util::scid_utils::scid_from_parts;
use crate::io;
payment_hash: PaymentHash,
state: OutboundHTLCState,
source: HTLCSource,
+ skimmed_fee_msat: Option<u64>,
}
/// See AwaitingRemoteRevoke ChannelState for more info
payment_hash: PaymentHash,
source: HTLCSource,
onion_routing_packet: msgs::OnionPacket,
+ // The extra fee we're skimming off the top of this HTLC.
+ skimmed_fee_msat: Option<u64>,
},
ClaimHTLC {
payment_preimage: PaymentPreimage,
pub const DEFAULT_MAX_HTLCS: u16 = 50;
-pub(crate) fn commitment_tx_base_weight(opt_anchors: bool) -> u64 {
+pub(crate) fn commitment_tx_base_weight(channel_type_features: &ChannelTypeFeatures) -> u64 {
const COMMITMENT_TX_BASE_WEIGHT: u64 = 724;
const COMMITMENT_TX_BASE_ANCHOR_WEIGHT: u64 = 1124;
- if opt_anchors { COMMITMENT_TX_BASE_ANCHOR_WEIGHT } else { COMMITMENT_TX_BASE_WEIGHT }
+ if channel_type_features.supports_anchors_zero_fee_htlc_tx() { COMMITMENT_TX_BASE_ANCHOR_WEIGHT } else { COMMITMENT_TX_BASE_WEIGHT }
}
#[cfg(not(test))]
}
/// The return type of get_update_fulfill_htlc_and_commit.
-pub enum UpdateFulfillCommitFetch<'a> {
+pub enum UpdateFulfillCommitFetch {
/// Indicates the HTLC fulfill is new, and either generated an update_fulfill message, placed
/// it in the holding cell, or re-generated the update_fulfill message after the same claim was
/// previously placed in the holding cell (and has since been removed).
NewClaim {
/// The ChannelMonitorUpdate which places the new payment preimage in the channel monitor
- monitor_update: &'a ChannelMonitorUpdate,
+ monitor_update: ChannelMonitorUpdate,
/// The value of the HTLC which was claimed, in msat.
htlc_value_msat: u64,
},
}
/// The return type of `force_shutdown`
+///
+/// Contains a (counterparty_node_id, funding_txo, [`ChannelMonitorUpdate`]) tuple
+/// followed by a list of HTLCs to fail back in the form of the (source, payment hash, and this
+/// channel's counterparty_node_id and channel_id).
pub(crate) type ShutdownResult = (
Option<(PublicKey, OutPoint, ChannelMonitorUpdate)>,
Vec<(HTLCSource, PaymentHash, PublicKey, [u8; 32])>
/// See [`ChannelContext::sent_message_awaiting_response`] for more information.
pub(crate) const DISCONNECT_PEER_AWAITING_RESPONSE_TICKS: usize = 2;
+/// The number of ticks that may elapse while we're waiting for an unfunded outbound/inbound channel
+/// to be promoted to a [`Channel`] since the unfunded channel was created. An unfunded channel
+/// exceeding this age limit will be force-closed and purged from memory.
+pub(crate) const UNFUNDED_CHANNEL_AGE_LIMIT_TICKS: usize = 60;
+
struct PendingChannelMonitorUpdate {
update: ChannelMonitorUpdate,
- /// In some cases we need to delay letting the [`ChannelMonitorUpdate`] go until after an
- /// `Event` is processed by the user. This bool indicates the [`ChannelMonitorUpdate`] is
- /// blocked on some external event and the [`ChannelManager`] will update us when we're ready.
- ///
- /// [`ChannelManager`]: super::channelmanager::ChannelManager
- blocked: bool,
}
impl_writeable_tlv_based!(PendingChannelMonitorUpdate, {
(0, update, required),
- (2, blocked, required),
});
+/// Contains all state common to unfunded inbound/outbound channels.
+pub(super) struct UnfundedChannelContext {
+ /// A counter tracking how many ticks have elapsed since this unfunded channel was
+ /// created. If this unfunded channel reaches peer has yet to respond after reaching
+ /// `UNFUNDED_CHANNEL_AGE_LIMIT_TICKS`, it will be force-closed and purged from memory.
+ ///
+ /// This is so that we don't keep channels around that haven't progressed to a funded state
+ /// in a timely manner.
+ unfunded_channel_age_ticks: usize,
+}
+
+impl UnfundedChannelContext {
+ /// Determines whether we should force-close and purge this unfunded channel from memory due to it
+ /// having reached the unfunded channel age limit.
+ ///
+ /// This should be called on every [`super::channelmanager::ChannelManager::timer_tick_occurred`].
+ pub fn should_expire_unfunded_channel(&mut self) -> bool {
+ self.unfunded_channel_age_ticks += 1;
+ self.unfunded_channel_age_ticks >= UNFUNDED_CHANNEL_AGE_LIMIT_TICKS
+ }
+}
+
/// Contains everything about the channel including state, and various flags.
pub(super) struct ChannelContext<Signer: ChannelSigner> {
config: LegacyChannelConfig,
/// [`SignerProvider::derive_channel_signer`].
channel_keys_id: [u8; 32],
- /// When we generate [`ChannelMonitorUpdate`]s to persist, they may not be persisted immediately.
- /// If we then persist the [`channelmanager::ChannelManager`] and crash before the persistence
- /// completes we still need to be able to complete the persistence. Thus, we have to keep a
- /// copy of the [`ChannelMonitorUpdate`] here until it is complete.
- pending_monitor_updates: Vec<PendingChannelMonitorUpdate>,
+ /// If we can't release a [`ChannelMonitorUpdate`] until some external action completes, we
+ /// store it here and only release it to the `ChannelManager` once it asks for it.
+ blocked_monitor_updates: Vec<PendingChannelMonitorUpdate>,
}
impl<Signer: ChannelSigner> ChannelContext<Signer> {
- pub(crate) fn opt_anchors(&self) -> bool {
- self.channel_transaction_parameters.opt_anchors.is_some()
- }
-
/// Allowed in any state (including after shutdown)
pub fn get_update_time_counter(&self) -> u32 {
self.update_time_counter
(self.channel_state & mask) == (ChannelState::ChannelReady as u32) && !self.monitor_pending_channel_ready
}
+ /// shutdown state returns the state of the channel in its various stages of shutdown
+ pub fn shutdown_state(&self) -> ChannelShutdownState {
+ if self.channel_state & (ChannelState::ShutdownComplete as u32) != 0 {
+ return ChannelShutdownState::ShutdownComplete;
+ }
+ if self.channel_state & (ChannelState::LocalShutdownSent as u32) != 0 && self.channel_state & (ChannelState::RemoteShutdownSent as u32) == 0 {
+ return ChannelShutdownState::ShutdownInitiated;
+ }
+ if (self.channel_state & BOTH_SIDES_SHUTDOWN_MASK != 0) && !self.closing_negotiation_ready() {
+ return ChannelShutdownState::ResolvingHTLCs;
+ }
+ if (self.channel_state & BOTH_SIDES_SHUTDOWN_MASK != 0) && self.closing_negotiation_ready() {
+ return ChannelShutdownState::NegotiatingClosingFee;
+ }
+ return ChannelShutdownState::NotShuttingDown;
+ }
+
+ fn closing_negotiation_ready(&self) -> bool {
+ self.pending_inbound_htlcs.is_empty() &&
+ self.pending_outbound_htlcs.is_empty() &&
+ self.pending_update_fee.is_none() &&
+ self.channel_state &
+ (BOTH_SIDES_SHUTDOWN_MASK |
+ ChannelState::AwaitingRemoteRevoke as u32 |
+ ChannelState::PeerDisconnected as u32 |
+ ChannelState::MonitorUpdateInProgress as u32) == BOTH_SIDES_SHUTDOWN_MASK
+ }
+
/// Returns true if this channel is currently available for use. This is a superset of
/// is_usable() and considers things like the channel being temporarily disabled.
/// Allowed in any state (including after shutdown)
}
/// Returns the funding_txo we either got from our peer, or were given by
- /// get_outbound_funding_created.
+ /// get_funding_created.
pub fn get_funding_txo(&self) -> Option<OutPoint> {
self.channel_transaction_parameters.funding_outpoint
}
cmp::max(self.config.options.cltv_expiry_delta, MIN_CLTV_EXPIRY_DELTA)
}
- pub fn get_max_dust_htlc_exposure_msat(&self) -> u64 {
- self.config.options.max_dust_htlc_exposure_msat
+ pub fn get_max_dust_htlc_exposure_msat<F: Deref>(&self,
+ fee_estimator: &LowerBoundedFeeEstimator<F>) -> u64
+ where F::Target: FeeEstimator
+ {
+ match self.config.options.max_dust_htlc_exposure {
+ MaxDustHTLCExposure::FeeRateMultiplier(multiplier) => {
+ let feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(
+ ConfirmationTarget::HighPriority);
+ feerate_per_kw as u64 * multiplier
+ },
+ MaxDustHTLCExposure::FixedLimitMsat(limit) => limit,
+ }
}
/// Returns the previous [`ChannelConfig`] applied to this channel, if any.
($htlc: expr, $outbound: expr, $source: expr, $state_name: expr) => {
if $outbound == local { // "offered HTLC output"
let htlc_in_tx = get_htlc_in_commitment!($htlc, true);
- let htlc_tx_fee = if self.opt_anchors() {
+ let htlc_tx_fee = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
0
} else {
- feerate_per_kw as u64 * htlc_timeout_tx_weight(false) / 1000
+ feerate_per_kw as u64 * htlc_timeout_tx_weight(self.get_channel_type()) / 1000
};
if $htlc.amount_msat / 1000 >= broadcaster_dust_limit_satoshis + htlc_tx_fee {
log_trace!(logger, " ...including {} {} HTLC {} (hash {}) with value {}", if $outbound { "outbound" } else { "inbound" }, $state_name, $htlc.htlc_id, log_bytes!($htlc.payment_hash.0), $htlc.amount_msat);
}
} else {
let htlc_in_tx = get_htlc_in_commitment!($htlc, false);
- let htlc_tx_fee = if self.opt_anchors() {
+ let htlc_tx_fee = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
0
} else {
- feerate_per_kw as u64 * htlc_success_tx_weight(false) / 1000
+ feerate_per_kw as u64 * htlc_success_tx_weight(self.get_channel_type()) / 1000
};
if $htlc.amount_msat / 1000 >= broadcaster_dust_limit_satoshis + htlc_tx_fee {
log_trace!(logger, " ...including {} {} HTLC {} (hash {}) with value {}", if $outbound { "outbound" } else { "inbound" }, $state_name, $htlc.htlc_id, log_bytes!($htlc.payment_hash.0), $htlc.amount_msat);
broadcaster_max_commitment_tx_output.1 = cmp::max(broadcaster_max_commitment_tx_output.1, value_to_remote_msat as u64);
}
- let total_fee_sat = commit_tx_fee_sat(feerate_per_kw, included_non_dust_htlcs.len(), self.channel_transaction_parameters.opt_anchors.is_some());
- let anchors_val = if self.channel_transaction_parameters.opt_anchors.is_some() { ANCHOR_OUTPUT_VALUE_SATOSHI * 2 } else { 0 } as i64;
+ let total_fee_sat = commit_tx_fee_sat(feerate_per_kw, included_non_dust_htlcs.len(), &self.channel_transaction_parameters.channel_type_features);
+ let anchors_val = if self.channel_transaction_parameters.channel_type_features.supports_anchors_zero_fee_htlc_tx() { ANCHOR_OUTPUT_VALUE_SATOSHI * 2 } else { 0 } as i64;
let (value_to_self, value_to_remote) = if self.is_outbound() {
(value_to_self_msat / 1000 - anchors_val - total_fee_sat as i64, value_to_remote_msat / 1000)
} else {
let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(commitment_number,
value_to_a as u64,
value_to_b as u64,
- self.channel_transaction_parameters.opt_anchors.is_some(),
funding_pubkey_a,
funding_pubkey_b,
keys.clone(),
#[inline]
/// Creates a set of keys for build_commitment_transaction to generate a transaction which we
/// will sign and send to our counterparty.
- /// If an Err is returned, it is a ChannelError::Close (for get_outbound_funding_created)
+ /// If an Err is returned, it is a ChannelError::Close (for get_funding_created)
fn build_remote_transaction_keys(&self) -> TxCreationKeys {
//TODO: Ensure that the payment_key derived here ends up in the library users' wallet as we
//may see payments to it!
on_holder_tx_holding_cell_htlcs_count: 0,
};
- let (htlc_timeout_dust_limit, htlc_success_dust_limit) = if context.opt_anchors() {
+ let (htlc_timeout_dust_limit, htlc_success_dust_limit) = if context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
(0, 0)
} else {
let dust_buffer_feerate = context.get_dust_buffer_feerate(outbound_feerate_update) as u64;
- (dust_buffer_feerate * htlc_timeout_tx_weight(false) / 1000,
- dust_buffer_feerate * htlc_success_tx_weight(false) / 1000)
+ (dust_buffer_feerate * htlc_timeout_tx_weight(context.get_channel_type()) / 1000,
+ dust_buffer_feerate * htlc_success_tx_weight(context.get_channel_type()) / 1000)
};
let counterparty_dust_limit_timeout_sat = htlc_timeout_dust_limit + context.counterparty_dust_limit_satoshis;
let holder_dust_limit_success_sat = htlc_success_dust_limit + context.holder_dust_limit_satoshis;
on_holder_tx_holding_cell_htlcs_count: 0,
};
- let (htlc_timeout_dust_limit, htlc_success_dust_limit) = if context.opt_anchors() {
+ let (htlc_timeout_dust_limit, htlc_success_dust_limit) = if context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
(0, 0)
} else {
let dust_buffer_feerate = context.get_dust_buffer_feerate(outbound_feerate_update) as u64;
- (dust_buffer_feerate * htlc_timeout_tx_weight(false) / 1000,
- dust_buffer_feerate * htlc_success_tx_weight(false) / 1000)
+ (dust_buffer_feerate * htlc_timeout_tx_weight(context.get_channel_type()) / 1000,
+ dust_buffer_feerate * htlc_success_tx_weight(context.get_channel_type()) / 1000)
};
let counterparty_dust_limit_success_sat = htlc_success_dust_limit + context.counterparty_dust_limit_satoshis;
let holder_dust_limit_timeout_sat = htlc_timeout_dust_limit + context.holder_dust_limit_satoshis;
/// Doesn't bother handling the
/// if-we-removed-it-already-but-haven't-fully-resolved-they-can-still-send-an-inbound-HTLC
/// corner case properly.
- pub fn get_available_balances(&self) -> AvailableBalances {
+ pub fn get_available_balances<F: Deref>(&self, fee_estimator: &LowerBoundedFeeEstimator<F>)
+ -> AvailableBalances
+ where F::Target: FeeEstimator
+ {
let context = &self;
// Note that we have to handle overflow due to the above case.
let inbound_stats = context.get_inbound_pending_htlc_stats(None);
// dependency.
// This complicates the computation around dust-values, up to the one-htlc-value.
let mut real_dust_limit_timeout_sat = context.holder_dust_limit_satoshis;
- if !context.opt_anchors() {
- real_dust_limit_timeout_sat += context.feerate_per_kw as u64 * htlc_timeout_tx_weight(false) / 1000;
+ if !context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
+ real_dust_limit_timeout_sat += context.feerate_per_kw as u64 * htlc_timeout_tx_weight(context.get_channel_type()) / 1000;
}
let htlc_above_dust = HTLCCandidate::new(real_dust_limit_timeout_sat * 1000, HTLCInitiator::LocalOffered);
// If the channel is inbound (i.e. counterparty pays the fee), we need to make sure
// sending a new HTLC won't reduce their balance below our reserve threshold.
let mut real_dust_limit_success_sat = context.counterparty_dust_limit_satoshis;
- if !context.opt_anchors() {
- real_dust_limit_success_sat += context.feerate_per_kw as u64 * htlc_success_tx_weight(false) / 1000;
+ if !context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
+ real_dust_limit_success_sat += context.feerate_per_kw as u64 * htlc_success_tx_weight(context.get_channel_type()) / 1000;
}
let htlc_above_dust = HTLCCandidate::new(real_dust_limit_success_sat * 1000, HTLCInitiator::LocalOffered);
// send above the dust limit (as the router can always overpay to meet the dust limit).
let mut remaining_msat_below_dust_exposure_limit = None;
let mut dust_exposure_dust_limit_msat = 0;
+ let max_dust_htlc_exposure_msat = context.get_max_dust_htlc_exposure_msat(fee_estimator);
- let (htlc_success_dust_limit, htlc_timeout_dust_limit) = if context.opt_anchors() {
+ let (htlc_success_dust_limit, htlc_timeout_dust_limit) = if context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
(context.counterparty_dust_limit_satoshis, context.holder_dust_limit_satoshis)
} else {
let dust_buffer_feerate = context.get_dust_buffer_feerate(None) as u64;
- (context.counterparty_dust_limit_satoshis + dust_buffer_feerate * htlc_success_tx_weight(false) / 1000,
- context.holder_dust_limit_satoshis + dust_buffer_feerate * htlc_timeout_tx_weight(false) / 1000)
+ (context.counterparty_dust_limit_satoshis + dust_buffer_feerate * htlc_success_tx_weight(context.get_channel_type()) / 1000,
+ context.holder_dust_limit_satoshis + dust_buffer_feerate * htlc_timeout_tx_weight(context.get_channel_type()) / 1000)
};
let on_counterparty_dust_htlc_exposure_msat = inbound_stats.on_counterparty_tx_dust_exposure_msat + outbound_stats.on_counterparty_tx_dust_exposure_msat;
- if on_counterparty_dust_htlc_exposure_msat as i64 + htlc_success_dust_limit as i64 * 1000 - 1 > context.get_max_dust_htlc_exposure_msat() as i64 {
+ if on_counterparty_dust_htlc_exposure_msat as i64 + htlc_success_dust_limit as i64 * 1000 - 1 > max_dust_htlc_exposure_msat as i64 {
remaining_msat_below_dust_exposure_limit =
- Some(context.get_max_dust_htlc_exposure_msat().saturating_sub(on_counterparty_dust_htlc_exposure_msat));
+ Some(max_dust_htlc_exposure_msat.saturating_sub(on_counterparty_dust_htlc_exposure_msat));
dust_exposure_dust_limit_msat = cmp::max(dust_exposure_dust_limit_msat, htlc_success_dust_limit * 1000);
}
let on_holder_dust_htlc_exposure_msat = inbound_stats.on_holder_tx_dust_exposure_msat + outbound_stats.on_holder_tx_dust_exposure_msat;
- if on_holder_dust_htlc_exposure_msat as i64 + htlc_timeout_dust_limit as i64 * 1000 - 1 > context.get_max_dust_htlc_exposure_msat() as i64 {
+ if on_holder_dust_htlc_exposure_msat as i64 + htlc_timeout_dust_limit as i64 * 1000 - 1 > max_dust_htlc_exposure_msat as i64 {
remaining_msat_below_dust_exposure_limit = Some(cmp::min(
remaining_msat_below_dust_exposure_limit.unwrap_or(u64::max_value()),
- context.get_max_dust_htlc_exposure_msat().saturating_sub(on_holder_dust_htlc_exposure_msat)));
+ max_dust_htlc_exposure_msat.saturating_sub(on_holder_dust_htlc_exposure_msat)));
dust_exposure_dust_limit_msat = cmp::max(dust_exposure_dust_limit_msat, htlc_timeout_dust_limit * 1000);
}
let context = &self;
assert!(context.is_outbound());
- let (htlc_success_dust_limit, htlc_timeout_dust_limit) = if context.opt_anchors() {
+ let (htlc_success_dust_limit, htlc_timeout_dust_limit) = if context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
(0, 0)
} else {
- (context.feerate_per_kw as u64 * htlc_success_tx_weight(false) / 1000,
- context.feerate_per_kw as u64 * htlc_timeout_tx_weight(false) / 1000)
+ (context.feerate_per_kw as u64 * htlc_success_tx_weight(context.get_channel_type()) / 1000,
+ context.feerate_per_kw as u64 * htlc_timeout_tx_weight(context.get_channel_type()) / 1000)
};
let real_dust_limit_success_sat = htlc_success_dust_limit + context.holder_dust_limit_satoshis;
let real_dust_limit_timeout_sat = htlc_timeout_dust_limit + context.holder_dust_limit_satoshis;
}
let num_htlcs = included_htlcs + addl_htlcs;
- let res = commit_tx_fee_msat(context.feerate_per_kw, num_htlcs, context.opt_anchors());
+ let res = commit_tx_fee_msat(context.feerate_per_kw, num_htlcs, &context.channel_type);
#[cfg(any(test, fuzzing))]
{
let mut fee = res;
if fee_spike_buffer_htlc.is_some() {
- fee = commit_tx_fee_msat(context.feerate_per_kw, num_htlcs - 1, context.opt_anchors());
+ fee = commit_tx_fee_msat(context.feerate_per_kw, num_htlcs - 1, &context.channel_type);
}
let total_pending_htlcs = context.pending_inbound_htlcs.len() + context.pending_outbound_htlcs.len()
+ context.holding_cell_htlc_updates.len();
let context = &self;
assert!(!context.is_outbound());
- let (htlc_success_dust_limit, htlc_timeout_dust_limit) = if context.opt_anchors() {
+ let (htlc_success_dust_limit, htlc_timeout_dust_limit) = if context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
(0, 0)
} else {
- (context.feerate_per_kw as u64 * htlc_success_tx_weight(false) / 1000,
- context.feerate_per_kw as u64 * htlc_timeout_tx_weight(false) / 1000)
+ (context.feerate_per_kw as u64 * htlc_success_tx_weight(context.get_channel_type()) / 1000,
+ context.feerate_per_kw as u64 * htlc_timeout_tx_weight(context.get_channel_type()) / 1000)
};
let real_dust_limit_success_sat = htlc_success_dust_limit + context.counterparty_dust_limit_satoshis;
let real_dust_limit_timeout_sat = htlc_timeout_dust_limit + context.counterparty_dust_limit_satoshis;
}
let num_htlcs = included_htlcs + addl_htlcs;
- let res = commit_tx_fee_msat(context.feerate_per_kw, num_htlcs, context.opt_anchors());
+ let res = commit_tx_fee_msat(context.feerate_per_kw, num_htlcs, &context.channel_type);
#[cfg(any(test, fuzzing))]
{
let mut fee = res;
if fee_spike_buffer_htlc.is_some() {
- fee = commit_tx_fee_msat(context.feerate_per_kw, num_htlcs - 1, context.opt_anchors());
+ fee = commit_tx_fee_msat(context.feerate_per_kw, num_htlcs - 1, &context.channel_type);
}
let total_pending_htlcs = context.pending_inbound_htlcs.len() + context.pending_outbound_htlcs.len();
let commitment_tx_info = CommitmentTxInfoCached {
// Get the fee cost in SATS of a commitment tx with a given number of HTLC outputs.
// Note that num_htlcs should not include dust HTLCs.
#[inline]
-fn commit_tx_fee_sat(feerate_per_kw: u32, num_htlcs: usize, opt_anchors: bool) -> u64 {
- feerate_per_kw as u64 * (commitment_tx_base_weight(opt_anchors) + num_htlcs as u64 * COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000
+fn commit_tx_fee_sat(feerate_per_kw: u32, num_htlcs: usize, channel_type_features: &ChannelTypeFeatures) -> u64 {
+ feerate_per_kw as u64 * (commitment_tx_base_weight(channel_type_features) + num_htlcs as u64 * COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000
}
// Get the fee cost in MSATS of a commitment tx with a given number of HTLC outputs.
// Note that num_htlcs should not include dust HTLCs.
-fn commit_tx_fee_msat(feerate_per_kw: u32, num_htlcs: usize, opt_anchors: bool) -> u64 {
+fn commit_tx_fee_msat(feerate_per_kw: u32, num_htlcs: usize, channel_type_features: &ChannelTypeFeatures) -> u64 {
// Note that we need to divide before multiplying to round properly,
// since the lowest denomination of bitcoin on-chain is the satoshi.
- (commitment_tx_base_weight(opt_anchors) + num_htlcs as u64 * COMMITMENT_TX_WEIGHT_PER_HTLC) * feerate_per_kw as u64 / 1000 * 1000
+ (commitment_tx_base_weight(channel_type_features) + num_htlcs as u64 * COMMITMENT_TX_WEIGHT_PER_HTLC) * feerate_per_kw as u64 / 1000 * 1000
}
// TODO: We should refactor this to be an Inbound/OutboundChannel until initial setup handshaking
// has been completed, and then turn into a Channel to get compiler-time enforcement of things like
-// calling channel_id() before we're set up or things like get_outbound_funding_signed on an
+// calling channel_id() before we're set up or things like get_funding_signed on an
// inbound channel.
//
// Holder designates channel data owned for the benefit of the user client.
}
impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
- fn check_remote_fee<F: Deref, L: Deref>(fee_estimator: &LowerBoundedFeeEstimator<F>,
- feerate_per_kw: u32, cur_feerate_per_kw: Option<u32>, logger: &L)
- -> Result<(), ChannelError> where F::Target: FeeEstimator, L::Target: Logger,
+ fn check_remote_fee<F: Deref, L: Deref>(
+ channel_type: &ChannelTypeFeatures, fee_estimator: &LowerBoundedFeeEstimator<F>,
+ feerate_per_kw: u32, cur_feerate_per_kw: Option<u32>, logger: &L
+ ) -> Result<(), ChannelError> where F::Target: FeeEstimator, L::Target: Logger,
{
// We only bound the fee updates on the upper side to prevent completely absurd feerates,
// always accepting up to 25 sat/vByte or 10x our fee estimator's "High Priority" fee.
// We generally don't care too much if they set the feerate to something very high, but it
- // could result in the channel being useless due to everything being dust.
- let upper_limit = cmp::max(250 * 25,
- fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::HighPriority) as u64 * 10);
- if feerate_per_kw as u64 > upper_limit {
- return Err(ChannelError::Close(format!("Peer's feerate much too high. Actual: {}. Our expected upper limit: {}", feerate_per_kw, upper_limit)));
- }
- let lower_limit = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Background);
+ // could result in the channel being useless due to everything being dust. This doesn't
+ // apply to channels supporting anchor outputs since HTLC transactions are pre-signed with a
+ // zero fee, so their fee is no longer considered to determine dust limits.
+ if !channel_type.supports_anchors_zero_fee_htlc_tx() {
+ let upper_limit = cmp::max(250 * 25,
+ fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::HighPriority) as u64 * 10);
+ if feerate_per_kw as u64 > upper_limit {
+ return Err(ChannelError::Close(format!("Peer's feerate much too high. Actual: {}. Our expected upper limit: {}", feerate_per_kw, upper_limit)));
+ }
+ }
+
+ // We can afford to use a lower bound with anchors than previously since we can now bump
+ // fees when broadcasting our commitment. However, we must still make sure we meet the
+ // minimum mempool feerate, until package relay is deployed, such that we can ensure the
+ // commitment transaction propagates throughout node mempools on its own.
+ let lower_limit_conf_target = if channel_type.supports_anchors_zero_fee_htlc_tx() {
+ ConfirmationTarget::MempoolMinimum
+ } else {
+ ConfirmationTarget::Background
+ };
+ let lower_limit = fee_estimator.bounded_sat_per_1000_weight(lower_limit_conf_target);
// Some fee estimators round up to the next full sat/vbyte (ie 250 sats per kw), causing
// occasional issues with feerate disagreements between an initiator that wants a feerate
// of 1.1 sat/vbyte and a receiver that wants 1.1 rounded up to 2. Thus, we always add 250
}
pub fn get_update_fulfill_htlc_and_commit<L: Deref>(&mut self, htlc_id: u64, payment_preimage: PaymentPreimage, logger: &L) -> UpdateFulfillCommitFetch where L::Target: Logger {
- let release_cs_monitor = self.context.pending_monitor_updates.iter().all(|upd| !upd.blocked);
+ let release_cs_monitor = self.context.blocked_monitor_updates.is_empty();
match self.get_update_fulfill_htlc(htlc_id, payment_preimage, logger) {
UpdateFulfillFetch::NewClaim { mut monitor_update, htlc_value_msat, msg } => {
// Even if we aren't supposed to let new monitor updates with commitment state
// matter what. Sadly, to push a new monitor update which flies before others
// already queued, we have to insert it into the pending queue and update the
// update_ids of all the following monitors.
- let unblocked_update_pos = if release_cs_monitor && msg.is_some() {
+ if release_cs_monitor && msg.is_some() {
let mut additional_update = self.build_commitment_no_status_check(logger);
// build_commitment_no_status_check may bump latest_monitor_id but we want them
// to be strictly increasing by one, so decrement it here.
self.context.latest_monitor_update_id = monitor_update.update_id;
monitor_update.updates.append(&mut additional_update.updates);
- self.context.pending_monitor_updates.push(PendingChannelMonitorUpdate {
- update: monitor_update, blocked: false,
- });
- self.context.pending_monitor_updates.len() - 1
} else {
- let insert_pos = self.context.pending_monitor_updates.iter().position(|upd| upd.blocked)
- .unwrap_or(self.context.pending_monitor_updates.len());
- let new_mon_id = self.context.pending_monitor_updates.get(insert_pos)
+ let new_mon_id = self.context.blocked_monitor_updates.get(0)
.map(|upd| upd.update.update_id).unwrap_or(monitor_update.update_id);
monitor_update.update_id = new_mon_id;
- self.context.pending_monitor_updates.insert(insert_pos, PendingChannelMonitorUpdate {
- update: monitor_update, blocked: false,
- });
- for held_update in self.context.pending_monitor_updates.iter_mut().skip(insert_pos + 1) {
+ for held_update in self.context.blocked_monitor_updates.iter_mut() {
held_update.update.update_id += 1;
}
if msg.is_some() {
debug_assert!(false, "If there is a pending blocked monitor we should have MonitorUpdateInProgress set");
let update = self.build_commitment_no_status_check(logger);
- self.context.pending_monitor_updates.push(PendingChannelMonitorUpdate {
- update, blocked: true,
+ self.context.blocked_monitor_updates.push(PendingChannelMonitorUpdate {
+ update,
});
}
- insert_pos
- };
- self.monitor_updating_paused(false, msg.is_some(), false, Vec::new(), Vec::new(), Vec::new());
- UpdateFulfillCommitFetch::NewClaim {
- monitor_update: &self.context.pending_monitor_updates.get(unblocked_update_pos)
- .expect("We just pushed the monitor update").update,
- htlc_value_msat,
}
+
+ self.monitor_updating_paused(false, msg.is_some(), false, Vec::new(), Vec::new(), Vec::new());
+ UpdateFulfillCommitFetch::NewClaim { monitor_update, htlc_value_msat, }
},
UpdateFulfillFetch::DuplicateClaim {} => UpdateFulfillCommitFetch::DuplicateClaim {},
}
Ok(self.get_announcement_sigs(node_signer, genesis_block_hash, user_config, best_block.height(), logger))
}
- pub fn update_add_htlc<F, L: Deref>(&mut self, msg: &msgs::UpdateAddHTLC, mut pending_forward_status: PendingHTLCStatus, create_pending_htlc_status: F, logger: &L) -> Result<(), ChannelError>
- where F: for<'a> Fn(&'a Self, PendingHTLCStatus, u16) -> PendingHTLCStatus, L::Target: Logger {
+ pub fn update_add_htlc<F, FE: Deref, L: Deref>(
+ &mut self, msg: &msgs::UpdateAddHTLC, mut pending_forward_status: PendingHTLCStatus,
+ create_pending_htlc_status: F, fee_estimator: &LowerBoundedFeeEstimator<FE>, logger: &L
+ ) -> Result<(), ChannelError>
+ where F: for<'a> Fn(&'a Self, PendingHTLCStatus, u16) -> PendingHTLCStatus,
+ FE::Target: FeeEstimator, L::Target: Logger,
+ {
// We can't accept HTLCs sent after we've sent a shutdown.
let local_sent_shutdown = (self.context.channel_state & (ChannelState::ChannelReady as u32 | ChannelState::LocalShutdownSent as u32)) != (ChannelState::ChannelReady as u32);
if local_sent_shutdown {
}
}
- let (htlc_timeout_dust_limit, htlc_success_dust_limit) = if self.context.opt_anchors() {
+ let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(fee_estimator);
+ let (htlc_timeout_dust_limit, htlc_success_dust_limit) = if self.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
(0, 0)
} else {
let dust_buffer_feerate = self.context.get_dust_buffer_feerate(None) as u64;
- (dust_buffer_feerate * htlc_timeout_tx_weight(false) / 1000,
- dust_buffer_feerate * htlc_success_tx_weight(false) / 1000)
+ (dust_buffer_feerate * htlc_timeout_tx_weight(self.context.get_channel_type()) / 1000,
+ dust_buffer_feerate * htlc_success_tx_weight(self.context.get_channel_type()) / 1000)
};
let exposure_dust_limit_timeout_sats = htlc_timeout_dust_limit + self.context.counterparty_dust_limit_satoshis;
if msg.amount_msat / 1000 < exposure_dust_limit_timeout_sats {
let on_counterparty_tx_dust_htlc_exposure_msat = inbound_stats.on_counterparty_tx_dust_exposure_msat + outbound_stats.on_counterparty_tx_dust_exposure_msat + msg.amount_msat;
- if on_counterparty_tx_dust_htlc_exposure_msat > self.context.get_max_dust_htlc_exposure_msat() {
+ if on_counterparty_tx_dust_htlc_exposure_msat > max_dust_htlc_exposure_msat {
log_info!(logger, "Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on counterparty commitment tx",
- on_counterparty_tx_dust_htlc_exposure_msat, self.context.get_max_dust_htlc_exposure_msat());
+ on_counterparty_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat);
pending_forward_status = create_pending_htlc_status(self, pending_forward_status, 0x1000|7);
}
}
let exposure_dust_limit_success_sats = htlc_success_dust_limit + self.context.holder_dust_limit_satoshis;
if msg.amount_msat / 1000 < exposure_dust_limit_success_sats {
let on_holder_tx_dust_htlc_exposure_msat = inbound_stats.on_holder_tx_dust_exposure_msat + outbound_stats.on_holder_tx_dust_exposure_msat + msg.amount_msat;
- if on_holder_tx_dust_htlc_exposure_msat > self.context.get_max_dust_htlc_exposure_msat() {
+ if on_holder_tx_dust_htlc_exposure_msat > max_dust_htlc_exposure_msat {
log_info!(logger, "Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx",
- on_holder_tx_dust_htlc_exposure_msat, self.context.get_max_dust_htlc_exposure_msat());
+ on_holder_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat);
pending_forward_status = create_pending_htlc_status(self, pending_forward_status, 0x1000|7);
}
}
Ok(())
}
- pub fn commitment_signed<L: Deref>(&mut self, msg: &msgs::CommitmentSigned, logger: &L) -> Result<Option<&ChannelMonitorUpdate>, ChannelError>
+ pub fn commitment_signed<L: Deref>(&mut self, msg: &msgs::CommitmentSigned, logger: &L) -> Result<Option<ChannelMonitorUpdate>, ChannelError>
where L::Target: Logger
{
if (self.context.channel_state & (ChannelState::ChannelReady as u32)) != (ChannelState::ChannelReady as u32) {
for (idx, (htlc, mut source_opt)) in htlcs_cloned.drain(..).enumerate() {
if let Some(_) = htlc.transaction_output_index {
let htlc_tx = chan_utils::build_htlc_transaction(&commitment_txid, commitment_stats.feerate_per_kw,
- self.context.get_counterparty_selected_contest_delay().unwrap(), &htlc, self.context.opt_anchors(),
- false, &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
+ self.context.get_counterparty_selected_contest_delay().unwrap(), &htlc, &self.context.channel_type,
+ &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
- let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, self.context.opt_anchors(), &keys);
- let htlc_sighashtype = if self.context.opt_anchors() { EcdsaSighashType::SinglePlusAnyoneCanPay } else { EcdsaSighashType::All };
+ let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, &self.context.channel_type, &keys);
+ let htlc_sighashtype = if self.context.channel_type.supports_anchors_zero_fee_htlc_tx() { EcdsaSighashType::SinglePlusAnyoneCanPay } else { EcdsaSighashType::All };
let htlc_sighash = hash_to_message!(&sighash::SighashCache::new(&htlc_tx).segwit_signature_hash(0, &htlc_redeemscript, htlc.amount_msat / 1000, htlc_sighashtype).unwrap()[..]);
log_trace!(logger, "Checking HTLC tx signature {} by key {} against tx {} (sighash {}) with redeemscript {} in channel {}.",
log_bytes!(msg.htlc_signatures[idx].serialize_compact()[..]), log_bytes!(keys.countersignatory_htlc_key.serialize()),
/// Public version of the below, checking relevant preconditions first.
/// If we're not in a state where freeing the holding cell makes sense, this is a no-op and
/// returns `(None, Vec::new())`.
- pub fn maybe_free_holding_cell_htlcs<L: Deref>(&mut self, logger: &L) -> (Option<&ChannelMonitorUpdate>, Vec<(HTLCSource, PaymentHash)>) where L::Target: Logger {
+ pub fn maybe_free_holding_cell_htlcs<F: Deref, L: Deref>(
+ &mut self, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
+ ) -> (Option<ChannelMonitorUpdate>, Vec<(HTLCSource, PaymentHash)>)
+ where F::Target: FeeEstimator, L::Target: Logger
+ {
if self.context.channel_state >= ChannelState::ChannelReady as u32 &&
(self.context.channel_state & (ChannelState::AwaitingRemoteRevoke as u32 | ChannelState::PeerDisconnected as u32 | ChannelState::MonitorUpdateInProgress as u32)) == 0 {
- self.free_holding_cell_htlcs(logger)
+ self.free_holding_cell_htlcs(fee_estimator, logger)
} else { (None, Vec::new()) }
}
/// Frees any pending commitment updates in the holding cell, generating the relevant messages
/// for our counterparty.
- fn free_holding_cell_htlcs<L: Deref>(&mut self, logger: &L) -> (Option<&ChannelMonitorUpdate>, Vec<(HTLCSource, PaymentHash)>) where L::Target: Logger {
+ fn free_holding_cell_htlcs<F: Deref, L: Deref>(
+ &mut self, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
+ ) -> (Option<ChannelMonitorUpdate>, Vec<(HTLCSource, PaymentHash)>)
+ where F::Target: FeeEstimator, L::Target: Logger
+ {
assert_eq!(self.context.channel_state & ChannelState::MonitorUpdateInProgress as u32, 0);
if self.context.holding_cell_htlc_updates.len() != 0 || self.context.holding_cell_update_fee.is_some() {
log_trace!(logger, "Freeing holding cell with {} HTLC updates{} in channel {}", self.context.holding_cell_htlc_updates.len(),
// handling this case better and maybe fulfilling some of the HTLCs while attempting
// to rebalance channels.
match &htlc_update {
- &HTLCUpdateAwaitingACK::AddHTLC {amount_msat, cltv_expiry, ref payment_hash, ref source, ref onion_routing_packet, ..} => {
- match self.send_htlc(amount_msat, *payment_hash, cltv_expiry, source.clone(), onion_routing_packet.clone(), false, logger) {
+ &HTLCUpdateAwaitingACK::AddHTLC {
+ amount_msat, cltv_expiry, ref payment_hash, ref source, ref onion_routing_packet,
+ skimmed_fee_msat, ..
+ } => {
+ match self.send_htlc(amount_msat, *payment_hash, cltv_expiry, source.clone(),
+ onion_routing_packet.clone(), false, skimmed_fee_msat, fee_estimator, logger)
+ {
Ok(update_add_msg_option) => update_add_htlcs.push(update_add_msg_option.unwrap()),
Err(e) => {
match e {
return (None, htlcs_to_fail);
}
let update_fee = if let Some(feerate) = self.context.holding_cell_update_fee.take() {
- self.send_update_fee(feerate, false, logger)
+ self.send_update_fee(feerate, false, fee_estimator, logger)
} else {
None
};
/// waiting on this revoke_and_ack. The generation of this new commitment_signed may also fail,
/// generating an appropriate error *after* the channel state has been updated based on the
/// revoke_and_ack message.
- pub fn revoke_and_ack<L: Deref>(&mut self, msg: &msgs::RevokeAndACK, logger: &L) -> Result<(Vec<(HTLCSource, PaymentHash)>, Option<&ChannelMonitorUpdate>), ChannelError>
- where L::Target: Logger,
+ pub fn revoke_and_ack<F: Deref, L: Deref>(&mut self, msg: &msgs::RevokeAndACK,
+ fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
+ ) -> Result<(Vec<(HTLCSource, PaymentHash)>, Option<ChannelMonitorUpdate>), ChannelError>
+ where F::Target: FeeEstimator, L::Target: Logger,
{
if (self.context.channel_state & (ChannelState::ChannelReady as u32)) != (ChannelState::ChannelReady as u32) {
return Err(ChannelError::Close("Got revoke/ACK message when channel was not in an operational state".to_owned()));
return Ok((Vec::new(), self.push_ret_blockable_mon_update(monitor_update)));
}
- match self.free_holding_cell_htlcs(logger) {
- (Some(_), htlcs_to_fail) => {
- let mut additional_update = self.context.pending_monitor_updates.pop().unwrap().update;
+ match self.free_holding_cell_htlcs(fee_estimator, logger) {
+ (Some(mut additional_update), htlcs_to_fail) => {
// free_holding_cell_htlcs may bump latest_monitor_id multiple times but we want them to be
// strictly increasing by one, so decrement it here.
self.context.latest_monitor_update_id = monitor_update.update_id;
/// Queues up an outbound update fee by placing it in the holding cell. You should call
/// [`Self::maybe_free_holding_cell_htlcs`] in order to actually generate and send the
/// commitment update.
- pub fn queue_update_fee<L: Deref>(&mut self, feerate_per_kw: u32, logger: &L) where L::Target: Logger {
- let msg_opt = self.send_update_fee(feerate_per_kw, true, logger);
+ pub fn queue_update_fee<F: Deref, L: Deref>(&mut self, feerate_per_kw: u32,
+ fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L)
+ where F::Target: FeeEstimator, L::Target: Logger
+ {
+ let msg_opt = self.send_update_fee(feerate_per_kw, true, fee_estimator, logger);
assert!(msg_opt.is_none(), "We forced holding cell?");
}
///
/// You MUST call [`Self::send_commitment_no_state_update`] prior to any other calls on this
/// [`Channel`] if `force_holding_cell` is false.
- fn send_update_fee<L: Deref>(&mut self, feerate_per_kw: u32, mut force_holding_cell: bool, logger: &L) -> Option<msgs::UpdateFee> where L::Target: Logger {
+ fn send_update_fee<F: Deref, L: Deref>(
+ &mut self, feerate_per_kw: u32, mut force_holding_cell: bool,
+ fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
+ ) -> Option<msgs::UpdateFee>
+ where F::Target: FeeEstimator, L::Target: Logger
+ {
if !self.context.is_outbound() {
panic!("Cannot send fee from inbound channel");
}
let outbound_stats = self.context.get_outbound_pending_htlc_stats(Some(feerate_per_kw));
let keys = self.context.build_holder_transaction_keys(self.context.cur_holder_commitment_transaction_number);
let commitment_stats = self.context.build_commitment_transaction(self.context.cur_holder_commitment_transaction_number, &keys, true, true, logger);
- let buffer_fee_msat = commit_tx_fee_sat(feerate_per_kw, commitment_stats.num_nondust_htlcs + outbound_stats.on_holder_tx_holding_cell_htlcs_count as usize + CONCURRENT_INBOUND_HTLC_FEE_BUFFER as usize, self.context.opt_anchors()) * 1000;
+ let buffer_fee_msat = commit_tx_fee_sat(feerate_per_kw, commitment_stats.num_nondust_htlcs + outbound_stats.on_holder_tx_holding_cell_htlcs_count as usize + CONCURRENT_INBOUND_HTLC_FEE_BUFFER as usize, self.context.get_channel_type()) * 1000;
let holder_balance_msat = commitment_stats.local_balance_msat - outbound_stats.holding_cell_msat;
if holder_balance_msat < buffer_fee_msat + self.context.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 {
//TODO: auto-close after a number of failures?
// Note, we evaluate pending htlc "preemptive" trimmed-to-dust threshold at the proposed `feerate_per_kw`.
let holder_tx_dust_exposure = inbound_stats.on_holder_tx_dust_exposure_msat + outbound_stats.on_holder_tx_dust_exposure_msat;
let counterparty_tx_dust_exposure = inbound_stats.on_counterparty_tx_dust_exposure_msat + outbound_stats.on_counterparty_tx_dust_exposure_msat;
- if holder_tx_dust_exposure > self.context.get_max_dust_htlc_exposure_msat() {
+ let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(fee_estimator);
+ if holder_tx_dust_exposure > max_dust_htlc_exposure_msat {
log_debug!(logger, "Cannot afford to send new feerate at {} without infringing max dust htlc exposure", feerate_per_kw);
return None;
}
- if counterparty_tx_dust_exposure > self.context.get_max_dust_htlc_exposure_msat() {
+ if counterparty_tx_dust_exposure > max_dust_htlc_exposure_msat {
log_debug!(logger, "Cannot afford to send new feerate at {} without infringing max dust htlc exposure", feerate_per_kw);
return None;
}
{
assert_eq!(self.context.channel_state & ChannelState::MonitorUpdateInProgress as u32, ChannelState::MonitorUpdateInProgress as u32);
self.context.channel_state &= !(ChannelState::MonitorUpdateInProgress as u32);
- let mut found_blocked = false;
- self.context.pending_monitor_updates.retain(|upd| {
- if found_blocked { debug_assert!(upd.blocked, "No mons may be unblocked after a blocked one"); }
- if upd.blocked { found_blocked = true; }
- upd.blocked
- });
// If we're past (or at) the FundingSent stage on an outbound channel, try to
// (re-)broadcast the funding transaction as we may have declined to broadcast it when we
if self.context.channel_state & (ChannelState::PeerDisconnected as u32) == ChannelState::PeerDisconnected as u32 {
return Err(ChannelError::Close("Peer sent update_fee when we needed a channel_reestablish".to_owned()));
}
- Channel::<Signer>::check_remote_fee(fee_estimator, msg.feerate_per_kw, Some(self.context.feerate_per_kw), logger)?;
+ Channel::<Signer>::check_remote_fee(&self.context.channel_type, fee_estimator, msg.feerate_per_kw, Some(self.context.feerate_per_kw), logger)?;
let feerate_over_dust_buffer = msg.feerate_per_kw > self.context.get_dust_buffer_feerate(None);
self.context.pending_update_fee = Some((msg.feerate_per_kw, FeeUpdateState::RemoteAnnounced));
let outbound_stats = self.context.get_outbound_pending_htlc_stats(None);
let holder_tx_dust_exposure = inbound_stats.on_holder_tx_dust_exposure_msat + outbound_stats.on_holder_tx_dust_exposure_msat;
let counterparty_tx_dust_exposure = inbound_stats.on_counterparty_tx_dust_exposure_msat + outbound_stats.on_counterparty_tx_dust_exposure_msat;
- if holder_tx_dust_exposure > self.context.get_max_dust_htlc_exposure_msat() {
+ let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(fee_estimator);
+ if holder_tx_dust_exposure > max_dust_htlc_exposure_msat {
return Err(ChannelError::Close(format!("Peer sent update_fee with a feerate ({}) which may over-expose us to dust-in-flight on our own transactions (totaling {} msat)",
msg.feerate_per_kw, holder_tx_dust_exposure)));
}
- if counterparty_tx_dust_exposure > self.context.get_max_dust_htlc_exposure_msat() {
+ if counterparty_tx_dust_exposure > max_dust_htlc_exposure_msat {
return Err(ChannelError::Close(format!("Peer sent update_fee with a feerate ({}) which may over-expose us to dust-in-flight on our counterparty's transactions (totaling {} msat)",
msg.feerate_per_kw, counterparty_tx_dust_exposure)));
}
payment_hash: htlc.payment_hash,
cltv_expiry: htlc.cltv_expiry,
onion_routing_packet: (**onion_packet).clone(),
+ skimmed_fee_msat: htlc.skimmed_fee_msat,
});
}
}
/// this point if we're the funder we should send the initial closing_signed, and in any case
/// shutdown should complete within a reasonable timeframe.
fn closing_negotiation_ready(&self) -> bool {
- self.context.pending_inbound_htlcs.is_empty() && self.context.pending_outbound_htlcs.is_empty() &&
- self.context.channel_state &
- (BOTH_SIDES_SHUTDOWN_MASK | ChannelState::AwaitingRemoteRevoke as u32 |
- ChannelState::PeerDisconnected as u32 | ChannelState::MonitorUpdateInProgress as u32)
- == BOTH_SIDES_SHUTDOWN_MASK &&
- self.context.pending_update_fee.is_none()
+ self.context.closing_negotiation_ready()
}
/// Checks if the closing_signed negotiation is making appropriate progress, possibly returning
pub fn shutdown<SP: Deref>(
&mut self, signer_provider: &SP, their_features: &InitFeatures, msg: &msgs::Shutdown
- ) -> Result<(Option<msgs::Shutdown>, Option<&ChannelMonitorUpdate>, Vec<(HTLCSource, PaymentHash)>), ChannelError>
+ ) -> Result<(Option<msgs::Shutdown>, Option<ChannelMonitorUpdate>, Vec<(HTLCSource, PaymentHash)>), ChannelError>
where SP::Target: SignerProvider
{
if self.context.channel_state & (ChannelState::PeerDisconnected as u32) == ChannelState::PeerDisconnected as u32 {
}],
};
self.monitor_updating_paused(false, false, false, Vec::new(), Vec::new(), Vec::new());
- if self.push_blockable_mon_update(monitor_update) {
- self.context.pending_monitor_updates.last().map(|upd| &upd.update)
- } else { None }
+ self.push_ret_blockable_mon_update(monitor_update)
} else { None };
let shutdown = if send_shutdown {
Some(msgs::Shutdown {
(self.context.channel_state & ChannelState::MonitorUpdateInProgress as u32) != 0
}
- pub fn get_latest_complete_monitor_update_id(&self) -> u64 {
- if self.context.pending_monitor_updates.is_empty() { return self.context.get_latest_monitor_update_id(); }
- self.context.pending_monitor_updates[0].update.update_id - 1
+ /// Gets the latest [`ChannelMonitorUpdate`] ID which has been released and is in-flight.
+ pub fn get_latest_unblocked_monitor_update_id(&self) -> u64 {
+ if self.context.blocked_monitor_updates.is_empty() { return self.context.get_latest_monitor_update_id(); }
+ self.context.blocked_monitor_updates[0].update.update_id - 1
}
/// Returns the next blocked monitor update, if one exists, and a bool which indicates a
/// further blocked monitor update exists after the next.
- pub fn unblock_next_blocked_monitor_update(&mut self) -> Option<(&ChannelMonitorUpdate, bool)> {
- for i in 0..self.context.pending_monitor_updates.len() {
- if self.context.pending_monitor_updates[i].blocked {
- self.context.pending_monitor_updates[i].blocked = false;
- return Some((&self.context.pending_monitor_updates[i].update,
- self.context.pending_monitor_updates.len() > i + 1));
- }
- }
- None
- }
-
- /// Pushes a new monitor update into our monitor update queue, returning whether it should be
- /// immediately given to the user for persisting or if it should be held as blocked.
- fn push_blockable_mon_update(&mut self, update: ChannelMonitorUpdate) -> bool {
- let release_monitor = self.context.pending_monitor_updates.iter().all(|upd| !upd.blocked);
- self.context.pending_monitor_updates.push(PendingChannelMonitorUpdate {
- update, blocked: !release_monitor
- });
- release_monitor
+ pub fn unblock_next_blocked_monitor_update(&mut self) -> Option<(ChannelMonitorUpdate, bool)> {
+ if self.context.blocked_monitor_updates.is_empty() { return None; }
+ Some((self.context.blocked_monitor_updates.remove(0).update,
+ !self.context.blocked_monitor_updates.is_empty()))
}
- /// Pushes a new monitor update into our monitor update queue, returning a reference to it if
- /// it should be immediately given to the user for persisting or `None` if it should be held as
- /// blocked.
+ /// Pushes a new monitor update into our monitor update queue, returning it if it should be
+ /// immediately given to the user for persisting or `None` if it should be held as blocked.
fn push_ret_blockable_mon_update(&mut self, update: ChannelMonitorUpdate)
- -> Option<&ChannelMonitorUpdate> {
- let release_monitor = self.push_blockable_mon_update(update);
- if release_monitor { self.context.pending_monitor_updates.last().map(|upd| &upd.update) } else { None }
- }
-
- pub fn no_monitor_updates_pending(&self) -> bool {
- self.context.pending_monitor_updates.is_empty()
- }
-
- pub fn complete_all_mon_updates_through(&mut self, update_id: u64) {
- self.context.pending_monitor_updates.retain(|upd| {
- if upd.update.update_id <= update_id {
- assert!(!upd.blocked, "Completed update must have flown");
- false
- } else { true }
- });
- }
-
- pub fn complete_one_mon_update(&mut self, update_id: u64) {
- self.context.pending_monitor_updates.retain(|upd| upd.update.update_id != update_id);
+ -> Option<ChannelMonitorUpdate> {
+ let release_monitor = self.context.blocked_monitor_updates.is_empty();
+ if !release_monitor {
+ self.context.blocked_monitor_updates.push(PendingChannelMonitorUpdate {
+ update,
+ });
+ None
+ } else {
+ Some(update)
+ }
}
- /// Returns an iterator over all unblocked monitor updates which have not yet completed.
- pub fn uncompleted_unblocked_mon_updates(&self) -> impl Iterator<Item=&ChannelMonitorUpdate> {
- self.context.pending_monitor_updates.iter()
- .filter_map(|upd| if upd.blocked { None } else { Some(&upd.update) })
+ pub fn blocked_monitor_updates_pending(&self) -> usize {
+ self.context.blocked_monitor_updates.len()
}
/// Returns true if the channel is awaiting the persistence of the initial ChannelMonitor.
/// commitment update.
///
/// `Err`s will only be [`ChannelError::Ignore`].
- pub fn queue_add_htlc<L: Deref>(&mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource,
- onion_routing_packet: msgs::OnionPacket, logger: &L)
- -> Result<(), ChannelError> where L::Target: Logger {
+ pub fn queue_add_htlc<F: Deref, L: Deref>(
+ &mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource,
+ onion_routing_packet: msgs::OnionPacket, skimmed_fee_msat: Option<u64>,
+ fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
+ ) -> Result<(), ChannelError>
+ where F::Target: FeeEstimator, L::Target: Logger
+ {
self
- .send_htlc(amount_msat, payment_hash, cltv_expiry, source, onion_routing_packet, true, logger)
+ .send_htlc(amount_msat, payment_hash, cltv_expiry, source, onion_routing_packet, true,
+ skimmed_fee_msat, fee_estimator, logger)
.map(|msg_opt| assert!(msg_opt.is_none(), "We forced holding cell?"))
.map_err(|err| {
if let ChannelError::Ignore(_) = err { /* fine */ }
/// on this [`Channel`] if `force_holding_cell` is false.
///
/// `Err`s will only be [`ChannelError::Ignore`].
- fn send_htlc<L: Deref>(&mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource,
- onion_routing_packet: msgs::OnionPacket, mut force_holding_cell: bool, logger: &L)
- -> Result<Option<msgs::UpdateAddHTLC>, ChannelError> where L::Target: Logger {
+ fn send_htlc<F: Deref, L: Deref>(
+ &mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource,
+ onion_routing_packet: msgs::OnionPacket, mut force_holding_cell: bool,
+ skimmed_fee_msat: Option<u64>, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
+ ) -> Result<Option<msgs::UpdateAddHTLC>, ChannelError>
+ where F::Target: FeeEstimator, L::Target: Logger
+ {
if (self.context.channel_state & (ChannelState::ChannelReady as u32 | BOTH_SIDES_SHUTDOWN_MASK)) != (ChannelState::ChannelReady as u32) {
return Err(ChannelError::Ignore("Cannot send HTLC until channel is fully established and we haven't started shutting down".to_owned()));
}
return Err(ChannelError::Ignore("Cannot send 0-msat HTLC".to_owned()));
}
- let available_balances = self.context.get_available_balances();
+ let available_balances = self.context.get_available_balances(fee_estimator);
if amount_msat < available_balances.next_outbound_htlc_minimum_msat {
return Err(ChannelError::Ignore(format!("Cannot send less than our next-HTLC minimum - {} msat",
available_balances.next_outbound_htlc_minimum_msat)));
cltv_expiry,
source,
onion_routing_packet,
+ skimmed_fee_msat,
});
return Ok(None);
}
cltv_expiry,
state: OutboundHTLCState::LocalAnnounced(Box::new(onion_routing_packet.clone())),
source,
+ skimmed_fee_msat,
});
let res = msgs::UpdateAddHTLC {
payment_hash,
cltv_expiry,
onion_routing_packet,
+ skimmed_fee_msat,
};
self.context.next_holder_htlc_id += 1;
&& info.next_holder_htlc_id == self.context.next_holder_htlc_id
&& info.next_counterparty_htlc_id == self.context.next_counterparty_htlc_id
&& info.feerate == self.context.feerate_per_kw {
- let actual_fee = commit_tx_fee_msat(self.context.feerate_per_kw, commitment_stats.num_nondust_htlcs, self.context.opt_anchors());
+ let actual_fee = commit_tx_fee_msat(self.context.feerate_per_kw, commitment_stats.num_nondust_htlcs, self.context.get_channel_type());
assert_eq!(actual_fee, info.fee);
}
}
for (ref htlc_sig, ref htlc) in htlc_signatures.iter().zip(htlcs) {
log_trace!(logger, "Signed remote HTLC tx {} with redeemscript {} with pubkey {} -> {} in channel {}",
- encode::serialize_hex(&chan_utils::build_htlc_transaction(&counterparty_commitment_txid, commitment_stats.feerate_per_kw, self.context.get_holder_selected_contest_delay(), htlc, self.context.opt_anchors(), false, &counterparty_keys.broadcaster_delayed_payment_key, &counterparty_keys.revocation_key)),
- encode::serialize_hex(&chan_utils::get_htlc_redeemscript(&htlc, self.context.opt_anchors(), &counterparty_keys)),
+ encode::serialize_hex(&chan_utils::build_htlc_transaction(&counterparty_commitment_txid, commitment_stats.feerate_per_kw, self.context.get_holder_selected_contest_delay(), htlc, &self.context.channel_type, &counterparty_keys.broadcaster_delayed_payment_key, &counterparty_keys.revocation_key)),
+ encode::serialize_hex(&chan_utils::get_htlc_redeemscript(&htlc, &self.context.channel_type, &counterparty_keys)),
log_bytes!(counterparty_keys.broadcaster_htlc_key.serialize()),
log_bytes!(htlc_sig.serialize_compact()[..]), log_bytes!(self.context.channel_id()));
}
///
/// Shorthand for calling [`Self::send_htlc`] followed by a commitment update, see docs on
/// [`Self::send_htlc`] and [`Self::build_commitment_no_state_update`] for more info.
- pub fn send_htlc_and_commit<L: Deref>(&mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource, onion_routing_packet: msgs::OnionPacket, logger: &L) -> Result<Option<&ChannelMonitorUpdate>, ChannelError> where L::Target: Logger {
- let send_res = self.send_htlc(amount_msat, payment_hash, cltv_expiry, source, onion_routing_packet, false, logger);
+ pub fn send_htlc_and_commit<F: Deref, L: Deref>(
+ &mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32,
+ source: HTLCSource, onion_routing_packet: msgs::OnionPacket, skimmed_fee_msat: Option<u64>,
+ fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
+ ) -> Result<Option<ChannelMonitorUpdate>, ChannelError>
+ where F::Target: FeeEstimator, L::Target: Logger
+ {
+ let send_res = self.send_htlc(amount_msat, payment_hash, cltv_expiry, source,
+ onion_routing_packet, false, skimmed_fee_msat, fee_estimator, logger);
if let Err(e) = &send_res { if let ChannelError::Ignore(_) = e {} else { debug_assert!(false, "Sending cannot trigger channel failure"); } }
match send_res? {
Some(_) => {
/// [`ChannelMonitorUpdate`] will be returned).
pub fn get_shutdown<SP: Deref>(&mut self, signer_provider: &SP, their_features: &InitFeatures,
target_feerate_sats_per_kw: Option<u32>, override_shutdown_script: Option<ShutdownScript>)
- -> Result<(msgs::Shutdown, Option<&ChannelMonitorUpdate>, Vec<(HTLCSource, PaymentHash)>), APIError>
+ -> Result<(msgs::Shutdown, Option<ChannelMonitorUpdate>, Vec<(HTLCSource, PaymentHash)>), APIError>
where SP::Target: SignerProvider {
for htlc in self.context.pending_outbound_htlcs.iter() {
if let OutboundHTLCState::LocalAnnounced(_) = htlc.state {
}],
};
self.monitor_updating_paused(false, false, false, Vec::new(), Vec::new(), Vec::new());
- if self.push_blockable_mon_update(monitor_update) {
- self.context.pending_monitor_updates.last().map(|upd| &upd.update)
- } else { None }
+ self.push_ret_blockable_mon_update(monitor_update)
} else { None };
let shutdown = msgs::Shutdown {
channel_id: self.context.channel_id,
/// A not-yet-funded outbound (from holder) channel using V1 channel establishment.
pub(super) struct OutboundV1Channel<Signer: ChannelSigner> {
pub context: ChannelContext<Signer>,
+ pub unfunded_context: UnfundedChannelContext,
}
impl<Signer: WriteableEcdsaChannelSigner> OutboundV1Channel<Signer> {
let channel_type = Self::get_initial_channel_type(&config, their_features);
debug_assert!(channel_type.is_subset(&channelmanager::provided_channel_type_features(&config)));
- let feerate = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
+ let commitment_conf_target = if channel_type.supports_anchors_zero_fee_htlc_tx() {
+ ConfirmationTarget::MempoolMinimum
+ } else {
+ ConfirmationTarget::Normal
+ };
+ let commitment_feerate = fee_estimator.bounded_sat_per_1000_weight(commitment_conf_target);
let value_to_self_msat = channel_value_satoshis * 1000 - push_msat;
- let commitment_tx_fee = commit_tx_fee_msat(feerate, MIN_AFFORDABLE_HTLC_COUNT, channel_type.requires_anchors_zero_fee_htlc_tx());
+ let commitment_tx_fee = commit_tx_fee_msat(commitment_feerate, MIN_AFFORDABLE_HTLC_COUNT, &channel_type);
if value_to_self_msat < commitment_tx_fee {
return Err(APIError::APIMisuseError{ err: format!("Funding amount ({}) can't even pay fee for initial commitment transaction fee of {}.", value_to_self_msat / 1000, commitment_tx_fee / 1000) });
}
short_channel_id: None,
channel_creation_height: current_chain_height,
- feerate_per_kw: feerate,
+ feerate_per_kw: commitment_feerate,
counterparty_dust_limit_satoshis: 0,
holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS,
counterparty_max_htlc_value_in_flight_msat: 0,
is_outbound_from_holder: true,
counterparty_parameters: None,
funding_outpoint: None,
- opt_anchors: if channel_type.requires_anchors_zero_fee_htlc_tx() { Some(()) } else { None },
- opt_non_zero_fee_anchors: None
+ channel_type_features: channel_type.clone()
},
funding_transaction: None,
channel_type,
channel_keys_id,
- pending_monitor_updates: Vec::new(),
- }
+ blocked_monitor_updates: Vec::new(),
+ },
+ unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }
})
}
- /// If an Err is returned, it is a ChannelError::Close (for get_outbound_funding_created)
- fn get_outbound_funding_created_signature<L: Deref>(&mut self, logger: &L) -> Result<Signature, ChannelError> where L::Target: Logger {
+ /// If an Err is returned, it is a ChannelError::Close (for get_funding_created)
+ fn get_funding_created_signature<L: Deref>(&mut self, logger: &L) -> Result<Signature, ChannelError> where L::Target: Logger {
let counterparty_keys = self.context.build_remote_transaction_keys();
let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx;
Ok(self.context.holder_signer.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), &self.context.secp_ctx)
/// Note that channel_id changes during this call!
/// Do NOT broadcast the funding transaction until after a successful funding_signed call!
/// If an Err is returned, it is a ChannelError::Close.
- pub fn get_outbound_funding_created<L: Deref>(mut self, funding_transaction: Transaction, funding_txo: OutPoint, logger: &L)
+ pub fn get_funding_created<L: Deref>(mut self, funding_transaction: Transaction, funding_txo: OutPoint, logger: &L)
-> Result<(Channel<Signer>, msgs::FundingCreated), (Self, ChannelError)> where L::Target: Logger {
if !self.context.is_outbound() {
panic!("Tried to create outbound funding_created message on an inbound channel!");
self.context.channel_transaction_parameters.funding_outpoint = Some(funding_txo);
self.context.holder_signer.provide_channel_parameters(&self.context.channel_transaction_parameters);
- let signature = match self.get_outbound_funding_created_signature(logger) {
+ let signature = match self.get_funding_created_signature(logger) {
Ok(res) => res,
Err(e) => {
log_error!(logger, "Got bad signatures: {:?}!", e);
// Optionally, if the user would like to negotiate the `anchors_zero_fee_htlc_tx` option, we
// set it now. If they don't understand it, we'll fall back to our default of
// `only_static_remotekey`.
- #[cfg(anchors)]
- { // Attributes are not allowed on if expressions on our current MSRV of 1.41.
- if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
- their_features.supports_anchors_zero_fee_htlc_tx() {
- ret.set_anchors_zero_fee_htlc_tx_required();
- }
+ if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
+ their_features.supports_anchors_zero_fee_htlc_tx() {
+ ret.set_anchors_zero_fee_htlc_tx_required();
}
ret
/// If we receive an error message, it may only be a rejection of the channel type we tried,
/// not of our ability to open any channel at all. Thus, on error, we should first call this
/// and see if we get a new `OpenChannel` message, otherwise the channel is failed.
- pub(crate) fn maybe_handle_error_without_close(&mut self, chain_hash: BlockHash) -> Result<msgs::OpenChannel, ()> {
+ pub(crate) fn maybe_handle_error_without_close<F: Deref>(
+ &mut self, chain_hash: BlockHash, fee_estimator: &LowerBoundedFeeEstimator<F>
+ ) -> Result<msgs::OpenChannel, ()>
+ where
+ F::Target: FeeEstimator
+ {
if !self.context.is_outbound() || self.context.channel_state != ChannelState::OurInitSent as u32 { return Err(()); }
if self.context.channel_type == ChannelTypeFeatures::only_static_remote_key() {
// We've exhausted our options
// whatever reason.
if self.context.channel_type.supports_anchors_zero_fee_htlc_tx() {
self.context.channel_type.clear_anchors_zero_fee_htlc_tx();
- assert!(self.context.channel_transaction_parameters.opt_non_zero_fee_anchors.is_none());
- self.context.channel_transaction_parameters.opt_anchors = None;
+ self.context.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
+ assert!(!self.context.channel_transaction_parameters.channel_type_features.supports_anchors_nonzero_fee_htlc_tx());
} else if self.context.channel_type.supports_scid_privacy() {
self.context.channel_type.clear_scid_privacy();
} else {
self.context.channel_type = ChannelTypeFeatures::only_static_remote_key();
}
+ self.context.channel_transaction_parameters.channel_type_features = self.context.channel_type.clone();
Ok(self.get_open_channel(chain_hash))
}
if channel_type != ChannelTypeFeatures::only_static_remote_key() {
return Err(ChannelError::Close("Only static_remote_key is supported for non-negotiated channel types".to_owned()));
}
- self.context.channel_type = channel_type;
+ self.context.channel_type = channel_type.clone();
+ self.context.channel_transaction_parameters.channel_type_features = channel_type;
}
let counterparty_shutdown_scriptpubkey = if their_features.supports_upfront_shutdown_script() {
/// A not-yet-funded inbound (from counterparty) channel using V1 channel establishment.
pub(super) struct InboundV1Channel<Signer: ChannelSigner> {
pub context: ChannelContext<Signer>,
+ pub unfunded_context: UnfundedChannelContext,
}
impl<Signer: WriteableEcdsaChannelSigner> InboundV1Channel<Signer> {
}
channel_type
};
- let opt_anchors = channel_type.supports_anchors_zero_fee_htlc_tx();
let channel_keys_id = signer_provider.generate_channel_keys_id(true, msg.funding_satoshis, user_id);
let holder_signer = signer_provider.derive_channel_signer(msg.funding_satoshis, channel_keys_id);
if msg.htlc_minimum_msat >= full_channel_value_msat {
return Err(ChannelError::Close(format!("Minimum htlc value ({}) was larger than full channel value ({})", msg.htlc_minimum_msat, full_channel_value_msat)));
}
- Channel::<Signer>::check_remote_fee(fee_estimator, msg.feerate_per_kw, None, logger)?;
+ Channel::<Signer>::check_remote_fee(&channel_type, fee_estimator, msg.feerate_per_kw, None, logger)?;
let max_counterparty_selected_contest_delay = u16::min(config.channel_handshake_limits.their_to_self_delay, MAX_LOCAL_BREAKDOWN_TIMEOUT);
if msg.to_self_delay > max_counterparty_selected_contest_delay {
// check if the funder's amount for the initial commitment tx is sufficient
// for full fee payment plus a few HTLCs to ensure the channel will be useful.
let funders_amount_msat = msg.funding_satoshis * 1000 - msg.push_msat;
- let commitment_tx_fee = commit_tx_fee_msat(msg.feerate_per_kw, MIN_AFFORDABLE_HTLC_COUNT, opt_anchors) / 1000;
+ let commitment_tx_fee = commit_tx_fee_msat(msg.feerate_per_kw, MIN_AFFORDABLE_HTLC_COUNT, &channel_type) / 1000;
if funders_amount_msat / 1000 < commitment_tx_fee {
return Err(ChannelError::Close(format!("Funding amount ({} sats) can't even pay fee for initial commitment transaction fee of {} sats.", funders_amount_msat / 1000, commitment_tx_fee)));
}
pubkeys: counterparty_pubkeys,
}),
funding_outpoint: None,
- opt_anchors: if opt_anchors { Some(()) } else { None },
- opt_non_zero_fee_anchors: None
+ channel_type_features: channel_type.clone()
},
funding_transaction: None,
channel_type,
channel_keys_id,
- pending_monitor_updates: Vec::new(),
- }
+ blocked_monitor_updates: Vec::new(),
+ },
+ unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }
};
Ok(chan)
}
let mut preimages: Vec<&Option<PaymentPreimage>> = vec![];
+ let mut pending_outbound_skimmed_fees: Vec<Option<u64>> = Vec::new();
(self.context.pending_outbound_htlcs.len() as u64).write(writer)?;
- for htlc in self.context.pending_outbound_htlcs.iter() {
+ for (idx, htlc) in self.context.pending_outbound_htlcs.iter().enumerate() {
htlc.htlc_id.write(writer)?;
htlc.amount_msat.write(writer)?;
htlc.cltv_expiry.write(writer)?;
reason.write(writer)?;
}
}
+ if let Some(skimmed_fee) = htlc.skimmed_fee_msat {
+ if pending_outbound_skimmed_fees.is_empty() {
+ for _ in 0..idx { pending_outbound_skimmed_fees.push(None); }
+ }
+ pending_outbound_skimmed_fees.push(Some(skimmed_fee));
+ } else if !pending_outbound_skimmed_fees.is_empty() {
+ pending_outbound_skimmed_fees.push(None);
+ }
}
+ let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::new();
(self.context.holding_cell_htlc_updates.len() as u64).write(writer)?;
- for update in self.context.holding_cell_htlc_updates.iter() {
+ for (idx, update) in self.context.holding_cell_htlc_updates.iter().enumerate() {
match update {
- &HTLCUpdateAwaitingACK::AddHTLC { ref amount_msat, ref cltv_expiry, ref payment_hash, ref source, ref onion_routing_packet } => {
+ &HTLCUpdateAwaitingACK::AddHTLC {
+ ref amount_msat, ref cltv_expiry, ref payment_hash, ref source, ref onion_routing_packet,
+ skimmed_fee_msat,
+ } => {
0u8.write(writer)?;
amount_msat.write(writer)?;
cltv_expiry.write(writer)?;
payment_hash.write(writer)?;
source.write(writer)?;
onion_routing_packet.write(writer)?;
+
+ if let Some(skimmed_fee) = skimmed_fee_msat {
+ if holding_cell_skimmed_fees.is_empty() {
+ for _ in 0..idx { holding_cell_skimmed_fees.push(None); }
+ }
+ holding_cell_skimmed_fees.push(Some(skimmed_fee));
+ } else if !holding_cell_skimmed_fees.is_empty() { holding_cell_skimmed_fees.push(None); }
},
&HTLCUpdateAwaitingACK::ClaimHTLC { ref payment_preimage, ref htlc_id } => {
1u8.write(writer)?;
(5, self.context.config, required),
(6, serialized_holder_htlc_max_in_flight, option),
(7, self.context.shutdown_scriptpubkey, option),
+ (8, self.context.blocked_monitor_updates, optional_vec),
(9, self.context.target_closing_feerate_sats_per_kw, option),
- (11, self.context.monitor_pending_finalized_fulfills, vec_type),
+ (11, self.context.monitor_pending_finalized_fulfills, required_vec),
(13, self.context.channel_creation_height, required),
- (15, preimages, vec_type),
+ (15, preimages, required_vec),
(17, self.context.announcement_sigs_state, required),
(19, self.context.latest_inbound_scid_alias, option),
(21, self.context.outbound_scid_alias, required),
(28, holder_max_accepted_htlcs, option),
(29, self.context.temporary_channel_id, option),
(31, channel_pending_event_emitted, option),
- (33, self.context.pending_monitor_updates, vec_type),
+ (35, pending_outbound_skimmed_fees, optional_vec),
+ (37, holding_cell_skimmed_fees, optional_vec),
});
Ok(())
},
_ => return Err(DecodeError::InvalidValue),
},
+ skimmed_fee_msat: None,
});
}
payment_hash: Readable::read(reader)?,
source: Readable::read(reader)?,
onion_routing_packet: Readable::read(reader)?,
+ skimmed_fee_msat: None,
},
1 => HTLCUpdateAwaitingACK::ClaimHTLC {
payment_preimage: Readable::read(reader)?,
_ => return Err(DecodeError::InvalidValue),
};
- let channel_parameters: ChannelTransactionParameters = Readable::read(reader)?;
+ let mut channel_parameters: ChannelTransactionParameters = Readable::read(reader)?;
let funding_transaction = Readable::read(reader)?;
let counterparty_cur_commitment_point = Readable::read(reader)?;
let mut temporary_channel_id: Option<[u8; 32]> = None;
let mut holder_max_accepted_htlcs: Option<u16> = None;
- let mut pending_monitor_updates = Some(Vec::new());
+ let mut blocked_monitor_updates = Some(Vec::new());
+
+ let mut pending_outbound_skimmed_fees_opt: Option<Vec<Option<u64>>> = None;
+ let mut holding_cell_skimmed_fees_opt: Option<Vec<Option<u64>>> = None;
read_tlv_fields!(reader, {
(0, announcement_sigs, option),
(5, config, option), // Note that if none is provided we will *not* overwrite the existing one.
(6, holder_max_htlc_value_in_flight_msat, option),
(7, shutdown_scriptpubkey, option),
+ (8, blocked_monitor_updates, optional_vec),
(9, target_closing_feerate_sats_per_kw, option),
- (11, monitor_pending_finalized_fulfills, vec_type),
+ (11, monitor_pending_finalized_fulfills, optional_vec),
(13, channel_creation_height, option),
- (15, preimages_opt, vec_type),
+ (15, preimages_opt, optional_vec),
(17, announcement_sigs_state, option),
(19, latest_inbound_scid_alias, option),
(21, outbound_scid_alias, option),
(28, holder_max_accepted_htlcs, option),
(29, temporary_channel_id, option),
(31, channel_pending_event_emitted, option),
- (33, pending_monitor_updates, vec_type),
+ (35, pending_outbound_skimmed_fees_opt, optional_vec),
+ (37, holding_cell_skimmed_fees_opt, optional_vec),
});
let (channel_keys_id, holder_signer) = if let Some(channel_keys_id) = channel_keys_id {
return Err(DecodeError::UnknownRequiredFeature);
}
+ // ChannelTransactionParameters may have had an empty features set upon deserialization.
+ // To account for that, we're proactively setting/overriding the field here.
+ channel_parameters.channel_type_features = chan_features.clone();
+
let mut secp_ctx = Secp256k1::new();
secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes());
let holder_max_accepted_htlcs = holder_max_accepted_htlcs.unwrap_or(DEFAULT_MAX_HTLCS);
+ if let Some(skimmed_fees) = pending_outbound_skimmed_fees_opt {
+ let mut iter = skimmed_fees.into_iter();
+ for htlc in pending_outbound_htlcs.iter_mut() {
+ htlc.skimmed_fee_msat = iter.next().ok_or(DecodeError::InvalidValue)?;
+ }
+ // We expect all skimmed fees to be consumed above
+ if iter.next().is_some() { return Err(DecodeError::InvalidValue) }
+ }
+ if let Some(skimmed_fees) = holding_cell_skimmed_fees_opt {
+ let mut iter = skimmed_fees.into_iter();
+ for htlc in holding_cell_htlc_updates.iter_mut() {
+ if let HTLCUpdateAwaitingACK::AddHTLC { ref mut skimmed_fee_msat, .. } = htlc {
+ *skimmed_fee_msat = iter.next().ok_or(DecodeError::InvalidValue)?;
+ }
+ }
+ // We expect all skimmed fees to be consumed above
+ if iter.next().is_some() { return Err(DecodeError::InvalidValue) }
+ }
+
Ok(Channel {
context: ChannelContext {
user_id,
channel_type: channel_type.unwrap(),
channel_keys_id,
- pending_monitor_updates: pending_monitor_updates.unwrap(),
+ blocked_monitor_updates: blocked_monitor_updates.unwrap(),
}
})
}
use hex;
use crate::ln::PaymentHash;
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
- #[cfg(anchors)]
use crate::ln::channel::InitFeatures;
use crate::ln::channel::{Channel, InboundHTLCOutput, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, commit_tx_fee_msat};
use crate::ln::channel::{MAX_FUNDING_SATOSHIS_NO_WUMBO, TOTAL_BITCOIN_SUPPLY_SATOSHIS, MIN_THEIR_CHAN_RESERVE_SATOSHIS};
// arithmetic, causing a panic with debug assertions enabled.
let fee_est = TestFeeEstimator { fee_est: 42 };
let bounded_fee_estimator = LowerBoundedFeeEstimator::new(&fee_est);
- assert!(Channel::<InMemorySigner>::check_remote_fee(&bounded_fee_estimator,
+ assert!(Channel::<InMemorySigner>::check_remote_fee(
+ &ChannelTypeFeatures::only_static_remote_key(), &bounded_fee_estimator,
u32::max_value(), None, &&test_utils::TestLogger::new()).is_err());
}
}
}
- #[cfg(not(feature = "grind_signatures"))]
+ #[cfg(all(feature = "_test_vectors", not(feature = "grind_signatures")))]
fn public_from_secret_hex(secp_ctx: &Secp256k1<bitcoin::secp256k1::All>, hex: &str) -> PublicKey {
PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&hex::decode(hex).unwrap()[..]).unwrap())
}
value: 10000000, script_pubkey: output_script.clone(),
}]};
let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
- let (mut node_a_chan, funding_created_msg) = node_a_chan.get_outbound_funding_created(tx.clone(), funding_outpoint, &&logger).map_err(|_| ()).unwrap();
+ let (mut node_a_chan, funding_created_msg) = node_a_chan.get_funding_created(tx.clone(), funding_outpoint, &&logger).map_err(|_| ()).unwrap();
let (_, funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&keys_provider, &&logger).map_err(|_| ()).unwrap();
// Node B --> Node A: funding signed
session_priv: SecretKey::from_slice(&hex::decode("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()[..]).unwrap(),
first_hop_htlc_msat: 548,
payment_id: PaymentId([42; 32]),
- }
+ },
+ skimmed_fee_msat: None,
});
// Make sure when Node A calculates their local commitment transaction, none of the HTLCs pass
// the dust limit check.
let htlc_candidate = HTLCCandidate::new(htlc_amount_msat, HTLCInitiator::LocalOffered);
let local_commit_tx_fee = node_a_chan.context.next_local_commit_tx_fee_msat(htlc_candidate, None);
- let local_commit_fee_0_htlcs = commit_tx_fee_msat(node_a_chan.context.feerate_per_kw, 0, node_a_chan.context.opt_anchors());
+ let local_commit_fee_0_htlcs = commit_tx_fee_msat(node_a_chan.context.feerate_per_kw, 0, node_a_chan.context.get_channel_type());
assert_eq!(local_commit_tx_fee, local_commit_fee_0_htlcs);
// Finally, make sure that when Node A calculates the remote's commitment transaction fees, all
// of the HTLCs are seen to be above the dust limit.
node_a_chan.context.channel_transaction_parameters.is_outbound_from_holder = false;
- let remote_commit_fee_3_htlcs = commit_tx_fee_msat(node_a_chan.context.feerate_per_kw, 3, node_a_chan.context.opt_anchors());
+ let remote_commit_fee_3_htlcs = commit_tx_fee_msat(node_a_chan.context.feerate_per_kw, 3, node_a_chan.context.get_channel_type());
let htlc_candidate = HTLCCandidate::new(htlc_amount_msat, HTLCInitiator::LocalOffered);
let remote_commit_tx_fee = node_a_chan.context.next_remote_commit_tx_fee_msat(htlc_candidate, None);
assert_eq!(remote_commit_tx_fee, remote_commit_fee_3_htlcs);
let config = UserConfig::default();
let mut chan = OutboundV1Channel::<EnforcingSigner>::new(&fee_est, &&keys_provider, &&keys_provider, node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42).unwrap();
- let commitment_tx_fee_0_htlcs = commit_tx_fee_msat(chan.context.feerate_per_kw, 0, chan.context.opt_anchors());
- let commitment_tx_fee_1_htlc = commit_tx_fee_msat(chan.context.feerate_per_kw, 1, chan.context.opt_anchors());
+ let commitment_tx_fee_0_htlcs = commit_tx_fee_msat(chan.context.feerate_per_kw, 0, chan.context.get_channel_type());
+ let commitment_tx_fee_1_htlc = commit_tx_fee_msat(chan.context.feerate_per_kw, 1, chan.context.get_channel_type());
// If HTLC_SUCCESS_TX_WEIGHT and HTLC_TIMEOUT_TX_WEIGHT were swapped: then this HTLC would be
// counted as dust when it shouldn't be.
- let htlc_amt_above_timeout = ((253 * htlc_timeout_tx_weight(chan.context.opt_anchors()) / 1000) + chan.context.holder_dust_limit_satoshis + 1) * 1000;
+ let htlc_amt_above_timeout = ((253 * htlc_timeout_tx_weight(chan.context.get_channel_type()) / 1000) + chan.context.holder_dust_limit_satoshis + 1) * 1000;
let htlc_candidate = HTLCCandidate::new(htlc_amt_above_timeout, HTLCInitiator::LocalOffered);
let commitment_tx_fee = chan.context.next_local_commit_tx_fee_msat(htlc_candidate, None);
assert_eq!(commitment_tx_fee, commitment_tx_fee_1_htlc);
// If swapped: this HTLC would be counted as non-dust when it shouldn't be.
- let dust_htlc_amt_below_success = ((253 * htlc_success_tx_weight(chan.context.opt_anchors()) / 1000) + chan.context.holder_dust_limit_satoshis - 1) * 1000;
+ let dust_htlc_amt_below_success = ((253 * htlc_success_tx_weight(chan.context.get_channel_type()) / 1000) + chan.context.holder_dust_limit_satoshis - 1) * 1000;
let htlc_candidate = HTLCCandidate::new(dust_htlc_amt_below_success, HTLCInitiator::RemoteOffered);
let commitment_tx_fee = chan.context.next_local_commit_tx_fee_msat(htlc_candidate, None);
assert_eq!(commitment_tx_fee, commitment_tx_fee_0_htlcs);
chan.context.channel_transaction_parameters.is_outbound_from_holder = false;
// If swapped: this HTLC would be counted as non-dust when it shouldn't be.
- let dust_htlc_amt_above_timeout = ((253 * htlc_timeout_tx_weight(chan.context.opt_anchors()) / 1000) + chan.context.counterparty_dust_limit_satoshis + 1) * 1000;
+ let dust_htlc_amt_above_timeout = ((253 * htlc_timeout_tx_weight(chan.context.get_channel_type()) / 1000) + chan.context.counterparty_dust_limit_satoshis + 1) * 1000;
let htlc_candidate = HTLCCandidate::new(dust_htlc_amt_above_timeout, HTLCInitiator::LocalOffered);
let commitment_tx_fee = chan.context.next_remote_commit_tx_fee_msat(htlc_candidate, None);
assert_eq!(commitment_tx_fee, commitment_tx_fee_0_htlcs);
// If swapped: this HTLC would be counted as dust when it shouldn't be.
- let htlc_amt_below_success = ((253 * htlc_success_tx_weight(chan.context.opt_anchors()) / 1000) + chan.context.counterparty_dust_limit_satoshis - 1) * 1000;
+ let htlc_amt_below_success = ((253 * htlc_success_tx_weight(chan.context.get_channel_type()) / 1000) + chan.context.counterparty_dust_limit_satoshis - 1) * 1000;
let htlc_candidate = HTLCCandidate::new(htlc_amt_below_success, HTLCInitiator::RemoteOffered);
let commitment_tx_fee = chan.context.next_remote_commit_tx_fee_msat(htlc_candidate, None);
assert_eq!(commitment_tx_fee, commitment_tx_fee_1_htlc);
value: 10000000, script_pubkey: output_script.clone(),
}]};
let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
- let (mut node_a_chan, funding_created_msg) = node_a_chan.get_outbound_funding_created(tx.clone(), funding_outpoint, &&logger).map_err(|_| ()).unwrap();
+ let (mut node_a_chan, funding_created_msg) = node_a_chan.get_funding_created(tx.clone(), funding_outpoint, &&logger).map_err(|_| ()).unwrap();
let (mut node_b_chan, funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&keys_provider, &&logger).map_err(|_| ()).unwrap();
// Node B --> Node A: funding signed
value: 10000000, script_pubkey: output_script.clone(),
}]};
let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
- let (mut node_a_chan, funding_created_msg) = node_a_chan.get_outbound_funding_created(tx.clone(), funding_outpoint, &&logger).map_err(|_| ()).unwrap();
+ let (mut node_a_chan, funding_created_msg) = node_a_chan.get_funding_created(tx.clone(), funding_outpoint, &&logger).map_err(|_| ()).unwrap();
let (_, funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&keys_provider, &&logger).map_err(|_| ()).unwrap();
// Node B --> Node A: funding signed
macro_rules! test_commitment {
( $counterparty_sig_hex: expr, $sig_hex: expr, $tx_hex: expr, $($remain:tt)* ) => {
- chan.context.channel_transaction_parameters.opt_anchors = None;
- test_commitment_common!($counterparty_sig_hex, $sig_hex, $tx_hex, false, $($remain)*);
+ chan.context.channel_transaction_parameters.channel_type_features = ChannelTypeFeatures::only_static_remote_key();
+ test_commitment_common!($counterparty_sig_hex, $sig_hex, $tx_hex, &ChannelTypeFeatures::only_static_remote_key(), $($remain)*);
};
}
macro_rules! test_commitment_with_anchors {
( $counterparty_sig_hex: expr, $sig_hex: expr, $tx_hex: expr, $($remain:tt)* ) => {
- chan.context.channel_transaction_parameters.opt_anchors = Some(());
- test_commitment_common!($counterparty_sig_hex, $sig_hex, $tx_hex, true, $($remain)*);
+ chan.context.channel_transaction_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
+ test_commitment_common!($counterparty_sig_hex, $sig_hex, $tx_hex, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), $($remain)*);
};
}
let ref htlc = htlcs[$htlc_idx];
let htlc_tx = chan_utils::build_htlc_transaction(&unsigned_tx.txid, chan.context.feerate_per_kw,
chan.context.get_counterparty_selected_contest_delay().unwrap(),
- &htlc, $opt_anchors, false, &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
+ &htlc, $opt_anchors, &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, $opt_anchors, &keys);
- let htlc_sighashtype = if $opt_anchors { EcdsaSighashType::SinglePlusAnyoneCanPay } else { EcdsaSighashType::All };
+ let htlc_sighashtype = if $opt_anchors.supports_anchors_zero_fee_htlc_tx() { EcdsaSighashType::SinglePlusAnyoneCanPay } else { EcdsaSighashType::All };
let htlc_sighash = Message::from_slice(&sighash::SighashCache::new(&htlc_tx).segwit_signature_hash(0, &htlc_redeemscript, htlc.amount_msat / 1000, htlc_sighashtype).unwrap()[..]).unwrap();
assert!(secp_ctx.verify_ecdsa(&htlc_sighash, &remote_signature, &keys.countersignatory_htlc_key).is_ok(), "verify counterparty htlc sig");
}
let htlc_sig = htlc_sig_iter.next().unwrap();
- let num_anchors = if $opt_anchors { 2 } else { 0 };
+ let num_anchors = if $opt_anchors.supports_anchors_zero_fee_htlc_tx() { 2 } else { 0 };
assert_eq!((htlc_sig.0).0.transaction_output_index, Some($htlc_idx + num_anchors), "output index");
let signature = Signature::from_der(&hex::decode($htlc_sig_hex).unwrap()[..]).unwrap();
payment_hash: PaymentHash([0; 32]),
state: OutboundHTLCState::Committed,
source: HTLCSource::dummy(),
+ skimmed_fee_msat: None,
};
out.payment_hash.0 = Sha256::hash(&hex::decode("0202020202020202020202020202020202020202020202020202020202020202").unwrap()).into_inner();
out
payment_hash: PaymentHash([0; 32]),
state: OutboundHTLCState::Committed,
source: HTLCSource::dummy(),
+ skimmed_fee_msat: None,
};
out.payment_hash.0 = Sha256::hash(&hex::decode("0303030303030303030303030303030303030303030303030303030303030303").unwrap()).into_inner();
out
chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000
chan.context.feerate_per_kw = 2185;
chan.context.holder_dust_limit_satoshis = 2001;
+ let cached_channel_type = chan.context.channel_type;
+ chan.context.channel_type = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
test_commitment_with_anchors!("3044022040f63a16148cf35c8d3d41827f5ae7f7c3746885bb64d4d1b895892a83812b3e02202fcf95c2bf02c466163b3fa3ced6a24926fbb4035095a96842ef516e86ba54c0",
"3045022100cd8479cfe1edb1e5a1d487391e0451a469c7171e51e680183f19eb4321f20e9b02204eab7d5a6384b1b08e03baa6e4d9748dfd2b5ab2bae7e39604a0d0055bbffdd5",
chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000
chan.context.feerate_per_kw = 3702;
chan.context.holder_dust_limit_satoshis = 546;
+ chan.context.channel_type = cached_channel_type.clone();
test_commitment!("304502210092a587aeb777f869e7ff0d7898ea619ee26a3dacd1f3672b945eea600be431100220077ee9eae3528d15251f2a52b607b189820e57a6ccfac8d1af502b132ee40169",
"3045022100e5efb73c32d32da2d79702299b6317de6fb24a60476e3855926d78484dd1b3c802203557cb66a42c944ef06e00bcc4da35a5bcb2f185aab0f8e403e519e1d66aaf75",
chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000
chan.context.feerate_per_kw = 3687;
chan.context.holder_dust_limit_satoshis = 3001;
+ chan.context.channel_type = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
test_commitment_with_anchors!("3045022100ad6c71569856b2d7ff42e838b4abe74a713426b37f22fa667a195a4c88908c6902202b37272b02a42dc6d9f4f82cab3eaf84ac882d9ed762859e1e75455c2c228377",
"3045022100c970799bcb33f43179eb43b3378a0a61991cf2923f69b36ef12548c3df0e6d500220413dc27d2e39ee583093adfcb7799be680141738babb31cc7b0669a777a31f5d",
chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000
chan.context.feerate_per_kw = 4914;
chan.context.holder_dust_limit_satoshis = 546;
+ chan.context.channel_type = cached_channel_type.clone();
test_commitment!("3045022100b4b16d5f8cc9fc4c1aff48831e832a0d8990e133978a66e302c133550954a44d022073573ce127e2200d316f6b612803a5c0c97b8d20e1e44dbe2ac0dd2fb8c95244",
"3045022100d72638bc6308b88bb6d45861aae83e5b9ff6e10986546e13bce769c70036e2620220320be7c6d66d22f30b9fcd52af66531505b1310ca3b848c19285b38d8a1a8c19",
chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000
chan.context.feerate_per_kw = 4894;
chan.context.holder_dust_limit_satoshis = 4001;
+ chan.context.channel_type = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
test_commitment_with_anchors!("3045022100e784a66b1588575801e237d35e510fd92a81ae3a4a2a1b90c031ad803d07b3f3022021bc5f16501f167607d63b681442da193eb0a76b4b7fd25c2ed4f8b28fd35b95",
"30450221009f16ac85d232e4eddb3fcd750a68ebf0b58e3356eaada45d3513ede7e817bf4c02207c2b043b4e5f971261975406cb955219fa56bffe5d834a833694b5abc1ce4cfd",
chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000
chan.context.feerate_per_kw = 9651180;
chan.context.holder_dust_limit_satoshis = 546;
+ chan.context.channel_type = cached_channel_type.clone();
test_commitment!("304402200a8544eba1d216f5c5e530597665fa9bec56943c0f66d98fc3d028df52d84f7002201e45fa5c6bc3a506cc2553e7d1c0043a9811313fc39c954692c0d47cfce2bbd3",
"3045022100e11b638c05c650c2f63a421d36ef8756c5ce82f2184278643520311cdf50aa200220259565fb9c8e4a87ccaf17f27a3b9ca4f20625754a0920d9c6c239d8156a11de",
chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000
chan.context.feerate_per_kw = 6216010;
chan.context.holder_dust_limit_satoshis = 4001;
+ chan.context.channel_type = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
test_commitment_with_anchors!("30450221008fd5dbff02e4b59020d4cd23a3c30d3e287065fda75a0a09b402980adf68ccda022001e0b8b620cd915ddff11f1de32addf23d81d51b90e6841b2cb8dcaf3faa5ecf",
"30450221009ad80792e3038fe6968d12ff23e6888a565c3ddd065037f357445f01675d63f3022018384915e5f1f4ae157e15debf4f49b61c8d9d2b073c7d6f97c4a68caa3ed4c1",
chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000
chan.context.feerate_per_kw = 9651936;
chan.context.holder_dust_limit_satoshis = 546;
+ chan.context.channel_type = cached_channel_type;
test_commitment!("304402202ade0142008309eb376736575ad58d03e5b115499709c6db0b46e36ff394b492022037b63d78d66404d6504d4c4ac13be346f3d1802928a6d3ad95a6a944227161a2",
"304402207e8d51e0c570a5868a78414f4e0cbfaed1106b171b9581542c30718ee4eb95ba02203af84194c97adf98898c9afe2f2ed4a7f8dba05a2dfab28ac9d9c604aa49a379",
payment_hash: PaymentHash([0; 32]),
state: OutboundHTLCState::Committed,
source: HTLCSource::dummy(),
+ skimmed_fee_msat: None,
};
out.payment_hash.0 = Sha256::hash(&hex::decode("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).into_inner();
out
payment_hash: PaymentHash([0; 32]),
state: OutboundHTLCState::Committed,
source: HTLCSource::dummy(),
+ skimmed_fee_msat: None,
};
out.payment_hash.0 = Sha256::hash(&hex::decode("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).into_inner();
out
"020000000001014bdccf28653066a2c554cafeffdfe1e678e64a69b056684deb0c4fba909423ec02000000000000000001e1120000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004730440220471c9f3ad92e49b13b7b8059f43ecf8f7887b0dccbb9fdb54bfe23d62a8ae332022024bd22fae0740e86a44228c35330da9526fd7306dffb2b9dc362d5e78abef7cc0147304402207157f452f2506d73c315192311893800cfb3cc235cc1185b1cfcc136b55230db022014be242dbc6c5da141fec4034e7f387f74d6ff1899453d72ba957467540e1ecb01008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9142002cc93ebefbb1b73f0af055dcc27a0b504ad7688ac6868fa010000" }
} );
+ chan.context.channel_type = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
test_commitment_with_anchors!("3044022027b38dfb654c34032ffb70bb43022981652fce923cbbe3cbe7394e2ade8b34230220584195b78da6e25c2e8da6b4308d9db25b65b64975db9266163ef592abb7c725",
"3045022100b4014970d9d7962853f3f85196144671d7d5d87426250f0a5fdaf9a55292e92502205360910c9abb397467e19dbd63d081deb4a3240903114c98cec0a23591b79b76",
"02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b80074a010000000000002200202b1b5854183c12d3316565972c4668929d314d81c5dcdbb21cb45fe8a9a8114f4a01000000000000220020e9e86e4823faa62e222ebc858a226636856158f07e69898da3b0d1af0ddb3994d007000000000000220020fe0598d74fee2205cc3672e6e6647706b4f3099713b4661b62482c3addd04a5e881300000000000022002018e40f9072c44350f134bdc887bab4d9bdfc8aa468a25616c80e21757ba5dac7881300000000000022002018e40f9072c44350f134bdc887bab4d9bdfc8aa468a25616c80e21757ba5dac7c0c62d0000000000220020f3394e1e619b0eca1f91be2fb5ab4dfc59ba5b84ebe014ad1d43a564d012994aad9c6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100b4014970d9d7962853f3f85196144671d7d5d87426250f0a5fdaf9a55292e92502205360910c9abb397467e19dbd63d081deb4a3240903114c98cec0a23591b79b7601473044022027b38dfb654c34032ffb70bb43022981652fce923cbbe3cbe7394e2ade8b34230220584195b78da6e25c2e8da6b4308d9db25b65b64975db9266163ef592abb7c72501475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", {
assert!(res.is_ok());
}
- #[cfg(anchors)]
#[test]
fn test_supports_anchors_zero_htlc_tx_fee() {
// Tests that if both sides support and negotiate `anchors_zero_fee_htlc_tx`, it is the
assert_eq!(channel_b.context.channel_type, expected_channel_type);
}
- #[cfg(anchors)]
#[test]
fn test_rejects_implicit_simple_anchors() {
// Tests that if `option_anchors` is being negotiated implicitly through the intersection of
assert!(channel_b.is_err());
}
- #[cfg(anchors)]
#[test]
fn test_rejects_simple_anchors_channel_type() {
// Tests that if `option_anchors` is being negotiated through the `channel_type` feature,
let simple_anchors_raw_features = static_remote_key_required | simple_anchors_required;
let simple_anchors_init = InitFeatures::from_le_bytes(simple_anchors_raw_features.to_le_bytes().to_vec());
let simple_anchors_channel_type = ChannelTypeFeatures::from_le_bytes(simple_anchors_raw_features.to_le_bytes().to_vec());
- assert!(simple_anchors_init.requires_unknown_bits());
- assert!(simple_anchors_channel_type.requires_unknown_bits());
+ assert!(!simple_anchors_init.requires_unknown_bits());
+ assert!(!simple_anchors_channel_type.requires_unknown_bits());
// First, we'll try to open a channel between A and B where A requests a channel type for
// the original `option_anchors` feature (non zero fee htlc tx). This should be rejected by
// Since this struct is returned in `list_channels` methods, expose it here in case users want to
// construct one themselves.
use crate::ln::{inbound_payment, PaymentHash, PaymentPreimage, PaymentSecret};
-use crate::ln::channel::{Channel, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel};
+use crate::ln::channel::{Channel, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UnfundedChannelContext, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel};
use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
#[cfg(any(feature = "_test_utils", test))]
-use crate::ln::features::InvoiceFeatures;
+use crate::ln::features::Bolt11InvoiceFeatures;
use crate::routing::gossip::NetworkGraph;
-use crate::routing::router::{BlindedTail, DefaultRouter, InFlightHtlcs, Path, Payee, PaymentParameters, Route, RouteHop, RouteParameters, Router};
+use crate::routing::router::{BlindedTail, DefaultRouter, InFlightHtlcs, Path, Payee, PaymentParameters, Route, RouteParameters, Router};
use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters};
use crate::ln::msgs;
use crate::ln::onion_utils;
use crate::ln::onion_utils::HTLCFailReason;
-use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError, MAX_VALUE_MSAT};
+use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
#[cfg(test)]
use crate::ln::outbound_payment;
use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment};
/// may overshoot this in either case)
pub(super) outgoing_amt_msat: u64,
pub(super) outgoing_cltv_value: u32,
+ /// The fee being skimmed off the top of this HTLC. If this is a forward, it'll be the fee we are
+ /// skimming. If we're receiving this HTLC, it's the fee that our counterparty skimmed.
+ pub(super) skimmed_fee_msat: Option<u64>,
}
#[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug
total_value_received: Option<u64>,
/// The sender intended sum total of all MPP parts specified in the onion
total_msat: u64,
+ /// The extra fee our counterparty skimmed off the top of this HTLC.
+ counterparty_skimmed_fee_msat: Option<u64>,
}
/// A payment identifier used to uniquely identify a payment to LDK.
}
}
impl HTLCSource {
- #[cfg(not(feature = "grind_signatures"))]
+ #[cfg(all(feature = "_test_vectors", not(feature = "grind_signatures")))]
#[cfg(test)]
pub fn dummy() -> Self {
HTLCSource::OutboundRoute {
/// running normally, and specifically must be processed before any other non-background
/// [`ChannelMonitorUpdate`]s are applied.
enum BackgroundEvent {
- /// Handle a ChannelMonitorUpdate which closes the channel. This is only separated from
- /// [`Self::MonitorUpdateRegeneratedOnStartup`] as the maybe-non-closing variant needs a public
- /// key to handle channel resumption, whereas if the channel has been force-closed we do not
- /// need the counterparty node_id.
+ /// Handle a ChannelMonitorUpdate which closes the channel or for an already-closed channel.
+ /// This is only separated from [`Self::MonitorUpdateRegeneratedOnStartup`] as the
+ /// maybe-non-closing variant needs a public key to handle channel resumption, whereas if the
+ /// channel has been force-closed we do not need the counterparty node_id.
///
/// Note that any such events are lost on shutdown, so in general they must be updates which
/// are regenerated on startup.
- ClosingMonitorUpdateRegeneratedOnStartup((OutPoint, ChannelMonitorUpdate)),
+ ClosedMonitorUpdateRegeneratedOnStartup((OutPoint, ChannelMonitorUpdate)),
/// Handle a ChannelMonitorUpdate which may or may not close the channel and may unblock the
/// channel to continue normal operation.
///
/// In general this should be used rather than
- /// [`Self::ClosingMonitorUpdateRegeneratedOnStartup`], however in cases where the
+ /// [`Self::ClosedMonitorUpdateRegeneratedOnStartup`], however in cases where the
/// `counterparty_node_id` is not available as the channel has closed from a [`ChannelMonitor`]
/// error the other variant is acceptable.
///
funding_txo: OutPoint,
update: ChannelMonitorUpdate
},
+ /// Some [`ChannelMonitorUpdate`] (s) completed before we were serialized but we still have
+ /// them marked pending, thus we need to run any [`MonitorUpdateCompletionAction`] (s) pending
+ /// on a channel.
+ MonitorUpdatesComplete {
+ counterparty_node_id: PublicKey,
+ channel_id: [u8; 32],
+ },
}
#[derive(Debug)]
/// Messages to send to the peer - pushed to in the same lock that they are generated in (except
/// for broadcast messages, where ordering isn't as strict).
pub(super) pending_msg_events: Vec<MessageSendEvent>,
+ /// Map from Channel IDs to pending [`ChannelMonitorUpdate`]s which have been passed to the
+ /// user but which have not yet completed.
+ ///
+ /// Note that the channel may no longer exist. For example if the channel was closed but we
+ /// later needed to claim an HTLC which is pending on-chain, we may generate a monitor update
+ /// for a missing channel.
+ in_flight_monitor_updates: BTreeMap<OutPoint, Vec<ChannelMonitorUpdate>>,
/// Map from a specific channel to some action(s) that should be taken when all pending
/// [`ChannelMonitorUpdate`]s for the channel complete updating.
///
return false
}
self.channel_by_id.is_empty() && self.monitor_update_blocked_actions.is_empty()
+ && self.in_flight_monitor_updates.is_empty()
}
- // Returns a count of all channels we have with this peer, including pending channels.
+ // Returns a count of all channels we have with this peer, including unfunded channels.
fn total_channel_count(&self) -> usize {
self.channel_by_id.len() +
self.outbound_v1_channel_by_id.len() +
/// of [`KeysManager`] and [`DefaultRouter`].
///
/// This is not exported to bindings users as Arcs don't make sense in bindings
-pub type SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, M, T, F, L> = ChannelManager<&'a M, &'b T, &'c KeysManager, &'c KeysManager, &'c KeysManager, &'d F, &'e DefaultRouter<&'f NetworkGraph<&'g L>, &'g L, &'h Mutex<ProbabilisticScorer<&'f NetworkGraph<&'g L>, &'g L>>, ProbabilisticScoringFeeParameters, ProbabilisticScorer<&'f NetworkGraph<&'g L>, &'g L>>, &'g L>;
+pub type SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, M, T, F, L> =
+ ChannelManager<
+ &'a M,
+ &'b T,
+ &'c KeysManager,
+ &'c KeysManager,
+ &'c KeysManager,
+ &'d F,
+ &'e DefaultRouter<
+ &'f NetworkGraph<&'g L>,
+ &'g L,
+ &'h Mutex<ProbabilisticScorer<&'f NetworkGraph<&'g L>, &'g L>>,
+ ProbabilisticScoringFeeParameters,
+ ProbabilisticScorer<&'f NetworkGraph<&'g L>, &'g L>
+ >,
+ &'g L
+ >;
macro_rules! define_test_pub_trait { ($vis: vis) => {
/// A trivial trait which describes any [`ChannelManager`] used in testing.
/// Notifier the lock contains sends out a notification when the lock is released.
total_consistency_lock: RwLock<()>,
- #[cfg(debug_assertions)]
background_events_processed_since_startup: AtomicBool,
persistence_notifier: Notifier,
///
/// [`confirmations_required`]: ChannelDetails::confirmations_required
pub is_channel_ready: bool,
+ /// The stage of the channel's shutdown.
+ /// `None` for `ChannelDetails` serialized on LDK versions prior to 0.0.116.
+ pub channel_shutdown_state: Option<ChannelShutdownState>,
/// True if the channel is (a) confirmed and channel_ready messages have been exchanged, (b)
/// the peer is connected, and (c) the channel is not currently negotiating a shutdown.
///
self.short_channel_id.or(self.outbound_scid_alias)
}
- fn from_channel_context<Signer: WriteableEcdsaChannelSigner>(context: &ChannelContext<Signer>,
- best_block_height: u32, latest_features: InitFeatures) -> Self {
-
- let balance = context.get_available_balances();
+ fn from_channel_context<Signer: WriteableEcdsaChannelSigner, F: Deref>(
+ context: &ChannelContext<Signer>, best_block_height: u32, latest_features: InitFeatures,
+ fee_estimator: &LowerBoundedFeeEstimator<F>
+ ) -> Self
+ where F::Target: FeeEstimator
+ {
+ let balance = context.get_available_balances(fee_estimator);
let (to_remote_reserve_satoshis, to_self_reserve_satoshis) =
context.get_holder_counterparty_selected_channel_reserve_satoshis();
ChannelDetails {
inbound_htlc_minimum_msat: Some(context.get_holder_htlc_minimum_msat()),
inbound_htlc_maximum_msat: context.get_holder_htlc_maximum_msat(),
config: Some(context.config()),
+ channel_shutdown_state: Some(context.shutdown_state()),
}
}
}
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+/// Further information on the details of the channel shutdown.
+/// Upon channels being forced closed (i.e. commitment transaction confirmation detected
+/// by `ChainMonitor`), ChannelShutdownState will be set to `ShutdownComplete` or
+/// the channel will be removed shortly.
+/// Also note, that in normal operation, peers could disconnect at any of these states
+/// and require peer re-connection before making progress onto other states
+pub enum ChannelShutdownState {
+ /// Channel has not sent or received a shutdown message.
+ NotShuttingDown,
+ /// Local node has sent a shutdown message for this channel.
+ ShutdownInitiated,
+ /// Shutdown message exchanges have concluded and the channels are in the midst of
+ /// resolving all existing open HTLCs before closing can continue.
+ ResolvingHTLCs,
+ /// All HTLCs have been resolved, nodes are currently negotiating channel close onchain fee rates.
+ NegotiatingClosingFee,
+ /// We've successfully negotiated a closing_signed dance. At this point `ChannelManager` is about
+ /// to drop the channel.
+ ShutdownComplete,
+}
+
/// Used by [`ChannelManager::list_recent_payments`] to express the status of recent payments.
/// These include payments that have yet to find a successful path, or have unresolved HTLCs.
#[derive(Debug, PartialEq)]
},
}
};
- ($self: ident, $err: expr, $channel_context: expr, $channel_id: expr, PREFUNDED) => {
+ ($self: ident, $err: expr, $channel_context: expr, $channel_id: expr, UNFUNDED) => {
match $err {
- // We should only ever have `ChannelError::Close` when prefunded channels error.
+ // We should only ever have `ChannelError::Close` when unfunded channels error.
// In any case, just close the channel.
ChannelError::Warn(msg) | ChannelError::Ignore(msg) | ChannelError::Close(msg) => {
- log_error!($self.logger, "Closing prefunded channel {} due to an error: {}", log_bytes!($channel_id[..]), msg);
+ log_error!($self.logger, "Closing unfunded channel {} due to an error: {}", log_bytes!($channel_id[..]), msg);
update_maps_on_chan_removal!($self, &$channel_context);
let shutdown_res = $channel_context.force_shutdown(false);
(true, MsgHandleErrInternal::from_finish_shutdown(msg, *$channel_id, $channel_context.get_user_id(),
match $res {
Ok(res) => res,
Err(e) => {
- let (drop, res) = convert_chan_err!($self, e, $entry.get_mut().context, $entry.key(), PREFUNDED);
+ let (drop, res) = convert_chan_err!($self, e, $entry.get_mut().context, $entry.key(), UNFUNDED);
if drop {
$entry.remove_entry();
}
}
macro_rules! handle_monitor_update_completion {
- ($self: ident, $update_id: expr, $peer_state_lock: expr, $peer_state: expr, $per_peer_state_lock: expr, $chan: expr) => { {
+ ($self: ident, $peer_state_lock: expr, $peer_state: expr, $per_peer_state_lock: expr, $chan: expr) => { {
let mut updates = $chan.monitor_updating_restored(&$self.logger,
&$self.node_signer, $self.genesis_hash, &$self.default_configuration,
$self.best_block.read().unwrap().height());
}
macro_rules! handle_new_monitor_update {
- ($self: ident, $update_res: expr, $update_id: expr, $peer_state_lock: expr, $peer_state: expr, $per_peer_state_lock: expr, $chan: expr, MANUALLY_REMOVING, $remove: expr) => { {
+ ($self: ident, $update_res: expr, $peer_state_lock: expr, $peer_state: expr, $per_peer_state_lock: expr, $chan: expr, _internal, $remove: expr, $completed: expr) => { {
// update_maps_on_chan_removal needs to be able to take id_to_peer, so make sure we can in
// any case so that it won't deadlock.
debug_assert_ne!($self.id_to_peer.held_by_thread(), LockHeldState::HeldByThread);
- #[cfg(debug_assertions)] {
- debug_assert!($self.background_events_processed_since_startup.load(Ordering::Acquire));
- }
+ debug_assert!($self.background_events_processed_since_startup.load(Ordering::Acquire));
match $update_res {
ChannelMonitorUpdateStatus::InProgress => {
log_debug!($self.logger, "ChannelMonitor update for {} in flight, holding messages until the update completes.",
log_bytes!($chan.context.channel_id()[..]));
- Ok(())
+ Ok(false)
},
ChannelMonitorUpdateStatus::PermanentFailure => {
log_error!($self.logger, "Closing channel {} due to monitor update ChannelMonitorUpdateStatus::PermanentFailure",
log_bytes!($chan.context.channel_id()[..]));
update_maps_on_chan_removal!($self, &$chan.context);
- let res: Result<(), _> = Err(MsgHandleErrInternal::from_finish_shutdown(
+ let res = Err(MsgHandleErrInternal::from_finish_shutdown(
"ChannelMonitor storage failure".to_owned(), $chan.context.channel_id(),
$chan.context.get_user_id(), $chan.context.force_shutdown(false),
$self.get_channel_update_for_broadcast(&$chan).ok()));
res
},
ChannelMonitorUpdateStatus::Completed => {
- $chan.complete_one_mon_update($update_id);
- if $chan.no_monitor_updates_pending() {
- handle_monitor_update_completion!($self, $update_id, $peer_state_lock, $peer_state, $per_peer_state_lock, $chan);
- }
- Ok(())
+ $completed;
+ Ok(true)
},
}
} };
- ($self: ident, $update_res: expr, $update_id: expr, $peer_state_lock: expr, $peer_state: expr, $per_peer_state_lock: expr, $chan_entry: expr) => {
- handle_new_monitor_update!($self, $update_res, $update_id, $peer_state_lock, $peer_state, $per_peer_state_lock, $chan_entry.get_mut(), MANUALLY_REMOVING, $chan_entry.remove_entry())
+ ($self: ident, $update_res: expr, $peer_state_lock: expr, $peer_state: expr, $per_peer_state_lock: expr, $chan: expr, MANUALLY_REMOVING_INITIAL_MONITOR, $remove: expr) => {
+ handle_new_monitor_update!($self, $update_res, $peer_state_lock, $peer_state,
+ $per_peer_state_lock, $chan, _internal, $remove,
+ handle_monitor_update_completion!($self, $peer_state_lock, $peer_state, $per_peer_state_lock, $chan))
+ };
+ ($self: ident, $update_res: expr, $peer_state_lock: expr, $peer_state: expr, $per_peer_state_lock: expr, $chan_entry: expr, INITIAL_MONITOR) => {
+ handle_new_monitor_update!($self, $update_res, $peer_state_lock, $peer_state, $per_peer_state_lock, $chan_entry.get_mut(), MANUALLY_REMOVING_INITIAL_MONITOR, $chan_entry.remove_entry())
+ };
+ ($self: ident, $funding_txo: expr, $update: expr, $peer_state_lock: expr, $peer_state: expr, $per_peer_state_lock: expr, $chan: expr, MANUALLY_REMOVING, $remove: expr) => { {
+ let in_flight_updates = $peer_state.in_flight_monitor_updates.entry($funding_txo)
+ .or_insert_with(Vec::new);
+ // During startup, we push monitor updates as background events through to here in
+ // order to replay updates that were in-flight when we shut down. Thus, we have to
+ // filter for uniqueness here.
+ let idx = in_flight_updates.iter().position(|upd| upd == &$update)
+ .unwrap_or_else(|| {
+ in_flight_updates.push($update);
+ in_flight_updates.len() - 1
+ });
+ let update_res = $self.chain_monitor.update_channel($funding_txo, &in_flight_updates[idx]);
+ handle_new_monitor_update!($self, update_res, $peer_state_lock, $peer_state,
+ $per_peer_state_lock, $chan, _internal, $remove,
+ {
+ let _ = in_flight_updates.remove(idx);
+ if in_flight_updates.is_empty() && $chan.blocked_monitor_updates_pending() == 0 {
+ handle_monitor_update_completion!($self, $peer_state_lock, $peer_state, $per_peer_state_lock, $chan);
+ }
+ })
+ } };
+ ($self: ident, $funding_txo: expr, $update: expr, $peer_state_lock: expr, $peer_state: expr, $per_peer_state_lock: expr, $chan_entry: expr) => {
+ handle_new_monitor_update!($self, $funding_txo, $update, $peer_state_lock, $peer_state, $per_peer_state_lock, $chan_entry.get_mut(), MANUALLY_REMOVING, $chan_entry.remove_entry())
}
}
let mut pending_events = $self.pending_events.lock().unwrap();
pending_events.drain(..num_events);
processed_all_events = pending_events.is_empty();
+ // Note that `push_pending_forwards_ev` relies on `pending_events_processor` being
+ // updated here with the `pending_events` lock acquired.
$self.pending_events_processor.store(false, Ordering::Release);
}
{
/// Constructs a new `ChannelManager` to hold several channels and route between them.
///
+ /// The current time or latest block header time can be provided as the `current_timestamp`.
+ ///
/// This is the main "logic hub" for all channel-related actions, and implements
/// [`ChannelMessageHandler`].
///
/// [`block_connected`]: chain::Listen::block_connected
/// [`block_disconnected`]: chain::Listen::block_disconnected
/// [`params.best_block.block_hash`]: chain::BestBlock::block_hash
- pub fn new(fee_est: F, chain_monitor: M, tx_broadcaster: T, router: R, logger: L, entropy_source: ES, node_signer: NS, signer_provider: SP, config: UserConfig, params: ChainParameters) -> Self {
+ pub fn new(
+ fee_est: F, chain_monitor: M, tx_broadcaster: T, router: R, logger: L, entropy_source: ES,
+ node_signer: NS, signer_provider: SP, config: UserConfig, params: ChainParameters,
+ current_timestamp: u32,
+ ) -> Self {
let mut secp_ctx = Secp256k1::new();
secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes());
let inbound_pmt_key_material = node_signer.get_inbound_payment_key_material();
probing_cookie_secret: entropy_source.get_secure_random_bytes(),
- highest_seen_timestamp: AtomicUsize::new(0),
+ highest_seen_timestamp: AtomicUsize::new(current_timestamp as usize),
per_peer_state: FairRwLock::new(HashMap::new()),
pending_events_processor: AtomicBool::new(false),
pending_background_events: Mutex::new(Vec::new()),
total_consistency_lock: RwLock::new(()),
- #[cfg(debug_assertions)]
background_events_processed_since_startup: AtomicBool::new(false),
persistence_notifier: Notifier::new(),
for (_cp_id, peer_state_mutex) in per_peer_state.iter() {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
+ // Only `Channels` in the channel_by_id map can be considered funded.
for (_channel_id, channel) in peer_state.channel_by_id.iter().filter(f) {
let details = ChannelDetails::from_channel_context(&channel.context, best_block_height,
- peer_state.latest_features.clone());
+ peer_state.latest_features.clone(), &self.fee_estimator);
res.push(details);
}
}
let peer_state = &mut *peer_state_lock;
for (_channel_id, channel) in peer_state.channel_by_id.iter() {
let details = ChannelDetails::from_channel_context(&channel.context, best_block_height,
- peer_state.latest_features.clone());
+ peer_state.latest_features.clone(), &self.fee_estimator);
res.push(details);
}
for (_channel_id, channel) in peer_state.inbound_v1_channel_by_id.iter() {
let details = ChannelDetails::from_channel_context(&channel.context, best_block_height,
- peer_state.latest_features.clone());
+ peer_state.latest_features.clone(), &self.fee_estimator);
res.push(details);
}
for (_channel_id, channel) in peer_state.outbound_v1_channel_by_id.iter() {
let details = ChannelDetails::from_channel_context(&channel.context, best_block_height,
- peer_state.latest_features.clone());
+ peer_state.latest_features.clone(), &self.fee_estimator);
res.push(details);
}
}
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
let features = &peer_state.latest_features;
+ let chan_context_to_details = |context| {
+ ChannelDetails::from_channel_context(context, best_block_height, features.clone(), &self.fee_estimator)
+ };
return peer_state.channel_by_id
.iter()
- .map(|(_, channel)|
- ChannelDetails::from_channel_context(&channel.context, best_block_height, features.clone()))
+ .map(|(_, channel)| &channel.context)
+ .chain(peer_state.outbound_v1_channel_by_id.iter().map(|(_, channel)| &channel.context))
+ .chain(peer_state.inbound_v1_channel_by_id.iter().map(|(_, channel)| &channel.context))
+ .map(chan_context_to_details)
.collect();
}
vec![]
let mut failed_htlcs: Vec<(HTLCSource, PaymentHash)>;
let result: Result<(), _> = loop {
- let per_peer_state = self.per_peer_state.read().unwrap();
+ {
+ let per_peer_state = self.per_peer_state.read().unwrap();
- let peer_state_mutex = per_peer_state.get(counterparty_node_id)
- .ok_or_else(|| APIError::ChannelUnavailable { err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id) })?;
+ let peer_state_mutex = per_peer_state.get(counterparty_node_id)
+ .ok_or_else(|| APIError::ChannelUnavailable { err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id) })?;
- let mut peer_state_lock = peer_state_mutex.lock().unwrap();
- let peer_state = &mut *peer_state_lock;
- match peer_state.channel_by_id.entry(channel_id.clone()) {
- hash_map::Entry::Occupied(mut chan_entry) => {
- let funding_txo_opt = chan_entry.get().context.get_funding_txo();
- let their_features = &peer_state.latest_features;
- let (shutdown_msg, mut monitor_update_opt, htlcs) = chan_entry.get_mut()
- .get_shutdown(&self.signer_provider, their_features, target_feerate_sats_per_1000_weight, override_shutdown_script)?;
- failed_htlcs = htlcs;
+ let mut peer_state_lock = peer_state_mutex.lock().unwrap();
+ let peer_state = &mut *peer_state_lock;
- // We can send the `shutdown` message before updating the `ChannelMonitor`
- // here as we don't need the monitor update to complete until we send a
- // `shutdown_signed`, which we'll delay if we're pending a monitor update.
- peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
- node_id: *counterparty_node_id,
- msg: shutdown_msg,
- });
+ match peer_state.channel_by_id.entry(channel_id.clone()) {
+ hash_map::Entry::Occupied(mut chan_entry) => {
+ let funding_txo_opt = chan_entry.get().context.get_funding_txo();
+ let their_features = &peer_state.latest_features;
+ let (shutdown_msg, mut monitor_update_opt, htlcs) = chan_entry.get_mut()
+ .get_shutdown(&self.signer_provider, their_features, target_feerate_sats_per_1000_weight, override_shutdown_script)?;
+ failed_htlcs = htlcs;
- // Update the monitor with the shutdown script if necessary.
- if let Some(monitor_update) = monitor_update_opt.take() {
- let update_id = monitor_update.update_id;
- let update_res = self.chain_monitor.update_channel(funding_txo_opt.unwrap(), monitor_update);
- break handle_new_monitor_update!(self, update_res, update_id, peer_state_lock, peer_state, per_peer_state, chan_entry);
- }
+ // We can send the `shutdown` message before updating the `ChannelMonitor`
+ // here as we don't need the monitor update to complete until we send a
+ // `shutdown_signed`, which we'll delay if we're pending a monitor update.
+ peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
+ node_id: *counterparty_node_id,
+ msg: shutdown_msg,
+ });
- if chan_entry.get().is_shutdown() {
- let channel = remove_channel!(self, chan_entry);
- if let Ok(channel_update) = self.get_channel_update_for_broadcast(&channel) {
- peer_state.pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
- msg: channel_update
- });
+ // Update the monitor with the shutdown script if necessary.
+ if let Some(monitor_update) = monitor_update_opt.take() {
+ break handle_new_monitor_update!(self, funding_txo_opt.unwrap(), monitor_update,
+ peer_state_lock, peer_state, per_peer_state, chan_entry).map(|_| ());
}
- self.issue_channel_close_events(&channel.context, ClosureReason::HolderForceClosed);
- }
- break Ok(());
- },
- hash_map::Entry::Vacant(_) => return Err(APIError::ChannelUnavailable{err: format!("Channel with id {} not found for the passed counterparty node_id {}", log_bytes!(*channel_id), counterparty_node_id) })
+
+ if chan_entry.get().is_shutdown() {
+ let channel = remove_channel!(self, chan_entry);
+ if let Ok(channel_update) = self.get_channel_update_for_broadcast(&channel) {
+ peer_state.pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
+ msg: channel_update
+ });
+ }
+ self.issue_channel_close_events(&channel.context, ClosureReason::HolderForceClosed);
+ }
+ break Ok(());
+ },
+ hash_map::Entry::Vacant(_) => (),
+ }
}
+ // If we reach this point, it means that the channel_id either refers to an unfunded channel or
+ // it does not exist for this peer. Either way, we can attempt to force-close it.
+ //
+ // An appropriate error will be returned for non-existence of the channel if that's the case.
+ return self.force_close_channel_with_peer(&channel_id, counterparty_node_id, None, false).map(|_| ())
+ // TODO(dunxen): This is still not ideal as we're doing some extra lookups.
+ // Fix this with https://github.com/lightningdevkit/rust-lightning/issues/2422
};
for htlc_source in failed_htlcs.drain(..) {
self.issue_channel_close_events(&chan.get().context, closure_reason);
let mut chan = remove_channel!(self, chan);
self.finish_force_close_channel(chan.context.force_shutdown(false));
- // Prefunded channel has no update
+ // Unfunded channel has no update
(None, chan.context.get_counterparty_node_id())
} else if let hash_map::Entry::Occupied(chan) = peer_state.inbound_v1_channel_by_id.entry(channel_id.clone()) {
log_error!(self.logger, "Force-closing channel {}", log_bytes!(channel_id[..]));
self.issue_channel_close_events(&chan.get().context, closure_reason);
let mut chan = remove_channel!(self, chan);
self.finish_force_close_channel(chan.context.force_shutdown(false));
- // Prefunded channel has no update
+ // Unfunded channel has no update
(None, chan.context.get_counterparty_node_id())
} else {
return Err(APIError::ChannelUnavailable{ err: format!("Channel with id {} not found for the passed counterparty node_id {}", log_bytes!(*channel_id), peer_node_id) });
}
}
- fn construct_recv_pending_htlc_info(&self, hop_data: msgs::OnionHopData, shared_secret: [u8; 32],
- payment_hash: PaymentHash, amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>) -> Result<PendingHTLCInfo, ReceiveError>
- {
+ fn construct_recv_pending_htlc_info(
+ &self, hop_data: msgs::OnionHopData, shared_secret: [u8; 32], payment_hash: PaymentHash,
+ amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>, allow_underpay: bool,
+ counterparty_skimmed_fee_msat: Option<u64>,
+ ) -> Result<PendingHTLCInfo, ReceiveError> {
// final_incorrect_cltv_expiry
if hop_data.outgoing_cltv_value > cltv_expiry {
return Err(ReceiveError {
msg: "The final CLTV expiry is too soon to handle",
});
}
- if hop_data.amt_to_forward > amt_msat {
+ if (!allow_underpay && hop_data.amt_to_forward > amt_msat) ||
+ (allow_underpay && hop_data.amt_to_forward >
+ amt_msat.saturating_add(counterparty_skimmed_fee_msat.unwrap_or(0)))
+ {
return Err(ReceiveError {
err_code: 19,
err_data: amt_msat.to_be_bytes().to_vec(),
incoming_amt_msat: Some(amt_msat),
outgoing_amt_msat: hop_data.amt_to_forward,
outgoing_cltv_value: hop_data.outgoing_cltv_value,
+ skimmed_fee_msat: counterparty_skimmed_fee_msat,
})
}
- fn decode_update_add_htlc_onion(&self, msg: &msgs::UpdateAddHTLC) -> PendingHTLCStatus {
+ fn decode_update_add_htlc_onion(
+ &self, msg: &msgs::UpdateAddHTLC
+ ) -> Result<(onion_utils::Hop, [u8; 32], Option<Result<PublicKey, secp256k1::Error>>), HTLCFailureMsg> {
macro_rules! return_malformed_err {
($msg: expr, $err_code: expr) => {
{
log_info!(self.logger, "Failed to accept/forward incoming HTLC: {}", $msg);
- return PendingHTLCStatus::Fail(HTLCFailureMsg::Malformed(msgs::UpdateFailMalformedHTLC {
+ return Err(HTLCFailureMsg::Malformed(msgs::UpdateFailMalformedHTLC {
channel_id: msg.channel_id,
htlc_id: msg.htlc_id,
sha256_of_onion: Sha256::hash(&msg.onion_routing_packet.hop_data).into_inner(),
($msg: expr, $err_code: expr, $data: expr) => {
{
log_info!(self.logger, "Failed to accept/forward incoming HTLC: {}", $msg);
- return PendingHTLCStatus::Fail(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
+ return Err(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
channel_id: msg.channel_id,
htlc_id: msg.htlc_id,
reason: HTLCFailReason::reason($err_code, $data.to_vec())
return_err!(err_msg, err_code, &[0; 0]);
},
};
+ let (outgoing_scid, outgoing_amt_msat, outgoing_cltv_value, next_packet_pk_opt) = match next_hop {
+ onion_utils::Hop::Forward {
+ next_hop_data: msgs::OnionHopData {
+ format: msgs::OnionHopDataFormat::NonFinalNode { short_channel_id }, amt_to_forward,
+ outgoing_cltv_value,
+ }, ..
+ } => {
+ let next_pk = onion_utils::next_hop_packet_pubkey(&self.secp_ctx,
+ msg.onion_routing_packet.public_key.unwrap(), &shared_secret);
+ (short_channel_id, amt_to_forward, outgoing_cltv_value, Some(next_pk))
+ },
+ // We'll do receive checks in [`Self::construct_pending_htlc_info`] so we have access to the
+ // inbound channel's state.
+ onion_utils::Hop::Receive { .. } => return Ok((next_hop, shared_secret, None)),
+ onion_utils::Hop::Forward {
+ next_hop_data: msgs::OnionHopData { format: msgs::OnionHopDataFormat::FinalNode { .. }, .. }, ..
+ } => {
+ return_err!("Final Node OnionHopData provided for us as an intermediary node", 0x4000 | 22, &[0; 0]);
+ }
+ };
- let pending_forward_info = match next_hop {
+ // Perform outbound checks here instead of in [`Self::construct_pending_htlc_info`] because we
+ // can't hold the outbound peer state lock at the same time as the inbound peer state lock.
+ if let Some((err, mut code, chan_update)) = loop {
+ let id_option = self.short_to_chan_info.read().unwrap().get(&outgoing_scid).cloned();
+ let forwarding_chan_info_opt = match id_option {
+ None => { // unknown_next_peer
+ // Note that this is likely a timing oracle for detecting whether an scid is a
+ // phantom or an intercept.
+ if (self.default_configuration.accept_intercept_htlcs &&
+ fake_scid::is_valid_intercept(&self.fake_scid_rand_bytes, outgoing_scid, &self.genesis_hash)) ||
+ fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, outgoing_scid, &self.genesis_hash)
+ {
+ None
+ } else {
+ break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
+ }
+ },
+ Some((cp_id, id)) => Some((cp_id.clone(), id.clone())),
+ };
+ let chan_update_opt = if let Some((counterparty_node_id, forwarding_id)) = forwarding_chan_info_opt {
+ let per_peer_state = self.per_peer_state.read().unwrap();
+ let peer_state_mutex_opt = per_peer_state.get(&counterparty_node_id);
+ if peer_state_mutex_opt.is_none() {
+ break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
+ }
+ let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
+ let peer_state = &mut *peer_state_lock;
+ let chan = match peer_state.channel_by_id.get_mut(&forwarding_id) {
+ None => {
+ // Channel was removed. The short_to_chan_info and channel_by_id maps
+ // have no consistency guarantees.
+ break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
+ },
+ Some(chan) => chan
+ };
+ if !chan.context.should_announce() && !self.default_configuration.accept_forwards_to_priv_channels {
+ // Note that the behavior here should be identical to the above block - we
+ // should NOT reveal the existence or non-existence of a private channel if
+ // we don't allow forwards outbound over them.
+ break Some(("Refusing to forward to a private channel based on our config.", 0x4000 | 10, None));
+ }
+ if chan.context.get_channel_type().supports_scid_privacy() && outgoing_scid != chan.context.outbound_scid_alias() {
+ // `option_scid_alias` (referred to in LDK as `scid_privacy`) means
+ // "refuse to forward unless the SCID alias was used", so we pretend
+ // we don't have the channel here.
+ break Some(("Refusing to forward over real channel SCID as our counterparty requested.", 0x4000 | 10, None));
+ }
+ let chan_update_opt = self.get_channel_update_for_onion(outgoing_scid, chan).ok();
+
+ // Note that we could technically not return an error yet here and just hope
+ // that the connection is reestablished or monitor updated by the time we get
+ // around to doing the actual forward, but better to fail early if we can and
+ // hopefully an attacker trying to path-trace payments cannot make this occur
+ // on a small/per-node/per-channel scale.
+ if !chan.context.is_live() { // channel_disabled
+ // If the channel_update we're going to return is disabled (i.e. the
+ // peer has been disabled for some time), return `channel_disabled`,
+ // otherwise return `temporary_channel_failure`.
+ if chan_update_opt.as_ref().map(|u| u.contents.flags & 2 == 2).unwrap_or(false) {
+ break Some(("Forwarding channel has been disconnected for some time.", 0x1000 | 20, chan_update_opt));
+ } else {
+ break Some(("Forwarding channel is not in a ready state.", 0x1000 | 7, chan_update_opt));
+ }
+ }
+ if outgoing_amt_msat < chan.context.get_counterparty_htlc_minimum_msat() { // amount_below_minimum
+ break Some(("HTLC amount was below the htlc_minimum_msat", 0x1000 | 11, chan_update_opt));
+ }
+ if let Err((err, code)) = chan.htlc_satisfies_config(&msg, outgoing_amt_msat, outgoing_cltv_value) {
+ break Some((err, code, chan_update_opt));
+ }
+ chan_update_opt
+ } else {
+ if (msg.cltv_expiry as u64) < (outgoing_cltv_value) as u64 + MIN_CLTV_EXPIRY_DELTA as u64 {
+ // We really should set `incorrect_cltv_expiry` here but as we're not
+ // forwarding over a real channel we can't generate a channel_update
+ // for it. Instead we just return a generic temporary_node_failure.
+ break Some((
+ "Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta",
+ 0x2000 | 2, None,
+ ));
+ }
+ None
+ };
+
+ let cur_height = self.best_block.read().unwrap().height() + 1;
+ // Theoretically, channel counterparty shouldn't send us a HTLC expiring now,
+ // but we want to be robust wrt to counterparty packet sanitization (see
+ // HTLC_FAIL_BACK_BUFFER rationale).
+ if msg.cltv_expiry <= cur_height + HTLC_FAIL_BACK_BUFFER as u32 { // expiry_too_soon
+ break Some(("CLTV expiry is too close", 0x1000 | 14, chan_update_opt));
+ }
+ if msg.cltv_expiry > cur_height + CLTV_FAR_FAR_AWAY as u32 { // expiry_too_far
+ break Some(("CLTV expiry is too far in the future", 21, None));
+ }
+ // If the HTLC expires ~now, don't bother trying to forward it to our
+ // counterparty. They should fail it anyway, but we don't want to bother with
+ // the round-trips or risk them deciding they definitely want the HTLC and
+ // force-closing to ensure they get it if we're offline.
+ // We previously had a much more aggressive check here which tried to ensure
+ // our counterparty receives an HTLC which has *our* risk threshold met on it,
+ // but there is no need to do that, and since we're a bit conservative with our
+ // risk threshold it just results in failing to forward payments.
+ if (outgoing_cltv_value) as u64 <= (cur_height + LATENCY_GRACE_PERIOD_BLOCKS) as u64 {
+ break Some(("Outgoing CLTV value is too soon", 0x1000 | 14, chan_update_opt));
+ }
+
+ break None;
+ }
+ {
+ let mut res = VecWriter(Vec::with_capacity(chan_update.serialized_length() + 2 + 8 + 2));
+ if let Some(chan_update) = chan_update {
+ if code == 0x1000 | 11 || code == 0x1000 | 12 {
+ msg.amount_msat.write(&mut res).expect("Writes cannot fail");
+ }
+ else if code == 0x1000 | 13 {
+ msg.cltv_expiry.write(&mut res).expect("Writes cannot fail");
+ }
+ else if code == 0x1000 | 20 {
+ // TODO: underspecified, follow https://github.com/lightning/bolts/issues/791
+ 0u16.write(&mut res).expect("Writes cannot fail");
+ }
+ (chan_update.serialized_length() as u16 + 2).write(&mut res).expect("Writes cannot fail");
+ msgs::ChannelUpdate::TYPE.write(&mut res).expect("Writes cannot fail");
+ chan_update.write(&mut res).expect("Writes cannot fail");
+ } else if code & 0x1000 == 0x1000 {
+ // If we're trying to return an error that requires a `channel_update` but
+ // we're forwarding to a phantom or intercept "channel" (i.e. cannot
+ // generate an update), just use the generic "temporary_node_failure"
+ // instead.
+ code = 0x2000 | 2;
+ }
+ return_err!(err, code, &res.0[..]);
+ }
+ Ok((next_hop, shared_secret, next_packet_pk_opt))
+ }
+
+ fn construct_pending_htlc_status<'a>(
+ &self, msg: &msgs::UpdateAddHTLC, shared_secret: [u8; 32], decoded_hop: onion_utils::Hop,
+ allow_underpay: bool, next_packet_pubkey_opt: Option<Result<PublicKey, secp256k1::Error>>
+ ) -> PendingHTLCStatus {
+ macro_rules! return_err {
+ ($msg: expr, $err_code: expr, $data: expr) => {
+ {
+ log_info!(self.logger, "Failed to accept/forward incoming HTLC: {}", $msg);
+ return PendingHTLCStatus::Fail(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
+ channel_id: msg.channel_id,
+ htlc_id: msg.htlc_id,
+ reason: HTLCFailReason::reason($err_code, $data.to_vec())
+ .get_encrypted_failure_packet(&shared_secret, &None),
+ }));
+ }
+ }
+ }
+ match decoded_hop {
onion_utils::Hop::Receive(next_hop_data) => {
// OUR PAYMENT!
- match self.construct_recv_pending_htlc_info(next_hop_data, shared_secret, msg.payment_hash, msg.amount_msat, msg.cltv_expiry, None) {
+ match self.construct_recv_pending_htlc_info(next_hop_data, shared_secret, msg.payment_hash,
+ msg.amount_msat, msg.cltv_expiry, None, allow_underpay, msg.skimmed_fee_msat)
+ {
Ok(info) => {
// Note that we could obviously respond immediately with an update_fulfill_htlc
// message, however that would leak that we are the recipient of this payment, so
}
},
onion_utils::Hop::Forward { next_hop_data, next_hop_hmac, new_packet_bytes } => {
- let new_pubkey = msg.onion_routing_packet.public_key.unwrap();
+ debug_assert!(next_packet_pubkey_opt.is_some());
let outgoing_packet = msgs::OnionPacket {
version: 0,
- public_key: onion_utils::next_hop_packet_pubkey(&self.secp_ctx, new_pubkey, &shared_secret),
+ public_key: next_packet_pubkey_opt.unwrap_or(Err(secp256k1::Error::InvalidPublicKey)),
hop_data: new_packet_bytes,
hmac: next_hop_hmac.clone(),
};
incoming_amt_msat: Some(msg.amount_msat),
outgoing_amt_msat: next_hop_data.amt_to_forward,
outgoing_cltv_value: next_hop_data.outgoing_cltv_value,
+ skimmed_fee_msat: None,
})
}
- };
-
- if let &PendingHTLCStatus::Forward(PendingHTLCInfo { ref routing, ref outgoing_amt_msat, ref outgoing_cltv_value, .. }) = &pending_forward_info {
- // If short_channel_id is 0 here, we'll reject the HTLC as there cannot be a channel
- // with a short_channel_id of 0. This is important as various things later assume
- // short_channel_id is non-0 in any ::Forward.
- if let &PendingHTLCRouting::Forward { ref short_channel_id, .. } = routing {
- if let Some((err, mut code, chan_update)) = loop {
- let id_option = self.short_to_chan_info.read().unwrap().get(short_channel_id).cloned();
- let forwarding_chan_info_opt = match id_option {
- None => { // unknown_next_peer
- // Note that this is likely a timing oracle for detecting whether an scid is a
- // phantom or an intercept.
- if (self.default_configuration.accept_intercept_htlcs &&
- fake_scid::is_valid_intercept(&self.fake_scid_rand_bytes, *short_channel_id, &self.genesis_hash)) ||
- fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, *short_channel_id, &self.genesis_hash)
- {
- None
- } else {
- break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
- }
- },
- Some((cp_id, id)) => Some((cp_id.clone(), id.clone())),
- };
- let chan_update_opt = if let Some((counterparty_node_id, forwarding_id)) = forwarding_chan_info_opt {
- let per_peer_state = self.per_peer_state.read().unwrap();
- let peer_state_mutex_opt = per_peer_state.get(&counterparty_node_id);
- if peer_state_mutex_opt.is_none() {
- break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
- }
- let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
- let peer_state = &mut *peer_state_lock;
- let chan = match peer_state.channel_by_id.get_mut(&forwarding_id) {
- None => {
- // Channel was removed. The short_to_chan_info and channel_by_id maps
- // have no consistency guarantees.
- break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
- },
- Some(chan) => chan
- };
- if !chan.context.should_announce() && !self.default_configuration.accept_forwards_to_priv_channels {
- // Note that the behavior here should be identical to the above block - we
- // should NOT reveal the existence or non-existence of a private channel if
- // we don't allow forwards outbound over them.
- break Some(("Refusing to forward to a private channel based on our config.", 0x4000 | 10, None));
- }
- if chan.context.get_channel_type().supports_scid_privacy() && *short_channel_id != chan.context.outbound_scid_alias() {
- // `option_scid_alias` (referred to in LDK as `scid_privacy`) means
- // "refuse to forward unless the SCID alias was used", so we pretend
- // we don't have the channel here.
- break Some(("Refusing to forward over real channel SCID as our counterparty requested.", 0x4000 | 10, None));
- }
- let chan_update_opt = self.get_channel_update_for_onion(*short_channel_id, chan).ok();
-
- // Note that we could technically not return an error yet here and just hope
- // that the connection is reestablished or monitor updated by the time we get
- // around to doing the actual forward, but better to fail early if we can and
- // hopefully an attacker trying to path-trace payments cannot make this occur
- // on a small/per-node/per-channel scale.
- if !chan.context.is_live() { // channel_disabled
- // If the channel_update we're going to return is disabled (i.e. the
- // peer has been disabled for some time), return `channel_disabled`,
- // otherwise return `temporary_channel_failure`.
- if chan_update_opt.as_ref().map(|u| u.contents.flags & 2 == 2).unwrap_or(false) {
- break Some(("Forwarding channel has been disconnected for some time.", 0x1000 | 20, chan_update_opt));
- } else {
- break Some(("Forwarding channel is not in a ready state.", 0x1000 | 7, chan_update_opt));
- }
- }
- if *outgoing_amt_msat < chan.context.get_counterparty_htlc_minimum_msat() { // amount_below_minimum
- break Some(("HTLC amount was below the htlc_minimum_msat", 0x1000 | 11, chan_update_opt));
- }
- if let Err((err, code)) = chan.htlc_satisfies_config(&msg, *outgoing_amt_msat, *outgoing_cltv_value) {
- break Some((err, code, chan_update_opt));
- }
- chan_update_opt
- } else {
- if (msg.cltv_expiry as u64) < (*outgoing_cltv_value) as u64 + MIN_CLTV_EXPIRY_DELTA as u64 {
- // We really should set `incorrect_cltv_expiry` here but as we're not
- // forwarding over a real channel we can't generate a channel_update
- // for it. Instead we just return a generic temporary_node_failure.
- break Some((
- "Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta",
- 0x2000 | 2, None,
- ));
- }
- None
- };
-
- let cur_height = self.best_block.read().unwrap().height() + 1;
- // Theoretically, channel counterparty shouldn't send us a HTLC expiring now,
- // but we want to be robust wrt to counterparty packet sanitization (see
- // HTLC_FAIL_BACK_BUFFER rationale).
- if msg.cltv_expiry <= cur_height + HTLC_FAIL_BACK_BUFFER as u32 { // expiry_too_soon
- break Some(("CLTV expiry is too close", 0x1000 | 14, chan_update_opt));
- }
- if msg.cltv_expiry > cur_height + CLTV_FAR_FAR_AWAY as u32 { // expiry_too_far
- break Some(("CLTV expiry is too far in the future", 21, None));
- }
- // If the HTLC expires ~now, don't bother trying to forward it to our
- // counterparty. They should fail it anyway, but we don't want to bother with
- // the round-trips or risk them deciding they definitely want the HTLC and
- // force-closing to ensure they get it if we're offline.
- // We previously had a much more aggressive check here which tried to ensure
- // our counterparty receives an HTLC which has *our* risk threshold met on it,
- // but there is no need to do that, and since we're a bit conservative with our
- // risk threshold it just results in failing to forward payments.
- if (*outgoing_cltv_value) as u64 <= (cur_height + LATENCY_GRACE_PERIOD_BLOCKS) as u64 {
- break Some(("Outgoing CLTV value is too soon", 0x1000 | 14, chan_update_opt));
- }
-
- break None;
- }
- {
- let mut res = VecWriter(Vec::with_capacity(chan_update.serialized_length() + 2 + 8 + 2));
- if let Some(chan_update) = chan_update {
- if code == 0x1000 | 11 || code == 0x1000 | 12 {
- msg.amount_msat.write(&mut res).expect("Writes cannot fail");
- }
- else if code == 0x1000 | 13 {
- msg.cltv_expiry.write(&mut res).expect("Writes cannot fail");
- }
- else if code == 0x1000 | 20 {
- // TODO: underspecified, follow https://github.com/lightning/bolts/issues/791
- 0u16.write(&mut res).expect("Writes cannot fail");
- }
- (chan_update.serialized_length() as u16 + 2).write(&mut res).expect("Writes cannot fail");
- msgs::ChannelUpdate::TYPE.write(&mut res).expect("Writes cannot fail");
- chan_update.write(&mut res).expect("Writes cannot fail");
- } else if code & 0x1000 == 0x1000 {
- // If we're trying to return an error that requires a `channel_update` but
- // we're forwarding to a phantom or intercept "channel" (i.e. cannot
- // generate an update), just use the generic "temporary_node_failure"
- // instead.
- code = 0x2000 | 2;
- }
- return_err!(err, code, &res.0[..]);
- }
- }
}
-
- pending_forward_info
}
/// Gets the current [`channel_update`] for the given channel. This first checks if the channel is
session_priv: session_priv.clone(),
first_hop_htlc_msat: htlc_msat,
payment_id,
- }, onion_packet, &self.logger);
+ }, onion_packet, None, &self.fee_estimator, &self.logger);
match break_chan_entry!(self, send_res, chan) {
Some(monitor_update) => {
- let update_id = monitor_update.update_id;
- let update_res = self.chain_monitor.update_channel(funding_txo, monitor_update);
- if let Err(e) = handle_new_monitor_update!(self, update_res, update_id, peer_state_lock, peer_state, per_peer_state, chan) {
- break Err(e);
- }
- if update_res == ChannelMonitorUpdateStatus::InProgress {
- // Note that MonitorUpdateInProgress here indicates (per function
- // docs) that we will resend the commitment update once monitor
- // updating completes. Therefore, we must return an error
- // indicating that it is unsafe to retry the payment wholesale,
- // which we do in the send_payment check for
- // MonitorUpdateInProgress, below.
- return Err(APIError::MonitorUpdateInProgress);
+ match handle_new_monitor_update!(self, funding_txo, monitor_update, peer_state_lock, peer_state, per_peer_state, chan) {
+ Err(e) => break Err(e),
+ Ok(false) => {
+ // Note that MonitorUpdateInProgress here indicates (per function
+ // docs) that we will resend the commitment update once monitor
+ // updating completes. Therefore, we must return an error
+ // indicating that it is unsafe to retry the payment wholesale,
+ // which we do in the send_payment check for
+ // MonitorUpdateInProgress, below.
+ return Err(APIError::MonitorUpdateInProgress);
+ },
+ Ok(true) => {},
}
},
None => { },
/// irrevocably committed to on our end. In such a case, do NOT retry the payment with a
/// different route unless you intend to pay twice!
///
+ /// [`RouteHop`]: crate::routing::router::RouteHop
/// [`Event::PaymentSent`]: events::Event::PaymentSent
/// [`Event::PaymentFailed`]: events::Event::PaymentFailed
/// [`UpdateHTLCs`]: events::MessageSendEvent::UpdateHTLCs
Some(chan) => {
let funding_txo = find_funding_output(&chan, &funding_transaction)?;
- let funding_res = chan.get_outbound_funding_created(funding_transaction, funding_txo, &self.logger)
+ let funding_res = chan.get_funding_created(funding_transaction, funding_txo, &self.logger)
.map_err(|(mut chan, e)| if let ChannelError::Close(msg) = e {
let channel_id = chan.context.channel_id();
let user_id = chan.context.get_user_id();
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
for channel_id in channel_ids {
- if !peer_state.channel_by_id.contains_key(channel_id) {
+ if !peer_state.has_channel(channel_id) {
return Err(APIError::ChannelUnavailable {
err: format!("Channel with ID {} was not found for the passed counterparty_node_id {}", log_bytes!(*channel_id), counterparty_node_id),
});
- }
+ };
}
for channel_id in channel_ids {
- let channel = peer_state.channel_by_id.get_mut(channel_id).unwrap();
- let mut config = channel.context.config();
- config.apply(config_update);
- if !channel.context.update_config(&config) {
+ if let Some(channel) = peer_state.channel_by_id.get_mut(channel_id) {
+ let mut config = channel.context.config();
+ config.apply(config_update);
+ if !channel.context.update_config(&config) {
+ continue;
+ }
+ if let Ok(msg) = self.get_channel_update_for_broadcast(channel) {
+ peer_state.pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate { msg });
+ } else if let Ok(msg) = self.get_channel_update_for_unicast(channel) {
+ peer_state.pending_msg_events.push(events::MessageSendEvent::SendChannelUpdate {
+ node_id: channel.context.get_counterparty_node_id(),
+ msg,
+ });
+ }
continue;
}
- if let Ok(msg) = self.get_channel_update_for_broadcast(channel) {
- peer_state.pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate { msg });
- } else if let Ok(msg) = self.get_channel_update_for_unicast(channel) {
- peer_state.pending_msg_events.push(events::MessageSendEvent::SendChannelUpdate {
- node_id: channel.context.get_counterparty_node_id(),
- msg,
+
+ let context = if let Some(channel) = peer_state.inbound_v1_channel_by_id.get_mut(channel_id) {
+ &mut channel.context
+ } else if let Some(channel) = peer_state.outbound_v1_channel_by_id.get_mut(channel_id) {
+ &mut channel.context
+ } else {
+ // This should not be reachable as we've already checked for non-existence in the previous channel_id loop.
+ debug_assert!(false);
+ return Err(APIError::ChannelUnavailable {
+ err: format!(
+ "Channel with ID {} for passed counterparty_node_id {} disappeared after we confirmed its existence - this should not be reachable!",
+ log_bytes!(*channel_id), counterparty_node_id),
});
- }
+ };
+ let mut config = context.config();
+ config.apply(config_update);
+ // We update the config, but we MUST NOT broadcast a `channel_update` before `channel_ready`
+ // which would be the case for pending inbound/outbound channels.
+ context.update_config(&config);
}
Ok(())
}
/// [`ChannelManager::fail_intercepted_htlc`] MUST be called in response to the event.
///
/// Note that LDK does not enforce fee requirements in `amt_to_forward_msat`, and will not stop
- /// you from forwarding more than you received.
+ /// you from forwarding more than you received. See
+ /// [`HTLCIntercepted::expected_outbound_amount_msat`] for more on forwarding a different amount
+ /// than expected.
///
/// Errors if the event was not handled in time, in which case the HTLC was automatically failed
/// backwards.
///
/// [`UserConfig::accept_intercept_htlcs`]: crate::util::config::UserConfig::accept_intercept_htlcs
/// [`HTLCIntercepted`]: events::Event::HTLCIntercepted
+ /// [`HTLCIntercepted::expected_outbound_amount_msat`]: events::Event::HTLCIntercepted::expected_outbound_amount_msat
// TODO: when we move to deciding the best outbound channel at forward time, only take
// `next_node_id` and not `next_hop_channel_id`
pub fn forward_intercepted_htlc(&self, intercept_id: InterceptId, next_hop_channel_id: &[u8; 32], next_node_id: PublicKey, amt_to_forward_msat: u64) -> Result<(), APIError> {
},
_ => unreachable!() // Only `PendingHTLCRouting::Forward`s are intercepted
};
+ let skimmed_fee_msat =
+ payment.forward_info.outgoing_amt_msat.saturating_sub(amt_to_forward_msat);
let pending_htlc_info = PendingHTLCInfo {
+ skimmed_fee_msat: if skimmed_fee_msat == 0 { None } else { Some(skimmed_fee_msat) },
outgoing_amt_msat: amt_to_forward_msat, routing, ..payment.forward_info
};
prev_short_channel_id, prev_htlc_id, prev_funding_outpoint, prev_user_channel_id,
forward_info: PendingHTLCInfo {
routing, incoming_shared_secret, payment_hash, outgoing_amt_msat,
- outgoing_cltv_value, incoming_amt_msat: _
+ outgoing_cltv_value, ..
}
}) => {
macro_rules! failure_handler {
};
match next_hop {
onion_utils::Hop::Receive(hop_data) => {
- match self.construct_recv_pending_htlc_info(hop_data, incoming_shared_secret, payment_hash, outgoing_amt_msat, outgoing_cltv_value, Some(phantom_shared_secret)) {
+ match self.construct_recv_pending_htlc_info(hop_data,
+ incoming_shared_secret, payment_hash, outgoing_amt_msat,
+ outgoing_cltv_value, Some(phantom_shared_secret), false, None)
+ {
Ok(info) => phantom_receives.push((prev_short_channel_id, prev_funding_outpoint, prev_user_channel_id, vec![(info, prev_htlc_id)])),
Err(ReceiveError { err_code, err_data, msg }) => failed_payment!(msg, err_code, err_data, Some(phantom_shared_secret))
}
prev_short_channel_id, prev_htlc_id, prev_funding_outpoint, prev_user_channel_id: _,
forward_info: PendingHTLCInfo {
incoming_shared_secret, payment_hash, outgoing_amt_msat, outgoing_cltv_value,
- routing: PendingHTLCRouting::Forward { onion_packet, .. }, incoming_amt_msat: _,
+ routing: PendingHTLCRouting::Forward { onion_packet, .. }, skimmed_fee_msat, ..
},
}) => {
log_trace!(self.logger, "Adding HTLC from short id {} with payment_hash {} to channel with short id {} after delay", prev_short_channel_id, log_bytes!(payment_hash.0), short_chan_id);
});
if let Err(e) = chan.get_mut().queue_add_htlc(outgoing_amt_msat,
payment_hash, outgoing_cltv_value, htlc_source.clone(),
- onion_packet, &self.logger)
+ onion_packet, skimmed_fee_msat, &self.fee_estimator,
+ &self.logger)
{
if let ChannelError::Ignore(msg) = e {
log_trace!(self.logger, "Failed to forward HTLC with payment_hash {}: {}", log_bytes!(payment_hash.0), msg);
HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo {
prev_short_channel_id, prev_htlc_id, prev_funding_outpoint, prev_user_channel_id,
forward_info: PendingHTLCInfo {
- routing, incoming_shared_secret, payment_hash, incoming_amt_msat, outgoing_amt_msat, ..
+ routing, incoming_shared_secret, payment_hash, incoming_amt_msat, outgoing_amt_msat,
+ skimmed_fee_msat, ..
}
}) => {
let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret, mut onion_fields) = match routing {
total_msat: if let Some(data) = &payment_data { data.total_msat } else { outgoing_amt_msat },
cltv_expiry,
onion_payload,
+ counterparty_skimmed_fee_msat: skimmed_fee_msat,
};
let mut committed_to_claimable = false;
htlcs.push(claimable_htlc);
let amount_msat = htlcs.iter().map(|htlc| htlc.value).sum();
htlcs.iter_mut().for_each(|htlc| htlc.total_value_received = Some(amount_msat));
+ let counterparty_skimmed_fee_msat = htlcs.iter()
+ .map(|htlc| htlc.counterparty_skimmed_fee_msat.unwrap_or(0)).sum();
+ debug_assert!(total_value.saturating_sub(amount_msat) <=
+ counterparty_skimmed_fee_msat);
new_events.push_back((events::Event::PaymentClaimable {
receiver_node_id: Some(receiver_node_id),
payment_hash,
purpose: $purpose,
amount_msat,
+ counterparty_skimmed_fee_msat,
via_channel_id: Some(prev_channel_id),
via_user_channel_id: Some(prev_user_channel_id),
claim_deadline: Some(earliest_expiry - HTLC_FAIL_BACK_BUFFER),
fn process_background_events(&self) -> NotifyOption {
debug_assert_ne!(self.total_consistency_lock.held_by_thread(), LockHeldState::NotHeldByThread);
- #[cfg(debug_assertions)]
self.background_events_processed_since_startup.store(true, Ordering::Release);
let mut background_events = Vec::new();
for event in background_events.drain(..) {
match event {
- BackgroundEvent::ClosingMonitorUpdateRegeneratedOnStartup((funding_txo, update)) => {
+ BackgroundEvent::ClosedMonitorUpdateRegeneratedOnStartup((funding_txo, update)) => {
// The channel has already been closed, so no use bothering to care about the
// monitor updating completing.
let _ = self.chain_monitor.update_channel(funding_txo, &update);
},
BackgroundEvent::MonitorUpdateRegeneratedOnStartup { counterparty_node_id, funding_txo, update } => {
- let update_res = self.chain_monitor.update_channel(funding_txo, &update);
-
+ let mut updated_chan = false;
let res = {
let per_peer_state = self.per_peer_state.read().unwrap();
if let Some(peer_state_mutex) = per_peer_state.get(&counterparty_node_id) {
let peer_state = &mut *peer_state_lock;
match peer_state.channel_by_id.entry(funding_txo.to_channel_id()) {
hash_map::Entry::Occupied(mut chan) => {
- handle_new_monitor_update!(self, update_res, update.update_id, peer_state_lock, peer_state, per_peer_state, chan)
+ updated_chan = true;
+ handle_new_monitor_update!(self, funding_txo, update.clone(),
+ peer_state_lock, peer_state, per_peer_state, chan).map(|_| ())
},
hash_map::Entry::Vacant(_) => Ok(()),
}
} else { Ok(()) }
};
+ if !updated_chan {
+ // TODO: Track this as in-flight even though the channel is closed.
+ let _ = self.chain_monitor.update_channel(funding_txo, &update);
+ }
// TODO: If this channel has since closed, we're likely providing a payment
// preimage update, which we must ensure is durable! We currently don't,
// however, ensure that.
}
let _ = handle_error!(self, res, counterparty_node_id);
},
+ BackgroundEvent::MonitorUpdatesComplete { counterparty_node_id, channel_id } => {
+ let per_peer_state = self.per_peer_state.read().unwrap();
+ if let Some(peer_state_mutex) = per_peer_state.get(&counterparty_node_id) {
+ let mut peer_state_lock = peer_state_mutex.lock().unwrap();
+ let peer_state = &mut *peer_state_lock;
+ if let Some(chan) = peer_state.channel_by_id.get_mut(&channel_id) {
+ handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);
+ } else {
+ let update_actions = peer_state.monitor_update_blocked_actions
+ .remove(&channel_id).unwrap_or(Vec::new());
+ mem::drop(peer_state_lock);
+ mem::drop(per_peer_state);
+ self.handle_monitor_update_completion_actions(update_actions);
+ }
+ }
+ },
}
}
NotifyOption::DoPersist
log_trace!(self.logger, "Channel {} qualifies for a feerate change from {} to {}.",
log_bytes!(chan_id[..]), chan.context.get_feerate_sat_per_1000_weight(), new_feerate);
- chan.queue_update_fee(new_feerate, &self.logger);
+ chan.queue_update_fee(new_feerate, &self.fee_estimator, &self.logger);
NotifyOption::DoPersist
}
PersistenceNotifierGuard::optionally_notify(&self.total_consistency_lock, &self.persistence_notifier, || {
let mut should_persist = self.process_background_events();
- let new_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
+ let normal_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
+ let min_mempool_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::MempoolMinimum);
let per_peer_state = self.per_peer_state.read().unwrap();
for (_cp_id, peer_state_mutex) in per_peer_state.iter() {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
for (chan_id, chan) in peer_state.channel_by_id.iter_mut() {
+ let new_feerate = if chan.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
+ min_mempool_feerate
+ } else {
+ normal_feerate
+ };
let chan_needs_persist = self.update_channel_fee(chan_id, chan, new_feerate);
if chan_needs_persist == NotifyOption::DoPersist { should_persist = NotifyOption::DoPersist; }
}
/// * Expiring a channel's previous [`ChannelConfig`] if necessary to only allow forwarding HTLCs
/// with the current [`ChannelConfig`].
/// * Removing peers which have disconnected but and no longer have any channels.
+ /// * Force-closing and removing channels which have not completed establishment in a timely manner.
///
/// Note that this may cause reentrancy through [`chain::Watch::update_channel`] calls or feerate
/// estimate fetches.
PersistenceNotifierGuard::optionally_notify(&self.total_consistency_lock, &self.persistence_notifier, || {
let mut should_persist = self.process_background_events();
- let new_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
+ let normal_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
+ let min_mempool_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::MempoolMinimum);
let mut handle_errors: Vec<(Result<(), _>, _)> = Vec::new();
let mut timed_out_mpp_htlcs = Vec::new();
let pending_msg_events = &mut peer_state.pending_msg_events;
let counterparty_node_id = *counterparty_node_id;
peer_state.channel_by_id.retain(|chan_id, chan| {
+ let new_feerate = if chan.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
+ min_mempool_feerate
+ } else {
+ normal_feerate
+ };
let chan_needs_persist = self.update_channel_fee(chan_id, chan, new_feerate);
if chan_needs_persist == NotifyOption::DoPersist { should_persist = NotifyOption::DoPersist; }
true
});
+
+ let process_unfunded_channel_tick = |
+ chan_id: &[u8; 32],
+ chan_context: &mut ChannelContext<<SP::Target as SignerProvider>::Signer>,
+ unfunded_chan_context: &mut UnfundedChannelContext,
+ | {
+ chan_context.maybe_expire_prev_config();
+ if unfunded_chan_context.should_expire_unfunded_channel() {
+ log_error!(self.logger, "Force-closing pending outbound channel {} for not establishing in a timely manner", log_bytes!(&chan_id[..]));
+ update_maps_on_chan_removal!(self, &chan_context);
+ self.issue_channel_close_events(&chan_context, ClosureReason::HolderForceClosed);
+ self.finish_force_close_channel(chan_context.force_shutdown(false));
+ false
+ } else {
+ true
+ }
+ };
+ peer_state.outbound_v1_channel_by_id.retain(|chan_id, chan| process_unfunded_channel_tick(chan_id, &mut chan.context, &mut chan.unfunded_context));
+ peer_state.inbound_v1_channel_by_id.retain(|chan_id, chan| process_unfunded_channel_tick(chan_id, &mut chan.context, &mut chan.unfunded_context));
+
if peer_state.ok_to_remove(true) {
pending_peers_awaiting_removal.push(counterparty_node_id);
}
-> Result<(), (PublicKey, MsgHandleErrInternal)> {
//TODO: Delay the claimed_funds relaying just like we do outbound relay!
+ // If we haven't yet run background events assume we're still deserializing and shouldn't
+ // actually pass `ChannelMonitorUpdate`s to users yet. Instead, queue them up as
+ // `BackgroundEvent`s.
+ let during_init = !self.background_events_processed_since_startup.load(Ordering::Acquire);
+
{
let per_peer_state = self.per_peer_state.read().unwrap();
let chan_id = prev_hop.outpoint.to_channel_id();
log_bytes!(chan_id), action);
peer_state.monitor_update_blocked_actions.entry(chan_id).or_insert(Vec::new()).push(action);
}
- let update_id = monitor_update.update_id;
- let update_res = self.chain_monitor.update_channel(prev_hop.outpoint, monitor_update);
- let res = handle_new_monitor_update!(self, update_res, update_id, peer_state_lock,
- peer_state, per_peer_state, chan);
- if let Err(e) = res {
- // TODO: This is a *critical* error - we probably updated the outbound edge
- // of the HTLC's monitor with a preimage. We should retry this monitor
- // update over and over again until morale improves.
- log_error!(self.logger, "Failed to update channel monitor with preimage {:?}", payment_preimage);
- return Err((counterparty_node_id, e));
+ if !during_init {
+ let res = handle_new_monitor_update!(self, prev_hop.outpoint, monitor_update, peer_state_lock,
+ peer_state, per_peer_state, chan);
+ if let Err(e) = res {
+ // TODO: This is a *critical* error - we probably updated the outbound edge
+ // of the HTLC's monitor with a preimage. We should retry this monitor
+ // update over and over again until morale improves.
+ log_error!(self.logger, "Failed to update channel monitor with preimage {:?}", payment_preimage);
+ return Err((counterparty_node_id, e));
+ }
+ } else {
+ // If we're running during init we cannot update a monitor directly -
+ // they probably haven't actually been loaded yet. Instead, push the
+ // monitor update as a background event.
+ self.pending_background_events.lock().unwrap().push(
+ BackgroundEvent::MonitorUpdateRegeneratedOnStartup {
+ counterparty_node_id,
+ funding_txo: prev_hop.outpoint,
+ update: monitor_update.clone(),
+ });
}
}
return Ok(());
payment_preimage,
}],
};
- // We update the ChannelMonitor on the backward link, after
- // receiving an `update_fulfill_htlc` from the forward link.
- let update_res = self.chain_monitor.update_channel(prev_hop.outpoint, &preimage_update);
- if update_res != ChannelMonitorUpdateStatus::Completed {
- // TODO: This needs to be handled somehow - if we receive a monitor update
- // with a preimage we *must* somehow manage to propagate it to the upstream
- // channel, or we must have an ability to receive the same event and try
- // again on restart.
- log_error!(self.logger, "Critical error: failed to update channel monitor with preimage {:?}: {:?}",
- payment_preimage, update_res);
+
+ if !during_init {
+ // We update the ChannelMonitor on the backward link, after
+ // receiving an `update_fulfill_htlc` from the forward link.
+ let update_res = self.chain_monitor.update_channel(prev_hop.outpoint, &preimage_update);
+ if update_res != ChannelMonitorUpdateStatus::Completed {
+ // TODO: This needs to be handled somehow - if we receive a monitor update
+ // with a preimage we *must* somehow manage to propagate it to the upstream
+ // channel, or we must have an ability to receive the same event and try
+ // again on restart.
+ log_error!(self.logger, "Critical error: failed to update channel monitor with preimage {:?}: {:?}",
+ payment_preimage, update_res);
+ }
+ } else {
+ // If we're running during init we cannot update a monitor directly - they probably
+ // haven't actually been loaded yet. Instead, push the monitor update as a background
+ // event.
+ // Note that while it's safe to use `ClosedMonitorUpdateRegeneratedOnStartup` here (the
+ // channel is already closed) we need to ultimately handle the monitor update
+ // completion action only after we've completed the monitor update. This is the only
+ // way to guarantee this update *will* be regenerated on startup (otherwise if this was
+ // from a forwarded HTLC the downstream preimage may be deleted before we claim
+ // upstream). Thus, we need to transition to some new `BackgroundEvent` type which will
+ // complete the monitor update completion action from `completion_action`.
+ self.pending_background_events.lock().unwrap().push(
+ BackgroundEvent::ClosedMonitorUpdateRegeneratedOnStartup((
+ prev_hop.outpoint, preimage_update,
+ )));
}
// Note that we do process the completion action here. This totally could be a
// duplicate claim, but we have no way of knowing without interrogating the
fn claim_funds_internal(&self, source: HTLCSource, payment_preimage: PaymentPreimage, forwarded_htlc_value_msat: Option<u64>, from_onchain: bool, next_channel_id: [u8; 32]) {
match source {
HTLCSource::OutboundRoute { session_priv, payment_id, path, .. } => {
+ debug_assert!(self.background_events_processed_since_startup.load(Ordering::Acquire),
+ "We don't support claim_htlc claims during startup - monitors may not be available yet");
self.pending_outbound_payments.claim_htlc(payment_id, payment_preimage, session_priv, path, from_onchain, &self.pending_events, &self.logger);
},
HTLCSource::PreviousHopData(hop_data) => {
if peer_state_mutex_opt.is_none() { return }
peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
let peer_state = &mut *peer_state_lock;
- let mut channel = {
- match peer_state.channel_by_id.entry(funding_txo.to_channel_id()){
- hash_map::Entry::Occupied(chan) => chan,
- hash_map::Entry::Vacant(_) => return,
- }
- };
- log_trace!(self.logger, "ChannelMonitor updated to {}. Current highest is {}",
- highest_applied_update_id, channel.get().context.get_latest_monitor_update_id());
- if !channel.get().is_awaiting_monitor_update() || channel.get().context.get_latest_monitor_update_id() != highest_applied_update_id {
+ let channel =
+ if let Some(chan) = peer_state.channel_by_id.get_mut(&funding_txo.to_channel_id()) {
+ chan
+ } else {
+ let update_actions = peer_state.monitor_update_blocked_actions
+ .remove(&funding_txo.to_channel_id()).unwrap_or(Vec::new());
+ mem::drop(peer_state_lock);
+ mem::drop(per_peer_state);
+ self.handle_monitor_update_completion_actions(update_actions);
+ return;
+ };
+ let remaining_in_flight =
+ if let Some(pending) = peer_state.in_flight_monitor_updates.get_mut(funding_txo) {
+ pending.retain(|upd| upd.update_id > highest_applied_update_id);
+ pending.len()
+ } else { 0 };
+ log_trace!(self.logger, "ChannelMonitor updated to {}. Current highest is {}. {} pending in-flight updates.",
+ highest_applied_update_id, channel.context.get_latest_monitor_update_id(),
+ remaining_in_flight);
+ if !channel.is_awaiting_monitor_update() || channel.context.get_latest_monitor_update_id() != highest_applied_update_id {
return;
}
- handle_monitor_update_completion!(self, highest_applied_update_id, peer_state_lock, peer_state, per_peer_state, channel.get_mut());
+ handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, channel);
}
/// Accepts a request to open a channel after a [`Event::OpenChannelRequest`].
return Err(MsgHandleErrInternal::send_err_msg_no_close("temporary_channel_id collision for the same peer!".to_owned(), msg.temporary_channel_id.clone()))
} else {
if !self.default_configuration.manually_accept_inbound_channels {
- if channel.context.get_channel_type().requires_zero_conf() {
+ let channel_type = channel.context.get_channel_type();
+ if channel_type.requires_zero_conf() {
return Err(MsgHandleErrInternal::send_err_msg_no_close("No zero confirmation channels accepted".to_owned(), msg.temporary_channel_id.clone()));
}
+ if channel_type.requires_anchors_zero_fee_htlc_tx() {
+ return Err(MsgHandleErrInternal::send_err_msg_no_close("No channels with anchor outputs accepted".to_owned(), msg.temporary_channel_id.clone()));
+ }
peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: counterparty_node_id.clone(),
msg: channel.accept_inbound_channel(user_channel_id),
let monitor_res = self.chain_monitor.watch_channel(monitor.get_funding_txo().0, monitor);
let chan = e.insert(chan);
- let mut res = handle_new_monitor_update!(self, monitor_res, 0, peer_state_lock, peer_state,
- per_peer_state, chan, MANUALLY_REMOVING, { peer_state.channel_by_id.remove(&new_channel_id) });
+ let mut res = handle_new_monitor_update!(self, monitor_res, peer_state_lock, peer_state,
+ per_peer_state, chan, MANUALLY_REMOVING_INITIAL_MONITOR,
+ { peer_state.channel_by_id.remove(&new_channel_id) });
// Note that we reply with the new channel_id in error messages if we gave up on the
// channel, not the temporary_channel_id. This is compatible with ourselves, but the
if let Err(MsgHandleErrInternal { shutdown_finish: Some((res, _)), .. }) = &mut res {
res.0 = None;
}
- res
+ res.map(|_| ())
}
}
}
let monitor = try_chan_entry!(self,
chan.get_mut().funding_signed(&msg, best_block, &self.signer_provider, &self.logger), chan);
let update_res = self.chain_monitor.watch_channel(chan.get().context.get_funding_txo().unwrap(), monitor);
- let mut res = handle_new_monitor_update!(self, update_res, 0, peer_state_lock, peer_state, per_peer_state, chan);
+ let mut res = handle_new_monitor_update!(self, update_res, peer_state_lock, peer_state, per_peer_state, chan, INITIAL_MONITOR);
if let Err(MsgHandleErrInternal { ref mut shutdown_finish, .. }) = res {
// We weren't able to watch the channel to begin with, so no updates should be made on
// it. Previously, full_stack_target found an (unreachable) panic when the
shutdown_finish.0.take();
}
}
- res
+ res.map(|_| ())
},
hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close("Failed to find corresponding channel".to_owned(), msg.channel_id))
}
})?;
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
- match peer_state.channel_by_id.entry(msg.channel_id.clone()) {
- hash_map::Entry::Occupied(mut chan_entry) => {
-
- if !chan_entry.get().received_shutdown() {
- log_info!(self.logger, "Received a shutdown message from our counterparty for channel {}{}.",
- log_bytes!(msg.channel_id),
- if chan_entry.get().sent_shutdown() { " after we initiated shutdown" } else { "" });
- }
+ // TODO(dunxen): Fix this duplication when we switch to a single map with enums as per
+ // https://github.com/lightningdevkit/rust-lightning/issues/2422
+ if let hash_map::Entry::Occupied(chan_entry) = peer_state.outbound_v1_channel_by_id.entry(msg.channel_id.clone()) {
+ log_error!(self.logger, "Immediately closing unfunded channel {} as peer asked to cooperatively shut it down (which is unnecessary)", log_bytes!(&msg.channel_id[..]));
+ self.issue_channel_close_events(&chan_entry.get().context, ClosureReason::CounterpartyCoopClosedUnfundedChannel);
+ let mut chan = remove_channel!(self, chan_entry);
+ self.finish_force_close_channel(chan.context.force_shutdown(false));
+ return Ok(());
+ } else if let hash_map::Entry::Occupied(chan_entry) = peer_state.inbound_v1_channel_by_id.entry(msg.channel_id.clone()) {
+ log_error!(self.logger, "Immediately closing unfunded channel {} as peer asked to cooperatively shut it down (which is unnecessary)", log_bytes!(&msg.channel_id[..]));
+ self.issue_channel_close_events(&chan_entry.get().context, ClosureReason::CounterpartyCoopClosedUnfundedChannel);
+ let mut chan = remove_channel!(self, chan_entry);
+ self.finish_force_close_channel(chan.context.force_shutdown(false));
+ return Ok(());
+ } else if let hash_map::Entry::Occupied(mut chan_entry) = peer_state.channel_by_id.entry(msg.channel_id.clone()) {
+ if !chan_entry.get().received_shutdown() {
+ log_info!(self.logger, "Received a shutdown message from our counterparty for channel {}{}.",
+ log_bytes!(msg.channel_id),
+ if chan_entry.get().sent_shutdown() { " after we initiated shutdown" } else { "" });
+ }
- let funding_txo_opt = chan_entry.get().context.get_funding_txo();
- let (shutdown, monitor_update_opt, htlcs) = try_chan_entry!(self,
- chan_entry.get_mut().shutdown(&self.signer_provider, &peer_state.latest_features, &msg), chan_entry);
- dropped_htlcs = htlcs;
+ let funding_txo_opt = chan_entry.get().context.get_funding_txo();
+ let (shutdown, monitor_update_opt, htlcs) = try_chan_entry!(self,
+ chan_entry.get_mut().shutdown(&self.signer_provider, &peer_state.latest_features, &msg), chan_entry);
+ dropped_htlcs = htlcs;
- if let Some(msg) = shutdown {
- // We can send the `shutdown` message before updating the `ChannelMonitor`
- // here as we don't need the monitor update to complete until we send a
- // `shutdown_signed`, which we'll delay if we're pending a monitor update.
- peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
- node_id: *counterparty_node_id,
- msg,
- });
- }
+ if let Some(msg) = shutdown {
+ // We can send the `shutdown` message before updating the `ChannelMonitor`
+ // here as we don't need the monitor update to complete until we send a
+ // `shutdown_signed`, which we'll delay if we're pending a monitor update.
+ peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
+ node_id: *counterparty_node_id,
+ msg,
+ });
+ }
- // Update the monitor with the shutdown script if necessary.
- if let Some(monitor_update) = monitor_update_opt {
- let update_id = monitor_update.update_id;
- let update_res = self.chain_monitor.update_channel(funding_txo_opt.unwrap(), monitor_update);
- break handle_new_monitor_update!(self, update_res, update_id, peer_state_lock, peer_state, per_peer_state, chan_entry);
- }
- break Ok(());
- },
- hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id))
+ // Update the monitor with the shutdown script if necessary.
+ if let Some(monitor_update) = monitor_update_opt {
+ break handle_new_monitor_update!(self, funding_txo_opt.unwrap(), monitor_update,
+ peer_state_lock, peer_state, per_peer_state, chan_entry).map(|_| ());
+ }
+ break Ok(());
+ } else {
+ return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id))
}
};
for htlc_source in dropped_htlcs.drain(..) {
//encrypted with the same key. It's not immediately obvious how to usefully exploit that,
//but we should prevent it anyway.
- let pending_forward_info = self.decode_update_add_htlc_onion(msg);
+ let decoded_hop_res = self.decode_update_add_htlc_onion(msg);
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
.ok_or_else(|| {
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Occupied(mut chan) => {
+ let pending_forward_info = match decoded_hop_res {
+ Ok((next_hop, shared_secret, next_packet_pk_opt)) =>
+ self.construct_pending_htlc_status(msg, shared_secret, next_hop,
+ chan.get().context.config().accept_underpaying_htlcs, next_packet_pk_opt),
+ Err(e) => PendingHTLCStatus::Fail(e)
+ };
let create_pending_htlc_status = |chan: &Channel<<SP::Target as SignerProvider>::Signer>, pending_forward_info: PendingHTLCStatus, error_code: u16| {
// If the update_add is completely bogus, the call will Err and we will close,
// but if we've sent a shutdown and they haven't acknowledged it yet, we just
_ => pending_forward_info
}
};
- try_chan_entry!(self, chan.get_mut().update_add_htlc(&msg, pending_forward_info, create_pending_htlc_status, &self.logger), chan);
+ try_chan_entry!(self, chan.get_mut().update_add_htlc(&msg, pending_forward_info, create_pending_htlc_status, &self.fee_estimator, &self.logger), chan);
},
hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id))
}
let funding_txo = chan.get().context.get_funding_txo();
let monitor_update_opt = try_chan_entry!(self, chan.get_mut().commitment_signed(&msg, &self.logger), chan);
if let Some(monitor_update) = monitor_update_opt {
- let update_res = self.chain_monitor.update_channel(funding_txo.unwrap(), monitor_update);
- let update_id = monitor_update.update_id;
- handle_new_monitor_update!(self, update_res, update_id, peer_state_lock,
- peer_state, per_peer_state, chan)
+ handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock,
+ peer_state, per_peer_state, chan).map(|_| ())
} else { Ok(()) }
},
hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id))
}
}
- // We only want to push a PendingHTLCsForwardable event if no others are queued.
fn push_pending_forwards_ev(&self) {
let mut pending_events = self.pending_events.lock().unwrap();
- let forward_ev_exists = pending_events.iter()
- .find(|(ev, _)| if let events::Event::PendingHTLCsForwardable { .. } = ev { true } else { false })
- .is_some();
- if !forward_ev_exists {
- pending_events.push_back((events::Event::PendingHTLCsForwardable {
- time_forwardable:
- Duration::from_millis(MIN_HTLC_RELAY_HOLDING_CELL_MILLIS),
+ let is_processing_events = self.pending_events_processor.load(Ordering::Acquire);
+ let num_forward_events = pending_events.iter().filter(|(ev, _)|
+ if let events::Event::PendingHTLCsForwardable { .. } = ev { true } else { false }
+ ).count();
+ // We only want to push a PendingHTLCsForwardable event if no others are queued. Processing
+ // events is done in batches and they are not removed until we're done processing each
+ // batch. Since handling a `PendingHTLCsForwardable` event will call back into the
+ // `ChannelManager`, we'll still see the original forwarding event not removed. Phantom
+ // payments will need an additional forwarding event before being claimed to make them look
+ // real by taking more time.
+ if (is_processing_events && num_forward_events <= 1) || num_forward_events < 1 {
+ pending_events.push_back((Event::PendingHTLCsForwardable {
+ time_forwardable: Duration::from_millis(MIN_HTLC_RELAY_HOLDING_CELL_MILLIS),
}, None));
}
}
/// Checks whether [`ChannelMonitorUpdate`]s generated by the receipt of a remote
- /// [`msgs::RevokeAndACK`] should be held for the given channel until some other event
+ /// [`msgs::RevokeAndACK`] should be held for the given channel until some other action
/// completes. Note that this needs to happen in the same [`PeerState`] mutex as any release of
/// the [`ChannelMonitorUpdate`] in question.
fn raa_monitor_updates_held(&self,
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Occupied(mut chan) => {
let funding_txo = chan.get().context.get_funding_txo();
- let (htlcs_to_fail, monitor_update_opt) = try_chan_entry!(self, chan.get_mut().revoke_and_ack(&msg, &self.logger), chan);
+ let (htlcs_to_fail, monitor_update_opt) = try_chan_entry!(self, chan.get_mut().revoke_and_ack(&msg, &self.fee_estimator, &self.logger), chan);
let res = if let Some(monitor_update) = monitor_update_opt {
- let update_res = self.chain_monitor.update_channel(funding_txo.unwrap(), monitor_update);
- let update_id = monitor_update.update_id;
- handle_new_monitor_update!(self, update_res, update_id,
- peer_state_lock, peer_state, per_peer_state, chan)
+ handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update,
+ peer_state_lock, peer_state, per_peer_state, chan).map(|_| ())
} else { Ok(()) };
(htlcs_to_fail, res)
},
let counterparty_node_id = chan.context.get_counterparty_node_id();
let funding_txo = chan.context.get_funding_txo();
let (monitor_opt, holding_cell_failed_htlcs) =
- chan.maybe_free_holding_cell_htlcs(&self.logger);
+ chan.maybe_free_holding_cell_htlcs(&self.fee_estimator, &self.logger);
if !holding_cell_failed_htlcs.is_empty() {
failed_htlcs.push((holding_cell_failed_htlcs, *channel_id, counterparty_node_id));
}
if let Some(monitor_update) = monitor_opt {
has_monitor_update = true;
- let update_res = self.chain_monitor.update_channel(
- funding_txo.expect("channel is live"), monitor_update);
- let update_id = monitor_update.update_id;
let channel_id: [u8; 32] = *channel_id;
- let res = handle_new_monitor_update!(self, update_res, update_id,
+ let res = handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update,
peer_state_lock, peer_state, per_peer_state, chan, MANUALLY_REMOVING,
peer_state.channel_by_id.remove(&channel_id));
if res.is_err() {
}
}
- fn set_payment_hash_secret_map(&self, payment_hash: PaymentHash, payment_preimage: Option<PaymentPreimage>, min_value_msat: Option<u64>, invoice_expiry_delta_secs: u32) -> Result<PaymentSecret, APIError> {
- assert!(invoice_expiry_delta_secs <= 60*60*24*365); // Sadly bitcoin timestamps are u32s, so panic before 2106
-
- if min_value_msat.is_some() && min_value_msat.unwrap() > MAX_VALUE_MSAT {
- return Err(APIError::APIMisuseError { err: format!("min_value_msat of {} greater than total 21 million bitcoin supply", min_value_msat.unwrap()) });
- }
-
- let payment_secret = PaymentSecret(self.entropy_source.get_secure_random_bytes());
-
- let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
- let mut payment_secrets = self.pending_inbound_payments.lock().unwrap();
- match payment_secrets.entry(payment_hash) {
- hash_map::Entry::Vacant(e) => {
- e.insert(PendingInboundPayment {
- payment_secret, min_value_msat, payment_preimage,
- user_payment_id: 0, // For compatibility with version 0.0.103 and earlier
- // We assume that highest_seen_timestamp is pretty close to the current time -
- // it's updated when we receive a new block with the maximum time we've seen in
- // a header. It should never be more than two hours in the future.
- // Thus, we add two hours here as a buffer to ensure we absolutely
- // never fail a payment too early.
- // Note that we assume that received blocks have reasonably up-to-date
- // timestamps.
- expiry_time: self.highest_seen_timestamp.load(Ordering::Acquire) as u64 + invoice_expiry_delta_secs as u64 + 7200,
- });
- },
- hash_map::Entry::Occupied(_) => return Err(APIError::APIMisuseError { err: "Duplicate payment hash".to_owned() }),
- }
- Ok(payment_secret)
- }
-
/// Gets a payment secret and payment hash for use in an invoice given to a third party wishing
/// to pay us.
///
min_final_cltv_expiry_delta)
}
- /// Legacy version of [`create_inbound_payment`]. Use this method if you wish to share
- /// serialized state with LDK node(s) running 0.0.103 and earlier.
- ///
- /// May panic if `invoice_expiry_delta_secs` is greater than one year.
- ///
- /// # Note
- /// This method is deprecated and will be removed soon.
- ///
- /// [`create_inbound_payment`]: Self::create_inbound_payment
- #[deprecated]
- pub fn create_inbound_payment_legacy(&self, min_value_msat: Option<u64>, invoice_expiry_delta_secs: u32) -> Result<(PaymentHash, PaymentSecret), APIError> {
- let payment_preimage = PaymentPreimage(self.entropy_source.get_secure_random_bytes());
- let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
- let payment_secret = self.set_payment_hash_secret_map(payment_hash, Some(payment_preimage), min_value_msat, invoice_expiry_delta_secs)?;
- Ok((payment_hash, payment_secret))
- }
-
/// Gets a [`PaymentSecret`] for a given [`PaymentHash`], for which the payment preimage is
/// stored external to LDK.
///
min_final_cltv_expiry)
}
- /// Legacy version of [`create_inbound_payment_for_hash`]. Use this method if you wish to share
- /// serialized state with LDK node(s) running 0.0.103 and earlier.
- ///
- /// May panic if `invoice_expiry_delta_secs` is greater than one year.
- ///
- /// # Note
- /// This method is deprecated and will be removed soon.
- ///
- /// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash
- #[deprecated]
- pub fn create_inbound_payment_for_hash_legacy(&self, payment_hash: PaymentHash, min_value_msat: Option<u64>, invoice_expiry_delta_secs: u32) -> Result<PaymentSecret, APIError> {
- self.set_payment_hash_secret_map(payment_hash, None, min_value_msat, invoice_expiry_delta_secs)
- }
-
/// Gets an LDK-generated payment preimage from a payment hash and payment secret that were
/// previously returned from [`create_inbound_payment`].
///
inflight_htlcs
}
- #[cfg(any(test, fuzzing, feature = "_test_utils"))]
+ #[cfg(any(test, feature = "_test_utils"))]
pub fn get_and_clear_pending_events(&self) -> Vec<events::Event> {
let events = core::cell::RefCell::new(Vec::new());
let event_handler = |event: events::Event| events.borrow_mut().push(event);
/// When something which was blocking a channel from updating its [`ChannelMonitor`] (e.g. an
/// [`Event`] being handled) completes, this should be called to restore the channel to normal
/// operation. It will double-check that nothing *else* is also blocking the same channel from
- /// making progress and then any blocked [`ChannelMonitorUpdate`]s fly.
+ /// making progress and then let any blocked [`ChannelMonitorUpdate`]s fly.
fn handle_monitor_update_release(&self, counterparty_node_id: PublicKey, channel_funding_outpoint: OutPoint, mut completed_blocker: Option<RAAMonitorUpdateBlockingAction>) {
let mut errors = Vec::new();
loop {
if let Some((monitor_update, further_update_exists)) = chan.get_mut().unblock_next_blocked_monitor_update() {
log_debug!(self.logger, "Unlocking monitor updating for channel {} and updating monitor",
log_bytes!(&channel_funding_outpoint.to_channel_id()[..]));
- let update_res = self.chain_monitor.update_channel(channel_funding_outpoint, monitor_update);
- let update_id = monitor_update.update_id;
- if let Err(e) = handle_new_monitor_update!(self, update_res, update_id,
+ if let Err(e) = handle_new_monitor_update!(self, channel_funding_outpoint, monitor_update,
peer_state_lck, peer_state, per_peer_state, chan)
{
errors.push((e, counterparty_node_id));
provided_node_features(&self.default_configuration)
}
- /// Fetches the set of [`InvoiceFeatures`] flags which are provided by or required by
+ /// Fetches the set of [`Bolt11InvoiceFeatures`] flags which are provided by or required by
/// [`ChannelManager`].
///
/// Note that the invoice feature flags can vary depending on if the invoice is a "phantom invoice"
/// or not. Thus, this method is not public.
#[cfg(any(feature = "_test_utils", test))]
- pub fn invoice_features(&self) -> InvoiceFeatures {
+ pub fn invoice_features(&self) -> Bolt11InvoiceFeatures {
provided_invoice_features(&self.default_configuration)
}
inbound_v1_channel_by_id: HashMap::new(),
latest_features: init_msg.features.clone(),
pending_msg_events: Vec::new(),
+ in_flight_monitor_updates: BTreeMap::new(),
monitor_update_blocked_actions: BTreeMap::new(),
actions_blocking_raa_monitor_updates: BTreeMap::new(),
is_connected: true,
log_debug!(self.logger, "Generating channel_reestablish events for {}", log_pubkey!(counterparty_node_id));
let per_peer_state = self.per_peer_state.read().unwrap();
- for (_cp_id, peer_state_mutex) in per_peer_state.iter() {
+ if let Some(peer_state_mutex) = per_peer_state.get(counterparty_node_id) {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
let pending_msg_events = &mut peer_state.pending_msg_events;
- peer_state.channel_by_id.retain(|_, chan| {
- let retain = if chan.context.get_counterparty_node_id() == *counterparty_node_id {
- if !chan.context.have_received_message() {
- // If we created this (outbound) channel while we were disconnected from the
- // peer we probably failed to send the open_channel message, which is now
- // lost. We can't have had anything pending related to this channel, so we just
- // drop it.
- false
- } else {
- pending_msg_events.push(events::MessageSendEvent::SendChannelReestablish {
- node_id: chan.context.get_counterparty_node_id(),
- msg: chan.get_channel_reestablish(&self.logger),
- });
- true
- }
- } else { true };
- if retain && chan.context.get_counterparty_node_id() != *counterparty_node_id {
- if let Some(msg) = chan.get_signed_channel_announcement(&self.node_signer, self.genesis_hash.clone(), self.best_block.read().unwrap().height(), &self.default_configuration) {
- if let Ok(update_msg) = self.get_channel_update_for_broadcast(chan) {
- pending_msg_events.push(events::MessageSendEvent::SendChannelAnnouncement {
- node_id: *counterparty_node_id,
- msg, update_msg,
- });
- }
- }
- }
- retain
+
+ // Since unfunded channel maps are cleared upon disconnecting a peer, and they're not persisted
+ // (so won't be recovered after a crash) we don't need to bother closing unfunded channels and
+ // clearing their maps here. Instead we can just send queue channel_reestablish messages for
+ // channels in the channel_by_id map.
+ peer_state.channel_by_id.iter_mut().for_each(|(_, chan)| {
+ pending_msg_events.push(events::MessageSendEvent::SendChannelReestablish {
+ node_id: chan.context.get_counterparty_node_id(),
+ msg: chan.get_channel_reestablish(&self.logger),
+ });
});
}
//TODO: Also re-broadcast announcement_signatures
let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
let peer_state = &mut *peer_state_lock;
if let Some(chan) = peer_state.outbound_v1_channel_by_id.get_mut(&msg.channel_id) {
- if let Ok(msg) = chan.maybe_handle_error_without_close(self.genesis_hash) {
+ if let Ok(msg) = chan.maybe_handle_error_without_close(self.genesis_hash, &self.fee_estimator) {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: *counterparty_node_id,
msg,
provided_init_features(config).to_context()
}
-/// Fetches the set of [`InvoiceFeatures`] flags which are provided by or required by
+/// Fetches the set of [`Bolt11InvoiceFeatures`] flags which are provided by or required by
/// [`ChannelManager`].
///
/// Note that the invoice feature flags can vary depending on if the invoice is a "phantom invoice"
/// or not. Thus, this method is not public.
#[cfg(any(feature = "_test_utils", test))]
-pub(crate) fn provided_invoice_features(config: &UserConfig) -> InvoiceFeatures {
+pub(crate) fn provided_invoice_features(config: &UserConfig) -> Bolt11InvoiceFeatures {
provided_init_features(config).to_context()
}
/// Fetches the set of [`InitFeatures`] flags which are provided by or required by
/// [`ChannelManager`].
-pub fn provided_init_features(_config: &UserConfig) -> InitFeatures {
+pub fn provided_init_features(config: &UserConfig) -> InitFeatures {
// Note that if new features are added here which other peers may (eventually) require, we
// should also add the corresponding (optional) bit to the [`ChannelMessageHandler`] impl for
// [`ErroringMessageHandler`].
features.set_channel_type_optional();
features.set_scid_privacy_optional();
features.set_zero_conf_optional();
- #[cfg(anchors)]
- { // Attributes are not allowed on if expressions on our current MSRV of 1.41.
- if _config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx {
- features.set_anchors_zero_fee_htlc_tx_optional();
- }
+ if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx {
+ features.set_anchors_zero_fee_htlc_tx_optional();
}
features
}
(35, self.inbound_htlc_maximum_msat, option),
(37, user_channel_id_high_opt, option),
(39, self.feerate_sat_per_1000_weight, option),
+ (41, self.channel_shutdown_state, option),
});
Ok(())
}
(35, inbound_htlc_maximum_msat, option),
(37, user_channel_id_high_opt, option),
(39, feerate_sat_per_1000_weight, option),
+ (41, channel_shutdown_state, option),
});
// `user_channel_id` used to be a single u64 value. In order to remain backwards compatible with
inbound_htlc_minimum_msat,
inbound_htlc_maximum_msat,
feerate_sat_per_1000_weight,
+ channel_shutdown_state,
})
}
}
impl_writeable_tlv_based!(PhantomRouteHints, {
- (2, channels, vec_type),
+ (2, channels, required_vec),
(4, phantom_scid, required),
(6, real_node_pubkey, required),
});
(6, outgoing_amt_msat, required),
(8, outgoing_cltv_value, required),
(9, incoming_amt_msat, option),
+ (10, skimmed_fee_msat, option),
});
(5, self.total_value_received, option),
(6, self.cltv_expiry, required),
(8, keysend_preimage, option),
+ (10, self.counterparty_skimmed_fee_msat, option),
});
Ok(())
}
impl Readable for ClaimableHTLC {
fn read<R: Read>(reader: &mut R) -> Result<Self, DecodeError> {
- let mut prev_hop = crate::util::ser::RequiredWrapper(None);
- let mut value = 0;
- let mut sender_intended_value = None;
- let mut payment_data: Option<msgs::FinalOnionHopData> = None;
- let mut cltv_expiry = 0;
- let mut total_value_received = None;
- let mut total_msat = None;
- let mut keysend_preimage: Option<PaymentPreimage> = None;
- read_tlv_fields!(reader, {
+ _init_and_read_tlv_fields!(reader, {
(0, prev_hop, required),
(1, total_msat, option),
- (2, value, required),
+ (2, value_ser, required),
(3, sender_intended_value, option),
- (4, payment_data, option),
+ (4, payment_data_opt, option),
(5, total_value_received, option),
(6, cltv_expiry, required),
- (8, keysend_preimage, option)
+ (8, keysend_preimage, option),
+ (10, counterparty_skimmed_fee_msat, option),
});
+ let payment_data: Option<msgs::FinalOnionHopData> = payment_data_opt;
+ let value = value_ser.0.unwrap();
let onion_payload = match keysend_preimage {
Some(p) => {
if payment_data.is_some() {
total_value_received,
total_msat: total_msat.unwrap(),
onion_payload,
- cltv_expiry,
+ cltv_expiry: cltv_expiry.0.unwrap(),
+ counterparty_skimmed_fee_msat,
})
}
}
0 => {
let mut session_priv: crate::util::ser::RequiredWrapper<SecretKey> = crate::util::ser::RequiredWrapper(None);
let mut first_hop_htlc_msat: u64 = 0;
- let mut path_hops: Option<Vec<RouteHop>> = Some(Vec::new());
+ let mut path_hops = Vec::new();
let mut payment_id = None;
let mut payment_params: Option<PaymentParameters> = None;
let mut blinded_tail: Option<BlindedTail> = None;
(0, session_priv, required),
(1, payment_id, option),
(2, first_hop_htlc_msat, required),
- (4, path_hops, vec_type),
+ (4, path_hops, required_vec),
(5, payment_params, (option: ReadableArgs, 0)),
(6, blinded_tail, option),
});
// instead.
payment_id = Some(PaymentId(*session_priv.0.unwrap().as_ref()));
}
- let path = Path { hops: path_hops.ok_or(DecodeError::InvalidValue)?, blinded_tail };
+ let path = Path { hops: path_hops, blinded_tail };
if path.hops.len() == 0 {
return Err(DecodeError::InvalidValue);
}
(1, payment_id_opt, option),
(2, first_hop_htlc_msat, required),
// 3 was previously used to write a PaymentSecret for the payment.
- (4, path.hops, vec_type),
+ (4, path.hops, required_vec),
(5, None::<PaymentParameters>, option), // payment_params in LDK versions prior to 0.0.115
(6, path.blinded_tail, option),
});
pending_claiming_payments = None;
}
+ let mut in_flight_monitor_updates: Option<HashMap<(&PublicKey, &OutPoint), &Vec<ChannelMonitorUpdate>>> = None;
+ for ((counterparty_id, _), peer_state) in per_peer_state.iter().zip(peer_states.iter()) {
+ for (funding_outpoint, updates) in peer_state.in_flight_monitor_updates.iter() {
+ if !updates.is_empty() {
+ if in_flight_monitor_updates.is_none() { in_flight_monitor_updates = Some(HashMap::new()); }
+ in_flight_monitor_updates.as_mut().unwrap().insert((counterparty_id, funding_outpoint), updates);
+ }
+ }
+ }
+
write_tlv_fields!(writer, {
(1, pending_outbound_payments_no_retry, required),
(2, pending_intercepted_htlcs, option),
(6, monitor_update_blocked_actions_per_peer, option),
(7, self.fake_scid_rand_bytes, required),
(8, if events_not_backwards_compatible { Some(&*events) } else { None }, option),
- (9, htlc_purposes, vec_type),
+ (9, htlc_purposes, required_vec),
+ (10, in_flight_monitor_updates, option),
(11, self.probing_cookie_secret, required),
(13, htlc_onion_fields, optional_vec),
});
}
}
+impl_writeable_tlv_based_enum!(ChannelShutdownState,
+ (0, NotShuttingDown) => {},
+ (2, ShutdownInitiated) => {},
+ (4, ResolvingHTLCs) => {},
+ (6, NegotiatingClosingFee) => {},
+ (8, ShutdownComplete) => {}, ;
+);
+
/// Arguments for the creation of a ChannelManager that are not deserialized.
///
/// At a high-level, the process for deserializing a ChannelManager and resuming normal operation
let mut id_to_peer = HashMap::with_capacity(cmp::min(channel_count as usize, 128));
let mut short_to_chan_info = HashMap::with_capacity(cmp::min(channel_count as usize, 128));
let mut channel_closures = VecDeque::new();
- let mut pending_background_events = Vec::new();
+ let mut close_background_events = Vec::new();
for _ in 0..channel_count {
let mut channel: Channel<<SP::Target as SignerProvider>::Signer> = Channel::read(reader, (
&args.entropy_source, &args.signer_provider, best_block_height, &provided_channel_type_features(&args.default_config)
let funding_txo = channel.context.get_funding_txo().ok_or(DecodeError::InvalidValue)?;
funding_txo_set.insert(funding_txo.clone());
if let Some(ref mut monitor) = args.channel_monitors.get_mut(&funding_txo) {
- if channel.get_latest_complete_monitor_update_id() > monitor.get_latest_update_id() {
- // If the channel is ahead of the monitor, return InvalidValue:
- log_error!(args.logger, "A ChannelMonitor is stale compared to the current ChannelManager! This indicates a potentially-critical violation of the chain::Watch API!");
- log_error!(args.logger, " The ChannelMonitor for channel {} is at update_id {} but the ChannelManager is at update_id {}.",
- log_bytes!(channel.context.channel_id()), monitor.get_latest_update_id(), channel.get_latest_complete_monitor_update_id());
- log_error!(args.logger, " The chain::Watch API *requires* that monitors are persisted durably before returning,");
- log_error!(args.logger, " client applications must ensure that ChannelMonitor data is always available and the latest to avoid funds loss!");
- log_error!(args.logger, " Without the latest ChannelMonitor we cannot continue without risking funds.");
- log_error!(args.logger, " Please ensure the chain::Watch API requirements are met and file a bug report at https://github.com/lightningdevkit/rust-lightning");
- return Err(DecodeError::InvalidValue);
- } else if channel.get_cur_holder_commitment_transaction_number() > monitor.get_cur_holder_commitment_number() ||
+ if channel.get_cur_holder_commitment_transaction_number() > monitor.get_cur_holder_commitment_number() ||
channel.get_revoked_counterparty_commitment_transaction_number() > monitor.get_min_seen_secret() ||
channel.get_cur_counterparty_commitment_transaction_number() > monitor.get_cur_counterparty_commitment_number() ||
channel.context.get_latest_monitor_update_id() < monitor.get_latest_update_id() {
log_bytes!(channel.context.channel_id()), monitor.get_latest_update_id(), channel.context.get_latest_monitor_update_id());
let (monitor_update, mut new_failed_htlcs) = channel.context.force_shutdown(true);
if let Some((counterparty_node_id, funding_txo, update)) = monitor_update {
- pending_background_events.push(BackgroundEvent::MonitorUpdateRegeneratedOnStartup {
+ close_background_events.push(BackgroundEvent::MonitorUpdateRegeneratedOnStartup {
counterparty_node_id, funding_txo, update
});
}
log_info!(args.logger, "Successfully loaded channel {} at update_id {} against monitor at update id {}",
log_bytes!(channel.context.channel_id()), channel.context.get_latest_monitor_update_id(),
monitor.get_latest_update_id());
- channel.complete_all_mon_updates_through(monitor.get_latest_update_id());
if let Some(short_channel_id) = channel.context.get_short_channel_id() {
short_to_chan_info.insert(short_channel_id, (channel.context.get_counterparty_node_id(), channel.context.channel_id()));
}
update_id: CLOSED_CHANNEL_UPDATE_ID,
updates: vec![ChannelMonitorUpdateStep::ChannelForceClosed { should_broadcast: true }],
};
- pending_background_events.push(BackgroundEvent::ClosingMonitorUpdateRegeneratedOnStartup((*funding_txo, monitor_update)));
+ close_background_events.push(BackgroundEvent::ClosedMonitorUpdateRegeneratedOnStartup((*funding_txo, monitor_update)));
}
}
claimable_htlcs_list.push((payment_hash, previous_hops));
}
- let peer_count: u64 = Readable::read(reader)?;
- let mut per_peer_state = HashMap::with_capacity(cmp::min(peer_count as usize, MAX_ALLOC_SIZE/mem::size_of::<(PublicKey, Mutex<PeerState<<SP::Target as SignerProvider>::Signer>>)>()));
- for _ in 0..peer_count {
- let peer_pubkey = Readable::read(reader)?;
- let peer_state = PeerState {
- channel_by_id: peer_channels.remove(&peer_pubkey).unwrap_or(HashMap::new()),
+ let peer_state_from_chans = |channel_by_id| {
+ PeerState {
+ channel_by_id,
outbound_v1_channel_by_id: HashMap::new(),
inbound_v1_channel_by_id: HashMap::new(),
- latest_features: Readable::read(reader)?,
+ latest_features: InitFeatures::empty(),
pending_msg_events: Vec::new(),
+ in_flight_monitor_updates: BTreeMap::new(),
monitor_update_blocked_actions: BTreeMap::new(),
actions_blocking_raa_monitor_updates: BTreeMap::new(),
is_connected: false,
- };
+ }
+ };
+
+ let peer_count: u64 = Readable::read(reader)?;
+ let mut per_peer_state = HashMap::with_capacity(cmp::min(peer_count as usize, MAX_ALLOC_SIZE/mem::size_of::<(PublicKey, Mutex<PeerState<<SP::Target as SignerProvider>::Signer>>)>()));
+ for _ in 0..peer_count {
+ let peer_pubkey = Readable::read(reader)?;
+ let peer_chans = peer_channels.remove(&peer_pubkey).unwrap_or(HashMap::new());
+ let mut peer_state = peer_state_from_chans(peer_chans);
+ peer_state.latest_features = Readable::read(reader)?;
per_peer_state.insert(peer_pubkey, Mutex::new(peer_state));
}
}
}
- for (node_id, peer_mtx) in per_peer_state.iter() {
- let peer_state = peer_mtx.lock().unwrap();
- for (_, chan) in peer_state.channel_by_id.iter() {
- for update in chan.uncompleted_unblocked_mon_updates() {
- if let Some(funding_txo) = chan.context.get_funding_txo() {
- log_trace!(args.logger, "Replaying ChannelMonitorUpdate {} for channel {}",
- update.update_id, log_bytes!(funding_txo.to_channel_id()));
- pending_background_events.push(
- BackgroundEvent::MonitorUpdateRegeneratedOnStartup {
- counterparty_node_id: *node_id, funding_txo, update: update.clone(),
- });
- } else {
- return Err(DecodeError::InvalidValue);
- }
- }
- }
- }
-
let _last_node_announcement_serial: u32 = Readable::read(reader)?; // Only used < 0.0.111
let highest_seen_timestamp: u32 = Readable::read(reader)?;
let mut pending_claiming_payments = Some(HashMap::new());
let mut monitor_update_blocked_actions_per_peer: Option<Vec<(_, BTreeMap<_, Vec<_>>)>> = Some(Vec::new());
let mut events_override = None;
+ let mut in_flight_monitor_updates: Option<HashMap<(PublicKey, OutPoint), Vec<ChannelMonitorUpdate>>> = None;
read_tlv_fields!(reader, {
(1, pending_outbound_payments_no_retry, option),
(2, pending_intercepted_htlcs, option),
(6, monitor_update_blocked_actions_per_peer, option),
(7, fake_scid_rand_bytes, option),
(8, events_override, option),
- (9, claimable_htlc_purposes, vec_type),
+ (9, claimable_htlc_purposes, optional_vec),
+ (10, in_flight_monitor_updates, option),
(11, probing_cookie_secret, option),
(13, claimable_htlc_onion_fields, optional_vec),
});
retry_lock: Mutex::new(())
};
+ // We have to replay (or skip, if they were completed after we wrote the `ChannelManager`)
+ // each `ChannelMonitorUpdate` in `in_flight_monitor_updates`. After doing so, we have to
+ // check that each channel we have isn't newer than the latest `ChannelMonitorUpdate`(s) we
+ // replayed, and for each monitor update we have to replay we have to ensure there's a
+ // `ChannelMonitor` for it.
+ //
+ // In order to do so we first walk all of our live channels (so that we can check their
+ // state immediately after doing the update replays, when we have the `update_id`s
+ // available) and then walk any remaining in-flight updates.
+ //
+ // Because the actual handling of the in-flight updates is the same, it's macro'ized here:
+ let mut pending_background_events = Vec::new();
+ macro_rules! handle_in_flight_updates {
+ ($counterparty_node_id: expr, $chan_in_flight_upds: expr, $funding_txo: expr,
+ $monitor: expr, $peer_state: expr, $channel_info_log: expr
+ ) => { {
+ let mut max_in_flight_update_id = 0;
+ $chan_in_flight_upds.retain(|upd| upd.update_id > $monitor.get_latest_update_id());
+ for update in $chan_in_flight_upds.iter() {
+ log_trace!(args.logger, "Replaying ChannelMonitorUpdate {} for {}channel {}",
+ update.update_id, $channel_info_log, log_bytes!($funding_txo.to_channel_id()));
+ max_in_flight_update_id = cmp::max(max_in_flight_update_id, update.update_id);
+ pending_background_events.push(
+ BackgroundEvent::MonitorUpdateRegeneratedOnStartup {
+ counterparty_node_id: $counterparty_node_id,
+ funding_txo: $funding_txo,
+ update: update.clone(),
+ });
+ }
+ if $chan_in_flight_upds.is_empty() {
+ // We had some updates to apply, but it turns out they had completed before we
+ // were serialized, we just weren't notified of that. Thus, we may have to run
+ // the completion actions for any monitor updates, but otherwise are done.
+ pending_background_events.push(
+ BackgroundEvent::MonitorUpdatesComplete {
+ counterparty_node_id: $counterparty_node_id,
+ channel_id: $funding_txo.to_channel_id(),
+ });
+ }
+ if $peer_state.in_flight_monitor_updates.insert($funding_txo, $chan_in_flight_upds).is_some() {
+ log_error!(args.logger, "Duplicate in-flight monitor update set for the same channel!");
+ return Err(DecodeError::InvalidValue);
+ }
+ max_in_flight_update_id
+ } }
+ }
+
+ for (counterparty_id, peer_state_mtx) in per_peer_state.iter_mut() {
+ let mut peer_state_lock = peer_state_mtx.lock().unwrap();
+ let peer_state = &mut *peer_state_lock;
+ for (_, chan) in peer_state.channel_by_id.iter() {
+ // Channels that were persisted have to be funded, otherwise they should have been
+ // discarded.
+ let funding_txo = chan.context.get_funding_txo().ok_or(DecodeError::InvalidValue)?;
+ let monitor = args.channel_monitors.get(&funding_txo)
+ .expect("We already checked for monitor presence when loading channels");
+ let mut max_in_flight_update_id = monitor.get_latest_update_id();
+ if let Some(in_flight_upds) = &mut in_flight_monitor_updates {
+ if let Some(mut chan_in_flight_upds) = in_flight_upds.remove(&(*counterparty_id, funding_txo)) {
+ max_in_flight_update_id = cmp::max(max_in_flight_update_id,
+ handle_in_flight_updates!(*counterparty_id, chan_in_flight_upds,
+ funding_txo, monitor, peer_state, ""));
+ }
+ }
+ if chan.get_latest_unblocked_monitor_update_id() > max_in_flight_update_id {
+ // If the channel is ahead of the monitor, return InvalidValue:
+ log_error!(args.logger, "A ChannelMonitor is stale compared to the current ChannelManager! This indicates a potentially-critical violation of the chain::Watch API!");
+ log_error!(args.logger, " The ChannelMonitor for channel {} is at update_id {} with update_id through {} in-flight",
+ log_bytes!(chan.context.channel_id()), monitor.get_latest_update_id(), max_in_flight_update_id);
+ log_error!(args.logger, " but the ChannelManager is at update_id {}.", chan.get_latest_unblocked_monitor_update_id());
+ log_error!(args.logger, " The chain::Watch API *requires* that monitors are persisted durably before returning,");
+ log_error!(args.logger, " client applications must ensure that ChannelMonitor data is always available and the latest to avoid funds loss!");
+ log_error!(args.logger, " Without the latest ChannelMonitor we cannot continue without risking funds.");
+ log_error!(args.logger, " Please ensure the chain::Watch API requirements are met and file a bug report at https://github.com/lightningdevkit/rust-lightning");
+ return Err(DecodeError::InvalidValue);
+ }
+ }
+ }
+
+ if let Some(in_flight_upds) = in_flight_monitor_updates {
+ for ((counterparty_id, funding_txo), mut chan_in_flight_updates) in in_flight_upds {
+ if let Some(monitor) = args.channel_monitors.get(&funding_txo) {
+ // Now that we've removed all the in-flight monitor updates for channels that are
+ // still open, we need to replay any monitor updates that are for closed channels,
+ // creating the neccessary peer_state entries as we go.
+ let peer_state_mutex = per_peer_state.entry(counterparty_id).or_insert_with(|| {
+ Mutex::new(peer_state_from_chans(HashMap::new()))
+ });
+ let mut peer_state = peer_state_mutex.lock().unwrap();
+ handle_in_flight_updates!(counterparty_id, chan_in_flight_updates,
+ funding_txo, monitor, peer_state, "closed ");
+ } else {
+ log_error!(args.logger, "A ChannelMonitor is missing even though we have in-flight updates for it! This indicates a potentially-critical violation of the chain::Watch API!");
+ log_error!(args.logger, " The ChannelMonitor for channel {} is missing.",
+ log_bytes!(funding_txo.to_channel_id()));
+ log_error!(args.logger, " The chain::Watch API *requires* that monitors are persisted durably before returning,");
+ log_error!(args.logger, " client applications must ensure that ChannelMonitor data is always available and the latest to avoid funds loss!");
+ log_error!(args.logger, " Without the latest ChannelMonitor we cannot continue without risking funds.");
+ log_error!(args.logger, " Please ensure the chain::Watch API requirements are met and file a bug report at https://github.com/lightningdevkit/rust-lightning");
+ return Err(DecodeError::InvalidValue);
+ }
+ }
+ }
+
+ // Note that we have to do the above replays before we push new monitor updates.
+ pending_background_events.append(&mut close_background_events);
+
+ // If there's any preimages for forwarded HTLCs hanging around in ChannelMonitors we
+ // should ensure we try them again on the inbound edge. We put them here and do so after we
+ // have a fully-constructed `ChannelManager` at the end.
+ let mut pending_claims_to_replay = Vec::new();
+
{
// If we're tracking pending payments, ensure we haven't lost any by looking at the
// ChannelMonitor data for any channels for which we do not have authorative state
// We only rebuild the pending payments map if we were most recently serialized by
// 0.0.102+
for (_, monitor) in args.channel_monitors.iter() {
- if id_to_peer.get(&monitor.get_funding_txo().0.to_channel_id()).is_none() {
+ let counterparty_opt = id_to_peer.get(&monitor.get_funding_txo().0.to_channel_id());
+ if counterparty_opt.is_none() {
for (htlc_source, (htlc, _)) in monitor.get_pending_or_resolved_outbound_htlcs() {
if let HTLCSource::OutboundRoute { payment_id, session_priv, path, .. } = htlc_source {
if path.hops.is_empty() {
}
}
}
+
+ // Whether the downstream channel was closed or not, try to re-apply any payment
+ // preimages from it which may be needed in upstream channels for forwarded
+ // payments.
+ let outbound_claimed_htlcs_iter = monitor.get_all_current_outbound_htlcs()
+ .into_iter()
+ .filter_map(|(htlc_source, (htlc, preimage_opt))| {
+ if let HTLCSource::PreviousHopData(_) = htlc_source {
+ if let Some(payment_preimage) = preimage_opt {
+ Some((htlc_source, payment_preimage, htlc.amount_msat,
+ // Check if `counterparty_opt.is_none()` to see if the
+ // downstream chan is closed (because we don't have a
+ // channel_id -> peer map entry).
+ counterparty_opt.is_none(),
+ monitor.get_funding_txo().0.to_channel_id()))
+ } else { None }
+ } else {
+ // If it was an outbound payment, we've handled it above - if a preimage
+ // came in and we persisted the `ChannelManager` we either handled it and
+ // are good to go or the channel force-closed - we don't have to handle the
+ // channel still live case here.
+ None
+ }
+ });
+ for tuple in outbound_claimed_htlcs_iter {
+ pending_claims_to_replay.push(tuple);
+ }
}
}
blocked_peer_state.lock().unwrap().actions_blocking_raa_monitor_updates
.entry(blocked_channel_outpoint.to_channel_id())
.or_insert_with(Vec::new).push(blocking_action.clone());
+ } else {
+ // If the channel we were blocking has closed, we don't need to
+ // worry about it - the blocked monitor update should never have
+ // been released from the `Channel` object so it can't have
+ // completed, and if the channel closed there's no reason to bother
+ // anymore.
}
}
}
pending_events_processor: AtomicBool::new(false),
pending_background_events: Mutex::new(pending_background_events),
total_consistency_lock: RwLock::new(()),
- #[cfg(debug_assertions)]
background_events_processed_since_startup: AtomicBool::new(false),
persistence_notifier: Notifier::new(),
channel_manager.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver);
}
+ for (source, preimage, downstream_value, downstream_closed, downstream_chan_id) in pending_claims_to_replay {
+ // We use `downstream_closed` in place of `from_onchain` here just as a guess - we
+ // don't remember in the `ChannelMonitor` where we got a preimage from, but if the
+ // channel is closed we just assume that it probably came from an on-chain claim.
+ channel_manager.claim_funds_internal(source, preimage, Some(downstream_value),
+ downstream_closed, downstream_chan_id);
+ }
+
//TODO: Broadcast channel update for closed channels, but only after we've made a
//connection or two.
use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
use crate::ln::channelmanager::{inbound_payment, PaymentId, PaymentSendFailure, RecipientOnionFields, InterceptId};
use crate::ln::functional_test_utils::*;
- use crate::ln::msgs;
+ use crate::ln::msgs::{self, ErrorAction};
use crate::ln::msgs::ChannelMessageHandler;
use crate::routing::router::{PaymentParameters, RouteParameters, find_route};
use crate::util::errors::APIError;
get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, last_random_pk);
}
- #[cfg(anchors)]
+ #[test]
+ fn reject_excessively_underpaying_htlcs() {
+ let chanmon_cfg = create_chanmon_cfgs(1);
+ let node_cfg = create_node_cfgs(1, &chanmon_cfg);
+ let node_chanmgr = create_node_chanmgrs(1, &node_cfg, &[None]);
+ let node = create_network(1, &node_cfg, &node_chanmgr);
+ let sender_intended_amt_msat = 100;
+ let extra_fee_msat = 10;
+ let hop_data = msgs::OnionHopData {
+ amt_to_forward: 100,
+ outgoing_cltv_value: 42,
+ format: msgs::OnionHopDataFormat::FinalNode {
+ keysend_preimage: None,
+ payment_metadata: None,
+ payment_data: Some(msgs::FinalOnionHopData {
+ payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
+ }),
+ }
+ };
+ // Check that if the amount we received + the penultimate hop extra fee is less than the sender
+ // intended amount, we fail the payment.
+ if let Err(crate::ln::channelmanager::ReceiveError { err_code, .. }) =
+ node[0].node.construct_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]),
+ sender_intended_amt_msat - extra_fee_msat - 1, 42, None, true, Some(extra_fee_msat))
+ {
+ assert_eq!(err_code, 19);
+ } else { panic!(); }
+
+ // If amt_received + extra_fee is equal to the sender intended amount, we're fine.
+ let hop_data = msgs::OnionHopData { // This is the same hop_data as above, OnionHopData doesn't implement Clone
+ amt_to_forward: 100,
+ outgoing_cltv_value: 42,
+ format: msgs::OnionHopDataFormat::FinalNode {
+ keysend_preimage: None,
+ payment_metadata: None,
+ payment_data: Some(msgs::FinalOnionHopData {
+ payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
+ }),
+ }
+ };
+ assert!(node[0].node.construct_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]),
+ sender_intended_amt_msat - extra_fee_msat, 42, None, true, Some(extra_fee_msat)).is_ok());
+ }
+
+ #[test]
+ fn test_inbound_anchors_manual_acceptance() {
+ // Tests that we properly limit inbound channels when we have the manual-channel-acceptance
+ // flag set and (sometimes) accept channels as 0conf.
+ let mut anchors_cfg = test_default_channel_config();
+ anchors_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
+
+ let mut anchors_manual_accept_cfg = anchors_cfg.clone();
+ anchors_manual_accept_cfg.manually_accept_inbound_channels = true;
+
+ let chanmon_cfgs = create_chanmon_cfgs(3);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs,
+ &[Some(anchors_cfg.clone()), Some(anchors_cfg.clone()), Some(anchors_manual_accept_cfg.clone())]);
+ let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+
+ nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 0, 42, None).unwrap();
+ let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
+
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_msg);
+ assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
+ let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
+ match &msg_events[0] {
+ MessageSendEvent::HandleError { node_id, action } => {
+ assert_eq!(*node_id, nodes[0].node.get_our_node_id());
+ match action {
+ ErrorAction::SendErrorMessage { msg } =>
+ assert_eq!(msg.data, "No channels with anchor outputs accepted".to_owned()),
+ _ => panic!("Unexpected error action"),
+ }
+ }
+ _ => panic!("Unexpected event"),
+ }
+
+ nodes[2].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_msg);
+ let events = nodes[2].node.get_and_clear_pending_events();
+ match events[0] {
+ Event::OpenChannelRequest { temporary_channel_id, .. } =>
+ nodes[2].node.accept_inbound_channel(&temporary_channel_id, &nodes[0].node.get_our_node_id(), 23).unwrap(),
+ _ => panic!("Unexpected event"),
+ }
+ get_event_msg!(nodes[2], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
+ }
+
#[test]
fn test_anchors_zero_fee_htlc_tx_fallback() {
// Tests that if both nodes support anchors, but the remote node does not want to accept
MessageSendEvent::BroadcastChannelUpdate { .. } => {},
_ => panic!("expected BroadcastChannelUpdate event"),
}
+
+ // If we provide a channel_id not associated with the peer, we should get an error and no updates
+ // should be applied to ensure update atomicity as specified in the API docs.
+ let bad_channel_id = [10; 32];
+ let current_fee = nodes[0].node.list_channels()[0].config.unwrap().forwarding_fee_proportional_millionths;
+ let new_fee = current_fee + 100;
+ assert!(
+ matches!(
+ nodes[0].node.update_partial_channel_config(&channel.counterparty.node_id, &[channel.channel_id, bad_channel_id], &ChannelConfigUpdate {
+ forwarding_fee_proportional_millionths: Some(new_fee),
+ ..Default::default()
+ }),
+ Err(APIError::ChannelUnavailable { err: _ }),
+ )
+ );
+ // Check that the fee hasn't changed for the channel that exists.
+ assert_eq!(nodes[0].node.list_channels()[0].config.unwrap().forwarding_fee_proportional_millionths, current_fee);
+ let events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(events.len(), 0);
}
}
use crate::routing::gossip::NetworkGraph;
use crate::routing::router::{PaymentParameters, RouteParameters};
use crate::util::test_utils;
- use crate::util::config::UserConfig;
+ use crate::util::config::{UserConfig, MaxDustHTLCExposure};
use bitcoin::hashes::Hash;
use bitcoin::hashes::sha256::Hash as Sha256;
// Note that this is unrealistic as each payment send will require at least two fsync
// calls per node.
let network = bitcoin::Network::Testnet;
+ let genesis_block = bitcoin::blockdata::constants::genesis_block(network);
let tx_broadcaster = test_utils::TestBroadcaster::new(network);
let fee_estimator = test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) };
let router = test_utils::TestRouter::new(Arc::new(NetworkGraph::new(network, &logger_a)), &scorer);
let mut config: UserConfig = Default::default();
+ config.channel_config.max_dust_htlc_exposure = MaxDustHTLCExposure::FeeRateMultiplier(5_000_000 / 253);
config.channel_handshake_config.minimum_depth = 1;
let chain_monitor_a = ChainMonitor::new(None, &tx_broadcaster, &logger_a, &fee_estimator, &persister_a);
let node_a = ChannelManager::new(&fee_estimator, &chain_monitor_a, &tx_broadcaster, &router, &logger_a, &keys_manager_a, &keys_manager_a, &keys_manager_a, config.clone(), ChainParameters {
network,
best_block: BestBlock::from_network(network),
- });
+ }, genesis_block.header.time);
let node_a_holder = ANodeHolder { node: &node_a };
let logger_b = test_utils::TestLogger::with_id("node a".to_owned());
let node_b = ChannelManager::new(&fee_estimator, &chain_monitor_b, &tx_broadcaster, &router, &logger_b, &keys_manager_b, &keys_manager_b, &keys_manager_b, config.clone(), ChainParameters {
network,
best_block: BestBlock::from_network(network),
- });
+ }, genesis_block.header.time);
let node_b_holder = ANodeHolder { node: &node_b };
node_a.peer_connected(&node_b.get_our_node_id(), &Init {
//! [BOLT-3](https://github.com/lightning/bolts/blob/master/03-transactions.md) for more
//! information).
//!
+//! LDK knows about the following features, but does not support them:
+//! - `AnchorsNonzeroFeeHtlcTx` - the initial version of anchor outputs, which was later found to be
+//! vulnerable (see this
+//! [mailing list post](https://lists.linuxfoundation.org/pipermail/lightning-dev/2020-September/002796.html)
+//! for more information).
+//!
//! [BOLT #9]: https://github.com/lightning/bolts/blob/master/09-features.md
//! [messages]: crate::ln::msgs
use crate::{io, io_extras};
use crate::prelude::*;
use core::{cmp, fmt};
+use core::borrow::Borrow;
use core::hash::{Hash, Hasher};
use core::marker::PhantomData;
// Byte 1
VariableLengthOnion | StaticRemoteKey | PaymentSecret,
// Byte 2
- BasicMPP | Wumbo | AnchorsZeroFeeHtlcTx,
+ BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx,
// Byte 3
ShutdownAnySegwit,
// Byte 4
// Byte 1
VariableLengthOnion | StaticRemoteKey | PaymentSecret,
// Byte 2
- BasicMPP | Wumbo | AnchorsZeroFeeHtlcTx,
+ BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx,
// Byte 3
ShutdownAnySegwit,
// Byte 4
ZeroConf | Keysend,
]);
define_context!(ChannelContext, []);
- define_context!(InvoiceContext, [
+ define_context!(Bolt11InvoiceContext, [
// Byte 0
,
// Byte 1
// Byte 1
StaticRemoteKey,
// Byte 2
- AnchorsZeroFeeHtlcTx,
+ AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx,
// Byte 3
,
// Byte 4
define_feature!(7, GossipQueries, [InitContext, NodeContext],
"Feature flags for `gossip_queries`.", set_gossip_queries_optional, set_gossip_queries_required,
supports_gossip_queries, requires_gossip_queries);
- define_feature!(9, VariableLengthOnion, [InitContext, NodeContext, InvoiceContext],
+ define_feature!(9, VariableLengthOnion, [InitContext, NodeContext, Bolt11InvoiceContext],
"Feature flags for `var_onion_optin`.", set_variable_length_onion_optional,
set_variable_length_onion_required, supports_variable_length_onion,
requires_variable_length_onion);
define_feature!(13, StaticRemoteKey, [InitContext, NodeContext, ChannelTypeContext],
"Feature flags for `option_static_remotekey`.", set_static_remote_key_optional,
set_static_remote_key_required, supports_static_remote_key, requires_static_remote_key);
- define_feature!(15, PaymentSecret, [InitContext, NodeContext, InvoiceContext],
+ define_feature!(15, PaymentSecret, [InitContext, NodeContext, Bolt11InvoiceContext],
"Feature flags for `payment_secret`.", set_payment_secret_optional, set_payment_secret_required,
supports_payment_secret, requires_payment_secret);
- define_feature!(17, BasicMPP, [InitContext, NodeContext, InvoiceContext, Bolt12InvoiceContext],
+ define_feature!(17, BasicMPP, [InitContext, NodeContext, Bolt11InvoiceContext, Bolt12InvoiceContext],
"Feature flags for `basic_mpp`.", set_basic_mpp_optional, set_basic_mpp_required,
supports_basic_mpp, requires_basic_mpp);
define_feature!(19, Wumbo, [InitContext, NodeContext],
"Feature flags for `option_support_large_channel` (aka wumbo channels).", set_wumbo_optional, set_wumbo_required,
supports_wumbo, requires_wumbo);
+ define_feature!(21, AnchorsNonzeroFeeHtlcTx, [InitContext, NodeContext, ChannelTypeContext],
+ "Feature flags for `option_anchors_nonzero_fee_htlc_tx`.", set_anchors_nonzero_fee_htlc_tx_optional,
+ set_anchors_nonzero_fee_htlc_tx_required, supports_anchors_nonzero_fee_htlc_tx, requires_anchors_nonzero_fee_htlc_tx);
define_feature!(23, AnchorsZeroFeeHtlcTx, [InitContext, NodeContext, ChannelTypeContext],
"Feature flags for `option_anchors_zero_fee_htlc_tx`.", set_anchors_zero_fee_htlc_tx_optional,
set_anchors_zero_fee_htlc_tx_required, supports_anchors_zero_fee_htlc_tx, requires_anchors_zero_fee_htlc_tx);
define_feature!(47, SCIDPrivacy, [InitContext, NodeContext, ChannelTypeContext],
"Feature flags for only forwarding with SCID aliasing. Called `option_scid_alias` in the BOLTs",
set_scid_privacy_optional, set_scid_privacy_required, supports_scid_privacy, requires_scid_privacy);
- define_feature!(49, PaymentMetadata, [InvoiceContext],
+ define_feature!(49, PaymentMetadata, [Bolt11InvoiceContext],
"Feature flags for payment metadata in invoices.", set_payment_metadata_optional,
set_payment_metadata_required, supports_payment_metadata, requires_payment_metadata);
define_feature!(51, ZeroConf, [InitContext, NodeContext, ChannelTypeContext],
#[cfg(test)]
define_feature!(123456789, UnknownFeature,
- [NodeContext, ChannelContext, InvoiceContext, OfferContext, InvoiceRequestContext, Bolt12InvoiceContext, BlindedHopContext],
+ [NodeContext, ChannelContext, Bolt11InvoiceContext, OfferContext, InvoiceRequestContext, Bolt12InvoiceContext, BlindedHopContext],
"Feature flags for an unknown feature used in testing.", set_unknown_feature_optional,
set_unknown_feature_required, supports_unknown_test_feature, requires_unknown_test_feature);
}
mark: PhantomData<T>,
}
+impl<T: sealed::Context, Rhs: Borrow<Self>> core::ops::BitOrAssign<Rhs> for Features<T> {
+ fn bitor_assign(&mut self, rhs: Rhs) {
+ let total_feature_len = cmp::max(self.flags.len(), rhs.borrow().flags.len());
+ self.flags.resize(total_feature_len, 0u8);
+ for (byte, rhs_byte) in self.flags.iter_mut().zip(rhs.borrow().flags.iter()) {
+ *byte |= *rhs_byte;
+ }
+ }
+}
+
impl<T: sealed::Context> core::ops::BitOr for Features<T> {
type Output = Self;
fn bitor(mut self, o: Self) -> Self {
- let total_feature_len = cmp::max(self.flags.len(), o.flags.len());
- self.flags.resize(total_feature_len, 0u8);
- for (byte, o_byte) in self.flags.iter_mut().zip(o.flags.iter()) {
- *byte |= *o_byte;
- }
+ self |= o;
self
}
}
/// Features used within a `channel_announcement` message.
pub type ChannelFeatures = Features<sealed::ChannelContext>;
/// Features used within an invoice.
-pub type InvoiceFeatures = Features<sealed::InvoiceContext>;
+pub type Bolt11InvoiceFeatures = Features<sealed::Bolt11InvoiceContext>;
/// Features used within an `offer`.
pub type OfferFeatures = Features<sealed::OfferContext>;
/// Features used within an `invoice_request`.
}
}
-impl InvoiceFeatures {
- /// Converts `InvoiceFeatures` to `Features<C>`. Only known `InvoiceFeatures` relevant to
+impl Bolt11InvoiceFeatures {
+ /// Converts `Bolt11InvoiceFeatures` to `Features<C>`. Only known `Bolt11InvoiceFeatures` relevant to
/// context `C` are included in the result.
pub(crate) fn to_context<C: sealed::Context>(&self) -> Features<C> {
self.to_context_internal()
/// features (since they were not announced in a node announcement). However, keysend payments
/// don't have an invoice to pull the payee's features from, so this method is provided for use in
/// [`PaymentParameters::for_keysend`], thus omitting the need for payers to manually construct an
- /// `InvoiceFeatures` for [`find_route`].
+ /// `Bolt11InvoiceFeatures` for [`find_route`].
///
/// MPP keysend is not widely supported yet, so we parameterize support to allow the user to
/// choose whether their router should find multi-part routes.
///
/// [`PaymentParameters::for_keysend`]: crate::routing::router::PaymentParameters::for_keysend
/// [`find_route`]: crate::routing::router::find_route
- pub(crate) fn for_keysend(allow_mpp: bool) -> InvoiceFeatures {
- let mut res = InvoiceFeatures::empty();
+ pub(crate) fn for_keysend(allow_mpp: bool) -> Bolt11InvoiceFeatures {
+ let mut res = Bolt11InvoiceFeatures::empty();
res.set_variable_length_onion_optional();
if allow_mpp {
res.set_basic_mpp_optional();
}
impl Bolt12InvoiceFeatures {
- /// Converts `Bolt12InvoiceFeatures` to `Features<C>`. Only known `Bolt12InvoiceFeatures` relevant
- /// to context `C` are included in the result.
+ /// Converts [`Bolt12InvoiceFeatures`] to [`Features<C>`]. Only known [`Bolt12InvoiceFeatures`]
+ /// relevant to context `C` are included in the result.
pub(crate) fn to_context<C: sealed::Context>(&self) -> Features<C> {
self.to_context_internal()
}
<sealed::ChannelTypeContext as sealed::StaticRemoteKey>::set_required_bit(&mut ret.flags);
ret
}
+
+ /// Constructs a ChannelTypeFeatures with anchors support
+ pub(crate) fn anchors_zero_htlc_fee_and_dependencies() -> Self {
+ let mut ret = Self::empty();
+ <sealed::ChannelTypeContext as sealed::StaticRemoteKey>::set_required_bit(&mut ret.flags);
+ <sealed::ChannelTypeContext as sealed::AnchorsZeroFeeHtlcTx>::set_required_bit(&mut ret.flags);
+ ret
+ }
}
-impl ToBase32 for InvoiceFeatures {
+impl ToBase32 for Bolt11InvoiceFeatures {
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
// Explanation for the "4": the normal way to round up when dividing is to add the divisor
// minus one before dividing
}
}
-impl Base32Len for InvoiceFeatures {
+impl Base32Len for Bolt11InvoiceFeatures {
fn base32_len(&self) -> usize {
self.to_base32().len()
}
}
-impl FromBase32 for InvoiceFeatures {
+impl FromBase32 for Bolt11InvoiceFeatures {
type Err = bech32::Error;
- fn from_base32(field_data: &[u5]) -> Result<InvoiceFeatures, bech32::Error> {
+ fn from_base32(field_data: &[u5]) -> Result<Bolt11InvoiceFeatures, bech32::Error> {
// Explanation for the "7": the normal way to round up when dividing is to add the divisor
// minus one before dividing
let length_bytes = (field_data.len() * 5 + 7) / 8 as usize;
while !res_bytes.is_empty() && res_bytes[res_bytes.len() - 1] == 0 {
res_bytes.pop();
}
- Ok(InvoiceFeatures::from_le_bytes(res_bytes))
+ Ok(Bolt11InvoiceFeatures::from_le_bytes(res_bytes))
}
}
}
/// Returns true if this `Features` object contains required features unknown by `other`.
- pub fn requires_unknown_bits_from(&self, other: &Features<T>) -> bool {
+ pub fn requires_unknown_bits_from(&self, other: &Self) -> bool {
// Bitwise AND-ing with all even bits set except for known features will select required
// unknown features.
self.flags.iter().enumerate().any(|(i, &byte)| {
impl_feature_len_prefixed_write!(InitFeatures);
impl_feature_len_prefixed_write!(ChannelFeatures);
impl_feature_len_prefixed_write!(NodeFeatures);
-impl_feature_len_prefixed_write!(InvoiceFeatures);
+impl_feature_len_prefixed_write!(Bolt11InvoiceFeatures);
impl_feature_len_prefixed_write!(Bolt12InvoiceFeatures);
impl_feature_len_prefixed_write!(BlindedHopFeatures);
#[cfg(test)]
mod tests {
- use super::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, InvoiceFeatures, NodeFeatures, OfferFeatures, sealed};
+ use super::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, Bolt11InvoiceFeatures, NodeFeatures, OfferFeatures, sealed};
use bitcoin::bech32::{Base32Len, FromBase32, ToBase32, u5};
use crate::util::ser::{Readable, WithoutLength, Writeable};
fn convert_to_context_with_unknown_flags() {
// Ensure the `from` context has fewer known feature bytes than the `to` context.
assert!(<sealed::ChannelContext as sealed::Context>::KNOWN_FEATURE_MASK.len() <
- <sealed::InvoiceContext as sealed::Context>::KNOWN_FEATURE_MASK.len());
+ <sealed::Bolt11InvoiceContext as sealed::Context>::KNOWN_FEATURE_MASK.len());
let mut channel_features = ChannelFeatures::empty();
channel_features.set_unknown_feature_optional();
assert!(channel_features.supports_unknown_bits());
- let invoice_features: InvoiceFeatures = channel_features.to_context_internal();
+ let invoice_features: Bolt11InvoiceFeatures = channel_features.to_context_internal();
assert!(!invoice_features.supports_unknown_bits());
}
#[test]
fn set_feature_bits() {
- let mut features = InvoiceFeatures::empty();
+ let mut features = Bolt11InvoiceFeatures::empty();
features.set_basic_mpp_optional();
features.set_payment_secret_required();
assert!(features.supports_basic_mpp());
#[test]
fn set_custom_bits() {
- let mut features = InvoiceFeatures::empty();
+ let mut features = Bolt11InvoiceFeatures::empty();
features.set_variable_length_onion_optional();
assert_eq!(features.flags[1], 0b00000010);
assert_eq!(features.flags[31], 0b00000000);
assert_eq!(features.flags[32], 0b00000101);
- let known_bit = <sealed::InvoiceContext as sealed::PaymentSecret>::EVEN_BIT;
- let byte_offset = <sealed::InvoiceContext as sealed::PaymentSecret>::BYTE_OFFSET;
+ let known_bit = <sealed::Bolt11InvoiceContext as sealed::PaymentSecret>::EVEN_BIT;
+ let byte_offset = <sealed::Bolt11InvoiceContext as sealed::PaymentSecret>::BYTE_OFFSET;
assert_eq!(byte_offset, 1);
assert_eq!(features.flags[byte_offset], 0b00000010);
assert!(features.set_required_custom_bit(known_bit).is_err());
assert_eq!(features.flags[byte_offset], 0b00000010);
- let mut features = InvoiceFeatures::empty();
+ let mut features = Bolt11InvoiceFeatures::empty();
assert!(features.set_optional_custom_bit(256).is_ok());
assert!(features.set_optional_custom_bit(259).is_ok());
assert_eq!(features.flags[32], 0b00001010);
- let mut features = InvoiceFeatures::empty();
+ let mut features = Bolt11InvoiceFeatures::empty();
assert!(features.set_required_custom_bit(257).is_ok());
assert!(features.set_required_custom_bit(258).is_ok());
assert_eq!(features.flags[32], 0b00000101);
u5::try_from_u8(16).unwrap(),
u5::try_from_u8(1).unwrap(),
];
- let features = InvoiceFeatures::from_le_bytes(vec![1, 2, 3, 4, 5, 42, 100, 101]);
+ let features = Bolt11InvoiceFeatures::from_le_bytes(vec![1, 2, 3, 4, 5, 42, 100, 101]);
// Test length calculation.
assert_eq!(features.base32_len(), 13);
assert_eq!(features_as_u5s, features_serialized);
// Test deserialization.
- let features_deserialized = InvoiceFeatures::from_base32(&features_as_u5s).unwrap();
+ let features_deserialized = Bolt11InvoiceFeatures::from_base32(&features_as_u5s).unwrap();
assert_eq!(features, features_deserialized);
}
#[test]
fn test_channel_type_mapping() {
- // If we map an InvoiceFeatures with StaticRemoteKey optional, it should map into a
+ // If we map an Bolt11InvoiceFeatures with StaticRemoteKey optional, it should map into a
// required-StaticRemoteKey ChannelTypeFeatures.
let mut init_features = InitFeatures::empty();
init_features.set_static_remote_key_optional();
use crate::chain::channelmonitor::ChannelMonitor;
use crate::chain::transaction::OutPoint;
use crate::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, PaymentFailureReason};
+use crate::events::bump_transaction::{BumpTransactionEventHandler, Wallet, WalletSource};
use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
use crate::ln::channelmanager::{AChannelManager, ChainParameters, ChannelManager, ChannelManagerReadArgs, RAACommitmentOrder, PaymentSendFailure, RecipientOnionFields, PaymentId, MIN_CLTV_EXPIRY_DELTA};
use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate};
use crate::util::test_utils;
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
use crate::util::errors::APIError;
-use crate::util::config::UserConfig;
+use crate::util::config::{UserConfig, MaxDustHTLCExposure};
use crate::util::ser::{ReadableArgs, Writeable};
use bitcoin::blockdata::block::{Block, BlockHeader};
use bitcoin::blockdata::transaction::{Transaction, TxOut};
-use bitcoin::network::constants::Network;
-
use bitcoin::hash_types::BlockHash;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::Hash as _;
-
-use bitcoin::secp256k1::PublicKey;
+use bitcoin::network::constants::Network;
+use bitcoin::secp256k1::{PublicKey, SecretKey};
use crate::io;
use crate::prelude::*;
}
call_claimable_balances(node);
node.node.test_process_background_events();
+
+ for tx in &block.txdata {
+ for input in &tx.input {
+ node.wallet_source.remove_utxo(input.previous_output);
+ }
+ let wallet_script = node.wallet_source.get_change_script().unwrap();
+ for (idx, output) in tx.output.iter().enumerate() {
+ if output.script_pubkey == wallet_script {
+ let outpoint = bitcoin::OutPoint { txid: tx.txid(), vout: idx as u32 };
+ node.wallet_source.add_utxo(outpoint, output.value);
+ }
+ }
+ }
}
pub fn disconnect_blocks<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, count: u32) {
pub blocks: Arc<Mutex<Vec<(Block, u32)>>>,
pub connect_style: Rc<RefCell<ConnectStyle>>,
pub override_init_features: Rc<RefCell<Option<InitFeatures>>>,
+ pub wallet_source: Arc<test_utils::TestWalletSource>,
+ pub bump_tx_handler: BumpTransactionEventHandler<
+ &'c test_utils::TestBroadcaster,
+ Arc<Wallet<Arc<test_utils::TestWalletSource>, &'c test_utils::TestLogger>>,
+ &'b test_utils::TestKeysInterface,
+ &'c test_utils::TestLogger,
+ >,
}
impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
pub fn best_block_hash(&self) -> BlockHash {
}
#[cfg(test)]
-macro_rules! get_opt_anchors {
+macro_rules! get_channel_type_features {
($node: expr, $counterparty_node: expr, $channel_id: expr) => {
{
let mut per_peer_state_lock;
let mut peer_state_lock;
let chan = get_channel_ref!($node, $counterparty_node, per_peer_state_lock, peer_state_lock, $channel_id);
- chan.context.opt_anchors()
+ chan.context.get_channel_type().clone()
}
}
}
assert_eq!(open_channel_msg.temporary_channel_id, create_chan_id);
assert_eq!(node_a.node.list_channels().iter().find(|channel| channel.channel_id == create_chan_id).unwrap().user_channel_id, 42);
node_b.node.handle_open_channel(&node_a.node.get_our_node_id(), &open_channel_msg);
+ if node_b.node.get_current_default_configuration().manually_accept_inbound_channels {
+ let events = node_b.node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match &events[0] {
+ Event::OpenChannelRequest { temporary_channel_id, counterparty_node_id, .. } =>
+ node_b.node.accept_inbound_channel(temporary_channel_id, counterparty_node_id, 42).unwrap(),
+ _ => panic!("Unexpected event"),
+ };
+ }
let accept_channel_msg = get_event_msg!(node_b, MessageSendEvent::SendAcceptChannel, node_a.node.get_our_node_id());
assert_eq!(accept_channel_msg.temporary_channel_id, create_chan_id);
node_a.node.handle_accept_channel(&node_b.node.get_our_node_id(), &accept_channel_msg);
}}
}
+pub fn check_payment_claimable(
+ event: &Event, expected_payment_hash: PaymentHash, expected_payment_secret: PaymentSecret,
+ expected_recv_value: u64, expected_payment_preimage: Option<PaymentPreimage>,
+ expected_receiver_node_id: PublicKey,
+) {
+ match event {
+ Event::PaymentClaimable { ref payment_hash, ref purpose, amount_msat, receiver_node_id, .. } => {
+ assert_eq!(expected_payment_hash, *payment_hash);
+ assert_eq!(expected_recv_value, *amount_msat);
+ assert_eq!(expected_receiver_node_id, receiver_node_id.unwrap());
+ match purpose {
+ PaymentPurpose::InvoicePayment { payment_preimage, payment_secret, .. } => {
+ assert_eq!(&expected_payment_preimage, payment_preimage);
+ assert_eq!(expected_payment_secret, *payment_secret);
+ },
+ _ => {},
+ }
+ },
+ _ => panic!("Unexpected event"),
+ }
+}
+
#[macro_export]
#[cfg(any(test, ldk_bench, feature = "_test_utils"))]
macro_rules! expect_payment_claimable {
($node: expr, $expected_payment_hash: expr, $expected_payment_secret: expr, $expected_recv_value: expr, $expected_payment_preimage: expr, $expected_receiver_node_id: expr) => {
let events = $node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
- match events[0] {
- $crate::events::Event::PaymentClaimable { ref payment_hash, ref purpose, amount_msat, receiver_node_id, .. } => {
- assert_eq!($expected_payment_hash, *payment_hash);
- assert_eq!($expected_recv_value, amount_msat);
- assert_eq!($expected_receiver_node_id, receiver_node_id.unwrap());
- match purpose {
- $crate::events::PaymentPurpose::InvoicePayment { payment_preimage, payment_secret, .. } => {
- assert_eq!(&$expected_payment_preimage, payment_preimage);
- assert_eq!($expected_payment_secret, *payment_secret);
- },
- _ => {},
- }
- },
- _ => panic!("Unexpected event"),
- }
- }
+ $crate::ln::functional_test_utils::check_payment_claimable(&events[0], $expected_payment_hash, $expected_payment_secret, $expected_recv_value, $expected_payment_preimage, $expected_receiver_node_id)
+ };
}
#[macro_export]
}
}
+#[cfg(test)]
+#[macro_export]
+macro_rules! expect_channel_shutdown_state {
+ ($node: expr, $chan_id: expr, $state: path) => {
+ let chan_details = $node.node.list_channels().into_iter().filter(|cd| cd.channel_id == $chan_id).collect::<Vec<ChannelDetails>>();
+ assert_eq!(chan_details.len(), 1);
+ assert_eq!(chan_details[0].channel_shutdown_state, Some($state));
+ }
+}
+
#[cfg(any(test, ldk_bench, feature = "_test_utils"))]
pub fn expect_channel_pending_event<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, expected_counterparty_node_id: &PublicKey) {
let events = node.node.get_and_clear_pending_events();
match &events_2[0] {
Event::PaymentClaimable { ref payment_hash, ref purpose, amount_msat,
receiver_node_id, ref via_channel_id, ref via_user_channel_id,
- claim_deadline, onion_fields,
+ claim_deadline, onion_fields, ..
} => {
assert_eq!(our_payment_hash, *payment_hash);
assert_eq!(node.node.get_our_node_id(), receiver_node_id.unwrap());
(our_payment_preimage, our_payment_hash, our_payment_secret, payment_id)
}
-pub fn do_claim_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_paths: &[&[&Node<'a, 'b, 'c>]], skip_last: bool, our_payment_preimage: PaymentPreimage) -> u64 {
+pub fn do_claim_payment_along_route<'a, 'b, 'c>(
+ origin_node: &Node<'a, 'b, 'c>, expected_paths: &[&[&Node<'a, 'b, 'c>]], skip_last: bool,
+ our_payment_preimage: PaymentPreimage
+) -> u64 {
+ let extra_fees = vec![0; expected_paths.len()];
+ do_claim_payment_along_route_with_extra_penultimate_hop_fees(origin_node, expected_paths,
+ &extra_fees[..], skip_last, our_payment_preimage)
+}
+
+pub fn do_claim_payment_along_route_with_extra_penultimate_hop_fees<'a, 'b, 'c>(
+ origin_node: &Node<'a, 'b, 'c>, expected_paths: &[&[&Node<'a, 'b, 'c>]], expected_extra_fees:
+ &[u32], skip_last: bool, our_payment_preimage: PaymentPreimage
+) -> u64 {
+ assert_eq!(expected_paths.len(), expected_extra_fees.len());
for path in expected_paths.iter() {
assert_eq!(path.last().unwrap().node.get_our_node_id(), expected_paths[0].last().unwrap().node.get_our_node_id());
}
}
}
- for (expected_route, (path_msgs, next_hop)) in expected_paths.iter().zip(per_path_msgs.drain(..)) {
+ for (i, (expected_route, (path_msgs, next_hop))) in expected_paths.iter().zip(per_path_msgs.drain(..)).enumerate() {
let mut next_msgs = Some(path_msgs);
let mut expected_next_node = next_hop;
}
}
macro_rules! mid_update_fulfill_dance {
- ($node: expr, $prev_node: expr, $next_node: expr, $new_msgs: expr) => {
+ ($idx: expr, $node: expr, $prev_node: expr, $next_node: expr, $new_msgs: expr) => {
{
$node.node.handle_update_fulfill_htlc(&$prev_node.node.get_our_node_id(), &next_msgs.as_ref().unwrap().0);
- let fee = {
+ let mut fee = {
let per_peer_state = $node.node.per_peer_state.read().unwrap();
let peer_state = per_peer_state.get(&$prev_node.node.get_our_node_id())
.unwrap().lock().unwrap();
channel.context.config().forwarding_fee_base_msat
}
};
+ if $idx == 1 { fee += expected_extra_fees[i]; }
expect_payment_forwarded!($node, $next_node, $prev_node, Some(fee as u64), false, false);
expected_total_fee_msat += fee as u64;
check_added_monitors!($node, 1);
} else {
next_node = expected_route[expected_route.len() - 1 - idx - 1];
}
- mid_update_fulfill_dance!(node, prev_node, next_node, update_next_msgs);
+ mid_update_fulfill_dance!(idx, node, prev_node, next_node, update_next_msgs);
} else {
assert!(!update_next_msgs);
assert!(node.node.get_and_clear_pending_msg_events().is_empty());
// It now defaults to 1, so we simply set it to the expected value here.
default_config.channel_handshake_config.our_htlc_minimum_msat = 1000;
// When most of our tests were written, we didn't have the notion of a `max_dust_htlc_exposure_msat`,
- // It now defaults to 5_000_000 msat; to avoid interfering with tests we bump it to 50_000_000 msat.
- default_config.channel_config.max_dust_htlc_exposure_msat = 50_000_000;
+ // to avoid interfering with tests we bump it to 50_000_000 msat (assuming the default test
+ // feerate of 253).
+ default_config.channel_config.max_dust_htlc_exposure =
+ MaxDustHTLCExposure::FeeRateMultiplier(50_000_000 / 253);
default_config
}
let mut chanmgrs = Vec::new();
for i in 0..node_count {
let network = Network::Testnet;
+ let genesis_block = bitcoin::blockdata::constants::genesis_block(network);
let params = ChainParameters {
network,
best_block: BestBlock::from_network(network),
};
let node = ChannelManager::new(cfgs[i].fee_estimator, &cfgs[i].chain_monitor, cfgs[i].tx_broadcaster, &cfgs[i].router, cfgs[i].logger, cfgs[i].keys_manager,
- cfgs[i].keys_manager, cfgs[i].keys_manager, if node_config[i].is_some() { node_config[i].clone().unwrap() } else { test_default_channel_config() }, params);
+ cfgs[i].keys_manager, cfgs[i].keys_manager, if node_config[i].is_some() { node_config[i].clone().unwrap() } else { test_default_channel_config() }, params, genesis_block.header.time);
chanmgrs.push(node);
}
for i in 0..node_count {
let gossip_sync = P2PGossipSync::new(cfgs[i].network_graph.as_ref(), None, cfgs[i].logger);
+ let wallet_source = Arc::new(test_utils::TestWalletSource::new(SecretKey::from_slice(&[i as u8 + 1; 32]).unwrap()));
nodes.push(Node{
chain_source: cfgs[i].chain_source, tx_broadcaster: cfgs[i].tx_broadcaster,
fee_estimator: cfgs[i].fee_estimator, router: &cfgs[i].router,
blocks: Arc::clone(&cfgs[i].tx_broadcaster.blocks),
connect_style: Rc::clone(&connect_style),
override_init_features: Rc::clone(&cfgs[i].override_init_features),
+ wallet_source: Arc::clone(&wallet_source),
+ bump_tx_handler: BumpTransactionEventHandler::new(
+ cfgs[i].tx_broadcaster, Arc::new(Wallet::new(Arc::clone(&wallet_source), cfgs[i].logger)),
+ &cfgs[i].keys_manager, cfgs[i].logger,
+ ),
})
}
panic!("Unexpected event")
}
}
- for chan in $src_node.node.list_channels() {
- if chan.is_public && chan.counterparty.node_id != $dst_node.node.get_our_node_id() {
- if let Some(scid) = chan.short_channel_id {
- assert!(announcements.remove(&scid));
- }
- }
- }
assert!(announcements.is_empty());
res
}
use crate::ln::chan_utils::{OFFERED_HTLC_SCRIPT_WEIGHT, htlc_success_tx_weight, htlc_timeout_tx_weight, HTLCOutputInCommitment};
use crate::routing::gossip::{NetworkGraph, NetworkUpdate};
use crate::routing::router::{Path, PaymentParameters, Route, RouteHop, RouteParameters, find_route, get_route};
-use crate::ln::features::{ChannelFeatures, NodeFeatures};
+use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, NodeFeatures};
use crate::ln::msgs;
use crate::ln::msgs::{ChannelMessageHandler, RoutingMessageHandler, ErrorAction};
use crate::util::enforcing_trait_impls::EnforcingSigner;
use crate::util::errors::APIError;
use crate::util::ser::{Writeable, ReadableArgs};
use crate::util::string::UntrustedString;
-use crate::util::config::UserConfig;
+use crate::util::config::{UserConfig, MaxDustHTLCExposure};
use bitcoin::hash_types::BlockHash;
use bitcoin::blockdata::script::{Builder, Script};
use crate::ln::functional_test_utils::*;
use crate::ln::chan_utils::CommitmentTransaction;
+use super::channel::UNFUNDED_CHANNEL_AGE_LIMIT_TICKS;
+
#[test]
fn test_insane_channel_opens() {
// Stand up a network of 2 nodes
// Have node0 initiate a channel to node1 with aforementioned parameters
let mut push_amt = 100_000_000;
let feerate_per_kw = 253;
- let opt_anchors = false;
- push_amt -= feerate_per_kw as u64 * (commitment_tx_base_weight(opt_anchors) + 4 * COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000 * 1000;
+ let channel_type_features = ChannelTypeFeatures::only_static_remote_key();
+ push_amt -= feerate_per_kw as u64 * (commitment_tx_base_weight(&channel_type_features) + 4 * COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000 * 1000;
push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, &default_config) * 1000;
let temp_channel_id = nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, if send_from_initiator { 0 } else { push_amt }, 42, None).unwrap();
// Note that for outbound channels we have to consider the commitment tx fee and the
// "fee spike buffer", which is currently a multiple of the total commitment tx fee as
// well as an additional HTLC.
- - FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE * commit_tx_fee_msat(feerate_per_kw, 2, opt_anchors));
+ - FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE * commit_tx_fee_msat(feerate_per_kw, 2, &channel_type_features));
} else {
send_payment(&nodes[1], &[&nodes[0]], push_amt);
}
let default_config = UserConfig::default();
let bs_channel_reserve_sats = get_holder_selected_channel_reserve_satoshis(channel_value, &default_config);
- let opt_anchors = false;
+ let channel_type_features = ChannelTypeFeatures::only_static_remote_key();
// Calculate the maximum feerate that A can afford. Note that we don't send an update_fee
// CONCURRENT_INBOUND_HTLC_FEE_BUFFER HTLCs before actually running out of local balance, so we
// calculate two different feerates here - the expected local limit as well as the expected
// remote limit.
- let feerate = ((channel_value - bs_channel_reserve_sats - push_sats) * 1000 / (commitment_tx_base_weight(opt_anchors) + CONCURRENT_INBOUND_HTLC_FEE_BUFFER as u64 * COMMITMENT_TX_WEIGHT_PER_HTLC)) as u32;
- let non_buffer_feerate = ((channel_value - bs_channel_reserve_sats - push_sats) * 1000 / commitment_tx_base_weight(opt_anchors)) as u32;
+ let feerate = ((channel_value - bs_channel_reserve_sats - push_sats) * 1000 / (commitment_tx_base_weight(&channel_type_features) + CONCURRENT_INBOUND_HTLC_FEE_BUFFER as u64 * COMMITMENT_TX_WEIGHT_PER_HTLC)) as u32;
+ let non_buffer_feerate = ((channel_value - bs_channel_reserve_sats - push_sats) * 1000 / commitment_tx_base_weight(&channel_type_features)) as u32;
{
let mut feerate_lock = chanmon_cfgs[0].fee_estimator.sat_per_kw.lock().unwrap();
*feerate_lock = feerate;
//We made sure neither party's funds are below the dust limit and there are no HTLCs here
assert_eq!(commitment_tx.output.len(), 2);
- let total_fee: u64 = commit_tx_fee_msat(feerate, 0, opt_anchors) / 1000;
+ let total_fee: u64 = commit_tx_fee_msat(feerate, 0, &channel_type_features) / 1000;
let mut actual_fee = commitment_tx.output.iter().fold(0, |acc, output| acc + output.value);
actual_fee = channel_value - actual_fee;
assert_eq!(total_fee, actual_fee);
let commitment_tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
INITIAL_COMMITMENT_NUMBER - 1,
push_sats,
- channel_value - push_sats - commit_tx_fee_msat(non_buffer_feerate + 4, 0, opt_anchors) / 1000,
- opt_anchors, local_funding, remote_funding,
+ channel_value - push_sats - commit_tx_fee_msat(non_buffer_feerate + 4, 0, &channel_type_features) / 1000,
+ local_funding, remote_funding,
commit_tx_keys.clone(),
non_buffer_feerate + 4,
&mut htlcs,
let channel_reserve = chan_stat.channel_reserve_msat;
// The 2* and +1 are for the fee spike reserve.
- let commit_tx_fee = 2 * commit_tx_fee_msat(get_feerate!(nodes[0], nodes[1], chan.2), 1 + 1, get_opt_anchors!(nodes[0], nodes[1], chan.2));
+ let commit_tx_fee = 2 * commit_tx_fee_msat(get_feerate!(nodes[0], nodes[1], chan.2), 1 + 1, &get_channel_type_features!(nodes[0], nodes[1], chan.2));
let max_can_send = 5000000 - channel_reserve - commit_tx_fee;
let (mut route, our_payment_hash, _, our_payment_secret) =
get_route_and_payment_hash!(nodes[0], nodes[1], max_can_send);
payment_hash: payment_hash,
cltv_expiry: htlc_cltv,
onion_routing_packet: onion_packet,
+ skimmed_fee_msat: None,
};
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &msg);
commitment_number,
95000,
local_chan_balance,
- local_chan.context.opt_anchors(), local_funding, remote_funding,
+ local_funding, remote_funding,
commit_tx_keys.clone(),
feerate_per_kw,
&mut vec![(accepted_htlc_info, ())],
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let default_config = UserConfig::default();
- let opt_anchors = false;
+ let channel_type_features = ChannelTypeFeatures::only_static_remote_key();
let mut push_amt = 100_000_000;
- push_amt -= commit_tx_fee_msat(feerate_per_kw, MIN_AFFORDABLE_HTLC_COUNT as u64, opt_anchors);
+ push_amt -= commit_tx_fee_msat(feerate_per_kw, MIN_AFFORDABLE_HTLC_COUNT as u64, &channel_type_features);
push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, &default_config) * 1000;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let default_config = UserConfig::default();
- let opt_anchors = false;
+ let channel_type_features = ChannelTypeFeatures::only_static_remote_key();
// Set nodes[0]'s balance such that they will consider any above-dust received HTLC to be a
// channel reserve violation (so their balance is channel reserve (1000 sats) + commitment
// transaction fee with 0 HTLCs (183 sats)).
let mut push_amt = 100_000_000;
- push_amt -= commit_tx_fee_msat(feerate_per_kw, MIN_AFFORDABLE_HTLC_COUNT as u64, opt_anchors);
+ push_amt -= commit_tx_fee_msat(feerate_per_kw, MIN_AFFORDABLE_HTLC_COUNT as u64, &channel_type_features);
push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, &default_config) * 1000;
let chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, push_amt);
payment_hash: payment_hash,
cltv_expiry: htlc_cltv,
onion_routing_packet: onion_packet,
+ skimmed_fee_msat: None,
};
nodes[0].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &msg);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None, None]);
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let default_config = UserConfig::default();
- let opt_anchors = false;
+ let channel_type_features = ChannelTypeFeatures::only_static_remote_key();
// Set nodes[0]'s balance such that they will consider any above-dust received HTLC to be a
// channel reserve violation (so their balance is channel reserve (1000 sats) + commitment
// transaction fee with 0 HTLCs (183 sats)).
let mut push_amt = 100_000_000;
- push_amt -= commit_tx_fee_msat(feerate_per_kw, MIN_AFFORDABLE_HTLC_COUNT as u64, opt_anchors);
+ push_amt -= commit_tx_fee_msat(feerate_per_kw, MIN_AFFORDABLE_HTLC_COUNT as u64, &channel_type_features);
push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, &default_config) * 1000;
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, push_amt);
let dust_amt = crate::ln::channel::MIN_CHAN_DUST_LIMIT_SATOSHIS * 1000
- + feerate_per_kw as u64 * htlc_success_tx_weight(opt_anchors) / 1000 * 1000 - 1;
+ + feerate_per_kw as u64 * htlc_success_tx_weight(&channel_type_features) / 1000 * 1000 - 1;
// In the previous code, routing this dust payment would cause nodes[0] to perceive a channel
// reserve violation even though it's a dust HTLC and therefore shouldn't count towards the
// commitment transaction fee.
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let default_config = UserConfig::default();
- let opt_anchors = false;
+ let channel_type_features = ChannelTypeFeatures::only_static_remote_key();
// Set the push_msat amount such that nodes[0] will not be able to afford to add even a single
// HTLC.
let mut push_amt = 100_000_000;
- push_amt -= commit_tx_fee_msat(feerate_per_kw, MIN_AFFORDABLE_HTLC_COUNT as u64, opt_anchors);
+ push_amt -= commit_tx_fee_msat(feerate_per_kw, MIN_AFFORDABLE_HTLC_COUNT as u64, &channel_type_features);
assert_eq!(nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, push_amt + 1, 42, None).unwrap_err(),
APIError::APIMisuseError { err: "Funding amount (356) can't even pay fee for initial commitment transaction fee of 357.".to_string() });
let total_routing_fee_msat = (nodes.len() - 2) as u64 * feemsat;
let chan_stat = get_channel_value_stat!(nodes[0], nodes[1], chan.2);
let feerate = get_feerate!(nodes[0], nodes[1], chan.2);
- let opt_anchors = get_opt_anchors!(nodes[0], nodes[1], chan.2);
+ let channel_type_features = get_channel_type_features!(nodes[0], nodes[1], chan.2);
// Add a 2* and +1 for the fee spike reserve.
- let commit_tx_fee_2_htlc = 2*commit_tx_fee_msat(feerate, 2 + 1, opt_anchors);
+ let commit_tx_fee_2_htlc = 2*commit_tx_fee_msat(feerate, 2 + 1, &channel_type_features);
let recv_value_1 = (chan_stat.value_to_self_msat - chan_stat.channel_reserve_msat - total_routing_fee_msat - commit_tx_fee_2_htlc)/2;
let amt_msat_1 = recv_value_1 + total_routing_fee_msat;
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event_1.msgs[0]);
// Attempt to trigger a channel reserve violation --> payment failure.
- let commit_tx_fee_2_htlcs = commit_tx_fee_msat(feerate, 2, opt_anchors);
+ let commit_tx_fee_2_htlcs = commit_tx_fee_msat(feerate, 2, &channel_type_features);
let recv_value_2 = chan_stat.value_to_self_msat - amt_msat_1 - chan_stat.channel_reserve_msat - total_routing_fee_msat - commit_tx_fee_2_htlcs + 1;
let amt_msat_2 = recv_value_2 + total_routing_fee_msat;
let mut route_2 = route_1.clone();
payment_hash: our_payment_hash_1,
cltv_expiry: htlc_cltv,
onion_routing_packet: onion_packet,
+ skimmed_fee_msat: None,
};
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &msg);
assert_eq!(channels1[0].inbound_capacity_msat, 100000 * 1000 - 95000000 - reserve*1000);
}
-fn commit_tx_fee_msat(feerate: u32, num_htlcs: u64, opt_anchors: bool) -> u64 {
- (commitment_tx_base_weight(opt_anchors) + num_htlcs * COMMITMENT_TX_WEIGHT_PER_HTLC) * feerate as u64 / 1000 * 1000
+fn commit_tx_fee_msat(feerate: u32, num_htlcs: u64, channel_type_features: &ChannelTypeFeatures) -> u64 {
+ (commitment_tx_base_weight(channel_type_features) + num_htlcs * COMMITMENT_TX_WEIGHT_PER_HTLC) * feerate as u64 / 1000 * 1000
}
#[test]
let feemsat = 239; // set above
let total_fee_msat = (nodes.len() - 2) as u64 * feemsat;
let feerate = get_feerate!(nodes[0], nodes[1], chan_1.2);
- let opt_anchors = get_opt_anchors!(nodes[0], nodes[1], chan_1.2);
+ let channel_type_features = get_channel_type_features!(nodes[0], nodes[1], chan_1.2);
let recv_value_0 = stat01.counterparty_max_htlc_value_in_flight_msat - total_fee_msat;
// 3 for the 3 HTLCs that will be sent, 2* and +1 for the fee spike reserve.
// Also, ensure that each payment has enough to be over the dust limit to
// ensure it'll be included in each commit tx fee calculation.
- let commit_tx_fee_all_htlcs = 2*commit_tx_fee_msat(feerate, 3 + 1, opt_anchors);
+ let commit_tx_fee_all_htlcs = 2*commit_tx_fee_msat(feerate, 3 + 1, &channel_type_features);
let ensure_htlc_amounts_above_dust_buffer = 3 * (stat01.counterparty_dust_limit_msat + 1000);
if stat01.value_to_self_msat < stat01.channel_reserve_msat + commit_tx_fee_all_htlcs + ensure_htlc_amounts_above_dust_buffer + amt_msat {
break;
// the amount of the first of these aforementioned 3 payments. The reason we split into 3 payments
// is to test the behavior of the holding cell with respect to channel reserve and commit tx fee
// policy.
- let commit_tx_fee_2_htlcs = 2*commit_tx_fee_msat(feerate, 2 + 1, opt_anchors);
+ let commit_tx_fee_2_htlcs = 2*commit_tx_fee_msat(feerate, 2 + 1, &channel_type_features);
let recv_value_1 = (stat01.value_to_self_msat - stat01.channel_reserve_msat - total_fee_msat - commit_tx_fee_2_htlcs)/2;
let amt_msat_1 = recv_value_1 + total_fee_msat;
}
// split the rest to test holding cell
- let commit_tx_fee_3_htlcs = 2*commit_tx_fee_msat(feerate, 3 + 1, opt_anchors);
+ let commit_tx_fee_3_htlcs = 2*commit_tx_fee_msat(feerate, 3 + 1, &channel_type_features);
let additional_htlc_cost_msat = commit_tx_fee_3_htlcs - commit_tx_fee_2_htlcs;
let recv_value_21 = recv_value_2/2 - additional_htlc_cost_msat/2;
let recv_value_22 = recv_value_2 - recv_value_21 - total_fee_msat - additional_htlc_cost_msat;
claim_payment(&nodes[0], &vec!(&nodes[1], &nodes[2]), our_payment_preimage_21);
claim_payment(&nodes[0], &vec!(&nodes[1], &nodes[2]), our_payment_preimage_22);
- let commit_tx_fee_0_htlcs = 2*commit_tx_fee_msat(feerate, 1, opt_anchors);
+ let commit_tx_fee_0_htlcs = 2*commit_tx_fee_msat(feerate, 1, &channel_type_features);
let recv_value_3 = commit_tx_fee_2_htlcs - commit_tx_fee_0_htlcs - total_fee_msat;
send_payment(&nodes[0], &vec![&nodes[1], &nodes[2]][..], recv_value_3);
- let commit_tx_fee_1_htlc = 2*commit_tx_fee_msat(feerate, 1 + 1, opt_anchors);
+ let commit_tx_fee_1_htlc = 2*commit_tx_fee_msat(feerate, 1 + 1, &channel_type_features);
let expected_value_to_self = stat01.value_to_self_msat - (recv_value_1 + total_fee_msat) - (recv_value_21 + total_fee_msat) - (recv_value_22 + total_fee_msat) - (recv_value_3 + total_fee_msat);
let stat0 = get_channel_value_stat!(nodes[0], nodes[1], chan_1.2);
assert_eq!(stat0.value_to_self_msat, expected_value_to_self);
payment_hash,
cltv_expiry,
onion_routing_packet,
+ skimmed_fee_msat: None,
};
nodes[0].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &update_add_htlc);
}
let mut chan_stat = get_channel_value_stat!(nodes[0], nodes[1], chan.2);
let channel_reserve = chan_stat.channel_reserve_msat;
let feerate = get_feerate!(nodes[0], nodes[1], chan.2);
- let opt_anchors = get_opt_anchors!(nodes[0], nodes[1], chan.2);
+ let channel_type_features = get_channel_type_features!(nodes[0], nodes[1], chan.2);
// 2* and +1 HTLCs on the commit tx fee calculation for the fee spike reserve.
- let max_can_send = 5000000 - channel_reserve - 2*commit_tx_fee_msat(feerate, 1 + 1, opt_anchors);
+ let max_can_send = 5000000 - channel_reserve - 2*commit_tx_fee_msat(feerate, 1 + 1, &channel_type_features);
let (route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], max_can_send);
// Send a payment which passes reserve checks but gets stuck in the holding cell.
let mut chan_stat = get_channel_value_stat!(nodes[0], nodes[1], chan.2);
let channel_reserve = chan_stat.channel_reserve_msat;
let feerate = get_feerate!(nodes[0], nodes[1], chan.2);
- let opt_anchors = get_opt_anchors!(nodes[0], nodes[1], chan.2);
+ let channel_type_features = get_channel_type_features!(nodes[0], nodes[1], chan.2);
// 2* and +1 HTLCs on the commit tx fee calculation for the fee spike reserve.
let amt_1 = 20000;
- let amt_2 = 5000000 - channel_reserve - 2*commit_tx_fee_msat(feerate, 2 + 1, opt_anchors) - amt_1;
+ let amt_2 = 5000000 - channel_reserve - 2*commit_tx_fee_msat(feerate, 2 + 1, &channel_type_features) - amt_1;
let (route_1, payment_hash_1, payment_preimage_1, payment_secret_1) = get_route_and_payment_hash!(nodes[0], nodes[1], amt_1);
let (route_2, payment_hash_2, _, payment_secret_2) = get_route_and_payment_hash!(nodes[0], nodes[1], amt_2);
let mut chan_stat = get_channel_value_stat!(nodes[0], nodes[1], chan_0_1.2);
let channel_reserve = chan_stat.channel_reserve_msat;
let feerate = get_feerate!(nodes[0], nodes[1], chan_0_1.2);
- let opt_anchors = get_opt_anchors!(nodes[0], nodes[1], chan_0_1.2);
+ let channel_type_features = get_channel_type_features!(nodes[0], nodes[1], chan_0_1.2);
// Send a payment which passes reserve checks but gets stuck in the holding cell.
- let max_can_send = 5000000 - channel_reserve - 2*commit_tx_fee_msat(feerate, 1 + 1, opt_anchors);
+ let max_can_send = 5000000 - channel_reserve - 2*commit_tx_fee_msat(feerate, 1 + 1, &channel_type_features);
let (route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], max_can_send);
let payment_event = {
nodes[0].node.send_payment_with_route(&route, our_payment_hash,
let chan_stat = get_channel_value_stat!(nodes[0], nodes[1], chan.2);
let channel_reserve = chan_stat.channel_reserve_msat;
let feerate = get_feerate!(nodes[0], nodes[1], chan.2);
- let opt_anchors = get_opt_anchors!(nodes[0], nodes[1], chan.2);
+ let channel_type_features = get_channel_type_features!(nodes[0], nodes[1], chan.2);
// The 2* and +1 are for the fee spike reserve.
- let commit_tx_fee_outbound = 2 * commit_tx_fee_msat(feerate, 1 + 1, opt_anchors);
+ let commit_tx_fee_outbound = 2 * commit_tx_fee_msat(feerate, 1 + 1, &channel_type_features);
let max_can_send = 5000000 - channel_reserve - commit_tx_fee_outbound;
let (route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], max_can_send);
payment_hash: our_payment_hash,
cltv_expiry: htlc_cltv,
onion_routing_packet: onion_packet.clone(),
+ skimmed_fee_msat: None,
};
for i in 0..50 {
}
}
-#[test]
-#[allow(deprecated)]
-fn test_secret_timeout() {
- // Simple test of payment secret storage time outs. After
- // `create_inbound_payment(_for_hash)_legacy` is removed, this test will be removed as well.
- let chanmon_cfgs = create_chanmon_cfgs(2);
- let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
- let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
- let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
-
- create_announced_chan_between_nodes(&nodes, 0, 1).0.contents.short_channel_id;
-
- let (payment_hash, payment_secret_1) = nodes[1].node.create_inbound_payment_legacy(Some(100_000), 2).unwrap();
-
- // We should fail to register the same payment hash twice, at least until we've connected a
- // block with time 7200 + CHAN_CONFIRM_DEPTH + 1.
- if let Err(APIError::APIMisuseError { err }) = nodes[1].node.create_inbound_payment_for_hash_legacy(payment_hash, Some(100_000), 2) {
- assert_eq!(err, "Duplicate payment hash");
- } else { panic!(); }
- let mut block = {
- let node_1_blocks = nodes[1].blocks.lock().unwrap();
- create_dummy_block(node_1_blocks.last().unwrap().0.block_hash(), node_1_blocks.len() as u32 + 7200, Vec::new())
- };
- connect_block(&nodes[1], &block);
- if let Err(APIError::APIMisuseError { err }) = nodes[1].node.create_inbound_payment_for_hash_legacy(payment_hash, Some(100_000), 2) {
- assert_eq!(err, "Duplicate payment hash");
- } else { panic!(); }
-
- // If we then connect the second block, we should be able to register the same payment hash
- // again (this time getting a new payment secret).
- block.header.prev_blockhash = block.header.block_hash();
- block.header.time += 1;
- connect_block(&nodes[1], &block);
- let our_payment_secret = nodes[1].node.create_inbound_payment_for_hash_legacy(payment_hash, Some(100_000), 2).unwrap();
- assert_ne!(payment_secret_1, our_payment_secret);
-
- {
- let (route, _, _, _) = get_route_and_payment_hash!(nodes[0], nodes[1], 100_000);
- nodes[0].node.send_payment_with_route(&route, payment_hash,
- RecipientOnionFields::secret_only(our_payment_secret), PaymentId(payment_hash.0)).unwrap();
- check_added_monitors!(nodes[0], 1);
- let mut events = nodes[0].node.get_and_clear_pending_msg_events();
- let mut payment_event = SendEvent::from_event(events.pop().unwrap());
- nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
- commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false);
- }
- // Note that after leaving the above scope we have no knowledge of any arguments or return
- // values from previous calls.
- expect_pending_htlcs_forwardable!(nodes[1]);
- let events = nodes[1].node.get_and_clear_pending_events();
- assert_eq!(events.len(), 1);
- match events[0] {
- Event::PaymentClaimable { purpose: PaymentPurpose::InvoicePayment { payment_preimage, payment_secret }, .. } => {
- assert!(payment_preimage.is_none());
- assert_eq!(payment_secret, our_payment_secret);
- // We don't actually have the payment preimage with which to claim this payment!
- },
- _ => panic!("Unexpected event"),
- }
-}
-
#[test]
fn test_bad_secret_hash() {
// Simple test of unregistered payment hash/invalid payment secret handling
let (_, funding_created) = {
let per_peer_state = nodes[0].node.per_peer_state.read().unwrap();
let mut a_peer_state = per_peer_state.get(&nodes[1].node.get_our_node_id()).unwrap().lock().unwrap();
- // Once we call `get_outbound_funding_created` the channel has a duplicate channel_id as
+ // Once we call `get_funding_created` the channel has a duplicate channel_id as
// another channel in the ChannelManager - an invalid state. Thus, we'd panic later when we
// try to create another channel. Instead, we drop the channel entirely here (leaving the
// channelmanager in a possibly nonsense state instead).
let mut as_chan = a_peer_state.outbound_v1_channel_by_id.remove(&open_chan_2_msg.temporary_channel_id).unwrap();
let logger = test_utils::TestLogger::new();
- as_chan.get_outbound_funding_created(tx.clone(), funding_outpoint, &&logger).map_err(|_| ()).unwrap()
+ as_chan.get_funding_created(tx.clone(), funding_outpoint, &&logger).map_err(|_| ()).unwrap()
};
check_added_monitors!(nodes[0], 0);
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created);
AtUpdateFeeOutbound,
}
-fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_event: ExposureEvent, on_holder_tx: bool) {
+fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_event: ExposureEvent, on_holder_tx: bool, multiplier_dust_limit: bool) {
// Test that we properly reject dust HTLC violating our `max_dust_htlc_exposure_msat`
// policy.
//
let chanmon_cfgs = create_chanmon_cfgs(2);
let mut config = test_default_channel_config();
- config.channel_config.max_dust_htlc_exposure_msat = 5_000_000; // default setting value
+ config.channel_config.max_dust_htlc_exposure = if multiplier_dust_limit {
+ // Default test fee estimator rate is 253 sat/kw, so we set the multiplier to 5_000_000 / 253
+ // to get roughly the same initial value as the default setting when this test was
+ // originally written.
+ MaxDustHTLCExposure::FeeRateMultiplier(5_000_000 / 253)
+ } else { MaxDustHTLCExposure::FixedLimitMsat(5_000_000) }; // initial default setting value
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config), None]);
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let mut accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel);
- let opt_anchors = false;
+ let channel_type_features = ChannelTypeFeatures::only_static_remote_key();
let (temporary_channel_id, tx, _) = create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 1_000_000, 42);
let (mut route, payment_hash, _, payment_secret) =
get_route_and_payment_hash!(nodes[0], nodes[1], 1000);
- let dust_buffer_feerate = {
+ let (dust_buffer_feerate, max_dust_htlc_exposure_msat) = {
let per_peer_state = nodes[0].node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(&nodes[1].node.get_our_node_id()).unwrap().lock().unwrap();
let chan = chan_lock.channel_by_id.get(&channel_id).unwrap();
- chan.context.get_dust_buffer_feerate(None) as u64
+ (chan.context.get_dust_buffer_feerate(None) as u64,
+ chan.context.get_max_dust_htlc_exposure_msat(&LowerBoundedFeeEstimator(nodes[0].fee_estimator)))
};
- let dust_outbound_htlc_on_holder_tx_msat: u64 = (dust_buffer_feerate * htlc_timeout_tx_weight(opt_anchors) / 1000 + open_channel.dust_limit_satoshis - 1) * 1000;
- let dust_outbound_htlc_on_holder_tx: u64 = config.channel_config.max_dust_htlc_exposure_msat / dust_outbound_htlc_on_holder_tx_msat;
+ let dust_outbound_htlc_on_holder_tx_msat: u64 = (dust_buffer_feerate * htlc_timeout_tx_weight(&channel_type_features) / 1000 + open_channel.dust_limit_satoshis - 1) * 1000;
+ let dust_outbound_htlc_on_holder_tx: u64 = max_dust_htlc_exposure_msat / dust_outbound_htlc_on_holder_tx_msat;
- let dust_inbound_htlc_on_holder_tx_msat: u64 = (dust_buffer_feerate * htlc_success_tx_weight(opt_anchors) / 1000 + open_channel.dust_limit_satoshis - 1) * 1000;
- let dust_inbound_htlc_on_holder_tx: u64 = config.channel_config.max_dust_htlc_exposure_msat / dust_inbound_htlc_on_holder_tx_msat;
+ let dust_inbound_htlc_on_holder_tx_msat: u64 = (dust_buffer_feerate * htlc_success_tx_weight(&channel_type_features) / 1000 + open_channel.dust_limit_satoshis - 1) * 1000;
+ let dust_inbound_htlc_on_holder_tx: u64 = max_dust_htlc_exposure_msat / dust_inbound_htlc_on_holder_tx_msat;
let dust_htlc_on_counterparty_tx: u64 = 4;
- let dust_htlc_on_counterparty_tx_msat: u64 = config.channel_config.max_dust_htlc_exposure_msat / dust_htlc_on_counterparty_tx;
+ let dust_htlc_on_counterparty_tx_msat: u64 = max_dust_htlc_exposure_msat / dust_htlc_on_counterparty_tx;
if on_holder_tx {
if dust_outbound_balance {
), true, APIError::ChannelUnavailable { .. }, {});
}
} else if exposure_breach_event == ExposureEvent::AtHTLCReception {
- let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[1], nodes[0], if on_holder_tx { dust_inbound_htlc_on_holder_tx_msat } else { dust_htlc_on_counterparty_tx_msat + 1 });
+ let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[1], nodes[0], if on_holder_tx { dust_inbound_htlc_on_holder_tx_msat } else { dust_htlc_on_counterparty_tx_msat + 4 });
nodes[1].node.send_payment_with_route(&route, payment_hash,
RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap();
check_added_monitors!(nodes[1], 1);
// Outbound dust balance: 6399 sats
let dust_inbound_overflow = dust_inbound_htlc_on_holder_tx_msat * (dust_inbound_htlc_on_holder_tx + 1);
let dust_outbound_overflow = dust_outbound_htlc_on_holder_tx_msat * dust_outbound_htlc_on_holder_tx + dust_inbound_htlc_on_holder_tx_msat;
- nodes[0].logger.assert_log("lightning::ln::channel".to_string(), format!("Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx", if dust_outbound_balance { dust_outbound_overflow } else { dust_inbound_overflow }, config.channel_config.max_dust_htlc_exposure_msat), 1);
+ nodes[0].logger.assert_log("lightning::ln::channel".to_string(), format!("Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx", if dust_outbound_balance { dust_outbound_overflow } else { dust_inbound_overflow }, max_dust_htlc_exposure_msat), 1);
} else {
// Outbound dust balance: 5200 sats
nodes[0].logger.assert_log("lightning::ln::channel".to_string(),
format!("Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on counterparty commitment tx",
- dust_htlc_on_counterparty_tx_msat * (dust_htlc_on_counterparty_tx - 1) + dust_htlc_on_counterparty_tx_msat + 1,
- config.channel_config.max_dust_htlc_exposure_msat), 1);
+ dust_htlc_on_counterparty_tx_msat * (dust_htlc_on_counterparty_tx - 1) + dust_htlc_on_counterparty_tx_msat + 4,
+ max_dust_htlc_exposure_msat), 1);
}
} else if exposure_breach_event == ExposureEvent::AtUpdateFeeOutbound {
route.paths[0].hops.last_mut().unwrap().fee_msat = 2_500_000;
- nodes[0].node.send_payment_with_route(&route, payment_hash,
- RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap();
+ // For the multiplier dust exposure limit, since it scales with feerate,
+ // we need to add a lot of HTLCs that will become dust at the new feerate
+ // to cross the threshold.
+ for _ in 0..20 {
+ let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(1_000), None);
+ nodes[0].node.send_payment_with_route(&route, payment_hash,
+ RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap();
+ }
{
let mut feerate_lock = chanmon_cfgs[0].fee_estimator.sat_per_kw.lock().unwrap();
*feerate_lock = *feerate_lock * 10;
added_monitors.clear();
}
+fn do_test_max_dust_htlc_exposure_by_threshold_type(multiplier_dust_limit: bool) {
+ do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCForward, true, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCForward, true, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCReception, true, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCReception, false, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCForward, false, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCReception, false, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCReception, true, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCForward, false, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(true, ExposureEvent::AtUpdateFeeOutbound, true, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(true, ExposureEvent::AtUpdateFeeOutbound, false, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, false, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, true, multiplier_dust_limit);
+}
+
#[test]
fn test_max_dust_htlc_exposure() {
- do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCForward, true);
- do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCForward, true);
- do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCReception, true);
- do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCReception, false);
- do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCForward, false);
- do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCReception, false);
- do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCReception, true);
- do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCForward, false);
- do_test_max_dust_htlc_exposure(true, ExposureEvent::AtUpdateFeeOutbound, true);
- do_test_max_dust_htlc_exposure(true, ExposureEvent::AtUpdateFeeOutbound, false);
- do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, false);
- do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, true);
+ do_test_max_dust_htlc_exposure_by_threshold_type(false);
+ do_test_max_dust_htlc_exposure_by_threshold_type(true);
}
#[test]
}
}
}
+
+#[test]
+fn test_remove_expired_outbound_unfunded_channels() {
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ let temp_channel_id = nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 0, 42, None).unwrap();
+ let open_channel_message = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_message);
+ let accept_channel_message = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel_message);
+
+ let events = nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match events[0] {
+ Event::FundingGenerationReady { .. } => (),
+ _ => panic!("Unexpected event"),
+ };
+
+ // Asserts the outbound channel has been removed from a nodes[0]'s peer state map.
+ let check_outbound_channel_existence = |should_exist: bool| {
+ let per_peer_state = nodes[0].node.per_peer_state.read().unwrap();
+ let chan_lock = per_peer_state.get(&nodes[1].node.get_our_node_id()).unwrap().lock().unwrap();
+ assert_eq!(chan_lock.outbound_v1_channel_by_id.contains_key(&temp_channel_id), should_exist);
+ };
+
+ // Channel should exist without any timer ticks.
+ check_outbound_channel_existence(true);
+
+ // Channel should exist with 1 timer tick less than required.
+ for _ in 0..UNFUNDED_CHANNEL_AGE_LIMIT_TICKS - 1 {
+ nodes[0].node.timer_tick_occurred();
+ check_outbound_channel_existence(true)
+ }
+
+ // Remove channel after reaching the required ticks.
+ nodes[0].node.timer_tick_occurred();
+ check_outbound_channel_existence(false);
+
+ check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed);
+}
+
+#[test]
+fn test_remove_expired_inbound_unfunded_channels() {
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ let temp_channel_id = nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 0, 42, None).unwrap();
+ let open_channel_message = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_message);
+ let accept_channel_message = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel_message);
+
+ let events = nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match events[0] {
+ Event::FundingGenerationReady { .. } => (),
+ _ => panic!("Unexpected event"),
+ };
+
+ // Asserts the inbound channel has been removed from a nodes[1]'s peer state map.
+ let check_inbound_channel_existence = |should_exist: bool| {
+ let per_peer_state = nodes[1].node.per_peer_state.read().unwrap();
+ let chan_lock = per_peer_state.get(&nodes[0].node.get_our_node_id()).unwrap().lock().unwrap();
+ assert_eq!(chan_lock.inbound_v1_channel_by_id.contains_key(&temp_channel_id), should_exist);
+ };
+
+ // Channel should exist without any timer ticks.
+ check_inbound_channel_existence(true);
+
+ // Channel should exist with 1 timer tick less than required.
+ for _ in 0..UNFUNDED_CHANNEL_AGE_LIMIT_TICKS - 1 {
+ nodes[1].node.timer_tick_occurred();
+ check_inbound_channel_existence(true)
+ }
+
+ // Remove channel after reaching the required ticks.
+ nodes[1].node.timer_tick_occurred();
+ check_inbound_channel_existence(false);
+
+ check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed);
+}
//! Further functional tests which test blockchain reorganizations.
-#[cfg(anchors)]
-use crate::sign::{ChannelSigner, EcdsaChannelSigner};
-#[cfg(anchors)]
-use crate::chain::channelmonitor::LATENCY_GRACE_PERIOD_BLOCKS;
-use crate::chain::channelmonitor::{ANTI_REORG_DELAY, Balance};
+use crate::sign::EcdsaChannelSigner;
+use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS, Balance};
use crate::chain::transaction::OutPoint;
-use crate::chain::chaininterface::LowerBoundedFeeEstimator;
-#[cfg(anchors)]
-use crate::events::bump_transaction::BumpTransactionEvent;
+use crate::chain::chaininterface::{LowerBoundedFeeEstimator, compute_feerate_sat_per_1000_weight};
+use crate::events::bump_transaction::{BumpTransactionEvent, WalletSource};
use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureReason, HTLCDestination};
use crate::ln::channel;
-#[cfg(anchors)]
-use crate::ln::chan_utils;
-#[cfg(anchors)]
-use crate::ln::channelmanager::ChannelManager;
-use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, PaymentId, RecipientOnionFields};
+use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, ChannelManager, PaymentId, RecipientOnionFields};
use crate::ln::msgs::ChannelMessageHandler;
-#[cfg(anchors)]
use crate::util::config::UserConfig;
-#[cfg(anchors)]
use crate::util::crypto::sign;
use crate::util::ser::Writeable;
use crate::util::test_utils;
-#[cfg(anchors)]
use bitcoin::blockdata::transaction::EcdsaSighashType;
use bitcoin::blockdata::script::Builder;
use bitcoin::blockdata::opcodes;
-use bitcoin::secp256k1::Secp256k1;
-#[cfg(anchors)]
-use bitcoin::secp256k1::SecretKey;
-#[cfg(anchors)]
-use bitcoin::{Amount, PublicKey, Script, TxIn, TxOut, PackedLockTime, Witness};
-use bitcoin::Transaction;
-#[cfg(anchors)]
+use bitcoin::secp256k1::{Secp256k1, SecretKey};
+use bitcoin::{Amount, PublicKey, Script, Transaction, TxIn, TxOut, PackedLockTime, Witness};
use bitcoin::util::sighash::SighashCache;
use crate::prelude::*;
assert_eq!(funding_outpoint.to_channel_id(), chan_id);
let chan_feerate = get_feerate!(nodes[0], nodes[1], chan_id) as u64;
- let opt_anchors = get_opt_anchors!(nodes[0], nodes[1], chan_id);
+ let channel_type_features = get_channel_type_features!(nodes[0], nodes[1], chan_id);
assert_eq!(vec![Balance::ClaimableOnChannelClose {
- claimable_amount_satoshis: 1_000_000 - 1_000 - chan_feerate * channel::commitment_tx_base_weight(opt_anchors) / 1000
+ claimable_amount_satoshis: 1_000_000 - 1_000 - chan_feerate * channel::commitment_tx_base_weight(&channel_type_features) / 1000
}],
nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
assert_eq!(vec![Balance::ClaimableOnChannelClose { claimable_amount_satoshis: 1_000, }],
assert!(nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
assert_eq!(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 1_000_000 - 1_000 - chan_feerate * channel::commitment_tx_base_weight(opt_anchors) / 1000,
+ claimable_amount_satoshis: 1_000_000 - 1_000 - chan_feerate * channel::commitment_tx_base_weight(&channel_type_features) / 1000,
confirmation_height: nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 1,
}],
nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
let htlc_cltv_timeout = nodes[0].best_block_info().1 + TEST_FINAL_CLTV + 1; // Note ChannelManager adds one to CLTV timeouts for safety
let chan_feerate = get_feerate!(nodes[0], nodes[1], chan_id) as u64;
- let opt_anchors = get_opt_anchors!(nodes[0], nodes[1], chan_id);
+ let channel_type_features = get_channel_type_features!(nodes[0], nodes[1], chan_id);
let remote_txn = get_local_commitment_txn!(nodes[1], chan_id);
let sent_htlc_balance = Balance::MaybeTimeoutClaimableHTLC {
// as claimable. A lists both its to-self balance and the (possibly-claimable) HTLCs.
assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose {
claimable_amount_satoshis: 1_000_000 - 3_000 - 4_000 - 1_000 - 3 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
}, sent_htlc_balance.clone(), sent_htlc_timeout_balance.clone()]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose {
1_000 - // The push_msat value in satoshis
3 - // The dust HTLC value in satoshis
// The commitment transaction fee with two HTLC outputs:
- chan_feerate * (channel::commitment_tx_base_weight(opt_anchors) +
+ chan_feerate * (channel::commitment_tx_base_weight(&channel_type_features) +
if prev_commitment_tx { 1 } else { 2 } *
channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
}, sent_htlc_timeout_balance.clone()];
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
claimable_amount_satoshis: 1_000_000 - 3_000 - 4_000 - 1_000 - 3 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 1,
}, sent_htlc_balance.clone(), sent_htlc_timeout_balance.clone()]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
expect_payment_claimed!(nodes[1], payment_hash_2, 20_000_000);
let chan_feerate = get_feerate!(nodes[0], nodes[1], chan_id) as u64;
- let opt_anchors = get_opt_anchors!(nodes[0], nodes[1], chan_id);
+ let channel_type_features = get_channel_type_features!(nodes[0], nodes[1], chan_id);
// Get nodes[0]'s commitment transaction and HTLC-Timeout transactions
let as_txn = get_local_commitment_txn!(nodes[0], chan_id);
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
claimable_amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: node_a_commitment_claimable,
}, htlc_balance_known_preimage.clone(), htlc_balance_unknown_preimage.clone()]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
connect_blocks(&nodes[0], TEST_FINAL_CLTV - 1);
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
claimable_amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: node_a_commitment_claimable,
}, htlc_balance_known_preimage.clone(), htlc_balance_unknown_preimage.clone()]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
// call, as described, two hunks down.
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
claimable_amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: node_a_commitment_claimable,
}, Balance::ClaimableAwaitingConfirmations {
claimable_amount_satoshis: 10_000,
expect_payment_sent!(nodes[0], payment_preimage_2);
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
claimable_amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: node_a_commitment_claimable,
}, Balance::ClaimableAwaitingConfirmations {
claimable_amount_satoshis: 10_000,
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
claimable_amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: node_a_commitment_claimable,
}, Balance::ClaimableAwaitingConfirmations {
claimable_amount_satoshis: 10_000,
let htlc_cltv_timeout = nodes[0].best_block_info().1 + TEST_FINAL_CLTV + 1; // Note ChannelManager adds one to CLTV timeouts for safety
let chan_feerate = get_feerate!(nodes[0], nodes[1], chan_id) as u64;
- let opt_anchors = get_opt_anchors!(nodes[0], nodes[1], chan_id);
+ let channel_type_features = get_channel_type_features!(nodes[0], nodes[1], chan_id);
let a_sent_htlc_balance = Balance::MaybeTimeoutClaimableHTLC {
claimable_amount_satoshis: 10_000,
assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose {
claimable_amount_satoshis: 1_000_000 - 500_000 - 10_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
}, a_received_htlc_balance.clone(), a_sent_htlc_balance.clone()]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
let node_a_commitment_claimable = nodes[0].best_block_info().1 + BREAKDOWN_TIMEOUT as u32;
let as_pre_spend_claims = sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
claimable_amount_satoshis: 1_000_000 - 500_000 - 10_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: node_a_commitment_claimable,
}, a_received_htlc_balance.clone(), a_sent_htlc_balance.clone()]);
let as_timeout_claimable_height = nodes[0].best_block_info().1 + (BREAKDOWN_TIMEOUT as u32) - 1;
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
claimable_amount_satoshis: 1_000_000 - 500_000 - 10_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: node_a_commitment_claimable,
}, a_received_htlc_balance.clone(), Balance::ClaimableAwaitingConfirmations {
claimable_amount_satoshis: 10_000,
mine_transaction(&nodes[0], &bs_htlc_timeout_claim[0]);
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
claimable_amount_satoshis: 1_000_000 - 500_000 - 10_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: node_a_commitment_claimable,
}, a_received_htlc_balance.clone(), Balance::ClaimableAwaitingConfirmations {
claimable_amount_satoshis: 10_000,
connect_blocks(&nodes[0], 1);
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
claimable_amount_satoshis: 1_000_000 - 500_000 - 10_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: node_a_commitment_claimable,
}, Balance::ClaimableAwaitingConfirmations {
claimable_amount_satoshis: 10_000,
// Get the latest commitment transaction from A and then update the fee to revoke it
let as_revoked_txn = get_local_commitment_txn!(nodes[0], chan_id);
- let opt_anchors = get_opt_anchors!(nodes[0], nodes[1], chan_id);
+ let channel_type_features = get_channel_type_features!(nodes[0], nodes[1], chan_id);
let chan_feerate = get_feerate!(nodes[0], nodes[1], chan_id) as u64;
let to_self_unclaimed_balance = Balance::CounterpartyRevokedOutputClaimable {
claimable_amount_satoshis: 1_000_000 - 100_000 - 3_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 3 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 3 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
};
let to_self_claimed_avail_height;
let largest_htlc_unclaimed_balance = Balance::CounterpartyRevokedOutputClaimable {
};
let to_self_claimed_balance = Balance::ClaimableAwaitingConfirmations {
claimable_amount_satoshis: 1_000_000 - 100_000 - 3_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 3 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000
+ (channel::commitment_tx_base_weight(&channel_type_features) + 3 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000
- chan_feerate * claim_txn[3].weight() as u64 / 1000,
confirmation_height: to_self_claimed_avail_height,
};
confirmation_height: nodes[1].best_block_info().1 + 1,
}, Balance::ClaimableAwaitingConfirmations {
claimable_amount_satoshis: 1_000_000 - 100_000 - 3_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 3 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000
+ (channel::commitment_tx_base_weight(&channel_type_features) + 3 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000
- chan_feerate * claim_txn[3].weight() as u64 / 1000,
confirmation_height: to_self_claimed_avail_height,
}, Balance::ClaimableAwaitingConfirmations {
claim_payment(&nodes[0], &[&nodes[1]], payment_preimage);
let chan_feerate = get_feerate!(nodes[0], nodes[1], chan_id) as u64;
- let opt_anchors = get_opt_anchors!(nodes[0], nodes[1], chan_id);
+ let channel_type_features = get_channel_type_features!(nodes[0], nodes[1], chan_id);
// B will generate an HTLC-Success from its revoked commitment tx
mine_transaction(&nodes[1], &revoked_local_txn[0]);
let as_balances = sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
// to_remote output in B's revoked commitment
claimable_amount_satoshis: 1_000_000 - 11_000 - 3_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: to_remote_conf_height,
}, Balance::CounterpartyRevokedOutputClaimable {
// to_self output in B's revoked commitment
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
// to_remote output in B's revoked commitment
claimable_amount_satoshis: 1_000_000 - 11_000 - 3_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: to_remote_conf_height,
}, Balance::CounterpartyRevokedOutputClaimable {
// to_self output in B's revoked commitment
check_spends!(as_revoked_txn[0], funding_tx);
check_spends!(as_revoked_txn[1], as_revoked_txn[0]); // The HTLC-Claim transaction
- let opt_anchors = get_opt_anchors!(nodes[0], nodes[1], chan_id);
+ let channel_type_features = get_channel_type_features!(nodes[0], nodes[1], chan_id);
let chan_feerate = get_feerate!(nodes[0], nodes[1], chan_id) as u64;
{
}, Balance::CounterpartyRevokedOutputClaimable {
// to_self output in A's revoked commitment
claimable_amount_satoshis: 1_000_000 - 100_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 1
claimable_amount_satoshis: 4_000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
}, Balance::CounterpartyRevokedOutputClaimable {
// to_self output in A's revoked commitment
claimable_amount_satoshis: 1_000_000 - 100_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 1
claimable_amount_satoshis: 4_000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable {
// to_self output in A's revoked commitment
claimable_amount_satoshis: 1_000_000 - 100_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 1
claimable_amount_satoshis: 4_000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable {
// to_self output in A's revoked commitment
claimable_amount_satoshis: 1_000_000 - 100_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 1
claimable_amount_satoshis: 4_000,
}, Balance::ClaimableAwaitingConfirmations { // HTLC 2
assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable {
// to_self output in A's revoked commitment
claimable_amount_satoshis: 1_000_000 - 100_000 - chan_feerate *
- (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ (channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 1
claimable_amount_satoshis: 4_000,
}]),
fn do_test_monitor_rebroadcast_pending_claims(anchors: bool) {
// Test that we will retry broadcasting pending claims for a force-closed channel on every
// `ChainMonitor::rebroadcast_pending_claims` call.
- if anchors {
- assert!(cfg!(anchors));
- }
let mut chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let mut config = test_default_channel_config();
if anchors {
- #[cfg(anchors)] {
- config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
- }
+ config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
+ config.manually_accept_inbound_channels = true;
}
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config), Some(config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
check_closed_event(&nodes[0], 1, ClosureReason::CommitmentTxConfirmed, false);
check_added_monitors(&nodes[0], 1);
+ let coinbase_tx = Transaction {
+ version: 2,
+ lock_time: PackedLockTime::ZERO,
+ input: vec![TxIn { ..Default::default() }],
+ output: vec![TxOut { // UTXO to attach fees to `htlc_tx` on anchors
+ value: Amount::ONE_BTC.to_sat(),
+ script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
+ }],
+ };
+ nodes[0].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 }, coinbase_tx.output[0].value);
+
// Set up a helper closure we'll use throughout our test. We should only expect retries without
// bumps if fees have not increased after a block has been connected (assuming the height timer
// re-evaluates at every block) or after `ChainMonitor::rebroadcast_pending_claims` is called.
let mut check_htlc_retry = |should_retry: bool, should_bump: bool| -> Option<Transaction> {
let (htlc_tx, htlc_tx_feerate) = if anchors {
assert!(nodes[0].tx_broadcaster.txn_broadcast().is_empty());
- let mut events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
+ let events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
assert_eq!(events.len(), if should_retry { 1 } else { 0 });
if !should_retry {
return None;
}
- #[allow(unused_assignments)]
- let mut tx = Transaction {
- version: 2,
- lock_time: bitcoin::PackedLockTime::ZERO,
- input: vec![],
- output: vec![],
- };
- #[allow(unused_assignments)]
- let mut feerate = 0;
- #[cfg(anchors)] {
- feerate = if let Event::BumpTransaction(BumpTransactionEvent::HTLCResolution {
- target_feerate_sat_per_1000_weight, mut htlc_descriptors, tx_lock_time,
- }) = events.pop().unwrap() {
- let secp = Secp256k1::new();
- assert_eq!(htlc_descriptors.len(), 1);
- let descriptor = htlc_descriptors.pop().unwrap();
- assert_eq!(descriptor.commitment_txid, commitment_txn[0].txid());
- let htlc_output_idx = descriptor.htlc.transaction_output_index.unwrap() as usize;
- assert!(htlc_output_idx < commitment_txn[0].output.len());
- tx.lock_time = tx_lock_time;
- // Note that we don't care about actually making the HTLC transaction meet the
- // feerate for the test, we just want to make sure the feerates we receive from
- // the events never decrease.
- tx.input.push(descriptor.unsigned_tx_input());
- let signer = nodes[0].keys_manager.derive_channel_keys(
- descriptor.channel_value_satoshis, &descriptor.channel_keys_id,
- );
- let per_commitment_point = signer.get_per_commitment_point(
- descriptor.per_commitment_number, &secp
- );
- tx.output.push(descriptor.tx_output(&per_commitment_point, &secp));
- let our_sig = signer.sign_holder_htlc_transaction(&mut tx, 0, &descriptor, &secp).unwrap();
- let witness_script = descriptor.witness_script(&per_commitment_point, &secp);
- tx.input[0].witness = descriptor.tx_input_witness(&our_sig, &witness_script);
- target_feerate_sat_per_1000_weight as u64
- } else { panic!("unexpected event"); };
+ match &events[0] {
+ Event::BumpTransaction(event) => {
+ nodes[0].bump_tx_handler.handle_event(&event);
+ let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast();
+ assert_eq!(txn.len(), 1);
+ let htlc_tx = txn.pop().unwrap();
+ check_spends!(&htlc_tx, &commitment_txn[0], &coinbase_tx);
+ let htlc_tx_fee = HTLC_AMT_SAT + coinbase_tx.output[0].value -
+ htlc_tx.output.iter().map(|output| output.value).sum::<u64>();
+ let htlc_tx_weight = htlc_tx.weight() as u64;
+ (htlc_tx, compute_feerate_sat_per_1000_weight(htlc_tx_fee, htlc_tx_weight))
+ }
+ _ => panic!("Unexpected event"),
}
- (tx, feerate)
} else {
assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
let htlc_tx = txn.pop().unwrap();
check_spends!(htlc_tx, commitment_txn[0]);
let htlc_tx_fee = HTLC_AMT_SAT - htlc_tx.output[0].value;
- let htlc_tx_feerate = htlc_tx_fee * 1000 / htlc_tx.weight() as u64;
- (htlc_tx, htlc_tx_feerate)
+ let htlc_tx_weight = htlc_tx.weight() as u64;
+ (htlc_tx, compute_feerate_sat_per_1000_weight(htlc_tx_fee, htlc_tx_weight))
};
if should_bump {
assert!(htlc_tx_feerate > prev_htlc_tx_feerate.take().unwrap());
// Mine the HTLC transaction to ensure we don't retry claims while they're confirmed.
mine_transaction(&nodes[0], &htlc_tx);
- // If we have a `ConnectStyle` that advertises the new block first without the transasctions,
+ // If we have a `ConnectStyle` that advertises the new block first without the transactions,
// we'll receive an extra bumped claim.
if nodes[0].connect_style.borrow().updates_best_block_first() {
+ nodes[0].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 }, coinbase_tx.output[0].value);
+ nodes[0].wallet_source.remove_utxo(bitcoin::OutPoint { txid: htlc_tx.txid(), vout: 1 });
check_htlc_retry(true, anchors);
}
nodes[0].chain_monitor.chain_monitor.rebroadcast_pending_claims();
#[test]
fn test_monitor_timer_based_claim() {
do_test_monitor_rebroadcast_pending_claims(false);
- #[cfg(anchors)]
do_test_monitor_rebroadcast_pending_claims(true);
}
-#[cfg(anchors)]
#[test]
fn test_yield_anchors_events() {
// Tests that two parties supporting anchor outputs can open a channel, route payments over
// allowing the consumer to provide additional fees to the commitment transaction to be
// broadcast. Once the commitment transaction confirms, events for the HTLC resolution should be
// emitted by LDK, such that the consumer can attach fees to the zero fee HTLC transactions.
- let secp = Secp256k1::new();
let mut chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let mut anchors_config = UserConfig::default();
anchors_config.channel_handshake_config.announced_channel = true;
anchors_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
+ anchors_config.manually_accept_inbound_channels = true;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(anchors_config), Some(anchors_config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
+ *nodes[0].fee_estimator.sat_per_kw.lock().unwrap() *= 2;
connect_blocks(&nodes[0], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1);
check_closed_broadcast!(&nodes[0], true);
assert!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().is_empty());
let mut holder_events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
assert_eq!(holder_events.len(), 1);
let (commitment_tx, anchor_tx) = match holder_events.pop().unwrap() {
- Event::BumpTransaction(BumpTransactionEvent::ChannelClose { commitment_tx, anchor_descriptor, .. }) => {
- assert_eq!(commitment_tx.input.len(), 1);
- assert_eq!(commitment_tx.output.len(), 6);
- let mut anchor_tx = Transaction {
+ Event::BumpTransaction(event) => {
+ let coinbase_tx = Transaction {
version: 2,
lock_time: PackedLockTime::ZERO,
- input: vec![
- TxIn { previous_output: anchor_descriptor.outpoint, ..Default::default() },
- TxIn { ..Default::default() },
- ],
- output: vec![TxOut {
+ input: vec![TxIn { ..Default::default() }],
+ output: vec![TxOut { // UTXO to attach fees to `anchor_tx`
value: Amount::ONE_BTC.to_sat(),
- script_pubkey: Script::new_op_return(&[]),
+ script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}],
};
- let signer = nodes[0].keys_manager.derive_channel_keys(
- anchor_descriptor.channel_value_satoshis, &anchor_descriptor.channel_keys_id,
- );
- let funding_sig = signer.sign_holder_anchor_input(&mut anchor_tx, 0, &secp).unwrap();
- anchor_tx.input[0].witness = chan_utils::build_anchor_input_witness(
- &signer.pubkeys().funding_pubkey, &funding_sig
- );
+ nodes[0].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 }, coinbase_tx.output[0].value);
+ nodes[0].bump_tx_handler.handle_event(&event);
+ let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast();
+ assert_eq!(txn.len(), 2);
+ let anchor_tx = txn.pop().unwrap();
+ let commitment_tx = txn.pop().unwrap();
+ check_spends!(anchor_tx, coinbase_tx, commitment_tx);
(commitment_tx, anchor_tx)
},
_ => panic!("Unexpected event"),
let mut htlc_txs = Vec::with_capacity(2);
for event in holder_events {
match event {
- Event::BumpTransaction(BumpTransactionEvent::HTLCResolution { htlc_descriptors, tx_lock_time, .. }) => {
- assert_eq!(htlc_descriptors.len(), 1);
- let htlc_descriptor = &htlc_descriptors[0];
- let signer = nodes[0].keys_manager.derive_channel_keys(
- htlc_descriptor.channel_value_satoshis, &htlc_descriptor.channel_keys_id
- );
- let per_commitment_point = signer.get_per_commitment_point(htlc_descriptor.per_commitment_number, &secp);
- let mut htlc_tx = Transaction {
- version: 2,
- lock_time: tx_lock_time,
- input: vec![
- htlc_descriptor.unsigned_tx_input(), // HTLC input
- TxIn { ..Default::default() } // Fee input
- ],
- output: vec![
- htlc_descriptor.tx_output(&per_commitment_point, &secp), // HTLC output
- TxOut { // Fee input change
- value: Amount::ONE_BTC.to_sat(),
- script_pubkey: Script::new_op_return(&[]),
- }
- ]
- };
- let our_sig = signer.sign_holder_htlc_transaction(&mut htlc_tx, 0, htlc_descriptor, &secp).unwrap();
- let witness_script = htlc_descriptor.witness_script(&per_commitment_point, &secp);
- htlc_tx.input[0].witness = htlc_descriptor.tx_input_witness(&our_sig, &witness_script);
+ Event::BumpTransaction(event) => {
+ nodes[0].bump_tx_handler.handle_event(&event);
+ let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast();
+ assert_eq!(txn.len(), 1);
+ let htlc_tx = txn.pop().unwrap();
+ check_spends!(htlc_tx, commitment_tx, anchor_tx);
htlc_txs.push(htlc_tx);
},
_ => panic!("Unexpected event"),
nodes[0].node.get_and_clear_pending_events();
}
-#[cfg(anchors)]
#[test]
fn test_anchors_aggregated_revoked_htlc_tx() {
// Test that `ChannelMonitor`s can properly detect and claim funds from a counterparty claiming
let mut anchors_config = UserConfig::default();
anchors_config.channel_handshake_config.announced_channel = true;
anchors_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
+ anchors_config.manually_accept_inbound_channels = true;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(anchors_config), Some(anchors_config)]);
let bob_persister: test_utils::TestPersister;
// Bob force closes by restarting with the outdated state, prompting the ChannelMonitors to
// broadcast the latest commitment transaction known to them, which in our case is the one with
// the HTLCs still pending.
+ *nodes[1].fee_estimator.sat_per_kw.lock().unwrap() *= 2;
nodes[1].node.timer_tick_occurred();
check_added_monitors(&nodes[1], 2);
check_closed_event!(&nodes[1], 2, ClosureReason::OutdatedChannelManager);
let (revoked_commitment_a, revoked_commitment_b) = {
- let txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
+ let txn = nodes[1].tx_broadcaster.unique_txn_broadcast();
assert_eq!(txn.len(), 2);
assert_eq!(txn[0].output.len(), 6); // 2 HTLC outputs + 1 to_self output + 1 to_remote output + 2 anchor outputs
assert_eq!(txn[1].output.len(), 6); // 2 HTLC outputs + 1 to_self output + 1 to_remote output + 2 anchor outputs
assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
let events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events();
assert_eq!(events.len(), 2);
- let anchor_tx = {
- let secret_key = SecretKey::from_slice(&[1; 32]).unwrap();
- let public_key = PublicKey::new(secret_key.public_key(&secp));
- let fee_utxo_script = Script::new_v0_p2wpkh(&public_key.wpubkey_hash().unwrap());
+ let mut anchor_txs = Vec::with_capacity(events.len());
+ for (idx, event) in events.into_iter().enumerate() {
+ let utxo_value = Amount::ONE_BTC.to_sat() * (idx + 1) as u64;
let coinbase_tx = Transaction {
version: 2,
lock_time: PackedLockTime::ZERO,
input: vec![TxIn { ..Default::default() }],
output: vec![TxOut { // UTXO to attach fees to `anchor_tx`
- value: Amount::ONE_BTC.to_sat(),
- script_pubkey: fee_utxo_script.clone(),
- }],
- };
- let mut anchor_tx = Transaction {
- version: 2,
- lock_time: PackedLockTime::ZERO,
- input: vec![
- TxIn { // Fee input
- previous_output: bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 },
- ..Default::default()
- },
- ],
- output: vec![TxOut { // Fee input change
- value: coinbase_tx.output[0].value / 2 ,
- script_pubkey: Script::new_op_return(&[]),
+ value: utxo_value,
+ script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
}],
};
- let mut signers = Vec::with_capacity(2);
- for event in events {
- match event {
- Event::BumpTransaction(BumpTransactionEvent::ChannelClose { anchor_descriptor, .. }) => {
- anchor_tx.input.push(TxIn {
- previous_output: anchor_descriptor.outpoint,
- ..Default::default()
- });
- let signer = nodes[1].keys_manager.derive_channel_keys(
- anchor_descriptor.channel_value_satoshis, &anchor_descriptor.channel_keys_id,
- );
- signers.push(signer);
- },
- _ => panic!("Unexpected event"),
- }
- }
- for (i, signer) in signers.into_iter().enumerate() {
- let anchor_idx = i + 1;
- let funding_sig = signer.sign_holder_anchor_input(&mut anchor_tx, anchor_idx, &secp).unwrap();
- anchor_tx.input[anchor_idx].witness = chan_utils::build_anchor_input_witness(
- &signer.pubkeys().funding_pubkey, &funding_sig
- );
- }
- let fee_utxo_sig = {
- let witness_script = Script::new_p2pkh(&public_key.pubkey_hash());
- let sighash = hash_to_message!(&SighashCache::new(&anchor_tx).segwit_signature_hash(
- 0, &witness_script, coinbase_tx.output[0].value, EcdsaSighashType::All
- ).unwrap()[..]);
- let sig = sign(&secp, &sighash, &secret_key);
- let mut sig = sig.serialize_der().to_vec();
- sig.push(EcdsaSighashType::All as u8);
- sig
+ nodes[1].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 }, utxo_value);
+ match event {
+ Event::BumpTransaction(event) => nodes[1].bump_tx_handler.handle_event(&event),
+ _ => panic!("Unexpected event"),
};
- anchor_tx.input[0].witness = Witness::from_vec(vec![fee_utxo_sig, public_key.to_bytes()]);
- check_spends!(anchor_tx, coinbase_tx, revoked_commitment_a, revoked_commitment_b);
- anchor_tx
+ let txn = nodes[1].tx_broadcaster.txn_broadcast();
+ assert_eq!(txn.len(), 2);
+ let (commitment_tx, anchor_tx) = (&txn[0], &txn[1]);
+ check_spends!(anchor_tx, coinbase_tx, commitment_tx);
+ anchor_txs.push(anchor_tx.clone());
};
for node in &nodes {
- mine_transactions(node, &[&revoked_commitment_a, &revoked_commitment_b, &anchor_tx]);
+ mine_transactions(node, &[&revoked_commitment_a, &anchor_txs[0], &revoked_commitment_b, &anchor_txs[1]]);
}
check_added_monitors!(&nodes[0], 2);
check_closed_broadcast(&nodes[0], 2, true);
};
let mut descriptors = Vec::with_capacity(4);
for event in events {
+ // We don't use the `BumpTransactionEventHandler` here because it does not support
+ // creating one transaction from multiple `HTLCResolution` events.
if let Event::BumpTransaction(BumpTransactionEvent::HTLCResolution { mut htlc_descriptors, tx_lock_time, .. }) = event {
assert_eq!(htlc_descriptors.len(), 2);
for htlc_descriptor in &htlc_descriptors {
assert!(!htlc_descriptor.htlc.offered);
- let signer = nodes[1].keys_manager.derive_channel_keys(
- htlc_descriptor.channel_value_satoshis, &htlc_descriptor.channel_keys_id
- );
- let per_commitment_point = signer.get_per_commitment_point(htlc_descriptor.per_commitment_number, &secp);
htlc_tx.input.push(htlc_descriptor.unsigned_tx_input());
- htlc_tx.output.push(htlc_descriptor.tx_output(&per_commitment_point, &secp));
+ htlc_tx.output.push(htlc_descriptor.tx_output(&secp));
}
descriptors.append(&mut htlc_descriptors);
htlc_tx.lock_time = tx_lock_time;
}
for (idx, htlc_descriptor) in descriptors.into_iter().enumerate() {
let htlc_input_idx = idx + 1;
- let signer = nodes[1].keys_manager.derive_channel_keys(
- htlc_descriptor.channel_value_satoshis, &htlc_descriptor.channel_keys_id
- );
+ let signer = htlc_descriptor.derive_channel_signer(&nodes[1].keys_manager);
let our_sig = signer.sign_holder_htlc_transaction(&htlc_tx, htlc_input_idx, &htlc_descriptor, &secp).unwrap();
- let per_commitment_point = signer.get_per_commitment_point(htlc_descriptor.per_commitment_number, &secp);
- let witness_script = htlc_descriptor.witness_script(&per_commitment_point, &secp);
+ let witness_script = htlc_descriptor.witness_script(&secp);
htlc_tx.input[htlc_input_idx].witness = htlc_descriptor.tx_input_witness(&our_sig, &witness_script);
}
let fee_utxo_sig = {
pub payment_hash: PaymentHash,
/// The expiry height of the HTLC
pub cltv_expiry: u32,
+ /// The extra fee skimmed by the sender of this message. See
+ /// [`ChannelConfig::accept_underpaying_htlcs`].
+ ///
+ /// [`ChannelConfig::accept_underpaying_htlcs`]: crate::util::config::ChannelConfig::accept_underpaying_htlcs
+ pub skimmed_fee_msat: Option<u64>,
pub(crate) onion_routing_packet: OnionPacket,
}
amount_msat,
payment_hash,
cltv_expiry,
- onion_routing_packet
-}, {});
+ onion_routing_packet,
+}, {
+ (65537, skimmed_fee_msat, option)
+});
impl Readable for OnionMessage {
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
amount_msat: 3608586615801332854,
payment_hash: PaymentHash([1; 32]),
cltv_expiry: 821716,
- onion_routing_packet
+ onion_routing_packet,
+ skimmed_fee_msat: None,
};
let encoded_value = update_add_htlc.encode();
let target_value = hex::decode("020202020202020202020202020202020202020202020202020202020202020200083a840000034d32144668701144760101010101010101010101010101010101010101010101010101010101010101000c89d4ff031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202").unwrap();
let test_bytes = vec![42u8; 1000];
if let OnionHopDataFormat::NonFinalNode { short_channel_id } = payload.format {
_encode_varint_length_prefixed_tlv!(&mut encoded_payload, {
- (1, test_bytes, vec_type),
+ (1, test_bytes, required_vec),
(2, HighZeroBytesDroppedBigSize(payload.amt_to_forward), required),
(4, HighZeroBytesDroppedBigSize(payload.outgoing_cltv_value), required),
(6, short_channel_id, required)
use crate::ln::onion_utils;
use crate::routing::gossip::{NetworkUpdate, RoutingFees};
use crate::routing::router::{get_route, PaymentParameters, Route, RouteHint, RouteHintHop};
-use crate::ln::features::{InitFeatures, InvoiceFeatures};
+use crate::ln::features::{InitFeatures, Bolt11InvoiceFeatures};
use crate::ln::msgs;
use crate::ln::msgs::{ChannelMessageHandler, ChannelUpdate};
use crate::ln::wire::Encode;
use crate::util::ser::{Writeable, Writer};
use crate::util::test_utils;
-use crate::util::config::{UserConfig, ChannelConfig};
+use crate::util::config::{UserConfig, ChannelConfig, MaxDustHTLCExposure};
use crate::util::errors::APIError;
use bitcoin::hash_types::BlockHash;
config.channel_handshake_config.announced_channel = announced_channel;
config.channel_handshake_limits.force_announced_channel_preference = false;
config.accept_forwards_to_priv_channels = !announced_channel;
+ config.channel_config.max_dust_htlc_exposure = MaxDustHTLCExposure::FeeRateMultiplier(5_000_000 / 253);
let chanmon_cfgs = create_chanmon_cfgs(3);
let persister;
let chain_monitor;
create_announced_chan_between_nodes(&nodes, 1, 2);
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)
- .with_bolt11_features(InvoiceFeatures::empty()).unwrap();
+ .with_bolt11_features(Bolt11InvoiceFeatures::empty()).unwrap();
let (route, _payment_hash, _payment_preimage, _payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 40000);
let hops = &route.paths[0].hops;
#[test]
fn test_phantom_dust_exposure_failure() {
+ do_test_phantom_dust_exposure_failure(false);
+ do_test_phantom_dust_exposure_failure(true);
+}
+
+fn do_test_phantom_dust_exposure_failure(multiplier_dust_limit: bool) {
// Set the max dust exposure to the dust limit.
let max_dust_exposure = 546;
let mut receiver_config = UserConfig::default();
- receiver_config.channel_config.max_dust_htlc_exposure_msat = max_dust_exposure;
+ // Default test fee estimator rate is 253, so to set the max dust exposure to the dust limit,
+ // we need to set the multiplier to 2.
+ receiver_config.channel_config.max_dust_htlc_exposure =
+ if multiplier_dust_limit { MaxDustHTLCExposure::FeeRateMultiplier(2) }
+ else { MaxDustHTLCExposure::FixedLimitMsat(max_dust_exposure) };
receiver_config.channel_handshake_config.announced_channel = true;
let chanmon_cfgs = create_chanmon_cfgs(2);
},
(1, Reason) => {
(0, failure_code, required),
- (2, data, vec_type),
+ (2, data, required_vec),
},
;);
},
#[cfg(all(not(feature = "no-std"), not(test)))]
(Retry::Timeout(max_duration), PaymentAttempts { first_attempted_at, .. }) =>
- *max_duration >= std::time::Instant::now().duration_since(*first_attempted_at),
+ *max_duration >= crate::util::time::MonotonicTime::now().duration_since(*first_attempted_at),
#[cfg(all(not(feature = "no-std"), test))]
(Retry::Timeout(max_duration), PaymentAttempts { first_attempted_at, .. }) =>
*max_duration >= SinceEpoch::now().duration_since(*first_attempted_at),
}
#[cfg(not(any(feature = "no-std", test)))]
-type ConfiguredTime = std::time::Instant;
+type ConfiguredTime = crate::util::time::MonotonicTime;
#[cfg(feature = "no-std")]
type ConfiguredTime = crate::util::time::Eternity;
#[cfg(all(not(feature = "no-std"), test))]
let route = router.find_route_with_id(
&node_signer.get_node_id(Recipient::Node).unwrap(), &route_params,
- Some(&first_hops.iter().collect::<Vec<_>>()), &inflight_htlcs(),
+ Some(&first_hops.iter().collect::<Vec<_>>()), inflight_htlcs(),
payment_hash, payment_id,
).map_err(|_| RetryableSendFailure::RouteNotFound)?;
let route = match router.find_route_with_id(
&node_signer.get_node_id(Recipient::Node).unwrap(), &route_params,
- Some(&first_hops.iter().collect::<Vec<_>>()), &inflight_htlcs(),
+ Some(&first_hops.iter().collect::<Vec<_>>()), inflight_htlcs(),
payment_hash, payment_id,
) {
Ok(route) => route,
use crate::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentFailureReason};
use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, ChannelManager, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, IDEMPOTENCY_TIMEOUT_TICKS, RecentPaymentDetails, RecipientOnionFields, HTLCForwardInfo, PendingHTLCRouting, PendingAddHTLCInfo};
-use crate::ln::features::InvoiceFeatures;
+use crate::ln::features::Bolt11InvoiceFeatures;
use crate::ln::{msgs, PaymentSecret, PaymentPreimage};
use crate::ln::msgs::ChannelMessageHandler;
use crate::ln::outbound_payment::Retry;
}
}
+#[test]
+fn accept_underpaying_htlcs_config() {
+ do_accept_underpaying_htlcs_config(1);
+ do_accept_underpaying_htlcs_config(2);
+ do_accept_underpaying_htlcs_config(3);
+}
+
+fn do_accept_underpaying_htlcs_config(num_mpp_parts: usize) {
+ let chanmon_cfgs = create_chanmon_cfgs(3);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+ let mut intercept_forwards_config = test_default_channel_config();
+ intercept_forwards_config.accept_intercept_htlcs = true;
+ let mut underpay_config = test_default_channel_config();
+ underpay_config.channel_config.accept_underpaying_htlcs = true;
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, Some(intercept_forwards_config), Some(underpay_config)]);
+ let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+
+ let mut chan_ids = Vec::new();
+ for _ in 0..num_mpp_parts {
+ let _ = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000, 0);
+ let channel_id = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 2_000_000, 0).0.channel_id;
+ chan_ids.push(channel_id);
+ }
+
+ // Send the initial payment.
+ let amt_msat = 900_000;
+ let skimmed_fee_msat = 20;
+ let mut route_hints = Vec::new();
+ for _ in 0..num_mpp_parts {
+ route_hints.push(RouteHint(vec![RouteHintHop {
+ src_node_id: nodes[1].node.get_our_node_id(),
+ short_channel_id: nodes[1].node.get_intercept_scid(),
+ fees: RoutingFees {
+ base_msat: 1000,
+ proportional_millionths: 0,
+ },
+ cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA,
+ htlc_minimum_msat: None,
+ htlc_maximum_msat: Some(amt_msat / num_mpp_parts as u64 + 5),
+ }]));
+ }
+ let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)
+ .with_route_hints(route_hints).unwrap()
+ .with_bolt11_features(nodes[2].node.invoice_features()).unwrap();
+ let route_params = RouteParameters {
+ payment_params,
+ final_value_msat: amt_msat,
+ };
+ let (payment_hash, payment_secret) = nodes[2].node.create_inbound_payment(Some(amt_msat), 60 * 60, None).unwrap();
+ nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
+ PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
+ check_added_monitors!(nodes[0], num_mpp_parts); // one monitor per path
+ let mut events: Vec<SendEvent> = nodes[0].node.get_and_clear_pending_msg_events().into_iter().map(|e| SendEvent::from_event(e)).collect();
+ assert_eq!(events.len(), num_mpp_parts);
+
+ // Forward the intercepted payments.
+ for (idx, ev) in events.into_iter().enumerate() {
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &ev.msgs[0]);
+ do_commitment_signed_dance(&nodes[1], &nodes[0], &ev.commitment_msg, false, true);
+
+ let events = nodes[1].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ let (intercept_id, expected_outbound_amt_msat) = match events[0] {
+ crate::events::Event::HTLCIntercepted {
+ intercept_id, expected_outbound_amount_msat, payment_hash: pmt_hash, ..
+ } => {
+ assert_eq!(pmt_hash, payment_hash);
+ (intercept_id, expected_outbound_amount_msat)
+ },
+ _ => panic!()
+ };
+ nodes[1].node.forward_intercepted_htlc(intercept_id, &chan_ids[idx],
+ nodes[2].node.get_our_node_id(), expected_outbound_amt_msat - skimmed_fee_msat).unwrap();
+ expect_pending_htlcs_forwardable!(nodes[1]);
+ let payment_event = {
+ {
+ let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap();
+ assert_eq!(added_monitors.len(), 1);
+ added_monitors.clear();
+ }
+ let mut events = nodes[1].node.get_and_clear_pending_msg_events();
+ assert_eq!(events.len(), 1);
+ SendEvent::from_event(events.remove(0))
+ };
+ nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event.msgs[0]);
+ do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event.commitment_msg, false, true);
+ if idx == num_mpp_parts - 1 {
+ expect_pending_htlcs_forwardable!(nodes[2]);
+ }
+ }
+
+ // Claim the payment and check that the skimmed fee is as expected.
+ let payment_preimage = nodes[2].node.get_payment_preimage(payment_hash, payment_secret).unwrap();
+ let events = nodes[2].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match events[0] {
+ crate::events::Event::PaymentClaimable {
+ ref payment_hash, ref purpose, amount_msat, counterparty_skimmed_fee_msat, receiver_node_id, ..
+ } => {
+ assert_eq!(payment_hash, payment_hash);
+ assert_eq!(amt_msat - skimmed_fee_msat * num_mpp_parts as u64, amount_msat);
+ assert_eq!(skimmed_fee_msat * num_mpp_parts as u64, counterparty_skimmed_fee_msat);
+ assert_eq!(nodes[2].node.get_our_node_id(), receiver_node_id.unwrap());
+ match purpose {
+ crate::events::PaymentPurpose::InvoicePayment { payment_preimage: ev_payment_preimage,
+ payment_secret: ev_payment_secret, .. } =>
+ {
+ assert_eq!(payment_preimage, ev_payment_preimage.unwrap());
+ assert_eq!(payment_secret, *ev_payment_secret);
+ },
+ _ => panic!(),
+ }
+ },
+ _ => panic!("Unexpected event"),
+ }
+ let mut expected_paths_vecs = Vec::new();
+ let mut expected_paths = Vec::new();
+ for _ in 0..num_mpp_parts { expected_paths_vecs.push(vec!(&nodes[1], &nodes[2])); }
+ for i in 0..num_mpp_parts { expected_paths.push(&expected_paths_vecs[i][..]); }
+ let total_fee_msat = do_claim_payment_along_route_with_extra_penultimate_hop_fees(
+ &nodes[0], &expected_paths[..], &vec![skimmed_fee_msat as u32; num_mpp_parts][..], false,
+ payment_preimage);
+ // The sender doesn't know that the penultimate hop took an extra fee.
+ expect_payment_sent(&nodes[0], payment_preimage,
+ Some(Some(total_fee_msat - skimmed_fee_msat * num_mpp_parts as u64)), true);
+}
+
#[derive(PartialEq)]
enum AutoRetry {
Success,
#[cfg(not(feature = "std"))]
let payment_expiry_secs = 60 * 60;
let amt_msat = 1000;
- let mut invoice_features = InvoiceFeatures::empty();
+ let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_variable_length_onion_required();
invoice_features.set_payment_secret_required();
invoice_features.set_basic_mpp_optional();
let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
#[cfg(not(feature = "std"))]
let payment_expiry_secs = 60 * 60;
- let mut invoice_features = InvoiceFeatures::empty();
+ let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_variable_length_onion_required();
invoice_features.set_payment_secret_required();
invoice_features.set_basic_mpp_optional();
let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
#[cfg(not(feature = "std"))]
let payment_expiry_secs = 60 * 60;
- let mut invoice_features = InvoiceFeatures::empty();
+ let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_variable_length_onion_required();
invoice_features.set_payment_secret_required();
invoice_features.set_basic_mpp_optional();
let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
#[cfg(not(feature = "std"))]
let payment_expiry_secs = 60 * 60;
- let mut invoice_features = InvoiceFeatures::empty();
+ let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_variable_length_onion_required();
invoice_features.set_payment_secret_required();
invoice_features.set_basic_mpp_optional();
let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
#[cfg(not(feature = "std"))]
let payment_expiry_secs = 60 * 60;
- let mut invoice_features = InvoiceFeatures::empty();
+ let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_variable_length_onion_required();
invoice_features.set_payment_secret_required();
invoice_features.set_basic_mpp_optional();
let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
#[cfg(not(feature = "std"))]
let payment_expiry_secs = 60 * 60;
- let mut invoice_features = InvoiceFeatures::empty();
+ let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_variable_length_onion_required();
invoice_features.set_payment_secret_required();
invoice_features.set_basic_mpp_optional();
let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
#[cfg(not(feature = "std"))]
let payment_expiry_secs = 60 * 60;
- let mut invoice_features = InvoiceFeatures::empty();
+ let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_variable_length_onion_required();
invoice_features.set_payment_secret_required();
invoice_features.set_basic_mpp_optional();
let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
#[cfg(not(feature = "std"))]
let payment_expiry_secs = 60 * 60;
- let mut invoice_features = InvoiceFeatures::empty();
+ let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_variable_length_onion_required();
invoice_features.set_payment_secret_required();
invoice_features.set_basic_mpp_optional();
let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
#[cfg(not(feature = "std"))]
let payment_expiry_secs = 60 * 60;
- let mut invoice_features = InvoiceFeatures::empty();
+ let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_variable_length_onion_required();
invoice_features.set_payment_secret_required();
invoice_features.set_basic_mpp_optional();
final_value_msat: 10_000_000,
};
let mut route = nodes[0].router.find_route(&nodes[0].node.get_our_node_id(), &route_params,
- None, &nodes[0].node.compute_inflight_htlcs()).unwrap();
+ None, nodes[0].node.compute_inflight_htlcs()).unwrap();
// Make sure the route is ordered as the B->D path before C->D
route.paths.sort_by(|a, _| if a.hops[0].pubkey == nodes[1].node.get_our_node_id() {
std::cmp::Ordering::Less } else { std::cmp::Ordering::Greater });
/// issues such as overly long function definitions.
///
/// This is not exported to bindings users as `Arc`s don't make sense in bindings.
-pub type SimpleArcPeerManager<SD, M, T, F, C, L, R> = PeerManager<
+pub type SimpleArcPeerManager<SD, M, T, F, C, L> = PeerManager<
SD,
Arc<SimpleArcChannelManager<M, T, F, L>>,
Arc<P2PGossipSync<Arc<NetworkGraph<Arc<L>>>, Arc<C>, Arc<L>>>,
- Arc<SimpleArcOnionMessenger<L, R>>,
+ Arc<SimpleArcOnionMessenger<L>>,
Arc<L>,
IgnoringMessageHandler,
Arc<KeysManager>
///
/// This is not exported to bindings users as general type aliases don't make sense in bindings.
pub type SimpleRefPeerManager<
- 'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k, 'l, 'm, 'n, SD, M, T, F, C, L, R
+ 'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k, 'l, 'm, 'n, SD, M, T, F, C, L
> = PeerManager<
SD,
&'n SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'm, M, T, F, L>,
&'f P2PGossipSync<&'g NetworkGraph<&'f L>, &'h C, &'f L>,
- &'i SimpleRefOnionMessenger<'g, 'm, 'n, L, R>,
+ &'i SimpleRefOnionMessenger<'g, 'm, 'n, L>,
&'f L,
IgnoringMessageHandler,
&'c KeysManager
/// A generic trait which is implemented for all [`PeerManager`]s. This makes bounding functions or
/// structs on any [`PeerManager`] much simpler as only this trait is needed as a bound, rather
/// than the full set of bounds on [`PeerManager`] itself.
+///
+/// This is not exported to bindings users as general cover traits aren't useful in other
+/// languages.
#[allow(missing_docs)]
pub trait APeerManager {
type Descriptor: SocketDescriptor;
use crate::ln::msgs;
use crate::ln::msgs::{ChannelMessageHandler, RoutingMessageHandler, ChannelUpdate, ErrorAction};
use crate::ln::wire::Encode;
-use crate::util::config::UserConfig;
+use crate::util::config::{UserConfig, MaxDustHTLCExposure};
use crate::util::ser::Writeable;
use crate::util::test_utils;
alice_config.channel_handshake_config.minimum_depth = 1;
alice_config.channel_handshake_config.announced_channel = true;
alice_config.channel_handshake_limits.force_announced_channel_preference = false;
+ alice_config.channel_config.max_dust_htlc_exposure = MaxDustHTLCExposure::FeeRateMultiplier(5_000_000 / 253);
let mut bob_config = UserConfig::default();
bob_config.channel_handshake_config.minimum_depth = 1;
bob_config.channel_handshake_config.announced_channel = true;
bob_config.channel_handshake_limits.force_announced_channel_preference = false;
+ bob_config.channel_config.max_dust_htlc_exposure = MaxDustHTLCExposure::FeeRateMultiplier(5_000_000 / 253);
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(alice_config), Some(bob_config)]);
fee_estimator = test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) };
persister = test_utils::TestPersister::new();
let keys_manager = &chanmon_cfgs[0].keys_manager;
- new_chain_monitor = test_utils::TestChainMonitor::new(Some(nodes[0].chain_source), nodes[0].tx_broadcaster.clone(), &logger, &fee_estimator, &persister, keys_manager);
+ new_chain_monitor = test_utils::TestChainMonitor::new(Some(nodes[0].chain_source), nodes[0].tx_broadcaster, &logger, &fee_estimator, &persister, keys_manager);
nodes[0].chain_monitor = &new_chain_monitor;
use crate::sign::{EntropySource, SignerProvider};
use crate::chain::transaction::OutPoint;
use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureReason};
-use crate::ln::channelmanager::{self, PaymentSendFailure, PaymentId, RecipientOnionFields};
+use crate::ln::channelmanager::{self, PaymentSendFailure, PaymentId, RecipientOnionFields, ChannelShutdownState, ChannelDetails};
use crate::routing::router::{PaymentParameters, get_route};
use crate::ln::msgs;
use crate::ln::msgs::{ChannelMessageHandler, ErrorAction};
check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
}
+#[test]
+fn expect_channel_shutdown_state() {
+ // Test sending a shutdown prior to channel_ready after funding generation
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+ let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1);
+
+ expect_channel_shutdown_state!(nodes[0], chan_1.2, ChannelShutdownState::NotShuttingDown);
+
+ nodes[0].node.close_channel(&chan_1.2, &nodes[1].node.get_our_node_id()).unwrap();
+
+ expect_channel_shutdown_state!(nodes[0], chan_1.2, ChannelShutdownState::ShutdownInitiated);
+ expect_channel_shutdown_state!(nodes[1], chan_1.2, ChannelShutdownState::NotShuttingDown);
+
+ let node_0_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_shutdown);
+
+ // node1 goes into NegotiatingClosingFee since there are no HTLCs in flight, note that it
+ // doesnt mean that node1 has sent/recved its closing signed message
+ expect_channel_shutdown_state!(nodes[0], chan_1.2, ChannelShutdownState::ShutdownInitiated);
+ expect_channel_shutdown_state!(nodes[1], chan_1.2, ChannelShutdownState::NegotiatingClosingFee);
+
+ let node_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_1_shutdown);
+
+ expect_channel_shutdown_state!(nodes[0], chan_1.2, ChannelShutdownState::NegotiatingClosingFee);
+ expect_channel_shutdown_state!(nodes[1], chan_1.2, ChannelShutdownState::NegotiatingClosingFee);
+
+ let node_0_closing_signed = get_event_msg!(nodes[0], MessageSendEvent::SendClosingSigned, nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_closing_signed);
+ let node_1_closing_signed = get_event_msg!(nodes[1], MessageSendEvent::SendClosingSigned, nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_closing_signed(&nodes[1].node.get_our_node_id(), &node_1_closing_signed);
+ let (_, node_0_2nd_closing_signed) = get_closing_signed_broadcast!(nodes[0].node, nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_2nd_closing_signed.unwrap());
+ let (_, node_1_none) = get_closing_signed_broadcast!(nodes[1].node, nodes[0].node.get_our_node_id());
+ assert!(node_1_none.is_none());
+
+ assert!(nodes[0].node.list_channels().is_empty());
+ assert!(nodes[1].node.list_channels().is_empty());
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
+}
+
+#[test]
+fn expect_channel_shutdown_state_with_htlc() {
+ // Test sending a shutdown with outstanding updates pending.
+ let chanmon_cfgs = create_chanmon_cfgs(3);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
+ let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+ let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1);
+ let _chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2);
+
+ let (payment_preimage_0, payment_hash_0, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 100_000);
+
+ expect_channel_shutdown_state!(nodes[0], chan_1.2, ChannelShutdownState::NotShuttingDown);
+ expect_channel_shutdown_state!(nodes[1], chan_1.2, ChannelShutdownState::NotShuttingDown);
+
+ nodes[0].node.close_channel(&chan_1.2, &nodes[1].node.get_our_node_id()).unwrap();
+
+ expect_channel_shutdown_state!(nodes[0], chan_1.2, ChannelShutdownState::ShutdownInitiated);
+ expect_channel_shutdown_state!(nodes[1], chan_1.2, ChannelShutdownState::NotShuttingDown);
+
+ let node_0_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_shutdown);
+
+ expect_channel_shutdown_state!(nodes[0], chan_1.2, ChannelShutdownState::ShutdownInitiated);
+ expect_channel_shutdown_state!(nodes[1], chan_1.2, ChannelShutdownState::ResolvingHTLCs);
+
+ let node_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_1_shutdown);
+
+ expect_channel_shutdown_state!(nodes[0], chan_1.2, ChannelShutdownState::ResolvingHTLCs);
+ expect_channel_shutdown_state!(nodes[1], chan_1.2, ChannelShutdownState::ResolvingHTLCs);
+
+ assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+
+ // Claim Funds on Node2
+ nodes[2].node.claim_funds(payment_preimage_0);
+ check_added_monitors!(nodes[2], 1);
+ expect_payment_claimed!(nodes[2], payment_hash_0, 100_000);
+
+ // Fulfil HTLCs on node1 and node0
+ let updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id());
+ assert!(updates.update_add_htlcs.is_empty());
+ assert!(updates.update_fail_htlcs.is_empty());
+ assert!(updates.update_fail_malformed_htlcs.is_empty());
+ assert!(updates.update_fee.is_none());
+ assert_eq!(updates.update_fulfill_htlcs.len(), 1);
+ nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &updates.update_fulfill_htlcs[0]);
+ expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], Some(1000), false, false);
+ check_added_monitors!(nodes[1], 1);
+ let updates_2 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ commitment_signed_dance!(nodes[1], nodes[2], updates.commitment_signed, false);
+
+ // Still in "resolvingHTLCs" on chan1 after htlc removed on chan2
+ expect_channel_shutdown_state!(nodes[0], chan_1.2, ChannelShutdownState::ResolvingHTLCs);
+ expect_channel_shutdown_state!(nodes[1], chan_1.2, ChannelShutdownState::ResolvingHTLCs);
+
+ assert!(updates_2.update_add_htlcs.is_empty());
+ assert!(updates_2.update_fail_htlcs.is_empty());
+ assert!(updates_2.update_fail_malformed_htlcs.is_empty());
+ assert!(updates_2.update_fee.is_none());
+ assert_eq!(updates_2.update_fulfill_htlcs.len(), 1);
+ nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &updates_2.update_fulfill_htlcs[0]);
+ commitment_signed_dance!(nodes[0], nodes[1], updates_2.commitment_signed, false, true);
+ expect_payment_sent!(nodes[0], payment_preimage_0);
+
+ // all htlcs removed, chan1 advances to NegotiatingClosingFee
+ expect_channel_shutdown_state!(nodes[0], chan_1.2, ChannelShutdownState::NegotiatingClosingFee);
+ expect_channel_shutdown_state!(nodes[1], chan_1.2, ChannelShutdownState::NegotiatingClosingFee);
+
+ // ClosingSignNegotion process
+ let node_0_closing_signed = get_event_msg!(nodes[0], MessageSendEvent::SendClosingSigned, nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_closing_signed);
+ let node_1_closing_signed = get_event_msg!(nodes[1], MessageSendEvent::SendClosingSigned, nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_closing_signed(&nodes[1].node.get_our_node_id(), &node_1_closing_signed);
+ let (_, node_0_2nd_closing_signed) = get_closing_signed_broadcast!(nodes[0].node, nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_2nd_closing_signed.unwrap());
+ let (_, node_1_none) = get_closing_signed_broadcast!(nodes[1].node, nodes[0].node.get_our_node_id());
+ assert!(node_1_none.is_none());
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
+
+ // Shutdown basically removes the channelDetails, testing of shutdowncomplete state unnecessary
+ assert!(nodes[0].node.list_channels().is_empty());
+}
+
+#[test]
+fn expect_channel_shutdown_state_with_force_closure() {
+ // Test sending a shutdown prior to channel_ready after funding generation
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+ let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1);
+
+ expect_channel_shutdown_state!(nodes[0], chan_1.2, ChannelShutdownState::NotShuttingDown);
+ expect_channel_shutdown_state!(nodes[1], chan_1.2, ChannelShutdownState::NotShuttingDown);
+
+ nodes[1].node.force_close_broadcasting_latest_txn(&chan_1.2, &nodes[0].node.get_our_node_id()).unwrap();
+ check_closed_broadcast!(nodes[1], true);
+ check_added_monitors!(nodes[1], 1);
+
+ expect_channel_shutdown_state!(nodes[0], chan_1.2, ChannelShutdownState::NotShuttingDown);
+ assert!(nodes[1].node.list_channels().is_empty());
+
+ let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
+ assert_eq!(node_txn.len(), 1);
+ check_spends!(node_txn[0], chan_1.3);
+ mine_transaction(&nodes[0], &node_txn[0]);
+ check_added_monitors!(nodes[0], 1);
+
+ assert!(nodes[0].node.list_channels().is_empty());
+ assert!(nodes[1].node.list_channels().is_empty());
+ check_closed_broadcast!(nodes[0], true);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed);
+}
+
#[test]
fn updates_shutdown_wait() {
// Test sending a shutdown with outstanding updates pending
//! Data structures and encoding for `invoice` messages.
//!
-//! An [`Invoice`] can be built from a parsed [`InvoiceRequest`] for the "offer to be paid" flow or
-//! from a [`Refund`] as an "offer for money" flow. The expected recipient of the payment then sends
-//! the invoice to the intended payer, who will then pay it.
+//! A [`Bolt12Invoice`] can be built from a parsed [`InvoiceRequest`] for the "offer to be paid"
+//! flow or from a [`Refund`] as an "offer for money" flow. The expected recipient of the payment
+//! then sends the invoice to the intended payer, who will then pay it.
//!
//! The payment recipient must include a [`PaymentHash`], so as to reveal the preimage upon payment
//! receipt, and one or more [`BlindedPath`]s for the payer to use when sending the payment.
//! # use lightning::offers::invoice::BlindedPayInfo;
//! # use lightning::blinded_path::BlindedPath;
//! #
-//! # fn create_payment_paths() -> Vec<(BlindedPath, BlindedPayInfo)> { unimplemented!() }
+//! # fn create_payment_paths() -> Vec<(BlindedPayInfo, BlindedPath)> { unimplemented!() }
//! # fn create_payment_hash() -> PaymentHash { unimplemented!() }
//! #
-//! # fn parse_invoice_request(bytes: Vec<u8>) -> Result<(), lightning::offers::parse::ParseError> {
+//! # fn parse_invoice_request(bytes: Vec<u8>) -> Result<(), lightning::offers::parse::Bolt12ParseError> {
//! let payment_paths = create_payment_paths();
//! let payment_hash = create_payment_hash();
//! let secp_ctx = Secp256k1::new();
//! # Ok(())
//! # }
//!
-//! # fn parse_refund(bytes: Vec<u8>) -> Result<(), lightning::offers::parse::ParseError> {
+//! # fn parse_refund(bytes: Vec<u8>) -> Result<(), lightning::offers::parse::Bolt12ParseError> {
//! # let payment_paths = create_payment_paths();
//! # let payment_hash = create_payment_hash();
//! # let secp_ctx = Secp256k1::new();
use crate::offers::invoice_request::{INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TlvStream, WithoutSignatures, self};
use crate::offers::offer::{Amount, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef};
-use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
+use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef};
use crate::offers::refund::{IV_BYTES as REFUND_IV_BYTES, Refund, RefundContents};
use crate::offers::signer;
pub(super) const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "signature");
-/// Builds an [`Invoice`] from either:
+/// Builds a [`Bolt12Invoice`] from either:
/// - an [`InvoiceRequest`] for the "offer to be paid" flow or
/// - a [`Refund`] for the "offer for money" flow.
///
signing_pubkey_strategy: core::marker::PhantomData<S>,
}
-/// Indicates how [`Invoice::signing_pubkey`] was set.
+/// Indicates how [`Bolt12Invoice::signing_pubkey`] was set.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
pub trait SigningPubkeyStrategy {}
-/// [`Invoice::signing_pubkey`] was explicitly set.
+/// [`Bolt12Invoice::signing_pubkey`] was explicitly set.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
pub struct ExplicitSigningPubkey {}
-/// [`Invoice::signing_pubkey`] was derived.
+/// [`Bolt12Invoice::signing_pubkey`] was derived.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
pub struct DerivedSigningPubkey {}
impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
pub(super) fn for_offer(
- invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>,
+ invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
created_at: Duration, payment_hash: PaymentHash
- ) -> Result<Self, SemanticError> {
+ ) -> Result<Self, Bolt12SemanticError> {
let amount_msats = Self::check_amount_msats(invoice_request)?;
let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
let contents = InvoiceContents::ForOffer {
}
pub(super) fn for_refund(
- refund: &'a Refund, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, created_at: Duration,
+ refund: &'a Refund, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration,
payment_hash: PaymentHash, signing_pubkey: PublicKey
- ) -> Result<Self, SemanticError> {
+ ) -> Result<Self, Bolt12SemanticError> {
let amount_msats = refund.amount_msats();
let contents = InvoiceContents::ForRefund {
refund: refund.contents.clone(),
impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
pub(super) fn for_offer_using_keys(
- invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>,
+ invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
created_at: Duration, payment_hash: PaymentHash, keys: KeyPair
- ) -> Result<Self, SemanticError> {
+ ) -> Result<Self, Bolt12SemanticError> {
let amount_msats = Self::check_amount_msats(invoice_request)?;
let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
let contents = InvoiceContents::ForOffer {
}
pub(super) fn for_refund_using_keys(
- refund: &'a Refund, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, created_at: Duration,
+ refund: &'a Refund, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration,
payment_hash: PaymentHash, keys: KeyPair,
- ) -> Result<Self, SemanticError> {
+ ) -> Result<Self, Bolt12SemanticError> {
let amount_msats = refund.amount_msats();
let signing_pubkey = keys.public_key();
let contents = InvoiceContents::ForRefund {
}
impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
- fn check_amount_msats(invoice_request: &InvoiceRequest) -> Result<u64, SemanticError> {
+ fn check_amount_msats(invoice_request: &InvoiceRequest) -> Result<u64, Bolt12SemanticError> {
match invoice_request.amount_msats() {
Some(amount_msats) => Ok(amount_msats),
None => match invoice_request.contents.inner.offer.amount() {
Some(Amount::Bitcoin { amount_msats }) => {
amount_msats.checked_mul(invoice_request.quantity().unwrap_or(1))
- .ok_or(SemanticError::InvalidAmount)
+ .ok_or(Bolt12SemanticError::InvalidAmount)
},
- Some(Amount::Currency { .. }) => Err(SemanticError::UnsupportedCurrency),
- None => Err(SemanticError::MissingAmount),
+ Some(Amount::Currency { .. }) => Err(Bolt12SemanticError::UnsupportedCurrency),
+ None => Err(Bolt12SemanticError::MissingAmount),
},
}
}
fn fields(
- payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, created_at: Duration,
+ payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration,
payment_hash: PaymentHash, amount_msats: u64, signing_pubkey: PublicKey
) -> InvoiceFields {
InvoiceFields {
fn new(
invreq_bytes: &'a Vec<u8>, contents: InvoiceContents, keys: Option<KeyPair>
- ) -> Result<Self, SemanticError> {
+ ) -> Result<Self, Bolt12SemanticError> {
if contents.fields().payment_paths.is_empty() {
- return Err(SemanticError::MissingPaths);
+ return Err(Bolt12SemanticError::MissingPaths);
}
Ok(Self {
})
}
- /// Sets the [`Invoice::relative_expiry`] as seconds since [`Invoice::created_at`]. Any expiry
- /// that has already passed is valid and can be checked for using [`Invoice::is_expired`].
+ /// Sets the [`Bolt12Invoice::relative_expiry`] as seconds since [`Bolt12Invoice::created_at`].
+ /// Any expiry that has already passed is valid and can be checked for using
+ /// [`Bolt12Invoice::is_expired`].
///
/// Successive calls to this method will override the previous setting.
pub fn relative_expiry(mut self, relative_expiry_secs: u32) -> Self {
self
}
- /// Adds a P2WSH address to [`Invoice::fallbacks`].
+ /// Adds a P2WSH address to [`Bolt12Invoice::fallbacks`].
///
/// Successive calls to this method will add another address. Caller is responsible for not
/// adding duplicate addresses and only calling if capable of receiving to P2WSH addresses.
self
}
- /// Adds a P2WPKH address to [`Invoice::fallbacks`].
+ /// Adds a P2WPKH address to [`Bolt12Invoice::fallbacks`].
///
/// Successive calls to this method will add another address. Caller is responsible for not
/// adding duplicate addresses and only calling if capable of receiving to P2WPKH addresses.
self
}
- /// Adds a P2TR address to [`Invoice::fallbacks`].
+ /// Adds a P2TR address to [`Bolt12Invoice::fallbacks`].
///
/// Successive calls to this method will add another address. Caller is responsible for not
/// adding duplicate addresses and only calling if capable of receiving to P2TR addresses.
self
}
- /// Sets [`Invoice::features`] to indicate MPP may be used. Otherwise, MPP is disallowed.
+ /// Sets [`Bolt12Invoice::features`] to indicate MPP may be used. Otherwise, MPP is disallowed.
pub fn allow_mpp(mut self) -> Self {
self.invoice.fields_mut().features.set_basic_mpp_optional();
self
}
impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
- /// Builds an unsigned [`Invoice`] after checking for valid semantics. It can be signed by
- /// [`UnsignedInvoice::sign`].
- pub fn build(self) -> Result<UnsignedInvoice<'a>, SemanticError> {
+ /// Builds an unsigned [`Bolt12Invoice`] after checking for valid semantics. It can be signed by
+ /// [`UnsignedBolt12Invoice::sign`].
+ pub fn build(self) -> Result<UnsignedBolt12Invoice<'a>, Bolt12SemanticError> {
#[cfg(feature = "std")] {
if self.invoice.is_offer_or_refund_expired() {
- return Err(SemanticError::AlreadyExpired);
+ return Err(Bolt12SemanticError::AlreadyExpired);
}
}
let InvoiceBuilder { invreq_bytes, invoice, .. } = self;
- Ok(UnsignedInvoice { invreq_bytes, invoice })
+ Ok(UnsignedBolt12Invoice { invreq_bytes, invoice })
}
}
impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
- /// Builds a signed [`Invoice`] after checking for valid semantics.
+ /// Builds a signed [`Bolt12Invoice`] after checking for valid semantics.
pub fn build_and_sign<T: secp256k1::Signing>(
self, secp_ctx: &Secp256k1<T>
- ) -> Result<Invoice, SemanticError> {
+ ) -> Result<Bolt12Invoice, Bolt12SemanticError> {
#[cfg(feature = "std")] {
if self.invoice.is_offer_or_refund_expired() {
- return Err(SemanticError::AlreadyExpired);
+ return Err(Bolt12SemanticError::AlreadyExpired);
}
}
let InvoiceBuilder { invreq_bytes, invoice, keys, .. } = self;
- let unsigned_invoice = UnsignedInvoice { invreq_bytes, invoice };
+ let unsigned_invoice = UnsignedBolt12Invoice { invreq_bytes, invoice };
let keys = keys.unwrap();
let invoice = unsigned_invoice
}
}
-/// A semantically valid [`Invoice`] that hasn't been signed.
-pub struct UnsignedInvoice<'a> {
+/// A semantically valid [`Bolt12Invoice`] that hasn't been signed.
+pub struct UnsignedBolt12Invoice<'a> {
invreq_bytes: &'a Vec<u8>,
invoice: InvoiceContents,
}
-impl<'a> UnsignedInvoice<'a> {
+impl<'a> UnsignedBolt12Invoice<'a> {
/// The public key corresponding to the key needed to sign the invoice.
pub fn signing_pubkey(&self) -> PublicKey {
self.invoice.fields().signing_pubkey
/// Signs the invoice using the given function.
///
/// This is not exported to bindings users as functions aren't currently mapped.
- pub fn sign<F, E>(self, sign: F) -> Result<Invoice, SignError<E>>
+ pub fn sign<F, E>(self, sign: F) -> Result<Bolt12Invoice, SignError<E>>
where
F: FnOnce(&Message) -> Result<Signature, E>
{
};
signature_tlv_stream.write(&mut bytes).unwrap();
- Ok(Invoice {
+ Ok(Bolt12Invoice {
bytes,
contents: self.invoice,
signature,
}
}
-/// An `Invoice` is a payment request, typically corresponding to an [`Offer`] or a [`Refund`].
+/// A `Bolt12Invoice` is a payment request, typically corresponding to an [`Offer`] or a [`Refund`].
///
/// An invoice may be sent in response to an [`InvoiceRequest`] in the case of an offer or sent
/// directly after scanning a refund. It includes all the information needed to pay a recipient.
///
-/// This is not exported to bindings users as its name conflicts with the BOLT 11 Invoice type.
-///
/// [`Offer`]: crate::offers::offer::Offer
/// [`Refund`]: crate::offers::refund::Refund
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
-pub struct Invoice {
+pub struct Bolt12Invoice {
bytes: Vec<u8>,
contents: InvoiceContents,
signature: Signature,
}
-/// The contents of an [`Invoice`] for responding to either an [`Offer`] or a [`Refund`].
+/// The contents of an [`Bolt12Invoice`] for responding to either an [`Offer`] or a [`Refund`].
///
/// [`Offer`]: crate::offers::offer::Offer
/// [`Refund`]: crate::offers::refund::Refund
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
enum InvoiceContents {
- /// Contents for an [`Invoice`] corresponding to an [`Offer`].
+ /// Contents for an [`Bolt12Invoice`] corresponding to an [`Offer`].
///
/// [`Offer`]: crate::offers::offer::Offer
ForOffer {
invoice_request: InvoiceRequestContents,
fields: InvoiceFields,
},
- /// Contents for an [`Invoice`] corresponding to a [`Refund`].
+ /// Contents for an [`Bolt12Invoice`] corresponding to a [`Refund`].
///
/// [`Refund`]: crate::offers::refund::Refund
ForRefund {
/// Invoice-specific fields for an `invoice` message.
#[derive(Clone, Debug, PartialEq)]
struct InvoiceFields {
- payment_paths: Vec<(BlindedPath, BlindedPayInfo)>,
+ payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
created_at: Duration,
relative_expiry: Option<Duration>,
payment_hash: PaymentHash,
signing_pubkey: PublicKey,
}
-impl Invoice {
+impl Bolt12Invoice {
/// A complete description of the purpose of the originating offer or refund. Intended to be
/// displayed to the user but with the caveat that it has not been verified in any way.
pub fn description(&self) -> PrintableString {
/// needed for routing payments across them.
///
/// Blinded paths provide recipient privacy by obfuscating its node id. Note, however, that this
- /// privacy is lost if a public node id is used for [`Invoice::signing_pubkey`].
- pub fn payment_paths(&self) -> &[(BlindedPath, BlindedPayInfo)] {
+ /// privacy is lost if a public node id is used for [`Bolt12Invoice::signing_pubkey`].
+ ///
+ /// This is not exported to bindings users as slices with non-reference types cannot be ABI
+ /// matched in another language.
+ pub fn payment_paths(&self) -> &[(BlindedPayInfo, BlindedPath)] {
&self.contents.fields().payment_paths[..]
}
self.contents.fields().created_at
}
- /// Duration since [`Invoice::created_at`] when the invoice has expired and therefore should no
- /// longer be paid.
+ /// Duration since [`Bolt12Invoice::created_at`] when the invoice has expired and therefore
+ /// should no longer be paid.
pub fn relative_expiry(&self) -> Duration {
self.contents.fields().relative_expiry.unwrap_or(DEFAULT_RELATIVE_EXPIRY)
}
self.contents.fields().signing_pubkey
}
- /// Signature of the invoice verified using [`Invoice::signing_pubkey`].
+ /// Signature of the invoice verified using [`Bolt12Invoice::signing_pubkey`].
pub fn signature(&self) -> Signature {
self.signature
}
};
InvoiceTlvStreamRef {
- paths: Some(Iterable(self.payment_paths.iter().map(|(path, _)| path))),
- blindedpay: Some(Iterable(self.payment_paths.iter().map(|(_, payinfo)| payinfo))),
+ paths: Some(Iterable(self.payment_paths.iter().map(|(_, path)| path))),
+ blindedpay: Some(Iterable(self.payment_paths.iter().map(|(payinfo, _)| payinfo))),
created_at: Some(self.created_at.as_secs()),
relative_expiry: self.relative_expiry.map(|duration| duration.as_secs() as u32),
payment_hash: Some(&self.payment_hash),
}
}
-impl Writeable for Invoice {
+impl Writeable for Bolt12Invoice {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
WithoutLength(&self.bytes).write(writer)
}
}
}
-impl TryFrom<Vec<u8>> for Invoice {
- type Error = ParseError;
+impl TryFrom<Vec<u8>> for Bolt12Invoice {
+ type Error = Bolt12ParseError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
let parsed_invoice = ParsedMessage::<FullInvoiceTlvStream>::try_from(bytes)?;
- Invoice::try_from(parsed_invoice)
+ Bolt12Invoice::try_from(parsed_invoice)
}
}
});
type BlindedPathIter<'a> = core::iter::Map<
- core::slice::Iter<'a, (BlindedPath, BlindedPayInfo)>,
- for<'r> fn(&'r (BlindedPath, BlindedPayInfo)) -> &'r BlindedPath,
+ core::slice::Iter<'a, (BlindedPayInfo, BlindedPath)>,
+ for<'r> fn(&'r (BlindedPayInfo, BlindedPath)) -> &'r BlindedPath,
>;
type BlindedPayInfoIter<'a> = core::iter::Map<
- core::slice::Iter<'a, (BlindedPath, BlindedPayInfo)>,
- for<'r> fn(&'r (BlindedPath, BlindedPayInfo)) -> &'r BlindedPayInfo,
+ core::slice::Iter<'a, (BlindedPayInfo, BlindedPath)>,
+ for<'r> fn(&'r (BlindedPayInfo, BlindedPath)) -> &'r BlindedPayInfo,
>;
/// Information needed to route a payment across a [`BlindedPath`].
InvoiceTlvStreamRef<'a>,
);
-impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Invoice {
- type Error = ParseError;
+impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
+ type Error = Bolt12ParseError;
fn try_from(invoice: ParsedMessage<FullInvoiceTlvStream>) -> Result<Self, Self::Error> {
let ParsedMessage { bytes, tlv_stream } = invoice;
)?;
let signature = match signature {
- None => return Err(ParseError::InvalidSemantics(SemanticError::MissingSignature)),
+ None => return Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)),
Some(signature) => signature,
};
let pubkey = contents.fields().signing_pubkey;
merkle::verify_signature(&signature, SIGNATURE_TAG, &bytes, pubkey)?;
- Ok(Invoice { bytes, contents, signature })
+ Ok(Bolt12Invoice { bytes, contents, signature })
}
}
impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
- type Error = SemanticError;
+ type Error = Bolt12SemanticError;
fn try_from(tlv_stream: PartialInvoiceTlvStream) -> Result<Self, Self::Error> {
let (
},
) = tlv_stream;
- let payment_paths = match (paths, blindedpay) {
- (None, _) => return Err(SemanticError::MissingPaths),
- (_, None) => return Err(SemanticError::InvalidPayInfo),
- (Some(paths), _) if paths.is_empty() => return Err(SemanticError::MissingPaths),
- (Some(paths), Some(blindedpay)) if paths.len() != blindedpay.len() => {
- return Err(SemanticError::InvalidPayInfo);
+ let payment_paths = match (blindedpay, paths) {
+ (_, None) => return Err(Bolt12SemanticError::MissingPaths),
+ (None, _) => return Err(Bolt12SemanticError::InvalidPayInfo),
+ (_, Some(paths)) if paths.is_empty() => return Err(Bolt12SemanticError::MissingPaths),
+ (Some(blindedpay), Some(paths)) if paths.len() != blindedpay.len() => {
+ return Err(Bolt12SemanticError::InvalidPayInfo);
},
- (Some(paths), Some(blindedpay)) => {
- paths.into_iter().zip(blindedpay.into_iter()).collect::<Vec<_>>()
+ (Some(blindedpay), Some(paths)) => {
+ blindedpay.into_iter().zip(paths.into_iter()).collect::<Vec<_>>()
},
};
let created_at = match created_at {
- None => return Err(SemanticError::MissingCreationTime),
+ None => return Err(Bolt12SemanticError::MissingCreationTime),
Some(timestamp) => Duration::from_secs(timestamp),
};
.map(Duration::from_secs);
let payment_hash = match payment_hash {
- None => return Err(SemanticError::MissingPaymentHash),
+ None => return Err(Bolt12SemanticError::MissingPaymentHash),
Some(payment_hash) => payment_hash,
};
let amount_msats = match amount {
- None => return Err(SemanticError::MissingAmount),
+ None => return Err(Bolt12SemanticError::MissingAmount),
Some(amount) => amount,
};
let features = features.unwrap_or_else(Bolt12InvoiceFeatures::empty);
let signing_pubkey = match node_id {
- None => return Err(SemanticError::MissingSigningPubkey),
+ None => return Err(Bolt12SemanticError::MissingSigningPubkey),
Some(node_id) => node_id,
};
match offer_tlv_stream.node_id {
Some(expected_signing_pubkey) => {
if fields.signing_pubkey != expected_signing_pubkey {
- return Err(SemanticError::InvalidSigningPubkey);
+ return Err(Bolt12SemanticError::InvalidSigningPubkey);
}
let invoice_request = InvoiceRequestContents::try_from(
#[cfg(test)]
mod tests {
- use super::{DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, Invoice, InvoiceTlvStreamRef, SIGNATURE_TAG};
+ use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, InvoiceTlvStreamRef, SIGNATURE_TAG};
use bitcoin::blockdata::script::Script;
use bitcoin::hashes::Hash;
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef, Quantity};
- use crate::offers::parse::{ParseError, SemanticError};
+ use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
use crate::offers::payer::PayerTlvStreamRef;
use crate::offers::refund::RefundBuilder;
use crate::offers::test_utils::*;
payer_note: None,
},
InvoiceTlvStreamRef {
- paths: Some(Iterable(payment_paths.iter().map(|(path, _)| path))),
- blindedpay: Some(Iterable(payment_paths.iter().map(|(_, payinfo)| payinfo))),
+ paths: Some(Iterable(payment_paths.iter().map(|(_, path)| path))),
+ blindedpay: Some(Iterable(payment_paths.iter().map(|(payinfo, _)| payinfo))),
created_at: Some(now.as_secs()),
relative_expiry: None,
payment_hash: Some(&payment_hash),
),
);
- if let Err(e) = Invoice::try_from(buffer) {
+ if let Err(e) = Bolt12Invoice::try_from(buffer) {
panic!("error parsing invoice: {:?}", e);
}
}
payer_note: None,
},
InvoiceTlvStreamRef {
- paths: Some(Iterable(payment_paths.iter().map(|(path, _)| path))),
- blindedpay: Some(Iterable(payment_paths.iter().map(|(_, payinfo)| payinfo))),
+ paths: Some(Iterable(payment_paths.iter().map(|(_, path)| path))),
+ blindedpay: Some(Iterable(payment_paths.iter().map(|(payinfo, _)| payinfo))),
created_at: Some(now.as_secs()),
relative_expiry: None,
payment_hash: Some(&payment_hash),
),
);
- if let Err(e) = Invoice::try_from(buffer) {
+ if let Err(e) = Bolt12Invoice::try_from(buffer) {
panic!("error parsing invoice: {:?}", e);
}
}
.build()
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::AlreadyExpired),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::AlreadyExpired),
}
}
.build()
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::AlreadyExpired),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::AlreadyExpired),
}
}
payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InvalidMetadata),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidMetadata),
}
let desc = "foo".to_string();
payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InvalidMetadata),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidMetadata),
}
}
.respond_with_no_std(payment_paths(), payment_hash(), now())
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InvalidAmount),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount),
}
}
let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();
- if let Err(e) = Invoice::try_from(buffer) {
+ if let Err(e) = Bolt12Invoice::try_from(buffer) {
panic!("error parsing invoice: {:?}", e);
}
let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.paths = None;
- match Invoice::try_from(tlv_stream.to_bytes()) {
+ match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaths)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPaths)),
}
let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.blindedpay = None;
- match Invoice::try_from(tlv_stream.to_bytes()) {
+ match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidPayInfo)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidPayInfo)),
}
let empty_payment_paths = vec![];
let mut tlv_stream = invoice.as_tlv_stream();
- tlv_stream.3.paths = Some(Iterable(empty_payment_paths.iter().map(|(path, _)| path)));
+ tlv_stream.3.paths = Some(Iterable(empty_payment_paths.iter().map(|(_, path)| path)));
- match Invoice::try_from(tlv_stream.to_bytes()) {
+ match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaths)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPaths)),
}
let mut payment_paths = payment_paths();
payment_paths.pop();
let mut tlv_stream = invoice.as_tlv_stream();
- tlv_stream.3.blindedpay = Some(Iterable(payment_paths.iter().map(|(_, payinfo)| payinfo)));
+ tlv_stream.3.blindedpay = Some(Iterable(payment_paths.iter().map(|(payinfo, _)| payinfo)));
- match Invoice::try_from(tlv_stream.to_bytes()) {
+ match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidPayInfo)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidPayInfo)),
}
}
let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();
- if let Err(e) = Invoice::try_from(buffer) {
+ if let Err(e) = Bolt12Invoice::try_from(buffer) {
panic!("error parsing invoice: {:?}", e);
}
let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.created_at = None;
- match Invoice::try_from(tlv_stream.to_bytes()) {
+ match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingCreationTime));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingCreationTime));
},
}
}
let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();
- match Invoice::try_from(buffer) {
+ match Bolt12Invoice::try_from(buffer) {
Ok(invoice) => assert_eq!(invoice.relative_expiry(), Duration::from_secs(3600)),
Err(e) => panic!("error parsing invoice: {:?}", e),
}
let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();
- if let Err(e) = Invoice::try_from(buffer) {
+ if let Err(e) = Bolt12Invoice::try_from(buffer) {
panic!("error parsing invoice: {:?}", e);
}
let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.payment_hash = None;
- match Invoice::try_from(tlv_stream.to_bytes()) {
+ match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaymentHash));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPaymentHash));
},
}
}
let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();
- if let Err(e) = Invoice::try_from(buffer) {
+ if let Err(e) = Bolt12Invoice::try_from(buffer) {
panic!("error parsing invoice: {:?}", e);
}
let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.amount = None;
- match Invoice::try_from(tlv_stream.to_bytes()) {
+ match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingAmount)),
}
}
let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();
- match Invoice::try_from(buffer) {
+ match Bolt12Invoice::try_from(buffer) {
Ok(invoice) => {
let mut features = Bolt12InvoiceFeatures::empty();
features.set_basic_mpp_optional();
let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();
- match Invoice::try_from(buffer) {
+ match Bolt12Invoice::try_from(buffer) {
Ok(invoice) => {
assert_eq!(
invoice.fallbacks(),
let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();
- if let Err(e) = Invoice::try_from(buffer) {
+ if let Err(e) = Bolt12Invoice::try_from(buffer) {
panic!("error parsing invoice: {:?}", e);
}
let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.node_id = None;
- match Invoice::try_from(tlv_stream.to_bytes()) {
+ match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSigningPubkey));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSigningPubkey));
},
}
let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.node_id = Some(&invalid_pubkey);
- match Invoice::try_from(tlv_stream.to_bytes()) {
+ match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidSigningPubkey));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidSigningPubkey));
},
}
}
.invoice
.write(&mut buffer).unwrap();
- match Invoice::try_from(buffer) {
+ match Bolt12Invoice::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSignature)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)),
}
}
let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();
- match Invoice::try_from(buffer) {
+ match Bolt12Invoice::try_from(buffer) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSignature(secp256k1::Error::InvalidSignature));
+ assert_eq!(e, Bolt12ParseError::InvalidSignature(secp256k1::Error::InvalidSignature));
},
}
}
BigSize(32).write(&mut encoded_invoice).unwrap();
[42u8; 32].write(&mut encoded_invoice).unwrap();
- match Invoice::try_from(encoded_invoice) {
+ match Bolt12Invoice::try_from(encoded_invoice) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
}
}
}
use crate::io;
use crate::ln::msgs::DecodeError;
-use crate::offers::parse::SemanticError;
+use crate::offers::parse::Bolt12SemanticError;
use crate::util::ser::{HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer};
use crate::util::string::UntrustedString;
use crate::prelude::*;
-/// An error in response to an [`InvoiceRequest`] or an [`Invoice`].
+/// An error in response to an [`InvoiceRequest`] or an [`Bolt12Invoice`].
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
-/// [`Invoice`]: crate::offers::invoice::Invoice
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct InvoiceError {
- /// The field in the [`InvoiceRequest`] or the [`Invoice`] that contained an error.
+ /// The field in the [`InvoiceRequest`] or the [`Bolt12Invoice`] that contained an error.
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
- /// [`Invoice`]: crate::offers::invoice::Invoice
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub erroneous_field: Option<ErroneousField>,
/// An explanation of the error.
pub message: UntrustedString,
}
-/// The field in the [`InvoiceRequest`] or the [`Invoice`] that contained an error.
+/// The field in the [`InvoiceRequest`] or the [`Bolt12Invoice`] that contained an error.
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
-/// [`Invoice`]: crate::offers::invoice::Invoice
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ErroneousField {
}
}
-impl From<SemanticError> for InvoiceError {
- fn from(error: SemanticError) -> Self {
+impl From<Bolt12SemanticError> for InvoiceError {
+ fn from(error: Bolt12SemanticError) -> Self {
InvoiceError {
erroneous_field: None,
message: UntrustedString(format!("{:?}", error)),
//!
//! An [`InvoiceRequest`] can be built from a parsed [`Offer`] as an "offer to be paid". It is
//! typically constructed by a customer and sent to the merchant who had published the corresponding
-//! offer. The recipient of the request responds with an [`Invoice`].
+//! offer. The recipient of the request responds with a [`Bolt12Invoice`].
//!
//! For an "offer for money" (e.g., refund, ATM withdrawal), where an offer doesn't exist as a
//! precursor, see [`Refund`].
//!
-//! [`Invoice`]: crate::offers::invoice::Invoice
+//! [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
//! [`Refund`]: crate::offers::refund::Refund
//!
//! ```
//! use lightning::offers::offer::Offer;
//! use lightning::util::ser::Writeable;
//!
-//! # fn parse() -> Result<(), lightning::offers::parse::ParseError> {
+//! # fn parse() -> Result<(), lightning::offers::parse::Bolt12ParseError> {
//! let secp_ctx = Secp256k1::new();
//! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32])?);
//! let pubkey = PublicKey::from(keys);
use crate::offers::invoice::{BlindedPayInfo, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder};
use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, self};
use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef};
-use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
+use crate::offers::parse::{Bolt12ParseError, ParsedMessage, Bolt12SemanticError};
use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
use crate::offers::signer::{Metadata, MetadataMaterial};
use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer};
/// by the offer.
///
/// Successive calls to this method will override the previous setting.
- pub fn chain(mut self, network: Network) -> Result<Self, SemanticError> {
+ pub fn chain(mut self, network: Network) -> Result<Self, Bolt12SemanticError> {
let chain = ChainHash::using_genesis_block(network);
if !self.offer.supports_chain(chain) {
- return Err(SemanticError::UnsupportedChain);
+ return Err(Bolt12SemanticError::UnsupportedChain);
}
self.invoice_request.chain = Some(chain);
/// Successive calls to this method will override the previous setting.
///
/// [`quantity`]: Self::quantity
- pub fn amount_msats(mut self, amount_msats: u64) -> Result<Self, SemanticError> {
+ pub fn amount_msats(mut self, amount_msats: u64) -> Result<Self, Bolt12SemanticError> {
self.invoice_request.offer.check_amount_msats_for_quantity(
Some(amount_msats), self.invoice_request.quantity
)?;
/// does not conform to [`Offer::is_valid_quantity`].
///
/// Successive calls to this method will override the previous setting.
- pub fn quantity(mut self, quantity: u64) -> Result<Self, SemanticError> {
+ pub fn quantity(mut self, quantity: u64) -> Result<Self, Bolt12SemanticError> {
self.invoice_request.offer.check_quantity(Some(quantity))?;
self.invoice_request.quantity = Some(quantity);
Ok(self)
fn build_with_checks(mut self) -> Result<
(UnsignedInvoiceRequest<'a>, Option<KeyPair>, Option<&'b Secp256k1<T>>),
- SemanticError
+ Bolt12SemanticError
> {
#[cfg(feature = "std")] {
if self.offer.is_expired() {
- return Err(SemanticError::AlreadyExpired);
+ return Err(Bolt12SemanticError::AlreadyExpired);
}
}
let chain = self.invoice_request.chain();
if !self.offer.supports_chain(chain) {
- return Err(SemanticError::UnsupportedChain);
+ return Err(Bolt12SemanticError::UnsupportedChain);
}
if chain == self.offer.implied_chain() {
}
if self.offer.amount().is_none() && self.invoice_request.amount_msats.is_none() {
- return Err(SemanticError::MissingAmount);
+ return Err(Bolt12SemanticError::MissingAmount);
}
self.invoice_request.offer.check_quantity(self.invoice_request.quantity)?;
fn build_without_checks(mut self) ->
(UnsignedInvoiceRequest<'a>, Option<KeyPair>, Option<&'b Secp256k1<T>>)
{
- // Create the metadata for stateless verification of an Invoice.
+ // Create the metadata for stateless verification of a Bolt12Invoice.
let mut keys = None;
let secp_ctx = self.secp_ctx.clone();
if self.invoice_request.payer.0.has_derivation_material() {
impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerId, T> {
/// Builds an unsigned [`InvoiceRequest`] after checking for valid semantics. It can be signed
/// by [`UnsignedInvoiceRequest::sign`].
- pub fn build(self) -> Result<UnsignedInvoiceRequest<'a>, SemanticError> {
+ pub fn build(self) -> Result<UnsignedInvoiceRequest<'a>, Bolt12SemanticError> {
let (unsigned_invoice_request, keys, _) = self.build_with_checks()?;
debug_assert!(keys.is_none());
Ok(unsigned_invoice_request)
impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> {
/// Builds a signed [`InvoiceRequest`] after checking for valid semantics.
- pub fn build_and_sign(self) -> Result<InvoiceRequest, SemanticError> {
+ pub fn build_and_sign(self) -> Result<InvoiceRequest, Bolt12SemanticError> {
let (unsigned_invoice_request, keys, secp_ctx) = self.build_with_checks()?;
debug_assert!(keys.is_some());
}
}
-/// An `InvoiceRequest` is a request for an [`Invoice`] formulated from an [`Offer`].
+/// An `InvoiceRequest` is a request for a [`Bolt12Invoice`] formulated from an [`Offer`].
///
/// An offer may provide choices such as quantity, amount, chain, features, etc. An invoice request
/// specifies these such that its recipient can send an invoice for payment.
///
-/// [`Invoice`]: crate::offers::invoice::Invoice
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
/// [`Offer`]: crate::offers::offer::Offer
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
signature: Signature,
}
-/// The contents of an [`InvoiceRequest`], which may be shared with an [`Invoice`].
+/// The contents of an [`InvoiceRequest`], which may be shared with an [`Bolt12Invoice`].
///
-/// [`Invoice`]: crate::offers::invoice::Invoice
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub(super) struct InvoiceRequestContents {
/// [`Duration`]: core::time::Duration
#[cfg(feature = "std")]
pub fn respond_with(
- &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash
- ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
+ &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
+ ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
let created_at = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
/// Creates an [`InvoiceBuilder`] for the request with the given required fields.
///
/// Unless [`InvoiceBuilder::relative_expiry`] is set, the invoice will expire two hours after
- /// `created_at`, which is used to set [`Invoice::created_at`]. Useful for `no-std` builds where
- /// [`std::time::SystemTime`] is not available.
+ /// `created_at`, which is used to set [`Bolt12Invoice::created_at`]. Useful for `no-std` builds
+ /// where [`std::time::SystemTime`] is not available.
///
/// The caller is expected to remember the preimage of `payment_hash` in order to claim a payment
/// for the invoice.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
- /// [`Invoice::created_at`]: crate::offers::invoice::Invoice::created_at
+ /// [`Bolt12Invoice::created_at`]: crate::offers::invoice::Bolt12Invoice::created_at
pub fn respond_with_no_std(
- &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+ &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
created_at: core::time::Duration
- ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
+ ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
if self.features().requires_unknown_bits() {
- return Err(SemanticError::UnknownRequiredFeatures);
+ return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
InvoiceBuilder::for_offer(self, payment_paths, created_at, payment_hash)
}
/// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
- /// derived signing keys from the originating [`Offer`] to sign the [`Invoice`]. Must use the
- /// same [`ExpandedKey`] as the one used to create the offer.
+ /// derived signing keys from the originating [`Offer`] to sign the [`Bolt12Invoice`]. Must use
+ /// the same [`ExpandedKey`] as the one used to create the offer.
///
/// See [`InvoiceRequest::respond_with`] for further details.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
- /// [`Invoice`]: crate::offers::invoice::Invoice
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[cfg(feature = "std")]
pub fn verify_and_respond_using_derived_keys<T: secp256k1::Signing>(
- &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+ &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
- ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, SemanticError> {
+ ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError> {
let created_at = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
}
/// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
- /// derived signing keys from the originating [`Offer`] to sign the [`Invoice`]. Must use the
- /// same [`ExpandedKey`] as the one used to create the offer.
+ /// derived signing keys from the originating [`Offer`] to sign the [`Bolt12Invoice`]. Must use
+ /// the same [`ExpandedKey`] as the one used to create the offer.
///
/// See [`InvoiceRequest::respond_with_no_std`] for further details.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
- /// [`Invoice`]: crate::offers::invoice::Invoice
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub fn verify_and_respond_using_derived_keys_no_std<T: secp256k1::Signing>(
- &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+ &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
created_at: core::time::Duration, expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
- ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, SemanticError> {
+ ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError> {
if self.features().requires_unknown_bits() {
- return Err(SemanticError::UnknownRequiredFeatures);
+ return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
let keys = match self.verify(expanded_key, secp_ctx) {
- Err(()) => return Err(SemanticError::InvalidMetadata),
- Ok(None) => return Err(SemanticError::InvalidMetadata),
+ Err(()) => return Err(Bolt12SemanticError::InvalidMetadata),
+ Ok(None) => return Err(Bolt12SemanticError::InvalidMetadata),
Ok(Some(keys)) => keys,
};
}
/// Verifies that the request was for an offer created using the given key. Returns the derived
- /// keys need to sign an [`Invoice`] for the request if they could be extracted from the
+ /// keys need to sign an [`Bolt12Invoice`] for the request if they could be extracted from the
/// metadata.
///
- /// [`Invoice`]: crate::offers::invoice::Invoice
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub fn verify<T: secp256k1::Signing>(
&self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
) -> Result<Option<KeyPair>, ()> {
);
impl TryFrom<Vec<u8>> for InvoiceRequest {
- type Error = ParseError;
+ type Error = Bolt12ParseError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
let invoice_request = ParsedMessage::<FullInvoiceRequestTlvStream>::try_from(bytes)?;
)?;
let signature = match signature {
- None => return Err(ParseError::InvalidSemantics(SemanticError::MissingSignature)),
+ None => return Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)),
Some(signature) => signature,
};
merkle::verify_signature(&signature, SIGNATURE_TAG, &bytes, contents.payer_id)?;
}
impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
- type Error = SemanticError;
+ type Error = Bolt12SemanticError;
fn try_from(tlv_stream: PartialInvoiceRequestTlvStream) -> Result<Self, Self::Error> {
let (
) = tlv_stream;
let payer = match metadata {
- None => return Err(SemanticError::MissingPayerMetadata),
+ None => return Err(Bolt12SemanticError::MissingPayerMetadata),
Some(metadata) => PayerContents(Metadata::Bytes(metadata)),
};
let offer = OfferContents::try_from(offer_tlv_stream)?;
if !offer.supports_chain(chain.unwrap_or_else(|| offer.implied_chain())) {
- return Err(SemanticError::UnsupportedChain);
+ return Err(Bolt12SemanticError::UnsupportedChain);
}
if offer.amount().is_none() && amount.is_none() {
- return Err(SemanticError::MissingAmount);
+ return Err(Bolt12SemanticError::MissingAmount);
}
offer.check_quantity(quantity)?;
let features = features.unwrap_or_else(InvoiceRequestFeatures::empty);
let payer_id = match payer_id {
- None => return Err(SemanticError::MissingPayerId),
+ None => return Err(Bolt12SemanticError::MissingPayerId),
Some(payer_id) => payer_id,
};
use crate::ln::features::InvoiceRequestFeatures;
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
- use crate::offers::invoice::{Invoice, SIGNATURE_TAG as INVOICE_SIGNATURE_TAG};
+ use crate::offers::invoice::{Bolt12Invoice, SIGNATURE_TAG as INVOICE_SIGNATURE_TAG};
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity};
- use crate::offers::parse::{ParseError, SemanticError};
+ use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
use crate::offers::payer::PayerTlvStreamRef;
use crate::offers::test_utils::*;
use crate::util::ser::{BigSize, Writeable};
.build()
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::AlreadyExpired),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::AlreadyExpired),
}
}
let mut encoded_invoice = bytes;
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
- let invoice = Invoice::try_from(encoded_invoice).unwrap();
+ let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
assert!(!invoice.verify(&expanded_key, &secp_ctx));
// Fails verification with altered metadata
let mut encoded_invoice = bytes;
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
- let invoice = Invoice::try_from(encoded_invoice).unwrap();
+ let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
assert!(!invoice.verify(&expanded_key, &secp_ctx));
}
let mut encoded_invoice = bytes;
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
- let invoice = Invoice::try_from(encoded_invoice).unwrap();
+ let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
assert!(!invoice.verify(&expanded_key, &secp_ctx));
// Fails verification with altered payer id
let mut encoded_invoice = bytes;
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
- let invoice = Invoice::try_from(encoded_invoice).unwrap();
+ let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
assert!(!invoice.verify(&expanded_key, &secp_ctx));
}
.chain(Network::Bitcoin)
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::UnsupportedChain),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedChain),
}
match OfferBuilder::new("foo".into(), recipient_pubkey())
.build()
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::UnsupportedChain),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedChain),
}
}
.amount_msats(999)
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InsufficientAmount),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InsufficientAmount),
}
match OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InsufficientAmount),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InsufficientAmount),
}
match OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(MAX_VALUE_MSAT + 1)
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InvalidAmount),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount),
}
match OfferBuilder::new("foo".into(), recipient_pubkey())
.build()
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InsufficientAmount),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InsufficientAmount),
}
match OfferBuilder::new("foo".into(), recipient_pubkey())
.build()
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::MissingAmount),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::MissingAmount),
}
match OfferBuilder::new("foo".into(), recipient_pubkey())
.build()
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InvalidAmount),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount),
}
}
.quantity(2)
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::UnexpectedQuantity),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::UnexpectedQuantity),
}
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
.quantity(11)
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InvalidQuantity),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidQuantity),
}
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
.build()
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::MissingQuantity),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::MissingQuantity),
}
match OfferBuilder::new("foo".into(), recipient_pubkey())
.build()
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::MissingQuantity),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::MissingQuantity),
}
}
.respond_with_no_std(payment_paths(), payment_hash(), now())
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::UnknownRequiredFeatures),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::UnknownRequiredFeatures),
}
}
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnsupportedChain)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnsupportedChain)),
}
}
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingAmount)),
}
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InsufficientAmount)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InsufficientAmount)),
}
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnsupportedCurrency));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnsupportedCurrency));
},
}
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidAmount)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidAmount)),
}
}
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedQuantity));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedQuantity));
},
}
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidQuantity)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidQuantity)),
}
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingQuantity)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingQuantity)),
}
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingQuantity)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingQuantity)),
}
}
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerMetadata));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPayerMetadata));
},
}
}
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerId)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPayerId)),
}
}
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSigningPubkey));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSigningPubkey));
},
}
}
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSignature)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)),
}
}
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSignature(secp256k1::Error::InvalidSignature));
+ assert_eq!(e, Bolt12ParseError::InvalidSignature(secp256k1::Error::InvalidSignature));
},
}
}
match InvoiceRequest::try_from(encoded_invoice_request) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
}
}
}
//!
//! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
//! use lightning::offers::offer::{Offer, OfferBuilder, Quantity};
-//! use lightning::offers::parse::ParseError;
+//! use lightning::offers::parse::Bolt12ParseError;
//! use lightning::util::ser::{Readable, Writeable};
//!
//! # use lightning::blinded_path::BlindedPath;
//! # fn create_another_blinded_path() -> BlindedPath { unimplemented!() }
//! #
//! # #[cfg(feature = "std")]
-//! # fn build() -> Result<(), ParseError> {
+//! # fn build() -> Result<(), Bolt12ParseError> {
//! let secp_ctx = Secp256k1::new();
//! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
//! let pubkey = PublicKey::from(keys);
use crate::ln::msgs::MAX_VALUE_MSAT;
use crate::offers::invoice_request::{DerivedPayerId, ExplicitPayerId, InvoiceRequestBuilder};
use crate::offers::merkle::TlvStream;
-use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
+use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
use crate::offers::signer::{Metadata, MetadataMaterial, self};
use crate::util::ser::{HighZeroBytesDroppedBigSize, WithoutLength, Writeable, Writer};
use crate::util::string::PrintableString;
/// Sets the [`Offer::metadata`] to the given bytes.
///
/// Successive calls to this method will override the previous setting.
- pub fn metadata(mut self, metadata: Vec<u8>) -> Result<Self, SemanticError> {
+ pub fn metadata(mut self, metadata: Vec<u8>) -> Result<Self, Bolt12SemanticError> {
self.offer.metadata = Some(Metadata::Bytes(metadata));
Ok(self)
}
}
/// Builds an [`Offer`] from the builder's settings.
- pub fn build(mut self) -> Result<Offer, SemanticError> {
+ pub fn build(mut self) -> Result<Offer, Bolt12SemanticError> {
match self.offer.amount {
Some(Amount::Bitcoin { amount_msats }) => {
if amount_msats > MAX_VALUE_MSAT {
- return Err(SemanticError::InvalidAmount);
+ return Err(Bolt12SemanticError::InvalidAmount);
}
},
- Some(Amount::Currency { .. }) => return Err(SemanticError::UnsupportedCurrency),
+ Some(Amount::Currency { .. }) => return Err(Bolt12SemanticError::UnsupportedCurrency),
None => {},
}
/// An `Offer` is a potentially long-lived proposal for payment of a good or service.
///
/// An offer is a precursor to an [`InvoiceRequest`]. A merchant publishes an offer from which a
-/// customer may request an [`Invoice`] for a specific quantity and using an amount sufficient to
-/// cover that quantity (i.e., at least `quantity * amount`). See [`Offer::amount`].
+/// customer may request an [`Bolt12Invoice`] for a specific quantity and using an amount sufficient
+/// to cover that quantity (i.e., at least `quantity * amount`). See [`Offer::amount`].
///
/// Offers may be denominated in currency other than bitcoin but are ultimately paid using the
/// latter.
/// Through the use of [`BlindedPath`]s, offers provide recipient privacy.
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
-/// [`Invoice`]: crate::offers::invoice::Invoice
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Offer {
pub(super) contents: OfferContents,
}
-/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or an [`Invoice`].
+/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or a
+/// [`Bolt12Invoice`].
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
-/// [`Invoice`]: crate::offers::invoice::Invoice
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub(super) struct OfferContents {
/// - derives the [`InvoiceRequest::payer_id`] such that a different key can be used for each
/// request, and
/// - sets the [`InvoiceRequest::metadata`] when [`InvoiceRequestBuilder::build`] is called such
- /// that it can be used by [`Invoice::verify`] to determine if the invoice was requested using
- /// a base [`ExpandedKey`] from which the payer id was derived.
+ /// that it can be used by [`Bolt12Invoice::verify`] to determine if the invoice was requested
+ /// using a base [`ExpandedKey`] from which the payer id was derived.
///
/// Useful to protect the sender's privacy.
///
///
/// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
/// [`InvoiceRequest::metadata`]: crate::offers::invoice_request::InvoiceRequest::metadata
- /// [`Invoice::verify`]: crate::offers::invoice::Invoice::verify
+ /// [`Bolt12Invoice::verify`]: crate::offers::invoice::Bolt12Invoice::verify
/// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
pub fn request_invoice_deriving_payer_id<'a, 'b, ES: Deref, T: secp256k1::Signing>(
&'a self, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1<T>
- ) -> Result<InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>, SemanticError>
+ ) -> Result<InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>, Bolt12SemanticError>
where
ES::Target: EntropySource,
{
if self.features().requires_unknown_bits() {
- return Err(SemanticError::UnknownRequiredFeatures);
+ return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
Ok(InvoiceRequestBuilder::deriving_payer_id(self, expanded_key, entropy_source, secp_ctx))
/// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
pub fn request_invoice_deriving_metadata<ES: Deref>(
&self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES
- ) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, SemanticError>
+ ) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, Bolt12SemanticError>
where
ES::Target: EntropySource,
{
if self.features().requires_unknown_bits() {
- return Err(SemanticError::UnknownRequiredFeatures);
+ return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
Ok(InvoiceRequestBuilder::deriving_metadata(self, payer_id, expanded_key, entropy_source))
}
/// Creates an [`InvoiceRequestBuilder`] for the offer with the given `metadata` and `payer_id`,
- /// which will be reflected in the `Invoice` response.
+ /// which will be reflected in the `Bolt12Invoice` response.
///
/// The `metadata` is useful for including information about the derivation of `payer_id` such
/// that invoice response handling can be stateless. Also serves as payer-provided entropy while
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
pub fn request_invoice(
&self, metadata: Vec<u8>, payer_id: PublicKey
- ) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, SemanticError> {
+ ) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, Bolt12SemanticError> {
if self.features().requires_unknown_bits() {
- return Err(SemanticError::UnknownRequiredFeatures);
+ return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
Ok(InvoiceRequestBuilder::new(self, metadata, payer_id))
pub(super) fn check_amount_msats_for_quantity(
&self, amount_msats: Option<u64>, quantity: Option<u64>
- ) -> Result<(), SemanticError> {
+ ) -> Result<(), Bolt12SemanticError> {
let offer_amount_msats = match self.amount {
None => 0,
Some(Amount::Bitcoin { amount_msats }) => amount_msats,
- Some(Amount::Currency { .. }) => return Err(SemanticError::UnsupportedCurrency),
+ Some(Amount::Currency { .. }) => return Err(Bolt12SemanticError::UnsupportedCurrency),
};
if !self.expects_quantity() || quantity.is_some() {
let expected_amount_msats = offer_amount_msats.checked_mul(quantity.unwrap_or(1))
- .ok_or(SemanticError::InvalidAmount)?;
+ .ok_or(Bolt12SemanticError::InvalidAmount)?;
let amount_msats = amount_msats.unwrap_or(expected_amount_msats);
if amount_msats < expected_amount_msats {
- return Err(SemanticError::InsufficientAmount);
+ return Err(Bolt12SemanticError::InsufficientAmount);
}
if amount_msats > MAX_VALUE_MSAT {
- return Err(SemanticError::InvalidAmount);
+ return Err(Bolt12SemanticError::InvalidAmount);
}
}
self.supported_quantity
}
- pub(super) fn check_quantity(&self, quantity: Option<u64>) -> Result<(), SemanticError> {
+ pub(super) fn check_quantity(&self, quantity: Option<u64>) -> Result<(), Bolt12SemanticError> {
let expects_quantity = self.expects_quantity();
match quantity {
- None if expects_quantity => Err(SemanticError::MissingQuantity),
- Some(_) if !expects_quantity => Err(SemanticError::UnexpectedQuantity),
+ None if expects_quantity => Err(Bolt12SemanticError::MissingQuantity),
+ Some(_) if !expects_quantity => Err(Bolt12SemanticError::UnexpectedQuantity),
Some(quantity) if !self.is_valid_quantity(quantity) => {
- Err(SemanticError::InvalidQuantity)
+ Err(Bolt12SemanticError::InvalidQuantity)
},
_ => Ok(()),
}
}
impl FromStr for Offer {
- type Err = ParseError;
+ type Err = Bolt12ParseError;
fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
Self::from_bech32_str(s)
}
impl TryFrom<Vec<u8>> for Offer {
- type Error = ParseError;
+ type Error = Bolt12ParseError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
let offer = ParsedMessage::<OfferTlvStream>::try_from(bytes)?;
}
impl TryFrom<OfferTlvStream> for OfferContents {
- type Error = SemanticError;
+ type Error = Bolt12SemanticError;
fn try_from(tlv_stream: OfferTlvStream) -> Result<Self, Self::Error> {
let OfferTlvStream {
let amount = match (currency, amount) {
(None, None) => None,
(None, Some(amount_msats)) if amount_msats > MAX_VALUE_MSAT => {
- return Err(SemanticError::InvalidAmount);
+ return Err(Bolt12SemanticError::InvalidAmount);
},
(None, Some(amount_msats)) => Some(Amount::Bitcoin { amount_msats }),
- (Some(_), None) => return Err(SemanticError::MissingAmount),
+ (Some(_), None) => return Err(Bolt12SemanticError::MissingAmount),
(Some(iso4217_code), Some(amount)) => Some(Amount::Currency { iso4217_code, amount }),
};
let description = match description {
- None => return Err(SemanticError::MissingDescription),
+ None => return Err(Bolt12SemanticError::MissingDescription),
Some(description) => description,
};
};
let signing_pubkey = match node_id {
- None => return Err(SemanticError::MissingSigningPubkey),
+ None => return Err(Bolt12SemanticError::MissingSigningPubkey),
Some(node_id) => node_id,
};
use crate::ln::features::OfferFeatures;
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
- use crate::offers::parse::{ParseError, SemanticError};
+ use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
use crate::offers::test_utils::*;
use crate::util::ser::{BigSize, Writeable};
use crate::util::string::PrintableString;
assert_eq!(tlv_stream.currency, Some(b"USD"));
match builder.build() {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::UnsupportedCurrency),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedCurrency),
}
let offer = OfferBuilder::new("foo".into(), pubkey(42))
let invalid_amount = Amount::Bitcoin { amount_msats: MAX_VALUE_MSAT + 1 };
match OfferBuilder::new("foo".into(), pubkey(42)).amount(invalid_amount).build() {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InvalidAmount),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount),
}
}
.request_invoice(vec![1; 32], pubkey(43))
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::UnknownRequiredFeatures),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::UnknownRequiredFeatures),
}
}
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingAmount)),
}
let mut tlv_stream = offer.as_tlv_stream();
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidAmount)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidAmount)),
}
}
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingDescription));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingDescription));
},
}
}
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSigningPubkey));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSigningPubkey));
},
}
}
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
}
}
}
#[cfg(test)]
mod bech32_tests {
- use super::{Offer, ParseError};
+ use super::{Bolt12ParseError, Offer};
use bitcoin::bech32;
use crate::ln::msgs::DecodeError;
for encoded_offer in &offers {
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
- Err(e) => assert_eq!(e, ParseError::InvalidContinuation),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidContinuation),
}
}
let encoded_offer = "lni1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
- Err(e) => assert_eq!(e, ParseError::InvalidBech32Hrp),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidBech32Hrp),
}
}
let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
- Err(e) => assert_eq!(e, ParseError::Bech32(bech32::Error::InvalidChar('o'))),
+ Err(e) => assert_eq!(e, Bolt12ParseError::Bech32(bech32::Error::InvalidChar('o'))),
}
}
let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxgqqqqq";
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
- Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
}
}
}
use bitcoin::bech32::{FromBase32, ToBase32};
use core::convert::TryFrom;
use core::fmt;
- use super::ParseError;
+ use super::Bolt12ParseError;
use crate::prelude::*;
/// Indicates a message can be encoded using bech32.
- pub trait Bech32Encode: AsRef<[u8]> + TryFrom<Vec<u8>, Error=ParseError> {
+ pub trait Bech32Encode: AsRef<[u8]> + TryFrom<Vec<u8>, Error=Bolt12ParseError> {
/// Human readable part of the message's bech32 encoding.
const BECH32_HRP: &'static str;
/// Parses a bech32-encoded message into a TLV stream.
- fn from_bech32_str(s: &str) -> Result<Self, ParseError> {
+ fn from_bech32_str(s: &str) -> Result<Self, Bolt12ParseError> {
// Offer encoding may be split by '+' followed by optional whitespace.
let encoded = match s.split('+').skip(1).next() {
Some(_) => {
for chunk in s.split('+') {
let chunk = chunk.trim_start();
if chunk.is_empty() || chunk.contains(char::is_whitespace) {
- return Err(ParseError::InvalidContinuation);
+ return Err(Bolt12ParseError::InvalidContinuation);
}
}
let (hrp, data) = bech32::decode_without_checksum(encoded.as_ref())?;
if hrp != Self::BECH32_HRP {
- return Err(ParseError::InvalidBech32Hrp);
+ return Err(Bolt12ParseError::InvalidBech32Hrp);
}
let data = Vec::<u8>::from_base32(&data)?;
}
/// Error when parsing a bech32 encoded message using [`str::parse`].
-///
-/// This is not exported to bindings users as its name conflicts with the BOLT 11 ParseError type.
-#[derive(Debug, PartialEq)]
-pub enum ParseError {
+#[derive(Clone, Debug, PartialEq)]
+pub enum Bolt12ParseError {
/// The bech32 encoding does not conform to the BOLT 12 requirements for continuing messages
/// across multiple parts (i.e., '+' followed by whitespace).
InvalidContinuation,
/// The bech32 decoded string could not be decoded as the expected message type.
Decode(DecodeError),
/// The parsed message has invalid semantics.
- InvalidSemantics(SemanticError),
+ InvalidSemantics(Bolt12SemanticError),
/// The parsed message has an invalid signature.
InvalidSignature(secp256k1::Error),
}
/// Error when interpreting a TLV stream as a specific type.
-///
-/// This is not exported to bindings users as its name conflicts with the BOLT 11 SemanticError type.
-#[derive(Debug, PartialEq)]
-pub enum SemanticError {
+#[derive(Clone, Debug, PartialEq)]
+pub enum Bolt12SemanticError {
/// The current [`std::time::SystemTime`] is past the offer or invoice's expiration.
AlreadyExpired,
/// The provided chain hash does not correspond to a supported chain.
MissingSignature,
}
-impl From<bech32::Error> for ParseError {
+impl From<bech32::Error> for Bolt12ParseError {
fn from(error: bech32::Error) -> Self {
Self::Bech32(error)
}
}
-impl From<DecodeError> for ParseError {
+impl From<DecodeError> for Bolt12ParseError {
fn from(error: DecodeError) -> Self {
Self::Decode(error)
}
}
-impl From<SemanticError> for ParseError {
- fn from(error: SemanticError) -> Self {
+impl From<Bolt12SemanticError> for Bolt12ParseError {
+ fn from(error: Bolt12SemanticError) -> Self {
Self::InvalidSemantics(error)
}
}
-impl From<secp256k1::Error> for ParseError {
+impl From<secp256k1::Error> for Bolt12ParseError {
fn from(error: secp256k1::Error) -> Self {
Self::InvalidSignature(error)
}
//! Data structures and encoding for refunds.
//!
//! A [`Refund`] is an "offer for money" and is typically constructed by a merchant and presented
-//! directly to the customer. The recipient responds with an [`Invoice`] to be paid.
+//! directly to the customer. The recipient responds with a [`Bolt12Invoice`] to be paid.
//!
//! This is an [`InvoiceRequest`] produced *not* in response to an [`Offer`].
//!
-//! [`Invoice`]: crate::offers::invoice::Invoice
+//! [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
//! [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
//! [`Offer`]: crate::offers::offer::Offer
//!
//!
//! use bitcoin::network::constants::Network;
//! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
-//! use lightning::offers::parse::ParseError;
+//! use lightning::offers::parse::Bolt12ParseError;
//! use lightning::offers::refund::{Refund, RefundBuilder};
//! use lightning::util::ser::{Readable, Writeable};
//!
//! # fn create_another_blinded_path() -> BlindedPath { unimplemented!() }
//! #
//! # #[cfg(feature = "std")]
-//! # fn build() -> Result<(), ParseError> {
+//! # fn build() -> Result<(), Bolt12ParseError> {
//! let secp_ctx = Secp256k1::new();
//! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
//! let pubkey = PublicKey::from(keys);
use crate::offers::invoice::{BlindedPayInfo, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder};
use crate::offers::invoice_request::{InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef};
-use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
+use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
use crate::offers::signer::{Metadata, MetadataMaterial, self};
use crate::util::ser::{SeekReadable, WithoutLength, Writeable, Writer};
/// [`Refund::amount_msats`].
pub fn new(
description: String, metadata: Vec<u8>, payer_id: PublicKey, amount_msats: u64
- ) -> Result<Self, SemanticError> {
+ ) -> Result<Self, Bolt12SemanticError> {
if amount_msats > MAX_VALUE_MSAT {
- return Err(SemanticError::InvalidAmount);
+ return Err(Bolt12SemanticError::InvalidAmount);
}
let metadata = Metadata::Bytes(metadata);
pub fn deriving_payer_id<ES: Deref>(
description: String, node_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
secp_ctx: &'a Secp256k1<T>, amount_msats: u64
- ) -> Result<Self, SemanticError> where ES::Target: EntropySource {
+ ) -> Result<Self, Bolt12SemanticError> where ES::Target: EntropySource {
if amount_msats > MAX_VALUE_MSAT {
- return Err(SemanticError::InvalidAmount);
+ return Err(Bolt12SemanticError::InvalidAmount);
}
let nonce = Nonce::from_entropy_source(entropy_source);
}
/// Sets [`Refund::quantity`] of items. This is purely for informational purposes. It is useful
- /// when the refund pertains to an [`Invoice`] that paid for more than one item from an
+ /// when the refund pertains to a [`Bolt12Invoice`] that paid for more than one item from an
/// [`Offer`] as specified by [`InvoiceRequest::quantity`].
///
/// Successive calls to this method will override the previous setting.
///
- /// [`Invoice`]: crate::offers::invoice::Invoice
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
/// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity
/// [`Offer`]: crate::offers::offer::Offer
pub fn quantity(mut self, quantity: u64) -> Self {
}
/// Builds a [`Refund`] after checking for valid semantics.
- pub fn build(mut self) -> Result<Refund, SemanticError> {
+ pub fn build(mut self) -> Result<Refund, Bolt12SemanticError> {
if self.refund.chain() == self.refund.implied_chain() {
self.refund.chain = None;
}
- // Create the metadata for stateless verification of an Invoice.
+ // Create the metadata for stateless verification of a Bolt12Invoice.
if self.refund.payer.0.has_derivation_material() {
let mut metadata = core::mem::take(&mut self.refund.payer.0);
}
}
-/// A `Refund` is a request to send an [`Invoice`] without a preceding [`Offer`].
+/// A `Refund` is a request to send an [`Bolt12Invoice`] without a preceding [`Offer`].
///
/// Typically, after an invoice is paid, the recipient may publish a refund allowing the sender to
/// recoup their funds. A refund may be used more generally as an "offer for money", such as with a
/// bitcoin ATM.
///
-/// [`Invoice`]: crate::offers::invoice::Invoice
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
/// [`Offer`]: crate::offers::offer::Offer
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub(super) contents: RefundContents,
}
-/// The contents of a [`Refund`], which may be shared with an [`Invoice`].
+/// The contents of a [`Refund`], which may be shared with an [`Bolt12Invoice`].
///
-/// [`Invoice`]: crate::offers::invoice::Invoice
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub(super) struct RefundContents {
/// [`Duration`]: core::time::Duration
#[cfg(feature = "std")]
pub fn respond_with(
- &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+ &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
signing_pubkey: PublicKey,
- ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
+ ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
let created_at = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
/// Creates an [`InvoiceBuilder`] for the refund with the given required fields.
///
/// Unless [`InvoiceBuilder::relative_expiry`] is set, the invoice will expire two hours after
- /// `created_at`, which is used to set [`Invoice::created_at`]. Useful for `no-std` builds where
- /// [`std::time::SystemTime`] is not available.
+ /// `created_at`, which is used to set [`Bolt12Invoice::created_at`]. Useful for `no-std` builds
+ /// where [`std::time::SystemTime`] is not available.
///
/// The caller is expected to remember the preimage of `payment_hash` in order to
/// claim a payment for the invoice.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
- /// [`Invoice::created_at`]: crate::offers::invoice::Invoice::created_at
+ /// [`Bolt12Invoice::created_at`]: crate::offers::invoice::Bolt12Invoice::created_at
pub fn respond_with_no_std(
- &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+ &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
signing_pubkey: PublicKey, created_at: Duration
- ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
+ ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
if self.features().requires_unknown_bits() {
- return Err(SemanticError::UnknownRequiredFeatures);
+ return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
InvoiceBuilder::for_refund(self, payment_paths, created_at, payment_hash, signing_pubkey)
}
/// Creates an [`InvoiceBuilder`] for the refund using the given required fields and that uses
- /// derived signing keys to sign the [`Invoice`].
+ /// derived signing keys to sign the [`Bolt12Invoice`].
///
/// See [`Refund::respond_with`] for further details.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
- /// [`Invoice`]: crate::offers::invoice::Invoice
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[cfg(feature = "std")]
pub fn respond_using_derived_keys<ES: Deref>(
- &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+ &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
expanded_key: &ExpandedKey, entropy_source: ES
- ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, SemanticError>
+ ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError>
where
ES::Target: EntropySource,
{
}
/// Creates an [`InvoiceBuilder`] for the refund using the given required fields and that uses
- /// derived signing keys to sign the [`Invoice`].
+ /// derived signing keys to sign the [`Bolt12Invoice`].
///
/// See [`Refund::respond_with_no_std`] for further details.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
- /// [`Invoice`]: crate::offers::invoice::Invoice
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub fn respond_using_derived_keys_no_std<ES: Deref>(
- &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+ &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
created_at: core::time::Duration, expanded_key: &ExpandedKey, entropy_source: ES
- ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, SemanticError>
+ ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError>
where
ES::Target: EntropySource,
{
if self.features().requires_unknown_bits() {
- return Err(SemanticError::UnknownRequiredFeatures);
+ return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
let nonce = Nonce::from_entropy_source(entropy_source);
}
impl FromStr for Refund {
- type Err = ParseError;
+ type Err = Bolt12ParseError;
fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
Refund::from_bech32_str(s)
}
impl TryFrom<Vec<u8>> for Refund {
- type Error = ParseError;
+ type Error = Bolt12ParseError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
let refund = ParsedMessage::<RefundTlvStream>::try_from(bytes)?;
}
impl TryFrom<RefundTlvStream> for RefundContents {
- type Error = SemanticError;
+ type Error = Bolt12SemanticError;
fn try_from(tlv_stream: RefundTlvStream) -> Result<Self, Self::Error> {
let (
) = tlv_stream;
let payer = match payer_metadata {
- None => return Err(SemanticError::MissingPayerMetadata),
+ None => return Err(Bolt12SemanticError::MissingPayerMetadata),
Some(metadata) => PayerContents(Metadata::Bytes(metadata)),
};
if metadata.is_some() {
- return Err(SemanticError::UnexpectedMetadata);
+ return Err(Bolt12SemanticError::UnexpectedMetadata);
}
if chains.is_some() {
- return Err(SemanticError::UnexpectedChain);
+ return Err(Bolt12SemanticError::UnexpectedChain);
}
if currency.is_some() || offer_amount.is_some() {
- return Err(SemanticError::UnexpectedAmount);
+ return Err(Bolt12SemanticError::UnexpectedAmount);
}
let description = match description {
- None => return Err(SemanticError::MissingDescription),
+ None => return Err(Bolt12SemanticError::MissingDescription),
Some(description) => description,
};
if offer_features.is_some() {
- return Err(SemanticError::UnexpectedFeatures);
+ return Err(Bolt12SemanticError::UnexpectedFeatures);
}
let absolute_expiry = absolute_expiry.map(Duration::from_secs);
if quantity_max.is_some() {
- return Err(SemanticError::UnexpectedQuantity);
+ return Err(Bolt12SemanticError::UnexpectedQuantity);
}
if node_id.is_some() {
- return Err(SemanticError::UnexpectedSigningPubkey);
+ return Err(Bolt12SemanticError::UnexpectedSigningPubkey);
}
let amount_msats = match amount {
- None => return Err(SemanticError::MissingAmount),
+ None => return Err(Bolt12SemanticError::MissingAmount),
Some(amount_msats) if amount_msats > MAX_VALUE_MSAT => {
- return Err(SemanticError::InvalidAmount);
+ return Err(Bolt12SemanticError::InvalidAmount);
},
Some(amount_msats) => amount_msats,
};
let features = features.unwrap_or_else(InvoiceRequestFeatures::empty);
let payer_id = match payer_id {
- None => return Err(SemanticError::MissingPayerId),
+ None => return Err(Bolt12SemanticError::MissingPayerId),
Some(payer_id) => payer_id,
};
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
use crate::offers::offer::OfferTlvStreamRef;
- use crate::offers::parse::{ParseError, SemanticError};
+ use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
use crate::offers::payer::PayerTlvStreamRef;
use crate::offers::test_utils::*;
use crate::util::ser::{BigSize, Writeable};
fn fails_building_refund_with_invalid_amount() {
match RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), MAX_VALUE_MSAT + 1) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InvalidAmount),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount),
}
}
.respond_with_no_std(payment_paths(), payment_hash(), recipient_pubkey(), now())
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::UnknownRequiredFeatures),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::UnknownRequiredFeatures),
}
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerMetadata));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPayerMetadata));
},
}
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingDescription));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingDescription));
},
}
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingAmount));
},
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidAmount));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidAmount));
},
}
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerId));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPayerId));
},
}
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedMetadata));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedMetadata));
},
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedChain));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedChain));
},
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedAmount));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedAmount));
},
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedFeatures));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedFeatures));
},
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedQuantity));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedQuantity));
},
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedSigningPubkey));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedSigningPubkey));
},
}
}
match Refund::try_from(encoded_refund) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
}
}
}
SecretKey::from_slice(&[byte; 32]).unwrap()
}
-pub(super) fn payment_paths() -> Vec<(BlindedPath, BlindedPayInfo)> {
+pub(super) fn payment_paths() -> Vec<(BlindedPayInfo, BlindedPath)> {
let paths = vec![
BlindedPath {
introduction_node_id: pubkey(40),
},
];
- paths.into_iter().zip(payinfo.into_iter()).collect()
+ payinfo.into_iter().zip(paths.into_iter()).collect()
}
pub(super) fn payment_hash() -> PaymentHash {
) -> Result<OnionMessagePath, ()>;
}
+/// A [`MessageRouter`] that always fails.
+pub struct DefaultMessageRouter;
+
+impl MessageRouter for DefaultMessageRouter {
+ fn find_path(
+ &self, _sender: PublicKey, _peers: Vec<PublicKey>, _destination: Destination
+ ) -> Result<OnionMessagePath, ()> {
+ Err(())
+ }
+}
+
/// A path for sending an [`msgs::OnionMessage`].
#[derive(Clone)]
pub struct OnionMessagePath {
///
/// [`SimpleArcChannelManager`]: crate::ln::channelmanager::SimpleArcChannelManager
/// [`SimpleArcPeerManager`]: crate::ln::peer_handler::SimpleArcPeerManager
-pub type SimpleArcOnionMessenger<L, R> = OnionMessenger<
+pub type SimpleArcOnionMessenger<L> = OnionMessenger<
Arc<KeysManager>,
Arc<KeysManager>,
Arc<L>,
- Arc<R>,
+ Arc<DefaultMessageRouter>,
IgnoringMessageHandler,
IgnoringMessageHandler
>;
///
/// [`SimpleRefChannelManager`]: crate::ln::channelmanager::SimpleRefChannelManager
/// [`SimpleRefPeerManager`]: crate::ln::peer_handler::SimpleRefPeerManager
-pub type SimpleRefOnionMessenger<'a, 'b, 'c, L, R> = OnionMessenger<
+pub type SimpleRefOnionMessenger<'a, 'b, 'c, L> = OnionMessenger<
&'a KeysManager,
&'a KeysManager,
&'b L,
- &'c R,
+ &'c DefaultMessageRouter,
IgnoringMessageHandler,
IgnoringMessageHandler
>;
mod functional_tests;
// Re-export structs so they can be imported with just the `onion_message::` module prefix.
-pub use self::messenger::{CustomOnionMessageContents, CustomOnionMessageHandler, Destination, MessageRouter, OnionMessageContents, OnionMessagePath, OnionMessenger, SendError, SimpleArcOnionMessenger, SimpleRefOnionMessenger};
+pub use self::messenger::{CustomOnionMessageContents, CustomOnionMessageHandler, DefaultMessageRouter, Destination, MessageRouter, OnionMessageContents, OnionMessagePath, OnionMessenger, SendError, SimpleArcOnionMessenger, SimpleRefOnionMessenger};
pub use self::offers::{OffersMessage, OffersMessageHandler};
pub(crate) use self::packet::{ControlTlvs, Packet};
use crate::ln::msgs::DecodeError;
use crate::offers::invoice_error::InvoiceError;
use crate::offers::invoice_request::InvoiceRequest;
-use crate::offers::invoice::Invoice;
-use crate::offers::parse::ParseError;
+use crate::offers::invoice::Bolt12Invoice;
+use crate::offers::parse::Bolt12ParseError;
use crate::util::logger::Logger;
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer};
///
/// [`OnionMessage`]: crate::ln::msgs::OnionMessage
pub trait OffersMessageHandler {
- /// Handles the given message by either responding with an [`Invoice`], sending a payment, or
- /// replying with an error.
+ /// Handles the given message by either responding with an [`Bolt12Invoice`], sending a payment,
+ /// or replying with an error.
fn handle_message(&self, message: OffersMessage) -> Option<OffersMessage>;
}
/// Possible BOLT 12 Offers messages sent and received via an [`OnionMessage`].
///
/// [`OnionMessage`]: crate::ln::msgs::OnionMessage
-#[derive(Debug)]
+#[derive(Clone, Debug)]
pub enum OffersMessage {
- /// A request for an [`Invoice`] for a particular [`Offer`].
+ /// A request for a [`Bolt12Invoice`] for a particular [`Offer`].
///
/// [`Offer`]: crate::offers::offer::Offer
InvoiceRequest(InvoiceRequest),
- /// An [`Invoice`] sent in response to an [`InvoiceRequest`] or a [`Refund`].
+ /// A [`Bolt12Invoice`] sent in response to an [`InvoiceRequest`] or a [`Refund`].
///
/// [`Refund`]: crate::offers::refund::Refund
- Invoice(Invoice),
+ Invoice(Bolt12Invoice),
/// An error from handling an [`OffersMessage`].
InvoiceError(InvoiceError),
}
}
- fn parse(tlv_type: u64, bytes: Vec<u8>) -> Result<Self, ParseError> {
+ fn parse(tlv_type: u64, bytes: Vec<u8>) -> Result<Self, Bolt12ParseError> {
match tlv_type {
INVOICE_REQUEST_TLV_TYPE => Ok(Self::InvoiceRequest(InvoiceRequest::try_from(bytes)?)),
- INVOICE_TLV_TYPE => Ok(Self::Invoice(Invoice::try_from(bytes)?)),
- _ => Err(ParseError::Decode(DecodeError::InvalidValue)),
+ INVOICE_TLV_TYPE => Ok(Self::Invoice(Bolt12Invoice::try_from(bytes)?)),
+ _ => Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
}
}
}
match Self::parse(tlv_type, bytes) {
Ok(message) => Ok(message),
- Err(ParseError::Decode(e)) => Err(e),
- Err(ParseError::InvalidSemantics(e)) => {
+ Err(Bolt12ParseError::Decode(e)) => Err(e),
+ Err(Bolt12ParseError::InvalidSemantics(e)) => {
log_trace!(logger, "Invalid semantics for TLV type {}: {:?}", tlv_type, e);
Err(DecodeError::InvalidValue)
},
- Err(ParseError::InvalidSignature(e)) => {
+ Err(Bolt12ParseError::InvalidSignature(e)) => {
log_trace!(logger, "Invalid signature for TLV type {}: {:?}", tlv_type, e);
Err(DecodeError::InvalidValue)
},
match &self.0 {
Payload::Forward(ForwardControlTlvs::Blinded(encrypted_bytes)) => {
_encode_varint_length_prefixed_tlv!(w, {
- (4, *encrypted_bytes, vec_type)
+ (4, *encrypted_bytes, required_vec)
})
},
Payload::Receive {
} => {
_encode_varint_length_prefixed_tlv!(w, {
(2, reply_path, option),
- (4, *encrypted_bytes, vec_type),
+ (4, *encrypted_bytes, required_vec),
(message.tlv_type(), message, required)
})
},
//! The [`NetworkGraph`] stores the network gossip and [`P2PGossipSync`] fetches it from peers
use bitcoin::secp256k1::constants::PUBLIC_KEY_SIZE;
-use bitcoin::secp256k1::PublicKey;
+use bitcoin::secp256k1::{PublicKey, Verification};
use bitcoin::secp256k1::Secp256k1;
use bitcoin::secp256k1;
}
}
+/// Verifies the signature of a [`NodeAnnouncement`].
+///
+/// Returns an error if it is invalid.
+pub fn verify_node_announcement<C: Verification>(msg: &NodeAnnouncement, secp_ctx: &Secp256k1<C>) -> Result<(), LightningError> {
+ let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.contents.encode()[..])[..]);
+ secp_verify_sig!(secp_ctx, &msg_hash, &msg.signature, &get_pubkey_from_node_id!(msg.contents.node_id, "node_announcement"), "node_announcement");
+
+ Ok(())
+}
+
+/// Verifies all signatures included in a [`ChannelAnnouncement`].
+///
+/// Returns an error if one of the signatures is invalid.
+pub fn verify_channel_announcement<C: Verification>(msg: &ChannelAnnouncement, secp_ctx: &Secp256k1<C>) -> Result<(), LightningError> {
+ let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.contents.encode()[..])[..]);
+ secp_verify_sig!(secp_ctx, &msg_hash, &msg.node_signature_1, &get_pubkey_from_node_id!(msg.contents.node_id_1, "channel_announcement"), "channel_announcement");
+ secp_verify_sig!(secp_ctx, &msg_hash, &msg.node_signature_2, &get_pubkey_from_node_id!(msg.contents.node_id_2, "channel_announcement"), "channel_announcement");
+ secp_verify_sig!(secp_ctx, &msg_hash, &msg.bitcoin_signature_1, &get_pubkey_from_node_id!(msg.contents.bitcoin_key_1, "channel_announcement"), "channel_announcement");
+ secp_verify_sig!(secp_ctx, &msg_hash, &msg.bitcoin_signature_2, &get_pubkey_from_node_id!(msg.contents.bitcoin_key_2, "channel_announcement"), "channel_announcement");
+
+ Ok(())
+}
+
impl<G: Deref<Target=NetworkGraph<L>>, U: Deref, L: Deref> RoutingMessageHandler for P2PGossipSync<G, U, L>
where U::Target: UtxoLookup, L::Target: Logger
{
(4, self.rgb, required),
(6, self.alias, required),
(8, self.announcement_message, option),
- (10, empty_addresses, vec_type), // Versions prior to 0.0.115 require this field
+ (10, empty_addresses, required_vec), // Versions prior to 0.0.115 require this field
});
Ok(())
}
}
impl Readable for NodeAnnouncementInfo {
- fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
+ fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
_init_and_read_tlv_fields!(reader, {
(0, features, required),
(2, last_update, required),
(4, rgb, required),
(6, alias, required),
(8, announcement_message, option),
- (10, _addresses, vec_type), // deprecated, not used anymore
+ (10, _addresses, optional_vec), // deprecated, not used anymore
});
let _: Option<Vec<NetAddress>> = _addresses;
Ok(Self { features: features.0.unwrap(), last_update: last_update.0.unwrap(), rgb: rgb.0.unwrap(),
alias: alias.0.unwrap(), announcement_message })
- }
+ }
}
/// A user-defined name for a node, which may be used when displaying the node in a graph.
write_tlv_fields!(writer, {
// Note that older versions of LDK wrote the lowest inbound fees here at type 0
(2, self.announcement_info, option),
- (4, self.channels, vec_type),
+ (4, self.channels, required_vec),
});
Ok(())
}
// with zero inbound fees, causing that heuristic to provide little gain. Worse, because it
// requires additional complexity and lookups during routing, it ends up being a
// performance loss. Thus, we simply ignore the old field here and no longer track it.
- let mut _lowest_inbound_channel_fees: Option<RoutingFees> = None;
- let mut announcement_info_wrap: Option<NodeAnnouncementInfoDeserWrapper> = None;
- _init_tlv_field_var!(channels, vec_type);
-
- read_tlv_fields!(reader, {
+ _init_and_read_tlv_fields!(reader, {
(0, _lowest_inbound_channel_fees, option),
(2, announcement_info_wrap, upgradable_option),
- (4, channels, vec_type),
+ (4, channels, required_vec),
});
+ let _: Option<RoutingFees> = _lowest_inbound_channel_fees;
+ let announcement_info_wrap: Option<NodeAnnouncementInfoDeserWrapper> = announcement_info_wrap;
Ok(NodeInfo {
announcement_info: announcement_info_wrap.map(|w| w.0),
- channels: _init_tlv_based_struct_field!(channels, vec_type),
+ channels,
})
}
}
/// RoutingMessageHandler implementation to call it indirectly. This may be useful to accept
/// routing messages from a source using a protocol other than the lightning P2P protocol.
pub fn update_node_from_announcement(&self, msg: &msgs::NodeAnnouncement) -> Result<(), LightningError> {
- let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.contents.encode()[..])[..]);
- secp_verify_sig!(self.secp_ctx, &msg_hash, &msg.signature, &get_pubkey_from_node_id!(msg.contents.node_id, "node_announcement"), "node_announcement");
+ verify_node_announcement(msg, &self.secp_ctx)?;
self.update_node_from_announcement_intern(&msg.contents, Some(&msg))
}
where
U::Target: UtxoLookup,
{
- let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.contents.encode()[..])[..]);
- secp_verify_sig!(self.secp_ctx, &msg_hash, &msg.node_signature_1, &get_pubkey_from_node_id!(msg.contents.node_id_1, "channel_announcement"), "channel_announcement");
- secp_verify_sig!(self.secp_ctx, &msg_hash, &msg.node_signature_2, &get_pubkey_from_node_id!(msg.contents.node_id_2, "channel_announcement"), "channel_announcement");
- secp_verify_sig!(self.secp_ctx, &msg_hash, &msg.bitcoin_signature_1, &get_pubkey_from_node_id!(msg.contents.bitcoin_key_1, "channel_announcement"), "channel_announcement");
- secp_verify_sig!(self.secp_ctx, &msg_hash, &msg.bitcoin_signature_2, &get_pubkey_from_node_id!(msg.contents.bitcoin_key_2, "channel_announcement"), "channel_announcement");
+ verify_channel_announcement(msg, &self.secp_ctx)?;
self.update_channel_from_unsigned_announcement_intern(&msg.contents, Some(msg), utxo_lookup)
}
use crate::blinded_path::{BlindedHop, BlindedPath};
use crate::ln::PaymentHash;
use crate::ln::channelmanager::{ChannelDetails, PaymentId};
-use crate::ln::features::{Bolt12InvoiceFeatures, ChannelFeatures, InvoiceFeatures, NodeFeatures};
+use crate::ln::features::{Bolt11InvoiceFeatures, Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures};
use crate::ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT};
-use crate::offers::invoice::BlindedPayInfo;
+use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice};
use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId, RoutingFees};
use crate::routing::scoring::{ChannelUsage, LockableScore, Score};
use crate::util::ser::{Writeable, Readable, ReadableArgs, Writer};
use crate::io;
use crate::prelude::*;
-use crate::sync::{Mutex, MutexGuard};
+use crate::sync::{Mutex};
use alloc::collections::BinaryHeap;
use core::{cmp, fmt};
-use core::ops::Deref;
+use core::ops::{Deref, DerefMut};
/// A [`Router`] implemented using [`find_route`].
pub struct DefaultRouter<G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref, SP: Sized, Sc: Score<ScoreParams = SP>> where
L::Target: Logger,
- S::Target: for <'a> LockableScore<'a, Locked = MutexGuard<'a, Sc>>,
+ S::Target: for <'a> LockableScore<'a, Score = Sc>,
{
network_graph: G,
logger: L,
impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref, SP: Sized, Sc: Score<ScoreParams = SP>> DefaultRouter<G, L, S, SP, Sc> where
L::Target: Logger,
- S::Target: for <'a> LockableScore<'a, Locked = MutexGuard<'a, Sc>>,
+ S::Target: for <'a> LockableScore<'a, Score = Sc>,
{
/// Creates a new router.
pub fn new(network_graph: G, logger: L, random_seed_bytes: [u8; 32], scorer: S, score_params: SP) -> Self {
}
}
-impl< G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref, SP: Sized, Sc: Score<ScoreParams = SP>> Router for DefaultRouter<G, L, S, SP, Sc> where
+impl< G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref, SP: Sized, Sc: Score<ScoreParams = SP>> Router for DefaultRouter<G, L, S, SP, Sc> where
L::Target: Logger,
- S::Target: for <'a> LockableScore<'a, Locked = MutexGuard<'a, Sc>>,
+ S::Target: for <'a> LockableScore<'a, Score = Sc>,
{
fn find_route(
&self,
payer: &PublicKey,
params: &RouteParameters,
first_hops: Option<&[&ChannelDetails]>,
- inflight_htlcs: &InFlightHtlcs
+ inflight_htlcs: InFlightHtlcs
) -> Result<Route, LightningError> {
let random_seed_bytes = {
let mut locked_random_seed_bytes = self.random_seed_bytes.lock().unwrap();
};
find_route(
payer, params, &self.network_graph, first_hops, &*self.logger,
- &ScorerAccountingForInFlightHtlcs::new(self.scorer.lock(), inflight_htlcs),
+ &ScorerAccountingForInFlightHtlcs::new(self.scorer.lock().deref_mut(), &inflight_htlcs),
&self.score_params,
&random_seed_bytes
)
/// Finds a [`Route`] between `payer` and `payee` for a payment with the given values.
fn find_route(
&self, payer: &PublicKey, route_params: &RouteParameters,
- first_hops: Option<&[&ChannelDetails]>, inflight_htlcs: &InFlightHtlcs
+ first_hops: Option<&[&ChannelDetails]>, inflight_htlcs: InFlightHtlcs
) -> Result<Route, LightningError>;
/// Finds a [`Route`] between `payer` and `payee` for a payment with the given values. Includes
/// `PaymentHash` and `PaymentId` to be able to correlate the request with a specific payment.
fn find_route_with_id(
&self, payer: &PublicKey, route_params: &RouteParameters,
- first_hops: Option<&[&ChannelDetails]>, inflight_htlcs: &InFlightHtlcs,
+ first_hops: Option<&[&ChannelDetails]>, inflight_htlcs: InFlightHtlcs,
_payment_hash: PaymentHash, _payment_id: PaymentId
) -> Result<Route, LightningError> {
self.find_route(payer, route_params, first_hops, inflight_htlcs)
/// [`find_route`].
///
/// [`Score`]: crate::routing::scoring::Score
-pub struct ScorerAccountingForInFlightHtlcs<'a, S: Score> {
- scorer: S,
+pub struct ScorerAccountingForInFlightHtlcs<'a, S: Score<ScoreParams = SP>, SP: Sized> {
+ scorer: &'a mut S,
// Maps a channel's short channel id and its direction to the liquidity used up.
inflight_htlcs: &'a InFlightHtlcs,
}
-impl<'a, S: Score> ScorerAccountingForInFlightHtlcs<'a, S> {
+impl<'a, S: Score<ScoreParams = SP>, SP: Sized> ScorerAccountingForInFlightHtlcs<'a, S, SP> {
/// Initialize a new `ScorerAccountingForInFlightHtlcs`.
- pub fn new(scorer: S, inflight_htlcs: &'a InFlightHtlcs) -> Self {
+ pub fn new(scorer: &'a mut S, inflight_htlcs: &'a InFlightHtlcs) -> Self {
ScorerAccountingForInFlightHtlcs {
scorer,
inflight_htlcs
}
#[cfg(c_bindings)]
-impl<'a, S: Score> Writeable for ScorerAccountingForInFlightHtlcs<'a, S> {
+impl<'a, S: Score<ScoreParams = SP>, SP: Sized> Writeable for ScorerAccountingForInFlightHtlcs<'a, S, SP> {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> { self.scorer.write(writer) }
}
-impl<'a, S: Score> Score for ScorerAccountingForInFlightHtlcs<'a, S> {
+impl<'a, S: Score<ScoreParams = SP>, SP: Sized> Score for ScorerAccountingForInFlightHtlcs<'a, S, SP> {
type ScoreParams = S::ScoreParams;
fn channel_penalty_msat(&self, short_channel_id: u64, source: &NodeId, target: &NodeId, usage: ChannelUsage, score_params: &Self::ScoreParams) -> u64 {
if let Some(used_liquidity) = self.inflight_htlcs.used_liquidity_msat(
}
}
+ /// Adds a known HTLC given the public key of the HTLC source, target, and short channel
+ /// id.
+ pub fn add_inflight_htlc(&mut self, source: &NodeId, target: &NodeId, channel_scid: u64, used_msat: u64){
+ self.0
+ .entry((channel_scid, source < target))
+ .and_modify(|used_liquidity_msat| *used_liquidity_msat += used_msat)
+ .or_insert(used_msat);
+ }
+
/// Returns liquidity in msat given the public key of the HTLC source, target, and short channel
/// id.
pub fn used_liquidity_msat(&self, source: &NodeId, target: &NodeId, channel_scid: u64) -> Option<u64> {
});
/// The blinded portion of a [`Path`], if we're routing to a recipient who provided blinded paths in
-/// their BOLT12 [`Invoice`].
+/// their [`Bolt12Invoice`].
///
-/// [`Invoice`]: crate::offers::invoice::Invoice
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct BlindedTail {
/// The hops of the [`BlindedPath`] provided by the recipient.
}
impl_writeable_tlv_based!(BlindedTail, {
- (0, hops, vec_type),
+ (0, hops, required_vec),
(2, blinding_point, required),
(4, excess_final_cltv_expiry_delta, required),
(6, final_value_msat, required),
let blinded_tails = blinded_tails.unwrap_or(Vec::new());
if blinded_tails.len() != 0 {
if blinded_tails.len() != paths.len() { return Err(DecodeError::InvalidValue) }
- for (mut path, blinded_tail_opt) in paths.iter_mut().zip(blinded_tails.into_iter()) {
+ for (path, blinded_tail_opt) in paths.iter_mut().zip(blinded_tails.into_iter()) {
path.blinded_tail = blinded_tail_opt;
}
}
// limits, but for now more than 10 paths likely carries too much one-path failure.
pub const DEFAULT_MAX_PATH_COUNT: u8 = 10;
+const DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF: u8 = 2;
+
// The median hop CLTV expiry delta currently seen in the network.
const MEDIAN_HOP_CLTV_EXPIRY_DELTA: u32 = 40;
(1, self.max_total_cltv_expiry_delta, required),
(2, self.payee.features(), option),
(3, self.max_path_count, required),
- (4, *clear_hints, vec_type),
+ (4, *clear_hints, required_vec),
(5, self.max_channel_saturation_power_of_half, required),
(6, self.expiry_time, option),
- (7, self.previously_failed_channels, vec_type),
+ (7, self.previously_failed_channels, required_vec),
(8, *blinded_hints, optional_vec),
(9, self.payee.final_cltv_expiry_delta(), option),
});
(1, max_total_cltv_expiry_delta, (default_value, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA)),
(2, features, (option: ReadableArgs, payee_pubkey.is_some())),
(3, max_path_count, (default_value, DEFAULT_MAX_PATH_COUNT)),
- (4, route_hints, vec_type),
- (5, max_channel_saturation_power_of_half, (default_value, 2)),
+ (4, clear_route_hints, required_vec),
+ (5, max_channel_saturation_power_of_half, (default_value, DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF)),
(6, expiry_time, option),
- (7, previously_failed_channels, vec_type),
+ (7, previously_failed_channels, optional_vec),
(8, blinded_route_hints, optional_vec),
(9, final_cltv_expiry_delta, (default_value, default_final_cltv_expiry_delta)),
});
- let clear_route_hints = route_hints.unwrap_or(vec![]);
let blinded_route_hints = blinded_route_hints.unwrap_or(vec![]);
let payee = if blinded_route_hints.len() != 0 {
if clear_route_hints.len() != 0 || payee_pubkey.is_some() { return Err(DecodeError::InvalidValue) }
expiry_time: None,
max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA,
max_path_count: DEFAULT_MAX_PATH_COUNT,
- max_channel_saturation_power_of_half: 2,
+ max_channel_saturation_power_of_half: DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF,
previously_failed_channels: Vec::new(),
}
}
/// [`RecipientOnionFields::secret_only`]: crate::ln::channelmanager::RecipientOnionFields::secret_only
pub fn for_keysend(payee_pubkey: PublicKey, final_cltv_expiry_delta: u32, allow_mpp: bool) -> Self {
Self::from_node_id(payee_pubkey, final_cltv_expiry_delta)
- .with_bolt11_features(InvoiceFeatures::for_keysend(allow_mpp))
+ .with_bolt11_features(Bolt11InvoiceFeatures::for_keysend(allow_mpp))
.expect("PaymentParameters::from_node_id should always initialize the payee as unblinded")
}
- /// Includes the payee's features. Errors if the parameters were initialized with blinded payment
- /// paths.
+ /// Creates parameters for paying to a blinded payee from the provided invoice. Sets
+ /// [`Payee::Blinded::route_hints`], [`Payee::Blinded::features`], and
+ /// [`PaymentParameters::expiry_time`].
+ pub fn from_bolt12_invoice(invoice: &Bolt12Invoice) -> Self {
+ Self::blinded(invoice.payment_paths().to_vec())
+ .with_bolt12_features(invoice.features().clone()).unwrap()
+ .with_expiry_time(invoice.created_at().as_secs().saturating_add(invoice.relative_expiry().as_secs()))
+ }
+
+ fn blinded(blinded_route_hints: Vec<(BlindedPayInfo, BlindedPath)>) -> Self {
+ Self {
+ payee: Payee::Blinded { route_hints: blinded_route_hints, features: None },
+ expiry_time: None,
+ max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA,
+ max_path_count: DEFAULT_MAX_PATH_COUNT,
+ max_channel_saturation_power_of_half: DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF,
+ previously_failed_channels: Vec::new(),
+ }
+ }
+
+ /// Includes the payee's features. Errors if the parameters were not initialized with
+ /// [`PaymentParameters::from_bolt12_invoice`].
+ ///
+ /// This is not exported to bindings users since bindings don't support move semantics
+ pub fn with_bolt12_features(self, features: Bolt12InvoiceFeatures) -> Result<Self, ()> {
+ match self.payee {
+ Payee::Clear { .. } => Err(()),
+ Payee::Blinded { route_hints, .. } =>
+ Ok(Self { payee: Payee::Blinded { route_hints, features: Some(features) }, ..self })
+ }
+ }
+
+ /// Includes the payee's features. Errors if the parameters were initialized with
+ /// [`PaymentParameters::from_bolt12_invoice`].
///
/// This is not exported to bindings users since bindings don't support move semantics
- pub fn with_bolt11_features(self, features: InvoiceFeatures) -> Result<Self, ()> {
+ pub fn with_bolt11_features(self, features: Bolt11InvoiceFeatures) -> Result<Self, ()> {
match self.payee {
Payee::Blinded { .. } => Err(()),
Payee::Clear { route_hints, node_id, final_cltv_expiry_delta, .. } =>
}
/// Includes hints for routing to the payee. Errors if the parameters were initialized with
- /// blinded payment paths.
+ /// [`PaymentParameters::from_bolt12_invoice`].
///
/// This is not exported to bindings users since bindings don't support move semantics
pub fn with_route_hints(self, route_hints: Vec<RouteHint>) -> Result<Self, ()> {
Self { max_path_count, ..self }
}
- /// Includes a limit for the maximum number of payment paths that may be used.
+ /// Includes a limit for the maximum share of a channel's total capacity that can be sent over, as
+ /// a power of 1/2. See [`PaymentParameters::max_channel_saturation_power_of_half`].
///
/// This is not exported to bindings users since bindings don't support move semantics
pub fn with_max_channel_saturation_power_of_half(self, max_channel_saturation_power_of_half: u8) -> Self {
/// does not contain any features.
///
/// [`for_keysend`]: PaymentParameters::for_keysend
- features: Option<InvoiceFeatures>,
+ features: Option<Bolt11InvoiceFeatures>,
/// The minimum CLTV delta at the end of the route. This value must not be zero.
final_cltv_expiry_delta: u32,
},
_ => None,
}
}
+ fn blinded_route_hints(&self) -> &[(BlindedPayInfo, BlindedPath)] {
+ match self {
+ Self::Blinded { route_hints, .. } => &route_hints[..],
+ Self::Clear { .. } => &[]
+ }
+ }
+
+ fn unblinded_route_hints(&self) -> &[RouteHint] {
+ match self {
+ Self::Blinded { .. } => &[],
+ Self::Clear { route_hints, .. } => &route_hints[..]
+ }
+ }
}
enum FeaturesRef<'a> {
- Bolt11(&'a InvoiceFeatures),
+ Bolt11(&'a Bolt11InvoiceFeatures),
Bolt12(&'a Bolt12InvoiceFeatures),
}
enum Features {
- Bolt11(InvoiceFeatures),
+ Bolt11(Bolt11InvoiceFeatures),
Bolt12(Bolt12InvoiceFeatures),
}
_ => None,
}
}
- fn bolt11(self) -> Option<InvoiceFeatures> {
+ fn bolt11(self) -> Option<Bolt11InvoiceFeatures> {
match self {
Self::Bolt11(f) => Some(f),
_ => None,
info: DirectedChannelInfo<'a>,
short_channel_id: u64,
},
- /// A hop to the payee found in the payment invoice, though not necessarily a direct channel.
+ /// A hop to the payee found in the BOLT 11 payment invoice, though not necessarily a direct
+ /// channel.
PrivateHop {
hint: &'a RouteHintHop,
- }
+ },
+ /// The payee's identity is concealed behind blinded paths provided in a BOLT 12 invoice.
+ Blinded {
+ hint: &'a (BlindedPayInfo, BlindedPath),
+ hint_idx: usize,
+ },
+ /// Similar to [`Self::Blinded`], but the path here has 1 blinded hop. `BlindedPayInfo` provided
+ /// for 1-hop blinded paths is ignored because it is meant to apply to the hops *between* the
+ /// introduction node and the destination. Useful for tracking that we need to include a blinded
+ /// path at the end of our [`Route`].
+ OneHopBlinded {
+ hint: &'a (BlindedPayInfo, BlindedPath),
+ hint_idx: usize,
+ },
}
impl<'a> CandidateRouteHop<'a> {
- fn short_channel_id(&self) -> u64 {
+ fn short_channel_id(&self) -> Option<u64> {
match self {
- CandidateRouteHop::FirstHop { details } => details.get_outbound_payment_scid().unwrap(),
- CandidateRouteHop::PublicHop { short_channel_id, .. } => *short_channel_id,
- CandidateRouteHop::PrivateHop { hint } => hint.short_channel_id,
+ CandidateRouteHop::FirstHop { details } => Some(details.get_outbound_payment_scid().unwrap()),
+ CandidateRouteHop::PublicHop { short_channel_id, .. } => Some(*short_channel_id),
+ CandidateRouteHop::PrivateHop { hint } => Some(hint.short_channel_id),
+ CandidateRouteHop::Blinded { .. } => None,
+ CandidateRouteHop::OneHopBlinded { .. } => None,
}
}
CandidateRouteHop::FirstHop { details } => details.counterparty.features.to_context(),
CandidateRouteHop::PublicHop { info, .. } => info.channel().features.clone(),
CandidateRouteHop::PrivateHop { .. } => ChannelFeatures::empty(),
+ CandidateRouteHop::Blinded { .. } => ChannelFeatures::empty(),
+ CandidateRouteHop::OneHopBlinded { .. } => ChannelFeatures::empty(),
}
}
CandidateRouteHop::FirstHop { .. } => 0,
CandidateRouteHop::PublicHop { info, .. } => info.direction().cltv_expiry_delta as u32,
CandidateRouteHop::PrivateHop { hint } => hint.cltv_expiry_delta as u32,
+ CandidateRouteHop::Blinded { hint, .. } => hint.0.cltv_expiry_delta as u32,
+ CandidateRouteHop::OneHopBlinded { .. } => 0,
}
}
CandidateRouteHop::FirstHop { details } => details.next_outbound_htlc_minimum_msat,
CandidateRouteHop::PublicHop { info, .. } => info.direction().htlc_minimum_msat,
CandidateRouteHop::PrivateHop { hint } => hint.htlc_minimum_msat.unwrap_or(0),
+ CandidateRouteHop::Blinded { hint, .. } => hint.0.htlc_minimum_msat,
+ CandidateRouteHop::OneHopBlinded { .. } => 0,
}
}
},
CandidateRouteHop::PublicHop { info, .. } => info.direction().fees,
CandidateRouteHop::PrivateHop { hint } => hint.fees,
+ CandidateRouteHop::Blinded { hint, .. } => {
+ RoutingFees {
+ base_msat: hint.0.fee_base_msat,
+ proportional_millionths: hint.0.fee_proportional_millionths
+ }
+ },
+ CandidateRouteHop::OneHopBlinded { .. } =>
+ RoutingFees { base_msat: 0, proportional_millionths: 0 },
}
}
EffectiveCapacity::HintMaxHTLC { amount_msat: *max },
CandidateRouteHop::PrivateHop { hint: RouteHintHop { htlc_maximum_msat: None, .. }} =>
EffectiveCapacity::Infinite,
+ CandidateRouteHop::Blinded { hint, .. } =>
+ EffectiveCapacity::HintMaxHTLC { amount_msat: hint.0.htlc_maximum_msat },
+ CandidateRouteHop::OneHopBlinded { .. } => EffectiveCapacity::Infinite,
}
}
+
+ fn id(&self, channel_direction: bool /* src_node_id < target_node_id */) -> CandidateHopId {
+ match self {
+ CandidateRouteHop::Blinded { hint_idx, .. } => CandidateHopId::Blinded(*hint_idx),
+ CandidateRouteHop::OneHopBlinded { hint_idx, .. } => CandidateHopId::Blinded(*hint_idx),
+ _ => CandidateHopId::Clear((self.short_channel_id().unwrap(), channel_direction)),
+ }
+ }
+ fn blinded_path(&self) -> Option<&'a BlindedPath> {
+ match self {
+ CandidateRouteHop::Blinded { hint, .. } | CandidateRouteHop::OneHopBlinded { hint, .. } => {
+ Some(&hint.1)
+ },
+ _ => None,
+ }
+ }
+}
+
+#[derive(Clone, Copy, Eq, Hash, Ord, PartialOrd, PartialEq)]
+enum CandidateHopId {
+ /// Contains (scid, src_node_id < target_node_id)
+ Clear((u64, bool)),
+ /// Index of the blinded route hint in [`Payee::Blinded::route_hints`].
+ Blinded(usize),
}
#[inline]
cur_hop_fees_msat = self.hops.get(i + 1).unwrap().0.hop_use_fee_msat;
}
- let mut cur_hop = &mut self.hops.get_mut(i).unwrap().0;
+ let cur_hop = &mut self.hops.get_mut(i).unwrap().0;
cur_hop.next_hops_fee_msat = total_fee_paid_msat;
// Overpay in fees if we can't save these funds due to htlc_minimum_msat.
// We try to account for htlc_minimum_msat in scoring (add_entry!), so that nodes don't
}
}
+struct LoggedCandidateHop<'a>(&'a CandidateRouteHop<'a>);
+impl<'a> fmt::Display for LoggedCandidateHop<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.0 {
+ CandidateRouteHop::Blinded { hint, .. } | CandidateRouteHop::OneHopBlinded { hint, .. } => {
+ "blinded route hint with introduction node id ".fmt(f)?;
+ hint.1.introduction_node_id.fmt(f)?;
+ " and blinding point ".fmt(f)?;
+ hint.1.blinding_point.fmt(f)
+ },
+ _ => {
+ "SCID ".fmt(f)?;
+ self.0.short_channel_id().unwrap().fmt(f)
+ },
+ }
+ }
+}
+
#[inline]
fn sort_first_hop_channels(
- channels: &mut Vec<&ChannelDetails>, used_channel_liquidities: &HashMap<(u64, bool), u64>,
+ channels: &mut Vec<&ChannelDetails>, used_liquidities: &HashMap<CandidateHopId, u64>,
recommended_value_msat: u64, our_node_pubkey: &PublicKey
) {
// Sort the first_hops channels to the same node(s) in priority order of which channel we'd
// Available outbound balances factor in liquidity already reserved for previously found paths.
channels.sort_unstable_by(|chan_a, chan_b| {
let chan_a_outbound_limit_msat = chan_a.next_outbound_htlc_limit_msat
- .saturating_sub(*used_channel_liquidities.get(&(chan_a.get_outbound_payment_scid().unwrap(),
- our_node_pubkey < &chan_a.counterparty.node_id)).unwrap_or(&0));
+ .saturating_sub(*used_liquidities.get(&CandidateHopId::Clear((chan_a.get_outbound_payment_scid().unwrap(),
+ our_node_pubkey < &chan_a.counterparty.node_id))).unwrap_or(&0));
let chan_b_outbound_limit_msat = chan_b.next_outbound_htlc_limit_msat
- .saturating_sub(*used_channel_liquidities.get(&(chan_b.get_outbound_payment_scid().unwrap(),
- our_node_pubkey < &chan_b.counterparty.node_id)).unwrap_or(&0));
+ .saturating_sub(*used_liquidities.get(&CandidateHopId::Clear((chan_b.get_outbound_payment_scid().unwrap(),
+ our_node_pubkey < &chan_b.counterparty.node_id))).unwrap_or(&0));
if chan_b_outbound_limit_msat < recommended_value_msat || chan_a_outbound_limit_msat < recommended_value_msat {
// Sort in descending order
chan_b_outbound_limit_msat.cmp(&chan_a_outbound_limit_msat)
// unblinded payee id as an option. We also need a non-optional "payee id" for path construction,
// so use a dummy id for this in the blinded case.
let payee_node_id_opt = payment_params.payee.node_id().map(|pk| NodeId::from_pubkey(&pk));
- const DUMMY_BLINDED_PAYEE_ID: [u8; 33] = [42u8; 33];
+ const DUMMY_BLINDED_PAYEE_ID: [u8; 33] = [2; 33];
let maybe_dummy_payee_pk = payment_params.payee.node_id().unwrap_or_else(|| PublicKey::from_slice(&DUMMY_BLINDED_PAYEE_ID).unwrap());
let maybe_dummy_payee_node_id = NodeId::from_pubkey(&maybe_dummy_payee_pk);
let our_node_id = NodeId::from_pubkey(&our_node_pubkey);
}
}
},
- _ => return Err(LightningError{err: "Routing to blinded paths isn't supported yet".to_owned(), action: ErrorAction::IgnoreError}),
-
+ Payee::Blinded { route_hints, .. } => {
+ if route_hints.iter().all(|(_, path)| &path.introduction_node_id == our_node_pubkey) {
+ return Err(LightningError{err: "Cannot generate a route to blinded paths if we are the introduction node to all of them".to_owned(), action: ErrorAction::IgnoreError});
+ }
+ for (_, blinded_path) in route_hints.iter() {
+ if blinded_path.blinded_hops.len() == 0 {
+ return Err(LightningError{err: "0-hop blinded path provided".to_owned(), action: ErrorAction::IgnoreError});
+ } else if &blinded_path.introduction_node_id == our_node_pubkey {
+ log_info!(logger, "Got blinded path with ourselves as the introduction node, ignoring");
+ } else if blinded_path.blinded_hops.len() == 1 &&
+ route_hints.iter().any( |(_, p)| p.blinded_hops.len() == 1
+ && p.introduction_node_id != blinded_path.introduction_node_id)
+ {
+ return Err(LightningError{err: format!("1-hop blinded paths must all have matching introduction node ids"), action: ErrorAction::IgnoreError});
+ }
+ }
+ }
}
let final_cltv_expiry_delta = payment_params.payee.final_cltv_expiry_delta().unwrap_or(0);
if payment_params.max_total_cltv_expiry_delta <= final_cltv_expiry_delta {
// drop the requirement by setting this to 0.
let mut channel_saturation_pow_half = payment_params.max_channel_saturation_power_of_half;
- // Keep track of how much liquidity has been used in selected channels. Used to determine
- // if the channel can be used by additional MPP paths or to inform path finding decisions. It is
- // aware of direction *only* to ensure that the correct htlc_maximum_msat value is used. Hence,
- // liquidity used in one direction will not offset any used in the opposite direction.
- let mut used_channel_liquidities: HashMap<(u64, bool), u64> =
+ // Keep track of how much liquidity has been used in selected channels or blinded paths. Used to
+ // determine if the channel can be used by additional MPP paths or to inform path finding
+ // decisions. It is aware of direction *only* to ensure that the correct htlc_maximum_msat value
+ // is used. Hence, liquidity used in one direction will not offset any used in the opposite
+ // direction.
+ let mut used_liquidities: HashMap<CandidateHopId, u64> =
HashMap::with_capacity(network_nodes.len());
// Keeping track of how much value we already collected across other paths. Helps to decide
let mut already_collected_value_msat = 0;
for (_, channels) in first_hop_targets.iter_mut() {
- sort_first_hop_channels(channels, &used_channel_liquidities, recommended_value_msat,
+ sort_first_hop_channels(channels, &used_liquidities, recommended_value_msat,
our_node_pubkey);
}
// - for regular channels at channel announcement (TODO)
// - for first and last hops early in get_route
if $src_node_id != $dest_node_id {
- let short_channel_id = $candidate.short_channel_id();
+ let scid_opt = $candidate.short_channel_id();
let effective_capacity = $candidate.effective_capacity();
let htlc_maximum_msat = max_htlc_from_capacity(effective_capacity, channel_saturation_pow_half);
// if the amount being transferred over this path is lower.
// We do this for now, but this is a subject for removal.
if let Some(mut available_value_contribution_msat) = htlc_maximum_msat.checked_sub($next_hops_fee_msat) {
- let used_liquidity_msat = used_channel_liquidities
- .get(&(short_channel_id, $src_node_id < $dest_node_id))
+ let used_liquidity_msat = used_liquidities
+ .get(&$candidate.id($src_node_id < $dest_node_id))
.map_or(0, |used_liquidity_msat| {
available_value_contribution_msat = available_value_contribution_msat
.saturating_sub(*used_liquidity_msat);
(amount_to_transfer_over_msat < $next_hops_path_htlc_minimum_msat &&
recommended_value_msat > $next_hops_path_htlc_minimum_msat));
- let payment_failed_on_this_channel =
- payment_params.previously_failed_channels.contains(&short_channel_id);
+ let payment_failed_on_this_channel = scid_opt.map_or(false,
+ |scid| payment_params.previously_failed_channels.contains(&scid));
// If HTLC minimum is larger than the amount we're going to transfer, we shouldn't
// bother considering this channel. If retrying with recommended_value_msat may
inflight_htlc_msat: used_liquidity_msat,
effective_capacity,
};
- let channel_penalty_msat = scorer.channel_penalty_msat(
- short_channel_id, &$src_node_id, &$dest_node_id, channel_usage, score_params
- );
+ let channel_penalty_msat = scid_opt.map_or(0,
+ |scid| scorer.channel_penalty_msat(scid, &$src_node_id, &$dest_node_id,
+ channel_usage, score_params));
let path_penalty_msat = $next_hops_path_penalty_msat
.saturating_add(channel_penalty_msat);
let new_graph_node = RouteGraphNode {
// TODO: diversify by nodes (so that all paths aren't doomed if one node is offline).
'paths_collection: loop {
- // For every new path, start from scratch, except for used_channel_liquidities, which
+ // For every new path, start from scratch, except for used_liquidities, which
// helps to avoid reusing previously selected paths in future iterations.
targets.clear();
dist.clear();
let candidate = CandidateRouteHop::FirstHop { details };
let added = add_entry!(candidate, our_node_id, payee, 0, path_value_msat,
0, 0u64, 0, 0).is_some();
- log_trace!(logger, "{} direct route to payee via SCID {}",
- if added { "Added" } else { "Skipped" }, candidate.short_channel_id());
+ log_trace!(logger, "{} direct route to payee via {}",
+ if added { "Added" } else { "Skipped" }, LoggedCandidateHop(&candidate));
}
}));
// If a caller provided us with last hops, add them to routing targets. Since this happens
// earlier than general path finding, they will be somewhat prioritized, although currently
// it matters only if the fees are exactly the same.
- let route_hints = match &payment_params.payee {
- Payee::Clear { route_hints, .. } => route_hints,
- _ => return Err(LightningError{err: "Routing to blinded paths isn't supported yet".to_owned(), action: ErrorAction::IgnoreError}),
- };
- for route in route_hints.iter().filter(|route| !route.0.is_empty()) {
+ for (hint_idx, hint) in payment_params.payee.blinded_route_hints().iter().enumerate() {
+ let intro_node_id = NodeId::from_pubkey(&hint.1.introduction_node_id);
+ let have_intro_node_in_graph =
+ // Only add the hops in this route to our candidate set if either
+ // we have a direct channel to the first hop or the first hop is
+ // in the regular network graph.
+ first_hop_targets.get(&intro_node_id).is_some() ||
+ network_nodes.get(&intro_node_id).is_some();
+ if !have_intro_node_in_graph { continue }
+ let candidate = if hint.1.blinded_hops.len() == 1 {
+ CandidateRouteHop::OneHopBlinded { hint, hint_idx }
+ } else { CandidateRouteHop::Blinded { hint, hint_idx } };
+ let mut path_contribution_msat = path_value_msat;
+ if let Some(hop_used_msat) = add_entry!(candidate, intro_node_id, maybe_dummy_payee_node_id,
+ 0, path_contribution_msat, 0, 0_u64, 0, 0)
+ {
+ path_contribution_msat = hop_used_msat;
+ } else { continue }
+ if let Some(first_channels) = first_hop_targets.get_mut(&NodeId::from_pubkey(&hint.1.introduction_node_id)) {
+ sort_first_hop_channels(first_channels, &used_liquidities, recommended_value_msat,
+ our_node_pubkey);
+ for details in first_channels {
+ let first_hop_candidate = CandidateRouteHop::FirstHop { details };
+ add_entry!(first_hop_candidate, our_node_id, intro_node_id, 0, path_contribution_msat, 0,
+ 0_u64, 0, 0);
+ }
+ }
+ }
+ for route in payment_params.payee.unblinded_route_hints().iter()
+ .filter(|route| !route.0.is_empty())
+ {
let first_hop_in_route = &(route.0)[0];
let have_hop_src_in_graph =
// Only add the hops in this route to our candidate set if either
hop_used = false;
}
- let used_liquidity_msat = used_channel_liquidities
- .get(&(hop.short_channel_id, source < target)).copied().unwrap_or(0);
+ let used_liquidity_msat = used_liquidities
+ .get(&candidate.id(source < target)).copied()
+ .unwrap_or(0);
let channel_usage = ChannelUsage {
amount_msat: final_value_msat + aggregate_next_hops_fee_msat,
inflight_htlc_msat: used_liquidity_msat,
// Searching for a direct channel between last checked hop and first_hop_targets
if let Some(first_channels) = first_hop_targets.get_mut(&NodeId::from_pubkey(&prev_hop_id)) {
- sort_first_hop_channels(first_channels, &used_channel_liquidities,
+ sort_first_hop_channels(first_channels, &used_liquidities,
recommended_value_msat, our_node_pubkey);
for details in first_channels {
let first_hop_candidate = CandidateRouteHop::FirstHop { details };
// always assumes that the third argument is a node to which we have a
// path.
if let Some(first_channels) = first_hop_targets.get_mut(&NodeId::from_pubkey(&hop.src_node_id)) {
- sort_first_hop_channels(first_channels, &used_channel_liquidities,
+ sort_first_hop_channels(first_channels, &used_liquidities,
recommended_value_msat, our_node_pubkey);
for details in first_channels {
let first_hop_candidate = CandidateRouteHop::FirstHop { details };
let mut features_set = false;
if let Some(first_channels) = first_hop_targets.get(&ordered_hops.last().unwrap().0.node_id) {
for details in first_channels {
- if details.get_outbound_payment_scid().unwrap() == ordered_hops.last().unwrap().0.candidate.short_channel_id() {
- ordered_hops.last_mut().unwrap().1 = details.counterparty.features.to_context();
- features_set = true;
- break;
+ if let Some(scid) = ordered_hops.last().unwrap().0.candidate.short_channel_id() {
+ if details.get_outbound_payment_scid().unwrap() == scid {
+ ordered_hops.last_mut().unwrap().1 = details.counterparty.features.to_context();
+ features_set = true;
+ break;
+ }
}
}
}
.chain(payment_path.hops.iter().map(|(hop, _)| &hop.node_id));
for (prev_hop, (hop, _)) in prev_hop_iter.zip(payment_path.hops.iter()) {
let spent_on_hop_msat = value_contribution_msat + hop.next_hops_fee_msat;
- let used_liquidity_msat = used_channel_liquidities
- .entry((hop.candidate.short_channel_id(), *prev_hop < hop.node_id))
+ let used_liquidity_msat = used_liquidities
+ .entry(hop.candidate.id(*prev_hop < hop.node_id))
.and_modify(|used_liquidity_msat| *used_liquidity_msat += spent_on_hop_msat)
.or_insert(spent_on_hop_msat);
let hop_capacity = hop.candidate.effective_capacity();
// If we weren't capped by hitting a liquidity limit on a channel in the path,
// we'll probably end up picking the same path again on the next iteration.
// Decrease the available liquidity of a hop in the middle of the path.
- let victim_scid = payment_path.hops[(payment_path.hops.len()) / 2].0.candidate.short_channel_id();
+ let victim_candidate = &payment_path.hops[(payment_path.hops.len()) / 2].0.candidate;
let exhausted = u64::max_value();
- log_trace!(logger, "Disabling channel {} for future path building iterations to avoid duplicates.", victim_scid);
- *used_channel_liquidities.entry((victim_scid, false)).or_default() = exhausted;
- *used_channel_liquidities.entry((victim_scid, true)).or_default() = exhausted;
+ log_trace!(logger, "Disabling route candidate {} for future path building iterations to
+ avoid duplicates.", LoggedCandidateHop(victim_candidate));
+ *used_liquidities.entry(victim_candidate.id(false)).or_default() = exhausted;
+ *used_liquidities.entry(victim_candidate.id(true)).or_default() = exhausted;
}
// Track the total amount all our collected paths allow to send so that we know
// compare both SCIDs and NodeIds as individual nodes may use random aliases causing collisions
// across nodes.
selected_route.sort_unstable_by_key(|path| {
- let mut key = [0u64; MAX_PATH_LENGTH_ESTIMATE as usize];
+ let mut key = [CandidateHopId::Clear((42, true)) ; MAX_PATH_LENGTH_ESTIMATE as usize];
debug_assert!(path.hops.len() <= key.len());
- for (scid, key) in path.hops.iter().map(|h| h.0.candidate.short_channel_id()).zip(key.iter_mut()) {
+ for (scid, key) in path.hops.iter() .map(|h| h.0.candidate.id(true)).zip(key.iter_mut()) {
*key = scid;
}
key
});
for idx in 0..(selected_route.len() - 1) {
if idx + 1 >= selected_route.len() { break; }
- if iter_equal(selected_route[idx ].hops.iter().map(|h| (h.0.candidate.short_channel_id(), h.0.node_id)),
- selected_route[idx + 1].hops.iter().map(|h| (h.0.candidate.short_channel_id(), h.0.node_id))) {
+ if iter_equal(selected_route[idx ].hops.iter().map(|h| (h.0.candidate.id(true), h.0.node_id)),
+ selected_route[idx + 1].hops.iter().map(|h| (h.0.candidate.id(true), h.0.node_id))) {
let new_value = selected_route[idx].get_value_msat() + selected_route[idx + 1].get_value_msat();
selected_route[idx].update_value_and_recompute_fees(new_value);
selected_route.remove(idx + 1);
}
}
- let mut selected_paths = Vec::<Vec<Result<RouteHop, LightningError>>>::new();
+ let mut paths = Vec::new();
for payment_path in selected_route {
- let mut path = payment_path.hops.iter().map(|(payment_hop, node_features)| {
- Ok(RouteHop {
- pubkey: PublicKey::from_slice(payment_hop.node_id.as_slice()).map_err(|_| LightningError{err: format!("Public key {:?} is invalid", &payment_hop.node_id), action: ErrorAction::IgnoreAndLog(Level::Trace)})?,
+ let mut hops = Vec::with_capacity(payment_path.hops.len());
+ for (hop, node_features) in payment_path.hops.iter()
+ .filter(|(h, _)| h.candidate.short_channel_id().is_some())
+ {
+ hops.push(RouteHop {
+ pubkey: PublicKey::from_slice(hop.node_id.as_slice()).map_err(|_| LightningError{err: format!("Public key {:?} is invalid", &hop.node_id), action: ErrorAction::IgnoreAndLog(Level::Trace)})?,
node_features: node_features.clone(),
- short_channel_id: payment_hop.candidate.short_channel_id(),
- channel_features: payment_hop.candidate.features(),
- fee_msat: payment_hop.fee_msat,
- cltv_expiry_delta: payment_hop.candidate.cltv_expiry_delta(),
- })
- }).collect::<Vec<_>>();
+ short_channel_id: hop.candidate.short_channel_id().unwrap(),
+ channel_features: hop.candidate.features(),
+ fee_msat: hop.fee_msat,
+ cltv_expiry_delta: hop.candidate.cltv_expiry_delta(),
+ });
+ }
+ let mut final_cltv_delta = final_cltv_expiry_delta;
+ let blinded_tail = payment_path.hops.last().and_then(|(h, _)| {
+ if let Some(blinded_path) = h.candidate.blinded_path() {
+ final_cltv_delta = h.candidate.cltv_expiry_delta();
+ Some(BlindedTail {
+ hops: blinded_path.blinded_hops.clone(),
+ blinding_point: blinded_path.blinding_point,
+ excess_final_cltv_expiry_delta: 0,
+ final_value_msat: h.fee_msat,
+ })
+ } else { None }
+ });
// Propagate the cltv_expiry_delta one hop backwards since the delta from the current hop is
// applicable for the previous hop.
- path.iter_mut().rev().fold(final_cltv_expiry_delta, |prev_cltv_expiry_delta, hop| {
- core::mem::replace(&mut hop.as_mut().unwrap().cltv_expiry_delta, prev_cltv_expiry_delta)
+ hops.iter_mut().rev().fold(final_cltv_delta, |prev_cltv_expiry_delta, hop| {
+ core::mem::replace(&mut hop.cltv_expiry_delta, prev_cltv_expiry_delta)
});
- selected_paths.push(path);
+
+ paths.push(Path { hops, blinded_tail });
}
// Make sure we would never create a route with more paths than we allow.
- debug_assert!(selected_paths.len() <= payment_params.max_path_count.into());
+ debug_assert!(paths.len() <= payment_params.max_path_count.into());
if let Some(node_features) = payment_params.payee.node_features() {
- for path in selected_paths.iter_mut() {
- if let Ok(route_hop) = path.last_mut().unwrap() {
- route_hop.node_features = node_features.clone();
- }
+ for path in paths.iter_mut() {
+ path.hops.last_mut().unwrap().node_features = node_features.clone();
}
}
- let mut paths: Vec<Path> = Vec::new();
- for results_vec in selected_paths {
- let mut hops = Vec::with_capacity(results_vec.len());
- for res in results_vec { hops.push(res?); }
- paths.push(Path { hops, blinded_tail: None });
- }
- let route = Route {
- paths,
- payment_params: Some(payment_params.clone()),
- };
+ let route = Route { paths, payment_params: Some(payment_params.clone()) };
log_info!(logger, "Got route: {}", log_route!(route));
Ok(route)
}
use crate::routing::test_utils::{add_channel, add_or_update_node, build_graph, build_line_graph, id_to_feature_flags, get_nodes, update_channel};
use crate::chain::transaction::OutPoint;
use crate::sign::EntropySource;
- use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures};
+ use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures, ChannelFeatures, InitFeatures, NodeFeatures};
use crate::ln::msgs::{ErrorAction, LightningError, UnsignedChannelUpdate, MAX_VALUE_MSAT};
use crate::ln::channelmanager;
+ use crate::offers::invoice::BlindedPayInfo;
use crate::util::config::UserConfig;
use crate::util::test_utils as ln_test_utils;
use crate::util::chacha20::ChaCha20;
inbound_htlc_minimum_msat: None,
inbound_htlc_maximum_msat: None,
config: None,
- feerate_sat_per_1000_weight: None
+ feerate_sat_per_1000_weight: None,
+ channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown),
}
}
#[test]
fn simple_mpp_route_test() {
+ let (secp_ctx, _, _, _, _) = build_graph();
+ let (_, _, _, nodes) = get_nodes(&secp_ctx);
+ let config = UserConfig::default();
+ let clear_payment_params = PaymentParameters::from_node_id(nodes[2], 42)
+ .with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
+ do_simple_mpp_route_test(clear_payment_params);
+
+ // MPP to a 1-hop blinded path for nodes[2]
+ let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+ let blinded_path = BlindedPath {
+ introduction_node_id: nodes[2],
+ blinding_point: ln_test_utils::pubkey(42),
+ blinded_hops: vec![BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }],
+ };
+ let blinded_payinfo = BlindedPayInfo { // These fields are ignored for 1-hop blinded paths
+ fee_base_msat: 0,
+ fee_proportional_millionths: 0,
+ htlc_minimum_msat: 0,
+ htlc_maximum_msat: 0,
+ cltv_expiry_delta: 0,
+ features: BlindedHopFeatures::empty(),
+ };
+ let one_hop_blinded_payment_params = PaymentParameters::blinded(vec![(blinded_payinfo.clone(), blinded_path.clone())])
+ .with_bolt12_features(bolt12_features.clone()).unwrap();
+ do_simple_mpp_route_test(one_hop_blinded_payment_params.clone());
+
+ // MPP to 3 2-hop blinded paths
+ let mut blinded_path_node_0 = blinded_path.clone();
+ blinded_path_node_0.introduction_node_id = nodes[0];
+ blinded_path_node_0.blinded_hops.push(blinded_path.blinded_hops[0].clone());
+ let mut node_0_payinfo = blinded_payinfo.clone();
+ node_0_payinfo.htlc_maximum_msat = 50_000;
+
+ let mut blinded_path_node_7 = blinded_path_node_0.clone();
+ blinded_path_node_7.introduction_node_id = nodes[7];
+ let mut node_7_payinfo = blinded_payinfo.clone();
+ node_7_payinfo.htlc_maximum_msat = 60_000;
+
+ let mut blinded_path_node_1 = blinded_path_node_0.clone();
+ blinded_path_node_1.introduction_node_id = nodes[1];
+ let mut node_1_payinfo = blinded_payinfo.clone();
+ node_1_payinfo.htlc_maximum_msat = 180_000;
+
+ let two_hop_blinded_payment_params = PaymentParameters::blinded(
+ vec![
+ (node_0_payinfo, blinded_path_node_0),
+ (node_7_payinfo, blinded_path_node_7),
+ (node_1_payinfo, blinded_path_node_1)
+ ])
+ .with_bolt12_features(bolt12_features).unwrap();
+ do_simple_mpp_route_test(two_hop_blinded_payment_params);
+ }
+
+
+ fn do_simple_mpp_route_test(payment_params: PaymentParameters) {
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
- let config = UserConfig::default();
- let payment_params = PaymentParameters::from_node_id(nodes[2], 42)
- .with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
// We need a route consisting of 3 paths:
// From our node to node2 via node0, node7, node1 (three paths one hop each).
assert_eq!(route.paths.len(), 3);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
- assert_eq!(path.hops.len(), 2);
- assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+ if let Some(bt) = &path.blinded_tail {
+ assert_eq!(path.hops.len() + if bt.hops.len() == 1 { 0 } else { 1 }, 2);
+ } else {
+ assert_eq!(path.hops.len(), 2);
+ assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+ }
total_amount_paid_msat += path.final_value_msat();
}
assert_eq!(total_amount_paid_msat, 250_000);
assert_eq!(route.paths.len(), 3);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
- assert_eq!(path.hops.len(), 2);
- assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+ if payment_params.payee.blinded_route_hints().len() != 0 {
+ assert!(path.blinded_tail.is_some()) } else { assert!(path.blinded_tail.is_none()) }
+ if let Some(bt) = &path.blinded_tail {
+ assert_eq!(path.hops.len() + if bt.hops.len() == 1 { 0 } else { 1 }, 2);
+ if bt.hops.len() > 1 {
+ assert_eq!(path.hops.last().unwrap().pubkey,
+ payment_params.payee.blinded_route_hints().iter()
+ .find(|(p, _)| p.htlc_maximum_msat == path.final_value_msat())
+ .map(|(_, p)| p.introduction_node_id).unwrap());
+ } else {
+ assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+ }
+ } else {
+ assert_eq!(path.hops.len(), 2);
+ assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+ }
total_amount_paid_msat += path.final_value_msat();
}
assert_eq!(total_amount_paid_msat, 290_000);
let params = ProbabilisticScoringFeeParameters::default();
let mut scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &graph, &logger);
- let features = super::InvoiceFeatures::empty();
+ let features = super::Bolt11InvoiceFeatures::empty();
super::bench_utils::generate_test_routes(&graph, &mut scorer, ¶ms, features, random_init_seed(), 0, 2);
}
assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat);
assert!(route.paths[1].hops.last().unwrap().fee_msat <= max_htlc_msat);
assert_eq!(route.get_total_amount(), amt_msat);
+
+ // Make sure this works for blinded route hints.
+ let blinded_path = BlindedPath {
+ introduction_node_id: intermed_node_id,
+ blinding_point: ln_test_utils::pubkey(42),
+ blinded_hops: vec![
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42), encrypted_payload: vec![] },
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(43), encrypted_payload: vec![] },
+ ],
+ };
+ let blinded_payinfo = BlindedPayInfo {
+ fee_base_msat: 100,
+ fee_proportional_millionths: 0,
+ htlc_minimum_msat: 1,
+ htlc_maximum_msat: max_htlc_msat,
+ cltv_expiry_delta: 10,
+ features: BlindedHopFeatures::empty(),
+ };
+ let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+ let payment_params = PaymentParameters::blinded(vec![
+ (blinded_payinfo.clone(), blinded_path.clone()),
+ (blinded_payinfo.clone(), blinded_path.clone())])
+ .with_bolt12_features(bolt12_features).unwrap();
+ let route = get_route(&our_node_id, &payment_params, &network_graph.read_only(),
+ Some(&first_hops.iter().collect::<Vec<_>>()), amt_msat, Arc::clone(&logger), &scorer, &(),
+ &random_seed_bytes).unwrap();
+ assert_eq!(route.paths.len(), 2);
+ assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat);
+ assert!(route.paths[1].hops.last().unwrap().fee_msat <= max_htlc_msat);
+ assert_eq!(route.get_total_amount(), amt_msat);
}
#[test]
assert_eq!(route.paths[0].blinded_tail.as_ref().unwrap().excess_final_cltv_expiry_delta, 40);
assert_eq!(route.paths[0].hops.last().unwrap().cltv_expiry_delta, 40);
}
+
+ #[test]
+ fn simple_blinded_route_hints() {
+ do_simple_blinded_route_hints(1);
+ do_simple_blinded_route_hints(2);
+ do_simple_blinded_route_hints(3);
+ }
+
+ fn do_simple_blinded_route_hints(num_blinded_hops: usize) {
+ // Check that we can generate a route to a blinded path with the expected hops.
+ let (secp_ctx, network, _, _, logger) = build_graph();
+ let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
+ let network_graph = network.read_only();
+
+ let scorer = ln_test_utils::TestScorer::new();
+ let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
+
+ let mut blinded_path = BlindedPath {
+ introduction_node_id: nodes[2],
+ blinding_point: ln_test_utils::pubkey(42),
+ blinded_hops: Vec::with_capacity(num_blinded_hops),
+ };
+ for i in 0..num_blinded_hops {
+ blinded_path.blinded_hops.push(
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 + i as u8), encrypted_payload: Vec::new() },
+ );
+ }
+ let blinded_payinfo = BlindedPayInfo {
+ fee_base_msat: 100,
+ fee_proportional_millionths: 500,
+ htlc_minimum_msat: 1000,
+ htlc_maximum_msat: 100_000_000,
+ cltv_expiry_delta: 15,
+ features: BlindedHopFeatures::empty(),
+ };
+
+ let final_amt_msat = 1001;
+ let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo.clone(), blinded_path.clone())]);
+ let route = get_route(&our_id, &payment_params, &network_graph, None, final_amt_msat , Arc::clone(&logger),
+ &scorer, &(), &random_seed_bytes).unwrap();
+ assert_eq!(route.paths.len(), 1);
+ assert_eq!(route.paths[0].hops.len(), 2);
+
+ let tail = route.paths[0].blinded_tail.as_ref().unwrap();
+ assert_eq!(tail.hops, blinded_path.blinded_hops);
+ assert_eq!(tail.excess_final_cltv_expiry_delta, 0);
+ assert_eq!(tail.final_value_msat, 1001);
+
+ let final_hop = route.paths[0].hops.last().unwrap();
+ assert_eq!(final_hop.pubkey, blinded_path.introduction_node_id);
+ if tail.hops.len() > 1 {
+ assert_eq!(final_hop.fee_msat,
+ blinded_payinfo.fee_base_msat as u64 + blinded_payinfo.fee_proportional_millionths as u64 * tail.final_value_msat / 1000000);
+ assert_eq!(final_hop.cltv_expiry_delta, blinded_payinfo.cltv_expiry_delta as u32);
+ } else {
+ assert_eq!(final_hop.fee_msat, 0);
+ assert_eq!(final_hop.cltv_expiry_delta, 0);
+ }
+ }
+
+ #[test]
+ fn blinded_path_routing_errors() {
+ // Check that we can generate a route to a blinded path with the expected hops.
+ let (secp_ctx, network, _, _, logger) = build_graph();
+ let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
+ let network_graph = network.read_only();
+
+ let scorer = ln_test_utils::TestScorer::new();
+ let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
+
+ let mut invalid_blinded_path = BlindedPath {
+ introduction_node_id: nodes[2],
+ blinding_point: ln_test_utils::pubkey(42),
+ blinded_hops: vec![
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(43), encrypted_payload: vec![0; 43] },
+ ],
+ };
+ let blinded_payinfo = BlindedPayInfo {
+ fee_base_msat: 100,
+ fee_proportional_millionths: 500,
+ htlc_minimum_msat: 1000,
+ htlc_maximum_msat: 100_000_000,
+ cltv_expiry_delta: 15,
+ features: BlindedHopFeatures::empty(),
+ };
+
+ let mut invalid_blinded_path_2 = invalid_blinded_path.clone();
+ invalid_blinded_path_2.introduction_node_id = ln_test_utils::pubkey(45);
+ let payment_params = PaymentParameters::blinded(vec![
+ (blinded_payinfo.clone(), invalid_blinded_path.clone()),
+ (blinded_payinfo.clone(), invalid_blinded_path_2)]);
+ match get_route(&our_id, &payment_params, &network_graph, None, 1001, Arc::clone(&logger),
+ &scorer, &(), &random_seed_bytes)
+ {
+ Err(LightningError { err, .. }) => {
+ assert_eq!(err, "1-hop blinded paths must all have matching introduction node ids");
+ },
+ _ => panic!("Expected error")
+ }
+
+ invalid_blinded_path.introduction_node_id = our_id;
+ let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo.clone(), invalid_blinded_path.clone())]);
+ match get_route(&our_id, &payment_params, &network_graph, None, 1001, Arc::clone(&logger),
+ &scorer, &(), &random_seed_bytes)
+ {
+ Err(LightningError { err, .. }) => {
+ assert_eq!(err, "Cannot generate a route to blinded paths if we are the introduction node to all of them");
+ },
+ _ => panic!("Expected error")
+ }
+
+ invalid_blinded_path.introduction_node_id = ln_test_utils::pubkey(46);
+ invalid_blinded_path.blinded_hops.clear();
+ let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo, invalid_blinded_path)]);
+ match get_route(&our_id, &payment_params, &network_graph, None, 1001, Arc::clone(&logger),
+ &scorer, &(), &random_seed_bytes)
+ {
+ Err(LightningError { err, .. }) => {
+ assert_eq!(err, "0-hop blinded path provided");
+ },
+ _ => panic!("Expected error")
+ }
+ }
+
+ #[test]
+ fn matching_intro_node_paths_provided() {
+ // Check that if multiple blinded paths with the same intro node are provided in payment
+ // parameters, we'll return the correct paths in the resulting MPP route.
+ let (secp_ctx, network, _, _, logger) = build_graph();
+ let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
+ let network_graph = network.read_only();
+
+ let scorer = ln_test_utils::TestScorer::new();
+ let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
+ let config = UserConfig::default();
+
+ let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+ let blinded_path_1 = BlindedPath {
+ introduction_node_id: nodes[2],
+ blinding_point: ln_test_utils::pubkey(42),
+ blinded_hops: vec![
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
+ ],
+ };
+ let blinded_payinfo_1 = BlindedPayInfo {
+ fee_base_msat: 0,
+ fee_proportional_millionths: 0,
+ htlc_minimum_msat: 0,
+ htlc_maximum_msat: 30_000,
+ cltv_expiry_delta: 0,
+ features: BlindedHopFeatures::empty(),
+ };
+
+ let mut blinded_path_2 = blinded_path_1.clone();
+ blinded_path_2.blinding_point = ln_test_utils::pubkey(43);
+ let mut blinded_payinfo_2 = blinded_payinfo_1.clone();
+ blinded_payinfo_2.htlc_maximum_msat = 70_000;
+
+ let blinded_hints = vec![
+ (blinded_payinfo_1.clone(), blinded_path_1.clone()),
+ (blinded_payinfo_2.clone(), blinded_path_2.clone()),
+ ];
+ let payment_params = PaymentParameters::blinded(blinded_hints.clone())
+ .with_bolt12_features(bolt12_features.clone()).unwrap();
+
+ let route = get_route(&our_id, &payment_params, &network_graph, None,
+ 100_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ assert_eq!(route.paths.len(), 2);
+ let mut total_amount_paid_msat = 0;
+ for path in route.paths.into_iter() {
+ assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+ if let Some(bt) = &path.blinded_tail {
+ assert_eq!(bt.blinding_point,
+ blinded_hints.iter().find(|(p, _)| p.htlc_maximum_msat == path.final_value_msat())
+ .map(|(_, bp)| bp.blinding_point).unwrap());
+ } else { panic!(); }
+ total_amount_paid_msat += path.final_value_msat();
+ }
+ assert_eq!(total_amount_paid_msat, 100_000);
+ }
}
#[cfg(all(any(test, ldk_bench), not(feature = "no-std")))]
use crate::chain::transaction::OutPoint;
use crate::sign::{EntropySource, KeysManager};
use crate::ln::channelmanager::{self, ChannelCounterparty, ChannelDetails};
- use crate::ln::features::InvoiceFeatures;
+ use crate::ln::features::Bolt11InvoiceFeatures;
use crate::routing::gossip::NetworkGraph;
use crate::util::config::UserConfig;
use crate::util::ser::ReadableArgs;
inbound_htlc_maximum_msat: None,
config: None,
feerate_sat_per_1000_weight: None,
+ channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown),
}
}
pub(crate) fn generate_test_routes<S: Score>(graph: &NetworkGraph<&TestLogger>, scorer: &mut S,
- score_params: &S::ScoreParams, features: InvoiceFeatures, mut seed: u64,
+ score_params: &S::ScoreParams, features: Bolt11InvoiceFeatures, mut seed: u64,
starting_amount: u64, route_count: usize,
) -> Vec<(ChannelDetails, PaymentParameters, u64)> {
let payer = payer_pubkey();
use super::*;
use crate::sign::{EntropySource, KeysManager};
use crate::ln::channelmanager;
- use crate::ln::features::InvoiceFeatures;
+ use crate::ln::features::Bolt11InvoiceFeatures;
use crate::routing::gossip::NetworkGraph;
use crate::routing::scoring::{FixedPenaltyScorer, ProbabilisticScorer, ProbabilisticScoringFeeParameters, ProbabilisticScoringDecayParameters};
use crate::util::config::UserConfig;
let logger = TestLogger::new();
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
let scorer = FixedPenaltyScorer::with_penalty(0);
- generate_routes(bench, &network_graph, scorer, &(), InvoiceFeatures::empty(), 0,
+ generate_routes(bench, &network_graph, scorer, &(), Bolt11InvoiceFeatures::empty(), 0,
"generate_routes_with_zero_penalty_scorer");
}
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
let params = ProbabilisticScoringFeeParameters::default();
let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger);
- generate_routes(bench, &network_graph, scorer, ¶ms, InvoiceFeatures::empty(), 0,
+ generate_routes(bench, &network_graph, scorer, ¶ms, Bolt11InvoiceFeatures::empty(), 0,
"generate_routes_with_probabilistic_scorer");
}
fn generate_routes<S: Score>(
bench: &mut Criterion, graph: &NetworkGraph<&TestLogger>, mut scorer: S,
- score_params: &S::ScoreParams, features: InvoiceFeatures, starting_amount: u64,
+ score_params: &S::ScoreParams, features: Bolt11InvoiceFeatures, starting_amount: u64,
bench_name: &'static str,
) {
let payer = bench_utils::payer_pubkey();
///
/// [`find_route`]: crate::routing::router::find_route
pub trait LockableScore<'a> {
+ /// The [`Score`] type.
+ type Score: 'a + Score;
+
/// The locked [`Score`] type.
- type Locked: 'a + Score;
+ type Locked: DerefMut<Target = Self::Score> + Sized;
/// Returns the locked scorer.
fn lock(&'a self) -> Self::Locked;
#[cfg(not(c_bindings))]
impl<'a, T> WriteableScore<'a> for T where T: LockableScore<'a> + Writeable {}
-/// This is not exported to bindings users
+#[cfg(not(c_bindings))]
impl<'a, T: 'a + Score> LockableScore<'a> for Mutex<T> {
+ type Score = T;
type Locked = MutexGuard<'a, T>;
- fn lock(&'a self) -> MutexGuard<'a, T> {
+ fn lock(&'a self) -> Self::Locked {
Mutex::lock(self).unwrap()
}
}
+#[cfg(not(c_bindings))]
impl<'a, T: 'a + Score> LockableScore<'a> for RefCell<T> {
+ type Score = T;
type Locked = RefMut<'a, T>;
- fn lock(&'a self) -> RefMut<'a, T> {
+ fn lock(&'a self) -> Self::Locked {
self.borrow_mut()
}
}
#[cfg(c_bindings)]
/// A concrete implementation of [`LockableScore`] which supports multi-threading.
-pub struct MultiThreadedLockableScore<S: Score> {
- score: Mutex<S>,
-}
-#[cfg(c_bindings)]
-/// A locked `MultiThreadedLockableScore`.
-pub struct MultiThreadedScoreLock<'a, S: Score>(MutexGuard<'a, S>);
-#[cfg(c_bindings)]
-impl<'a, T: Score + 'a> Score for MultiThreadedScoreLock<'a, T> {
- type ScoreParams = <T as Score>::ScoreParams;
- fn channel_penalty_msat(&self, scid: u64, source: &NodeId, target: &NodeId, usage: ChannelUsage, score_params: &Self::ScoreParams) -> u64 {
- self.0.channel_penalty_msat(scid, source, target, usage, score_params)
- }
- fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64) {
- self.0.payment_path_failed(path, short_channel_id)
- }
- fn payment_path_successful(&mut self, path: &Path) {
- self.0.payment_path_successful(path)
- }
- fn probe_failed(&mut self, path: &Path, short_channel_id: u64) {
- self.0.probe_failed(path, short_channel_id)
- }
- fn probe_successful(&mut self, path: &Path) {
- self.0.probe_successful(path)
- }
-}
-#[cfg(c_bindings)]
-impl<'a, T: Score + 'a> Writeable for MultiThreadedScoreLock<'a, T> {
- fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
- self.0.write(writer)
- }
+pub struct MultiThreadedLockableScore<T: Score> {
+ score: Mutex<T>,
}
#[cfg(c_bindings)]
-impl<'a, T: Score + 'a> LockableScore<'a> for MultiThreadedLockableScore<T> {
+impl<'a, T: 'a + Score> LockableScore<'a> for MultiThreadedLockableScore<T> {
+ type Score = T;
type Locked = MultiThreadedScoreLock<'a, T>;
- fn lock(&'a self) -> MultiThreadedScoreLock<'a, T> {
+ fn lock(&'a self) -> Self::Locked {
MultiThreadedScoreLock(Mutex::lock(&self.score).unwrap())
}
}
}
#[cfg(c_bindings)]
-impl<'a, T: Score + 'a> WriteableScore<'a> for MultiThreadedLockableScore<T> {}
+impl<'a, T: 'a + Score> WriteableScore<'a> for MultiThreadedLockableScore<T> {}
#[cfg(c_bindings)]
impl<T: Score> MultiThreadedLockableScore<T> {
}
#[cfg(c_bindings)]
-/// This is not exported to bindings users
-impl<'a, T: Writeable> Writeable for RefMut<'a, T> {
+/// A locked `MultiThreadedLockableScore`.
+pub struct MultiThreadedScoreLock<'a, T: Score>(MutexGuard<'a, T>);
+
+#[cfg(c_bindings)]
+impl<'a, T: 'a + Score> Writeable for MultiThreadedScoreLock<'a, T> {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
- T::write(&**self, writer)
+ self.0.write(writer)
}
}
#[cfg(c_bindings)]
-/// This is not exported to bindings users
-impl<'a, S: Writeable> Writeable for MutexGuard<'a, S> {
- fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
- S::write(&**self, writer)
- }
+impl<'a, T: 'a + Score> DerefMut for MultiThreadedScoreLock<'a, T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.0.deref_mut()
+ }
+}
+
+#[cfg(c_bindings)]
+impl<'a, T: 'a + Score> Deref for MultiThreadedScoreLock<'a, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ self.0.deref()
+ }
}
+
+
/// Proposed use of a channel passed as a parameter to [`Score::channel_penalty_msat`].
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ChannelUsage {
}
#[cfg(not(feature = "no-std"))]
-type ConfiguredTime = std::time::Instant;
+type ConfiguredTime = crate::util::time::MonotonicTime;
#[cfg(feature = "no-std")]
use crate::util::time::Eternity;
#[cfg(feature = "no-std")]
use crate::util::crypto::{hkdf_extract_expand_twice, sign, sign_with_aux_rand};
use crate::util::ser::{Writeable, Writer, Readable, ReadableArgs};
use crate::chain::transaction::OutPoint;
-#[cfg(anchors)]
use crate::events::bump_transaction::HTLCDescriptor;
use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI;
use crate::ln::{chan_utils, PaymentPreimage};
use core::ops::Deref;
use core::sync::atomic::{AtomicUsize, Ordering};
use crate::io::{self, Error};
+use crate::ln::features::ChannelTypeFeatures;
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
use crate::util::atomic_counter::AtomicCounter;
use crate::util::chacha20::ChaCha20;
fn sign_justice_revoked_htlc(&self, justice_tx: &Transaction, input: usize, amount: u64,
per_commitment_key: &SecretKey, htlc: &HTLCOutputInCommitment,
secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()>;
- #[cfg(anchors)]
/// Computes the signature for a commitment transaction's HTLC output used as an input within
/// `htlc_tx`, which spends the commitment transaction at index `input`. The signature returned
/// must be be computed using [`EcdsaSighashType::All`]. Note that this should only be used to
pub fn get_channel_parameters(&self) -> &ChannelTransactionParameters {
self.channel_parameters.as_ref().unwrap()
}
- /// Returns whether anchors should be used.
+ /// Returns the channel type features of the channel parameters. Should be helpful for
+ /// determining a channel's category, i. e. legacy/anchors/taproot/etc.
///
/// Will panic if [`ChannelSigner::provide_channel_parameters`] has not been called before.
- pub fn opt_anchors(&self) -> bool {
- self.get_channel_parameters().opt_anchors.is_some()
+ pub fn channel_type_features(&self) -> &ChannelTypeFeatures {
+ &self.get_channel_parameters().channel_type_features
}
/// Sign the single input of `spend_tx` at index `input_idx`, which spends the output described
/// by `descriptor`, returning the witness stack for the input.
let mut htlc_sigs = Vec::with_capacity(commitment_tx.htlcs().len());
for htlc in commitment_tx.htlcs() {
let channel_parameters = self.get_channel_parameters();
- let htlc_tx = chan_utils::build_htlc_transaction(&commitment_txid, commitment_tx.feerate_per_kw(), self.holder_selected_contest_delay(), htlc, self.opt_anchors(), channel_parameters.opt_non_zero_fee_anchors.is_some(), &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
- let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, self.opt_anchors(), &keys);
- let htlc_sighashtype = if self.opt_anchors() { EcdsaSighashType::SinglePlusAnyoneCanPay } else { EcdsaSighashType::All };
+ let htlc_tx = chan_utils::build_htlc_transaction(&commitment_txid, commitment_tx.feerate_per_kw(), self.holder_selected_contest_delay(), htlc, &channel_parameters.channel_type_features, &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
+ let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, self.channel_type_features(), &keys);
+ let htlc_sighashtype = if self.channel_type_features().supports_anchors_zero_fee_htlc_tx() { EcdsaSighashType::SinglePlusAnyoneCanPay } else { EcdsaSighashType::All };
let htlc_sighash = hash_to_message!(&sighash::SighashCache::new(&htlc_tx).segwit_signature_hash(0, &htlc_redeemscript, htlc.amount_msat / 1000, htlc_sighashtype).unwrap()[..]);
let holder_htlc_key = chan_utils::derive_private_key(&secp_ctx, &keys.per_commitment_point, &self.htlc_base_key);
htlc_sigs.push(sign(secp_ctx, &htlc_sighash, &holder_htlc_key));
let witness_script = {
let counterparty_htlcpubkey = chan_utils::derive_public_key(&secp_ctx, &per_commitment_point, &self.counterparty_pubkeys().htlc_basepoint);
let holder_htlcpubkey = chan_utils::derive_public_key(&secp_ctx, &per_commitment_point, &self.pubkeys().htlc_basepoint);
- chan_utils::get_htlc_redeemscript_with_explicit_keys(&htlc, self.opt_anchors(), &counterparty_htlcpubkey, &holder_htlcpubkey, &revocation_pubkey)
+ chan_utils::get_htlc_redeemscript_with_explicit_keys(&htlc, self.channel_type_features(), &counterparty_htlcpubkey, &holder_htlcpubkey, &revocation_pubkey)
};
let mut sighash_parts = sighash::SighashCache::new(justice_tx);
let sighash = hash_to_message!(&sighash_parts.segwit_signature_hash(input, &witness_script, amount, EcdsaSighashType::All).unwrap()[..]);
return Ok(sign_with_aux_rand(secp_ctx, &sighash, &revocation_key, &self))
}
- #[cfg(anchors)]
fn sign_holder_htlc_transaction(
&self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor,
secp_ctx: &Secp256k1<secp256k1::All>
) -> Result<Signature, ()> {
- let per_commitment_point = self.get_per_commitment_point(
- htlc_descriptor.per_commitment_number, &secp_ctx
- );
- let witness_script = htlc_descriptor.witness_script(&per_commitment_point, secp_ctx);
+ let witness_script = htlc_descriptor.witness_script(secp_ctx);
let sighash = &sighash::SighashCache::new(&*htlc_tx).segwit_signature_hash(
input, &witness_script, htlc_descriptor.htlc.amount_msat / 1000, EcdsaSighashType::All
).map_err(|_| ())?;
let our_htlc_private_key = chan_utils::derive_private_key(
- &secp_ctx, &per_commitment_point, &self.htlc_base_key
+ &secp_ctx, &htlc_descriptor.per_commitment_point, &self.htlc_base_key
);
Ok(sign_with_aux_rand(&secp_ctx, &hash_to_message!(sighash), &our_htlc_private_key, &self))
}
let revocation_pubkey = chan_utils::derive_public_revocation_key(&secp_ctx, &per_commitment_point, &self.pubkeys().revocation_basepoint);
let counterparty_htlcpubkey = chan_utils::derive_public_key(&secp_ctx, &per_commitment_point, &self.counterparty_pubkeys().htlc_basepoint);
let htlcpubkey = chan_utils::derive_public_key(&secp_ctx, &per_commitment_point, &self.pubkeys().htlc_basepoint);
- let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&htlc, self.opt_anchors(), &counterparty_htlcpubkey, &htlcpubkey, &revocation_pubkey);
+ let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&htlc, self.channel_type_features(), &counterparty_htlcpubkey, &htlcpubkey, &revocation_pubkey);
let mut sighash_parts = sighash::SighashCache::new(htlc_tx);
let sighash = hash_to_message!(&sighash_parts.segwit_signature_hash(input, &witness_script, amount, EcdsaSighashType::All).unwrap()[..]);
Ok(sign_with_aux_rand(secp_ctx, &sighash, &htlc_key, &self))
///
/// May panic if the [`SpendableOutputDescriptor`]s were not generated by channels which used
/// this [`KeysManager`] or one of the [`InMemorySigner`] created by this [`KeysManager`].
- pub fn sign_spendable_outputs_psbt<C: Signing>(&self, descriptors: &[&SpendableOutputDescriptor], psbt: &mut PartiallySignedTransaction, secp_ctx: &Secp256k1<C>) -> Result<(), ()> {
+ pub fn sign_spendable_outputs_psbt<C: Signing>(&self, descriptors: &[&SpendableOutputDescriptor], mut psbt: PartiallySignedTransaction, secp_ctx: &Secp256k1<C>) -> Result<PartiallySignedTransaction, ()> {
let mut keys_cache: Option<(InMemorySigner, [u8; 32])> = None;
for outp in descriptors {
match outp {
}
}
- Ok(())
+ Ok(psbt)
}
/// Creates a [`Transaction`] which spends the given descriptors to the given outputs, plus an
/// this [`KeysManager`] or one of the [`InMemorySigner`] created by this [`KeysManager`].
pub fn spend_spendable_outputs<C: Signing>(&self, descriptors: &[&SpendableOutputDescriptor], outputs: Vec<TxOut>, change_destination_script: Script, feerate_sat_per_1000_weight: u32, locktime: Option<PackedLockTime>, secp_ctx: &Secp256k1<C>) -> Result<Transaction, ()> {
let (mut psbt, expected_max_weight) = SpendableOutputDescriptor::create_spendable_outputs_psbt(descriptors, outputs, change_destination_script, feerate_sat_per_1000_weight, locktime)?;
- self.sign_spendable_outputs_psbt(descriptors, &mut psbt, secp_ctx)?;
+ psbt = self.sign_spendable_outputs_psbt(descriptors, psbt, secp_ctx)?;
let spend_tx = psbt.extract_tx();
field3: Vec<u8>,
}
impl_writeable_tlv_based!(TestWriteable, {
- (1, field1, vec_type),
- (2, field2, vec_type),
- (3, field3, vec_type),
+ (1, field1, required_vec),
+ (2, field2, required_vec),
+ (3, field3, required_vec),
});
#[test]
/// Maximum value: 1,000,000, any values larger than 1 Million will be treated as 1 Million (or 100%)
/// instead, although channel negotiations will fail in that case.
pub their_channel_reserve_proportional_millionths: u32,
- #[cfg(anchors)]
- /// If set, we attempt to negotiate the `anchors_zero_fee_htlc_tx`option for outbound channels.
+ /// If set, we attempt to negotiate the `anchors_zero_fee_htlc_tx`option for all future
+ /// channels. This feature requires having a reserve of onchain funds readily available to bump
+ /// transactions in the event of a channel force close to avoid the possibility of losing funds.
+ ///
+ /// Note that if you wish accept inbound channels with anchor outputs, you must enable
+ /// [`UserConfig::manually_accept_inbound_channels`] and manually accept them with
+ /// [`ChannelManager::accept_inbound_channel`]. This is done to give you the chance to check
+ /// whether your reserve of onchain funds is enough to cover the fees for all existing and new
+ /// channels featuring anchor outputs in the event of a force close.
///
/// If this option is set, channels may be created that will not be readable by LDK versions
- /// prior to 0.0.114, causing [`ChannelManager`]'s read method to return a
+ /// prior to 0.0.116, causing [`ChannelManager`]'s read method to return a
/// [`DecodeError::InvalidValue`].
///
/// Note that setting this to true does *not* prevent us from opening channels with
/// Default value: false. This value is likely to change to true in the future.
///
/// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
+ /// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel
/// [`DecodeError::InvalidValue`]: crate::ln::msgs::DecodeError::InvalidValue
/// [`SIGHASH_SINGLE + update_fee Considered Harmful`]: https://lists.linuxfoundation.org/pipermail/lightning-dev/2020-September/002796.html
pub negotiate_anchors_zero_fee_htlc_tx: bool,
announced_channel: false,
commit_upfront_shutdown_pubkey: true,
their_channel_reserve_proportional_millionths: 10_000,
- #[cfg(anchors)]
negotiate_anchors_zero_fee_htlc_tx: false,
our_max_accepted_htlcs: 50,
}
}
}
+/// Options for how to set the max dust HTLC exposure allowed on a channel. See
+/// [`ChannelConfig::max_dust_htlc_exposure`] for details.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum MaxDustHTLCExposure {
+ /// This sets a fixed limit on the total dust exposure in millisatoshis. Setting this too low
+ /// may prevent the sending or receipt of low-value HTLCs on high-traffic nodes, however this
+ /// limit is very important to prevent stealing of large amounts of dust HTLCs by miners
+ /// through [fee griefing
+ /// attacks](https://lists.linuxfoundation.org/pipermail/lightning-dev/2020-May/002714.html).
+ ///
+ /// Note that if the feerate increases significantly, without a manual increase
+ /// to this maximum the channel may be unable to send/receive HTLCs between the maximum dust
+ /// exposure and the new minimum value for HTLCs to be economically viable to claim.
+ FixedLimitMsat(u64),
+ /// This sets a multiplier on the estimated high priority feerate (sats/KW, as obtained from
+ /// [`FeeEstimator`]) to determine the maximum allowed dust exposure. If this variant is used
+ /// then the maximum dust exposure in millisatoshis is calculated as:
+ /// `high_priority_feerate_per_kw * value`. For example, with our default value
+ /// `FeeRateMultiplier(5000)`:
+ ///
+ /// - For the minimum fee rate of 1 sat/vByte (250 sat/KW, although the minimum
+ /// defaults to 253 sats/KW for rounding, see [`FeeEstimator`]), the max dust exposure would
+ /// be 253 * 5000 = 1,265,000 msats.
+ /// - For a fee rate of 30 sat/vByte (7500 sat/KW), the max dust exposure would be
+ /// 7500 * 5000 = 37,500,000 msats.
+ ///
+ /// This allows the maximum dust exposure to automatically scale with fee rate changes.
+ ///
+ /// Note, if you're using a third-party fee estimator, this may leave you more exposed to a
+ /// fee griefing attack, where your fee estimator may purposely overestimate the fee rate,
+ /// causing you to accept more dust HTLCs than you would otherwise.
+ ///
+ /// This variant is primarily meant to serve pre-anchor channels, as HTLC fees being included
+ /// on HTLC outputs means your channel may be subject to more dust exposure in the event of
+ /// increases in fee rate.
+ ///
+ /// # Backwards Compatibility
+ /// This variant only became available in LDK 0.0.116, so if you downgrade to a prior version
+ /// by default this will be set to a [`Self::FixedLimitMsat`] of 5,000,000 msat.
+ ///
+ /// [`FeeEstimator`]: crate::chain::chaininterface::FeeEstimator
+ FeeRateMultiplier(u64),
+}
+
+impl_writeable_tlv_based_enum!(MaxDustHTLCExposure, ;
+ (1, FixedLimitMsat),
+ (3, FeeRateMultiplier),
+);
+
/// Options which apply on a per-channel basis and may change at runtime or based on negotiation
/// with our counterparty.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
/// channel negotiated throughout the channel open process, along with the fees required to have
/// a broadcastable HTLC spending transaction. When a channel supports anchor outputs
/// (specifically the zero fee HTLC transaction variant), this threshold no longer takes into
- /// account the HTLC transaction fee as it is zero.
+ /// account the HTLC transaction fee as it is zero. Because of this, you may want to set this
+ /// value to a fixed limit for channels using anchor outputs, while the fee rate multiplier
+ /// variant is primarily intended for use with pre-anchor channels.
///
- /// This limit is applied for sent, forwarded, and received HTLCs and limits the total
- /// exposure across all three types per-channel. Setting this too low may prevent the
- /// sending or receipt of low-value HTLCs on high-traffic nodes, and this limit is very
- /// important to prevent stealing of dust HTLCs by miners.
+ /// The selected limit is applied for sent, forwarded, and received HTLCs and limits the total
+ /// exposure across all three types per-channel.
///
- /// Default value: 5_000_000 msat.
- pub max_dust_htlc_exposure_msat: u64,
+ /// Default value: [`MaxDustHTLCExposure::FeeRateMultiplier`] with a multiplier of 5000.
+ pub max_dust_htlc_exposure: MaxDustHTLCExposure,
/// The additional fee we're willing to pay to avoid waiting for the counterparty's
/// `to_self_delay` to reclaim funds.
///
/// [`Normal`]: crate::chain::chaininterface::ConfirmationTarget::Normal
/// [`Background`]: crate::chain::chaininterface::ConfirmationTarget::Background
pub force_close_avoidance_max_fee_satoshis: u64,
+ /// If set, allows this channel's counterparty to skim an additional fee off this node's inbound
+ /// HTLCs. Useful for liquidity providers to offload on-chain channel costs to end users.
+ ///
+ /// Usage:
+ /// - The payee will set this option and set its invoice route hints to use [intercept scids]
+ /// generated by this channel's counterparty.
+ /// - The counterparty will get an [`HTLCIntercepted`] event upon payment forward, and call
+ /// [`forward_intercepted_htlc`] with less than the amount provided in
+ /// [`HTLCIntercepted::expected_outbound_amount_msat`]. The difference between the expected and
+ /// actual forward amounts is their fee.
+ // TODO: link to LSP JIT channel invoice generation spec when it's merged
+ ///
+ /// # Note
+ /// It's important for payee wallet software to verify that [`PaymentClaimable::amount_msat`] is
+ /// as-expected if this feature is activated, otherwise they may lose money!
+ /// [`PaymentClaimable::counterparty_skimmed_fee_msat`] provides the fee taken by the
+ /// counterparty.
+ ///
+ /// # Note
+ /// Switching this config flag on may break compatibility with versions of LDK prior to 0.0.116.
+ /// Unsetting this flag between restarts may lead to payment receive failures.
+ ///
+ /// Default value: false.
+ ///
+ /// [intercept scids]: crate::ln::channelmanager::ChannelManager::get_intercept_scid
+ /// [`forward_intercepted_htlc`]: crate::ln::channelmanager::ChannelManager::forward_intercepted_htlc
+ /// [`HTLCIntercepted`]: crate::events::Event::HTLCIntercepted
+ /// [`HTLCIntercepted::expected_outbound_amount_msat`]: crate::events::Event::HTLCIntercepted::expected_outbound_amount_msat
+ /// [`PaymentClaimable::amount_msat`]: crate::events::Event::PaymentClaimable::amount_msat
+ /// [`PaymentClaimable::counterparty_skimmed_fee_msat`]: crate::events::Event::PaymentClaimable::counterparty_skimmed_fee_msat
+ // TODO: link to bLIP when it's merged
+ pub accept_underpaying_htlcs: bool,
}
impl ChannelConfig {
self.cltv_expiry_delta = cltv_expiry_delta;
}
if let Some(max_dust_htlc_exposure_msat) = update.max_dust_htlc_exposure_msat {
- self.max_dust_htlc_exposure_msat = max_dust_htlc_exposure_msat;
+ self.max_dust_htlc_exposure = max_dust_htlc_exposure_msat;
}
if let Some(force_close_avoidance_max_fee_satoshis) = update.force_close_avoidance_max_fee_satoshis {
self.force_close_avoidance_max_fee_satoshis = force_close_avoidance_max_fee_satoshis;
forwarding_fee_proportional_millionths: 0,
forwarding_fee_base_msat: 1000,
cltv_expiry_delta: 6 * 12, // 6 blocks/hour * 12 hours
- max_dust_htlc_exposure_msat: 5_000_000,
+ max_dust_htlc_exposure: MaxDustHTLCExposure::FeeRateMultiplier(5000),
force_close_avoidance_max_fee_satoshis: 1000,
+ accept_underpaying_htlcs: false,
}
}
}
-impl_writeable_tlv_based!(ChannelConfig, {
- (0, forwarding_fee_proportional_millionths, required),
- (2, forwarding_fee_base_msat, required),
- (4, cltv_expiry_delta, required),
- (6, max_dust_htlc_exposure_msat, required),
- // ChannelConfig serialized this field with a required type of 8 prior to the introduction of
- // LegacyChannelConfig. To make sure that serialization is not compatible with this one, we use
- // the next required type of 10, which if seen by the old serialization will always fail.
- (10, force_close_avoidance_max_fee_satoshis, required),
-});
+impl crate::util::ser::Writeable for ChannelConfig {
+ fn write<W: crate::util::ser::Writer>(&self, writer: &mut W) -> Result<(), crate::io::Error> {
+ let max_dust_htlc_exposure_msat_fixed_limit = match self.max_dust_htlc_exposure {
+ MaxDustHTLCExposure::FixedLimitMsat(limit) => limit,
+ MaxDustHTLCExposure::FeeRateMultiplier(_) => 5_000_000,
+ };
+ write_tlv_fields!(writer, {
+ (0, self.forwarding_fee_proportional_millionths, required),
+ (1, self.accept_underpaying_htlcs, (default_value, false)),
+ (2, self.forwarding_fee_base_msat, required),
+ (3, self.max_dust_htlc_exposure, required),
+ (4, self.cltv_expiry_delta, required),
+ (6, max_dust_htlc_exposure_msat_fixed_limit, required),
+ // ChannelConfig serialized this field with a required type of 8 prior to the introduction of
+ // LegacyChannelConfig. To make sure that serialization is not compatible with this one, we use
+ // the next required type of 10, which if seen by the old serialization will always fail.
+ (10, self.force_close_avoidance_max_fee_satoshis, required),
+ });
+ Ok(())
+ }
+}
+
+impl crate::util::ser::Readable for ChannelConfig {
+ fn read<R: crate::io::Read>(reader: &mut R) -> Result<Self, crate::ln::msgs::DecodeError> {
+ let mut forwarding_fee_proportional_millionths = 0;
+ let mut accept_underpaying_htlcs = false;
+ let mut forwarding_fee_base_msat = 1000;
+ let mut cltv_expiry_delta = 6 * 12;
+ let mut max_dust_htlc_exposure_msat = None;
+ let mut max_dust_htlc_exposure_enum = None;
+ let mut force_close_avoidance_max_fee_satoshis = 1000;
+ read_tlv_fields!(reader, {
+ (0, forwarding_fee_proportional_millionths, required),
+ (1, accept_underpaying_htlcs, (default_value, false)),
+ (2, forwarding_fee_base_msat, required),
+ (3, max_dust_htlc_exposure_enum, option),
+ (4, cltv_expiry_delta, required),
+ // Has always been written, but became optionally read in 0.0.116
+ (6, max_dust_htlc_exposure_msat, option),
+ (10, force_close_avoidance_max_fee_satoshis, required),
+ });
+ let max_dust_htlc_fixed_limit = max_dust_htlc_exposure_msat.unwrap_or(5_000_000);
+ let max_dust_htlc_exposure_msat = max_dust_htlc_exposure_enum
+ .unwrap_or(MaxDustHTLCExposure::FixedLimitMsat(max_dust_htlc_fixed_limit));
+ Ok(Self {
+ forwarding_fee_proportional_millionths,
+ accept_underpaying_htlcs,
+ forwarding_fee_base_msat,
+ cltv_expiry_delta,
+ max_dust_htlc_exposure: max_dust_htlc_exposure_msat,
+ force_close_avoidance_max_fee_satoshis,
+ })
+ }
+}
/// A parallel struct to [`ChannelConfig`] to define partial updates.
#[allow(missing_docs)]
pub forwarding_fee_proportional_millionths: Option<u32>,
pub forwarding_fee_base_msat: Option<u32>,
pub cltv_expiry_delta: Option<u16>,
- pub max_dust_htlc_exposure_msat: Option<u64>,
+ pub max_dust_htlc_exposure_msat: Option<MaxDustHTLCExposure>,
pub force_close_avoidance_max_fee_satoshis: Option<u64>,
}
forwarding_fee_proportional_millionths: Some(config.forwarding_fee_proportional_millionths),
forwarding_fee_base_msat: Some(config.forwarding_fee_base_msat),
cltv_expiry_delta: Some(config.cltv_expiry_delta),
- max_dust_htlc_exposure_msat: Some(config.max_dust_htlc_exposure_msat),
+ max_dust_htlc_exposure_msat: Some(config.max_dust_htlc_exposure),
force_close_avoidance_max_fee_satoshis: Some(config.force_close_avoidance_max_fee_satoshis),
}
}
impl crate::util::ser::Writeable for LegacyChannelConfig {
fn write<W: crate::util::ser::Writer>(&self, writer: &mut W) -> Result<(), crate::io::Error> {
+ let max_dust_htlc_exposure_msat_fixed_limit = match self.options.max_dust_htlc_exposure {
+ MaxDustHTLCExposure::FixedLimitMsat(limit) => limit,
+ MaxDustHTLCExposure::FeeRateMultiplier(_) => 5_000_000,
+ };
write_tlv_fields!(writer, {
(0, self.options.forwarding_fee_proportional_millionths, required),
- (1, self.options.max_dust_htlc_exposure_msat, (default_value, 5_000_000)),
+ (1, max_dust_htlc_exposure_msat_fixed_limit, required),
(2, self.options.cltv_expiry_delta, required),
(3, self.options.force_close_avoidance_max_fee_satoshis, (default_value, 1000)),
(4, self.announced_channel, required),
+ (5, self.options.max_dust_htlc_exposure, required),
(6, self.commit_upfront_shutdown_pubkey, required),
(8, self.options.forwarding_fee_base_msat, required),
});
impl crate::util::ser::Readable for LegacyChannelConfig {
fn read<R: crate::io::Read>(reader: &mut R) -> Result<Self, crate::ln::msgs::DecodeError> {
let mut forwarding_fee_proportional_millionths = 0;
- let mut max_dust_htlc_exposure_msat = 5_000_000;
+ let mut max_dust_htlc_exposure_msat_fixed_limit = None;
let mut cltv_expiry_delta = 0;
let mut force_close_avoidance_max_fee_satoshis = 1000;
let mut announced_channel = false;
let mut commit_upfront_shutdown_pubkey = false;
let mut forwarding_fee_base_msat = 0;
+ let mut max_dust_htlc_exposure_enum = None;
read_tlv_fields!(reader, {
(0, forwarding_fee_proportional_millionths, required),
- (1, max_dust_htlc_exposure_msat, (default_value, 5_000_000u64)),
+ // Has always been written, but became optionally read in 0.0.116
+ (1, max_dust_htlc_exposure_msat_fixed_limit, option),
(2, cltv_expiry_delta, required),
(3, force_close_avoidance_max_fee_satoshis, (default_value, 1000u64)),
(4, announced_channel, required),
+ (5, max_dust_htlc_exposure_enum, option),
(6, commit_upfront_shutdown_pubkey, required),
(8, forwarding_fee_base_msat, required),
});
+ let max_dust_htlc_exposure_msat_fixed_limit =
+ max_dust_htlc_exposure_msat_fixed_limit.unwrap_or(5_000_000);
+ let max_dust_htlc_exposure_msat = max_dust_htlc_exposure_enum
+ .unwrap_or(MaxDustHTLCExposure::FixedLimitMsat(max_dust_htlc_exposure_msat_fixed_limit));
Ok(Self {
options: ChannelConfig {
forwarding_fee_proportional_millionths,
- max_dust_htlc_exposure_msat,
+ max_dust_htlc_exposure: max_dust_htlc_exposure_msat,
cltv_expiry_delta,
force_close_avoidance_max_fee_satoshis,
forwarding_fee_base_msat,
+ accept_underpaying_htlcs: false,
},
announced_channel,
commit_upfront_shutdown_pubkey,
use bitcoin::secp256k1;
use bitcoin::secp256k1::{SecretKey, PublicKey};
use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature};
-#[cfg(anchors)]
use crate::events::bump_transaction::HTLCDescriptor;
use crate::util::ser::{Writeable, Writer};
use crate::io::Error;
+use crate::ln::features::ChannelTypeFeatures;
/// Initial value for revoked commitment downward counter
pub const INITIAL_REVOKED_COMMITMENT_NUMBER: u64 = 1 << 48;
}
}
- pub fn opt_anchors(&self) -> bool { self.inner.opt_anchors() }
+ pub fn channel_type_features(&self) -> &ChannelTypeFeatures { self.inner.channel_type_features() }
#[cfg(test)]
pub fn get_enforcement_state(&self) -> MutexGuard<EnforcementState> {
for (this_htlc, sig) in trusted_tx.htlcs().iter().zip(&commitment_tx.counterparty_htlc_sigs) {
assert!(this_htlc.transaction_output_index.is_some());
let keys = trusted_tx.keys();
- let htlc_tx = chan_utils::build_htlc_transaction(&commitment_txid, trusted_tx.feerate_per_kw(), holder_csv, &this_htlc, self.opt_anchors(), false, &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
+ let htlc_tx = chan_utils::build_htlc_transaction(&commitment_txid, trusted_tx.feerate_per_kw(), holder_csv, &this_htlc, self.channel_type_features(), &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
- let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&this_htlc, self.opt_anchors(), &keys);
+ let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&this_htlc, self.channel_type_features(), &keys);
- let sighash_type = if self.opt_anchors() {
+ let sighash_type = if self.channel_type_features().supports_anchors_zero_fee_htlc_tx() {
EcdsaSighashType::SinglePlusAnyoneCanPay
} else {
EcdsaSighashType::All
Ok(self.inner.sign_justice_revoked_htlc(justice_tx, input, amount, per_commitment_key, htlc, secp_ctx).unwrap())
}
- #[cfg(anchors)]
fn sign_holder_htlc_transaction(
&self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor,
secp_ctx: &Secp256k1<secp256k1::All>
) -> Result<Signature, ()> {
- let per_commitment_point = self.get_per_commitment_point(htlc_descriptor.per_commitment_number, secp_ctx);
assert_eq!(htlc_tx.input[input], htlc_descriptor.unsigned_tx_input());
- assert_eq!(htlc_tx.output[input], htlc_descriptor.tx_output(&per_commitment_point, secp_ctx));
+ assert_eq!(htlc_tx.output[input], htlc_descriptor.tx_output(secp_ctx));
Ok(self.inner.sign_holder_htlc_transaction(htlc_tx, input, htlc_descriptor, secp_ctx).unwrap())
}
}
}
+/// Wrapper for logging `Iterator`s.
+///
+/// This is not exported to bindings users as fmt can't be used in C
+#[doc(hidden)]
+pub struct DebugIter<T: fmt::Display, I: core::iter::Iterator<Item = T> + Clone>(pub I);
+impl<T: fmt::Display, I: core::iter::Iterator<Item = T> + Clone> fmt::Display for DebugIter<T, I> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ write!(f, "[")?;
+ let mut iter = self.0.clone();
+ if let Some(item) = iter.next() {
+ write!(f, "{}", item)?;
+ }
+ while let Some(item) = iter.next() {
+ write!(f, ", {}", item)?;
+ }
+ write!(f, "]")?;
+ Ok(())
+ }
+}
+
#[cfg(test)]
mod tests {
use crate::util::logger::{Logger, Level};
use crate::ln::chan_utils::HTLCClaim;
use crate::util::logger::DebugBytes;
+macro_rules! log_iter {
+ ($obj: expr) => {
+ $crate::util::logger::DebugIter($obj)
+ }
+}
+
/// Logs a pubkey in hex format.
#[macro_export]
macro_rules! log_pubkey {
pub mod logger;
pub mod config;
-#[cfg(any(test, fuzzing, feature = "_test_utils"))]
+#[cfg(any(test, feature = "_test_utils"))]
pub mod test_utils;
/// impls of traits that add exra enforcement on the way they're called. Useful for detecting state
/// machine errors and used in fuzz targets and tests.
-#[cfg(any(test, fuzzing, feature = "_test_utils"))]
+#[cfg(any(test, feature = "_test_utils"))]
pub mod enforcing_trait_impls;
use bitcoin::hash_types::{Txid, BlockHash};
use core::marker::Sized;
use core::time::Duration;
+use crate::chain::ClaimId;
use crate::ln::msgs::DecodeError;
#[cfg(taproot)]
use crate::ln::msgs::PartialSignatureWithNonce;
/// encoded in several different ways, which we must check for at deserialization-time. Thus, if
/// you're looking for an example of a variable-length integer to use for your own project, move
/// along, this is a rather poor design.
+#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct BigSize(pub u64);
impl Writeable for BigSize {
#[inline]
}
impl_for_vec!(ecdsa::Signature);
+impl_for_vec!(crate::chain::channelmonitor::ChannelMonitorUpdate);
impl_for_vec!(crate::ln::channelmanager::MonitorUpdateCompletionAction);
impl_for_vec!((A, B), A, B);
impl_writeable_for_vec!(&crate::routing::router::BlindedTail);
}
}
+/// This is not exported to bindings users as `Duration`s are simply mapped as ints.
impl Writeable for Duration {
#[inline]
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
self.subsec_nanos().write(w)
}
}
+/// This is not exported to bindings users as `Duration`s are simply mapped as ints.
impl Readable for Duration {
#[inline]
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
}
}
+impl Writeable for ClaimId {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ self.0.write(writer)
+ }
+}
+
+impl Readable for ClaimId {
+ fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
+ Ok(Self(Readable::read(reader)?))
+ }
+}
+
#[cfg(test)]
mod tests {
use core::convert::TryFrom;
//! [`Readable`]: crate::util::ser::Readable
//! [`Writeable`]: crate::util::ser::Writeable
+// There are quite a few TLV serialization "types" which behave differently. We currently only
+// publicly document the `optional` and `required` types, not supporting anything else publicly and
+// changing them at will.
+//
+// Some of the other types include:
+// * (default_value, $default) - reads optionally, reading $default if no TLV is present
+// * (static_value, $value) - ignores any TLVs, always using $value
+// * required_vec - reads into a Vec without a length prefix, failing if no TLV is present.
+// * optional_vec - reads into an Option<Vec> without a length prefix, continuing if no TLV is
+// present. Writes from a Vec directly, only if any elements are present. Note
+// that the struct deserialization macros return a Vec, not an Option.
+// * upgradable_option - reads via MaybeReadable.
+// * upgradable_required - reads via MaybeReadable, requiring a TLV be present but may return None
+// if MaybeReadable::read() returns None.
+
/// Implements serialization for a single TLV record.
/// This is exported for use by other exported macros, do not use directly.
#[doc(hidden)]
BigSize($field.serialized_length() as u64).write($stream)?;
$field.write($stream)?;
};
- ($stream: expr, $type: expr, $field: expr, vec_type) => {
+ ($stream: expr, $type: expr, $field: expr, required_vec) => {
$crate::_encode_tlv!($stream, $type, $crate::util::ser::WithoutLength(&$field), required);
};
($stream: expr, $optional_type: expr, $optional_field: expr, option) => {
};
($stream: expr, $type: expr, $field: expr, optional_vec) => {
if !$field.is_empty() {
- $crate::_encode_tlv!($stream, $type, $field, vec_type);
+ $crate::_encode_tlv!($stream, $type, $field, required_vec);
}
};
($stream: expr, $type: expr, $field: expr, upgradable_required) => {
BigSize(field_len as u64).write(&mut $len).expect("No in-memory data may fail to serialize");
$len.0 += field_len;
};
- ($len: expr, $type: expr, $field: expr, vec_type) => {
+ ($len: expr, $type: expr, $field: expr, required_vec) => {
$crate::_get_varint_length_prefixed_tlv_length!($len, $type, $crate::util::ser::WithoutLength(&$field), required);
};
($len: expr, $optional_type: expr, $optional_field: expr, option) => {
};
($len: expr, $type: expr, $field: expr, optional_vec) => {
if !$field.is_empty() {
- $crate::_get_varint_length_prefixed_tlv_length!($len, $type, $field, vec_type);
+ $crate::_get_varint_length_prefixed_tlv_length!($len, $type, $field, required_vec);
}
};
($len: expr, $type: expr, $field: expr, (option: $trait: ident $(, $read_arg: expr)?)) => {
($last_seen_type: expr, $typ: expr, $type: expr, $field: ident, option) => {{
// no-op
}};
- ($last_seen_type: expr, $typ: expr, $type: expr, $field: ident, vec_type) => {{
- // no-op
+ ($last_seen_type: expr, $typ: expr, $type: expr, $field: ident, required_vec) => {{
+ $crate::_check_decoded_tlv_order!($last_seen_type, $typ, $type, $field, required);
}};
($last_seen_type: expr, $typ: expr, $type: expr, $field: ident, optional_vec) => {{
// no-op
($last_seen_type: expr, $type: expr, $field: ident, (required: $trait: ident $(, $read_arg: expr)?)) => {{
$crate::_check_missing_tlv!($last_seen_type, $type, $field, required);
}};
- ($last_seen_type: expr, $type: expr, $field: ident, vec_type) => {{
- // no-op
+ ($last_seen_type: expr, $type: expr, $field: ident, required_vec) => {{
+ $crate::_check_missing_tlv!($last_seen_type, $type, $field, required);
}};
($last_seen_type: expr, $type: expr, $field: ident, option) => {{
// no-op
($reader: expr, $field: ident, (required: $trait: ident $(, $read_arg: expr)?)) => {{
$field = $trait::read(&mut $reader $(, $read_arg)*)?;
}};
- ($reader: expr, $field: ident, vec_type) => {{
+ ($reader: expr, $field: ident, required_vec) => {{
let f: $crate::util::ser::WithoutLength<Vec<_>> = $crate::util::ser::Readable::read(&mut $reader)?;
- $field = Some(f.0);
+ $field = f.0;
}};
($reader: expr, $field: ident, option) => {{
$field = Some($crate::util::ser::Readable::read(&mut $reader)?);
}};
($reader: expr, $field: ident, optional_vec) => {{
- $crate::_decode_tlv!($reader, $field, vec_type);
+ let f: $crate::util::ser::WithoutLength<Vec<_>> = $crate::util::ser::Readable::read(&mut $reader)?;
+ $field = Some(f.0);
}};
// `upgradable_required` indicates we're reading a required TLV that may have been upgraded
// without backwards compat. We'll error if the field is missing, and return `Ok(None)` if the
($field: ident, required) => {
$field.0.unwrap()
};
- ($field: ident, vec_type) => {
- $field.unwrap()
+ ($field: ident, required_vec) => {
+ $field
};
($field: ident, optional_vec) => {
$field.unwrap()
($field: ident, (required: $trait: ident $(, $read_arg: expr)?)) => {
$crate::_init_tlv_field_var!($field, required);
};
- ($field: ident, vec_type) => {
- let mut $field = Some(Vec::new());
+ ($field: ident, required_vec) => {
+ let mut $field = Vec::new();
};
($field: ident, option) => {
let mut $field = None;
f()
}),*
$($tuple_variant_id => {
- Ok($st::$tuple_variant_name(Readable::read(reader)?))
+ Ok($st::$tuple_variant_name($crate::util::ser::Readable::read(reader)?))
}),*
_ => {
Err($crate::ln::msgs::DecodeError::UnknownRequiredFeature)
use crate::chain::transaction::OutPoint;
use crate::sign;
use crate::events;
+use crate::events::bump_transaction::{WalletSource, Utxo};
use crate::ln::channelmanager;
use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures};
use crate::ln::{msgs, wire};
use crate::util::logger::{Logger, Level, Record};
use crate::util::ser::{Readable, ReadableArgs, Writer, Writeable};
+use bitcoin::EcdsaSighashType;
use bitcoin::blockdata::constants::ChainHash;
use bitcoin::blockdata::constants::genesis_block;
use bitcoin::blockdata::transaction::{Transaction, TxOut};
use bitcoin::blockdata::block::Block;
use bitcoin::network::constants::Network;
use bitcoin::hash_types::{BlockHash, Txid};
+use bitcoin::util::sighash::SighashCache;
use bitcoin::secp256k1::{SecretKey, PublicKey, Secp256k1, ecdsa::Signature, Scalar};
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::ecdsa::RecoverableSignature;
+#[cfg(any(test, feature = "_test_utils"))]
use regex;
use crate::io;
use crate::prelude::*;
use core::cell::RefCell;
+use core::ops::DerefMut;
use core::time::Duration;
use crate::sync::{Mutex, Arc};
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
impl<'a> Router for TestRouter<'a> {
fn find_route(
&self, payer: &PublicKey, params: &RouteParameters, first_hops: Option<&[&channelmanager::ChannelDetails]>,
- inflight_htlcs: &InFlightHtlcs
+ inflight_htlcs: InFlightHtlcs
) -> Result<Route, msgs::LightningError> {
if let Some((find_route_query, find_route_res)) = self.next_routes.lock().unwrap().pop_front() {
assert_eq!(find_route_query, *params);
if let Ok(ref route) = find_route_res {
- let locked_scorer = self.scorer.lock().unwrap();
- let scorer = ScorerAccountingForInFlightHtlcs::new(locked_scorer, inflight_htlcs);
+ let mut binding = self.scorer.lock().unwrap();
+ let scorer = ScorerAccountingForInFlightHtlcs::new(binding.deref_mut(), &inflight_htlcs);
for path in &route.paths {
let mut aggregate_msat = 0u64;
for (idx, hop) in path.hops.iter().rev().enumerate() {
return find_route_res;
}
let logger = TestLogger::new();
- let scorer = self.scorer.lock().unwrap();
find_route(
payer, params, &self.network_graph, first_hops, &logger,
- &ScorerAccountingForInFlightHtlcs::new(scorer, &inflight_htlcs), &(),
+ &ScorerAccountingForInFlightHtlcs::new(self.scorer.lock().unwrap().deref_mut(), &inflight_htlcs), &(),
&[42; 32]
)
}
/// 1. belong to the specified module and
/// 2. match the given regex pattern.
/// Assert that the number of occurrences equals the given `count`
+ #[cfg(any(test, feature = "_test_utils"))]
pub fn assert_log_regex(&self, module: &str, pattern: regex::Regex, count: usize) {
let log_entries = self.lines.lock().unwrap();
let l: usize = log_entries.iter().filter(|&(&(ref m, ref l), _c)| {
}
}
}
+
+pub struct TestWalletSource {
+ secret_key: SecretKey,
+ utxos: RefCell<Vec<Utxo>>,
+ secp: Secp256k1<bitcoin::secp256k1::All>,
+}
+
+impl TestWalletSource {
+ pub fn new(secret_key: SecretKey) -> Self {
+ Self {
+ secret_key,
+ utxos: RefCell::new(Vec::new()),
+ secp: Secp256k1::new(),
+ }
+ }
+
+ pub fn add_utxo(&self, outpoint: bitcoin::OutPoint, value: u64) -> TxOut {
+ let public_key = bitcoin::PublicKey::new(self.secret_key.public_key(&self.secp));
+ let utxo = Utxo::new_p2pkh(outpoint, value, &public_key.pubkey_hash());
+ self.utxos.borrow_mut().push(utxo.clone());
+ utxo.output
+ }
+
+ pub fn add_custom_utxo(&self, utxo: Utxo) -> TxOut {
+ let output = utxo.output.clone();
+ self.utxos.borrow_mut().push(utxo);
+ output
+ }
+
+ pub fn remove_utxo(&self, outpoint: bitcoin::OutPoint) {
+ self.utxos.borrow_mut().retain(|utxo| utxo.outpoint != outpoint);
+ }
+}
+
+impl WalletSource for TestWalletSource {
+ fn list_confirmed_utxos(&self) -> Result<Vec<Utxo>, ()> {
+ Ok(self.utxos.borrow().clone())
+ }
+
+ fn get_change_script(&self) -> Result<Script, ()> {
+ let public_key = bitcoin::PublicKey::new(self.secret_key.public_key(&self.secp));
+ Ok(Script::new_p2pkh(&public_key.pubkey_hash()))
+ }
+
+ fn sign_tx(&self, mut tx: Transaction) -> Result<Transaction, ()> {
+ let utxos = self.utxos.borrow();
+ for i in 0..tx.input.len() {
+ if let Some(utxo) = utxos.iter().find(|utxo| utxo.outpoint == tx.input[i].previous_output) {
+ let sighash = SighashCache::new(&tx)
+ .legacy_signature_hash(i, &utxo.output.script_pubkey, EcdsaSighashType::All as u32)
+ .map_err(|_| ())?;
+ let sig = self.secp.sign_ecdsa(&sighash.as_hash().into(), &self.secret_key);
+ let bitcoin_sig = bitcoin::EcdsaSig { sig, hash_ty: EcdsaSighashType::All }.to_vec();
+ tx.input[i].script_sig = Builder::new()
+ .push_slice(&bitcoin_sig)
+ .push_slice(&self.secret_key.public_key(&self.secp).serialize())
+ .into_script();
+ }
+ }
+ Ok(tx)
+ }
+}
}
}
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+#[cfg(not(feature = "no-std"))]
+pub struct MonotonicTime(std::time::Instant);
+
+/// The amount of time to shift `Instant` forward to prevent overflow when subtracting a `Duration`
+/// from `Instant::now` on some operating systems (e.g., iOS representing `Instance` as `u64`).
+#[cfg(not(feature = "no-std"))]
+const SHIFT: Duration = Duration::from_secs(10 * 365 * 24 * 60 * 60); // 10 years.
+
#[cfg(not(feature = "no-std"))]
-impl Time for std::time::Instant {
+impl Time for MonotonicTime {
fn now() -> Self {
- std::time::Instant::now()
+ let instant = std::time::Instant::now().checked_add(SHIFT).expect("Overflow on MonotonicTime instantiation");
+ Self(instant)
}
fn duration_since(&self, earlier: Self) -> Duration {
// clocks" that go backwards in practice (likely relatively ancient kernels/etc). Thus, we
// manually check for time going backwards here and return a duration of zero in that case.
let now = Self::now();
- if now > earlier { now - earlier } else { Duration::from_secs(0) }
+ if now.0 > earlier.0 { now.0 - earlier.0 } else { Duration::from_secs(0) }
}
fn duration_since_epoch() -> Duration {
use std::time::SystemTime;
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap()
}
+
fn elapsed(&self) -> Duration {
- std::time::Instant::elapsed(self)
+ Self::now().0 - self.0
+ }
+}
+
+#[cfg(not(feature = "no-std"))]
+impl Sub<Duration> for MonotonicTime {
+ type Output = Self;
+
+ fn sub(self, other: Duration) -> Self {
+ let instant = self.0.checked_sub(other).expect("MonotonicTime is not supposed to go backward futher than 10 years");
+ Self(instant)
}
}
assert_eq!(now.elapsed(), Duration::from_secs(0));
assert_eq!(later - elapsed, now);
}
+
+ #[test]
+ #[cfg(not(feature = "no-std"))]
+ fn monotonic_time_subtracts() {
+ let now = super::MonotonicTime::now();
+ assert!(now.elapsed() < Duration::from_secs(10));
+
+ let ten_years = Duration::from_secs(10 * 365 * 24 * 60 * 60);
+ let past = now - ten_years;
+ assert!(past.elapsed() >= ten_years);
+ }
}
--- /dev/null
+[package]
+name = "msrv-check"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
+lightning = { path = "../lightning" }
+lightning-block-sync = { path = "../lightning-block-sync", features = [ "rest-client", "rpc-client" ] }
+lightning-invoice = { path = "../lightning-invoice" }
+lightning-net-tokio = { path = "../lightning-net-tokio" }
+lightning-persister = { path = "../lightning-persister" }
+lightning-background-processor = { path = "../lightning-background-processor", features = ["futures"] }
+lightning-rapid-gossip-sync = { path = "../lightning-rapid-gossip-sync" }
+++ /dev/null
-## Bug Fixes
-
-* Fixed sending large onion messages, which previously would result in an HMAC error on the second
- hop (#2277).
+++ /dev/null
-## Backwards Compatibility
-
-* `PaymentParameters` written with blinded path info using 0.0.115 will not be readable in 0.0.116