diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ca7ac30cb..c7aaf9e58 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -45,6 +45,7 @@ jobs: cargo update -p proptest --precise "1.2.0" --verbose # proptest 1.3.0 requires rustc 1.64.0 cargo update -p regex --precise "1.9.6" --verbose # regex 1.10.0 requires rustc 1.65.0 cargo update -p home --precise "0.5.5" --verbose # home v0.5.9 requires rustc 1.70 or newer + cargo update -p url --precise "2.5.0" --verbose # url v2.5.1 requires rustc 1.67 or newer - name: Set RUSTFLAGS to deny warnings if: "matrix.toolchain == 'stable'" run: echo "RUSTFLAGS=-D warnings" >> "$GITHUB_ENV" diff --git a/CHANGELOG.md b/CHANGELOG.md index e92a7c9e8..3a3894899 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,55 @@ +# 0.3.0 - June 21, 2024 + +This third minor release notably adds support for BOLT12 payments, Anchor +channels, and sourcing inbound liquidity via LSPS2 just-in-time channels. + +## Feature and API updates +- Support for creating and paying BOLT12 offers and refunds has been added (#265). +- Support for Anchor channels has been added (#141). +- Support for sourcing inbound liquidity via LSPS2 just-in-time (JIT) channels has been added (#223). +- The node's local view of the network graph can now be accessed via interface methods (#293). +- A new `next_event_async` method was added that allows polling the event queue asynchronously (#224). +- A `default_config` method was introduced that allows to retrieve sane default values, also in bindings (#242). +- The `PaymentFailed` and `ChannelClosed` events now include `reason` fields (#260). +- All available balances outside of channel balances are now exposed via a unified `list_balances` interface method (#250). +- The maximum in-flight HTLC value has been bumped to 100% of the channel capacity for private outbound channels (#303) and, if JIT channel support is enabled, for inbound channels (#262). +- The fee paid is now exposed via the `PaymentSuccessful` event (#271). +- A `status` method has been added allowing to retrieve information about the `Node`'s status (#272). +- `Node` no longer takes a `KVStore` type parameter, allowing to use the filesystem storage backend in bindings (#244). +- The payment APIs have been restructured to use per-type (`bolt11`, `onchain`, `bolt12`, ..) payment handlers which can be accessed via corresponding `Node::{type}_payment` methods (#270). +- Fully resolved channel monitors are now eventually moved to an archive location (#307). +- The ability to register and claim from custom payment hashes generated outside of LDK Node has been added (#308). + +## Bug Fixes +- Node announcements are now correctly only broadcast if we have any public, sufficiently confirmed channels (#248, #314). +- Falling back to default fee values is now disallowed on mainnet, ensuring we won't startup without a successful fee cache update (#249). +- Persisted peers are now correctly reconnected after startup (#265). +- Concurrent connection attempts to the same peer are no longer overriding each other (#266). +- Several steps have been taken to reduce the risk of blocking node operation on wallet syncing in the face of unresponsive Esplora services (#281). + +## Compatibility Notes +- LDK has been updated to version 0.0.123 (#291). + +In total, this release features 54 files changed, 7282 insertions, 2410 deletions in 165 commits from 3 authors, in alphabetical order: + +- Elias Rohrer +- jbesraa +- Srikanth Iyengar + +# 0.2.2 - May 21, 2024 + +This is a bugfix release that reestablishes compatibility of Swift packages +with Xcode 15.3 and later. + +## Bug Fixes + +- Swift bindings can now be built using Xcode 15.3 and later again (#294) + +In total, this release features 5 files changed, 66 insertions, 2 deletions +deletions in 2 commits from 1 author, in alphabetical order: + +- Elias Rohrer + # 0.2.1 - Jan 26, 2024 This is a bugfix release bumping the used LDK and BDK dependencies to the diff --git a/Cargo.toml b/Cargo.toml index aa7a41e22..46bb48143 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ldk-node" -version = "0.2.1" +version = "0.3.0" authors = ["Elias Rohrer "] homepage = "https://lightningdevkit.org/" license = "MIT OR Apache-2.0" diff --git a/Package.swift b/Package.swift index c7d203713..67c02dd8b 100644 --- a/Package.swift +++ b/Package.swift @@ -3,8 +3,8 @@ import PackageDescription -let tag = "v0.2.1" -let checksum = "cca3d5f380c3c216c22ac892cb04a792f3982730e570df71d824462f14c1350e" +let tag = "v0.3.0" +let checksum = "07c8741768956bf1a51d1c25f751b5e29d1ae9ee2fd786c4282031c9a8a92f0c" let url = "https://github.com/lightningdevkit/ldk-node/releases/download/\(tag)/LDKNodeFFI.xcframework.zip" let package = Package( diff --git a/bindings/kotlin/ldk-node-android/gradle.properties b/bindings/kotlin/ldk-node-android/gradle.properties index f4f8cd571..70f5823b6 100644 --- a/bindings/kotlin/ldk-node-android/gradle.properties +++ b/bindings/kotlin/ldk-node-android/gradle.properties @@ -2,4 +2,4 @@ org.gradle.jvmargs=-Xmx1536m android.useAndroidX=true android.enableJetifier=true kotlin.code.style=official -libraryVersion=0.2.1 +libraryVersion=0.3.0 diff --git a/bindings/kotlin/ldk-node-jvm/gradle.properties b/bindings/kotlin/ldk-node-jvm/gradle.properties index 46f202595..4ed588117 100644 --- a/bindings/kotlin/ldk-node-jvm/gradle.properties +++ b/bindings/kotlin/ldk-node-jvm/gradle.properties @@ -1,3 +1,3 @@ org.gradle.jvmargs=-Xmx1536m kotlin.code.style=official -libraryVersion=0.2.1 +libraryVersion=0.3.0 diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index c0345351d..ccdf0d950 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -60,6 +60,7 @@ interface Node { PublicKey node_id(); sequence? listening_addresses(); Bolt11Payment bolt11_payment(); + Bolt12Payment bolt12_payment(); SpontaneousPayment spontaneous_payment(); OnchainPayment onchain_payment(); [Throws=NodeError] @@ -69,7 +70,9 @@ interface Node { [Throws=NodeError] UserChannelId connect_open_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config, boolean announce_channel); [Throws=NodeError] - void close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, boolean force); + void close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id); + [Throws=NodeError] + void force_close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id); [Throws=NodeError] void update_channel_config([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, ChannelConfig channel_config); [Throws=NodeError] @@ -97,15 +100,38 @@ interface Bolt11Payment { [Throws=NodeError] void send_probes_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat); [Throws=NodeError] + void claim_for_hash(PaymentHash payment_hash, u64 claimable_amount_msat, PaymentPreimage preimage); + [Throws=NodeError] + void fail_for_hash(PaymentHash payment_hash); + [Throws=NodeError] Bolt11Invoice receive(u64 amount_msat, [ByRef]string description, u32 expiry_secs); [Throws=NodeError] + Bolt11Invoice receive_for_hash(u64 amount_msat, [ByRef]string description, u32 expiry_secs, PaymentHash payment_hash); + [Throws=NodeError] Bolt11Invoice receive_variable_amount([ByRef]string description, u32 expiry_secs); [Throws=NodeError] + Bolt11Invoice receive_variable_amount_for_hash([ByRef]string description, u32 expiry_secs, PaymentHash payment_hash); + [Throws=NodeError] Bolt11Invoice receive_via_jit_channel(u64 amount_msat, [ByRef]string description, u32 expiry_secs, u64? max_lsp_fee_limit_msat); [Throws=NodeError] Bolt11Invoice receive_variable_amount_via_jit_channel([ByRef]string description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat); }; +interface Bolt12Payment { + [Throws=NodeError] + PaymentId send([ByRef]Offer offer, string? payer_note); + [Throws=NodeError] + PaymentId send_using_amount([ByRef]Offer offer, string? payer_note, u64 amount_msat); + [Throws=NodeError] + Offer receive(u64 amount_msat, [ByRef]string description); + [Throws=NodeError] + Offer receive_variable_amount([ByRef]string description); + [Throws=NodeError] + Bolt12Invoice request_refund_payment([ByRef]Refund refund); + [Throws=NodeError] + Refund initiate_refund(u64 amount_msat, u32 expiry_secs); +}; + interface SpontaneousPayment { [Throws=NodeError] PaymentId send(u64 amount_msat, PublicKey node_id, sequence custom_tlvs); @@ -129,6 +155,9 @@ enum NodeError { "OnchainTxCreationFailed", "ConnectionFailed", "InvoiceCreationFailed", + "InvoiceRequestCreationFailed", + "OfferCreationFailed", + "RefundCreationFailed", "PaymentSendingFailed", "ProbeSendingFailed", "ChannelCreationFailed", @@ -142,6 +171,7 @@ enum NodeError { "OnchainTxSigningFailed", "MessageSigningFailed", "TxSyncFailed", + "TxSyncTimeout", "GossipUpdateFailed", "GossipUpdateTimeout", "LiquidityRequestFailed", @@ -149,6 +179,7 @@ enum NodeError { "InvalidSocketAddress", "InvalidPublicKey", "InvalidSecretKey", + "InvalidOfferId", "InvalidNodeId", "InvalidPaymentId", "InvalidPaymentHash", @@ -156,10 +187,13 @@ enum NodeError { "InvalidPaymentSecret", "InvalidAmount", "InvalidInvoice", + "InvalidOffer", + "InvalidRefund", "InvalidChannelId", "InvalidNetwork", "InvalidCustomTlv", "DuplicatePayment", + "UnsupportedCurrency", "InsufficientFunds", "LiquiditySourceUnavailable", "LiquidityFeeTooHigh", @@ -201,6 +235,7 @@ interface Event { PaymentSuccessful(PaymentId? payment_id, PaymentHash payment_hash, u64? fee_paid_msat); PaymentFailed(PaymentId? payment_id, PaymentHash payment_hash, PaymentFailureReason? reason); PaymentReceived(PaymentId? payment_id, PaymentHash payment_hash, u64 amount_msat); + PaymentClaimable(PaymentId payment_id, PaymentHash payment_hash, u64 claimable_amount_msat, u32? claim_deadline); ChannelPending(ChannelId channel_id, UserChannelId user_channel_id, ChannelId former_temporary_channel_id, PublicKey counterparty_node_id, OutPoint funding_txo); ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id); ChannelClosed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, ClosureReason? reason); @@ -237,6 +272,8 @@ interface PaymentKind { Onchain(); Bolt11(PaymentHash hash, PaymentPreimage? preimage, PaymentSecret? secret, string? bolt11_invoice); Bolt11Jit(PaymentHash hash, PaymentPreimage? preimage, PaymentSecret? secret, LSPFeeLimits lsp_fee_limits); + Bolt12Offer(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, OfferId offer_id); + Bolt12Refund(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret); Spontaneous(PaymentHash hash, PaymentPreimage? preimage, sequence custom_tlvs); }; @@ -262,9 +299,11 @@ dictionary PaymentDetails { u64? amount_msat; PaymentDirection direction; PaymentStatus status; + // TODO: remove - use latest_update_timestamp u64 last_update; u64? fee_msat; u64 created_at; + u64 latest_update_timestamp; }; // [NonExhaustive] @@ -444,6 +483,18 @@ typedef string Address; [Custom] typedef string Bolt11Invoice; +[Custom] +typedef string Offer; + +[Custom] +typedef string Refund; + +[Custom] +typedef string Bolt12Invoice; + +[Custom] +typedef string OfferId; + [Custom] typedef string PaymentId; diff --git a/bindings/python/pyproject.toml b/bindings/python/pyproject.toml index 13ad46ce6..c8ff0a79d 100644 --- a/bindings/python/pyproject.toml +++ b/bindings/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ldk_node" -version = "0.2.1" +version = "0.3.0" authors = [ { name="Elias Rohrer", email="dev@tnull.de" }, ] diff --git a/bindings/swift/LDKNodeFFI.xcframework/Info.plist b/bindings/swift/LDKNodeFFI.xcframework/Info.plist index ee11d9a89..9b1d666c7 100644 --- a/bindings/swift/LDKNodeFFI.xcframework/Info.plist +++ b/bindings/swift/LDKNodeFFI.xcframework/Info.plist @@ -16,6 +16,8 @@ SupportedPlatform macos + LSMinimumSystemVersion + 12.0 LibraryIdentifier @@ -31,6 +33,8 @@ ios SupportedPlatformVariant simulator + MinimumOSVersion + 15.0 LibraryIdentifier @@ -43,6 +47,8 @@ SupportedPlatform ios + MinimumOSVersion + 15.0 CFBundlePackageType diff --git a/bindings/swift/LDKNodeFFI.xcframework/ios-arm64/LDKNodeFFI.framework/Info.plist b/bindings/swift/LDKNodeFFI.xcframework/ios-arm64/LDKNodeFFI.framework/Info.plist new file mode 100644 index 000000000..8d0bf0f09 --- /dev/null +++ b/bindings/swift/LDKNodeFFI.xcframework/ios-arm64/LDKNodeFFI.framework/Info.plist @@ -0,0 +1,18 @@ + + + + + CFBundleIdentifier + org.lightningdevkit.LDKNodeFFI + CFBundleName + LDKNodeFFI + CFBundleVersion + 0.3.0 + CFBundleShortVersionString + 0.3.0 + CFBundleExecutable + LDKNodeFFI + MinimumOSVersion + 100.0 + + diff --git a/bindings/swift/LDKNodeFFI.xcframework/ios-arm64_x86_64-simulator/LDKNodeFFI.framework/Info.plist b/bindings/swift/LDKNodeFFI.xcframework/ios-arm64_x86_64-simulator/LDKNodeFFI.framework/Info.plist new file mode 100644 index 000000000..92e0f395d --- /dev/null +++ b/bindings/swift/LDKNodeFFI.xcframework/ios-arm64_x86_64-simulator/LDKNodeFFI.framework/Info.plist @@ -0,0 +1,18 @@ + + + + + CFBundleIdentifier + org.lightningdevkit.LDKNodeFFI + CFBundleName + LDKNodeFFI + CFBundleVersion + 0.3.0 + CFBundleShortVersionString + 0.3.0 + CFBundleExecutable + LDKNodeFFI + MinimumOSVersion + 15.0 + + diff --git a/bindings/swift/LDKNodeFFI.xcframework/macos-arm64_x86_64/LDKNodeFFI.framework/Info.plist b/bindings/swift/LDKNodeFFI.xcframework/macos-arm64_x86_64/LDKNodeFFI.framework/Info.plist new file mode 100644 index 000000000..d3536e8b1 --- /dev/null +++ b/bindings/swift/LDKNodeFFI.xcframework/macos-arm64_x86_64/LDKNodeFFI.framework/Info.plist @@ -0,0 +1,18 @@ + + + + + CFBundleIdentifier + org.lightningdevkit.LDKNodeFFI + CFBundleName + LDKNodeFFI + CFBundleVersion + 0.3.0 + CFBundleShortVersionString + 0.3.0 + CFBundleExecutable + LDKNodeFFI + LSMinimumSystemVersion + 12.0 + + diff --git a/bindings/swift/Sources/LDKNode/LDKNode.swift b/bindings/swift/Sources/LDKNode/LDKNode.swift index 18551648c..5937c9050 100644 --- a/bindings/swift/Sources/LDKNode/LDKNode.swift +++ b/bindings/swift/Sources/LDKNode/LDKNode.swift @@ -288,7 +288,7 @@ private func uniffiCheckCallStatus( } case CALL_CANCELLED: - throw CancellationError() + fatalError("Cancellation not supported yet") default: throw UniffiInternalError.unexpectedRustCallStatusCode @@ -410,21 +410,38 @@ fileprivate struct FfiConverterString: FfiConverter { } -public protocol BuilderProtocol { - func build() throws -> LdkNode - func setEntropyBip39Mnemonic(mnemonic: Mnemonic, passphrase: String?) - func setEntropySeedBytes(seedBytes: [UInt8]) throws - func setEntropySeedPath(seedPath: String) - func setEsploraServer(esploraServerUrl: String) - func setGossipSourceP2p() - func setGossipSourceRgs(rgsServerUrl: String) - func setListeningAddresses(listeningAddresses: [SocketAddress]) throws - func setNetwork(network: Network) - func setStorageDirPath(storageDirPath: String) + + +public protocol Bolt11PaymentProtocol : AnyObject { + + func claimForHash(paymentHash: PaymentHash, claimableAmountMsat: UInt64, preimage: PaymentPreimage) throws + + func failForHash(paymentHash: PaymentHash) throws + + func receive(amountMsat: UInt64, description: String, expirySecs: UInt32) throws -> Bolt11Invoice + + func receiveForHash(amountMsat: UInt64, description: String, expirySecs: UInt32, paymentHash: PaymentHash) throws -> Bolt11Invoice + + func receiveVariableAmount(description: String, expirySecs: UInt32) throws -> Bolt11Invoice + + func receiveVariableAmountForHash(description: String, expirySecs: UInt32, paymentHash: PaymentHash) throws -> Bolt11Invoice + + func receiveVariableAmountViaJitChannel(description: String, expirySecs: UInt32, maxProportionalLspFeeLimitPpmMsat: UInt64?) throws -> Bolt11Invoice + + func receiveViaJitChannel(amountMsat: UInt64, description: String, expirySecs: UInt32, maxLspFeeLimitMsat: UInt64?) throws -> Bolt11Invoice + + func send(invoice: Bolt11Invoice) throws -> PaymentId + + func sendProbes(invoice: Bolt11Invoice) throws + + func sendProbesUsingAmount(invoice: Bolt11Invoice, amountMsat: UInt64) throws + + func sendUsingAmount(invoice: Bolt11Invoice, amountMsat: UInt64) throws -> PaymentId } -public class Builder: BuilderProtocol { +public class Bolt11Payment: + Bolt11PaymentProtocol { fileprivate let pointer: UnsafeMutableRawPointer // TODO: We'd like this to be `private` but for Swifty reasons, @@ -433,134 +450,165 @@ public class Builder: BuilderProtocol { required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { self.pointer = pointer } - public convenience init() { - self.init(unsafeFromRawPointer: try! rustCall() { - uniffi_ldk_node_fn_constructor_builder_new($0) -}) - } - deinit { - try! rustCall { uniffi_ldk_node_fn_free_builder(pointer, $0) } + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + return try! rustCall { uniffi_ldk_node_fn_clone_bolt11payment(self.pointer, $0) } } - - - public static func fromConfig(config: Config) -> Builder { - return Builder(unsafeFromRawPointer: try! rustCall() { - uniffi_ldk_node_fn_constructor_builder_from_config( - FfiConverterTypeConfig.lower(config),$0) -}) + deinit { + try! rustCall { uniffi_ldk_node_fn_free_bolt11payment(pointer, $0) } } - - public func build() throws -> LdkNode { - return try FfiConverterTypeLDKNode.lift( + public func claimForHash(paymentHash: PaymentHash, claimableAmountMsat: UInt64, preimage: PaymentPreimage) throws { + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_bolt11payment_claim_for_hash(self.uniffiClonePointer(), + FfiConverterTypePaymentHash.lower(paymentHash), + FfiConverterUInt64.lower(claimableAmountMsat), + FfiConverterTypePaymentPreimage.lower(preimage),$0 + ) +} + } + public func failForHash(paymentHash: PaymentHash) throws { + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_bolt11payment_fail_for_hash(self.uniffiClonePointer(), + FfiConverterTypePaymentHash.lower(paymentHash),$0 + ) +} + } + public func receive(amountMsat: UInt64, description: String, expirySecs: UInt32) throws -> Bolt11Invoice { + return try FfiConverterTypeBolt11Invoice.lift( try - rustCallWithError(FfiConverterTypeBuildError.lift) { - uniffi_ldk_node_fn_method_builder_build(self.pointer, $0 + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_bolt11payment_receive(self.uniffiClonePointer(), + FfiConverterUInt64.lower(amountMsat), + FfiConverterString.lower(description), + FfiConverterUInt32.lower(expirySecs),$0 ) } ) } - - public func setEntropyBip39Mnemonic(mnemonic: Mnemonic, passphrase: String?) { - try! - rustCall() { - - uniffi_ldk_node_fn_method_builder_set_entropy_bip39_mnemonic(self.pointer, - FfiConverterTypeMnemonic.lower(mnemonic), - FfiConverterOptionString.lower(passphrase),$0 + public func receiveForHash(amountMsat: UInt64, description: String, expirySecs: UInt32, paymentHash: PaymentHash) throws -> Bolt11Invoice { + return try FfiConverterTypeBolt11Invoice.lift( + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_bolt11payment_receive_for_hash(self.uniffiClonePointer(), + FfiConverterUInt64.lower(amountMsat), + FfiConverterString.lower(description), + FfiConverterUInt32.lower(expirySecs), + FfiConverterTypePaymentHash.lower(paymentHash),$0 ) } + ) } - - public func setEntropySeedBytes(seedBytes: [UInt8]) throws { - try - rustCallWithError(FfiConverterTypeBuildError.lift) { - uniffi_ldk_node_fn_method_builder_set_entropy_seed_bytes(self.pointer, - FfiConverterSequenceUInt8.lower(seedBytes),$0 + public func receiveVariableAmount(description: String, expirySecs: UInt32) throws -> Bolt11Invoice { + return try FfiConverterTypeBolt11Invoice.lift( + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_bolt11payment_receive_variable_amount(self.uniffiClonePointer(), + FfiConverterString.lower(description), + FfiConverterUInt32.lower(expirySecs),$0 ) } + ) } - - public func setEntropySeedPath(seedPath: String) { - try! - rustCall() { - - uniffi_ldk_node_fn_method_builder_set_entropy_seed_path(self.pointer, - FfiConverterString.lower(seedPath),$0 + public func receiveVariableAmountForHash(description: String, expirySecs: UInt32, paymentHash: PaymentHash) throws -> Bolt11Invoice { + return try FfiConverterTypeBolt11Invoice.lift( + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_bolt11payment_receive_variable_amount_for_hash(self.uniffiClonePointer(), + FfiConverterString.lower(description), + FfiConverterUInt32.lower(expirySecs), + FfiConverterTypePaymentHash.lower(paymentHash),$0 ) } + ) } - - public func setEsploraServer(esploraServerUrl: String) { - try! - rustCall() { - - uniffi_ldk_node_fn_method_builder_set_esplora_server(self.pointer, - FfiConverterString.lower(esploraServerUrl),$0 + public func receiveVariableAmountViaJitChannel(description: String, expirySecs: UInt32, maxProportionalLspFeeLimitPpmMsat: UInt64?) throws -> Bolt11Invoice { + return try FfiConverterTypeBolt11Invoice.lift( + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_bolt11payment_receive_variable_amount_via_jit_channel(self.uniffiClonePointer(), + FfiConverterString.lower(description), + FfiConverterUInt32.lower(expirySecs), + FfiConverterOptionUInt64.lower(maxProportionalLspFeeLimitPpmMsat),$0 ) } + ) } - - public func setGossipSourceP2p() { - try! - rustCall() { - - uniffi_ldk_node_fn_method_builder_set_gossip_source_p2p(self.pointer, $0 + public func receiveViaJitChannel(amountMsat: UInt64, description: String, expirySecs: UInt32, maxLspFeeLimitMsat: UInt64?) throws -> Bolt11Invoice { + return try FfiConverterTypeBolt11Invoice.lift( + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_bolt11payment_receive_via_jit_channel(self.uniffiClonePointer(), + FfiConverterUInt64.lower(amountMsat), + FfiConverterString.lower(description), + FfiConverterUInt32.lower(expirySecs), + FfiConverterOptionUInt64.lower(maxLspFeeLimitMsat),$0 ) } + ) } - - public func setGossipSourceRgs(rgsServerUrl: String) { - try! - rustCall() { - - uniffi_ldk_node_fn_method_builder_set_gossip_source_rgs(self.pointer, - FfiConverterString.lower(rgsServerUrl),$0 + public func send(invoice: Bolt11Invoice) throws -> PaymentId { + return try FfiConverterTypePaymentId.lift( + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_bolt11payment_send(self.uniffiClonePointer(), + FfiConverterTypeBolt11Invoice.lower(invoice),$0 ) } + ) } - - public func setListeningAddresses(listeningAddresses: [SocketAddress]) throws { + public func sendProbes(invoice: Bolt11Invoice) throws { try - rustCallWithError(FfiConverterTypeBuildError.lift) { - uniffi_ldk_node_fn_method_builder_set_listening_addresses(self.pointer, - FfiConverterSequenceTypeSocketAddress.lower(listeningAddresses),$0 + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_bolt11payment_send_probes(self.uniffiClonePointer(), + FfiConverterTypeBolt11Invoice.lower(invoice),$0 ) } } - - public func setNetwork(network: Network) { - try! - rustCall() { - - uniffi_ldk_node_fn_method_builder_set_network(self.pointer, - FfiConverterTypeNetwork.lower(network),$0 + public func sendProbesUsingAmount(invoice: Bolt11Invoice, amountMsat: UInt64) throws { + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_bolt11payment_send_probes_using_amount(self.uniffiClonePointer(), + FfiConverterTypeBolt11Invoice.lower(invoice), + FfiConverterUInt64.lower(amountMsat),$0 ) } } - - public func setStorageDirPath(storageDirPath: String) { - try! - rustCall() { - - uniffi_ldk_node_fn_method_builder_set_storage_dir_path(self.pointer, - FfiConverterString.lower(storageDirPath),$0 + public func sendUsingAmount(invoice: Bolt11Invoice, amountMsat: UInt64) throws -> PaymentId { + return try FfiConverterTypePaymentId.lift( + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_bolt11payment_send_using_amount(self.uniffiClonePointer(), + FfiConverterTypeBolt11Invoice.lower(invoice), + FfiConverterUInt64.lower(amountMsat),$0 ) } + ) } + } -public struct FfiConverterTypeBuilder: FfiConverter { +public struct FfiConverterTypeBolt11Payment: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer - typealias SwiftType = Builder + typealias SwiftType = Bolt11Payment - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Builder { + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> Bolt11Payment { + return Bolt11Payment(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: Bolt11Payment) -> UnsafeMutableRawPointer { + return value.uniffiClonePointer() + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Bolt11Payment { let v: UInt64 = try readInt(&buf) // The Rust code won't compile if a pointer won't fit in a UInt64. // We have to go via `UInt` because that's the thing that's the size of a pointer. @@ -571,48 +619,43 @@ public struct FfiConverterTypeBuilder: FfiConverter { return try lift(ptr!) } - public static func write(_ value: Builder, into buf: inout [UInt8]) { + public static func write(_ value: Bolt11Payment, into buf: inout [UInt8]) { // This fiddling is because `Int` is the thing that's the same size as a pointer. // The Rust code won't compile if a pointer won't fit in a `UInt64`. writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) } - - public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> Builder { - return Builder(unsafeFromRawPointer: pointer) - } - - public static func lower(_ value: Builder) -> UnsafeMutableRawPointer { - return value.pointer - } } -public func FfiConverterTypeBuilder_lift(_ pointer: UnsafeMutableRawPointer) throws -> Builder { - return try FfiConverterTypeBuilder.lift(pointer) +public func FfiConverterTypeBolt11Payment_lift(_ pointer: UnsafeMutableRawPointer) throws -> Bolt11Payment { + return try FfiConverterTypeBolt11Payment.lift(pointer) } -public func FfiConverterTypeBuilder_lower(_ value: Builder) -> UnsafeMutableRawPointer { - return FfiConverterTypeBuilder.lower(value) +public func FfiConverterTypeBolt11Payment_lower(_ value: Bolt11Payment) -> UnsafeMutableRawPointer { + return FfiConverterTypeBolt11Payment.lower(value) } -public protocol ChannelConfigProtocol { - func acceptUnderpayingHtlcs() -> Bool - func cltvExpiryDelta() -> UInt16 - func forceCloseAvoidanceMaxFeeSatoshis() -> UInt64 - func forwardingFeeBaseMsat() -> UInt32 - func forwardingFeeProportionalMillionths() -> UInt32 - func setAcceptUnderpayingHtlcs(value: Bool) - func setCltvExpiryDelta(value: UInt16) - func setForceCloseAvoidanceMaxFeeSatoshis(valueSat: UInt64) - func setForwardingFeeBaseMsat(feeMsat: UInt32) - func setForwardingFeeProportionalMillionths(value: UInt32) - func setMaxDustHtlcExposureFromFeeRateMultiplier(multiplier: UInt64) - func setMaxDustHtlcExposureFromFixedLimit(limitMsat: UInt64) + + +public protocol Bolt12PaymentProtocol : AnyObject { + + func initiateRefund(amountMsat: UInt64, expirySecs: UInt32) throws -> Refund + + func receive(amountMsat: UInt64, description: String) throws -> Offer + + func receiveVariableAmount(description: String) throws -> Offer + + func requestRefundPayment(refund: Refund) throws -> Bolt12Invoice + + func send(offer: Offer, payerNote: String?) throws -> PaymentId + + func sendUsingAmount(offer: Offer, payerNote: String?, amountMsat: UInt64) throws -> PaymentId } -public class ChannelConfig: ChannelConfigProtocol { +public class Bolt12Payment: + Bolt12PaymentProtocol { fileprivate let pointer: UnsafeMutableRawPointer // TODO: We'd like this to be `private` but for Swifty reasons, @@ -621,151 +664,533 @@ public class ChannelConfig: ChannelConfigProtocol { required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { self.pointer = pointer } - public convenience init() { - self.init(unsafeFromRawPointer: try! rustCall() { - uniffi_ldk_node_fn_constructor_channelconfig_new($0) -}) + + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + return try! rustCall { uniffi_ldk_node_fn_clone_bolt12payment(self.pointer, $0) } } deinit { - try! rustCall { uniffi_ldk_node_fn_free_channelconfig(pointer, $0) } + try! rustCall { uniffi_ldk_node_fn_free_bolt12payment(pointer, $0) } } - - public func acceptUnderpayingHtlcs() -> Bool { - return try! FfiConverterBool.lift( - try! - rustCall() { - - uniffi_ldk_node_fn_method_channelconfig_accept_underpaying_htlcs(self.pointer, $0 + public func initiateRefund(amountMsat: UInt64, expirySecs: UInt32) throws -> Refund { + return try FfiConverterTypeRefund.lift( + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_bolt12payment_initiate_refund(self.uniffiClonePointer(), + FfiConverterUInt64.lower(amountMsat), + FfiConverterUInt32.lower(expirySecs),$0 ) } ) } - - public func cltvExpiryDelta() -> UInt16 { - return try! FfiConverterUInt16.lift( - try! - rustCall() { - - uniffi_ldk_node_fn_method_channelconfig_cltv_expiry_delta(self.pointer, $0 + public func receive(amountMsat: UInt64, description: String) throws -> Offer { + return try FfiConverterTypeOffer.lift( + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_bolt12payment_receive(self.uniffiClonePointer(), + FfiConverterUInt64.lower(amountMsat), + FfiConverterString.lower(description),$0 ) } ) } - - public func forceCloseAvoidanceMaxFeeSatoshis() -> UInt64 { - return try! FfiConverterUInt64.lift( - try! - rustCall() { - - uniffi_ldk_node_fn_method_channelconfig_force_close_avoidance_max_fee_satoshis(self.pointer, $0 + public func receiveVariableAmount(description: String) throws -> Offer { + return try FfiConverterTypeOffer.lift( + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_bolt12payment_receive_variable_amount(self.uniffiClonePointer(), + FfiConverterString.lower(description),$0 ) } ) } - - public func forwardingFeeBaseMsat() -> UInt32 { - return try! FfiConverterUInt32.lift( - try! - rustCall() { - - uniffi_ldk_node_fn_method_channelconfig_forwarding_fee_base_msat(self.pointer, $0 + public func requestRefundPayment(refund: Refund) throws -> Bolt12Invoice { + return try FfiConverterTypeBolt12Invoice.lift( + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_bolt12payment_request_refund_payment(self.uniffiClonePointer(), + FfiConverterTypeRefund.lower(refund),$0 ) } ) } - - public func forwardingFeeProportionalMillionths() -> UInt32 { - return try! FfiConverterUInt32.lift( - try! - rustCall() { - - uniffi_ldk_node_fn_method_channelconfig_forwarding_fee_proportional_millionths(self.pointer, $0 + public func send(offer: Offer, payerNote: String?) throws -> PaymentId { + return try FfiConverterTypePaymentId.lift( + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_bolt12payment_send(self.uniffiClonePointer(), + FfiConverterTypeOffer.lower(offer), + FfiConverterOptionString.lower(payerNote),$0 ) } ) } - - public func setAcceptUnderpayingHtlcs(value: Bool) { - try! - rustCall() { - - uniffi_ldk_node_fn_method_channelconfig_set_accept_underpaying_htlcs(self.pointer, - FfiConverterBool.lower(value),$0 + public func sendUsingAmount(offer: Offer, payerNote: String?, amountMsat: UInt64) throws -> PaymentId { + return try FfiConverterTypePaymentId.lift( + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_bolt12payment_send_using_amount(self.uniffiClonePointer(), + FfiConverterTypeOffer.lower(offer), + FfiConverterOptionString.lower(payerNote), + FfiConverterUInt64.lower(amountMsat),$0 ) } + ) } - public func setCltvExpiryDelta(value: UInt16) { - try! - rustCall() { - - uniffi_ldk_node_fn_method_channelconfig_set_cltv_expiry_delta(self.pointer, - FfiConverterUInt16.lower(value),$0 - ) } - } - public func setForceCloseAvoidanceMaxFeeSatoshis(valueSat: UInt64) { - try! - rustCall() { - - uniffi_ldk_node_fn_method_channelconfig_set_force_close_avoidance_max_fee_satoshis(self.pointer, - FfiConverterUInt64.lower(valueSat),$0 - ) -} +public struct FfiConverterTypeBolt12Payment: FfiConverter { + + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = Bolt12Payment + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> Bolt12Payment { + return Bolt12Payment(unsafeFromRawPointer: pointer) } - public func setForwardingFeeBaseMsat(feeMsat: UInt32) { - try! - rustCall() { - - uniffi_ldk_node_fn_method_channelconfig_set_forwarding_fee_base_msat(self.pointer, - FfiConverterUInt32.lower(feeMsat),$0 - ) -} + public static func lower(_ value: Bolt12Payment) -> UnsafeMutableRawPointer { + return value.uniffiClonePointer() } - public func setForwardingFeeProportionalMillionths(value: UInt32) { - try! - rustCall() { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Bolt12Payment { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: Bolt12Payment, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } +} + + +public func FfiConverterTypeBolt12Payment_lift(_ pointer: UnsafeMutableRawPointer) throws -> Bolt12Payment { + return try FfiConverterTypeBolt12Payment.lift(pointer) +} + +public func FfiConverterTypeBolt12Payment_lower(_ value: Bolt12Payment) -> UnsafeMutableRawPointer { + return FfiConverterTypeBolt12Payment.lower(value) +} + + + + +public protocol BuilderProtocol : AnyObject { - uniffi_ldk_node_fn_method_channelconfig_set_forwarding_fee_proportional_millionths(self.pointer, - FfiConverterUInt32.lower(value),$0 + func build() throws -> Node + + func buildWithFsStore() throws -> Node + + func setEntropyBip39Mnemonic(mnemonic: Mnemonic, passphrase: String?) + + func setEntropySeedBytes(seedBytes: [UInt8]) throws + + func setEntropySeedPath(seedPath: String) + + func setEsploraServer(esploraServerUrl: String) + + func setGossipSourceP2p() + + func setGossipSourceRgs(rgsServerUrl: String) + + func setLiquiditySourceLsps2(address: SocketAddress, nodeId: PublicKey, token: String?) + + func setListeningAddresses(listeningAddresses: [SocketAddress]) throws + + func setNetwork(network: Network) + + func setStorageDirPath(storageDirPath: String) + +} + +public class Builder: + BuilderProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + return try! rustCall { uniffi_ldk_node_fn_clone_builder(self.pointer, $0) } + } + public convenience init() { + self.init(unsafeFromRawPointer: try! rustCall() { + uniffi_ldk_node_fn_constructor_builder_new($0) +}) + } + + deinit { + try! rustCall { uniffi_ldk_node_fn_free_builder(pointer, $0) } + } + + + public static func fromConfig(config: Config) -> Builder { + return Builder(unsafeFromRawPointer: try! rustCall() { + uniffi_ldk_node_fn_constructor_builder_from_config( + FfiConverterTypeConfig.lower(config),$0) +}) + } + + + + + + public func build() throws -> Node { + return try FfiConverterTypeNode.lift( + try + rustCallWithError(FfiConverterTypeBuildError.lift) { + uniffi_ldk_node_fn_method_builder_build(self.uniffiClonePointer(), $0 + ) +} + ) + } + public func buildWithFsStore() throws -> Node { + return try FfiConverterTypeNode.lift( + try + rustCallWithError(FfiConverterTypeBuildError.lift) { + uniffi_ldk_node_fn_method_builder_build_with_fs_store(self.uniffiClonePointer(), $0 + ) +} + ) + } + public func setEntropyBip39Mnemonic(mnemonic: Mnemonic, passphrase: String?) { + try! + rustCall() { + + uniffi_ldk_node_fn_method_builder_set_entropy_bip39_mnemonic(self.uniffiClonePointer(), + FfiConverterTypeMnemonic.lower(mnemonic), + FfiConverterOptionString.lower(passphrase),$0 + ) +} + } + public func setEntropySeedBytes(seedBytes: [UInt8]) throws { + try + rustCallWithError(FfiConverterTypeBuildError.lift) { + uniffi_ldk_node_fn_method_builder_set_entropy_seed_bytes(self.uniffiClonePointer(), + FfiConverterSequenceUInt8.lower(seedBytes),$0 + ) +} + } + public func setEntropySeedPath(seedPath: String) { + try! + rustCall() { + + uniffi_ldk_node_fn_method_builder_set_entropy_seed_path(self.uniffiClonePointer(), + FfiConverterString.lower(seedPath),$0 + ) +} + } + public func setEsploraServer(esploraServerUrl: String) { + try! + rustCall() { + + uniffi_ldk_node_fn_method_builder_set_esplora_server(self.uniffiClonePointer(), + FfiConverterString.lower(esploraServerUrl),$0 + ) +} + } + public func setGossipSourceP2p() { + try! + rustCall() { + + uniffi_ldk_node_fn_method_builder_set_gossip_source_p2p(self.uniffiClonePointer(), $0 + ) +} + } + public func setGossipSourceRgs(rgsServerUrl: String) { + try! + rustCall() { + + uniffi_ldk_node_fn_method_builder_set_gossip_source_rgs(self.uniffiClonePointer(), + FfiConverterString.lower(rgsServerUrl),$0 + ) +} + } + public func setLiquiditySourceLsps2(address: SocketAddress, nodeId: PublicKey, token: String?) { + try! + rustCall() { + + uniffi_ldk_node_fn_method_builder_set_liquidity_source_lsps2(self.uniffiClonePointer(), + FfiConverterTypeSocketAddress.lower(address), + FfiConverterTypePublicKey.lower(nodeId), + FfiConverterOptionString.lower(token),$0 ) } } + public func setListeningAddresses(listeningAddresses: [SocketAddress]) throws { + try + rustCallWithError(FfiConverterTypeBuildError.lift) { + uniffi_ldk_node_fn_method_builder_set_listening_addresses(self.uniffiClonePointer(), + FfiConverterSequenceTypeSocketAddress.lower(listeningAddresses),$0 + ) +} + } + public func setNetwork(network: Network) { + try! + rustCall() { + + uniffi_ldk_node_fn_method_builder_set_network(self.uniffiClonePointer(), + FfiConverterTypeNetwork.lower(network),$0 + ) +} + } + public func setStorageDirPath(storageDirPath: String) { + try! + rustCall() { + + uniffi_ldk_node_fn_method_builder_set_storage_dir_path(self.uniffiClonePointer(), + FfiConverterString.lower(storageDirPath),$0 + ) +} + } + +} + +public struct FfiConverterTypeBuilder: FfiConverter { + + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = Builder + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> Builder { + return Builder(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: Builder) -> UnsafeMutableRawPointer { + return value.uniffiClonePointer() + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Builder { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: Builder, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } +} + + +public func FfiConverterTypeBuilder_lift(_ pointer: UnsafeMutableRawPointer) throws -> Builder { + return try FfiConverterTypeBuilder.lift(pointer) +} +public func FfiConverterTypeBuilder_lower(_ value: Builder) -> UnsafeMutableRawPointer { + return FfiConverterTypeBuilder.lower(value) +} + + + + +public protocol ChannelConfigProtocol : AnyObject { + + func acceptUnderpayingHtlcs() -> Bool + + func cltvExpiryDelta() -> UInt16 + + func forceCloseAvoidanceMaxFeeSatoshis() -> UInt64 + + func forwardingFeeBaseMsat() -> UInt32 + + func forwardingFeeProportionalMillionths() -> UInt32 + + func setAcceptUnderpayingHtlcs(value: Bool) + + func setCltvExpiryDelta(value: UInt16) + + func setForceCloseAvoidanceMaxFeeSatoshis(valueSat: UInt64) + + func setForwardingFeeBaseMsat(feeMsat: UInt32) + + func setForwardingFeeProportionalMillionths(value: UInt32) + + func setMaxDustHtlcExposureFromFeeRateMultiplier(multiplier: UInt64) + + func setMaxDustHtlcExposureFromFixedLimit(limitMsat: UInt64) + +} + +public class ChannelConfig: + ChannelConfigProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + return try! rustCall { uniffi_ldk_node_fn_clone_channelconfig(self.pointer, $0) } + } + public convenience init() { + self.init(unsafeFromRawPointer: try! rustCall() { + uniffi_ldk_node_fn_constructor_channelconfig_new($0) +}) + } + + deinit { + try! rustCall { uniffi_ldk_node_fn_free_channelconfig(pointer, $0) } + } + + + + + + public func acceptUnderpayingHtlcs() -> Bool { + return try! FfiConverterBool.lift( + try! + rustCall() { + + uniffi_ldk_node_fn_method_channelconfig_accept_underpaying_htlcs(self.uniffiClonePointer(), $0 + ) +} + ) + } + public func cltvExpiryDelta() -> UInt16 { + return try! FfiConverterUInt16.lift( + try! + rustCall() { + + uniffi_ldk_node_fn_method_channelconfig_cltv_expiry_delta(self.uniffiClonePointer(), $0 + ) +} + ) + } + public func forceCloseAvoidanceMaxFeeSatoshis() -> UInt64 { + return try! FfiConverterUInt64.lift( + try! + rustCall() { + + uniffi_ldk_node_fn_method_channelconfig_force_close_avoidance_max_fee_satoshis(self.uniffiClonePointer(), $0 + ) +} + ) + } + public func forwardingFeeBaseMsat() -> UInt32 { + return try! FfiConverterUInt32.lift( + try! + rustCall() { + + uniffi_ldk_node_fn_method_channelconfig_forwarding_fee_base_msat(self.uniffiClonePointer(), $0 + ) +} + ) + } + public func forwardingFeeProportionalMillionths() -> UInt32 { + return try! FfiConverterUInt32.lift( + try! + rustCall() { + + uniffi_ldk_node_fn_method_channelconfig_forwarding_fee_proportional_millionths(self.uniffiClonePointer(), $0 + ) +} + ) + } + public func setAcceptUnderpayingHtlcs(value: Bool) { + try! + rustCall() { + + uniffi_ldk_node_fn_method_channelconfig_set_accept_underpaying_htlcs(self.uniffiClonePointer(), + FfiConverterBool.lower(value),$0 + ) +} + } + public func setCltvExpiryDelta(value: UInt16) { + try! + rustCall() { + + uniffi_ldk_node_fn_method_channelconfig_set_cltv_expiry_delta(self.uniffiClonePointer(), + FfiConverterUInt16.lower(value),$0 + ) +} + } + public func setForceCloseAvoidanceMaxFeeSatoshis(valueSat: UInt64) { + try! + rustCall() { + + uniffi_ldk_node_fn_method_channelconfig_set_force_close_avoidance_max_fee_satoshis(self.uniffiClonePointer(), + FfiConverterUInt64.lower(valueSat),$0 + ) +} + } + public func setForwardingFeeBaseMsat(feeMsat: UInt32) { + try! + rustCall() { + + uniffi_ldk_node_fn_method_channelconfig_set_forwarding_fee_base_msat(self.uniffiClonePointer(), + FfiConverterUInt32.lower(feeMsat),$0 + ) +} + } + public func setForwardingFeeProportionalMillionths(value: UInt32) { + try! + rustCall() { + + uniffi_ldk_node_fn_method_channelconfig_set_forwarding_fee_proportional_millionths(self.uniffiClonePointer(), + FfiConverterUInt32.lower(value),$0 + ) +} + } public func setMaxDustHtlcExposureFromFeeRateMultiplier(multiplier: UInt64) { try! rustCall() { - uniffi_ldk_node_fn_method_channelconfig_set_max_dust_htlc_exposure_from_fee_rate_multiplier(self.pointer, + uniffi_ldk_node_fn_method_channelconfig_set_max_dust_htlc_exposure_from_fee_rate_multiplier(self.uniffiClonePointer(), FfiConverterUInt64.lower(multiplier),$0 ) } } - public func setMaxDustHtlcExposureFromFixedLimit(limitMsat: UInt64) { try! rustCall() { - uniffi_ldk_node_fn_method_channelconfig_set_max_dust_htlc_exposure_from_fixed_limit(self.pointer, + uniffi_ldk_node_fn_method_channelconfig_set_max_dust_htlc_exposure_from_fixed_limit(self.uniffiClonePointer(), FfiConverterUInt64.lower(limitMsat),$0 ) } } + } public struct FfiConverterTypeChannelConfig: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer typealias SwiftType = ChannelConfig + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> ChannelConfig { + return ChannelConfig(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: ChannelConfig) -> UnsafeMutableRawPointer { + return value.uniffiClonePointer() + } + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ChannelConfig { let v: UInt64 = try readInt(&buf) // The Rust code won't compile if a pointer won't fit in a UInt64. @@ -782,14 +1207,6 @@ public struct FfiConverterTypeChannelConfig: FfiConverter { // The Rust code won't compile if a pointer won't fit in a `UInt64`. writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) } - - public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> ChannelConfig { - return ChannelConfig(unsafeFromRawPointer: pointer) - } - - public static func lower(_ value: ChannelConfig) -> UnsafeMutableRawPointer { - return value.pointer - } } @@ -801,46 +1218,198 @@ public func FfiConverterTypeChannelConfig_lower(_ value: ChannelConfig) -> Unsaf return FfiConverterTypeChannelConfig.lower(value) } - -public protocol LDKNodeProtocol { - func closeChannel(channelId: ChannelId, counterpartyNodeId: PublicKey) throws - func connect(nodeId: PublicKey, address: SocketAddress, persist: Bool) throws - func connectOpenChannel(nodeId: PublicKey, address: SocketAddress, channelAmountSats: UInt64, pushToCounterpartyMsat: UInt64?, channelConfig: ChannelConfig?, announceChannel: Bool) throws - func disconnect(nodeId: PublicKey) throws - func eventHandled() - func isRunning() -> Bool - func listChannels() -> [ChannelDetails] - func listPayments() -> [PaymentDetails] - func listPeers() -> [PeerDetails] - func listeningAddresses() -> [SocketAddress]? - func newOnchainAddress() throws -> Address - func nextEvent() -> Event? - func nodeId() -> PublicKey - func payment(paymentHash: PaymentHash) -> PaymentDetails? - func receivePayment(amountMsat: UInt64, description: String, expirySecs: UInt32) throws -> Bolt11Invoice - func receiveVariableAmountPayment(description: String, expirySecs: UInt32) throws -> Bolt11Invoice - func removePayment(paymentHash: PaymentHash) throws - func sendAllToOnchainAddress(address: Address) throws -> Txid - func sendPayment(invoice: Bolt11Invoice) throws -> PaymentHash - func sendPaymentProbes(invoice: Bolt11Invoice) throws - func sendPaymentProbesUsingAmount(invoice: Bolt11Invoice, amountMsat: UInt64) throws - func sendPaymentUsingAmount(invoice: Bolt11Invoice, amountMsat: UInt64) throws -> PaymentHash - func sendSpontaneousPayment(amountMsat: UInt64, nodeId: PublicKey) throws -> PaymentHash - func sendSpontaneousPaymentProbes(amountMsat: UInt64, nodeId: PublicKey) throws - func sendToOnchainAddress(address: Address, amountMsat: UInt64) throws -> Txid - func signMessage(msg: [UInt8]) throws -> String - func spendableOnchainBalanceSats() throws -> UInt64 - func start() throws - func stop() throws - func syncWallets() throws - func totalOnchainBalanceSats() throws -> UInt64 - func updateChannelConfig(channelId: ChannelId, counterpartyNodeId: PublicKey, channelConfig: ChannelConfig) throws - func verifySignature(msg: [UInt8], sig: String, pkey: PublicKey) -> Bool - func waitNextEvent() -> Event - -} - -public class LdkNode: LDKNodeProtocol { + + + +public protocol NetworkGraphProtocol : AnyObject { + + func channel(shortChannelId: UInt64) -> ChannelInfo? + + func listChannels() -> [UInt64] + + func listNodes() -> [NodeId] + + func node(nodeId: NodeId) -> NodeInfo? + +} + +public class NetworkGraph: + NetworkGraphProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + return try! rustCall { uniffi_ldk_node_fn_clone_networkgraph(self.pointer, $0) } + } + + deinit { + try! rustCall { uniffi_ldk_node_fn_free_networkgraph(pointer, $0) } + } + + + + + + public func channel(shortChannelId: UInt64) -> ChannelInfo? { + return try! FfiConverterOptionTypeChannelInfo.lift( + try! + rustCall() { + + uniffi_ldk_node_fn_method_networkgraph_channel(self.uniffiClonePointer(), + FfiConverterUInt64.lower(shortChannelId),$0 + ) +} + ) + } + public func listChannels() -> [UInt64] { + return try! FfiConverterSequenceUInt64.lift( + try! + rustCall() { + + uniffi_ldk_node_fn_method_networkgraph_list_channels(self.uniffiClonePointer(), $0 + ) +} + ) + } + public func listNodes() -> [NodeId] { + return try! FfiConverterSequenceTypeNodeId.lift( + try! + rustCall() { + + uniffi_ldk_node_fn_method_networkgraph_list_nodes(self.uniffiClonePointer(), $0 + ) +} + ) + } + public func node(nodeId: NodeId) -> NodeInfo? { + return try! FfiConverterOptionTypeNodeInfo.lift( + try! + rustCall() { + + uniffi_ldk_node_fn_method_networkgraph_node(self.uniffiClonePointer(), + FfiConverterTypeNodeId.lower(nodeId),$0 + ) +} + ) + } + +} + +public struct FfiConverterTypeNetworkGraph: FfiConverter { + + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = NetworkGraph + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> NetworkGraph { + return NetworkGraph(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: NetworkGraph) -> UnsafeMutableRawPointer { + return value.uniffiClonePointer() + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> NetworkGraph { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: NetworkGraph, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } +} + + +public func FfiConverterTypeNetworkGraph_lift(_ pointer: UnsafeMutableRawPointer) throws -> NetworkGraph { + return try FfiConverterTypeNetworkGraph.lift(pointer) +} + +public func FfiConverterTypeNetworkGraph_lower(_ value: NetworkGraph) -> UnsafeMutableRawPointer { + return FfiConverterTypeNetworkGraph.lower(value) +} + + + + +public protocol NodeProtocol : AnyObject { + + func bolt11Payment() -> Bolt11Payment + + func bolt12Payment() -> Bolt12Payment + + func closeChannel(userChannelId: UserChannelId, counterpartyNodeId: PublicKey) throws + + func config() -> Config + + func connect(nodeId: PublicKey, address: SocketAddress, persist: Bool) throws + + func connectOpenChannel(nodeId: PublicKey, address: SocketAddress, channelAmountSats: UInt64, pushToCounterpartyMsat: UInt64?, channelConfig: ChannelConfig?, announceChannel: Bool) throws -> UserChannelId + + func disconnect(nodeId: PublicKey) throws + + func eventHandled() + + func forceCloseChannel(userChannelId: UserChannelId, counterpartyNodeId: PublicKey) throws + + func listBalances() -> BalanceDetails + + func listChannels() -> [ChannelDetails] + + func listPayments() -> [PaymentDetails] + + func listPeers() -> [PeerDetails] + + func listeningAddresses() -> [SocketAddress]? + + func networkGraph() -> NetworkGraph + + func nextEvent() -> Event? + + func nextEventAsync() async -> Event + + func nodeId() -> PublicKey + + func onchainPayment() -> OnchainPayment + + func payment(paymentId: PaymentId) -> PaymentDetails? + + func removePayment(paymentId: PaymentId) throws + + func signMessage(msg: [UInt8]) throws -> String + + func spontaneousPayment() -> SpontaneousPayment + + func start() throws + + func status() -> NodeStatus + + func stop() throws + + func syncWallets() throws + + func updateChannelConfig(userChannelId: UserChannelId, counterpartyNodeId: PublicKey, channelConfig: ChannelConfig) throws + + func verifySignature(msg: [UInt8], sig: String, pkey: PublicKey) -> Bool + + func waitNextEvent() -> Event + +} + +public class Node: + NodeProtocol { fileprivate let pointer: UnsafeMutableRawPointer // TODO: We'd like this to be `private` but for Swifty reasons, @@ -850,40 +1419,72 @@ public class LdkNode: LDKNodeProtocol { self.pointer = pointer } + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + return try! rustCall { uniffi_ldk_node_fn_clone_node(self.pointer, $0) } + } + deinit { - try! rustCall { uniffi_ldk_node_fn_free_ldknode(pointer, $0) } + try! rustCall { uniffi_ldk_node_fn_free_node(pointer, $0) } } - - public func closeChannel(channelId: ChannelId, counterpartyNodeId: PublicKey) throws { + public func bolt11Payment() -> Bolt11Payment { + return try! FfiConverterTypeBolt11Payment.lift( + try! + rustCall() { + + uniffi_ldk_node_fn_method_node_bolt11_payment(self.uniffiClonePointer(), $0 + ) +} + ) + } + public func bolt12Payment() -> Bolt12Payment { + return try! FfiConverterTypeBolt12Payment.lift( + try! + rustCall() { + + uniffi_ldk_node_fn_method_node_bolt12_payment(self.uniffiClonePointer(), $0 + ) +} + ) + } + public func closeChannel(userChannelId: UserChannelId, counterpartyNodeId: PublicKey) throws { try rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_close_channel(self.pointer, - FfiConverterTypeChannelId.lower(channelId), + uniffi_ldk_node_fn_method_node_close_channel(self.uniffiClonePointer(), + FfiConverterTypeUserChannelId.lower(userChannelId), FfiConverterTypePublicKey.lower(counterpartyNodeId),$0 ) } } - - public func connect(nodeId: PublicKey, address: SocketAddress, persist: Bool) throws { + public func config() -> Config { + return try! FfiConverterTypeConfig.lift( + try! + rustCall() { + + uniffi_ldk_node_fn_method_node_config(self.uniffiClonePointer(), $0 + ) +} + ) + } + public func connect(nodeId: PublicKey, address: SocketAddress, persist: Bool) throws { try rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_connect(self.pointer, + uniffi_ldk_node_fn_method_node_connect(self.uniffiClonePointer(), FfiConverterTypePublicKey.lower(nodeId), FfiConverterTypeSocketAddress.lower(address), FfiConverterBool.lower(persist),$0 ) } } - - public func connectOpenChannel(nodeId: PublicKey, address: SocketAddress, channelAmountSats: UInt64, pushToCounterpartyMsat: UInt64?, channelConfig: ChannelConfig?, announceChannel: Bool) throws { - try + public func connectOpenChannel(nodeId: PublicKey, address: SocketAddress, channelAmountSats: UInt64, pushToCounterpartyMsat: UInt64?, channelConfig: ChannelConfig?, announceChannel: Bool) throws -> UserChannelId { + return try FfiConverterTypeUserChannelId.lift( + try rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_connect_open_channel(self.pointer, + uniffi_ldk_node_fn_method_node_connect_open_channel(self.uniffiClonePointer(), FfiConverterTypePublicKey.lower(nodeId), FfiConverterTypeSocketAddress.lower(address), FfiConverterUInt64.lower(channelAmountSats), @@ -892,375 +1493,700 @@ public class LdkNode: LDKNodeProtocol { FfiConverterBool.lower(announceChannel),$0 ) } + ) } - - public func disconnect(nodeId: PublicKey) throws { + public func disconnect(nodeId: PublicKey) throws { try rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_disconnect(self.pointer, + uniffi_ldk_node_fn_method_node_disconnect(self.uniffiClonePointer(), FfiConverterTypePublicKey.lower(nodeId),$0 ) } } - public func eventHandled() { try! rustCall() { - uniffi_ldk_node_fn_method_ldknode_event_handled(self.pointer, $0 + uniffi_ldk_node_fn_method_node_event_handled(self.uniffiClonePointer(), $0 ) } } - - public func isRunning() -> Bool { - return try! FfiConverterBool.lift( + public func forceCloseChannel(userChannelId: UserChannelId, counterpartyNodeId: PublicKey) throws { + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_node_force_close_channel(self.uniffiClonePointer(), + FfiConverterTypeUserChannelId.lower(userChannelId), + FfiConverterTypePublicKey.lower(counterpartyNodeId),$0 + ) +} + } + public func listBalances() -> BalanceDetails { + return try! FfiConverterTypeBalanceDetails.lift( try! rustCall() { - uniffi_ldk_node_fn_method_ldknode_is_running(self.pointer, $0 + uniffi_ldk_node_fn_method_node_list_balances(self.uniffiClonePointer(), $0 ) } ) } - public func listChannels() -> [ChannelDetails] { return try! FfiConverterSequenceTypeChannelDetails.lift( try! rustCall() { - uniffi_ldk_node_fn_method_ldknode_list_channels(self.pointer, $0 + uniffi_ldk_node_fn_method_node_list_channels(self.uniffiClonePointer(), $0 ) } ) } - public func listPayments() -> [PaymentDetails] { return try! FfiConverterSequenceTypePaymentDetails.lift( try! rustCall() { - uniffi_ldk_node_fn_method_ldknode_list_payments(self.pointer, $0 + uniffi_ldk_node_fn_method_node_list_payments(self.uniffiClonePointer(), $0 ) } ) } - public func listPeers() -> [PeerDetails] { return try! FfiConverterSequenceTypePeerDetails.lift( try! rustCall() { - uniffi_ldk_node_fn_method_ldknode_list_peers(self.pointer, $0 + uniffi_ldk_node_fn_method_node_list_peers(self.uniffiClonePointer(), $0 ) } ) } - public func listeningAddresses() -> [SocketAddress]? { return try! FfiConverterOptionSequenceTypeSocketAddress.lift( try! rustCall() { - uniffi_ldk_node_fn_method_ldknode_listening_addresses(self.pointer, $0 + uniffi_ldk_node_fn_method_node_listening_addresses(self.uniffiClonePointer(), $0 ) } ) } - - public func newOnchainAddress() throws -> Address { - return try FfiConverterTypeAddress.lift( - try - rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_new_onchain_address(self.pointer, $0 + public func networkGraph() -> NetworkGraph { + return try! FfiConverterTypeNetworkGraph.lift( + try! + rustCall() { + + uniffi_ldk_node_fn_method_node_network_graph(self.uniffiClonePointer(), $0 ) } ) } - public func nextEvent() -> Event? { return try! FfiConverterOptionTypeEvent.lift( try! rustCall() { - uniffi_ldk_node_fn_method_ldknode_next_event(self.pointer, $0 + uniffi_ldk_node_fn_method_node_next_event(self.uniffiClonePointer(), $0 ) } ) } + public func nextEventAsync() async -> Event { + return try! await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_ldk_node_fn_method_node_next_event_async( + self.uniffiClonePointer() + ) + }, + pollFunc: ffi_ldk_node_rust_future_poll_rust_buffer, + completeFunc: ffi_ldk_node_rust_future_complete_rust_buffer, + freeFunc: ffi_ldk_node_rust_future_free_rust_buffer, + liftFunc: FfiConverterTypeEvent.lift, + errorHandler: nil + + ) + } + public func nodeId() -> PublicKey { return try! FfiConverterTypePublicKey.lift( try! rustCall() { - uniffi_ldk_node_fn_method_ldknode_node_id(self.pointer, $0 + uniffi_ldk_node_fn_method_node_node_id(self.uniffiClonePointer(), $0 ) } ) } - - public func payment(paymentHash: PaymentHash) -> PaymentDetails? { + public func onchainPayment() -> OnchainPayment { + return try! FfiConverterTypeOnchainPayment.lift( + try! + rustCall() { + + uniffi_ldk_node_fn_method_node_onchain_payment(self.uniffiClonePointer(), $0 + ) +} + ) + } + public func payment(paymentId: PaymentId) -> PaymentDetails? { return try! FfiConverterOptionTypePaymentDetails.lift( try! rustCall() { - uniffi_ldk_node_fn_method_ldknode_payment(self.pointer, - FfiConverterTypePaymentHash.lower(paymentHash),$0 + uniffi_ldk_node_fn_method_node_payment(self.uniffiClonePointer(), + FfiConverterTypePaymentId.lower(paymentId),$0 ) } ) } - - public func receivePayment(amountMsat: UInt64, description: String, expirySecs: UInt32) throws -> Bolt11Invoice { - return try FfiConverterTypeBolt11Invoice.lift( + public func removePayment(paymentId: PaymentId) throws { + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_node_remove_payment(self.uniffiClonePointer(), + FfiConverterTypePaymentId.lower(paymentId),$0 + ) +} + } + public func signMessage(msg: [UInt8]) throws -> String { + return try FfiConverterString.lift( try rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_receive_payment(self.pointer, - FfiConverterUInt64.lower(amountMsat), - FfiConverterString.lower(description), - FfiConverterUInt32.lower(expirySecs),$0 + uniffi_ldk_node_fn_method_node_sign_message(self.uniffiClonePointer(), + FfiConverterSequenceUInt8.lower(msg),$0 ) } ) } - - public func receiveVariableAmountPayment(description: String, expirySecs: UInt32) throws -> Bolt11Invoice { - return try FfiConverterTypeBolt11Invoice.lift( - try + public func spontaneousPayment() -> SpontaneousPayment { + return try! FfiConverterTypeSpontaneousPayment.lift( + try! + rustCall() { + + uniffi_ldk_node_fn_method_node_spontaneous_payment(self.uniffiClonePointer(), $0 + ) +} + ) + } + public func start() throws { + try rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_receive_variable_amount_payment(self.pointer, - FfiConverterString.lower(description), - FfiConverterUInt32.lower(expirySecs),$0 + uniffi_ldk_node_fn_method_node_start(self.uniffiClonePointer(), $0 + ) +} + } + public func status() -> NodeStatus { + return try! FfiConverterTypeNodeStatus.lift( + try! + rustCall() { + + uniffi_ldk_node_fn_method_node_status(self.uniffiClonePointer(), $0 ) } ) } - - public func removePayment(paymentHash: PaymentHash) throws { + public func stop() throws { try rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_remove_payment(self.pointer, - FfiConverterTypePaymentHash.lower(paymentHash),$0 + uniffi_ldk_node_fn_method_node_stop(self.uniffiClonePointer(), $0 + ) +} + } + public func syncWallets() throws { + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_node_sync_wallets(self.uniffiClonePointer(), $0 ) } } + public func updateChannelConfig(userChannelId: UserChannelId, counterpartyNodeId: PublicKey, channelConfig: ChannelConfig) throws { + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_node_update_channel_config(self.uniffiClonePointer(), + FfiConverterTypeUserChannelId.lower(userChannelId), + FfiConverterTypePublicKey.lower(counterpartyNodeId), + FfiConverterTypeChannelConfig.lower(channelConfig),$0 + ) +} + } + public func verifySignature(msg: [UInt8], sig: String, pkey: PublicKey) -> Bool { + return try! FfiConverterBool.lift( + try! + rustCall() { + + uniffi_ldk_node_fn_method_node_verify_signature(self.uniffiClonePointer(), + FfiConverterSequenceUInt8.lower(msg), + FfiConverterString.lower(sig), + FfiConverterTypePublicKey.lower(pkey),$0 + ) +} + ) + } + public func waitNextEvent() -> Event { + return try! FfiConverterTypeEvent.lift( + try! + rustCall() { + + uniffi_ldk_node_fn_method_node_wait_next_event(self.uniffiClonePointer(), $0 + ) +} + ) + } + +} + +public struct FfiConverterTypeNode: FfiConverter { + + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = Node + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> Node { + return Node(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: Node) -> UnsafeMutableRawPointer { + return value.uniffiClonePointer() + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Node { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: Node, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } +} + + +public func FfiConverterTypeNode_lift(_ pointer: UnsafeMutableRawPointer) throws -> Node { + return try FfiConverterTypeNode.lift(pointer) +} + +public func FfiConverterTypeNode_lower(_ value: Node) -> UnsafeMutableRawPointer { + return FfiConverterTypeNode.lower(value) +} + + + + +public protocol OnchainPaymentProtocol : AnyObject { + + func newAddress() throws -> Address + + func sendAllToAddress(address: Address) throws -> Txid + + func sendToAddress(address: Address, amountMsat: UInt64) throws -> Txid + +} + +public class OnchainPayment: + OnchainPaymentProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + return try! rustCall { uniffi_ldk_node_fn_clone_onchainpayment(self.pointer, $0) } + } + + deinit { + try! rustCall { uniffi_ldk_node_fn_free_onchainpayment(pointer, $0) } + } + + - public func sendAllToOnchainAddress(address: Address) throws -> Txid { + + + public func newAddress() throws -> Address { + return try FfiConverterTypeAddress.lift( + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_onchainpayment_new_address(self.uniffiClonePointer(), $0 + ) +} + ) + } + public func sendAllToAddress(address: Address) throws -> Txid { return try FfiConverterTypeTxid.lift( try rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_send_all_to_onchain_address(self.pointer, + uniffi_ldk_node_fn_method_onchainpayment_send_all_to_address(self.uniffiClonePointer(), FfiConverterTypeAddress.lower(address),$0 ) } ) } + public func sendToAddress(address: Address, amountMsat: UInt64) throws -> Txid { + return try FfiConverterTypeTxid.lift( + try + rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_method_onchainpayment_send_to_address(self.uniffiClonePointer(), + FfiConverterTypeAddress.lower(address), + FfiConverterUInt64.lower(amountMsat),$0 + ) +} + ) + } + +} + +public struct FfiConverterTypeOnchainPayment: FfiConverter { + + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = OnchainPayment + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> OnchainPayment { + return OnchainPayment(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: OnchainPayment) -> UnsafeMutableRawPointer { + return value.uniffiClonePointer() + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> OnchainPayment { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: OnchainPayment, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } +} + + +public func FfiConverterTypeOnchainPayment_lift(_ pointer: UnsafeMutableRawPointer) throws -> OnchainPayment { + return try FfiConverterTypeOnchainPayment.lift(pointer) +} - public func sendPayment(invoice: Bolt11Invoice) throws -> PaymentHash { - return try FfiConverterTypePaymentHash.lift( - try - rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_send_payment(self.pointer, - FfiConverterTypeBolt11Invoice.lower(invoice),$0 - ) +public func FfiConverterTypeOnchainPayment_lower(_ value: OnchainPayment) -> UnsafeMutableRawPointer { + return FfiConverterTypeOnchainPayment.lower(value) } - ) - } - public func sendPaymentProbes(invoice: Bolt11Invoice) throws { - try - rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_send_payment_probes(self.pointer, - FfiConverterTypeBolt11Invoice.lower(invoice),$0 - ) + + + +public protocol SpontaneousPaymentProtocol : AnyObject { + + func send(amountMsat: UInt64, nodeId: PublicKey) throws -> PaymentId + + func sendProbes(amountMsat: UInt64, nodeId: PublicKey) throws + } + +public class SpontaneousPayment: + SpontaneousPaymentProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer } - public func sendPaymentProbesUsingAmount(invoice: Bolt11Invoice, amountMsat: UInt64) throws { - try - rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_send_payment_probes_using_amount(self.pointer, - FfiConverterTypeBolt11Invoice.lower(invoice), - FfiConverterUInt64.lower(amountMsat),$0 - ) -} + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + return try! rustCall { uniffi_ldk_node_fn_clone_spontaneouspayment(self.pointer, $0) } } - public func sendPaymentUsingAmount(invoice: Bolt11Invoice, amountMsat: UInt64) throws -> PaymentHash { - return try FfiConverterTypePaymentHash.lift( - try - rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_send_payment_using_amount(self.pointer, - FfiConverterTypeBolt11Invoice.lower(invoice), - FfiConverterUInt64.lower(amountMsat),$0 - ) -} - ) + deinit { + try! rustCall { uniffi_ldk_node_fn_free_spontaneouspayment(pointer, $0) } } - public func sendSpontaneousPayment(amountMsat: UInt64, nodeId: PublicKey) throws -> PaymentHash { - return try FfiConverterTypePaymentHash.lift( + + + + + public func send(amountMsat: UInt64, nodeId: PublicKey) throws -> PaymentId { + return try FfiConverterTypePaymentId.lift( try rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_send_spontaneous_payment(self.pointer, + uniffi_ldk_node_fn_method_spontaneouspayment_send(self.uniffiClonePointer(), FfiConverterUInt64.lower(amountMsat), FfiConverterTypePublicKey.lower(nodeId),$0 ) } ) } - - public func sendSpontaneousPaymentProbes(amountMsat: UInt64, nodeId: PublicKey) throws { + public func sendProbes(amountMsat: UInt64, nodeId: PublicKey) throws { try rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_send_spontaneous_payment_probes(self.pointer, + uniffi_ldk_node_fn_method_spontaneouspayment_send_probes(self.uniffiClonePointer(), FfiConverterUInt64.lower(amountMsat), FfiConverterTypePublicKey.lower(nodeId),$0 ) } } - public func sendToOnchainAddress(address: Address, amountMsat: UInt64) throws -> Txid { - return try FfiConverterTypeTxid.lift( - try - rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_send_to_onchain_address(self.pointer, - FfiConverterTypeAddress.lower(address), - FfiConverterUInt64.lower(amountMsat),$0 - ) } - ) + +public struct FfiConverterTypeSpontaneousPayment: FfiConverter { + + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = SpontaneousPayment + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> SpontaneousPayment { + return SpontaneousPayment(unsafeFromRawPointer: pointer) } - public func signMessage(msg: [UInt8]) throws -> String { - return try FfiConverterString.lift( - try - rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_sign_message(self.pointer, - FfiConverterSequenceUInt8.lower(msg),$0 - ) -} - ) + public static func lower(_ value: SpontaneousPayment) -> UnsafeMutableRawPointer { + return value.uniffiClonePointer() } - public func spendableOnchainBalanceSats() throws -> UInt64 { - return try FfiConverterUInt64.lift( - try - rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_spendable_onchain_balance_sats(self.pointer, $0 - ) -} - ) + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SpontaneousPayment { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) } - public func start() throws { - try - rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_start(self.pointer, $0 - ) -} + public static func write(_ value: SpontaneousPayment, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) } +} - public func stop() throws { - try - rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_stop(self.pointer, $0 - ) + +public func FfiConverterTypeSpontaneousPayment_lift(_ pointer: UnsafeMutableRawPointer) throws -> SpontaneousPayment { + return try FfiConverterTypeSpontaneousPayment.lift(pointer) } - } - public func syncWallets() throws { - try - rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_sync_wallets(self.pointer, $0 - ) +public func FfiConverterTypeSpontaneousPayment_lower(_ value: SpontaneousPayment) -> UnsafeMutableRawPointer { + return FfiConverterTypeSpontaneousPayment.lower(value) +} + + +public struct AnchorChannelsConfig { + public var trustedPeersNoReserve: [PublicKey] + public var perChannelReserveSats: UInt64 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + trustedPeersNoReserve: [PublicKey], + perChannelReserveSats: UInt64) { + self.trustedPeersNoReserve = trustedPeersNoReserve + self.perChannelReserveSats = perChannelReserveSats + } } + + +extension AnchorChannelsConfig: Equatable, Hashable { + public static func ==(lhs: AnchorChannelsConfig, rhs: AnchorChannelsConfig) -> Bool { + if lhs.trustedPeersNoReserve != rhs.trustedPeersNoReserve { + return false + } + if lhs.perChannelReserveSats != rhs.perChannelReserveSats { + return false + } + return true } - public func totalOnchainBalanceSats() throws -> UInt64 { - return try FfiConverterUInt64.lift( - try - rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_total_onchain_balance_sats(self.pointer, $0 - ) + public func hash(into hasher: inout Hasher) { + hasher.combine(trustedPeersNoReserve) + hasher.combine(perChannelReserveSats) + } } + + +public struct FfiConverterTypeAnchorChannelsConfig: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AnchorChannelsConfig { + return + try AnchorChannelsConfig( + trustedPeersNoReserve: FfiConverterSequenceTypePublicKey.read(from: &buf), + perChannelReserveSats: FfiConverterUInt64.read(from: &buf) ) } - public func updateChannelConfig(channelId: ChannelId, counterpartyNodeId: PublicKey, channelConfig: ChannelConfig) throws { - try - rustCallWithError(FfiConverterTypeNodeError.lift) { - uniffi_ldk_node_fn_method_ldknode_update_channel_config(self.pointer, - FfiConverterTypeChannelId.lower(channelId), - FfiConverterTypePublicKey.lower(counterpartyNodeId), - FfiConverterTypeChannelConfig.lower(channelConfig),$0 - ) -} + public static func write(_ value: AnchorChannelsConfig, into buf: inout [UInt8]) { + FfiConverterSequenceTypePublicKey.write(value.trustedPeersNoReserve, into: &buf) + FfiConverterUInt64.write(value.perChannelReserveSats, into: &buf) } +} - public func verifySignature(msg: [UInt8], sig: String, pkey: PublicKey) -> Bool { - return try! FfiConverterBool.lift( - try! - rustCall() { - - uniffi_ldk_node_fn_method_ldknode_verify_signature(self.pointer, - FfiConverterSequenceUInt8.lower(msg), - FfiConverterString.lower(sig), - FfiConverterTypePublicKey.lower(pkey),$0 - ) + +public func FfiConverterTypeAnchorChannelsConfig_lift(_ buf: RustBuffer) throws -> AnchorChannelsConfig { + return try FfiConverterTypeAnchorChannelsConfig.lift(buf) +} + +public func FfiConverterTypeAnchorChannelsConfig_lower(_ value: AnchorChannelsConfig) -> RustBuffer { + return FfiConverterTypeAnchorChannelsConfig.lower(value) } + + +public struct BalanceDetails { + public var totalOnchainBalanceSats: UInt64 + public var spendableOnchainBalanceSats: UInt64 + public var totalAnchorChannelsReserveSats: UInt64 + public var totalLightningBalanceSats: UInt64 + public var lightningBalances: [LightningBalance] + public var pendingBalancesFromChannelClosures: [PendingSweepBalance] + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + totalOnchainBalanceSats: UInt64, + spendableOnchainBalanceSats: UInt64, + totalAnchorChannelsReserveSats: UInt64, + totalLightningBalanceSats: UInt64, + lightningBalances: [LightningBalance], + pendingBalancesFromChannelClosures: [PendingSweepBalance]) { + self.totalOnchainBalanceSats = totalOnchainBalanceSats + self.spendableOnchainBalanceSats = spendableOnchainBalanceSats + self.totalAnchorChannelsReserveSats = totalAnchorChannelsReserveSats + self.totalLightningBalanceSats = totalLightningBalanceSats + self.lightningBalances = lightningBalances + self.pendingBalancesFromChannelClosures = pendingBalancesFromChannelClosures + } +} + + +extension BalanceDetails: Equatable, Hashable { + public static func ==(lhs: BalanceDetails, rhs: BalanceDetails) -> Bool { + if lhs.totalOnchainBalanceSats != rhs.totalOnchainBalanceSats { + return false + } + if lhs.spendableOnchainBalanceSats != rhs.spendableOnchainBalanceSats { + return false + } + if lhs.totalAnchorChannelsReserveSats != rhs.totalAnchorChannelsReserveSats { + return false + } + if lhs.totalLightningBalanceSats != rhs.totalLightningBalanceSats { + return false + } + if lhs.lightningBalances != rhs.lightningBalances { + return false + } + if lhs.pendingBalancesFromChannelClosures != rhs.pendingBalancesFromChannelClosures { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(totalOnchainBalanceSats) + hasher.combine(spendableOnchainBalanceSats) + hasher.combine(totalAnchorChannelsReserveSats) + hasher.combine(totalLightningBalanceSats) + hasher.combine(lightningBalances) + hasher.combine(pendingBalancesFromChannelClosures) + } +} + + +public struct FfiConverterTypeBalanceDetails: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BalanceDetails { + return + try BalanceDetails( + totalOnchainBalanceSats: FfiConverterUInt64.read(from: &buf), + spendableOnchainBalanceSats: FfiConverterUInt64.read(from: &buf), + totalAnchorChannelsReserveSats: FfiConverterUInt64.read(from: &buf), + totalLightningBalanceSats: FfiConverterUInt64.read(from: &buf), + lightningBalances: FfiConverterSequenceTypeLightningBalance.read(from: &buf), + pendingBalancesFromChannelClosures: FfiConverterSequenceTypePendingSweepBalance.read(from: &buf) ) } - public func waitNextEvent() -> Event { - return try! FfiConverterTypeEvent.lift( - try! - rustCall() { - - uniffi_ldk_node_fn_method_ldknode_wait_next_event(self.pointer, $0 - ) + public static func write(_ value: BalanceDetails, into buf: inout [UInt8]) { + FfiConverterUInt64.write(value.totalOnchainBalanceSats, into: &buf) + FfiConverterUInt64.write(value.spendableOnchainBalanceSats, into: &buf) + FfiConverterUInt64.write(value.totalAnchorChannelsReserveSats, into: &buf) + FfiConverterUInt64.write(value.totalLightningBalanceSats, into: &buf) + FfiConverterSequenceTypeLightningBalance.write(value.lightningBalances, into: &buf) + FfiConverterSequenceTypePendingSweepBalance.write(value.pendingBalancesFromChannelClosures, into: &buf) + } } - ) + + +public func FfiConverterTypeBalanceDetails_lift(_ buf: RustBuffer) throws -> BalanceDetails { + return try FfiConverterTypeBalanceDetails.lift(buf) +} + +public func FfiConverterTypeBalanceDetails_lower(_ value: BalanceDetails) -> RustBuffer { + return FfiConverterTypeBalanceDetails.lower(value) +} + + +public struct BestBlock { + public var blockHash: BlockHash + public var height: UInt32 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + blockHash: BlockHash, + height: UInt32) { + self.blockHash = blockHash + self.height = height } } -public struct FfiConverterTypeLDKNode: FfiConverter { - typealias FfiType = UnsafeMutableRawPointer - typealias SwiftType = LdkNode - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> LdkNode { - let v: UInt64 = try readInt(&buf) - // The Rust code won't compile if a pointer won't fit in a UInt64. - // We have to go via `UInt` because that's the thing that's the size of a pointer. - let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) - if (ptr == nil) { - throw UniffiInternalError.unexpectedNullPointer +extension BestBlock: Equatable, Hashable { + public static func ==(lhs: BestBlock, rhs: BestBlock) -> Bool { + if lhs.blockHash != rhs.blockHash { + return false } - return try lift(ptr!) + if lhs.height != rhs.height { + return false + } + return true } - public static func write(_ value: LdkNode, into buf: inout [UInt8]) { - // This fiddling is because `Int` is the thing that's the same size as a pointer. - // The Rust code won't compile if a pointer won't fit in a `UInt64`. - writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + public func hash(into hasher: inout Hasher) { + hasher.combine(blockHash) + hasher.combine(height) } +} + - public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> LdkNode { - return LdkNode(unsafeFromRawPointer: pointer) +public struct FfiConverterTypeBestBlock: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BestBlock { + return + try BestBlock( + blockHash: FfiConverterTypeBlockHash.read(from: &buf), + height: FfiConverterUInt32.read(from: &buf) + ) } - public static func lower(_ value: LdkNode) -> UnsafeMutableRawPointer { - return value.pointer + public static func write(_ value: BestBlock, into buf: inout [UInt8]) { + FfiConverterTypeBlockHash.write(value.blockHash, into: &buf) + FfiConverterUInt32.write(value.height, into: &buf) } } -public func FfiConverterTypeLDKNode_lift(_ pointer: UnsafeMutableRawPointer) throws -> LdkNode { - return try FfiConverterTypeLDKNode.lift(pointer) +public func FfiConverterTypeBestBlock_lift(_ buf: RustBuffer) throws -> BestBlock { + return try FfiConverterTypeBestBlock.lift(buf) } -public func FfiConverterTypeLDKNode_lower(_ value: LdkNode) -> UnsafeMutableRawPointer { - return FfiConverterTypeLDKNode.lower(value) +public func FfiConverterTypeBestBlock_lower(_ value: BestBlock) -> RustBuffer { + return FfiConverterTypeBestBlock.lower(value) } @@ -1272,7 +2198,6 @@ public struct ChannelDetails { public var unspendablePunishmentReserve: UInt64? public var userChannelId: UserChannelId public var feerateSatPer1000Weight: UInt32 - public var balanceMsat: UInt64 public var outboundCapacityMsat: UInt64 public var inboundCapacityMsat: UInt64 public var confirmationsRequired: UInt32? @@ -1297,7 +2222,35 @@ public struct ChannelDetails { // Default memberwise initializers are never public by default, so we // declare one manually. - public init(channelId: ChannelId, counterpartyNodeId: PublicKey, fundingTxo: OutPoint?, channelValueSats: UInt64, unspendablePunishmentReserve: UInt64?, userChannelId: UserChannelId, feerateSatPer1000Weight: UInt32, balanceMsat: UInt64, outboundCapacityMsat: UInt64, inboundCapacityMsat: UInt64, confirmationsRequired: UInt32?, confirmations: UInt32?, isOutbound: Bool, isChannelReady: Bool, isUsable: Bool, isPublic: Bool, cltvExpiryDelta: UInt16?, counterpartyUnspendablePunishmentReserve: UInt64, counterpartyOutboundHtlcMinimumMsat: UInt64?, counterpartyOutboundHtlcMaximumMsat: UInt64?, counterpartyForwardingInfoFeeBaseMsat: UInt32?, counterpartyForwardingInfoFeeProportionalMillionths: UInt32?, counterpartyForwardingInfoCltvExpiryDelta: UInt16?, nextOutboundHtlcLimitMsat: UInt64, nextOutboundHtlcMinimumMsat: UInt64, forceCloseSpendDelay: UInt16?, inboundHtlcMinimumMsat: UInt64, inboundHtlcMaximumMsat: UInt64?, config: ChannelConfig) { + public init( + channelId: ChannelId, + counterpartyNodeId: PublicKey, + fundingTxo: OutPoint?, + channelValueSats: UInt64, + unspendablePunishmentReserve: UInt64?, + userChannelId: UserChannelId, + feerateSatPer1000Weight: UInt32, + outboundCapacityMsat: UInt64, + inboundCapacityMsat: UInt64, + confirmationsRequired: UInt32?, + confirmations: UInt32?, + isOutbound: Bool, + isChannelReady: Bool, + isUsable: Bool, + isPublic: Bool, + cltvExpiryDelta: UInt16?, + counterpartyUnspendablePunishmentReserve: UInt64, + counterpartyOutboundHtlcMinimumMsat: UInt64?, + counterpartyOutboundHtlcMaximumMsat: UInt64?, + counterpartyForwardingInfoFeeBaseMsat: UInt32?, + counterpartyForwardingInfoFeeProportionalMillionths: UInt32?, + counterpartyForwardingInfoCltvExpiryDelta: UInt16?, + nextOutboundHtlcLimitMsat: UInt64, + nextOutboundHtlcMinimumMsat: UInt64, + forceCloseSpendDelay: UInt16?, + inboundHtlcMinimumMsat: UInt64, + inboundHtlcMaximumMsat: UInt64?, + config: ChannelConfig) { self.channelId = channelId self.counterpartyNodeId = counterpartyNodeId self.fundingTxo = fundingTxo @@ -1305,7 +2258,6 @@ public struct ChannelDetails { self.unspendablePunishmentReserve = unspendablePunishmentReserve self.userChannelId = userChannelId self.feerateSatPer1000Weight = feerateSatPer1000Weight - self.balanceMsat = balanceMsat self.outboundCapacityMsat = outboundCapacityMsat self.inboundCapacityMsat = inboundCapacityMsat self.confirmationsRequired = confirmationsRequired @@ -1334,36 +2286,36 @@ public struct ChannelDetails { public struct FfiConverterTypeChannelDetails: FfiConverterRustBuffer { public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ChannelDetails { - return try ChannelDetails( - channelId: FfiConverterTypeChannelId.read(from: &buf), - counterpartyNodeId: FfiConverterTypePublicKey.read(from: &buf), - fundingTxo: FfiConverterOptionTypeOutPoint.read(from: &buf), - channelValueSats: FfiConverterUInt64.read(from: &buf), - unspendablePunishmentReserve: FfiConverterOptionUInt64.read(from: &buf), - userChannelId: FfiConverterTypeUserChannelId.read(from: &buf), - feerateSatPer1000Weight: FfiConverterUInt32.read(from: &buf), - balanceMsat: FfiConverterUInt64.read(from: &buf), - outboundCapacityMsat: FfiConverterUInt64.read(from: &buf), - inboundCapacityMsat: FfiConverterUInt64.read(from: &buf), - confirmationsRequired: FfiConverterOptionUInt32.read(from: &buf), - confirmations: FfiConverterOptionUInt32.read(from: &buf), - isOutbound: FfiConverterBool.read(from: &buf), - isChannelReady: FfiConverterBool.read(from: &buf), - isUsable: FfiConverterBool.read(from: &buf), - isPublic: FfiConverterBool.read(from: &buf), - cltvExpiryDelta: FfiConverterOptionUInt16.read(from: &buf), - counterpartyUnspendablePunishmentReserve: FfiConverterUInt64.read(from: &buf), - counterpartyOutboundHtlcMinimumMsat: FfiConverterOptionUInt64.read(from: &buf), - counterpartyOutboundHtlcMaximumMsat: FfiConverterOptionUInt64.read(from: &buf), - counterpartyForwardingInfoFeeBaseMsat: FfiConverterOptionUInt32.read(from: &buf), - counterpartyForwardingInfoFeeProportionalMillionths: FfiConverterOptionUInt32.read(from: &buf), - counterpartyForwardingInfoCltvExpiryDelta: FfiConverterOptionUInt16.read(from: &buf), - nextOutboundHtlcLimitMsat: FfiConverterUInt64.read(from: &buf), - nextOutboundHtlcMinimumMsat: FfiConverterUInt64.read(from: &buf), - forceCloseSpendDelay: FfiConverterOptionUInt16.read(from: &buf), - inboundHtlcMinimumMsat: FfiConverterUInt64.read(from: &buf), - inboundHtlcMaximumMsat: FfiConverterOptionUInt64.read(from: &buf), - config: FfiConverterTypeChannelConfig.read(from: &buf) + return + try ChannelDetails( + channelId: FfiConverterTypeChannelId.read(from: &buf), + counterpartyNodeId: FfiConverterTypePublicKey.read(from: &buf), + fundingTxo: FfiConverterOptionTypeOutPoint.read(from: &buf), + channelValueSats: FfiConverterUInt64.read(from: &buf), + unspendablePunishmentReserve: FfiConverterOptionUInt64.read(from: &buf), + userChannelId: FfiConverterTypeUserChannelId.read(from: &buf), + feerateSatPer1000Weight: FfiConverterUInt32.read(from: &buf), + outboundCapacityMsat: FfiConverterUInt64.read(from: &buf), + inboundCapacityMsat: FfiConverterUInt64.read(from: &buf), + confirmationsRequired: FfiConverterOptionUInt32.read(from: &buf), + confirmations: FfiConverterOptionUInt32.read(from: &buf), + isOutbound: FfiConverterBool.read(from: &buf), + isChannelReady: FfiConverterBool.read(from: &buf), + isUsable: FfiConverterBool.read(from: &buf), + isPublic: FfiConverterBool.read(from: &buf), + cltvExpiryDelta: FfiConverterOptionUInt16.read(from: &buf), + counterpartyUnspendablePunishmentReserve: FfiConverterUInt64.read(from: &buf), + counterpartyOutboundHtlcMinimumMsat: FfiConverterOptionUInt64.read(from: &buf), + counterpartyOutboundHtlcMaximumMsat: FfiConverterOptionUInt64.read(from: &buf), + counterpartyForwardingInfoFeeBaseMsat: FfiConverterOptionUInt32.read(from: &buf), + counterpartyForwardingInfoFeeProportionalMillionths: FfiConverterOptionUInt32.read(from: &buf), + counterpartyForwardingInfoCltvExpiryDelta: FfiConverterOptionUInt16.read(from: &buf), + nextOutboundHtlcLimitMsat: FfiConverterUInt64.read(from: &buf), + nextOutboundHtlcMinimumMsat: FfiConverterUInt64.read(from: &buf), + forceCloseSpendDelay: FfiConverterOptionUInt16.read(from: &buf), + inboundHtlcMinimumMsat: FfiConverterUInt64.read(from: &buf), + inboundHtlcMaximumMsat: FfiConverterOptionUInt64.read(from: &buf), + config: FfiConverterTypeChannelConfig.read(from: &buf) ) } @@ -1375,7 +2327,6 @@ public struct FfiConverterTypeChannelDetails: FfiConverterRustBuffer { FfiConverterOptionUInt64.write(value.unspendablePunishmentReserve, into: &buf) FfiConverterTypeUserChannelId.write(value.userChannelId, into: &buf) FfiConverterUInt32.write(value.feerateSatPer1000Weight, into: &buf) - FfiConverterUInt64.write(value.balanceMsat, into: &buf) FfiConverterUInt64.write(value.outboundCapacityMsat, into: &buf) FfiConverterUInt64.write(value.inboundCapacityMsat, into: &buf) FfiConverterOptionUInt32.write(value.confirmationsRequired, into: &buf) @@ -1401,12 +2352,191 @@ public struct FfiConverterTypeChannelDetails: FfiConverterRustBuffer { } -public func FfiConverterTypeChannelDetails_lift(_ buf: RustBuffer) throws -> ChannelDetails { - return try FfiConverterTypeChannelDetails.lift(buf) +public func FfiConverterTypeChannelDetails_lift(_ buf: RustBuffer) throws -> ChannelDetails { + return try FfiConverterTypeChannelDetails.lift(buf) +} + +public func FfiConverterTypeChannelDetails_lower(_ value: ChannelDetails) -> RustBuffer { + return FfiConverterTypeChannelDetails.lower(value) +} + + +public struct ChannelInfo { + public var nodeOne: NodeId + public var oneToTwo: ChannelUpdateInfo? + public var nodeTwo: NodeId + public var twoToOne: ChannelUpdateInfo? + public var capacitySats: UInt64? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + nodeOne: NodeId, + oneToTwo: ChannelUpdateInfo?, + nodeTwo: NodeId, + twoToOne: ChannelUpdateInfo?, + capacitySats: UInt64?) { + self.nodeOne = nodeOne + self.oneToTwo = oneToTwo + self.nodeTwo = nodeTwo + self.twoToOne = twoToOne + self.capacitySats = capacitySats + } +} + + +extension ChannelInfo: Equatable, Hashable { + public static func ==(lhs: ChannelInfo, rhs: ChannelInfo) -> Bool { + if lhs.nodeOne != rhs.nodeOne { + return false + } + if lhs.oneToTwo != rhs.oneToTwo { + return false + } + if lhs.nodeTwo != rhs.nodeTwo { + return false + } + if lhs.twoToOne != rhs.twoToOne { + return false + } + if lhs.capacitySats != rhs.capacitySats { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(nodeOne) + hasher.combine(oneToTwo) + hasher.combine(nodeTwo) + hasher.combine(twoToOne) + hasher.combine(capacitySats) + } +} + + +public struct FfiConverterTypeChannelInfo: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ChannelInfo { + return + try ChannelInfo( + nodeOne: FfiConverterTypeNodeId.read(from: &buf), + oneToTwo: FfiConverterOptionTypeChannelUpdateInfo.read(from: &buf), + nodeTwo: FfiConverterTypeNodeId.read(from: &buf), + twoToOne: FfiConverterOptionTypeChannelUpdateInfo.read(from: &buf), + capacitySats: FfiConverterOptionUInt64.read(from: &buf) + ) + } + + public static func write(_ value: ChannelInfo, into buf: inout [UInt8]) { + FfiConverterTypeNodeId.write(value.nodeOne, into: &buf) + FfiConverterOptionTypeChannelUpdateInfo.write(value.oneToTwo, into: &buf) + FfiConverterTypeNodeId.write(value.nodeTwo, into: &buf) + FfiConverterOptionTypeChannelUpdateInfo.write(value.twoToOne, into: &buf) + FfiConverterOptionUInt64.write(value.capacitySats, into: &buf) + } +} + + +public func FfiConverterTypeChannelInfo_lift(_ buf: RustBuffer) throws -> ChannelInfo { + return try FfiConverterTypeChannelInfo.lift(buf) +} + +public func FfiConverterTypeChannelInfo_lower(_ value: ChannelInfo) -> RustBuffer { + return FfiConverterTypeChannelInfo.lower(value) +} + + +public struct ChannelUpdateInfo { + public var lastUpdate: UInt32 + public var enabled: Bool + public var cltvExpiryDelta: UInt16 + public var htlcMinimumMsat: UInt64 + public var htlcMaximumMsat: UInt64 + public var fees: RoutingFees + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + lastUpdate: UInt32, + enabled: Bool, + cltvExpiryDelta: UInt16, + htlcMinimumMsat: UInt64, + htlcMaximumMsat: UInt64, + fees: RoutingFees) { + self.lastUpdate = lastUpdate + self.enabled = enabled + self.cltvExpiryDelta = cltvExpiryDelta + self.htlcMinimumMsat = htlcMinimumMsat + self.htlcMaximumMsat = htlcMaximumMsat + self.fees = fees + } +} + + +extension ChannelUpdateInfo: Equatable, Hashable { + public static func ==(lhs: ChannelUpdateInfo, rhs: ChannelUpdateInfo) -> Bool { + if lhs.lastUpdate != rhs.lastUpdate { + return false + } + if lhs.enabled != rhs.enabled { + return false + } + if lhs.cltvExpiryDelta != rhs.cltvExpiryDelta { + return false + } + if lhs.htlcMinimumMsat != rhs.htlcMinimumMsat { + return false + } + if lhs.htlcMaximumMsat != rhs.htlcMaximumMsat { + return false + } + if lhs.fees != rhs.fees { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(lastUpdate) + hasher.combine(enabled) + hasher.combine(cltvExpiryDelta) + hasher.combine(htlcMinimumMsat) + hasher.combine(htlcMaximumMsat) + hasher.combine(fees) + } +} + + +public struct FfiConverterTypeChannelUpdateInfo: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ChannelUpdateInfo { + return + try ChannelUpdateInfo( + lastUpdate: FfiConverterUInt32.read(from: &buf), + enabled: FfiConverterBool.read(from: &buf), + cltvExpiryDelta: FfiConverterUInt16.read(from: &buf), + htlcMinimumMsat: FfiConverterUInt64.read(from: &buf), + htlcMaximumMsat: FfiConverterUInt64.read(from: &buf), + fees: FfiConverterTypeRoutingFees.read(from: &buf) + ) + } + + public static func write(_ value: ChannelUpdateInfo, into buf: inout [UInt8]) { + FfiConverterUInt32.write(value.lastUpdate, into: &buf) + FfiConverterBool.write(value.enabled, into: &buf) + FfiConverterUInt16.write(value.cltvExpiryDelta, into: &buf) + FfiConverterUInt64.write(value.htlcMinimumMsat, into: &buf) + FfiConverterUInt64.write(value.htlcMaximumMsat, into: &buf) + FfiConverterTypeRoutingFees.write(value.fees, into: &buf) + } +} + + +public func FfiConverterTypeChannelUpdateInfo_lift(_ buf: RustBuffer) throws -> ChannelUpdateInfo { + return try FfiConverterTypeChannelUpdateInfo.lift(buf) } -public func FfiConverterTypeChannelDetails_lower(_ value: ChannelDetails) -> RustBuffer { - return FfiConverterTypeChannelDetails.lower(value) +public func FfiConverterTypeChannelUpdateInfo_lower(_ value: ChannelUpdateInfo) -> RustBuffer { + return FfiConverterTypeChannelUpdateInfo.lower(value) } @@ -1422,10 +2552,23 @@ public struct Config { public var trustedPeers0conf: [PublicKey] public var probingLiquidityLimitMultiplier: UInt64 public var logLevel: LogLevel + public var anchorChannelsConfig: AnchorChannelsConfig? // Default memberwise initializers are never public by default, so we // declare one manually. - public init(storageDirPath: String = "/tmp/ldk_node/", logDirPath: String? = nil, network: Network = .bitcoin, listeningAddresses: [SocketAddress]? = nil, defaultCltvExpiryDelta: UInt32 = UInt32(144), onchainWalletSyncIntervalSecs: UInt64 = UInt64(80), walletSyncIntervalSecs: UInt64 = UInt64(30), feeRateCacheUpdateIntervalSecs: UInt64 = UInt64(600), trustedPeers0conf: [PublicKey] = [], probingLiquidityLimitMultiplier: UInt64 = UInt64(3), logLevel: LogLevel = .debug) { + public init( + storageDirPath: String, + logDirPath: String?, + network: Network, + listeningAddresses: [SocketAddress]?, + defaultCltvExpiryDelta: UInt32, + onchainWalletSyncIntervalSecs: UInt64, + walletSyncIntervalSecs: UInt64, + feeRateCacheUpdateIntervalSecs: UInt64, + trustedPeers0conf: [PublicKey], + probingLiquidityLimitMultiplier: UInt64, + logLevel: LogLevel, + anchorChannelsConfig: AnchorChannelsConfig?) { self.storageDirPath = storageDirPath self.logDirPath = logDirPath self.network = network @@ -1437,6 +2580,7 @@ public struct Config { self.trustedPeers0conf = trustedPeers0conf self.probingLiquidityLimitMultiplier = probingLiquidityLimitMultiplier self.logLevel = logLevel + self.anchorChannelsConfig = anchorChannelsConfig } } @@ -1476,6 +2620,9 @@ extension Config: Equatable, Hashable { if lhs.logLevel != rhs.logLevel { return false } + if lhs.anchorChannelsConfig != rhs.anchorChannelsConfig { + return false + } return true } @@ -1491,24 +2638,27 @@ extension Config: Equatable, Hashable { hasher.combine(trustedPeers0conf) hasher.combine(probingLiquidityLimitMultiplier) hasher.combine(logLevel) + hasher.combine(anchorChannelsConfig) } } public struct FfiConverterTypeConfig: FfiConverterRustBuffer { public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Config { - return try Config( - storageDirPath: FfiConverterString.read(from: &buf), - logDirPath: FfiConverterOptionString.read(from: &buf), - network: FfiConverterTypeNetwork.read(from: &buf), - listeningAddresses: FfiConverterOptionSequenceTypeSocketAddress.read(from: &buf), - defaultCltvExpiryDelta: FfiConverterUInt32.read(from: &buf), - onchainWalletSyncIntervalSecs: FfiConverterUInt64.read(from: &buf), - walletSyncIntervalSecs: FfiConverterUInt64.read(from: &buf), - feeRateCacheUpdateIntervalSecs: FfiConverterUInt64.read(from: &buf), - trustedPeers0conf: FfiConverterSequenceTypePublicKey.read(from: &buf), - probingLiquidityLimitMultiplier: FfiConverterUInt64.read(from: &buf), - logLevel: FfiConverterTypeLogLevel.read(from: &buf) + return + try Config( + storageDirPath: FfiConverterString.read(from: &buf), + logDirPath: FfiConverterOptionString.read(from: &buf), + network: FfiConverterTypeNetwork.read(from: &buf), + listeningAddresses: FfiConverterOptionSequenceTypeSocketAddress.read(from: &buf), + defaultCltvExpiryDelta: FfiConverterUInt32.read(from: &buf), + onchainWalletSyncIntervalSecs: FfiConverterUInt64.read(from: &buf), + walletSyncIntervalSecs: FfiConverterUInt64.read(from: &buf), + feeRateCacheUpdateIntervalSecs: FfiConverterUInt64.read(from: &buf), + trustedPeers0conf: FfiConverterSequenceTypePublicKey.read(from: &buf), + probingLiquidityLimitMultiplier: FfiConverterUInt64.read(from: &buf), + logLevel: FfiConverterTypeLogLevel.read(from: &buf), + anchorChannelsConfig: FfiConverterOptionTypeAnchorChannelsConfig.read(from: &buf) ) } @@ -1524,6 +2674,7 @@ public struct FfiConverterTypeConfig: FfiConverterRustBuffer { FfiConverterSequenceTypePublicKey.write(value.trustedPeers0conf, into: &buf) FfiConverterUInt64.write(value.probingLiquidityLimitMultiplier, into: &buf) FfiConverterTypeLogLevel.write(value.logLevel, into: &buf) + FfiConverterOptionTypeAnchorChannelsConfig.write(value.anchorChannelsConfig, into: &buf) } } @@ -1537,13 +2688,310 @@ public func FfiConverterTypeConfig_lower(_ value: Config) -> RustBuffer { } +public struct LspFeeLimits { + public var maxTotalOpeningFeeMsat: UInt64? + public var maxProportionalOpeningFeePpmMsat: UInt64? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + maxTotalOpeningFeeMsat: UInt64?, + maxProportionalOpeningFeePpmMsat: UInt64?) { + self.maxTotalOpeningFeeMsat = maxTotalOpeningFeeMsat + self.maxProportionalOpeningFeePpmMsat = maxProportionalOpeningFeePpmMsat + } +} + + +extension LspFeeLimits: Equatable, Hashable { + public static func ==(lhs: LspFeeLimits, rhs: LspFeeLimits) -> Bool { + if lhs.maxTotalOpeningFeeMsat != rhs.maxTotalOpeningFeeMsat { + return false + } + if lhs.maxProportionalOpeningFeePpmMsat != rhs.maxProportionalOpeningFeePpmMsat { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(maxTotalOpeningFeeMsat) + hasher.combine(maxProportionalOpeningFeePpmMsat) + } +} + + +public struct FfiConverterTypeLSPFeeLimits: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> LspFeeLimits { + return + try LspFeeLimits( + maxTotalOpeningFeeMsat: FfiConverterOptionUInt64.read(from: &buf), + maxProportionalOpeningFeePpmMsat: FfiConverterOptionUInt64.read(from: &buf) + ) + } + + public static func write(_ value: LspFeeLimits, into buf: inout [UInt8]) { + FfiConverterOptionUInt64.write(value.maxTotalOpeningFeeMsat, into: &buf) + FfiConverterOptionUInt64.write(value.maxProportionalOpeningFeePpmMsat, into: &buf) + } +} + + +public func FfiConverterTypeLSPFeeLimits_lift(_ buf: RustBuffer) throws -> LspFeeLimits { + return try FfiConverterTypeLSPFeeLimits.lift(buf) +} + +public func FfiConverterTypeLSPFeeLimits_lower(_ value: LspFeeLimits) -> RustBuffer { + return FfiConverterTypeLSPFeeLimits.lower(value) +} + + +public struct NodeAnnouncementInfo { + public var lastUpdate: UInt32 + public var alias: String + public var addresses: [SocketAddress] + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + lastUpdate: UInt32, + alias: String, + addresses: [SocketAddress]) { + self.lastUpdate = lastUpdate + self.alias = alias + self.addresses = addresses + } +} + + +extension NodeAnnouncementInfo: Equatable, Hashable { + public static func ==(lhs: NodeAnnouncementInfo, rhs: NodeAnnouncementInfo) -> Bool { + if lhs.lastUpdate != rhs.lastUpdate { + return false + } + if lhs.alias != rhs.alias { + return false + } + if lhs.addresses != rhs.addresses { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(lastUpdate) + hasher.combine(alias) + hasher.combine(addresses) + } +} + + +public struct FfiConverterTypeNodeAnnouncementInfo: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> NodeAnnouncementInfo { + return + try NodeAnnouncementInfo( + lastUpdate: FfiConverterUInt32.read(from: &buf), + alias: FfiConverterString.read(from: &buf), + addresses: FfiConverterSequenceTypeSocketAddress.read(from: &buf) + ) + } + + public static func write(_ value: NodeAnnouncementInfo, into buf: inout [UInt8]) { + FfiConverterUInt32.write(value.lastUpdate, into: &buf) + FfiConverterString.write(value.alias, into: &buf) + FfiConverterSequenceTypeSocketAddress.write(value.addresses, into: &buf) + } +} + + +public func FfiConverterTypeNodeAnnouncementInfo_lift(_ buf: RustBuffer) throws -> NodeAnnouncementInfo { + return try FfiConverterTypeNodeAnnouncementInfo.lift(buf) +} + +public func FfiConverterTypeNodeAnnouncementInfo_lower(_ value: NodeAnnouncementInfo) -> RustBuffer { + return FfiConverterTypeNodeAnnouncementInfo.lower(value) +} + + +public struct NodeInfo { + public var channels: [UInt64] + public var announcementInfo: NodeAnnouncementInfo? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + channels: [UInt64], + announcementInfo: NodeAnnouncementInfo?) { + self.channels = channels + self.announcementInfo = announcementInfo + } +} + + +extension NodeInfo: Equatable, Hashable { + public static func ==(lhs: NodeInfo, rhs: NodeInfo) -> Bool { + if lhs.channels != rhs.channels { + return false + } + if lhs.announcementInfo != rhs.announcementInfo { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(channels) + hasher.combine(announcementInfo) + } +} + + +public struct FfiConverterTypeNodeInfo: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> NodeInfo { + return + try NodeInfo( + channels: FfiConverterSequenceUInt64.read(from: &buf), + announcementInfo: FfiConverterOptionTypeNodeAnnouncementInfo.read(from: &buf) + ) + } + + public static func write(_ value: NodeInfo, into buf: inout [UInt8]) { + FfiConverterSequenceUInt64.write(value.channels, into: &buf) + FfiConverterOptionTypeNodeAnnouncementInfo.write(value.announcementInfo, into: &buf) + } +} + + +public func FfiConverterTypeNodeInfo_lift(_ buf: RustBuffer) throws -> NodeInfo { + return try FfiConverterTypeNodeInfo.lift(buf) +} + +public func FfiConverterTypeNodeInfo_lower(_ value: NodeInfo) -> RustBuffer { + return FfiConverterTypeNodeInfo.lower(value) +} + + +public struct NodeStatus { + public var isRunning: Bool + public var isListening: Bool + public var currentBestBlock: BestBlock + public var latestWalletSyncTimestamp: UInt64? + public var latestOnchainWalletSyncTimestamp: UInt64? + public var latestFeeRateCacheUpdateTimestamp: UInt64? + public var latestRgsSnapshotTimestamp: UInt64? + public var latestNodeAnnouncementBroadcastTimestamp: UInt64? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + isRunning: Bool, + isListening: Bool, + currentBestBlock: BestBlock, + latestWalletSyncTimestamp: UInt64?, + latestOnchainWalletSyncTimestamp: UInt64?, + latestFeeRateCacheUpdateTimestamp: UInt64?, + latestRgsSnapshotTimestamp: UInt64?, + latestNodeAnnouncementBroadcastTimestamp: UInt64?) { + self.isRunning = isRunning + self.isListening = isListening + self.currentBestBlock = currentBestBlock + self.latestWalletSyncTimestamp = latestWalletSyncTimestamp + self.latestOnchainWalletSyncTimestamp = latestOnchainWalletSyncTimestamp + self.latestFeeRateCacheUpdateTimestamp = latestFeeRateCacheUpdateTimestamp + self.latestRgsSnapshotTimestamp = latestRgsSnapshotTimestamp + self.latestNodeAnnouncementBroadcastTimestamp = latestNodeAnnouncementBroadcastTimestamp + } +} + + +extension NodeStatus: Equatable, Hashable { + public static func ==(lhs: NodeStatus, rhs: NodeStatus) -> Bool { + if lhs.isRunning != rhs.isRunning { + return false + } + if lhs.isListening != rhs.isListening { + return false + } + if lhs.currentBestBlock != rhs.currentBestBlock { + return false + } + if lhs.latestWalletSyncTimestamp != rhs.latestWalletSyncTimestamp { + return false + } + if lhs.latestOnchainWalletSyncTimestamp != rhs.latestOnchainWalletSyncTimestamp { + return false + } + if lhs.latestFeeRateCacheUpdateTimestamp != rhs.latestFeeRateCacheUpdateTimestamp { + return false + } + if lhs.latestRgsSnapshotTimestamp != rhs.latestRgsSnapshotTimestamp { + return false + } + if lhs.latestNodeAnnouncementBroadcastTimestamp != rhs.latestNodeAnnouncementBroadcastTimestamp { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(isRunning) + hasher.combine(isListening) + hasher.combine(currentBestBlock) + hasher.combine(latestWalletSyncTimestamp) + hasher.combine(latestOnchainWalletSyncTimestamp) + hasher.combine(latestFeeRateCacheUpdateTimestamp) + hasher.combine(latestRgsSnapshotTimestamp) + hasher.combine(latestNodeAnnouncementBroadcastTimestamp) + } +} + + +public struct FfiConverterTypeNodeStatus: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> NodeStatus { + return + try NodeStatus( + isRunning: FfiConverterBool.read(from: &buf), + isListening: FfiConverterBool.read(from: &buf), + currentBestBlock: FfiConverterTypeBestBlock.read(from: &buf), + latestWalletSyncTimestamp: FfiConverterOptionUInt64.read(from: &buf), + latestOnchainWalletSyncTimestamp: FfiConverterOptionUInt64.read(from: &buf), + latestFeeRateCacheUpdateTimestamp: FfiConverterOptionUInt64.read(from: &buf), + latestRgsSnapshotTimestamp: FfiConverterOptionUInt64.read(from: &buf), + latestNodeAnnouncementBroadcastTimestamp: FfiConverterOptionUInt64.read(from: &buf) + ) + } + + public static func write(_ value: NodeStatus, into buf: inout [UInt8]) { + FfiConverterBool.write(value.isRunning, into: &buf) + FfiConverterBool.write(value.isListening, into: &buf) + FfiConverterTypeBestBlock.write(value.currentBestBlock, into: &buf) + FfiConverterOptionUInt64.write(value.latestWalletSyncTimestamp, into: &buf) + FfiConverterOptionUInt64.write(value.latestOnchainWalletSyncTimestamp, into: &buf) + FfiConverterOptionUInt64.write(value.latestFeeRateCacheUpdateTimestamp, into: &buf) + FfiConverterOptionUInt64.write(value.latestRgsSnapshotTimestamp, into: &buf) + FfiConverterOptionUInt64.write(value.latestNodeAnnouncementBroadcastTimestamp, into: &buf) + } +} + + +public func FfiConverterTypeNodeStatus_lift(_ buf: RustBuffer) throws -> NodeStatus { + return try FfiConverterTypeNodeStatus.lift(buf) +} + +public func FfiConverterTypeNodeStatus_lower(_ value: NodeStatus) -> RustBuffer { + return FfiConverterTypeNodeStatus.lower(value) +} + + public struct OutPoint { public var txid: Txid public var vout: UInt32 // Default memberwise initializers are never public by default, so we // declare one manually. - public init(txid: Txid, vout: UInt32) { + public init( + txid: Txid, + vout: UInt32) { self.txid = txid self.vout = vout } @@ -1570,9 +3018,10 @@ extension OutPoint: Equatable, Hashable { public struct FfiConverterTypeOutPoint: FfiConverterRustBuffer { public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> OutPoint { - return try OutPoint( - txid: FfiConverterTypeTxid.read(from: &buf), - vout: FfiConverterUInt32.read(from: &buf) + return + try OutPoint( + txid: FfiConverterTypeTxid.read(from: &buf), + vout: FfiConverterUInt32.read(from: &buf) ) } @@ -1593,35 +3042,38 @@ public func FfiConverterTypeOutPoint_lower(_ value: OutPoint) -> RustBuffer { public struct PaymentDetails { - public var hash: PaymentHash - public var preimage: PaymentPreimage? - public var secret: PaymentSecret? + public var id: PaymentId + public var kind: PaymentKind public var amountMsat: UInt64? public var direction: PaymentDirection public var status: PaymentStatus + public var latestUpdateTimestamp: UInt64 // Default memberwise initializers are never public by default, so we // declare one manually. - public init(hash: PaymentHash, preimage: PaymentPreimage?, secret: PaymentSecret?, amountMsat: UInt64?, direction: PaymentDirection, status: PaymentStatus) { - self.hash = hash - self.preimage = preimage - self.secret = secret + public init( + id: PaymentId, + kind: PaymentKind, + amountMsat: UInt64?, + direction: PaymentDirection, + status: PaymentStatus, + latestUpdateTimestamp: UInt64) { + self.id = id + self.kind = kind self.amountMsat = amountMsat self.direction = direction self.status = status + self.latestUpdateTimestamp = latestUpdateTimestamp } } extension PaymentDetails: Equatable, Hashable { public static func ==(lhs: PaymentDetails, rhs: PaymentDetails) -> Bool { - if lhs.hash != rhs.hash { - return false - } - if lhs.preimage != rhs.preimage { + if lhs.id != rhs.id { return false } - if lhs.secret != rhs.secret { + if lhs.kind != rhs.kind { return false } if lhs.amountMsat != rhs.amountMsat { @@ -1633,39 +3085,43 @@ extension PaymentDetails: Equatable, Hashable { if lhs.status != rhs.status { return false } + if lhs.latestUpdateTimestamp != rhs.latestUpdateTimestamp { + return false + } return true } public func hash(into hasher: inout Hasher) { - hasher.combine(hash) - hasher.combine(preimage) - hasher.combine(secret) + hasher.combine(id) + hasher.combine(kind) hasher.combine(amountMsat) hasher.combine(direction) hasher.combine(status) + hasher.combine(latestUpdateTimestamp) } } public struct FfiConverterTypePaymentDetails: FfiConverterRustBuffer { public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PaymentDetails { - return try PaymentDetails( - hash: FfiConverterTypePaymentHash.read(from: &buf), - preimage: FfiConverterOptionTypePaymentPreimage.read(from: &buf), - secret: FfiConverterOptionTypePaymentSecret.read(from: &buf), - amountMsat: FfiConverterOptionUInt64.read(from: &buf), - direction: FfiConverterTypePaymentDirection.read(from: &buf), - status: FfiConverterTypePaymentStatus.read(from: &buf) + return + try PaymentDetails( + id: FfiConverterTypePaymentId.read(from: &buf), + kind: FfiConverterTypePaymentKind.read(from: &buf), + amountMsat: FfiConverterOptionUInt64.read(from: &buf), + direction: FfiConverterTypePaymentDirection.read(from: &buf), + status: FfiConverterTypePaymentStatus.read(from: &buf), + latestUpdateTimestamp: FfiConverterUInt64.read(from: &buf) ) } public static func write(_ value: PaymentDetails, into buf: inout [UInt8]) { - FfiConverterTypePaymentHash.write(value.hash, into: &buf) - FfiConverterOptionTypePaymentPreimage.write(value.preimage, into: &buf) - FfiConverterOptionTypePaymentSecret.write(value.secret, into: &buf) + FfiConverterTypePaymentId.write(value.id, into: &buf) + FfiConverterTypePaymentKind.write(value.kind, into: &buf) FfiConverterOptionUInt64.write(value.amountMsat, into: &buf) FfiConverterTypePaymentDirection.write(value.direction, into: &buf) FfiConverterTypePaymentStatus.write(value.status, into: &buf) + FfiConverterUInt64.write(value.latestUpdateTimestamp, into: &buf) } } @@ -1687,7 +3143,11 @@ public struct PeerDetails { // Default memberwise initializers are never public by default, so we // declare one manually. - public init(nodeId: PublicKey, address: SocketAddress, isPersisted: Bool, isConnected: Bool) { + public init( + nodeId: PublicKey, + address: SocketAddress, + isPersisted: Bool, + isConnected: Bool) { self.nodeId = nodeId self.address = address self.isPersisted = isPersisted @@ -1724,11 +3184,12 @@ extension PeerDetails: Equatable, Hashable { public struct FfiConverterTypePeerDetails: FfiConverterRustBuffer { public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PeerDetails { - return try PeerDetails( - nodeId: FfiConverterTypePublicKey.read(from: &buf), - address: FfiConverterTypeSocketAddress.read(from: &buf), - isPersisted: FfiConverterBool.read(from: &buf), - isConnected: FfiConverterBool.read(from: &buf) + return + try PeerDetails( + nodeId: FfiConverterTypePublicKey.read(from: &buf), + address: FfiConverterTypeSocketAddress.read(from: &buf), + isPersisted: FfiConverterBool.read(from: &buf), + isConnected: FfiConverterBool.read(from: &buf) ) } @@ -1749,41 +3210,89 @@ public func FfiConverterTypePeerDetails_lower(_ value: PeerDetails) -> RustBuffe return FfiConverterTypePeerDetails.lower(value) } + +public struct RoutingFees { + public var baseMsat: UInt32 + public var proportionalMillionths: UInt32 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + baseMsat: UInt32, + proportionalMillionths: UInt32) { + self.baseMsat = baseMsat + self.proportionalMillionths = proportionalMillionths + } +} + + +extension RoutingFees: Equatable, Hashable { + public static func ==(lhs: RoutingFees, rhs: RoutingFees) -> Bool { + if lhs.baseMsat != rhs.baseMsat { + return false + } + if lhs.proportionalMillionths != rhs.proportionalMillionths { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(baseMsat) + hasher.combine(proportionalMillionths) + } +} + + +public struct FfiConverterTypeRoutingFees: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> RoutingFees { + return + try RoutingFees( + baseMsat: FfiConverterUInt32.read(from: &buf), + proportionalMillionths: FfiConverterUInt32.read(from: &buf) + ) + } + + public static func write(_ value: RoutingFees, into buf: inout [UInt8]) { + FfiConverterUInt32.write(value.baseMsat, into: &buf) + FfiConverterUInt32.write(value.proportionalMillionths, into: &buf) + } +} + + +public func FfiConverterTypeRoutingFees_lift(_ buf: RustBuffer) throws -> RoutingFees { + return try FfiConverterTypeRoutingFees.lift(buf) +} + +public func FfiConverterTypeRoutingFees_lower(_ value: RoutingFees) -> RustBuffer { + return FfiConverterTypeRoutingFees.lower(value) +} + + public enum BuildError { - // Simple error enums only carry a message case InvalidSeedBytes(message: String) - // Simple error enums only carry a message case InvalidSeedFile(message: String) - // Simple error enums only carry a message case InvalidSystemTime(message: String) - // Simple error enums only carry a message case InvalidChannelMonitor(message: String) - // Simple error enums only carry a message case InvalidListeningAddresses(message: String) - // Simple error enums only carry a message case ReadFailed(message: String) - // Simple error enums only carry a message case WriteFailed(message: String) - // Simple error enums only carry a message case StoragePathAccessFailed(message: String) - // Simple error enums only carry a message case KvStoreSetupFailed(message: String) - // Simple error enums only carry a message case WalletSetupFailed(message: String) - // Simple error enums only carry a message case LoggerSetupFailed(message: String) @@ -1835,72 +3344,244 @@ public struct FfiConverterTypeBuildError: FfiConverterRustBuffer { message: try FfiConverterString.read(from: &buf) ) - case 9: return .KvStoreSetupFailed( - message: try FfiConverterString.read(from: &buf) - ) + case 9: return .KvStoreSetupFailed( + message: try FfiConverterString.read(from: &buf) + ) + + case 10: return .WalletSetupFailed( + message: try FfiConverterString.read(from: &buf) + ) + + case 11: return .LoggerSetupFailed( + message: try FfiConverterString.read(from: &buf) + ) + + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: BuildError, into buf: inout [UInt8]) { + switch value { + + + + + case .InvalidSeedBytes(_ /* message is ignored*/): + writeInt(&buf, Int32(1)) + case .InvalidSeedFile(_ /* message is ignored*/): + writeInt(&buf, Int32(2)) + case .InvalidSystemTime(_ /* message is ignored*/): + writeInt(&buf, Int32(3)) + case .InvalidChannelMonitor(_ /* message is ignored*/): + writeInt(&buf, Int32(4)) + case .InvalidListeningAddresses(_ /* message is ignored*/): + writeInt(&buf, Int32(5)) + case .ReadFailed(_ /* message is ignored*/): + writeInt(&buf, Int32(6)) + case .WriteFailed(_ /* message is ignored*/): + writeInt(&buf, Int32(7)) + case .StoragePathAccessFailed(_ /* message is ignored*/): + writeInt(&buf, Int32(8)) + case .KvStoreSetupFailed(_ /* message is ignored*/): + writeInt(&buf, Int32(9)) + case .WalletSetupFailed(_ /* message is ignored*/): + writeInt(&buf, Int32(10)) + case .LoggerSetupFailed(_ /* message is ignored*/): + writeInt(&buf, Int32(11)) + + + } + } +} + + +extension BuildError: Equatable, Hashable {} + +extension BuildError: Error { } + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +public enum ClosureReason { + + case counterpartyForceClosed( + peerMsg: UntrustedString + ) + case holderForceClosed + case legacyCooperativeClosure + case counterpartyInitiatedCooperativeClosure + case locallyInitiatedCooperativeClosure + case commitmentTxConfirmed + case fundingTimedOut + case processingError( + err: String + ) + case disconnectedPeer + case outdatedChannelManager + case counterpartyCoopClosedUnfundedChannel + case fundingBatchClosure + case htlCsTimedOut +} + +public struct FfiConverterTypeClosureReason: FfiConverterRustBuffer { + typealias SwiftType = ClosureReason + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ClosureReason { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .counterpartyForceClosed( + peerMsg: try FfiConverterTypeUntrustedString.read(from: &buf) + ) + + case 2: return .holderForceClosed + + case 3: return .legacyCooperativeClosure + + case 4: return .counterpartyInitiatedCooperativeClosure + + case 5: return .locallyInitiatedCooperativeClosure + + case 6: return .commitmentTxConfirmed + + case 7: return .fundingTimedOut - case 10: return .WalletSetupFailed( - message: try FfiConverterString.read(from: &buf) + case 8: return .processingError( + err: try FfiConverterString.read(from: &buf) ) - case 11: return .LoggerSetupFailed( - message: try FfiConverterString.read(from: &buf) - ) + case 9: return .disconnectedPeer + + case 10: return .outdatedChannelManager + + case 11: return .counterpartyCoopClosedUnfundedChannel + + case 12: return .fundingBatchClosure + + case 13: return .htlCsTimedOut - default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: BuildError, into buf: inout [UInt8]) { + public static func write(_ value: ClosureReason, into buf: inout [UInt8]) { switch value { - - - case .InvalidSeedBytes(_ /* message is ignored*/): + case let .counterpartyForceClosed(peerMsg): writeInt(&buf, Int32(1)) - case .InvalidSeedFile(_ /* message is ignored*/): + FfiConverterTypeUntrustedString.write(peerMsg, into: &buf) + + + case .holderForceClosed: writeInt(&buf, Int32(2)) - case .InvalidSystemTime(_ /* message is ignored*/): + + + case .legacyCooperativeClosure: writeInt(&buf, Int32(3)) - case .InvalidChannelMonitor(_ /* message is ignored*/): + + + case .counterpartyInitiatedCooperativeClosure: writeInt(&buf, Int32(4)) - case .InvalidListeningAddresses(_ /* message is ignored*/): + + + case .locallyInitiatedCooperativeClosure: writeInt(&buf, Int32(5)) - case .ReadFailed(_ /* message is ignored*/): + + + case .commitmentTxConfirmed: writeInt(&buf, Int32(6)) - case .WriteFailed(_ /* message is ignored*/): + + + case .fundingTimedOut: writeInt(&buf, Int32(7)) - case .StoragePathAccessFailed(_ /* message is ignored*/): + + + case let .processingError(err): writeInt(&buf, Int32(8)) - case .KvStoreSetupFailed(_ /* message is ignored*/): + FfiConverterString.write(err, into: &buf) + + + case .disconnectedPeer: writeInt(&buf, Int32(9)) - case .WalletSetupFailed(_ /* message is ignored*/): + + + case .outdatedChannelManager: writeInt(&buf, Int32(10)) - case .LoggerSetupFailed(_ /* message is ignored*/): + + + case .counterpartyCoopClosedUnfundedChannel: writeInt(&buf, Int32(11)) - + + + case .fundingBatchClosure: + writeInt(&buf, Int32(12)) + + + case .htlCsTimedOut: + writeInt(&buf, Int32(13)) } } } -extension BuildError: Equatable, Hashable {} +public func FfiConverterTypeClosureReason_lift(_ buf: RustBuffer) throws -> ClosureReason { + return try FfiConverterTypeClosureReason.lift(buf) +} + +public func FfiConverterTypeClosureReason_lower(_ value: ClosureReason) -> RustBuffer { + return FfiConverterTypeClosureReason.lower(value) +} + + +extension ClosureReason: Equatable, Hashable {} + -extension BuildError: Error { } // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. public enum Event { - case paymentSuccessful(paymentHash: PaymentHash) - case paymentFailed(paymentHash: PaymentHash) - case paymentReceived(paymentHash: PaymentHash, amountMsat: UInt64) - case channelPending(channelId: ChannelId, userChannelId: UserChannelId, formerTemporaryChannelId: ChannelId, counterpartyNodeId: PublicKey, fundingTxo: OutPoint) - case channelReady(channelId: ChannelId, userChannelId: UserChannelId, counterpartyNodeId: PublicKey?) - case channelClosed(channelId: ChannelId, userChannelId: UserChannelId, counterpartyNodeId: PublicKey?) + case paymentSuccessful( + paymentId: PaymentId?, + paymentHash: PaymentHash, + feePaidMsat: UInt64? + ) + case paymentFailed( + paymentId: PaymentId?, + paymentHash: PaymentHash, + reason: PaymentFailureReason? + ) + case paymentReceived( + paymentId: PaymentId?, + paymentHash: PaymentHash, + amountMsat: UInt64 + ) + case paymentClaimable( + paymentId: PaymentId, + paymentHash: PaymentHash, + claimableAmountMsat: UInt64, + claimDeadline: UInt32? + ) + case channelPending( + channelId: ChannelId, + userChannelId: UserChannelId, + formerTemporaryChannelId: ChannelId, + counterpartyNodeId: PublicKey, + fundingTxo: OutPoint + ) + case channelReady( + channelId: ChannelId, + userChannelId: UserChannelId, + counterpartyNodeId: PublicKey? + ) + case channelClosed( + channelId: ChannelId, + userChannelId: UserChannelId, + counterpartyNodeId: PublicKey?, + reason: ClosureReason? + ) } public struct FfiConverterTypeEvent: FfiConverterRustBuffer { @@ -1911,19 +3592,31 @@ public struct FfiConverterTypeEvent: FfiConverterRustBuffer { switch variant { case 1: return .paymentSuccessful( - paymentHash: try FfiConverterTypePaymentHash.read(from: &buf) + paymentId: try FfiConverterOptionTypePaymentId.read(from: &buf), + paymentHash: try FfiConverterTypePaymentHash.read(from: &buf), + feePaidMsat: try FfiConverterOptionUInt64.read(from: &buf) ) case 2: return .paymentFailed( - paymentHash: try FfiConverterTypePaymentHash.read(from: &buf) + paymentId: try FfiConverterOptionTypePaymentId.read(from: &buf), + paymentHash: try FfiConverterTypePaymentHash.read(from: &buf), + reason: try FfiConverterOptionTypePaymentFailureReason.read(from: &buf) ) case 3: return .paymentReceived( + paymentId: try FfiConverterOptionTypePaymentId.read(from: &buf), paymentHash: try FfiConverterTypePaymentHash.read(from: &buf), amountMsat: try FfiConverterUInt64.read(from: &buf) ) - case 4: return .channelPending( + case 4: return .paymentClaimable( + paymentId: try FfiConverterTypePaymentId.read(from: &buf), + paymentHash: try FfiConverterTypePaymentHash.read(from: &buf), + claimableAmountMsat: try FfiConverterUInt64.read(from: &buf), + claimDeadline: try FfiConverterOptionUInt32.read(from: &buf) + ) + + case 5: return .channelPending( channelId: try FfiConverterTypeChannelId.read(from: &buf), userChannelId: try FfiConverterTypeUserChannelId.read(from: &buf), formerTemporaryChannelId: try FfiConverterTypeChannelId.read(from: &buf), @@ -1931,16 +3624,17 @@ public struct FfiConverterTypeEvent: FfiConverterRustBuffer { fundingTxo: try FfiConverterTypeOutPoint.read(from: &buf) ) - case 5: return .channelReady( + case 6: return .channelReady( channelId: try FfiConverterTypeChannelId.read(from: &buf), userChannelId: try FfiConverterTypeUserChannelId.read(from: &buf), counterpartyNodeId: try FfiConverterOptionTypePublicKey.read(from: &buf) ) - case 6: return .channelClosed( + case 7: return .channelClosed( channelId: try FfiConverterTypeChannelId.read(from: &buf), userChannelId: try FfiConverterTypeUserChannelId.read(from: &buf), - counterpartyNodeId: try FfiConverterOptionTypePublicKey.read(from: &buf) + counterpartyNodeId: try FfiConverterOptionTypePublicKey.read(from: &buf), + reason: try FfiConverterOptionTypeClosureReason.read(from: &buf) ) default: throw UniffiInternalError.unexpectedEnumCase @@ -1951,24 +3645,37 @@ public struct FfiConverterTypeEvent: FfiConverterRustBuffer { switch value { - case let .paymentSuccessful(paymentHash): + case let .paymentSuccessful(paymentId,paymentHash,feePaidMsat): writeInt(&buf, Int32(1)) + FfiConverterOptionTypePaymentId.write(paymentId, into: &buf) FfiConverterTypePaymentHash.write(paymentHash, into: &buf) + FfiConverterOptionUInt64.write(feePaidMsat, into: &buf) - case let .paymentFailed(paymentHash): + case let .paymentFailed(paymentId,paymentHash,reason): writeInt(&buf, Int32(2)) + FfiConverterOptionTypePaymentId.write(paymentId, into: &buf) FfiConverterTypePaymentHash.write(paymentHash, into: &buf) + FfiConverterOptionTypePaymentFailureReason.write(reason, into: &buf) - case let .paymentReceived(paymentHash,amountMsat): + case let .paymentReceived(paymentId,paymentHash,amountMsat): writeInt(&buf, Int32(3)) + FfiConverterOptionTypePaymentId.write(paymentId, into: &buf) FfiConverterTypePaymentHash.write(paymentHash, into: &buf) FfiConverterUInt64.write(amountMsat, into: &buf) - case let .channelPending(channelId,userChannelId,formerTemporaryChannelId,counterpartyNodeId,fundingTxo): + case let .paymentClaimable(paymentId,paymentHash,claimableAmountMsat,claimDeadline): writeInt(&buf, Int32(4)) + FfiConverterTypePaymentId.write(paymentId, into: &buf) + FfiConverterTypePaymentHash.write(paymentHash, into: &buf) + FfiConverterUInt64.write(claimableAmountMsat, into: &buf) + FfiConverterOptionUInt32.write(claimDeadline, into: &buf) + + + case let .channelPending(channelId,userChannelId,formerTemporaryChannelId,counterpartyNodeId,fundingTxo): + writeInt(&buf, Int32(5)) FfiConverterTypeChannelId.write(channelId, into: &buf) FfiConverterTypeUserChannelId.write(userChannelId, into: &buf) FfiConverterTypeChannelId.write(formerTemporaryChannelId, into: &buf) @@ -1977,17 +3684,18 @@ public struct FfiConverterTypeEvent: FfiConverterRustBuffer { case let .channelReady(channelId,userChannelId,counterpartyNodeId): - writeInt(&buf, Int32(5)) + writeInt(&buf, Int32(6)) FfiConverterTypeChannelId.write(channelId, into: &buf) FfiConverterTypeUserChannelId.write(userChannelId, into: &buf) FfiConverterOptionTypePublicKey.write(counterpartyNodeId, into: &buf) - case let .channelClosed(channelId,userChannelId,counterpartyNodeId): - writeInt(&buf, Int32(6)) + case let .channelClosed(channelId,userChannelId,counterpartyNodeId,reason): + writeInt(&buf, Int32(7)) FfiConverterTypeChannelId.write(channelId, into: &buf) FfiConverterTypeUserChannelId.write(userChannelId, into: &buf) FfiConverterOptionTypePublicKey.write(counterpartyNodeId, into: &buf) + FfiConverterOptionTypeClosureReason.write(reason, into: &buf) } } @@ -2007,6 +3715,176 @@ extension Event: Equatable, Hashable {} +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +public enum LightningBalance { + + case claimableOnChannelClose( + channelId: ChannelId, + counterpartyNodeId: PublicKey, + amountSatoshis: UInt64 + ) + case claimableAwaitingConfirmations( + channelId: ChannelId, + counterpartyNodeId: PublicKey, + amountSatoshis: UInt64, + confirmationHeight: UInt32 + ) + case contentiousClaimable( + channelId: ChannelId, + counterpartyNodeId: PublicKey, + amountSatoshis: UInt64, + timeoutHeight: UInt32, + paymentHash: PaymentHash, + paymentPreimage: PaymentPreimage + ) + case maybeTimeoutClaimableHtlc( + channelId: ChannelId, + counterpartyNodeId: PublicKey, + amountSatoshis: UInt64, + claimableHeight: UInt32, + paymentHash: PaymentHash + ) + case maybePreimageClaimableHtlc( + channelId: ChannelId, + counterpartyNodeId: PublicKey, + amountSatoshis: UInt64, + expiryHeight: UInt32, + paymentHash: PaymentHash + ) + case counterpartyRevokedOutputClaimable( + channelId: ChannelId, + counterpartyNodeId: PublicKey, + amountSatoshis: UInt64 + ) +} + +public struct FfiConverterTypeLightningBalance: FfiConverterRustBuffer { + typealias SwiftType = LightningBalance + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> LightningBalance { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .claimableOnChannelClose( + channelId: try FfiConverterTypeChannelId.read(from: &buf), + counterpartyNodeId: try FfiConverterTypePublicKey.read(from: &buf), + amountSatoshis: try FfiConverterUInt64.read(from: &buf) + ) + + case 2: return .claimableAwaitingConfirmations( + channelId: try FfiConverterTypeChannelId.read(from: &buf), + counterpartyNodeId: try FfiConverterTypePublicKey.read(from: &buf), + amountSatoshis: try FfiConverterUInt64.read(from: &buf), + confirmationHeight: try FfiConverterUInt32.read(from: &buf) + ) + + case 3: return .contentiousClaimable( + channelId: try FfiConverterTypeChannelId.read(from: &buf), + counterpartyNodeId: try FfiConverterTypePublicKey.read(from: &buf), + amountSatoshis: try FfiConverterUInt64.read(from: &buf), + timeoutHeight: try FfiConverterUInt32.read(from: &buf), + paymentHash: try FfiConverterTypePaymentHash.read(from: &buf), + paymentPreimage: try FfiConverterTypePaymentPreimage.read(from: &buf) + ) + + case 4: return .maybeTimeoutClaimableHtlc( + channelId: try FfiConverterTypeChannelId.read(from: &buf), + counterpartyNodeId: try FfiConverterTypePublicKey.read(from: &buf), + amountSatoshis: try FfiConverterUInt64.read(from: &buf), + claimableHeight: try FfiConverterUInt32.read(from: &buf), + paymentHash: try FfiConverterTypePaymentHash.read(from: &buf) + ) + + case 5: return .maybePreimageClaimableHtlc( + channelId: try FfiConverterTypeChannelId.read(from: &buf), + counterpartyNodeId: try FfiConverterTypePublicKey.read(from: &buf), + amountSatoshis: try FfiConverterUInt64.read(from: &buf), + expiryHeight: try FfiConverterUInt32.read(from: &buf), + paymentHash: try FfiConverterTypePaymentHash.read(from: &buf) + ) + + case 6: return .counterpartyRevokedOutputClaimable( + channelId: try FfiConverterTypeChannelId.read(from: &buf), + counterpartyNodeId: try FfiConverterTypePublicKey.read(from: &buf), + amountSatoshis: try FfiConverterUInt64.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: LightningBalance, into buf: inout [UInt8]) { + switch value { + + + case let .claimableOnChannelClose(channelId,counterpartyNodeId,amountSatoshis): + writeInt(&buf, Int32(1)) + FfiConverterTypeChannelId.write(channelId, into: &buf) + FfiConverterTypePublicKey.write(counterpartyNodeId, into: &buf) + FfiConverterUInt64.write(amountSatoshis, into: &buf) + + + case let .claimableAwaitingConfirmations(channelId,counterpartyNodeId,amountSatoshis,confirmationHeight): + writeInt(&buf, Int32(2)) + FfiConverterTypeChannelId.write(channelId, into: &buf) + FfiConverterTypePublicKey.write(counterpartyNodeId, into: &buf) + FfiConverterUInt64.write(amountSatoshis, into: &buf) + FfiConverterUInt32.write(confirmationHeight, into: &buf) + + + case let .contentiousClaimable(channelId,counterpartyNodeId,amountSatoshis,timeoutHeight,paymentHash,paymentPreimage): + writeInt(&buf, Int32(3)) + FfiConverterTypeChannelId.write(channelId, into: &buf) + FfiConverterTypePublicKey.write(counterpartyNodeId, into: &buf) + FfiConverterUInt64.write(amountSatoshis, into: &buf) + FfiConverterUInt32.write(timeoutHeight, into: &buf) + FfiConverterTypePaymentHash.write(paymentHash, into: &buf) + FfiConverterTypePaymentPreimage.write(paymentPreimage, into: &buf) + + + case let .maybeTimeoutClaimableHtlc(channelId,counterpartyNodeId,amountSatoshis,claimableHeight,paymentHash): + writeInt(&buf, Int32(4)) + FfiConverterTypeChannelId.write(channelId, into: &buf) + FfiConverterTypePublicKey.write(counterpartyNodeId, into: &buf) + FfiConverterUInt64.write(amountSatoshis, into: &buf) + FfiConverterUInt32.write(claimableHeight, into: &buf) + FfiConverterTypePaymentHash.write(paymentHash, into: &buf) + + + case let .maybePreimageClaimableHtlc(channelId,counterpartyNodeId,amountSatoshis,expiryHeight,paymentHash): + writeInt(&buf, Int32(5)) + FfiConverterTypeChannelId.write(channelId, into: &buf) + FfiConverterTypePublicKey.write(counterpartyNodeId, into: &buf) + FfiConverterUInt64.write(amountSatoshis, into: &buf) + FfiConverterUInt32.write(expiryHeight, into: &buf) + FfiConverterTypePaymentHash.write(paymentHash, into: &buf) + + + case let .counterpartyRevokedOutputClaimable(channelId,counterpartyNodeId,amountSatoshis): + writeInt(&buf, Int32(6)) + FfiConverterTypeChannelId.write(channelId, into: &buf) + FfiConverterTypePublicKey.write(counterpartyNodeId, into: &buf) + FfiConverterUInt64.write(amountSatoshis, into: &buf) + + } + } +} + + +public func FfiConverterTypeLightningBalance_lift(_ buf: RustBuffer) throws -> LightningBalance { + return try FfiConverterTypeLightningBalance.lift(buf) +} + +public func FfiConverterTypeLightningBalance_lower(_ value: LightningBalance) -> RustBuffer { + return FfiConverterTypeLightningBalance.lower(value) +} + + +extension LightningBalance: Equatable, Hashable {} + + + // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. public enum LogLevel { @@ -2153,100 +4031,103 @@ extension Network: Equatable, Hashable {} + public enum NodeError { - // Simple error enums only carry a message case AlreadyRunning(message: String) - // Simple error enums only carry a message case NotRunning(message: String) - // Simple error enums only carry a message case OnchainTxCreationFailed(message: String) - // Simple error enums only carry a message case ConnectionFailed(message: String) - // Simple error enums only carry a message case InvoiceCreationFailed(message: String) - // Simple error enums only carry a message + case InvoiceRequestCreationFailed(message: String) + + case OfferCreationFailed(message: String) + + case RefundCreationFailed(message: String) + case PaymentSendingFailed(message: String) - // Simple error enums only carry a message case ProbeSendingFailed(message: String) - // Simple error enums only carry a message case ChannelCreationFailed(message: String) - // Simple error enums only carry a message case ChannelClosingFailed(message: String) - // Simple error enums only carry a message case ChannelConfigUpdateFailed(message: String) - // Simple error enums only carry a message case PersistenceFailed(message: String) - // Simple error enums only carry a message case FeerateEstimationUpdateFailed(message: String) - // Simple error enums only carry a message + case FeerateEstimationUpdateTimeout(message: String) + case WalletOperationFailed(message: String) - // Simple error enums only carry a message + case WalletOperationTimeout(message: String) + case OnchainTxSigningFailed(message: String) - // Simple error enums only carry a message case MessageSigningFailed(message: String) - // Simple error enums only carry a message case TxSyncFailed(message: String) - // Simple error enums only carry a message + case TxSyncTimeout(message: String) + case GossipUpdateFailed(message: String) - // Simple error enums only carry a message + case GossipUpdateTimeout(message: String) + + case LiquidityRequestFailed(message: String) + case InvalidAddress(message: String) - // Simple error enums only carry a message case InvalidSocketAddress(message: String) - // Simple error enums only carry a message case InvalidPublicKey(message: String) - // Simple error enums only carry a message case InvalidSecretKey(message: String) - // Simple error enums only carry a message + case InvalidOfferId(message: String) + + case InvalidNodeId(message: String) + + case InvalidPaymentId(message: String) + case InvalidPaymentHash(message: String) - // Simple error enums only carry a message case InvalidPaymentPreimage(message: String) - // Simple error enums only carry a message case InvalidPaymentSecret(message: String) - // Simple error enums only carry a message case InvalidAmount(message: String) - // Simple error enums only carry a message case InvalidInvoice(message: String) - // Simple error enums only carry a message + case InvalidOffer(message: String) + + case InvalidRefund(message: String) + case InvalidChannelId(message: String) - // Simple error enums only carry a message case InvalidNetwork(message: String) - // Simple error enums only carry a message case DuplicatePayment(message: String) - // Simple error enums only carry a message + case UnsupportedCurrency(message: String) + case InsufficientFunds(message: String) + case LiquiditySourceUnavailable(message: String) + + case LiquidityFeeTooHigh(message: String) + fileprivate static func uniffiErrorHandler(_ error: RustBuffer) throws -> Error { return try FfiConverterTypeNodeError.lift(error) @@ -2284,103 +4165,167 @@ public struct FfiConverterTypeNodeError: FfiConverterRustBuffer { message: try FfiConverterString.read(from: &buf) ) - case 6: return .PaymentSendingFailed( + case 6: return .InvoiceRequestCreationFailed( + message: try FfiConverterString.read(from: &buf) + ) + + case 7: return .OfferCreationFailed( + message: try FfiConverterString.read(from: &buf) + ) + + case 8: return .RefundCreationFailed( + message: try FfiConverterString.read(from: &buf) + ) + + case 9: return .PaymentSendingFailed( + message: try FfiConverterString.read(from: &buf) + ) + + case 10: return .ProbeSendingFailed( + message: try FfiConverterString.read(from: &buf) + ) + + case 11: return .ChannelCreationFailed( + message: try FfiConverterString.read(from: &buf) + ) + + case 12: return .ChannelClosingFailed( + message: try FfiConverterString.read(from: &buf) + ) + + case 13: return .ChannelConfigUpdateFailed( + message: try FfiConverterString.read(from: &buf) + ) + + case 14: return .PersistenceFailed( + message: try FfiConverterString.read(from: &buf) + ) + + case 15: return .FeerateEstimationUpdateFailed( + message: try FfiConverterString.read(from: &buf) + ) + + case 16: return .FeerateEstimationUpdateTimeout( + message: try FfiConverterString.read(from: &buf) + ) + + case 17: return .WalletOperationFailed( + message: try FfiConverterString.read(from: &buf) + ) + + case 18: return .WalletOperationTimeout( + message: try FfiConverterString.read(from: &buf) + ) + + case 19: return .OnchainTxSigningFailed( + message: try FfiConverterString.read(from: &buf) + ) + + case 20: return .MessageSigningFailed( message: try FfiConverterString.read(from: &buf) ) - case 7: return .ProbeSendingFailed( + case 21: return .TxSyncFailed( message: try FfiConverterString.read(from: &buf) ) - case 8: return .ChannelCreationFailed( + case 22: return .TxSyncTimeout( message: try FfiConverterString.read(from: &buf) ) - case 9: return .ChannelClosingFailed( + case 23: return .GossipUpdateFailed( message: try FfiConverterString.read(from: &buf) ) - case 10: return .ChannelConfigUpdateFailed( + case 24: return .GossipUpdateTimeout( message: try FfiConverterString.read(from: &buf) ) - case 11: return .PersistenceFailed( + case 25: return .LiquidityRequestFailed( message: try FfiConverterString.read(from: &buf) ) - case 12: return .FeerateEstimationUpdateFailed( + case 26: return .InvalidAddress( message: try FfiConverterString.read(from: &buf) ) - case 13: return .WalletOperationFailed( + case 27: return .InvalidSocketAddress( message: try FfiConverterString.read(from: &buf) ) - case 14: return .OnchainTxSigningFailed( + case 28: return .InvalidPublicKey( message: try FfiConverterString.read(from: &buf) ) - case 15: return .MessageSigningFailed( + case 29: return .InvalidSecretKey( message: try FfiConverterString.read(from: &buf) ) - case 16: return .TxSyncFailed( + case 30: return .InvalidOfferId( message: try FfiConverterString.read(from: &buf) ) - case 17: return .GossipUpdateFailed( + case 31: return .InvalidNodeId( message: try FfiConverterString.read(from: &buf) ) - case 18: return .InvalidAddress( + case 32: return .InvalidPaymentId( message: try FfiConverterString.read(from: &buf) ) - case 19: return .InvalidSocketAddress( + case 33: return .InvalidPaymentHash( message: try FfiConverterString.read(from: &buf) ) - case 20: return .InvalidPublicKey( + case 34: return .InvalidPaymentPreimage( message: try FfiConverterString.read(from: &buf) ) - case 21: return .InvalidSecretKey( + case 35: return .InvalidPaymentSecret( message: try FfiConverterString.read(from: &buf) ) - case 22: return .InvalidPaymentHash( + case 36: return .InvalidAmount( message: try FfiConverterString.read(from: &buf) ) - case 23: return .InvalidPaymentPreimage( + case 37: return .InvalidInvoice( message: try FfiConverterString.read(from: &buf) ) - case 24: return .InvalidPaymentSecret( + case 38: return .InvalidOffer( message: try FfiConverterString.read(from: &buf) ) - case 25: return .InvalidAmount( + case 39: return .InvalidRefund( message: try FfiConverterString.read(from: &buf) ) - case 26: return .InvalidInvoice( + case 40: return .InvalidChannelId( message: try FfiConverterString.read(from: &buf) ) - case 27: return .InvalidChannelId( + case 41: return .InvalidNetwork( message: try FfiConverterString.read(from: &buf) ) - case 28: return .InvalidNetwork( + case 42: return .DuplicatePayment( message: try FfiConverterString.read(from: &buf) ) - case 29: return .DuplicatePayment( + case 43: return .UnsupportedCurrency( message: try FfiConverterString.read(from: &buf) ) - case 30: return .InsufficientFunds( + case 44: return .InsufficientFunds( + message: try FfiConverterString.read(from: &buf) + ) + + case 45: return .LiquiditySourceUnavailable( + message: try FfiConverterString.read(from: &buf) + ) + + case 46: return .LiquidityFeeTooHigh( message: try FfiConverterString.read(from: &buf) ) @@ -2405,56 +4350,88 @@ public struct FfiConverterTypeNodeError: FfiConverterRustBuffer { writeInt(&buf, Int32(4)) case .InvoiceCreationFailed(_ /* message is ignored*/): writeInt(&buf, Int32(5)) - case .PaymentSendingFailed(_ /* message is ignored*/): + case .InvoiceRequestCreationFailed(_ /* message is ignored*/): writeInt(&buf, Int32(6)) - case .ProbeSendingFailed(_ /* message is ignored*/): + case .OfferCreationFailed(_ /* message is ignored*/): writeInt(&buf, Int32(7)) - case .ChannelCreationFailed(_ /* message is ignored*/): + case .RefundCreationFailed(_ /* message is ignored*/): writeInt(&buf, Int32(8)) - case .ChannelClosingFailed(_ /* message is ignored*/): + case .PaymentSendingFailed(_ /* message is ignored*/): writeInt(&buf, Int32(9)) + case .ProbeSendingFailed(_ /* message is ignored*/): + writeInt(&buf, Int32(10)) + case .ChannelCreationFailed(_ /* message is ignored*/): + writeInt(&buf, Int32(11)) + case .ChannelClosingFailed(_ /* message is ignored*/): + writeInt(&buf, Int32(12)) case .ChannelConfigUpdateFailed(_ /* message is ignored*/): - writeInt(&buf, Int32(10)) + writeInt(&buf, Int32(13)) case .PersistenceFailed(_ /* message is ignored*/): - writeInt(&buf, Int32(11)) + writeInt(&buf, Int32(14)) case .FeerateEstimationUpdateFailed(_ /* message is ignored*/): - writeInt(&buf, Int32(12)) + writeInt(&buf, Int32(15)) + case .FeerateEstimationUpdateTimeout(_ /* message is ignored*/): + writeInt(&buf, Int32(16)) case .WalletOperationFailed(_ /* message is ignored*/): - writeInt(&buf, Int32(13)) + writeInt(&buf, Int32(17)) + case .WalletOperationTimeout(_ /* message is ignored*/): + writeInt(&buf, Int32(18)) case .OnchainTxSigningFailed(_ /* message is ignored*/): - writeInt(&buf, Int32(14)) + writeInt(&buf, Int32(19)) case .MessageSigningFailed(_ /* message is ignored*/): - writeInt(&buf, Int32(15)) + writeInt(&buf, Int32(20)) case .TxSyncFailed(_ /* message is ignored*/): - writeInt(&buf, Int32(16)) + writeInt(&buf, Int32(21)) + case .TxSyncTimeout(_ /* message is ignored*/): + writeInt(&buf, Int32(22)) case .GossipUpdateFailed(_ /* message is ignored*/): - writeInt(&buf, Int32(17)) + writeInt(&buf, Int32(23)) + case .GossipUpdateTimeout(_ /* message is ignored*/): + writeInt(&buf, Int32(24)) + case .LiquidityRequestFailed(_ /* message is ignored*/): + writeInt(&buf, Int32(25)) case .InvalidAddress(_ /* message is ignored*/): - writeInt(&buf, Int32(18)) + writeInt(&buf, Int32(26)) case .InvalidSocketAddress(_ /* message is ignored*/): - writeInt(&buf, Int32(19)) + writeInt(&buf, Int32(27)) case .InvalidPublicKey(_ /* message is ignored*/): - writeInt(&buf, Int32(20)) + writeInt(&buf, Int32(28)) case .InvalidSecretKey(_ /* message is ignored*/): - writeInt(&buf, Int32(21)) + writeInt(&buf, Int32(29)) + case .InvalidOfferId(_ /* message is ignored*/): + writeInt(&buf, Int32(30)) + case .InvalidNodeId(_ /* message is ignored*/): + writeInt(&buf, Int32(31)) + case .InvalidPaymentId(_ /* message is ignored*/): + writeInt(&buf, Int32(32)) case .InvalidPaymentHash(_ /* message is ignored*/): - writeInt(&buf, Int32(22)) + writeInt(&buf, Int32(33)) case .InvalidPaymentPreimage(_ /* message is ignored*/): - writeInt(&buf, Int32(23)) + writeInt(&buf, Int32(34)) case .InvalidPaymentSecret(_ /* message is ignored*/): - writeInt(&buf, Int32(24)) + writeInt(&buf, Int32(35)) case .InvalidAmount(_ /* message is ignored*/): - writeInt(&buf, Int32(25)) + writeInt(&buf, Int32(36)) case .InvalidInvoice(_ /* message is ignored*/): - writeInt(&buf, Int32(26)) + writeInt(&buf, Int32(37)) + case .InvalidOffer(_ /* message is ignored*/): + writeInt(&buf, Int32(38)) + case .InvalidRefund(_ /* message is ignored*/): + writeInt(&buf, Int32(39)) case .InvalidChannelId(_ /* message is ignored*/): - writeInt(&buf, Int32(27)) + writeInt(&buf, Int32(40)) case .InvalidNetwork(_ /* message is ignored*/): - writeInt(&buf, Int32(28)) + writeInt(&buf, Int32(41)) case .DuplicatePayment(_ /* message is ignored*/): - writeInt(&buf, Int32(29)) + writeInt(&buf, Int32(42)) + case .UnsupportedCurrency(_ /* message is ignored*/): + writeInt(&buf, Int32(43)) case .InsufficientFunds(_ /* message is ignored*/): - writeInt(&buf, Int32(30)) + writeInt(&buf, Int32(44)) + case .LiquiditySourceUnavailable(_ /* message is ignored*/): + writeInt(&buf, Int32(45)) + case .LiquidityFeeTooHigh(_ /* message is ignored*/): + writeInt(&buf, Int32(46)) } @@ -2518,6 +4495,224 @@ extension PaymentDirection: Equatable, Hashable {} +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +public enum PaymentFailureReason { + + case recipientRejected + case userAbandoned + case retriesExhausted + case paymentExpired + case routeNotFound + case unexpectedError +} + +public struct FfiConverterTypePaymentFailureReason: FfiConverterRustBuffer { + typealias SwiftType = PaymentFailureReason + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PaymentFailureReason { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .recipientRejected + + case 2: return .userAbandoned + + case 3: return .retriesExhausted + + case 4: return .paymentExpired + + case 5: return .routeNotFound + + case 6: return .unexpectedError + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: PaymentFailureReason, into buf: inout [UInt8]) { + switch value { + + + case .recipientRejected: + writeInt(&buf, Int32(1)) + + + case .userAbandoned: + writeInt(&buf, Int32(2)) + + + case .retriesExhausted: + writeInt(&buf, Int32(3)) + + + case .paymentExpired: + writeInt(&buf, Int32(4)) + + + case .routeNotFound: + writeInt(&buf, Int32(5)) + + + case .unexpectedError: + writeInt(&buf, Int32(6)) + + } + } +} + + +public func FfiConverterTypePaymentFailureReason_lift(_ buf: RustBuffer) throws -> PaymentFailureReason { + return try FfiConverterTypePaymentFailureReason.lift(buf) +} + +public func FfiConverterTypePaymentFailureReason_lower(_ value: PaymentFailureReason) -> RustBuffer { + return FfiConverterTypePaymentFailureReason.lower(value) +} + + +extension PaymentFailureReason: Equatable, Hashable {} + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +public enum PaymentKind { + + case onchain + case bolt11( + hash: PaymentHash, + preimage: PaymentPreimage?, + secret: PaymentSecret? + ) + case bolt11Jit( + hash: PaymentHash, + preimage: PaymentPreimage?, + secret: PaymentSecret?, + lspFeeLimits: LspFeeLimits + ) + case bolt12Offer( + hash: PaymentHash?, + preimage: PaymentPreimage?, + secret: PaymentSecret?, + offerId: OfferId + ) + case bolt12Refund( + hash: PaymentHash?, + preimage: PaymentPreimage?, + secret: PaymentSecret? + ) + case spontaneous( + hash: PaymentHash, + preimage: PaymentPreimage? + ) +} + +public struct FfiConverterTypePaymentKind: FfiConverterRustBuffer { + typealias SwiftType = PaymentKind + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PaymentKind { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .onchain + + case 2: return .bolt11( + hash: try FfiConverterTypePaymentHash.read(from: &buf), + preimage: try FfiConverterOptionTypePaymentPreimage.read(from: &buf), + secret: try FfiConverterOptionTypePaymentSecret.read(from: &buf) + ) + + case 3: return .bolt11Jit( + hash: try FfiConverterTypePaymentHash.read(from: &buf), + preimage: try FfiConverterOptionTypePaymentPreimage.read(from: &buf), + secret: try FfiConverterOptionTypePaymentSecret.read(from: &buf), + lspFeeLimits: try FfiConverterTypeLSPFeeLimits.read(from: &buf) + ) + + case 4: return .bolt12Offer( + hash: try FfiConverterOptionTypePaymentHash.read(from: &buf), + preimage: try FfiConverterOptionTypePaymentPreimage.read(from: &buf), + secret: try FfiConverterOptionTypePaymentSecret.read(from: &buf), + offerId: try FfiConverterTypeOfferId.read(from: &buf) + ) + + case 5: return .bolt12Refund( + hash: try FfiConverterOptionTypePaymentHash.read(from: &buf), + preimage: try FfiConverterOptionTypePaymentPreimage.read(from: &buf), + secret: try FfiConverterOptionTypePaymentSecret.read(from: &buf) + ) + + case 6: return .spontaneous( + hash: try FfiConverterTypePaymentHash.read(from: &buf), + preimage: try FfiConverterOptionTypePaymentPreimage.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: PaymentKind, into buf: inout [UInt8]) { + switch value { + + + case .onchain: + writeInt(&buf, Int32(1)) + + + case let .bolt11(hash,preimage,secret): + writeInt(&buf, Int32(2)) + FfiConverterTypePaymentHash.write(hash, into: &buf) + FfiConverterOptionTypePaymentPreimage.write(preimage, into: &buf) + FfiConverterOptionTypePaymentSecret.write(secret, into: &buf) + + + case let .bolt11Jit(hash,preimage,secret,lspFeeLimits): + writeInt(&buf, Int32(3)) + FfiConverterTypePaymentHash.write(hash, into: &buf) + FfiConverterOptionTypePaymentPreimage.write(preimage, into: &buf) + FfiConverterOptionTypePaymentSecret.write(secret, into: &buf) + FfiConverterTypeLSPFeeLimits.write(lspFeeLimits, into: &buf) + + + case let .bolt12Offer(hash,preimage,secret,offerId): + writeInt(&buf, Int32(4)) + FfiConverterOptionTypePaymentHash.write(hash, into: &buf) + FfiConverterOptionTypePaymentPreimage.write(preimage, into: &buf) + FfiConverterOptionTypePaymentSecret.write(secret, into: &buf) + FfiConverterTypeOfferId.write(offerId, into: &buf) + + + case let .bolt12Refund(hash,preimage,secret): + writeInt(&buf, Int32(5)) + FfiConverterOptionTypePaymentHash.write(hash, into: &buf) + FfiConverterOptionTypePaymentPreimage.write(preimage, into: &buf) + FfiConverterOptionTypePaymentSecret.write(secret, into: &buf) + + + case let .spontaneous(hash,preimage): + writeInt(&buf, Int32(6)) + FfiConverterTypePaymentHash.write(hash, into: &buf) + FfiConverterOptionTypePaymentPreimage.write(preimage, into: &buf) + + } + } +} + + +public func FfiConverterTypePaymentKind_lift(_ buf: RustBuffer) throws -> PaymentKind { + return try FfiConverterTypePaymentKind.lift(buf) +} + +public func FfiConverterTypePaymentKind_lower(_ value: PaymentKind) -> RustBuffer { + return FfiConverterTypePaymentKind.lower(value) +} + + +extension PaymentKind: Equatable, Hashable {} + + + // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. public enum PaymentStatus { @@ -2564,21 +4759,224 @@ public struct FfiConverterTypePaymentStatus: FfiConverterRustBuffer { } -public func FfiConverterTypePaymentStatus_lift(_ buf: RustBuffer) throws -> PaymentStatus { - return try FfiConverterTypePaymentStatus.lift(buf) -} +public func FfiConverterTypePaymentStatus_lift(_ buf: RustBuffer) throws -> PaymentStatus { + return try FfiConverterTypePaymentStatus.lift(buf) +} + +public func FfiConverterTypePaymentStatus_lower(_ value: PaymentStatus) -> RustBuffer { + return FfiConverterTypePaymentStatus.lower(value) +} + + +extension PaymentStatus: Equatable, Hashable {} + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +public enum PendingSweepBalance { + + case pendingBroadcast( + channelId: ChannelId?, + amountSatoshis: UInt64 + ) + case broadcastAwaitingConfirmation( + channelId: ChannelId?, + latestBroadcastHeight: UInt32, + latestSpendingTxid: Txid, + amountSatoshis: UInt64 + ) + case awaitingThresholdConfirmations( + channelId: ChannelId?, + latestSpendingTxid: Txid, + confirmationHash: BlockHash, + confirmationHeight: UInt32, + amountSatoshis: UInt64 + ) +} + +public struct FfiConverterTypePendingSweepBalance: FfiConverterRustBuffer { + typealias SwiftType = PendingSweepBalance + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PendingSweepBalance { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .pendingBroadcast( + channelId: try FfiConverterOptionTypeChannelId.read(from: &buf), + amountSatoshis: try FfiConverterUInt64.read(from: &buf) + ) + + case 2: return .broadcastAwaitingConfirmation( + channelId: try FfiConverterOptionTypeChannelId.read(from: &buf), + latestBroadcastHeight: try FfiConverterUInt32.read(from: &buf), + latestSpendingTxid: try FfiConverterTypeTxid.read(from: &buf), + amountSatoshis: try FfiConverterUInt64.read(from: &buf) + ) + + case 3: return .awaitingThresholdConfirmations( + channelId: try FfiConverterOptionTypeChannelId.read(from: &buf), + latestSpendingTxid: try FfiConverterTypeTxid.read(from: &buf), + confirmationHash: try FfiConverterTypeBlockHash.read(from: &buf), + confirmationHeight: try FfiConverterUInt32.read(from: &buf), + amountSatoshis: try FfiConverterUInt64.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: PendingSweepBalance, into buf: inout [UInt8]) { + switch value { + + + case let .pendingBroadcast(channelId,amountSatoshis): + writeInt(&buf, Int32(1)) + FfiConverterOptionTypeChannelId.write(channelId, into: &buf) + FfiConverterUInt64.write(amountSatoshis, into: &buf) + + + case let .broadcastAwaitingConfirmation(channelId,latestBroadcastHeight,latestSpendingTxid,amountSatoshis): + writeInt(&buf, Int32(2)) + FfiConverterOptionTypeChannelId.write(channelId, into: &buf) + FfiConverterUInt32.write(latestBroadcastHeight, into: &buf) + FfiConverterTypeTxid.write(latestSpendingTxid, into: &buf) + FfiConverterUInt64.write(amountSatoshis, into: &buf) + + + case let .awaitingThresholdConfirmations(channelId,latestSpendingTxid,confirmationHash,confirmationHeight,amountSatoshis): + writeInt(&buf, Int32(3)) + FfiConverterOptionTypeChannelId.write(channelId, into: &buf) + FfiConverterTypeTxid.write(latestSpendingTxid, into: &buf) + FfiConverterTypeBlockHash.write(confirmationHash, into: &buf) + FfiConverterUInt32.write(confirmationHeight, into: &buf) + FfiConverterUInt64.write(amountSatoshis, into: &buf) + + } + } +} + + +public func FfiConverterTypePendingSweepBalance_lift(_ buf: RustBuffer) throws -> PendingSweepBalance { + return try FfiConverterTypePendingSweepBalance.lift(buf) +} + +public func FfiConverterTypePendingSweepBalance_lower(_ value: PendingSweepBalance) -> RustBuffer { + return FfiConverterTypePendingSweepBalance.lower(value) +} + + +extension PendingSweepBalance: Equatable, Hashable {} + + + +fileprivate struct FfiConverterOptionUInt16: FfiConverterRustBuffer { + typealias SwiftType = UInt16? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterUInt16.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterUInt16.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +fileprivate struct FfiConverterOptionUInt32: FfiConverterRustBuffer { + typealias SwiftType = UInt32? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterUInt32.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterUInt32.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +fileprivate struct FfiConverterOptionUInt64: FfiConverterRustBuffer { + typealias SwiftType = UInt64? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterUInt64.write(value, into: &buf) + } -public func FfiConverterTypePaymentStatus_lower(_ value: PaymentStatus) -> RustBuffer { - return FfiConverterTypePaymentStatus.lower(value) + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterUInt64.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } } +fileprivate struct FfiConverterOptionString: FfiConverterRustBuffer { + typealias SwiftType = String? -extension PaymentStatus: Equatable, Hashable {} + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterString.write(value, into: &buf) + } + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterString.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} +fileprivate struct FfiConverterOptionTypeChannelConfig: FfiConverterRustBuffer { + typealias SwiftType = ChannelConfig? -fileprivate struct FfiConverterOptionUInt16: FfiConverterRustBuffer { - typealias SwiftType = UInt16? + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypeChannelConfig.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypeChannelConfig.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +fileprivate struct FfiConverterOptionTypeAnchorChannelsConfig: FfiConverterRustBuffer { + typealias SwiftType = AnchorChannelsConfig? public static func write(_ value: SwiftType, into buf: inout [UInt8]) { guard let value = value else { @@ -2586,20 +4984,20 @@ fileprivate struct FfiConverterOptionUInt16: FfiConverterRustBuffer { return } writeInt(&buf, Int8(1)) - FfiConverterUInt16.write(value, into: &buf) + FfiConverterTypeAnchorChannelsConfig.write(value, into: &buf) } public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { switch try readInt(&buf) as Int8 { case 0: return nil - case 1: return try FfiConverterUInt16.read(from: &buf) + case 1: return try FfiConverterTypeAnchorChannelsConfig.read(from: &buf) default: throw UniffiInternalError.unexpectedOptionalTag } } } -fileprivate struct FfiConverterOptionUInt32: FfiConverterRustBuffer { - typealias SwiftType = UInt32? +fileprivate struct FfiConverterOptionTypeChannelInfo: FfiConverterRustBuffer { + typealias SwiftType = ChannelInfo? public static func write(_ value: SwiftType, into buf: inout [UInt8]) { guard let value = value else { @@ -2607,20 +5005,20 @@ fileprivate struct FfiConverterOptionUInt32: FfiConverterRustBuffer { return } writeInt(&buf, Int8(1)) - FfiConverterUInt32.write(value, into: &buf) + FfiConverterTypeChannelInfo.write(value, into: &buf) } public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { switch try readInt(&buf) as Int8 { case 0: return nil - case 1: return try FfiConverterUInt32.read(from: &buf) + case 1: return try FfiConverterTypeChannelInfo.read(from: &buf) default: throw UniffiInternalError.unexpectedOptionalTag } } } -fileprivate struct FfiConverterOptionUInt64: FfiConverterRustBuffer { - typealias SwiftType = UInt64? +fileprivate struct FfiConverterOptionTypeChannelUpdateInfo: FfiConverterRustBuffer { + typealias SwiftType = ChannelUpdateInfo? public static func write(_ value: SwiftType, into buf: inout [UInt8]) { guard let value = value else { @@ -2628,20 +5026,20 @@ fileprivate struct FfiConverterOptionUInt64: FfiConverterRustBuffer { return } writeInt(&buf, Int8(1)) - FfiConverterUInt64.write(value, into: &buf) + FfiConverterTypeChannelUpdateInfo.write(value, into: &buf) } public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { switch try readInt(&buf) as Int8 { case 0: return nil - case 1: return try FfiConverterUInt64.read(from: &buf) + case 1: return try FfiConverterTypeChannelUpdateInfo.read(from: &buf) default: throw UniffiInternalError.unexpectedOptionalTag } } } -fileprivate struct FfiConverterOptionString: FfiConverterRustBuffer { - typealias SwiftType = String? +fileprivate struct FfiConverterOptionTypeNodeAnnouncementInfo: FfiConverterRustBuffer { + typealias SwiftType = NodeAnnouncementInfo? public static func write(_ value: SwiftType, into buf: inout [UInt8]) { guard let value = value else { @@ -2649,20 +5047,20 @@ fileprivate struct FfiConverterOptionString: FfiConverterRustBuffer { return } writeInt(&buf, Int8(1)) - FfiConverterString.write(value, into: &buf) + FfiConverterTypeNodeAnnouncementInfo.write(value, into: &buf) } public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { switch try readInt(&buf) as Int8 { case 0: return nil - case 1: return try FfiConverterString.read(from: &buf) + case 1: return try FfiConverterTypeNodeAnnouncementInfo.read(from: &buf) default: throw UniffiInternalError.unexpectedOptionalTag } } } -fileprivate struct FfiConverterOptionTypeChannelConfig: FfiConverterRustBuffer { - typealias SwiftType = ChannelConfig? +fileprivate struct FfiConverterOptionTypeNodeInfo: FfiConverterRustBuffer { + typealias SwiftType = NodeInfo? public static func write(_ value: SwiftType, into buf: inout [UInt8]) { guard let value = value else { @@ -2670,13 +5068,13 @@ fileprivate struct FfiConverterOptionTypeChannelConfig: FfiConverterRustBuffer { return } writeInt(&buf, Int8(1)) - FfiConverterTypeChannelConfig.write(value, into: &buf) + FfiConverterTypeNodeInfo.write(value, into: &buf) } public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { switch try readInt(&buf) as Int8 { case 0: return nil - case 1: return try FfiConverterTypeChannelConfig.read(from: &buf) + case 1: return try FfiConverterTypeNodeInfo.read(from: &buf) default: throw UniffiInternalError.unexpectedOptionalTag } } @@ -2724,6 +5122,27 @@ fileprivate struct FfiConverterOptionTypePaymentDetails: FfiConverterRustBuffer } } +fileprivate struct FfiConverterOptionTypeClosureReason: FfiConverterRustBuffer { + typealias SwiftType = ClosureReason? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypeClosureReason.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypeClosureReason.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + fileprivate struct FfiConverterOptionTypeEvent: FfiConverterRustBuffer { typealias SwiftType = Event? @@ -2745,6 +5164,27 @@ fileprivate struct FfiConverterOptionTypeEvent: FfiConverterRustBuffer { } } +fileprivate struct FfiConverterOptionTypePaymentFailureReason: FfiConverterRustBuffer { + typealias SwiftType = PaymentFailureReason? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypePaymentFailureReason.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypePaymentFailureReason.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + fileprivate struct FfiConverterOptionSequenceTypeSocketAddress: FfiConverterRustBuffer { typealias SwiftType = [SocketAddress]? @@ -2766,6 +5206,69 @@ fileprivate struct FfiConverterOptionSequenceTypeSocketAddress: FfiConverterRust } } +fileprivate struct FfiConverterOptionTypeChannelId: FfiConverterRustBuffer { + typealias SwiftType = ChannelId? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypeChannelId.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypeChannelId.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +fileprivate struct FfiConverterOptionTypePaymentHash: FfiConverterRustBuffer { + typealias SwiftType = PaymentHash? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypePaymentHash.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypePaymentHash.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +fileprivate struct FfiConverterOptionTypePaymentId: FfiConverterRustBuffer { + typealias SwiftType = PaymentId? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypePaymentId.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypePaymentId.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + fileprivate struct FfiConverterOptionTypePaymentPreimage: FfiConverterRustBuffer { typealias SwiftType = PaymentPreimage? @@ -2851,6 +5354,28 @@ fileprivate struct FfiConverterSequenceUInt8: FfiConverterRustBuffer { } } +fileprivate struct FfiConverterSequenceUInt64: FfiConverterRustBuffer { + typealias SwiftType = [UInt64] + + public static func write(_ value: [UInt64], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterUInt64.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [UInt64] { + let len: Int32 = try readInt(&buf) + var seq = [UInt64]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterUInt64.read(from: &buf)) + } + return seq + } +} + fileprivate struct FfiConverterSequenceTypeChannelDetails: FfiConverterRustBuffer { typealias SwiftType = [ChannelDetails] @@ -2917,6 +5442,72 @@ fileprivate struct FfiConverterSequenceTypePeerDetails: FfiConverterRustBuffer { } } +fileprivate struct FfiConverterSequenceTypeLightningBalance: FfiConverterRustBuffer { + typealias SwiftType = [LightningBalance] + + public static func write(_ value: [LightningBalance], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeLightningBalance.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [LightningBalance] { + let len: Int32 = try readInt(&buf) + var seq = [LightningBalance]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeLightningBalance.read(from: &buf)) + } + return seq + } +} + +fileprivate struct FfiConverterSequenceTypePendingSweepBalance: FfiConverterRustBuffer { + typealias SwiftType = [PendingSweepBalance] + + public static func write(_ value: [PendingSweepBalance], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypePendingSweepBalance.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [PendingSweepBalance] { + let len: Int32 = try readInt(&buf) + var seq = [PendingSweepBalance]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypePendingSweepBalance.read(from: &buf)) + } + return seq + } +} + +fileprivate struct FfiConverterSequenceTypeNodeId: FfiConverterRustBuffer { + typealias SwiftType = [NodeId] + + public static func write(_ value: [NodeId], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeNodeId.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [NodeId] { + let len: Int32 = try readInt(&buf) + var seq = [NodeId]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeNodeId.read(from: &buf)) + } + return seq + } +} + fileprivate struct FfiConverterSequenceTypePublicKey: FfiConverterRustBuffer { typealias SwiftType = [PublicKey] @@ -2966,32 +5557,66 @@ fileprivate struct FfiConverterSequenceTypeSocketAddress: FfiConverterRustBuffer * Typealias from the type name used in the UDL file to the builtin type. This * is needed because the UDL type name is used in function/method signatures. */ -public typealias Address = String -public struct FfiConverterTypeAddress: FfiConverter { - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Address { +public typealias Address = String +public struct FfiConverterTypeAddress: FfiConverter { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Address { + return try FfiConverterString.read(from: &buf) + } + + public static func write(_ value: Address, into buf: inout [UInt8]) { + return FfiConverterString.write(value, into: &buf) + } + + public static func lift(_ value: RustBuffer) throws -> Address { + return try FfiConverterString.lift(value) + } + + public static func lower(_ value: Address) -> RustBuffer { + return FfiConverterString.lower(value) + } +} + + +public func FfiConverterTypeAddress_lift(_ value: RustBuffer) throws -> Address { + return try FfiConverterTypeAddress.lift(value) +} + +public func FfiConverterTypeAddress_lower(_ value: Address) -> RustBuffer { + return FfiConverterTypeAddress.lower(value) +} + + + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias BlockHash = String +public struct FfiConverterTypeBlockHash: FfiConverter { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BlockHash { return try FfiConverterString.read(from: &buf) } - public static func write(_ value: Address, into buf: inout [UInt8]) { + public static func write(_ value: BlockHash, into buf: inout [UInt8]) { return FfiConverterString.write(value, into: &buf) } - public static func lift(_ value: RustBuffer) throws -> Address { + public static func lift(_ value: RustBuffer) throws -> BlockHash { return try FfiConverterString.lift(value) } - public static func lower(_ value: Address) -> RustBuffer { + public static func lower(_ value: BlockHash) -> RustBuffer { return FfiConverterString.lower(value) } } -public func FfiConverterTypeAddress_lift(_ value: RustBuffer) throws -> Address { - return try FfiConverterTypeAddress.lift(value) +public func FfiConverterTypeBlockHash_lift(_ value: RustBuffer) throws -> BlockHash { + return try FfiConverterTypeBlockHash.lift(value) } -public func FfiConverterTypeAddress_lower(_ value: Address) -> RustBuffer { - return FfiConverterTypeAddress.lower(value) +public func FfiConverterTypeBlockHash_lower(_ value: BlockHash) -> RustBuffer { + return FfiConverterTypeBlockHash.lower(value) } @@ -3030,6 +5655,40 @@ public func FfiConverterTypeBolt11Invoice_lower(_ value: Bolt11Invoice) -> RustB +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias Bolt12Invoice = String +public struct FfiConverterTypeBolt12Invoice: FfiConverter { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Bolt12Invoice { + return try FfiConverterString.read(from: &buf) + } + + public static func write(_ value: Bolt12Invoice, into buf: inout [UInt8]) { + return FfiConverterString.write(value, into: &buf) + } + + public static func lift(_ value: RustBuffer) throws -> Bolt12Invoice { + return try FfiConverterString.lift(value) + } + + public static func lower(_ value: Bolt12Invoice) -> RustBuffer { + return FfiConverterString.lower(value) + } +} + + +public func FfiConverterTypeBolt12Invoice_lift(_ value: RustBuffer) throws -> Bolt12Invoice { + return try FfiConverterTypeBolt12Invoice.lift(value) +} + +public func FfiConverterTypeBolt12Invoice_lower(_ value: Bolt12Invoice) -> RustBuffer { + return FfiConverterTypeBolt12Invoice.lower(value) +} + + + /** * Typealias from the type name used in the UDL file to the builtin type. This * is needed because the UDL type name is used in function/method signatures. @@ -3098,6 +5757,108 @@ public func FfiConverterTypeMnemonic_lower(_ value: Mnemonic) -> RustBuffer { +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias NodeId = String +public struct FfiConverterTypeNodeId: FfiConverter { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> NodeId { + return try FfiConverterString.read(from: &buf) + } + + public static func write(_ value: NodeId, into buf: inout [UInt8]) { + return FfiConverterString.write(value, into: &buf) + } + + public static func lift(_ value: RustBuffer) throws -> NodeId { + return try FfiConverterString.lift(value) + } + + public static func lower(_ value: NodeId) -> RustBuffer { + return FfiConverterString.lower(value) + } +} + + +public func FfiConverterTypeNodeId_lift(_ value: RustBuffer) throws -> NodeId { + return try FfiConverterTypeNodeId.lift(value) +} + +public func FfiConverterTypeNodeId_lower(_ value: NodeId) -> RustBuffer { + return FfiConverterTypeNodeId.lower(value) +} + + + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias Offer = String +public struct FfiConverterTypeOffer: FfiConverter { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Offer { + return try FfiConverterString.read(from: &buf) + } + + public static func write(_ value: Offer, into buf: inout [UInt8]) { + return FfiConverterString.write(value, into: &buf) + } + + public static func lift(_ value: RustBuffer) throws -> Offer { + return try FfiConverterString.lift(value) + } + + public static func lower(_ value: Offer) -> RustBuffer { + return FfiConverterString.lower(value) + } +} + + +public func FfiConverterTypeOffer_lift(_ value: RustBuffer) throws -> Offer { + return try FfiConverterTypeOffer.lift(value) +} + +public func FfiConverterTypeOffer_lower(_ value: Offer) -> RustBuffer { + return FfiConverterTypeOffer.lower(value) +} + + + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias OfferId = String +public struct FfiConverterTypeOfferId: FfiConverter { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> OfferId { + return try FfiConverterString.read(from: &buf) + } + + public static func write(_ value: OfferId, into buf: inout [UInt8]) { + return FfiConverterString.write(value, into: &buf) + } + + public static func lift(_ value: RustBuffer) throws -> OfferId { + return try FfiConverterString.lift(value) + } + + public static func lower(_ value: OfferId) -> RustBuffer { + return FfiConverterString.lower(value) + } +} + + +public func FfiConverterTypeOfferId_lift(_ value: RustBuffer) throws -> OfferId { + return try FfiConverterTypeOfferId.lift(value) +} + +public func FfiConverterTypeOfferId_lower(_ value: OfferId) -> RustBuffer { + return FfiConverterTypeOfferId.lower(value) +} + + + /** * Typealias from the type name used in the UDL file to the builtin type. This * is needed because the UDL type name is used in function/method signatures. @@ -3132,6 +5893,40 @@ public func FfiConverterTypePaymentHash_lower(_ value: PaymentHash) -> RustBuffe +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias PaymentId = String +public struct FfiConverterTypePaymentId: FfiConverter { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PaymentId { + return try FfiConverterString.read(from: &buf) + } + + public static func write(_ value: PaymentId, into buf: inout [UInt8]) { + return FfiConverterString.write(value, into: &buf) + } + + public static func lift(_ value: RustBuffer) throws -> PaymentId { + return try FfiConverterString.lift(value) + } + + public static func lower(_ value: PaymentId) -> RustBuffer { + return FfiConverterString.lower(value) + } +} + + +public func FfiConverterTypePaymentId_lift(_ value: RustBuffer) throws -> PaymentId { + return try FfiConverterTypePaymentId.lift(value) +} + +public func FfiConverterTypePaymentId_lower(_ value: PaymentId) -> RustBuffer { + return FfiConverterTypePaymentId.lower(value) +} + + + /** * Typealias from the type name used in the UDL file to the builtin type. This * is needed because the UDL type name is used in function/method signatures. @@ -3234,6 +6029,40 @@ public func FfiConverterTypePublicKey_lower(_ value: PublicKey) -> RustBuffer { +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias Refund = String +public struct FfiConverterTypeRefund: FfiConverter { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Refund { + return try FfiConverterString.read(from: &buf) + } + + public static func write(_ value: Refund, into buf: inout [UInt8]) { + return FfiConverterString.write(value, into: &buf) + } + + public static func lift(_ value: RustBuffer) throws -> Refund { + return try FfiConverterString.lift(value) + } + + public static func lower(_ value: Refund) -> RustBuffer { + return FfiConverterString.lower(value) + } +} + + +public func FfiConverterTypeRefund_lift(_ value: RustBuffer) throws -> Refund { + return try FfiConverterTypeRefund.lift(value) +} + +public func FfiConverterTypeRefund_lower(_ value: Refund) -> RustBuffer { + return FfiConverterTypeRefund.lower(value) +} + + + /** * Typealias from the type name used in the UDL file to the builtin type. This * is needed because the UDL type name is used in function/method signatures. @@ -3302,6 +6131,40 @@ public func FfiConverterTypeTxid_lower(_ value: Txid) -> RustBuffer { +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias UntrustedString = String +public struct FfiConverterTypeUntrustedString: FfiConverter { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UntrustedString { + return try FfiConverterString.read(from: &buf) + } + + public static func write(_ value: UntrustedString, into buf: inout [UInt8]) { + return FfiConverterString.write(value, into: &buf) + } + + public static func lift(_ value: RustBuffer) throws -> UntrustedString { + return try FfiConverterString.lift(value) + } + + public static func lower(_ value: UntrustedString) -> RustBuffer { + return FfiConverterString.lower(value) + } +} + + +public func FfiConverterTypeUntrustedString_lift(_ value: RustBuffer) throws -> UntrustedString { + return try FfiConverterTypeUntrustedString.lift(value) +} + +public func FfiConverterTypeUntrustedString_lower(_ value: UntrustedString) -> RustBuffer { + return FfiConverterTypeUntrustedString.lower(value) +} + + + /** * Typealias from the type name used in the UDL file to the builtin type. This * is needed because the UDL type name is used in function/method signatures. @@ -3334,7 +6197,71 @@ public func FfiConverterTypeUserChannelId_lower(_ value: UserChannelId) -> RustB return FfiConverterTypeUserChannelId.lower(value) } +private let UNIFFI_RUST_FUTURE_POLL_READY: Int8 = 0 +private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1 + +fileprivate func uniffiRustCallAsync( + rustFutureFunc: () -> UnsafeMutableRawPointer, + pollFunc: (UnsafeMutableRawPointer, @escaping UniFfiRustFutureContinuation, UnsafeMutableRawPointer) -> (), + completeFunc: (UnsafeMutableRawPointer, UnsafeMutablePointer) -> F, + freeFunc: (UnsafeMutableRawPointer) -> (), + liftFunc: (F) throws -> T, + errorHandler: ((RustBuffer) throws -> Error)? +) async throws -> T { + // Make sure to call uniffiEnsureInitialized() since future creation doesn't have a + // RustCallStatus param, so doesn't use makeRustCall() + uniffiEnsureInitialized() + let rustFuture = rustFutureFunc() + defer { + freeFunc(rustFuture) + } + var pollResult: Int8; + repeat { + pollResult = await withUnsafeContinuation { + pollFunc(rustFuture, uniffiFutureContinuationCallback, ContinuationHolder($0).toOpaque()) + } + } while pollResult != UNIFFI_RUST_FUTURE_POLL_READY + + return try liftFunc(makeRustCall( + { completeFunc(rustFuture, $0) }, + errorHandler: errorHandler + )) +} + +// Callback handlers for an async calls. These are invoked by Rust when the future is ready. They +// lift the return value or error and resume the suspended function. +fileprivate func uniffiFutureContinuationCallback(ptr: UnsafeMutableRawPointer, pollResult: Int8) { + ContinuationHolder.fromOpaque(ptr).resume(pollResult) +} + +// Wraps UnsafeContinuation in a class so that we can use reference counting when passing it across +// the FFI +fileprivate class ContinuationHolder { + let continuation: UnsafeContinuation + + init(_ continuation: UnsafeContinuation) { + self.continuation = continuation + } + + func resume(_ pollResult: Int8) { + self.continuation.resume(returning: pollResult) + } + + func toOpaque() -> UnsafeMutableRawPointer { + return Unmanaged.passRetained(self).toOpaque() + } + static func fromOpaque(_ ptr: UnsafeRawPointer) -> ContinuationHolder { + return Unmanaged.fromOpaque(ptr).takeRetainedValue() + } +} +public func defaultConfig() -> Config { + return try! FfiConverterTypeConfig.lift( + try! rustCall() { + uniffi_ldk_node_fn_func_default_config($0) +} + ) +} public func generateEntropyMnemonic() -> Mnemonic { return try! FfiConverterTypeMnemonic.lift( try! rustCall() { @@ -3352,22 +6279,82 @@ private enum InitializationResult { // the code inside is only computed once. private var initializationResult: InitializationResult { // Get the bindings contract version from our ComponentInterface - let bindings_contract_version = 24 + let bindings_contract_version = 25 // Get the scaffolding contract version by calling the into the dylib let scaffolding_contract_version = ffi_ldk_node_uniffi_contract_version() if bindings_contract_version != scaffolding_contract_version { return InitializationResult.contractVersionMismatch } - if (uniffi_ldk_node_checksum_func_generate_entropy_mnemonic() != 7251) { + if (uniffi_ldk_node_checksum_func_default_config() != 55381) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_func_generate_entropy_mnemonic() != 59926) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_bolt11payment_claim_for_hash() != 52848) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_bolt11payment_fail_for_hash() != 24516) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_bolt11payment_receive() != 28084) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_bolt11payment_receive_for_hash() != 3869) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_bolt11payment_receive_variable_amount() != 51453) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_bolt11payment_receive_variable_amount_for_hash() != 21975) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_bolt11payment_receive_variable_amount_via_jit_channel() != 58617) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_bolt11payment_receive_via_jit_channel() != 50555) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_bolt11payment_send() != 35346) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_bolt11payment_send_probes() != 39625) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_bolt11payment_send_probes_using_amount() != 25010) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_bolt11payment_send_using_amount() != 15471) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_bolt12payment_initiate_refund() != 15379) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_bolt12payment_receive() != 20864) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_bolt12payment_receive_variable_amount() != 10863) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_bolt12payment_request_refund_payment() != 61945) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_bolt12payment_send() != 15282) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_bolt12payment_send_using_amount() != 21384) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_builder_build() != 785) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_builder_build() != 48294) { + if (uniffi_ldk_node_checksum_method_builder_build_with_fs_store() != 61304) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_builder_set_entropy_bip39_mnemonic() != 35659) { + if (uniffi_ldk_node_checksum_method_builder_set_entropy_bip39_mnemonic() != 827) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_builder_set_entropy_seed_bytes() != 26795) { + if (uniffi_ldk_node_checksum_method_builder_set_entropy_seed_bytes() != 44799) { return InitializationResult.apiChecksumMismatch } if (uniffi_ldk_node_checksum_method_builder_set_entropy_seed_path() != 64056) { @@ -3382,10 +6369,13 @@ private var initializationResult: InitializationResult { if (uniffi_ldk_node_checksum_method_builder_set_gossip_source_rgs() != 64312) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_builder_set_listening_addresses() != 18689) { + if (uniffi_ldk_node_checksum_method_builder_set_liquidity_source_lsps2() != 2667) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_builder_set_listening_addresses() != 14051) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_builder_set_network() != 23321) { + if (uniffi_ldk_node_checksum_method_builder_set_network() != 27539) { return InitializationResult.apiChecksumMismatch } if (uniffi_ldk_node_checksum_method_builder_set_storage_dir_path() != 59019) { @@ -3427,109 +6417,124 @@ private var initializationResult: InitializationResult { if (uniffi_ldk_node_checksum_method_channelconfig_set_max_dust_htlc_exposure_from_fixed_limit() != 16864) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_close_channel() != 7103) { + if (uniffi_ldk_node_checksum_method_networkgraph_channel() != 38070) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_networkgraph_list_channels() != 4693) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_networkgraph_list_nodes() != 36715) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_networkgraph_node() != 48925) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_node_bolt11_payment() != 41402) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_ldk_node_checksum_method_node_bolt12_payment() != 49254) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_connect() != 5558) { + if (uniffi_ldk_node_checksum_method_node_close_channel() != 62479) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_connect_open_channel() != 59688) { + if (uniffi_ldk_node_checksum_method_node_config() != 7511) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_disconnect() != 43777) { + if (uniffi_ldk_node_checksum_method_node_connect() != 34120) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_event_handled() != 28838) { + if (uniffi_ldk_node_checksum_method_node_connect_open_channel() != 64763) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_is_running() != 18666) { + if (uniffi_ldk_node_checksum_method_node_disconnect() != 43538) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_list_channels() != 43935) { + if (uniffi_ldk_node_checksum_method_node_event_handled() != 47939) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_list_payments() != 58666) { + if (uniffi_ldk_node_checksum_method_node_force_close_channel() != 44813) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_list_peers() != 22735) { + if (uniffi_ldk_node_checksum_method_node_list_balances() != 57528) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_listening_addresses() != 49178) { + if (uniffi_ldk_node_checksum_method_node_list_channels() != 7954) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_new_onchain_address() != 34077) { + if (uniffi_ldk_node_checksum_method_node_list_payments() != 35002) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_next_event() != 41150) { + if (uniffi_ldk_node_checksum_method_node_list_peers() != 14889) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_node_id() != 39688) { + if (uniffi_ldk_node_checksum_method_node_listening_addresses() != 2665) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_payment() != 35034) { + if (uniffi_ldk_node_checksum_method_node_network_graph() != 2695) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_receive_payment() != 4148) { + if (uniffi_ldk_node_checksum_method_node_next_event() != 7682) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_receive_variable_amount_payment() != 25209) { + if (uniffi_ldk_node_checksum_method_node_next_event_async() != 25426) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_remove_payment() != 12673) { + if (uniffi_ldk_node_checksum_method_node_node_id() != 51489) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_send_all_to_onchain_address() != 24019) { + if (uniffi_ldk_node_checksum_method_node_onchain_payment() != 6092) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_send_payment() != 56244) { + if (uniffi_ldk_node_checksum_method_node_payment() != 60296) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_send_payment_probes() != 38405) { + if (uniffi_ldk_node_checksum_method_node_remove_payment() != 47952) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_send_payment_probes_using_amount() != 340) { + if (uniffi_ldk_node_checksum_method_node_sign_message() != 51392) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_send_payment_using_amount() != 42148) { + if (uniffi_ldk_node_checksum_method_node_spontaneous_payment() != 37403) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_send_spontaneous_payment() != 39235) { + if (uniffi_ldk_node_checksum_method_node_start() != 58480) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_send_spontaneous_payment_probes() != 52786) { + if (uniffi_ldk_node_checksum_method_node_status() != 55952) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_send_to_onchain_address() != 43948) { + if (uniffi_ldk_node_checksum_method_node_stop() != 42188) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_sign_message() != 40383) { + if (uniffi_ldk_node_checksum_method_node_sync_wallets() != 32474) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_spendable_onchain_balance_sats() != 1454) { + if (uniffi_ldk_node_checksum_method_node_update_channel_config() != 38109) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_start() != 44334) { + if (uniffi_ldk_node_checksum_method_node_verify_signature() != 20486) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_stop() != 16494) { + if (uniffi_ldk_node_checksum_method_node_wait_next_event() != 55101) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_sync_wallets() != 2447) { + if (uniffi_ldk_node_checksum_method_onchainpayment_new_address() != 37251) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_total_onchain_balance_sats() != 44607) { + if (uniffi_ldk_node_checksum_method_onchainpayment_send_all_to_address() != 20046) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_update_channel_config() != 13742) { + if (uniffi_ldk_node_checksum_method_onchainpayment_send_to_address() != 34782) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_verify_signature() != 35778) { + if (uniffi_ldk_node_checksum_method_spontaneouspayment_send() != 16613) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_method_ldknode_wait_next_event() != 34319) { + if (uniffi_ldk_node_checksum_method_spontaneouspayment_send_probes() != 25937) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ldk_node_checksum_constructor_builder_from_config() != 56443) { + if (uniffi_ldk_node_checksum_constructor_builder_from_config() != 64393) { return InitializationResult.apiChecksumMismatch } if (uniffi_ldk_node_checksum_constructor_builder_new() != 48442) { @@ -3551,4 +6556,4 @@ private func uniffiEnsureInitialized() { case .apiChecksumMismatch: fatalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } -} +} \ No newline at end of file diff --git a/scripts/uniffi_bindgen_generate_swift.sh b/scripts/uniffi_bindgen_generate_swift.sh index 277d23209..ba2d84d2a 100755 --- a/scripts/uniffi_bindgen_generate_swift.sh +++ b/scripts/uniffi_bindgen_generate_swift.sh @@ -34,6 +34,10 @@ swiftc -module-name LDKNode -emit-library -o "$BINDINGS_DIR"/libldk_node.dylib - # Create xcframework from bindings Swift file and libs mkdir -p "$BINDINGS_DIR"/Sources/LDKNode || exit 1 + +# Patch LDKNode.swift with `SystemConfiguration` import. +sed -i '' '4s/^/import SystemConfiguration\n/' "$BINDINGS_DIR"/LDKNode.swift + mv "$BINDINGS_DIR"/LDKNode.swift "$BINDINGS_DIR"/Sources/LDKNode/LDKNode.swift || exit 1 cp "$BINDINGS_DIR"/LDKNodeFFI.h "$BINDINGS_DIR"/LDKNodeFFI.xcframework/ios-arm64/LDKNodeFFI.framework/Headers || exit 1 cp "$BINDINGS_DIR"/LDKNodeFFI.h "$BINDINGS_DIR"/LDKNodeFFI.xcframework/ios-arm64_x86_64-simulator/LDKNodeFFI.framework/Headers || exit 1 @@ -41,6 +45,6 @@ cp "$BINDINGS_DIR"/LDKNodeFFI.h "$BINDINGS_DIR"/LDKNodeFFI.xcframework/macos-arm cp target/aarch64-apple-ios/release-smaller/libldk_node.a "$BINDINGS_DIR"/LDKNodeFFI.xcframework/ios-arm64/LDKNodeFFI.framework/LDKNodeFFI || exit 1 cp target/lipo-ios-sim/release-smaller/libldk_node.a "$BINDINGS_DIR"/LDKNodeFFI.xcframework/ios-arm64_x86_64-simulator/LDKNodeFFI.framework/LDKNodeFFI || exit 1 cp target/lipo-macos/release-smaller/libldk_node.a "$BINDINGS_DIR"/LDKNodeFFI.xcframework/macos-arm64_x86_64/LDKNodeFFI.framework/LDKNodeFFI || exit 1 -# rm "$BINDINGS_DIR"/LDKNodeFFI.h || exit 1 -# rm "$BINDINGS_DIR"/LDKNodeFFI.modulemap || exit 1 +rm "$BINDINGS_DIR"/LDKNodeFFI.h || exit 1 +rm "$BINDINGS_DIR"/LDKNodeFFI.modulemap || exit 1 echo finished successfully! diff --git a/src/balance.rs b/src/balance.rs index 9591be785..f1c95dcbe 100644 --- a/src/balance.rs +++ b/src/balance.rs @@ -21,7 +21,7 @@ pub struct BalanceDetails { /// /// [`total_anchor_channels_reserve_sats`]: Self::total_anchor_channels_reserve_sats pub spendable_onchain_balance_sats: u64, - /// The share of our total balance which we retain as an emergency reserve to (hopefully) be + /// The share of our total balance that we retain as an emergency reserve to (hopefully) be /// able to spend the Anchor outputs when one of our channels is closed. pub total_anchor_channels_reserve_sats: u64, /// The total balance that we would be able to claim across all our Lightning channels. diff --git a/src/builder.rs b/src/builder.rs index 2400e96c6..1ab3c7b4d 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,6 +1,6 @@ use crate::config::{ - Config, BDK_CLIENT_CONCURRENCY, BDK_CLIENT_STOP_GAP, DEFAULT_ESPLORA_SERVER_URL, - WALLET_KEYS_SEED_LEN, + default_user_config, Config, BDK_CLIENT_CONCURRENCY, BDK_CLIENT_STOP_GAP, + DEFAULT_ESPLORA_CLIENT_TIMEOUT_SECS, DEFAULT_ESPLORA_SERVER_URL, WALLET_KEYS_SEED_LEN, }; use crate::connection::ConnectionManager; use crate::event::EventQueue; @@ -31,7 +31,6 @@ use lightning::routing::scoring::{ }; use lightning::sign::EntropySource; -use lightning::util::config::UserConfig; use lightning::util::persist::{ read_channel_monitors, CHANNEL_MANAGER_PERSISTENCE_KEY, CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE, CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE, @@ -559,10 +558,15 @@ fn build_with_store_internal( let (blockchain, tx_sync, tx_broadcaster, fee_estimator) = match chain_data_source_config { Some(ChainDataSourceConfig::Esplora(server_url)) => { - let tx_sync = Arc::new(EsploraSyncClient::new(server_url.clone(), Arc::clone(&logger))); - let blockchain = - EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP) - .with_concurrency(BDK_CLIENT_CONCURRENCY); + let mut client_builder = esplora_client::Builder::new(&server_url.clone()); + client_builder = client_builder.timeout(DEFAULT_ESPLORA_CLIENT_TIMEOUT_SECS); + let esplora_client = client_builder.build_async().unwrap(); + let tx_sync = Arc::new(EsploraSyncClient::from_client( + esplora_client.clone(), + Arc::clone(&logger), + )); + let blockchain = EsploraBlockchain::from_client(esplora_client, BDK_CLIENT_STOP_GAP) + .with_concurrency(BDK_CLIENT_CONCURRENCY); let tx_broadcaster = Arc::new(TransactionBroadcaster::new( tx_sync.client().clone(), Arc::clone(&logger), @@ -695,19 +699,7 @@ fn build_with_store_internal( }, }; - // Initialize the default config values. - // - // Note that methods such as Node::connect_open_channel might override some of the values set - // here, e.g. the ChannelHandshakeConfig, meaning these default values will mostly be relevant - // for inbound channels. - let mut user_config = UserConfig::default(); - user_config.channel_handshake_limits.force_announced_channel_preference = false; - user_config.manually_accept_inbound_channels = true; - // Note the channel_handshake_config will be overwritten in `connect_open_channel`, but we - // still set a default here. - user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = - config.anchor_channels_config.is_some(); - + let mut user_config = default_user_config(&config); if liquidity_source_config.and_then(|lsc| lsc.lsps2_service.as_ref()).is_some() { // Generally allow claiming underpaying HTLCs as the LSP will skim off some fee. We'll // check that they don't take too much before claiming. @@ -793,7 +785,7 @@ fn build_with_store_internal( Arc::clone(&logger), Arc::clone(&channel_manager), Arc::new(message_router), - IgnoringMessageHandler {}, + Arc::clone(&channel_manager), IgnoringMessageHandler {}, )); let ephemeral_bytes: [u8; 32] = keys_manager.get_secure_random_bytes(); @@ -989,6 +981,7 @@ fn build_with_store_internal( let latest_fee_rate_cache_update_timestamp = Arc::new(RwLock::new(None)); let latest_rgs_snapshot_timestamp = Arc::new(RwLock::new(None)); let latest_node_announcement_broadcast_timestamp = Arc::new(RwLock::new(None)); + let latest_channel_monitor_archival_height = Arc::new(RwLock::new(None)); Ok(Node { runtime, @@ -1021,6 +1014,7 @@ fn build_with_store_internal( latest_fee_rate_cache_update_timestamp, latest_rgs_snapshot_timestamp, latest_node_announcement_broadcast_timestamp, + latest_channel_monitor_archival_height, }) } diff --git a/src/config.rs b/src/config.rs index 69aeb5f79..b87027a00 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ use std::time::Duration; use lightning::ln::msgs::SocketAddress; +use lightning::util::config::UserConfig; use lightning::util::logger::Level as LogLevel; use bitcoin::secp256k1::PublicKey; @@ -30,9 +31,15 @@ pub(crate) const BDK_CLIENT_CONCURRENCY: u8 = 4; // The default Esplora server we're using. pub(crate) const DEFAULT_ESPLORA_SERVER_URL: &str = "https://blockstream.info/api"; +// The default Esplora client timeout we're using. +pub(crate) const DEFAULT_ESPLORA_CLIENT_TIMEOUT_SECS: u64 = 10; + // The timeout after which we abandon retrying failed payments. pub(crate) const LDK_PAYMENT_RETRY_TIMEOUT: Duration = Duration::from_secs(10); +// The interval (in block height) after which we retry archiving fully resolved channel monitors. +pub(crate) const RESOLVED_CHANNEL_MONITOR_ARCHIVAL_INTERVAL: u32 = 6; + // The time in-between peer reconnection attempts. pub(crate) const PEER_RECONNECTION_INTERVAL: Duration = Duration::from_secs(10); @@ -54,6 +61,9 @@ pub(crate) const LDK_WALLET_SYNC_TIMEOUT_SECS: u64 = 90; // 30 // The timeout after which we abort a fee rate cache update operation. pub(crate) const FEE_RATE_CACHE_UPDATE_TIMEOUT_SECS: u64 = 5; +// The timeout after which we abort a transaction broadcast operation. +pub(crate) const TX_BROADCAST_TIMEOUT_SECS: u64 = 5; + // The timeout after which we abort a RGS sync operation. pub(crate) const RGS_SYNC_TIMEOUT_SECS: u64 = 5; @@ -128,11 +138,13 @@ pub struct Config { /// /// Please refer to [`AnchorChannelsConfig`] for further information on Anchor channels. /// - /// If set to `Some`, new channels will have Anchors enabled, i.e., will be negotiated with the - /// `option_anchors_zero_fee_htlc_tx` channel type. If set to `None`, new channels will be - /// negotiated with the legacy `option_static_remotekey` channel type. + /// If set to `Some`, we'll try to open new channels with Anchors enabled, i.e., new channels + /// will be negotiated with the `option_anchors_zero_fee_htlc_tx` channel type if supported by + /// the counterparty. Note that this won't prevent us from opening non-Anchor channels if the + /// counterparty doesn't support `option_anchors_zero_fee_htlc_tx`. If set to `None`, new + /// channels will be negotiated with the legacy `option_static_remotekey` channel type only. /// - /// **Note:** Please note that if set to `None` *after* some Anchor channels have already been + /// **Note:** If set to `None` *after* some Anchor channels have already been /// opened, no dedicated emergency on-chain reserve will be maintained for these channels, /// which can be dangerous if only insufficient funds are available at the time of channel /// closure. We *will* however still try to get the Anchor spending transactions confirmed @@ -167,11 +179,12 @@ impl Default for Config { /// opening. This required to estimate what fee rate would be sufficient to still have the /// closing transactions be spendable on-chain (i.e., not be considered dust). This legacy /// design of pre-anchor channels proved inadequate in the unpredictable, often turbulent, fee -/// markets we experience today. In contrast, Anchor channels allow to determine an adequate -/// fee rate *at the time of channel closure*, making them much more robust in the face of fee -/// spikes. In turn, they require to maintain a reserve of on-chain funds to be able to get the -/// channel closure transactions confirmed on-chain, at least if the channel counterparty can't -/// be trusted to do this for us. +/// markets we experience today. +/// +/// In contrast, Anchor channels allow to determine an adequate fee rate *at the time of channel +/// closure*, making them much more robust in the face of fee spikes. In turn, they require to +/// maintain a reserve of on-chain funds to have the channel closure transactions confirmed +/// on-chain, at least if the channel counterparty can't be trusted to do this for us. /// /// See [BOLT 3] for more technical details on Anchor channels. /// @@ -187,7 +200,7 @@ impl Default for Config { /// [BOLT 3]: https://github.com/lightning/bolts/blob/master/03-transactions.md#htlc-timeout-and-htlc-success-transactions #[derive(Debug, Clone)] pub struct AnchorChannelsConfig { - /// A list of peers which we trust to get the required channel closing transactions confirmed + /// A list of peers that we trust to get the required channel closing transactions confirmed /// on-chain. /// /// Channels with these peers won't count towards the retained on-chain reserve and we won't @@ -199,9 +212,20 @@ pub struct AnchorChannelsConfig { /// funds stuck *or* even allow the counterparty to steal any in-flight funds after the /// corresponding HTLCs time out. pub trusted_peers_no_reserve: Vec, - /// The amount of satoshis we keep as an emergency reserve in our on-chain wallet in order to - /// be able to get the required Anchor output spending and HTLC transactions confirmed when the - /// channel is closed. + /// The amount of satoshis per anchors-negotiated channel with an untrusted peer that we keep + /// as an emergency reserve in our on-chain wallet. + /// + /// This allows for having the required Anchor output spending and HTLC transactions confirmed + /// when the channel is closed. + /// + /// If the channel peer is not marked as trusted via + /// [`AnchorChannelsConfig::trusted_peers_no_reserve`], we will always try to spend the Anchor + /// outputs with *any* on-chain funds available, i.e., the total reserve value as well as any + /// spendable funds available in the on-chain wallet. Therefore, this per-channel multiplier is + /// really a emergencey reserve that we maintain at all time to reduce reduce the risk of + /// insufficient funds at time of a channel closure. To this end, we will refuse to open + /// outbound or accept inbound channels if we don't have sufficient on-chain funds availble to + /// cover the additional reserve requirement. /// /// **Note:** Depending on the fee market at the time of closure, this reserve amount might or /// might not suffice to successfully spend the Anchor output and have the HTLC transactions @@ -227,3 +251,18 @@ impl Default for AnchorChannelsConfig { pub fn default_config() -> Config { Config::default() } + +pub(crate) fn default_user_config(config: &Config) -> UserConfig { + // Initialize the default config values. + // + // Note that methods such as Node::connect_open_channel might override some of the values set + // here, e.g. the ChannelHandshakeConfig, meaning these default values will mostly be relevant + // for inbound channels. + let mut user_config = UserConfig::default(); + user_config.channel_handshake_limits.force_announced_channel_preference = false; + user_config.manually_accept_inbound_channels = true; + user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = + config.anchor_channels_config.is_some(); + + user_config +} diff --git a/src/error.rs b/src/error.rs index 8877c8924..15aa5a960 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,6 +13,12 @@ pub enum Error { ConnectionFailed, /// Invoice creation failed. InvoiceCreationFailed, + /// Invoice request creation failed. + InvoiceRequestCreationFailed, + /// Offer creation failed. + OfferCreationFailed, + /// Refund creation failed. + RefundCreationFailed, /// Sending a payment has failed. PaymentSendingFailed, /// Sending a payment probe has failed. @@ -39,6 +45,8 @@ pub enum Error { MessageSigningFailed, /// A transaction sync operation failed. TxSyncFailed, + /// A transaction sync operation timed out. + TxSyncTimeout, /// A gossip updating operation failed. GossipUpdateFailed, /// A gossip updating operation timed out. @@ -53,6 +61,8 @@ pub enum Error { InvalidPublicKey, /// The given secret key is invalid. InvalidSecretKey, + /// The given offer id is invalid. + InvalidOfferId, /// The given node id is invalid. InvalidNodeId, /// The given payment id is invalid. @@ -67,6 +77,10 @@ pub enum Error { InvalidAmount, /// The given invoice is invalid. InvalidInvoice, + /// The given offer is invalid. + InvalidOffer, + /// The given refund is invalid. + InvalidRefund, /// The given channel ID is invalid. InvalidChannelId, /// The given network is invalid. @@ -75,6 +89,8 @@ pub enum Error { InvalidCustomTlv, /// A payment with the given hash has already been initiated. DuplicatePayment, + /// The provided offer was denonminated in an unsupported currency. + UnsupportedCurrency, /// The available funds are insufficient to complete the given operation. InsufficientFunds, /// The given operation failed due to the required liquidity source being unavailable. @@ -93,6 +109,9 @@ impl fmt::Display for Error { }, Self::ConnectionFailed => write!(f, "Network connection closed."), Self::InvoiceCreationFailed => write!(f, "Failed to create invoice."), + Self::InvoiceRequestCreationFailed => write!(f, "Failed to create invoice request."), + Self::OfferCreationFailed => write!(f, "Failed to create offer."), + Self::RefundCreationFailed => write!(f, "Failed to create refund."), Self::PaymentSendingFailed => write!(f, "Failed to send the given payment."), Self::ProbeSendingFailed => write!(f, "Failed to send the given payment probe."), Self::ChannelCreationFailed => write!(f, "Failed to create channel."), @@ -110,6 +129,7 @@ impl fmt::Display for Error { Self::OnchainTxSigningFailed => write!(f, "Failed to sign given transaction."), Self::MessageSigningFailed => write!(f, "Failed to sign given message."), Self::TxSyncFailed => write!(f, "Failed to sync transactions."), + Self::TxSyncTimeout => write!(f, "Syncing transactions timed out."), Self::GossipUpdateFailed => write!(f, "Failed to update gossip data."), Self::GossipUpdateTimeout => write!(f, "Updating gossip data timed out."), Self::LiquidityRequestFailed => write!(f, "Failed to request inbound liquidity."), @@ -117,6 +137,7 @@ impl fmt::Display for Error { Self::InvalidSocketAddress => write!(f, "The given network address is invalid."), Self::InvalidPublicKey => write!(f, "The given public key is invalid."), Self::InvalidSecretKey => write!(f, "The given secret key is invalid."), + Self::InvalidOfferId => write!(f, "The given offer id is invalid."), Self::InvalidNodeId => write!(f, "The given node id is invalid."), Self::InvalidPaymentId => write!(f, "The given payment id is invalid."), Self::InvalidPaymentHash => write!(f, "The given payment hash is invalid."), @@ -124,6 +145,8 @@ impl fmt::Display for Error { Self::InvalidPaymentSecret => write!(f, "The given payment secret is invalid."), Self::InvalidAmount => write!(f, "The given amount is invalid."), Self::InvalidInvoice => write!(f, "The given invoice is invalid."), + Self::InvalidOffer => write!(f, "The given offer is invalid."), + Self::InvalidRefund => write!(f, "The given refund is invalid."), Self::InvalidChannelId => write!(f, "The given channel ID is invalid."), Self::InvalidNetwork => write!(f, "The given network is invalid."), Self::InvalidCustomTlv => write!(f, "The given custom TLVs are invalid."), @@ -133,6 +156,9 @@ impl fmt::Display for Error { Self::InsufficientFunds => { write!(f, "The available funds are insufficient to complete the given operation.") }, + Self::UnsupportedCurrency => { + write!(f, "The provided offer was denonminated in an unsupported currency.") + }, Self::LiquiditySourceUnavailable => { write!(f, "The given operation failed due to the required liquidity source being unavailable.") }, diff --git a/src/event.rs b/src/event.rs index 3183528ba..0bf437051 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,9 +1,12 @@ use crate::types::{DynStore, Sweeper, Wallet}; + use crate::{ hex_utils, BumpTransactionEventHandler, ChannelManager, Config, Error, Graph, PeerInfo, PeerStore, TlvEntry, UserChannelId, }; +use crate::connection::ConnectionManager; + use crate::payment::store::{ PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind, PaymentStatus, PaymentStore, @@ -40,7 +43,7 @@ use std::collections::VecDeque; use std::ops::Deref; use std::str::FromStr; use std::sync::{Arc, Condvar, Mutex, RwLock}; -use std::time::{self, Duration}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; /// An event emitted by [`Node`], which should be handled by the user. /// @@ -82,6 +85,28 @@ pub enum Event { /// The value, in thousandths of a satoshi, that has been received. amount_msat: u64, }, + /// A payment for a previously-registered payment hash has been received. + /// + /// This needs to be manually claimed by supplying the correct preimage to [`claim_for_hash`]. + /// + /// If the the provided parameters don't match the expectations or the preimage can't be + /// retrieved in time, should be failed-back via [`fail_for_hash`]. + /// + /// Note claiming will necessarily fail after the `claim_deadline` has been reached. + /// + /// [`claim_for_hash`]: crate::payment::Bolt11Payment::claim_for_hash + /// [`fail_for_hash`]: crate::payment::Bolt11Payment::fail_for_hash + PaymentClaimable { + /// A local identifier used to track the payment. + payment_id: PaymentId, + /// The hash of the payment. + payment_hash: PaymentHash, + /// The value, in thousandths of a satoshi, that is claimable. + claimable_amount_msat: u64, + /// The block height at which this payment will be failed back and will no longer be + /// eligible for claiming. + claim_deadline: Option, + }, /// A channel has been created and is pending confirmation on-chain. ChannelPending { /// The `channel_id` of the channel. @@ -154,6 +179,12 @@ impl_writeable_tlv_based_enum!(Event, (1, counterparty_node_id, option), (2, user_channel_id, required), (3, reason, upgradable_option), + }, + (6, PaymentClaimable) => { + (0, payment_hash, required), + (2, payment_id, required), + (4, claimable_amount_msat, required), + (6, claim_deadline, option), }; ); @@ -319,6 +350,7 @@ where wallet: Arc, bump_tx_event_handler: Arc, channel_manager: Arc, + connection_manager: Arc>, output_sweeper: Arc, network_graph: Arc, payment_store: Arc>, @@ -335,16 +367,17 @@ where pub fn new( event_queue: Arc>, wallet: Arc, bump_tx_event_handler: Arc, - channel_manager: Arc, output_sweeper: Arc, - network_graph: Arc, payment_store: Arc>, - peer_store: Arc>, runtime: Arc>>, - logger: L, config: Arc, + channel_manager: Arc, connection_manager: Arc>, + output_sweeper: Arc, network_graph: Arc, + payment_store: Arc>, peer_store: Arc>, + runtime: Arc>>, logger: L, config: Arc, ) -> Self { Self { event_queue, wallet, bump_tx_event_handler, channel_manager, + connection_manager, output_sweeper, network_graph, payment_store, @@ -430,13 +463,34 @@ where receiver_node_id: _, via_channel_id: _, via_user_channel_id: _, - claim_deadline: _, + claim_deadline, onion_fields, counterparty_skimmed_fee_msat, } => { let payment_id = PaymentId(payment_hash.0); if let Some(info) = self.payment_store.get(&payment_id) { - if info.status == PaymentStatus::Succeeded { + if info.direction == PaymentDirection::Outbound { + log_info!( + self.logger, + "Refused inbound payment with ID {}: circular payments are unsupported.", + payment_id + ); + self.channel_manager.fail_htlc_backwards(&payment_hash); + + let update = PaymentDetailsUpdate { + status: Some(PaymentStatus::Failed), + ..PaymentDetailsUpdate::new(payment_id) + }; + self.payment_store.update(&update).unwrap_or_else(|e| { + log_error!(self.logger, "Failed to access payment store: {}", e); + panic!("Failed to access payment store"); + }); + return; + } + + if info.status == PaymentStatus::Succeeded + || matches!(info.kind, PaymentKind::Spontaneous { .. }) + { log_info!( self.logger, "Refused duplicate inbound payment from payment hash {} of {}msat", @@ -484,6 +538,7 @@ where self.channel_manager.fail_htlc_backwards(&payment_hash); let update = PaymentDetailsUpdate { + hash: Some(Some(payment_hash)), status: Some(PaymentStatus::Failed), ..PaymentDetailsUpdate::new(payment_id) }; @@ -493,6 +548,38 @@ where }); return; } + + // If this is known by the store but ChannelManager doesn't know the preimage, + // the payment has been registered via `_for_hash` variants and needs to be manually claimed via + // user interaction. + match info.kind { + PaymentKind::Bolt11 { preimage, .. } => { + if purpose.preimage().is_none() { + debug_assert!( + preimage.is_none(), + "We would have registered the preimage if we knew" + ); + + self.event_queue + .add_event(Event::PaymentClaimable { + payment_id, + payment_hash, + claimable_amount_msat: amount_msat, + claim_deadline, + }) + .unwrap_or_else(|e| { + log_error!( + self.logger, + "Failed to push to event queue: {}", + e + ); + panic!("Failed to push to event queue"); + }); + return; + } + }, + _ => {}, + } } log_info!( @@ -502,36 +589,122 @@ where amount_msat, ); let payment_preimage = match purpose { - PaymentPurpose::Bolt11InvoicePayment { payment_preimage, payment_secret } => { - if payment_preimage.is_some() { - payment_preimage - } else { - self.channel_manager - .get_payment_preimage(payment_hash, payment_secret) - .ok() - } + PaymentPurpose::Bolt11InvoicePayment { payment_preimage, .. } => { + payment_preimage }, - PaymentPurpose::Bolt12OfferPayment { .. } => { - // TODO: support BOLT12. - log_error!( - self.logger, - "Failed to claim unsupported BOLT12 payment with hash: {}", - payment_hash + PaymentPurpose::Bolt12OfferPayment { + payment_preimage, + payment_secret, + payment_context, + .. + } => { + let offer_id = payment_context.offer_id; + let kind = PaymentKind::Bolt12Offer { + hash: Some(payment_hash), + preimage: payment_preimage, + secret: Some(payment_secret), + offer_id, + }; + + let payment = PaymentDetails::new( + payment_id, + kind, + Some(amount_msat), + PaymentDirection::Inbound, + PaymentStatus::Pending, ); - self.channel_manager.fail_htlc_backwards(&payment_hash); - return; + + match self.payment_store.insert(payment) { + Ok(false) => (), + Ok(true) => { + log_error!( + self.logger, + "Bolt12OfferPayment with ID {} was previously known", + payment_id, + ); + debug_assert!(false); + }, + Err(e) => { + log_error!( + self.logger, + "Failed to insert payment with ID {}: {}", + payment_id, + e + ); + debug_assert!(false); + }, + } + payment_preimage }, - PaymentPurpose::Bolt12RefundPayment { .. } => { - // TODO: support BOLT12. - log_error!( + PaymentPurpose::Bolt12RefundPayment { payment_preimage, .. } => { + payment_preimage + }, + PaymentPurpose::SpontaneousPayment(preimage) => { + let custom_tlvs = onion_fields + .map(|of| { + of.custom_tlvs() + .iter() + .map(|(t, v)| TlvEntry { r#type: *t, value: v.clone() }) + .collect() + }) + .unwrap_or_default(); + log_info!( self.logger, - "Failed to claim unsupported BOLT12 payment with hash: {}", - payment_hash + "Saving spontaneous payment with custom TLVs {:?} for payment hash {} of {}msat", + custom_tlvs, + hex_utils::to_string(&payment_hash.0), + amount_msat, ); - self.channel_manager.fail_htlc_backwards(&payment_hash); - return; + + // Since it's spontaneous, we insert it now into our store. + let kind = PaymentKind::Spontaneous { + hash: payment_hash, + preimage: Some(preimage), + custom_tlvs, + }; + + let mut payment = PaymentDetails::new( + payment_id, + kind, + Some(amount_msat), + PaymentDirection::Inbound, + PaymentStatus::Pending, + ); + + // TODO: remove (use latest_update_timestamp) + payment.last_update = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or(Duration::ZERO) + .as_secs(); + + // TODO: is this still needed? + payment.fee_msat = None; + // TODO: move to PaymentDetails::new ? + payment.created_at = 0; + + match self.payment_store.insert(payment) { + Ok(false) => (), + Ok(true) => { + log_error!( + self.logger, + "Spontaneous payment with ID {} was previously known", + payment_id, + ); + debug_assert!(false); + }, + Err(e) => { + log_error!( + self.logger, + "Failed to insert payment with ID {}: {}", + payment_id, + e + ); + debug_assert!(false); + }, + } + + Some(preimage) }, - PaymentPurpose::SpontaneousPayment(preimage) => Some(preimage), }; if let Some(preimage) = payment_preimage { @@ -539,12 +712,13 @@ where } else { log_error!( self.logger, - "Failed to claim payment with hash {}: preimage unknown.", - hex_utils::to_string(&payment_hash.0), + "Failed to claim payment with ID {}: preimage unknown.", + payment_id, ); self.channel_manager.fail_htlc_backwards(&payment_hash); let update = PaymentDetailsUpdate { + hash: Some(Some(payment_hash)), status: Some(PaymentStatus::Failed), ..PaymentDetailsUpdate::new(payment_id) }; @@ -553,63 +727,6 @@ where panic!("Failed to access payment store"); }); } - - if let PaymentPurpose::SpontaneousPayment(preimage) = purpose { - let custom_tlvs = onion_fields - .map(|of| { - of.custom_tlvs() - .iter() - .map(|(t, v)| TlvEntry { r#type: *t, value: v.clone() }) - .collect() - }) - .unwrap_or_default(); - log_info!( - self.logger, - "Saving spontaneous payment with custom TLVs {:?} for payment hash {} of {}msat", - custom_tlvs, - hex_utils::to_string(&payment_hash.0), - amount_msat, - ); - - let payment = PaymentDetails { - id: payment_id, - kind: PaymentKind::Spontaneous { - hash: payment_hash, - preimage: Some(preimage), - custom_tlvs, - }, - amount_msat: Some(amount_msat), - direction: PaymentDirection::Inbound, - status: PaymentStatus::Pending, - last_update: time::SystemTime::now() - .duration_since(time::UNIX_EPOCH) - .unwrap_or(time::Duration::ZERO) - .as_secs(), - fee_msat: None, - created_at: 0, - }; - - match self.payment_store.insert(payment) { - Ok(false) => (), - Ok(true) => { - log_error!( - self.logger, - "Spontaneous payment with hash {} was previously known", - hex_utils::to_string(&payment_hash.0) - ); - debug_assert!(false); - }, - Err(e) => { - log_error!( - self.logger, - "Failed to insert spontaneous payment with hash {}: {}", - hex_utils::to_string(&payment_hash.0), - e - ); - debug_assert!(false); - }, - }; - } }, LdkEvent::PaymentClaimed { payment_hash, @@ -619,93 +736,75 @@ where htlcs: _, sender_intended_total_msat: _, } => { + let payment_id = PaymentId(payment_hash.0); log_info!( self.logger, - "Claimed payment from payment hash {} of {}msat.", + "Claimed payment with ID {} from payment hash {} of {}msat.", + payment_id, hex_utils::to_string(&payment_hash.0), amount_msat, ); - let payment_id = PaymentId(payment_hash.0); - match purpose { + + let update = match purpose { PaymentPurpose::Bolt11InvoicePayment { payment_preimage, payment_secret, .. - } => { - let update = PaymentDetailsUpdate { - preimage: Some(payment_preimage), - secret: Some(Some(payment_secret)), - amount_msat: Some(Some(amount_msat)), - status: Some(PaymentStatus::Succeeded), - ..PaymentDetailsUpdate::new(payment_id) - }; - match self.payment_store.update(&update) { - Ok(true) => (), - Ok(false) => { - log_error!( - self.logger, - "Payment with hash {} couldn't be found in store", - hex_utils::to_string(&payment_hash.0) - ); - debug_assert!(false); - }, - Err(e) => { - log_error!( - self.logger, - "Failed to update payment with hash {}: {}", - hex_utils::to_string(&payment_hash.0), - e - ); - debug_assert!(false); - }, - } + } => PaymentDetailsUpdate { + preimage: Some(payment_preimage), + secret: Some(Some(payment_secret)), + amount_msat: Some(Some(amount_msat)), + status: Some(PaymentStatus::Succeeded), + ..PaymentDetailsUpdate::new(payment_id) + }, + PaymentPurpose::Bolt12OfferPayment { + payment_preimage, payment_secret, .. + } => PaymentDetailsUpdate { + preimage: Some(payment_preimage), + secret: Some(Some(payment_secret)), + amount_msat: Some(Some(amount_msat)), + status: Some(PaymentStatus::Succeeded), + ..PaymentDetailsUpdate::new(payment_id) }, - PaymentPurpose::Bolt12OfferPayment { .. } => { - // TODO: support BOLT12. + PaymentPurpose::Bolt12RefundPayment { + payment_preimage, + payment_secret, + .. + } => PaymentDetailsUpdate { + preimage: Some(payment_preimage), + secret: Some(Some(payment_secret)), + amount_msat: Some(Some(amount_msat)), + status: Some(PaymentStatus::Succeeded), + ..PaymentDetailsUpdate::new(payment_id) + }, + PaymentPurpose::SpontaneousPayment(preimage) => PaymentDetailsUpdate { + preimage: Some(Some(preimage)), + amount_msat: Some(Some(amount_msat)), + status: Some(PaymentStatus::Succeeded), + ..PaymentDetailsUpdate::new(payment_id) + }, + }; + + match self.payment_store.update(&update) { + Ok(true) => (), + Ok(false) => { log_error!( self.logger, - "Failed to claim unsupported BOLT12 payment with hash: {}", - payment_hash + "Payment with ID {} couldn't be found in store", + payment_id, ); - return; + debug_assert!(false); }, - PaymentPurpose::Bolt12RefundPayment { .. } => { - // TODO: support BOLT12. + Err(e) => { log_error!( self.logger, - "Failed to claim unsupported BOLT12 payment with hash: {}", - payment_hash + "Failed to update payment with ID {}: {}", + payment_id, + e ); - return; - }, - PaymentPurpose::SpontaneousPayment(_) => { - let update = PaymentDetailsUpdate { - status: Some(PaymentStatus::Succeeded), - ..PaymentDetailsUpdate::new(payment_id) - }; - - match self.payment_store.update(&update) { - Ok(true) => (), - Ok(false) => { - log_error!( - self.logger, - "Spontaneous payment with hash {} couldn't be found in store", - hex_utils::to_string(&payment_hash.0) - ); - debug_assert!(false); - }, - Err(e) => { - log_error!( - self.logger, - "Failed to update payment with hash {}: {}", - hex_utils::to_string(&payment_hash.0), - e - ); - debug_assert!(false); - }, - } + panic!("Failed to access payment store"); }, - }; + } self.event_queue .add_event(Event::PaymentReceived { @@ -733,6 +832,7 @@ where }; let update = PaymentDetailsUpdate { + hash: Some(Some(payment_hash)), preimage: Some(Some(payment_preimage)), status: Some(PaymentStatus::Succeeded), fee_msat: Some(fee_paid_msat), @@ -780,6 +880,7 @@ where ); let update = PaymentDetailsUpdate { + hash: Some(Some(payment_hash)), status: Some(PaymentStatus::Failed), ..PaymentDetailsUpdate::new(payment_id) }; @@ -847,8 +948,7 @@ where ); let spendable_amount_sats = self .wallet - .get_balances(cur_anchor_reserve_sats) - .map(|(_, s)| s) + .get_spendable_amount_sats(cur_anchor_reserve_sats) .unwrap_or(0); let required_amount_sats = if anchor_channels_config @@ -862,10 +962,10 @@ where if spendable_amount_sats < required_amount_sats { log_error!( - self.logger, - "Rejecting inbound Anchor channel from peer {} due to insufficient available on-chain reserves.", - counterparty_node_id, - ); + self.logger, + "Rejecting inbound Anchor channel from peer {} due to insufficient available on-chain reserves.", + counterparty_node_id, + ); self.channel_manager .force_close_without_broadcasting_txn( &temporary_channel_id, @@ -1143,6 +1243,49 @@ where }, LdkEvent::DiscardFunding { .. } => {}, LdkEvent::HTLCIntercepted { .. } => {}, + LdkEvent::InvoiceRequestFailed { payment_id } => { + log_error!( + self.logger, + "Failed to request invoice for outbound BOLT12 payment {}", + payment_id + ); + let update = PaymentDetailsUpdate { + status: Some(PaymentStatus::Failed), + ..PaymentDetailsUpdate::new(payment_id) + }; + self.payment_store.update(&update).unwrap_or_else(|e| { + log_error!(self.logger, "Failed to access payment store: {}", e); + panic!("Failed to access payment store"); + }); + return; + }, + LdkEvent::ConnectionNeeded { node_id, addresses } => { + let runtime_lock = self.runtime.read().unwrap(); + debug_assert!(runtime_lock.is_some()); + + if let Some(runtime) = runtime_lock.as_ref() { + let spawn_logger = self.logger.clone(); + let spawn_cm = Arc::clone(&self.connection_manager); + runtime.spawn(async move { + for addr in &addresses { + match spawn_cm.connect_peer_if_necessary(node_id, addr.clone()).await { + Ok(()) => { + return; + }, + Err(e) => { + log_error!( + spawn_logger, + "Failed to establish connection to peer {}@{}: {}", + node_id, + addr, + e + ); + }, + } + } + }); + } + }, LdkEvent::BumpTransaction(bte) => { let (channel_id, counterparty_node_id) = match bte { BumpTransactionEvent::ChannelClose { @@ -1163,18 +1306,15 @@ where .contains(counterparty_node_id) { log_debug!(self.logger, - "Ignoring BumpTransactionEvent for channel {} due to trusted counterparty {}", - channel_id, counterparty_node_id - ); + "Ignoring BumpTransactionEvent for channel {} due to trusted counterparty {}", + channel_id, counterparty_node_id + ); return; } } self.bump_tx_event_handler.handle_event(&bte); }, - LdkEvent::InvoiceRequestFailed { .. } => {}, - // LdkEvent::InvoiceGenerated { .. } => {}, - LdkEvent::ConnectionNeeded { .. } => {}, } } } diff --git a/src/lib.rs b/src/lib.rs index e7746a60a..24fc64043 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,8 +123,10 @@ pub use builder::BuildError; pub use builder::NodeBuilder as Builder; use config::{ - ENABLE_BACKGROUND_SYNC, LDK_WALLET_SYNC_TIMEOUT_SECS, NODE_ANN_BCAST_INTERVAL, - PEER_RECONNECTION_INTERVAL, RGS_SYNC_INTERVAL, WALLET_SYNC_INTERVAL_MINIMUM_SECS, + default_user_config, ENABLE_BACKGROUND_SYNC, LDK_WALLET_SYNC_TIMEOUT_SECS, + NODE_ANN_BCAST_INTERVAL, PEER_RECONNECTION_INTERVAL, + RESOLVED_CHANNEL_MONITOR_ARCHIVAL_INTERVAL, RGS_SYNC_INTERVAL, + WALLET_SYNC_INTERVAL_MINIMUM_SECS, }; use connection::ConnectionManager; use event::{EventHandler, EventQueue}; @@ -132,7 +134,7 @@ use gossip::GossipSource; use graph::NetworkGraph; use liquidity::LiquiditySource; use payment::store::PaymentStore; -use payment::{Bolt11Payment, OnchainPayment, PaymentDetails, SpontaneousPayment}; +use payment::{Bolt11Payment, Bolt12Payment, OnchainPayment, PaymentDetails, SpontaneousPayment}; use peer_store::{PeerInfo, PeerStore}; use types::{ Broadcaster, BumpTransactionEventHandler, ChainMonitor, ChannelManager, DynStore, FeeEstimator, @@ -147,7 +149,6 @@ use lightning::events::bump_transaction::Wallet as LdkWallet; use lightning::ln::channelmanager::{ChannelShutdownState, PaymentId}; use lightning::ln::msgs::SocketAddress; -use lightning::util::config::{ChannelHandshakeConfig, UserConfig}; pub use lightning::util::logger::Level as LogLevel; use lightning_background_processor::process_events_async; @@ -201,6 +202,7 @@ pub struct Node { latest_fee_rate_cache_update_timestamp: Arc>>, latest_rgs_snapshot_timestamp: Arc>>, latest_node_announcement_broadcast_timestamp: Arc>>, + latest_channel_monitor_archival_height: Arc>>, } impl Node { @@ -217,7 +219,12 @@ impl Node { return Err(Error::AlreadyRunning); } - log_info!(self.logger, "Starting up LDK Node on network: {}", self.config.network); + log_info!( + self.logger, + "Starting up LDK Node with node ID {} on network: {}", + self.node_id(), + self.config.network + ); let runtime = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap(); @@ -360,58 +367,68 @@ impl Node { let tx_sync = Arc::clone(&self.tx_sync); let sync_cman = Arc::clone(&self.channel_manager); + let archive_cman = Arc::clone(&self.channel_manager); let sync_cmon = Arc::clone(&self.chain_monitor); + let archive_cmon = Arc::clone(&self.chain_monitor); let sync_sweeper = Arc::clone(&self.output_sweeper); let sync_logger = Arc::clone(&self.logger); let sync_wallet_timestamp = Arc::clone(&self.latest_wallet_sync_timestamp); + let sync_monitor_archival_height = + Arc::clone(&self.latest_channel_monitor_archival_height); let mut stop_sync = self.stop_sender.subscribe(); let wallet_sync_interval_secs = self.config.wallet_sync_interval_secs.max(WALLET_SYNC_INTERVAL_MINIMUM_SECS); runtime.spawn(async move { - let mut wallet_sync_interval = - tokio::time::interval(Duration::from_secs(wallet_sync_interval_secs)); - wallet_sync_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - loop { - tokio::select! { - _ = stop_sync.changed() => { - log_trace!( - sync_logger, - "Stopping background syncing Lightning wallet.", - ); - return; - } - _ = wallet_sync_interval.tick() => { - let confirmables = vec![ - &*sync_cman as &(dyn Confirm + Sync + Send), - &*sync_cmon as &(dyn Confirm + Sync + Send), - &*sync_sweeper as &(dyn Confirm + Sync + Send), - ]; - let now = Instant::now(); - let timeout_fut = tokio::time::timeout(Duration::from_secs(LDK_WALLET_SYNC_TIMEOUT_SECS), tx_sync.sync(confirmables)); - match timeout_fut.await { - Ok(res) => match res { - Ok(()) => { - log_trace!( - sync_logger, - "Background sync of Lightning wallet finished in {}ms.", - now.elapsed().as_millis() - ); - let unix_time_secs_opt = - SystemTime::now().duration_since(UNIX_EPOCH).ok().map(|d| d.as_secs()); - *sync_wallet_timestamp.write().unwrap() = unix_time_secs_opt; - } - Err(e) => { - log_error!(sync_logger, "Background sync of Lightning wallet failed: {}", e) - } + let mut wallet_sync_interval = + tokio::time::interval(Duration::from_secs(wallet_sync_interval_secs)); + wallet_sync_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + loop { + tokio::select! { + _ = stop_sync.changed() => { + log_trace!( + sync_logger, + "Stopping background syncing Lightning wallet.", + ); + return; + } + _ = wallet_sync_interval.tick() => { + let confirmables = vec![ + &*sync_cman as &(dyn Confirm + Sync + Send), + &*sync_cmon as &(dyn Confirm + Sync + Send), + &*sync_sweeper as &(dyn Confirm + Sync + Send), + ]; + let now = Instant::now(); + let timeout_fut = tokio::time::timeout(Duration::from_secs(LDK_WALLET_SYNC_TIMEOUT_SECS), tx_sync.sync(confirmables)); + match timeout_fut.await { + Ok(res) => match res { + Ok(()) => { + log_trace!( + sync_logger, + "Background sync of Lightning wallet finished in {}ms.", + now.elapsed().as_millis() + ); + let unix_time_secs_opt = + SystemTime::now().duration_since(UNIX_EPOCH).ok().map(|d| d.as_secs()); + *sync_wallet_timestamp.write().unwrap() = unix_time_secs_opt; + + periodically_archive_fully_resolved_monitors( + Arc::clone(&archive_cman), + Arc::clone(&archive_cmon), + Arc::clone(&sync_monitor_archival_height) + ); } Err(e) => { - log_error!(sync_logger, "Background sync of Lightning wallet timed out: {}", e) + log_error!(sync_logger, "Background sync of Lightning wallet failed: {}", e) } } + Err(e) => { + log_error!(sync_logger, "Background sync of Lightning wallet timed out: {}", e) + } } } } - }); + } + }); } if self.gossip_source.is_rgs() { @@ -582,7 +599,10 @@ impl Node { let mut stop_bcast = self.stop_sender.subscribe(); runtime.spawn(async move { // We check every 30 secs whether our last broadcast is NODE_ANN_BCAST_INTERVAL away. + #[cfg(not(test))] let mut interval = tokio::time::interval(Duration::from_secs(30)); + #[cfg(test)] + let mut interval = tokio::time::interval(Duration::from_secs(5)); loop { tokio::select! { _ = stop_bcast.changed() => { @@ -609,8 +629,8 @@ impl Node { continue; } - if !bcast_cm.list_channels().iter().any(|chan| chan.is_public) { - // Skip if we don't have any public channels. + if !bcast_cm.list_channels().iter().any(|chan| chan.is_public && chan.is_channel_ready) { + // Skip if we don't have any public channels that are ready. continue; } @@ -679,6 +699,7 @@ impl Node { Arc::clone(&self.wallet), bump_tx_event_handler, Arc::clone(&self.channel_manager), + Arc::clone(&self.connection_manager), Arc::clone(&self.output_sweeper), Arc::clone(&self.network_graph), Arc::clone(&self.payment_store), @@ -787,7 +808,7 @@ impl Node { pub fn stop(&self) -> Result<(), Error> { let runtime = self.runtime.write().unwrap().take().ok_or(Error::NotRunning)?; - log_info!(self.logger, "Shutting down LDK Node..."); + log_info!(self.logger, "Shutting down LDK Node with node ID {}...", self.node_id()); // Stop the runtime. match self.stop_sender.send(()) { @@ -975,6 +996,32 @@ impl Node { )) } + /// Returns a payment handler allowing to create and pay [BOLT 12] offers and refunds. + /// + /// [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md + #[cfg(not(feature = "uniffi"))] + pub fn bolt12_payment(&self) -> Arc { + Arc::new(Bolt12Payment::new( + Arc::clone(&self.runtime), + Arc::clone(&self.channel_manager), + Arc::clone(&self.payment_store), + Arc::clone(&self.logger), + )) + } + + /// Returns a payment handler allowing to create and pay [BOLT 12] offers and refunds. + /// + /// [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md + #[cfg(feature = "uniffi")] + pub fn bolt12_payment(&self) -> Arc { + Arc::new(Bolt12Payment::new( + Arc::clone(&self.runtime), + Arc::clone(&self.channel_manager), + Arc::clone(&self.payment_store), + Arc::clone(&self.logger), + )) + } + /// Returns a payment handler allowing to send spontaneous ("keysend") payments. #[cfg(not(feature = "uniffi"))] pub fn spontaneous_payment(&self) -> SpontaneousPayment { @@ -1121,7 +1168,7 @@ impl Node { let cur_anchor_reserve_sats = total_anchor_channels_reserve_sats(&self.channel_manager, &self.config); let spendable_amount_sats = - self.wallet.get_balances(cur_anchor_reserve_sats).map(|(_, s)| s).unwrap_or(0); + self.wallet.get_spendable_amount_sats(cur_anchor_reserve_sats).unwrap_or(0); // Fail early if we have less than the channel value available. if spendable_amount_sats < channel_amount_sats { @@ -1165,19 +1212,21 @@ impl Node { return Err(Error::InsufficientFunds); } - let channel_config = (*(channel_config.unwrap_or_default())).clone().into(); - let user_config = UserConfig { - channel_handshake_limits: Default::default(), - channel_handshake_config: ChannelHandshakeConfig { - announced_channel: announce_channel, - negotiate_anchors_zero_fee_htlc_tx: self.config.anchor_channels_config.is_some(), - // Alby: always allow receiving 100% of channel size. - max_inbound_htlc_value_in_flight_percent_of_channel: 100, - ..Default::default() - }, - channel_config, - ..Default::default() - }; + let mut user_config = default_user_config(&self.config); + user_config.channel_handshake_config.announced_channel = announce_channel; + user_config.channel_config = (*(channel_config.unwrap_or_default())).clone().into(); + // We set the max inflight to 100% for private channels. + // FIXME: LDK will default to this behavior soon, too, at which point we should drop this + // manual override. + if !announce_channel { + user_config + .channel_handshake_config + .max_inbound_htlc_value_in_flight_percent_of_channel = 100; + } + + // Alby: always allow receiving 100% of channel size. + user_config.channel_handshake_config.max_inbound_htlc_value_in_flight_percent_of_channel = + 100; let push_msat = push_to_counterparty_msat.unwrap_or(0); let user_channel_id: u128 = rand::thread_rng().gen::(); @@ -1206,7 +1255,8 @@ impl Node { } } - /// Manually sync the LDK and BDK wallets with the current chain state. + /// Manually sync the LDK and BDK wallets with the current chain state and update the fee rate + /// cache. /// /// **Note:** The wallets are regularly synced in the background, which is configurable via /// [`Config::onchain_wallet_sync_interval_secs`] and [`Config::wallet_sync_interval_secs`]. @@ -1223,7 +1273,10 @@ impl Node { let wallet = Arc::clone(&self.wallet); let tx_sync = Arc::clone(&self.tx_sync); let sync_cman = Arc::clone(&self.channel_manager); + let archive_cman = Arc::clone(&self.channel_manager); let sync_cmon = Arc::clone(&self.chain_monitor); + let archive_cmon = Arc::clone(&self.chain_monitor); + let fee_estimator = Arc::clone(&self.fee_estimator); let sync_sweeper = Arc::clone(&self.output_sweeper); let sync_logger = Arc::clone(&self.logger); let confirmables = vec![ @@ -1231,38 +1284,18 @@ impl Node { &*sync_cmon as &(dyn Confirm + Sync + Send), &*sync_sweeper as &(dyn Confirm + Sync + Send), ]; + let sync_wallet_timestamp = Arc::clone(&self.latest_wallet_sync_timestamp); + let sync_fee_rate_update_timestamp = + Arc::clone(&self.latest_fee_rate_cache_update_timestamp); + let sync_onchain_wallet_timestamp = Arc::clone(&self.latest_onchain_wallet_sync_timestamp); + let sync_monitor_archival_height = Arc::clone(&self.latest_channel_monitor_archival_height); tokio::task::block_in_place(move || { tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap().block_on( async move { let now = Instant::now(); - let fee_estimator = Arc::clone(&self.fee_estimator); - let fee_update_timestamp = - Arc::clone(&self.latest_fee_rate_cache_update_timestamp); - - match fee_estimator.update_fee_estimates().await { - Ok(()) => { - log_trace!( - sync_logger, - "Update of fee rate cache finished in {}ms.", - now.elapsed().as_millis() - ); - let unix_time_secs_opt = SystemTime::now() - .duration_since(UNIX_EPOCH) - .ok() - .map(|d| d.as_secs()); - *fee_update_timestamp.write().unwrap() = unix_time_secs_opt; - }, - Err(err) => { - log_error!(sync_logger, "Update of fee rate cache failed: {}", err); - return Err(err); - }, - } - - let now = Instant::now(); - let sync_onchain_wallet_timestamp = - Arc::clone(&self.latest_onchain_wallet_sync_timestamp); - + // We don't add an additional timeout here, as `Wallet::sync` already returns + // after a timeout. match wallet.sync().await { Ok(()) => { log_info!( @@ -1283,24 +1316,62 @@ impl Node { }; let now = Instant::now(); - let sync_wallet_timestamp = Arc::clone(&self.latest_wallet_sync_timestamp); - match tx_sync.sync(confirmables).await { + // We don't add an additional timeout here, as + // `FeeEstimator::update_fee_estimates` already returns after a timeout. + match fee_estimator.update_fee_estimates().await { Ok(()) => { log_info!( sync_logger, - "Sync of Lightning wallet finished in {}ms.", + "Fee rate cache update finished in {}ms.", now.elapsed().as_millis() ); let unix_time_secs_opt = SystemTime::now() .duration_since(UNIX_EPOCH) .ok() .map(|d| d.as_secs()); - *sync_wallet_timestamp.write().unwrap() = unix_time_secs_opt; - Ok(()) + *sync_fee_rate_update_timestamp.write().unwrap() = unix_time_secs_opt; }, Err(e) => { - log_error!(sync_logger, "Sync of Lightning wallet failed: {}", e); - Err(e.into()) + log_error!(sync_logger, "Fee rate cache update failed: {}", e,); + return Err(e); + }, + } + + let now = Instant::now(); + let tx_sync_timeout_fut = tokio::time::timeout( + Duration::from_secs(LDK_WALLET_SYNC_TIMEOUT_SECS), + tx_sync.sync(confirmables), + ); + match tx_sync_timeout_fut.await { + Ok(res) => match res { + Ok(()) => { + log_info!( + sync_logger, + "Sync of Lightning wallet finished in {}ms.", + now.elapsed().as_millis() + ); + + let unix_time_secs_opt = SystemTime::now() + .duration_since(UNIX_EPOCH) + .ok() + .map(|d| d.as_secs()); + *sync_wallet_timestamp.write().unwrap() = unix_time_secs_opt; + + periodically_archive_fully_resolved_monitors( + archive_cman, + archive_cmon, + sync_monitor_archival_height, + ); + Ok(()) + }, + Err(e) => { + log_error!(sync_logger, "Sync of Lightning wallet failed: {}", e); + Err(e.into()) + }, + }, + Err(e) => { + log_error!(sync_logger, "Sync of Lightning wallet timed out: {}", e); + Err(Error::TxSyncTimeout) }, } }, @@ -1310,16 +1381,32 @@ impl Node { /// Close a previously opened channel. /// - /// If `force` is set to `true`, we will force-close the channel, potentially broadcasting our - /// latest state. Note that in contrast to cooperative closure, force-closing will have the - /// channel funds time-locked, i.e., they will only be available after the counterparty had - /// time to contest our claim. Force-closing channels also more costly in terms of on-chain - /// fees. So cooperative closure should always be preferred (and tried first). + /// Will attempt to close a channel coopertively. If this fails, users might need to resort to + /// [`Node::force_close_channel`]. + pub fn close_channel( + &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, + ) -> Result<(), Error> { + self.close_channel_internal(user_channel_id, counterparty_node_id, false) + } + + /// Force-close a previously opened channel. + /// + /// Will force-close the channel, potentially broadcasting our latest state. Note that in + /// contrast to cooperative closure, force-closing will have the channel funds time-locked, + /// i.e., they will only be available after the counterparty had time to contest our claim. + /// Force-closing channels also more costly in terms of on-chain fees. So cooperative closure + /// should always be preferred (and tried first). /// /// Broadcasting the closing transactions will be omitted for Anchor channels if we trust the /// counterparty to broadcast for us (see [`AnchorChannelsConfig::trusted_peers_no_reserve`] /// for more information). - pub fn close_channel( + pub fn force_close_channel( + &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, + ) -> Result<(), Error> { + self.close_channel_internal(user_channel_id, counterparty_node_id, true) + } + + fn close_channel_internal( &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, force: bool, ) -> Result<(), Error> { let open_channels = @@ -1368,10 +1455,9 @@ impl Node { if open_channels.len() == 1 { self.peer_store.remove_peer(&counterparty_node_id)?; } - Ok(()) - } else { - Ok(()) } + + Ok(()) } /// Update the config for a previously opened channel. @@ -1616,3 +1702,19 @@ pub(crate) fn total_anchor_channels_reserve_sats( * anchor_channels_config.per_channel_reserve_sats }) } + +fn periodically_archive_fully_resolved_monitors( + channel_manager: Arc, chain_monitor: Arc, + latest_channel_monitor_archival_height: Arc>>, +) { + let mut latest_archival_height_lock = latest_channel_monitor_archival_height.write().unwrap(); + let cur_height = channel_manager.current_best_block().height; + let should_archive = latest_archival_height_lock + .as_ref() + .map_or(true, |h| cur_height >= h + RESOLVED_CHANNEL_MONITOR_ARCHIVAL_INTERVAL); + + if should_archive { + chain_monitor.archive_fully_resolved_channel_monitors(); + *latest_archival_height_lock = Some(cur_height); + } +} diff --git a/src/payment/bolt11.rs b/src/payment/bolt11.rs index 9a8d025a9..e14bb9823 100644 --- a/src/payment/bolt11.rs +++ b/src/payment/bolt11.rs @@ -8,20 +8,23 @@ use crate::error::Error; use crate::liquidity::LiquiditySource; use crate::logger::{log_error, log_info, FilesystemLogger, Logger}; use crate::payment::store::{ - LSPFeeLimits, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, PaymentStore, + LSPFeeLimits, PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind, + PaymentStatus, PaymentStore, }; use crate::peer_store::{PeerInfo, PeerStore}; use crate::types::{ChannelManager, KeysManager}; use lightning::ln::channelmanager::{PaymentId, RecipientOnionFields, Retry, RetryableSendFailure}; -use lightning::ln::PaymentHash; +use lightning::ln::{PaymentHash, PaymentPreimage}; use lightning::routing::router::{PaymentParameters, RouteParameters}; use lightning_invoice::{payment, Bolt11Invoice, Currency}; +use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash; use std::sync::{Arc, RwLock}; +use std::time::SystemTime; /// A payment handler allowing to create and pay [BOLT 11] invoices. /// @@ -102,21 +105,19 @@ impl Bolt11Payment { let amt_msat = invoice.amount_milli_satoshis().unwrap(); log_info!(self.logger, "Initiated sending {}msat to {}", amt_msat, payee_pubkey); - let payment = PaymentDetails { - id: payment_id, - kind: PaymentKind::Bolt11 { - hash: payment_hash, - preimage: None, - secret: payment_secret, - bolt11_invoice: Some(invoice.to_string()), - }, - amount_msat: invoice.amount_milli_satoshis(), - direction: PaymentDirection::Outbound, - status: PaymentStatus::Pending, - last_update: 0, - fee_msat: None, - created_at: 0, + let kind = PaymentKind::Bolt11 { + hash: payment_hash, + preimage: None, + secret: payment_secret, + bolt11_invoice: Some(invoice.to_string()), }; + let payment = PaymentDetails::new( + payment_id, + kind, + invoice.amount_milli_satoshis(), + PaymentDirection::Outbound, + PaymentStatus::Pending, + ); self.payment_store.insert(payment)?; @@ -127,21 +128,19 @@ impl Bolt11Payment { match e { RetryableSendFailure::DuplicatePayment => Err(Error::DuplicatePayment), _ => { - let payment = PaymentDetails { - id: payment_id, - kind: PaymentKind::Bolt11 { - hash: payment_hash, - preimage: None, - secret: payment_secret, - bolt11_invoice: Some(invoice.to_string()), - }, - amount_msat: invoice.amount_milli_satoshis(), - direction: PaymentDirection::Outbound, - status: PaymentStatus::Failed, - last_update: 0, - fee_msat: None, - created_at: 0, + let kind = PaymentKind::Bolt11 { + hash: payment_hash, + preimage: None, + secret: payment_secret, + bolt11_invoice: Some(invoice.to_string()), }; + let payment = PaymentDetails::new( + payment_id, + kind, + invoice.amount_milli_satoshis(), + PaymentDirection::Outbound, + PaymentStatus::Failed, + ); self.payment_store.insert(payment)?; Err(Error::PaymentSendingFailed) @@ -221,21 +220,21 @@ impl Bolt11Payment { payee_pubkey ); - let payment = PaymentDetails { - id: payment_id, - kind: PaymentKind::Bolt11 { - hash: payment_hash, - preimage: None, - secret: Some(*payment_secret), - bolt11_invoice: Some(invoice.to_string()), - }, - amount_msat: Some(amount_msat), - direction: PaymentDirection::Outbound, - status: PaymentStatus::Pending, - last_update: 0, - fee_msat: None, - created_at: 0, + let kind = PaymentKind::Bolt11 { + hash: payment_hash, + preimage: None, + secret: Some(*payment_secret), + bolt11_invoice: Some(invoice.to_string()), }; + + let payment = PaymentDetails::new( + payment_id, + kind, + Some(amount_msat), + PaymentDirection::Outbound, + PaymentStatus::Pending, + ); + self.payment_store.insert(payment)?; Ok(payment_id) @@ -246,21 +245,20 @@ impl Bolt11Payment { match e { RetryableSendFailure::DuplicatePayment => Err(Error::DuplicatePayment), _ => { - let payment = PaymentDetails { - id: payment_id, - kind: PaymentKind::Bolt11 { - hash: payment_hash, - preimage: None, - secret: Some(*payment_secret), - bolt11_invoice: Some(invoice.to_string()), - }, - amount_msat: Some(amount_msat), - direction: PaymentDirection::Outbound, - status: PaymentStatus::Failed, - last_update: 0, - fee_msat: None, - created_at: 0, + let kind = PaymentKind::Bolt11 { + hash: payment_hash, + preimage: None, + secret: Some(*payment_secret), + bolt11_invoice: Some(invoice.to_string()), }; + let payment = PaymentDetails::new( + payment_id, + kind, + Some(amount_msat), + PaymentDirection::Outbound, + PaymentStatus::Failed, + ); + self.payment_store.insert(payment)?; Err(Error::PaymentSendingFailed) @@ -270,65 +268,234 @@ impl Bolt11Payment { } } + /// Allows to attempt manually claiming payments with the given preimage that have previously + /// been registered via [`receive_for_hash`] or [`receive_variable_amount_for_hash`]. + /// + /// This should be called in reponse to a [`PaymentClaimable`] event as soon as the preimage is + /// available. + /// + /// Will check that the payment is known, and that the given preimage and claimable amount + /// match our expectations before attempting to claim the payment, and will return an error + /// otherwise. + /// + /// When claiming the payment has succeeded, a [`PaymentReceived`] event will be emitted. + /// + /// [`receive_for_hash`]: Self::receive_for_hash + /// [`receive_variable_amount_for_hash`]: Self::receive_variable_amount_for_hash + /// [`PaymentClaimable`]: crate::Event::PaymentClaimable + /// [`PaymentReceived`]: crate::Event::PaymentReceived + pub fn claim_for_hash( + &self, payment_hash: PaymentHash, claimable_amount_msat: u64, preimage: PaymentPreimage, + ) -> Result<(), Error> { + let payment_id = PaymentId(payment_hash.0); + + let expected_payment_hash = PaymentHash(Sha256::hash(&preimage.0).to_byte_array()); + + if expected_payment_hash != payment_hash { + log_error!( + self.logger, + "Failed to manually claim payment as the given preimage doesn't match the hash {}", + payment_hash + ); + return Err(Error::InvalidPaymentPreimage); + } + + if let Some(details) = self.payment_store.get(&payment_id) { + if let Some(expected_amount_msat) = details.amount_msat { + if claimable_amount_msat < expected_amount_msat { + log_error!( + self.logger, + "Failed to manually claim payment {} as the claimable amount is less than expected", + payment_id + ); + return Err(Error::InvalidAmount); + } + } + } else { + log_error!( + self.logger, + "Failed to manually claim unknown payment with hash: {}", + payment_hash + ); + return Err(Error::InvalidPaymentHash); + } + + self.channel_manager.claim_funds(preimage); + Ok(()) + } + + /// Allows to manually fail payments with the given hash that have previously + /// been registered via [`receive_for_hash`] or [`receive_variable_amount_for_hash`]. + /// + /// This should be called in reponse to a [`PaymentClaimable`] event if the payment needs to be + /// failed back, e.g., if the correct preimage can't be retrieved in time before the claim + /// deadline has been reached. + /// + /// Will check that the payment is known before failing the payment, and will return an error + /// otherwise. + /// + /// [`receive_for_hash`]: Self::receive_for_hash + /// [`receive_variable_amount_for_hash`]: Self::receive_variable_amount_for_hash + /// [`PaymentClaimable`]: crate::Event::PaymentClaimable + pub fn fail_for_hash(&self, payment_hash: PaymentHash) -> Result<(), Error> { + let payment_id = PaymentId(payment_hash.0); + + let update = PaymentDetailsUpdate { + status: Some(PaymentStatus::Failed), + ..PaymentDetailsUpdate::new(payment_id) + }; + + if !self.payment_store.update(&update)? { + log_error!( + self.logger, + "Failed to manually fail unknown payment with hash: {}", + payment_hash + ); + return Err(Error::InvalidPaymentHash); + } + + self.channel_manager.fail_htlc_backwards(&payment_hash); + Ok(()) + } + /// Returns a payable invoice that can be used to request and receive a payment of the amount /// given. + /// + /// The inbound payment will be automatically claimed upon arrival. pub fn receive( &self, amount_msat: u64, description: &str, expiry_secs: u32, ) -> Result { - self.receive_inner(Some(amount_msat), description, expiry_secs) + self.receive_inner(Some(amount_msat), description, expiry_secs, None) + } + + /// Returns a payable invoice that can be used to request a payment of the amount + /// given for the given payment hash. + /// + /// We will register the given payment hash and emit a [`PaymentClaimable`] event once + /// the inbound payment arrives. + /// + /// **Note:** users *MUST* handle this event and claim the payment manually via + /// [`claim_for_hash`] as soon as they have obtained access to the preimage of the given + /// payment hash. If they're unable to obtain the preimage, they *MUST* immediately fail the payment via + /// [`fail_for_hash`]. + /// + /// [`PaymentClaimable`]: crate::Event::PaymentClaimable + /// [`claim_for_hash`]: Self::claim_for_hash + /// [`fail_for_hash`]: Self::fail_for_hash + pub fn receive_for_hash( + &self, amount_msat: u64, description: &str, expiry_secs: u32, payment_hash: PaymentHash, + ) -> Result { + self.receive_inner(Some(amount_msat), description, expiry_secs, Some(payment_hash)) } /// Returns a payable invoice that can be used to request and receive a payment for which the /// amount is to be determined by the user, also known as a "zero-amount" invoice. + /// + /// The inbound payment will be automatically claimed upon arrival. pub fn receive_variable_amount( &self, description: &str, expiry_secs: u32, ) -> Result { - self.receive_inner(None, description, expiry_secs) + self.receive_inner(None, description, expiry_secs, None) + } + + /// Returns a payable invoice that can be used to request a payment for the given payment hash + /// and the amount to be determined by the user, also known as a "zero-amount" invoice. + /// + /// We will register the given payment hash and emit a [`PaymentClaimable`] event once + /// the inbound payment arrives. + /// + /// **Note:** users *MUST* handle this event and claim the payment manually via + /// [`claim_for_hash`] as soon as they have obtained access to the preimage of the given + /// payment hash. If they're unable to obtain the preimage, they *MUST* immediately fail the payment via + /// [`fail_for_hash`]. + /// + /// [`PaymentClaimable`]: crate::Event::PaymentClaimable + /// [`claim_for_hash`]: Self::claim_for_hash + /// [`fail_for_hash`]: Self::fail_for_hash + pub fn receive_variable_amount_for_hash( + &self, description: &str, expiry_secs: u32, payment_hash: PaymentHash, + ) -> Result { + self.receive_inner(None, description, expiry_secs, Some(payment_hash)) } fn receive_inner( &self, amount_msat: Option, description: &str, expiry_secs: u32, + manual_claim_payment_hash: Option, ) -> Result { let currency = Currency::from(self.config.network); let keys_manager = Arc::clone(&self.keys_manager); - let invoice = match lightning_invoice::utils::create_invoice_from_channelmanager( - &self.channel_manager, - keys_manager, - Arc::clone(&self.logger), - currency, - amount_msat, - description.to_string(), - expiry_secs, - None, - ) { - Ok(inv) => { - log_info!(self.logger, "Invoice created: {}", inv); - inv - }, - Err(e) => { - log_error!(self.logger, "Failed to create invoice: {}", e); - return Err(Error::InvoiceCreationFailed); - }, + let duration = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("for the foreseeable future this shouldn't happen"); + + let invoice = { + let invoice_res = if let Some(payment_hash) = manual_claim_payment_hash { + lightning_invoice::utils::create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( + &self.channel_manager, + keys_manager, + Arc::clone(&self.logger), + currency, + amount_msat, + description.to_string(), + duration, + expiry_secs, + payment_hash, + None, + ) + } else { + lightning_invoice::utils::create_invoice_from_channelmanager_and_duration_since_epoch( + &self.channel_manager, + keys_manager, + Arc::clone(&self.logger), + currency, + amount_msat, + description.to_string(), + duration, + expiry_secs, + None, + ) + }; + + match invoice_res { + Ok(inv) => { + log_info!(self.logger, "Invoice created: {}", inv); + inv + }, + Err(e) => { + log_error!(self.logger, "Failed to create invoice: {}", e); + return Err(Error::InvoiceCreationFailed); + }, + } }; let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array()); + let payment_secret = invoice.payment_secret(); let id = PaymentId(payment_hash.0); - let payment = PaymentDetails { + let preimage = if manual_claim_payment_hash.is_none() { + // If the user hasn't registered a custom payment hash, we're positive ChannelManager + // will know the preimage at this point. + let res = self + .channel_manager + .get_payment_preimage(payment_hash, payment_secret.clone()) + .ok(); + debug_assert!(res.is_some(), "We just let ChannelManager create an inbound payment, it can't have forgotten the preimage by now."); + res + } else { + None + }; + let kind = PaymentKind::Bolt11 { + hash: payment_hash, + preimage, + secret: Some(payment_secret.clone()), + bolt11_invoice: Some(invoice.to_string()), + }; + let payment = PaymentDetails::new( id, - kind: PaymentKind::Bolt11 { - hash: payment_hash, - preimage: None, - secret: Some(invoice.payment_secret().clone()), - bolt11_invoice: Some(invoice.to_string()), - }, - + kind, amount_msat, - direction: PaymentDirection::Inbound, - status: PaymentStatus::Pending, - last_update: 0, - fee_msat: None, - created_at: 0, - }; + PaymentDirection::Inbound, + PaymentStatus::Pending, + ); self.payment_store.insert(payment)?; @@ -442,26 +609,27 @@ impl Bolt11Payment { // Register payment in payment store. let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array()); + let payment_secret = invoice.payment_secret(); let lsp_fee_limits = LSPFeeLimits { max_total_opening_fee_msat: lsp_total_opening_fee, max_proportional_opening_fee_ppm_msat: lsp_prop_opening_fee, }; let id = PaymentId(payment_hash.0); - let payment = PaymentDetails { + let preimage = + self.channel_manager.get_payment_preimage(payment_hash, payment_secret.clone()).ok(); + let kind = PaymentKind::Bolt11Jit { + hash: payment_hash, + preimage, + secret: Some(payment_secret.clone()), + lsp_fee_limits, + }; + let payment = PaymentDetails::new( id, - kind: PaymentKind::Bolt11Jit { - hash: payment_hash, - preimage: None, - secret: Some(invoice.payment_secret().clone()), - lsp_fee_limits, - }, + kind, amount_msat, - direction: PaymentDirection::Inbound, - status: PaymentStatus::Pending, - last_update: 0, - fee_msat: None, - created_at: 0, - }; + PaymentDirection::Inbound, + PaymentStatus::Pending, + ); self.payment_store.insert(payment)?; diff --git a/src/payment/bolt12.rs b/src/payment/bolt12.rs new file mode 100644 index 000000000..5fd1208cc --- /dev/null +++ b/src/payment/bolt12.rs @@ -0,0 +1,347 @@ +//! Holds a payment handler allowing to create and pay [BOLT 12] offers and refunds. +//! +//! [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md + +use crate::config::LDK_PAYMENT_RETRY_TIMEOUT; +use crate::error::Error; +use crate::logger::{log_error, log_info, FilesystemLogger, Logger}; +use crate::payment::store::{ + PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, PaymentStore, +}; +use crate::types::ChannelManager; + +use lightning::ln::channelmanager::{PaymentId, Retry}; +use lightning::offers::invoice::Bolt12Invoice; +use lightning::offers::offer::{Amount, Offer}; +use lightning::offers::parse::Bolt12SemanticError; +use lightning::offers::refund::Refund; + +use rand::RngCore; + +use std::sync::{Arc, RwLock}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +/// A payment handler allowing to create and pay [BOLT 12] offers and refunds. +/// +/// Should be retrieved by calling [`Node::bolt12_payment`]. +/// +/// [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md +/// [`Node::bolt12_payment`]: crate::Node::bolt12_payment +pub struct Bolt12Payment { + runtime: Arc>>, + channel_manager: Arc, + payment_store: Arc>>, + logger: Arc, +} + +impl Bolt12Payment { + pub(crate) fn new( + runtime: Arc>>, + channel_manager: Arc, + payment_store: Arc>>, logger: Arc, + ) -> Self { + Self { runtime, channel_manager, payment_store, logger } + } + + /// Send a payment given an offer. + /// + /// If `payer_note` is `Some` it will be seen by the recipient and reflected back in the invoice + /// response. + pub fn send(&self, offer: &Offer, payer_note: Option) -> Result { + let rt_lock = self.runtime.read().unwrap(); + if rt_lock.is_none() { + return Err(Error::NotRunning); + } + + let quantity = None; + let mut random_bytes = [0u8; 32]; + rand::thread_rng().fill_bytes(&mut random_bytes); + let payment_id = PaymentId(random_bytes); + let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); + let max_total_routing_fee_msat = None; + + let offer_amount_msat = match offer.amount() { + Some(Amount::Bitcoin { amount_msats }) => amount_msats, + Some(_) => { + log_error!(self.logger, "Failed to send payment as the provided offer was denominated in an unsupported currency."); + return Err(Error::UnsupportedCurrency); + }, + None => { + log_error!(self.logger, "Failed to send payment due to the given offer being \"zero-amount\". Please use send_using_amount instead."); + return Err(Error::InvalidOffer); + }, + }; + + match self.channel_manager.pay_for_offer( + &offer, + quantity, + None, + payer_note, + payment_id, + retry_strategy, + max_total_routing_fee_msat, + ) { + Ok(()) => { + let payee_pubkey = offer.signing_pubkey(); + log_info!( + self.logger, + "Initiated sending {}msat to {:?}", + offer_amount_msat, + payee_pubkey + ); + + let kind = PaymentKind::Bolt12Offer { + hash: None, + preimage: None, + secret: None, + offer_id: offer.id(), + }; + let payment = PaymentDetails::new( + payment_id, + kind, + Some(*offer_amount_msat), + PaymentDirection::Outbound, + PaymentStatus::Pending, + ); + self.payment_store.insert(payment)?; + + Ok(payment_id) + }, + Err(e) => { + log_error!(self.logger, "Failed to send invoice request: {:?}", e); + match e { + Bolt12SemanticError::DuplicatePaymentId => Err(Error::DuplicatePayment), + _ => { + let kind = PaymentKind::Bolt12Offer { + hash: None, + preimage: None, + secret: None, + offer_id: offer.id(), + }; + let payment = PaymentDetails::new( + payment_id, + kind, + Some(*offer_amount_msat), + PaymentDirection::Outbound, + PaymentStatus::Failed, + ); + self.payment_store.insert(payment)?; + Err(Error::InvoiceRequestCreationFailed) + }, + } + }, + } + } + + /// Send a payment given an offer and an amount in millisatoshi. + /// + /// This will fail if the amount given is less than the value required by the given offer. + /// + /// This can be used to pay a so-called "zero-amount" offers, i.e., an offer that leaves the + /// amount paid to be determined by the user. + /// + /// If `payer_note` is `Some` it will be seen by the recipient and reflected back in the invoice + /// response. + pub fn send_using_amount( + &self, offer: &Offer, payer_note: Option, amount_msat: u64, + ) -> Result { + let rt_lock = self.runtime.read().unwrap(); + if rt_lock.is_none() { + return Err(Error::NotRunning); + } + + let quantity = None; + let mut random_bytes = [0u8; 32]; + rand::thread_rng().fill_bytes(&mut random_bytes); + let payment_id = PaymentId(random_bytes); + let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); + let max_total_routing_fee_msat = None; + + let offer_amount_msat = match offer.amount() { + Some(Amount::Bitcoin { amount_msats }) => *amount_msats, + Some(_) => { + log_error!(self.logger, "Failed to send payment as the provided offer was denominated in an unsupported currency."); + return Err(Error::UnsupportedCurrency); + }, + None => amount_msat, + }; + + if amount_msat < offer_amount_msat { + log_error!( + self.logger, + "Failed to pay as the given amount needs to be at least the offer amount: required {}msat, gave {}msat.", offer_amount_msat, amount_msat); + return Err(Error::InvalidAmount); + } + + match self.channel_manager.pay_for_offer( + &offer, + quantity, + Some(amount_msat), + payer_note, + payment_id, + retry_strategy, + max_total_routing_fee_msat, + ) { + Ok(()) => { + let payee_pubkey = offer.signing_pubkey(); + log_info!( + self.logger, + "Initiated sending {}msat to {:?}", + amount_msat, + payee_pubkey + ); + + let kind = PaymentKind::Bolt12Offer { + hash: None, + preimage: None, + secret: None, + offer_id: offer.id(), + }; + let payment = PaymentDetails::new( + payment_id, + kind, + Some(amount_msat), + PaymentDirection::Outbound, + PaymentStatus::Pending, + ); + self.payment_store.insert(payment)?; + + Ok(payment_id) + }, + Err(e) => { + log_error!(self.logger, "Failed to send payment: {:?}", e); + match e { + Bolt12SemanticError::DuplicatePaymentId => Err(Error::DuplicatePayment), + _ => { + let kind = PaymentKind::Bolt12Offer { + hash: None, + preimage: None, + secret: None, + offer_id: offer.id(), + }; + let payment = PaymentDetails::new( + payment_id, + kind, + Some(amount_msat), + PaymentDirection::Outbound, + PaymentStatus::Failed, + ); + self.payment_store.insert(payment)?; + Err(Error::PaymentSendingFailed) + }, + } + }, + } + } + + /// Returns a payable offer that can be used to request and receive a payment of the amount + /// given. + pub fn receive(&self, amount_msat: u64, description: &str) -> Result { + let offer_builder = self.channel_manager.create_offer_builder().map_err(|e| { + log_error!(self.logger, "Failed to create offer builder: {:?}", e); + Error::OfferCreationFailed + })?; + let offer = offer_builder + .amount_msats(amount_msat) + .description(description.to_string()) + .build() + .map_err(|e| { + log_error!(self.logger, "Failed to create offer: {:?}", e); + Error::OfferCreationFailed + })?; + + Ok(offer) + } + + /// Returns a payable offer that can be used to request and receive a payment for which the + /// amount is to be determined by the user, also known as a "zero-amount" offer. + pub fn receive_variable_amount(&self, description: &str) -> Result { + let offer_builder = self.channel_manager.create_offer_builder().map_err(|e| { + log_error!(self.logger, "Failed to create offer builder: {:?}", e); + Error::OfferCreationFailed + })?; + let offer = offer_builder.description(description.to_string()).build().map_err(|e| { + log_error!(self.logger, "Failed to create offer: {:?}", e); + Error::OfferCreationFailed + })?; + + Ok(offer) + } + + /// Requests a refund payment for the given [`Refund`]. + /// + /// The returned [`Bolt12Invoice`] is for informational purposes only (i.e., isn't needed to + /// retrieve the refund). + pub fn request_refund_payment(&self, refund: &Refund) -> Result { + let invoice = self.channel_manager.request_refund_payment(refund).map_err(|e| { + log_error!(self.logger, "Failed to request refund payment: {:?}", e); + Error::InvoiceRequestCreationFailed + })?; + + let payment_hash = invoice.payment_hash(); + let payment_id = PaymentId(payment_hash.0); + + let kind = + PaymentKind::Bolt12Refund { hash: Some(payment_hash), preimage: None, secret: None }; + + let payment = PaymentDetails::new( + payment_id, + kind, + Some(refund.amount_msats()), + PaymentDirection::Inbound, + PaymentStatus::Pending, + ); + + self.payment_store.insert(payment)?; + + Ok(invoice) + } + + /// Returns a [`Refund`] object that can be used to offer a refund payment of the amount given. + pub fn initiate_refund(&self, amount_msat: u64, expiry_secs: u32) -> Result { + let mut random_bytes = [0u8; 32]; + rand::thread_rng().fill_bytes(&mut random_bytes); + let payment_id = PaymentId(random_bytes); + + let expiration = (SystemTime::now() + Duration::from_secs(expiry_secs as u64)) + .duration_since(UNIX_EPOCH) + .unwrap(); + let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); + let max_total_routing_fee_msat = None; + + let refund = self + .channel_manager + .create_refund_builder( + amount_msat, + expiration, + payment_id, + retry_strategy, + max_total_routing_fee_msat, + ) + .map_err(|e| { + log_error!(self.logger, "Failed to create refund builder: {:?}", e); + Error::RefundCreationFailed + })? + .build() + .map_err(|e| { + log_error!(self.logger, "Failed to create refund: {:?}", e); + Error::RefundCreationFailed + })?; + + log_info!(self.logger, "Offering refund of {}msat", amount_msat); + + let kind = PaymentKind::Bolt12Refund { hash: None, preimage: None, secret: None }; + + let payment = PaymentDetails::new( + payment_id, + kind, + Some(amount_msat), + PaymentDirection::Outbound, + PaymentStatus::Pending, + ); + + self.payment_store.insert(payment)?; + + Ok(refund) + } +} diff --git a/src/payment/mod.rs b/src/payment/mod.rs index 3649f1fcc..1862bf2df 100644 --- a/src/payment/mod.rs +++ b/src/payment/mod.rs @@ -1,11 +1,13 @@ //! Objects for different types of payments. mod bolt11; +mod bolt12; mod onchain; mod spontaneous; pub(crate) mod store; pub use bolt11::Bolt11Payment; +pub use bolt12::Bolt12Payment; pub use onchain::OnchainPayment; pub use spontaneous::SpontaneousPayment; pub use store::{LSPFeeLimits, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus}; diff --git a/src/payment/onchain.rs b/src/payment/onchain.rs index 41d66861f..8a879ae8c 100644 --- a/src/payment/onchain.rs +++ b/src/payment/onchain.rs @@ -54,7 +54,7 @@ impl OnchainPayment { let cur_anchor_reserve_sats = crate::total_anchor_channels_reserve_sats(&self.channel_manager, &self.config); let spendable_amount_sats = - self.wallet.get_balances(cur_anchor_reserve_sats).map(|(_, s)| s).unwrap_or(0); + self.wallet.get_spendable_amount_sats(cur_anchor_reserve_sats).unwrap_or(0); if spendable_amount_sats < amount_sats { log_error!(self.logger, diff --git a/src/payment/spontaneous.rs b/src/payment/spontaneous.rs index 83c7dc283..9efb8fefd 100644 --- a/src/payment/spontaneous.rs +++ b/src/payment/spontaneous.rs @@ -86,20 +86,19 @@ impl SpontaneousPayment { Ok(_hash) => { log_info!(self.logger, "Initiated sending {}msat to {}.", amount_msat, node_id); - let payment = PaymentDetails { - id: payment_id, - kind: PaymentKind::Spontaneous { - hash: payment_hash, - preimage: Some(payment_preimage), - custom_tlvs, - }, - status: PaymentStatus::Pending, - direction: PaymentDirection::Outbound, - amount_msat: Some(amount_msat), - last_update: 0, - fee_msat: None, - created_at: 0, + let kind = PaymentKind::Spontaneous { + hash: payment_hash, + preimage: Some(payment_preimage), + custom_tlvs, }; + let payment = PaymentDetails::new( + payment_id, + kind, + Some(amount_msat), + PaymentDirection::Outbound, + PaymentStatus::Pending, + ); + self.payment_store.insert(payment)?; Ok(payment_id) @@ -110,21 +109,18 @@ impl SpontaneousPayment { match e { RetryableSendFailure::DuplicatePayment => Err(Error::DuplicatePayment), _ => { - let payment = PaymentDetails { - id: payment_id, - kind: PaymentKind::Spontaneous { - hash: payment_hash, - preimage: Some(payment_preimage), - custom_tlvs, - }, - - status: PaymentStatus::Failed, - direction: PaymentDirection::Outbound, - amount_msat: Some(amount_msat), - last_update: 0, - fee_msat: None, - created_at: 0, + let kind = PaymentKind::Spontaneous { + hash: payment_hash, + preimage: Some(payment_preimage), + custom_tlvs, }; + let payment = PaymentDetails::new( + payment_id, + kind, + Some(amount_msat), + PaymentDirection::Outbound, + PaymentStatus::Failed, + ); self.payment_store.insert(payment)?; Err(Error::PaymentSendingFailed) diff --git a/src/payment/store.rs b/src/payment/store.rs index b174d5f6b..354044f51 100644 --- a/src/payment/store.rs +++ b/src/payment/store.rs @@ -9,6 +9,7 @@ use crate::Error; use lightning::ln::channelmanager::PaymentId; use lightning::ln::msgs::DecodeError; use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; +use lightning::offers::offer::OfferId; use lightning::util::ser::{Readable, Writeable}; use lightning::{ _init_and_read_len_prefixed_tlv_fields, impl_writeable_tlv_based, @@ -19,7 +20,7 @@ use std::collections::HashMap; use std::iter::FromIterator; use std::ops::Deref; use std::sync::{Arc, Mutex}; -use std::time; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; /// Represents a payment. #[derive(Clone, Debug, PartialEq, Eq)] @@ -34,7 +35,10 @@ pub struct PaymentDetails { pub direction: PaymentDirection, /// The status of the payment. pub status: PaymentStatus, - /// Last update timestamp, as seconds since Unix epoch. + /// The timestamp, in seconds since start of the UNIX epoch, when this entry was last updated. + pub latest_update_timestamp: u64, + + /// Last update timestamp, as seconds since Unix epoch. TODO: remove and use latest_update_timestamp pub last_update: u64, /// Fee paid. pub fee_msat: Option, @@ -42,6 +46,34 @@ pub struct PaymentDetails { pub created_at: u64, } +impl PaymentDetails { + pub(crate) fn new( + id: PaymentId, kind: PaymentKind, amount_msat: Option, direction: PaymentDirection, + status: PaymentStatus, + ) -> Self { + let latest_update_timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or(Duration::from_secs(0)) + .as_secs(); + + let last_update = 0; + let fee_msat = None; + let created_at = 0; + + Self { + id, + kind, + amount_msat, + direction, + status, + latest_update_timestamp, + last_update, + fee_msat, + created_at, + } + } +} + impl Writeable for PaymentDetails { fn write( &self, writer: &mut W, @@ -54,6 +86,7 @@ impl Writeable for PaymentDetails { (3, self.kind, required), // 4 used to be `secret` before it was moved to `kind` in v0.3.0 (4, None::>, required), + (5, self.latest_update_timestamp, required), (6, self.amount_msat, required), (8, self.direction, required), (10, self.status, required), @@ -67,12 +100,17 @@ impl Writeable for PaymentDetails { impl Readable for PaymentDetails { fn read(reader: &mut R) -> Result { + let unix_time_secs = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or(Duration::from_secs(0)) + .as_secs(); _init_and_read_len_prefixed_tlv_fields!(reader, { (0, id, required), // Used to be `hash` (1, lsp_fee_limits, option), (2, preimage, required), (3, kind_opt, option), (4, secret, required), + (5, latest_update_timestamp, (default_value, unix_time_secs)), (6, amount_msat, required), (8, direction, required), (10, status, required), @@ -84,6 +122,8 @@ impl Readable for PaymentDetails { let id: PaymentId = id.0.ok_or(DecodeError::InvalidValue)?; let preimage: Option = preimage.0.ok_or(DecodeError::InvalidValue)?; let secret: Option = secret.0.ok_or(DecodeError::InvalidValue)?; + let latest_update_timestamp: u64 = + latest_update_timestamp.0.ok_or(DecodeError::InvalidValue)?; let amount_msat: Option = amount_msat.0.ok_or(DecodeError::InvalidValue)?; let direction: PaymentDirection = direction.0.ok_or(DecodeError::InvalidValue)?; let status: PaymentStatus = status.0.ok_or(DecodeError::InvalidValue)?; @@ -123,6 +163,7 @@ impl Readable for PaymentDetails { amount_msat, direction, status, + latest_update_timestamp, last_update, fee_msat, created_at, @@ -169,7 +210,6 @@ pub enum PaymentKind { /// A [BOLT 11] payment. /// /// [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md - // TODO: Bolt11 { invoice: Option }, Bolt11 { /// The payment hash, i.e., the hash of the `preimage`. hash: PaymentHash, @@ -184,7 +224,6 @@ pub enum PaymentKind { /// /// [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md /// [LSPS 2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md - // TODO: Bolt11Jit { invoice: Option }, Bolt11Jit { /// The payment hash, i.e., the hash of the `preimage`. hash: PaymentHash, @@ -202,6 +241,32 @@ pub enum PaymentKind { /// [`LdkChannelConfig::accept_underpaying_htlcs`]: lightning::util::config::ChannelConfig::accept_underpaying_htlcs lsp_fee_limits: LSPFeeLimits, }, + /// A [BOLT 12] 'offer' payment, i.e., a payment for an [`Offer`]. + /// + /// [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md + /// [`Offer`]: crate::lightning::offers::offer::Offer + Bolt12Offer { + /// The payment hash, i.e., the hash of the `preimage`. + hash: Option, + /// The pre-image used by the payment. + preimage: Option, + /// The secret used by the payment. + secret: Option, + /// The ID of the offer this payment is for. + offer_id: OfferId, + }, + /// A [BOLT 12] 'refund' payment, i.e., a payment for a [`Refund`]. + /// + /// [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md + /// [`Refund`]: lightning::offers::refund::Refund + Bolt12Refund { + /// The payment hash, i.e., the hash of the `preimage`. + hash: Option, + /// The pre-image used by the payment. + preimage: Option, + /// The secret used by the payment. + secret: Option, + }, /// A spontaneous ("keysend") payment. Spontaneous { /// The payment hash, i.e., the hash of the `preimage`. @@ -227,10 +292,21 @@ impl_writeable_tlv_based_enum!(PaymentKind, (4, secret, option), (6, lsp_fee_limits, required), }, + (6, Bolt12Offer) => { + (0, hash, option), + (2, preimage, option), + (4, secret, option), + (6, offer_id, required), + }, (8, Spontaneous) => { (0, hash, required), (2, preimage, option), (131072, custom_tlvs, optional_vec), + }, + (10, Bolt12Refund) => { + (0, hash, option), + (2, preimage, option), + (4, secret, option), }; ); @@ -257,6 +333,7 @@ impl_writeable_tlv_based!(LSPFeeLimits, { #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct PaymentDetailsUpdate { pub id: PaymentId, + pub hash: Option>, pub preimage: Option>, pub secret: Option>, pub amount_msat: Option>, @@ -269,6 +346,7 @@ impl PaymentDetailsUpdate { pub fn new(id: PaymentId) -> Self { Self { id, + hash: None, preimage: None, secret: None, amount_msat: None, @@ -304,12 +382,7 @@ where // If the payment already exists, reuse its timestamp instead of overwriting it. let created_at = locked_payments.get(&payment.id).map_or_else( - || { - time::SystemTime::now() - .duration_since(time::UNIX_EPOCH) - .unwrap_or(time::Duration::ZERO) - .as_secs() - }, + || SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or(Duration::ZERO).as_secs(), |p| p.created_at, ); @@ -351,10 +424,30 @@ where let mut locked_payments = self.payments.lock().unwrap(); if let Some(payment) = locked_payments.get_mut(&update.id) { + if let Some(hash_opt) = update.hash { + match payment.kind { + PaymentKind::Bolt12Offer { ref mut hash, .. } => { + debug_assert_eq!(payment.direction, PaymentDirection::Outbound, + "We should only ever override payment hash for outbound BOLT 12 payments"); + *hash = hash_opt + }, + PaymentKind::Bolt12Refund { ref mut hash, .. } => { + debug_assert_eq!(payment.direction, PaymentDirection::Outbound, + "We should only ever override payment hash for outbound BOLT 12 payments"); + *hash = hash_opt + }, + _ => { + // We can omit updating the hash for BOLT11 payments as the payment hash + // will always be known from the beginning. + }, + } + } if let Some(preimage_opt) = update.preimage { match payment.kind { PaymentKind::Bolt11 { ref mut preimage, .. } => *preimage = preimage_opt, PaymentKind::Bolt11Jit { ref mut preimage, .. } => *preimage = preimage_opt, + PaymentKind::Bolt12Offer { ref mut preimage, .. } => *preimage = preimage_opt, + PaymentKind::Bolt12Refund { ref mut preimage, .. } => *preimage = preimage_opt, PaymentKind::Spontaneous { ref mut preimage, .. } => *preimage = preimage_opt, _ => {}, } @@ -364,6 +457,8 @@ where match payment.kind { PaymentKind::Bolt11 { ref mut secret, .. } => *secret = secret_opt, PaymentKind::Bolt11Jit { ref mut secret, .. } => *secret = secret_opt, + PaymentKind::Bolt12Offer { ref mut secret, .. } => *secret = secret_opt, + PaymentKind::Bolt12Refund { ref mut secret, .. } => *secret = secret_opt, _ => {}, } } @@ -380,15 +475,18 @@ where payment.fee_msat = fee_msat; } - payment.last_update = time::SystemTime::now() - .duration_since(time::UNIX_EPOCH) - .unwrap_or(time::Duration::ZERO) + // TODO: remove + payment.last_update = + SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or(Duration::ZERO).as_secs(); + + payment.latest_update_timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or(Duration::from_secs(0)) .as_secs(); self.persist_info(&update.id, payment)?; updated = true; } - Ok(updated) } @@ -483,16 +581,8 @@ mod tests { .is_err()); let kind = PaymentKind::Bolt11 { hash, preimage: None, secret: None, bolt11_invoice: None }; - let payment = PaymentDetails { - id, - kind, - amount_msat: None, - direction: PaymentDirection::Inbound, - status: PaymentStatus::Pending, - last_update: 0, - fee_msat: None, - created_at: 0, - }; + let payment = + PaymentDetails::new(id, kind, None, PaymentDirection::Inbound, PaymentStatus::Pending); assert_eq!(Ok(false), payment_store.insert(payment.clone())); assert!(payment_store.get(&id).is_some()); diff --git a/src/tx_broadcaster.rs b/src/tx_broadcaster.rs index 40483f578..4492bcfc6 100644 --- a/src/tx_broadcaster.rs +++ b/src/tx_broadcaster.rs @@ -1,4 +1,5 @@ -use crate::logger::{log_bytes, log_debug, log_error, log_trace, Logger}; +use crate::config::TX_BROADCAST_TIMEOUT_SECS; +use crate::logger::{log_bytes, log_error, log_trace, Logger}; use lightning::chain::chaininterface::BroadcasterInterface; use lightning::util::ser::Writeable; @@ -7,6 +8,7 @@ use esplora_client::AsyncClient as EsploraClient; use bitcoin::Transaction; +use reqwest::StatusCode; use tokio::sync::mpsc; use tokio::sync::Mutex; @@ -38,57 +40,68 @@ where let mut receiver = self.queue_receiver.lock().await; while let Some(next_package) = receiver.recv().await { for tx in &next_package { - match self.esplora_client.broadcast(tx).await { - Ok(()) => { - log_trace!(self.logger, "Successfully broadcast transaction {}", tx.txid()); - }, - Err(e) => match e { - esplora_client::Error::Reqwest(_) => { - // Wait 500 ms and retry in case we get a `Reqwest` error (typically - // 429) - tokio::time::sleep(Duration::from_millis(500)).await; - log_error!( + let timeout_fut = tokio::time::timeout( + Duration::from_secs(TX_BROADCAST_TIMEOUT_SECS), + self.esplora_client.broadcast(tx), + ); + match timeout_fut.await { + Ok(res) => match res { + Ok(()) => { + log_trace!( self.logger, - "Sync failed due to HTTP connection error, retrying: {}", - e + "Successfully broadcast transaction {}", + tx.txid() ); - match self.esplora_client.broadcast(tx).await { - Ok(()) => { - log_debug!( - self.logger, - "Successfully broadcast transaction {}", - tx.txid() - ); - }, - Err(e) => { + }, + Err(e) => match e { + esplora_client::Error::Reqwest(err) => { + if err.status() == StatusCode::from_u16(400).ok() { + // Ignore 400, as this just means bitcoind already knows the + // transaction. + // FIXME: We can further differentiate here based on the error + // message which will be available with rust-esplora-client 0.7 and + // later. + } else { log_error!( self.logger, - "Failed to broadcast transaction {}: {}", - tx.txid(), - e - ); - log_trace!( - self.logger, - "Failed broadcast transaction bytes: {}", - log_bytes!(tx.encode()) + "Failed to broadcast due to HTTP connection error: {}", + err ); - }, - } - }, - _ => { - log_error!( - self.logger, - "Failed to broadcast transaction {}: {}", - tx.txid(), - e - ); - log_trace!( - self.logger, - "Failed broadcast transaction bytes: {}", - log_bytes!(tx.encode()) - ); + } + log_trace!( + self.logger, + "Failed broadcast transaction bytes: {}", + log_bytes!(tx.encode()) + ); + }, + _ => { + log_error!( + self.logger, + "Failed to broadcast transaction {}: {}", + tx.txid(), + e + ); + log_trace!( + self.logger, + "Failed broadcast transaction bytes: {}", + log_bytes!(tx.encode()) + ); + }, }, }, + Err(e) => { + log_error!( + self.logger, + "Failed to broadcast transaction due to timeout {}: {}", + tx.txid(), + e + ); + log_trace!( + self.logger, + "Failed broadcast transaction bytes: {}", + log_bytes!(tx.encode()) + ); + }, } } } diff --git a/src/types.rs b/src/types.rs index ae8b76a43..151de9c38 100644 --- a/src/types.rs +++ b/src/types.rs @@ -113,7 +113,7 @@ pub(crate) type OnionMessenger = lightning::onion_message::messenger::OnionMesse Arc, Arc, Arc, - IgnoringMessageHandler, + Arc, IgnoringMessageHandler, >; diff --git a/src/uniffi_types.rs b/src/uniffi_types.rs index 0771dff7d..13e7d2ea9 100644 --- a/src/uniffi_types.rs +++ b/src/uniffi_types.rs @@ -3,6 +3,9 @@ pub use crate::payment::store::{LSPFeeLimits, PaymentDirection, PaymentKind, Pay pub use lightning::events::{ClosureReason, PaymentFailureReason}; pub use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; +pub use lightning::offers::invoice::Bolt12Invoice; +pub use lightning::offers::offer::{Offer, OfferId}; +pub use lightning::offers::refund::Refund; pub use lightning::routing::gossip::{NodeId, RoutingFees}; pub use lightning::util::string::UntrustedString; @@ -22,6 +25,7 @@ use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash; use bitcoin::secp256k1::PublicKey; use lightning::ln::channelmanager::PaymentId; +use lightning::util::ser::Writeable; use lightning_invoice::SignedRawBolt11Invoice; use std::convert::TryInto; @@ -93,6 +97,65 @@ impl UniffiCustomTypeConverter for Bolt11Invoice { } } +impl UniffiCustomTypeConverter for Offer { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + Offer::from_str(&val).map_err(|_| Error::InvalidOffer.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.to_string() + } +} + +impl UniffiCustomTypeConverter for Refund { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + Refund::from_str(&val).map_err(|_| Error::InvalidRefund.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.to_string() + } +} + +impl UniffiCustomTypeConverter for Bolt12Invoice { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + if let Some(bytes_vec) = hex_utils::to_vec(&val) { + if let Ok(invoice) = Bolt12Invoice::try_from(bytes_vec) { + return Ok(invoice); + } + } + Err(Error::InvalidInvoice.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + hex_utils::to_string(&obj.encode()) + } +} + +impl UniffiCustomTypeConverter for OfferId { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + if let Some(bytes_vec) = hex_utils::to_vec(&val) { + let bytes_res = bytes_vec.try_into(); + if let Ok(bytes) = bytes_res { + return Ok(OfferId(bytes)); + } + } + Err(Error::InvalidOfferId.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + hex_utils::to_string(&obj.0) + } +} + impl UniffiCustomTypeConverter for PaymentId { type Builtin = String; diff --git a/src/wallet.rs b/src/wallet.rs index bde3873ae..33838da84 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -34,14 +34,13 @@ use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, Signing}; use bitcoin::{ScriptBuf, Transaction, TxOut, Txid}; -use std::mem; use std::ops::{Deref, DerefMut}; use std::sync::{Arc, Mutex, RwLock}; use std::time::Duration; enum WalletSyncStatus { Completed, - InProgress { subscribers: Vec>> }, + InProgress { subscribers: tokio::sync::broadcast::Sender> }, } pub struct Wallet @@ -90,9 +89,9 @@ where } pub(crate) async fn sync(&self) -> Result<(), Error> { - if let Some(sync_receiver) = self.register_or_subscribe_pending_sync() { + if let Some(mut sync_receiver) = self.register_or_subscribe_pending_sync() { log_info!(self.logger, "Sync in progress, skipping."); - return sync_receiver.await.map_err(|e| { + return sync_receiver.recv().await.map_err(|e| { debug_assert!(false, "Failed to receive wallet sync result: {:?}", e); log_error!(self.logger, "Failed to receive wallet sync result: {:?}", e); Error::WalletOperationFailed @@ -100,15 +99,14 @@ where } let res = { - let sync_options = SyncOptions { progress: None }; let wallet_lock = self.inner.lock().unwrap(); - let timeout_fut = tokio::time::timeout( + let wallet_sync_timeout_fut = tokio::time::timeout( Duration::from_secs(BDK_WALLET_SYNC_TIMEOUT_SECS), - wallet_lock.sync(&self.blockchain, sync_options), + wallet_lock.sync(&self.blockchain, SyncOptions { progress: None }), ); - match timeout_fut.await { + match wallet_sync_timeout_fut.await { Ok(res) => match res { Ok(()) => { // TODO: Drop this workaround after BDK 1.0 upgrade. @@ -140,12 +138,7 @@ where }, }, Err(e) => { - log_error!( - self.logger, - "On-chain wallet sync timed out after {}s: {}", - BDK_WALLET_SYNC_TIMEOUT_SECS, - e - ); + log_error!(self.logger, "On-chain wallet sync timed out: {}", e); Err(Error::WalletOperationTimeout) }, } @@ -237,6 +230,12 @@ where Ok((total, spendable)) } + pub(crate) fn get_spendable_amount_sats( + &self, total_anchor_channels_reserve_sats: u64, + ) -> Result { + self.get_balances(total_anchor_channels_reserve_sats).map(|(_, s)| s) + } + /// Send funds to the given address. /// /// If `amount_msat_or_drain` is `None` the wallet will be drained, i.e., all available funds will be @@ -317,18 +316,18 @@ where fn register_or_subscribe_pending_sync( &self, - ) -> Option>> { + ) -> Option>> { let mut sync_status_lock = self.sync_status.lock().unwrap(); match sync_status_lock.deref_mut() { WalletSyncStatus::Completed => { // We're first to register for a sync. - *sync_status_lock = WalletSyncStatus::InProgress { subscribers: Vec::new() }; + let (tx, _) = tokio::sync::broadcast::channel(1); + *sync_status_lock = WalletSyncStatus::InProgress { subscribers: tx }; None }, WalletSyncStatus::InProgress { subscribers } => { // A sync is in-progress, we subscribe. - let (tx, rx) = tokio::sync::oneshot::channel(); - subscribers.push(tx); + let rx = subscribers.subscribe(); Some(rx) }, } @@ -336,7 +335,6 @@ where fn propagate_result_to_subscribers(&self, res: Result<(), Error>) { // Send the notification to any other tasks that might be waiting on it by now. - let mut waiting_subscribers = Vec::new(); { let mut sync_status_lock = self.sync_status.lock().unwrap(); match sync_status_lock.deref_mut() { @@ -346,22 +344,27 @@ where }, WalletSyncStatus::InProgress { subscribers } => { // A sync is in-progress, we notify subscribers. - mem::swap(&mut waiting_subscribers, subscribers); + if subscribers.receiver_count() > 0 { + match subscribers.send(res) { + Ok(_) => (), + Err(e) => { + debug_assert!( + false, + "Failed to send wallet sync result to subscribers: {:?}", + e + ); + log_error!( + self.logger, + "Failed to send wallet sync result to subscribers: {:?}", + e + ); + }, + } + } *sync_status_lock = WalletSyncStatus::Completed; }, } } - - for sender in waiting_subscribers { - sender.send(res).unwrap_or_else(|e| { - debug_assert!(false, "Failed to send wallet sync result to subscribers: {:?}", e); - log_error!( - self.logger, - "Failed to send wallet sync result to subscribers: {:?}", - e - ); - }); - } } } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index a86f1b824..a85aa3032 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -2,17 +2,20 @@ #![allow(dead_code)] use ldk_node::io::sqlite_store::SqliteStore; -use ldk_node::payment::{PaymentDirection, PaymentStatus}; +use ldk_node::payment::{PaymentDirection, PaymentKind, PaymentStatus}; use ldk_node::{ Builder, Config, Event, LightningBalance, LogLevel, Node, NodeError, PendingSweepBalance, TlvEntry, }; use lightning::ln::msgs::SocketAddress; +use lightning::ln::{PaymentHash, PaymentPreimage}; use lightning::util::persist::KVStore; use lightning::util::test_utils::TestStore; use lightning_persister::fs_store::FilesystemStore; +use bitcoin::hashes::sha256::Hash as Sha256; +use bitcoin::hashes::Hash; use bitcoin::{Address, Amount, Network, OutPoint, Txid}; use bitcoincore_rpc::bitcoincore_rpc_json::AddressType; @@ -100,12 +103,39 @@ macro_rules! expect_payment_received_event { pub(crate) use expect_payment_received_event; +macro_rules! expect_payment_claimable_event { + ($node: expr, $payment_id: expr, $payment_hash: expr, $claimable_amount_msat: expr) => {{ + match $node.wait_next_event() { + ref e @ Event::PaymentClaimable { + payment_id, + payment_hash, + claimable_amount_msat, + .. + } => { + println!("{} got event {:?}", std::stringify!($node), e); + assert_eq!(payment_hash, $payment_hash); + assert_eq!(payment_id, $payment_id); + assert_eq!(claimable_amount_msat, $claimable_amount_msat); + $node.event_handled(); + claimable_amount_msat + }, + ref e => { + panic!("{} got unexpected event!: {:?}", std::stringify!($node), e); + }, + } + }}; +} + +pub(crate) use expect_payment_claimable_event; + macro_rules! expect_payment_successful_event { ($node: expr, $payment_id: expr, $fee_paid_msat: expr) => {{ match $node.wait_next_event() { ref e @ Event::PaymentSuccessful { payment_id, fee_paid_msat, .. } => { println!("{} got event {:?}", $node.node_id(), e); - assert_eq!(fee_paid_msat, $fee_paid_msat); + if let Some(fee_msat) = $fee_paid_msat { + assert_eq!(fee_paid_msat, fee_msat); + } assert_eq!(payment_id, $payment_id); $node.event_handled(); }, @@ -206,7 +236,7 @@ macro_rules! setup_builder { pub(crate) use setup_builder; pub(crate) fn setup_two_nodes( - electrsd: &ElectrsD, allow_0conf: bool, anchor_channels: bool, + electrsd: &ElectrsD, allow_0conf: bool, anchor_channels: bool, anchors_trusted_no_reserve: bool, ) -> (TestNode, TestNode) { println!("== Node A =="); let config_a = random_config(anchor_channels); @@ -217,6 +247,14 @@ pub(crate) fn setup_two_nodes( if allow_0conf { config_b.trusted_peers_0conf.push(node_a.node_id()); } + if anchor_channels && anchors_trusted_no_reserve { + config_b + .anchor_channels_config + .as_mut() + .unwrap() + .trusted_peers_no_reserve + .push(node_a.node_id()); + } let node_b = setup_node(electrsd, config_b); (node_a, node_b) } @@ -369,7 +407,7 @@ pub(crate) fn do_channel_full_cycle( let addr_a = node_a.onchain_payment().new_address().unwrap(); let addr_b = node_b.onchain_payment().new_address().unwrap(); - let premine_amount_sat = if expect_anchor_channel { 125_000 } else { 100_000 }; + let premine_amount_sat = if expect_anchor_channel { 2_125_000 } else { 2_100_000 }; premine_and_distribute_funds( &bitcoind, @@ -387,7 +425,7 @@ pub(crate) fn do_channel_full_cycle( assert_eq!(node_b.next_event(), None); println!("\nA -- connect_open_channel -> B"); - let funding_amount_sat = 80_000; + let funding_amount_sat = 2_080_000; let push_msat = (funding_amount_sat / 2) * 1000; // balance the channel node_a .connect_open_channel( @@ -416,20 +454,40 @@ pub(crate) fn do_channel_full_cycle( node_b.sync_wallets().unwrap(); let onchain_fee_buffer_sat = 1500; - let anchor_reserve_sat = if expect_anchor_channel { 25_000 } else { 0 }; - let node_a_upper_bound_sat = premine_amount_sat - anchor_reserve_sat - funding_amount_sat; - let node_a_lower_bound_sat = - premine_amount_sat - anchor_reserve_sat - funding_amount_sat - onchain_fee_buffer_sat; + let node_a_anchor_reserve_sat = if expect_anchor_channel { 25_000 } else { 0 }; + let node_a_upper_bound_sat = + premine_amount_sat - node_a_anchor_reserve_sat - funding_amount_sat; + let node_a_lower_bound_sat = premine_amount_sat + - node_a_anchor_reserve_sat + - funding_amount_sat + - onchain_fee_buffer_sat; assert!(node_a.list_balances().spendable_onchain_balance_sats < node_a_upper_bound_sat); assert!(node_a.list_balances().spendable_onchain_balance_sats > node_a_lower_bound_sat); assert_eq!( - node_b.list_balances().spendable_onchain_balance_sats, - premine_amount_sat - anchor_reserve_sat + node_a.list_balances().total_anchor_channels_reserve_sats, + node_a_anchor_reserve_sat ); - expect_channel_ready_event!(node_a, node_b.node_id()); + let node_b_anchor_reserve_sat = if node_b + .config() + .anchor_channels_config + .map_or(true, |acc| acc.trusted_peers_no_reserve.contains(&node_a.node_id())) + { + 0 + } else { + 25_000 + }; + assert_eq!( + node_b.list_balances().spendable_onchain_balance_sats, + premine_amount_sat - node_b_anchor_reserve_sat + ); + assert_eq!( + node_b.list_balances().total_anchor_channels_reserve_sats, + node_b_anchor_reserve_sat + ); - let user_channel_id = expect_channel_ready_event!(node_b, node_a.node_id()); + let user_channel_id = expect_channel_ready_event!(node_a, node_b.node_id()); + expect_channel_ready_event!(node_b, node_a.node_id()); println!("\nB receive"); let invoice_amount_1_msat = 2500_000; @@ -462,9 +520,11 @@ pub(crate) fn do_channel_full_cycle( assert_eq!(node_a.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); assert_eq!(node_a.payment(&payment_id).unwrap().direction, PaymentDirection::Outbound); assert_eq!(node_a.payment(&payment_id).unwrap().amount_msat, Some(invoice_amount_1_msat)); + assert!(matches!(node_a.payment(&payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); assert_eq!(node_b.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); assert_eq!(node_b.payment(&payment_id).unwrap().direction, PaymentDirection::Inbound); assert_eq!(node_b.payment(&payment_id).unwrap().amount_msat, Some(invoice_amount_1_msat)); + assert!(matches!(node_b.payment(&payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); // Assert we fail duplicate outbound payments and check the status hasn't changed. assert_eq!(Err(NodeError::DuplicatePayment), node_a.bolt11_payment().send(&invoice)); @@ -507,9 +567,11 @@ pub(crate) fn do_channel_full_cycle( assert_eq!(node_a.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); assert_eq!(node_a.payment(&payment_id).unwrap().direction, PaymentDirection::Outbound); assert_eq!(node_a.payment(&payment_id).unwrap().amount_msat, Some(overpaid_amount_msat)); + assert!(matches!(node_a.payment(&payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); assert_eq!(node_b.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); assert_eq!(node_b.payment(&payment_id).unwrap().direction, PaymentDirection::Inbound); assert_eq!(node_b.payment(&payment_id).unwrap().amount_msat, Some(overpaid_amount_msat)); + assert!(matches!(node_b.payment(&payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); // Test "zero-amount" invoice payment println!("\nB receive_variable_amount_payment"); @@ -541,9 +603,94 @@ pub(crate) fn do_channel_full_cycle( assert_eq!(node_a.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); assert_eq!(node_a.payment(&payment_id).unwrap().direction, PaymentDirection::Outbound); assert_eq!(node_a.payment(&payment_id).unwrap().amount_msat, Some(determined_amount_msat)); + assert!(matches!(node_a.payment(&payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); assert_eq!(node_b.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); assert_eq!(node_b.payment(&payment_id).unwrap().direction, PaymentDirection::Inbound); assert_eq!(node_b.payment(&payment_id).unwrap().amount_msat, Some(determined_amount_msat)); + assert!(matches!(node_b.payment(&payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); + + // Test claiming manually registered payments. + let invoice_amount_3_msat = 5_532_000; + let manual_preimage = PaymentPreimage([42u8; 32]); + let manual_payment_hash = PaymentHash(Sha256::hash(&manual_preimage.0).to_byte_array()); + let manual_invoice = node_b + .bolt11_payment() + .receive_for_hash(invoice_amount_3_msat, &"asdf", 9217, manual_payment_hash) + .unwrap(); + let manual_payment_id = node_a.bolt11_payment().send(&manual_invoice).unwrap(); + + let claimable_amount_msat = expect_payment_claimable_event!( + node_b, + manual_payment_id, + manual_payment_hash, + invoice_amount_3_msat + ); + node_b + .bolt11_payment() + .claim_for_hash(manual_payment_hash, claimable_amount_msat, manual_preimage) + .unwrap(); + expect_payment_received_event!(node_b, claimable_amount_msat); + expect_payment_successful_event!(node_a, Some(manual_payment_id), None); + assert_eq!(node_a.payment(&manual_payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&manual_payment_id).unwrap().direction, PaymentDirection::Outbound); + assert_eq!( + node_a.payment(&manual_payment_id).unwrap().amount_msat, + Some(invoice_amount_3_msat) + ); + assert!(matches!(node_a.payment(&manual_payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); + assert_eq!(node_b.payment(&manual_payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&manual_payment_id).unwrap().direction, PaymentDirection::Inbound); + assert_eq!( + node_b.payment(&manual_payment_id).unwrap().amount_msat, + Some(invoice_amount_3_msat) + ); + assert!(matches!(node_b.payment(&manual_payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); + + // Test failing manually registered payments. + let invoice_amount_4_msat = 5_532_000; + let manual_fail_preimage = PaymentPreimage([43u8; 32]); + let manual_fail_payment_hash = + PaymentHash(Sha256::hash(&manual_fail_preimage.0).to_byte_array()); + let manual_fail_invoice = node_b + .bolt11_payment() + .receive_for_hash(invoice_amount_3_msat, &"asdf", 9217, manual_fail_payment_hash) + .unwrap(); + let manual_fail_payment_id = node_a.bolt11_payment().send(&manual_fail_invoice).unwrap(); + + expect_payment_claimable_event!( + node_b, + manual_fail_payment_id, + manual_fail_payment_hash, + invoice_amount_4_msat + ); + node_b.bolt11_payment().fail_for_hash(manual_fail_payment_hash).unwrap(); + expect_event!(node_a, PaymentFailed); + assert_eq!(node_a.payment(&manual_fail_payment_id).unwrap().status, PaymentStatus::Failed); + assert_eq!( + node_a.payment(&manual_fail_payment_id).unwrap().direction, + PaymentDirection::Outbound + ); + assert_eq!( + node_a.payment(&manual_fail_payment_id).unwrap().amount_msat, + Some(invoice_amount_4_msat) + ); + assert!(matches!( + node_a.payment(&manual_fail_payment_id).unwrap().kind, + PaymentKind::Bolt11 { .. } + )); + assert_eq!(node_b.payment(&manual_fail_payment_id).unwrap().status, PaymentStatus::Failed); + assert_eq!( + node_b.payment(&manual_fail_payment_id).unwrap().direction, + PaymentDirection::Inbound + ); + assert_eq!( + node_b.payment(&manual_fail_payment_id).unwrap().amount_msat, + Some(invoice_amount_4_msat) + ); + assert!(matches!( + node_b.payment(&manual_fail_payment_id).unwrap().kind, + PaymentKind::Bolt11 { .. } + )); // Test spontaneous/keysend payments println!("\nA send_spontaneous_payment"); @@ -569,16 +716,26 @@ pub(crate) fn do_channel_full_cycle( assert_eq!(node_a.payment(&keysend_payment_id).unwrap().status, PaymentStatus::Succeeded); assert_eq!(node_a.payment(&keysend_payment_id).unwrap().direction, PaymentDirection::Outbound); assert_eq!(node_a.payment(&keysend_payment_id).unwrap().amount_msat, Some(keysend_amount_msat)); + assert!(matches!( + node_a.payment(&keysend_payment_id).unwrap().kind, + PaymentKind::Spontaneous { .. } + )); assert_eq!(node_b.payment(&keysend_payment_id).unwrap().status, PaymentStatus::Succeeded); assert_eq!(node_b.payment(&keysend_payment_id).unwrap().direction, PaymentDirection::Inbound); assert_eq!(node_b.payment(&keysend_payment_id).unwrap().amount_msat, Some(keysend_amount_msat)); + assert!(matches!( + node_b.payment(&keysend_payment_id).unwrap().kind, + PaymentKind::Spontaneous { .. } + )); + assert_eq!(node_a.list_payments().len(), 6); + assert_eq!(node_b.list_payments().len(), 7); println!("\nB close_channel (force: {})", force_close); if force_close { std::thread::sleep(Duration::from_secs(1)); - node_b.close_channel(&user_channel_id, node_a.node_id(), true).unwrap(); + node_a.force_close_channel(&user_channel_id, node_b.node_id()).unwrap(); } else { - node_b.close_channel(&user_channel_id, node_a.node_id(), false).unwrap(); + node_a.close_channel(&user_channel_id, node_b.node_id()).unwrap(); } expect_event!(node_a, ChannelClosed); @@ -591,89 +748,90 @@ pub(crate) fn do_channel_full_cycle( node_b.sync_wallets().unwrap(); if force_close { - // Check node_a properly sees all balances and sweeps them. - assert_eq!(node_a.list_balances().lightning_balances.len(), 1); - match node_a.list_balances().lightning_balances[0] { + // Check node_b properly sees all balances and sweeps them. + assert_eq!(node_b.list_balances().lightning_balances.len(), 1); + match node_b.list_balances().lightning_balances[0] { LightningBalance::ClaimableAwaitingConfirmations { counterparty_node_id, confirmation_height, .. } => { - assert_eq!(counterparty_node_id, node_b.node_id()); - let cur_height = node_a.status().current_best_block.height; + assert_eq!(counterparty_node_id, node_a.node_id()); + let cur_height = node_b.status().current_best_block.height; let blocks_to_go = confirmation_height - cur_height; generate_blocks_and_wait(&bitcoind, electrsd, blocks_to_go as usize); - node_a.sync_wallets().unwrap(); node_b.sync_wallets().unwrap(); + node_a.sync_wallets().unwrap(); }, _ => panic!("Unexpected balance state!"), } - assert!(node_a.list_balances().lightning_balances.is_empty()); - assert_eq!(node_a.list_balances().pending_balances_from_channel_closures.len(), 1); - match node_a.list_balances().pending_balances_from_channel_closures[0] { + assert!(node_b.list_balances().lightning_balances.is_empty()); + assert_eq!(node_b.list_balances().pending_balances_from_channel_closures.len(), 1); + match node_b.list_balances().pending_balances_from_channel_closures[0] { PendingSweepBalance::BroadcastAwaitingConfirmation { .. } => {}, _ => panic!("Unexpected balance state!"), } generate_blocks_and_wait(&bitcoind, electrsd, 1); - node_a.sync_wallets().unwrap(); node_b.sync_wallets().unwrap(); + node_a.sync_wallets().unwrap(); - assert!(node_a.list_balances().lightning_balances.is_empty()); - assert_eq!(node_a.list_balances().pending_balances_from_channel_closures.len(), 1); - match node_a.list_balances().pending_balances_from_channel_closures[0] { + assert!(node_b.list_balances().lightning_balances.is_empty()); + assert_eq!(node_b.list_balances().pending_balances_from_channel_closures.len(), 1); + match node_b.list_balances().pending_balances_from_channel_closures[0] { PendingSweepBalance::AwaitingThresholdConfirmations { .. } => {}, _ => panic!("Unexpected balance state!"), } generate_blocks_and_wait(&bitcoind, electrsd, 5); - node_a.sync_wallets().unwrap(); node_b.sync_wallets().unwrap(); + node_a.sync_wallets().unwrap(); - assert!(node_a.list_balances().lightning_balances.is_empty()); - assert!(node_a.list_balances().pending_balances_from_channel_closures.is_empty()); + assert!(node_b.list_balances().lightning_balances.is_empty()); + assert!(node_b.list_balances().pending_balances_from_channel_closures.is_empty()); - // Check node_b properly sees all balances and sweeps them. - assert_eq!(node_b.list_balances().lightning_balances.len(), 1); - match node_b.list_balances().lightning_balances[0] { + // Check node_a properly sees all balances and sweeps them. + assert_eq!(node_a.list_balances().lightning_balances.len(), 1); + match node_a.list_balances().lightning_balances[0] { LightningBalance::ClaimableAwaitingConfirmations { counterparty_node_id, confirmation_height, .. } => { - assert_eq!(counterparty_node_id, node_a.node_id()); - let cur_height = node_b.status().current_best_block.height; + assert_eq!(counterparty_node_id, node_b.node_id()); + let cur_height = node_a.status().current_best_block.height; let blocks_to_go = confirmation_height - cur_height; generate_blocks_and_wait(&bitcoind, electrsd, blocks_to_go as usize); - node_b.sync_wallets().unwrap(); node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); }, _ => panic!("Unexpected balance state!"), } - assert!(node_b.list_balances().lightning_balances.is_empty()); - assert_eq!(node_b.list_balances().pending_balances_from_channel_closures.len(), 1); - match node_b.list_balances().pending_balances_from_channel_closures[0] { + assert!(node_a.list_balances().lightning_balances.is_empty()); + assert_eq!(node_a.list_balances().pending_balances_from_channel_closures.len(), 1); + match node_a.list_balances().pending_balances_from_channel_closures[0] { PendingSweepBalance::BroadcastAwaitingConfirmation { .. } => {}, _ => panic!("Unexpected balance state!"), } generate_blocks_and_wait(&bitcoind, electrsd, 1); - node_b.sync_wallets().unwrap(); node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); - assert!(node_b.list_balances().lightning_balances.is_empty()); - assert_eq!(node_b.list_balances().pending_balances_from_channel_closures.len(), 1); - match node_b.list_balances().pending_balances_from_channel_closures[0] { + assert!(node_a.list_balances().lightning_balances.is_empty()); + assert_eq!(node_a.list_balances().pending_balances_from_channel_closures.len(), 1); + match node_a.list_balances().pending_balances_from_channel_closures[0] { PendingSweepBalance::AwaitingThresholdConfirmations { .. } => {}, _ => panic!("Unexpected balance state!"), } generate_blocks_and_wait(&bitcoind, electrsd, 5); - node_b.sync_wallets().unwrap(); node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); } let sum_of_all_payments_sat = (push_msat + invoice_amount_1_msat + overpaid_amount_msat + + invoice_amount_3_msat + determined_amount_msat + keysend_amount_msat) / 1000; @@ -688,6 +846,9 @@ pub(crate) fn do_channel_full_cycle( assert!(node_b.list_balances().spendable_onchain_balance_sats > node_b_lower_bound_sat); assert!(node_b.list_balances().spendable_onchain_balance_sats <= node_b_upper_bound_sat); + assert_eq!(node_a.list_balances().total_anchor_channels_reserve_sats, 0); + assert_eq!(node_b.list_balances().total_anchor_channels_reserve_sats, 0); + // Check we handled all events assert_eq!(node_a.next_event(), None); assert_eq!(node_b.next_event(), None); diff --git a/tests/integration_tests_cln.rs b/tests/integration_tests_cln.rs index 752cce26e..fc3b10bfc 100644 --- a/tests/integration_tests_cln.rs +++ b/tests/integration_tests_cln.rs @@ -88,6 +88,7 @@ fn test_cln() { let funding_txo = common::expect_channel_pending_event!(node, cln_node_id); common::wait_for_tx(&electrs_client, funding_txo.txid); common::generate_blocks_and_wait(&bitcoind_client, &electrs_client, 6); + node.sync_wallets().unwrap(); let user_channel_id = common::expect_channel_ready_event!(node, cln_node_id); assert_eq!(node.list_channels().first().unwrap().channel_type, Some(ChannelType::Anchors)); @@ -111,7 +112,7 @@ fn test_cln() { cln_client.pay(&ldk_invoice.to_string(), Default::default()).unwrap(); common::expect_event!(node, PaymentReceived); - node.close_channel(&user_channel_id, cln_node_id, false).unwrap(); + node.close_channel(&user_channel_id, cln_node_id).unwrap(); common::expect_event!(node, ChannelClosed); node.stop().unwrap(); } diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 8732e1e41..37ddeb9a7 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -7,46 +7,57 @@ use common::{ setup_node, setup_two_nodes, wait_for_tx, TestSyncStore, }; +use ldk_node::payment::PaymentKind; use ldk_node::{Builder, Event, NodeError}; +use lightning::ln::channelmanager::PaymentId; use lightning::util::persist::KVStore; use bitcoin::{Amount, Network}; use std::sync::Arc; +use crate::common::expect_channel_ready_event; + #[test] fn channel_full_cycle() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let (node_a, node_b) = setup_two_nodes(&electrsd, false, true); + let (node_a, node_b) = setup_two_nodes(&electrsd, false, true, false); do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, true, false); } #[test] fn channel_full_cycle_force_close() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let (node_a, node_b) = setup_two_nodes(&electrsd, false, true); + let (node_a, node_b) = setup_two_nodes(&electrsd, false, true, false); + do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, true, true); +} + +#[test] +fn channel_full_cycle_force_close_trusted_no_reserve() { + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let (node_a, node_b) = setup_two_nodes(&electrsd, false, true, true); do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, true, true); } #[test] fn channel_full_cycle_0conf() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let (node_a, node_b) = setup_two_nodes(&electrsd, true, true); + let (node_a, node_b) = setup_two_nodes(&electrsd, true, true, false); do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, true, true, false) } #[test] fn channel_full_cycle_legacy_staticremotekey() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let (node_a, node_b) = setup_two_nodes(&electrsd, false, false); + let (node_a, node_b) = setup_two_nodes(&electrsd, false, false, false); do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, false, false); } #[test] fn channel_open_fails_when_funds_insufficient() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let (node_a, node_b) = setup_two_nodes(&electrsd, false, true); + let (node_a, node_b) = setup_two_nodes(&electrsd, false, true, false); let addr_a = node_a.onchain_payment().new_address().unwrap(); let addr_b = node_b.onchain_payment().new_address().unwrap(); @@ -152,7 +163,7 @@ fn multi_hop_sending() { let payment_id = expect_payment_received_event!(&nodes[4], 2_500_000); let fee_paid_msat = Some(2000); - expect_payment_successful_event!(nodes[0], payment_id, fee_paid_msat); + expect_payment_successful_event!(nodes[0], payment_id, Some(fee_paid_msat)); } #[test] @@ -237,7 +248,7 @@ fn start_stop_reinit() { #[test] fn onchain_spend_receive() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let (node_a, node_b) = setup_two_nodes(&electrsd, false, true); + let (node_a, node_b) = setup_two_nodes(&electrsd, false, true, false); let addr_a = node_a.onchain_payment().new_address().unwrap(); let addr_b = node_b.onchain_payment().new_address().unwrap(); @@ -303,7 +314,7 @@ fn connection_restart_behavior() { fn do_connection_restart_behavior(persist: bool) { let (_bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let (node_a, node_b) = setup_two_nodes(&electrsd, false, false); + let (node_a, node_b) = setup_two_nodes(&electrsd, false, false, false); let node_id_a = node_a.node_id(); let node_id_b = node_b.node_id(); @@ -354,7 +365,7 @@ fn do_connection_restart_behavior(persist: bool) { #[test] fn concurrent_connections_succeed() { let (_bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let (node_a, node_b) = setup_two_nodes(&electrsd, false, true); + let (node_a, node_b) = setup_two_nodes(&electrsd, false, true, false); let node_a = Arc::new(node_a); let node_b = Arc::new(node_b); @@ -380,3 +391,164 @@ fn concurrent_connections_succeed() { h.join().unwrap(); } } + +#[test] +fn simple_bolt12_send_receive() { + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let (node_a, node_b) = setup_two_nodes(&electrsd, false, true, false); + + let address_a = node_a.onchain_payment().new_address().unwrap(); + let premine_amount_sat = 5_000_000; + premine_and_distribute_funds( + &bitcoind.client, + &electrsd.client, + vec![address_a], + Amount::from_sat(premine_amount_sat), + ); + + node_a.sync_wallets().unwrap(); + open_channel(&node_a, &node_b, 4_000_000, true, &electrsd); + + generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6); + + node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); + + expect_channel_ready_event!(node_a, node_b.node_id()); + expect_channel_ready_event!(node_b, node_a.node_id()); + + // Sleep until we broadcasted a node announcement. + while node_b.status().latest_node_announcement_broadcast_timestamp.is_none() { + std::thread::sleep(std::time::Duration::from_millis(10)); + } + + // Sleep one more sec to make sure the node announcement propagates. + std::thread::sleep(std::time::Duration::from_secs(1)); + + let expected_amount_msat = 100_000_000; + let offer = node_b.bolt12_payment().receive(expected_amount_msat, "asdf").unwrap(); + let payment_id = node_a.bolt12_payment().send(&offer, None).unwrap(); + + expect_payment_successful_event!(node_a, Some(payment_id), None); + let node_a_payments = node_a.list_payments(); + assert_eq!(node_a_payments.len(), 1); + match node_a_payments.first().unwrap().kind { + PaymentKind::Bolt12Offer { hash, preimage, secret: _, offer_id } => { + assert!(hash.is_some()); + assert!(preimage.is_some()); + assert_eq!(offer_id, offer.id()); + //TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12 + //API currently doesn't allow to do that. + }, + _ => { + panic!("Unexpected payment kind"); + }, + } + assert_eq!(node_a_payments.first().unwrap().amount_msat, Some(expected_amount_msat)); + + expect_payment_received_event!(node_b, expected_amount_msat); + let node_b_payments = node_b.list_payments(); + assert_eq!(node_b_payments.len(), 1); + match node_b_payments.first().unwrap().kind { + PaymentKind::Bolt12Offer { hash, preimage, secret, offer_id } => { + assert!(hash.is_some()); + assert!(preimage.is_some()); + assert!(secret.is_some()); + assert_eq!(offer_id, offer.id()); + }, + _ => { + panic!("Unexpected payment kind"); + }, + } + assert_eq!(node_b_payments.first().unwrap().amount_msat, Some(expected_amount_msat)); + + // Test send_using_amount + let offer_amount_msat = 100_000_000; + let less_than_offer_amount = offer_amount_msat - 10_000; + let expected_amount_msat = offer_amount_msat + 10_000; + let offer = node_b.bolt12_payment().receive(offer_amount_msat, "asdf").unwrap(); + assert!(node_a + .bolt12_payment() + .send_using_amount(&offer, None, less_than_offer_amount) + .is_err()); + let payment_id = + node_a.bolt12_payment().send_using_amount(&offer, None, expected_amount_msat).unwrap(); + + expect_payment_successful_event!(node_a, Some(payment_id), None); + let node_a_payments = node_a.list_payments_with_filter(|p| p.id == payment_id); + assert_eq!(node_a_payments.len(), 1); + let payment_hash = match node_a_payments.first().unwrap().kind { + PaymentKind::Bolt12Offer { hash, preimage, secret: _, offer_id } => { + assert!(hash.is_some()); + assert!(preimage.is_some()); + assert_eq!(offer_id, offer.id()); + //TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12 + //API currently doesn't allow to do that. + hash.unwrap() + }, + _ => { + panic!("Unexpected payment kind"); + }, + }; + assert_eq!(node_a_payments.first().unwrap().amount_msat, Some(expected_amount_msat)); + + expect_payment_received_event!(node_b, expected_amount_msat); + let node_b_payment_id = PaymentId(payment_hash.0); + let node_b_payments = node_b.list_payments_with_filter(|p| p.id == node_b_payment_id); + assert_eq!(node_b_payments.len(), 1); + match node_b_payments.first().unwrap().kind { + PaymentKind::Bolt12Offer { hash, preimage, secret, offer_id } => { + assert!(hash.is_some()); + assert!(preimage.is_some()); + assert!(secret.is_some()); + assert_eq!(offer_id, offer.id()); + }, + _ => { + panic!("Unexpected payment kind"); + }, + } + assert_eq!(node_b_payments.first().unwrap().amount_msat, Some(expected_amount_msat)); + + // Now node_b refunds the amount node_a just overpaid. + let overpaid_amount = expected_amount_msat - offer_amount_msat; + let refund = node_b.bolt12_payment().initiate_refund(overpaid_amount, 3600).unwrap(); + let invoice = node_a.bolt12_payment().request_refund_payment(&refund).unwrap(); + expect_payment_received_event!(node_a, overpaid_amount); + + let node_b_payment_id = node_b + .list_payments_with_filter(|p| p.amount_msat == Some(overpaid_amount)) + .first() + .unwrap() + .id; + expect_payment_successful_event!(node_b, Some(node_b_payment_id), None); + + let node_b_payments = node_b.list_payments_with_filter(|p| p.id == node_b_payment_id); + assert_eq!(node_b_payments.len(), 1); + match node_b_payments.first().unwrap().kind { + PaymentKind::Bolt12Refund { hash, preimage, secret: _ } => { + assert!(hash.is_some()); + assert!(preimage.is_some()); + //TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12 + //API currently doesn't allow to do that. + }, + _ => { + panic!("Unexpected payment kind"); + }, + } + assert_eq!(node_b_payments.first().unwrap().amount_msat, Some(overpaid_amount)); + + let node_a_payment_id = PaymentId(invoice.payment_hash().0); + let node_a_payments = node_a.list_payments_with_filter(|p| p.id == node_a_payment_id); + assert_eq!(node_a_payments.len(), 1); + match node_a_payments.first().unwrap().kind { + PaymentKind::Bolt12Refund { hash, preimage, secret } => { + assert!(hash.is_some()); + assert!(preimage.is_some()); + assert!(secret.is_some()); + }, + _ => { + panic!("Unexpected payment kind"); + }, + } + assert_eq!(node_a_payments.first().unwrap().amount_msat, Some(overpaid_amount)); +}