diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 73cbba6b3..0ebb1bf72 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,23 +39,6 @@ jobs: shell: bash # Default on Winblows is powershell run: cargo check --workspace --verbose --color always - wasm: - runs-on: ubuntu-latest - env: - TOOLCHAIN: stable - steps: - - name: Checkout source code - uses: actions/checkout@v4 - - name: Install Rust ${{ env.TOOLCHAIN }} toolchain - run: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ env.TOOLCHAIN }} - - name: Install wasm32 target - run: rustup target add wasm32-unknown-unknown - - name: Build lightning for wasm32 (rgb-wasm backend) - run: cargo check -p lightning --target wasm32-unknown-unknown --no-default-features --features std,grind_signatures,rgb-wasm --verbose --color always - - name: Build lightning-transaction-sync for wasm32 (rgb-wasm backend) - run: cargo check --manifest-path lightning-transaction-sync/Cargo.toml --target wasm32-unknown-unknown --no-default-features --features time,esplora-async,rgb-wasm --verbose --color always - linting: runs-on: ubuntu-latest env: diff --git a/lightning-background-processor/Cargo.toml b/lightning-background-processor/Cargo.toml index dfd19244a..828a80175 100644 --- a/lightning-background-processor/Cargo.toml +++ b/lightning-background-processor/Cargo.toml @@ -23,7 +23,7 @@ std = ["lightning/std", "lightning-liquidity/std", "bitcoin-io/std", "bitcoin_ha bitcoin = { version = "0.32.2", default-features = false } bitcoin_hashes = { version = "0.14.0", default-features = false } bitcoin-io = { version = "0.1.2", default-features = false } -lightning = { version = "0.2.0", path = "../lightning", default-features = false, features = ["rgb-native"] } +lightning = { version = "0.2.0", path = "../lightning", default-features = false } lightning-rapid-gossip-sync = { version = "0.2.0", path = "../lightning-rapid-gossip-sync", default-features = false } lightning-liquidity = { version = "0.2.0", path = "../lightning-liquidity", default-features = false } possiblyrandom = { version = "0.2", path = "../possiblyrandom", default-features = false } diff --git a/lightning-dns-resolver/Cargo.toml b/lightning-dns-resolver/Cargo.toml index c20f99d9b..248cb7302 100644 --- a/lightning-dns-resolver/Cargo.toml +++ b/lightning-dns-resolver/Cargo.toml @@ -9,7 +9,7 @@ description = "A crate which implements DNSSEC resolution for lightning clients edition = "2021" [dependencies] -lightning = { version = "0.2.0", path = "../lightning", default-features = false, features = ["rgb-native"] } +lightning = { version = "0.2.0", path = "../lightning", default-features = false } lightning-types = { version = "0.3.0", path = "../lightning-types", default-features = false } dnssec-prover = { version = "0.6", default-features = false, features = [ "std", "tokio" ] } tokio = { version = "1.0", default-features = false, features = ["rt"] } diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml index 117745a6d..fe5d4b0db 100644 --- a/lightning-invoice/Cargo.toml +++ b/lightning-invoice/Cargo.toml @@ -17,16 +17,17 @@ rustdoc-args = ["--cfg", "docsrs"] [features] std = [] -default = [] - [dependencies] bech32 = { version = "0.11.0", default-features = false } lightning-types = { version = "0.3.0", path = "../lightning-types", default-features = false } serde = { version = "1.0", optional = true, default-features = false, features = ["alloc"] } bitcoin = { version = "0.32.2", default-features = false, features = ["secp-recovery"] } -# RGB protocol types -rgb-ops = { version = "=0.11.1-rc.10", default-features = false } +# RGB and related +rgb-lib = { git = "https://github.com/UTEXO-Protocol/rgb-lib.git", tag = "v0.3.0-beta.24", features = [ + "electrum", + "esplora", +] } [dev-dependencies] serde_json = { version = "1"} diff --git a/lightning-invoice/src/de.rs b/lightning-invoice/src/de.rs index f872f1b0b..9256dd37c 100644 --- a/lightning-invoice/src/de.rs +++ b/lightning-invoice/src/de.rs @@ -22,7 +22,7 @@ use lightning_types::routing::{RouteHint, RouteHintHop, RoutingFees}; use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId}; use bitcoin::secp256k1::PublicKey; -use crate::ContractId; +use rgb_lib::ContractId; use super::{ constants, Bolt11Invoice, Bolt11InvoiceFeatures, Bolt11InvoiceSignature, Bolt11ParseError, diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index 5e9db074a..1731b2198 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -40,7 +40,7 @@ use bitcoin::secp256k1::ecdsa::RecoverableSignature; use bitcoin::secp256k1::PublicKey; use bitcoin::secp256k1::{Message, Secp256k1}; -pub use rgbstd::ContractId; +use rgb_lib::ContractId; use alloc::boxed::Box; use alloc::string; @@ -1682,7 +1682,7 @@ impl Bolt11Invoice { /// Returns the invoice's `rgb_contract_id` if present pub fn rgb_contract_id(&self) -> Option { - self.signed_invoice.rgb_contract_id().map(|x| x.0.clone()) + self.signed_invoice.rgb_contract_id().map(|x| x.0) } } diff --git a/lightning-liquidity/Cargo.toml b/lightning-liquidity/Cargo.toml index 0763f5231..a4855957f 100644 --- a/lightning-liquidity/Cargo.toml +++ b/lightning-liquidity/Cargo.toml @@ -21,7 +21,7 @@ backtrace = ["dep:backtrace"] _test_utils = [] [dependencies] -lightning = { version = "0.2.0", path = "../lightning", default-features = false, features = ["rgb-native"] } +lightning = { version = "0.2.0", path = "../lightning", default-features = false } lightning-types = { version = "0.3.0", path = "../lightning-types", default-features = false } lightning-invoice = { version = "0.34.0", path = "../lightning-invoice", default-features = false, features = ["serde"] } lightning-macros = { version = "0.2.0", path = "../lightning-macros" } @@ -34,7 +34,7 @@ serde_json = { version = "1.0", default-features = false, features = ["alloc"] } backtrace = { version = "0.3", optional = true } [dev-dependencies] -lightning = { version = "0.2.0", path = "../lightning", default-features = false, features = ["_test_utils", "rgb-native"] } +lightning = { version = "0.2.0", path = "../lightning", default-features = false, features = ["_test_utils"] } lightning-invoice = { version = "0.34.0", path = "../lightning-invoice", default-features = false, features = ["serde", "std"] } lightning-persister = { version = "0.2.0", path = "../lightning-persister", default-features = false } diff --git a/lightning-rapid-gossip-sync/Cargo.toml b/lightning-rapid-gossip-sync/Cargo.toml index cb292228c..695e41a36 100644 --- a/lightning-rapid-gossip-sync/Cargo.toml +++ b/lightning-rapid-gossip-sync/Cargo.toml @@ -14,7 +14,7 @@ default = ["std"] std = ["bitcoin-io/std", "bitcoin_hashes/std"] [dependencies] -lightning = { version = "0.2.0", path = "../lightning", default-features = false, features = ["rgb-native"] } +lightning = { version = "0.2.0", path = "../lightning", default-features = false } bitcoin = { version = "0.32.2", default-features = false } bitcoin_hashes = { version = "0.14.0", default-features = false } bitcoin-io = { version = "0.1.2", default-features = false } diff --git a/lightning-transaction-sync/Cargo.toml b/lightning-transaction-sync/Cargo.toml index cf0394d61..30908cabd 100644 --- a/lightning-transaction-sync/Cargo.toml +++ b/lightning-transaction-sync/Cargo.toml @@ -15,10 +15,8 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] -default = ["time", "rgb-native"] -time = ["dep:web-time"] -rgb-native = ["lightning/rgb-native"] -rgb-wasm = ["lightning/rgb-wasm"] +default = ["time"] +time = [] esplora-async = ["async-interface", "esplora-client/async", "esplora-client/tokio", "futures"] esplora-async-https = ["esplora-async", "esplora-client/async-https-rustls"] esplora-blocking = ["esplora-client/blocking"] @@ -40,10 +38,9 @@ bitcoin = { version = "0.32.2", default-features = false } futures = { version = "0.3", optional = true } esplora-client = { version = "0.12", default-features = false, optional = true } electrum-client = { version = "0.24.0", optional = true, default-features = false, features = ["proxy"] } -web-time = { version = "1.1", optional = true } [dev-dependencies] -lightning = { version = "0.2.0", path = "../lightning", default-features = false, features = ["std", "_test_utils", "rgb-native"] } +lightning = { version = "0.2.0", path = "../lightning", default-features = false, features = ["std", "_test_utils"] } tokio = { version = "1.35.0", features = ["macros"] } [target.'cfg(not(target_os = "windows"))'.dev-dependencies] diff --git a/lightning-transaction-sync/src/electrum.rs b/lightning-transaction-sync/src/electrum.rs index fb06c65ed..695e8c7c4 100644 --- a/lightning-transaction-sync/src/electrum.rs +++ b/lightning-transaction-sync/src/electrum.rs @@ -23,7 +23,7 @@ use bitcoin::{BlockHash, Script, Transaction, Txid}; use std::collections::HashSet; use std::ops::Deref; use std::sync::{Arc, Mutex}; -use web_time::Instant; +use std::time::Instant; /// Synchronizes LDK with a given Electrum server. /// diff --git a/lightning-transaction-sync/src/esplora.rs b/lightning-transaction-sync/src/esplora.rs index bcf1207ac..a191260bc 100644 --- a/lightning-transaction-sync/src/esplora.rs +++ b/lightning-transaction-sync/src/esplora.rs @@ -100,7 +100,7 @@ where log_trace!(self.logger, "Starting transaction sync."); #[cfg(feature = "time")] - let start_time = web_time::Instant::now(); + let start_time = std::time::Instant::now(); let mut num_confirmed = 0; let mut num_unconfirmed = 0; diff --git a/lightning/Cargo.toml b/lightning/Cargo.toml index a92795e94..a46ec6dfb 100644 --- a/lightning/Cargo.toml +++ b/lightning/Cargo.toml @@ -24,17 +24,14 @@ _externalize_tests = ["inventory", "_test_utils"] # This is unsafe to use in production because it may result in the counterparty publishing taking our funds. unsafe_revoked_tx_signing = [] -std = ["dep:web-time"] +std = [] dnssec = ["dnssec-prover/validation"] # Generates low-r bitcoin signatures, which saves 1 byte in 50% of the cases grind_signatures = [] -rgb-native = ["dep:rgb-lib", "dep:tokio"] -rgb-wasm = ["std", "dep:rgb-lib-wasm"] - -default = ["std", "grind_signatures", "rgb-native"] +default = ["std", "grind_signatures"] [dependencies] lightning-types = { version = "0.3.0", path = "../lightning-types", default-features = false } @@ -56,16 +53,18 @@ inventory = { version = "0.3", optional = true } # RGB and related bincode = "1.3" -rgb-invoicing = { version = "=0.11.1-rc.10", default-features = false, features = ["serde"] } -rgb-lib = { git = "https://github.com/UTEXO-Protocol/rgb-lib.git", tag = "v0.3.0-beta.24", optional = true, features = [ +futures = "0.3" +rgb-lib = { git = "https://github.com/UTEXO-Protocol/rgb-lib.git", tag = "v0.3.0-beta.24", features = [ "electrum", "esplora", ] } -rgb-lib-wasm = { git = "https://github.com/UTEXO-Protocol/rgb-lib-wasm.git", branch = "dev", optional = true } -serde = { version = "^1.0", features = ["derive"] } -serde_json = "1.0" -tokio = { version = "1.14.1", optional = true, features = ["macros", "rt-multi-thread"] } -web-time = { version = "1.1", optional = true } +serde = { version = "^1.0", features = [ + "derive", +] } +tokio = { version = "1.14.1", features = [ + "macros", + "rt-multi-thread", +] } [dev-dependencies] regex = "1.5.6" diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 3b6876258..bf6e51325 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -32,7 +32,7 @@ use bitcoin::hashes::Hash; use bitcoin::ecdsa::Signature as BitcoinSignature; use bitcoin::secp256k1::{self, ecdsa::Signature, PublicKey, Secp256k1, SecretKey}; -use crate::rgb_utils::ContractId; +use rgb_lib::ContractId; use crate::chain; use crate::chain::chaininterface::{ diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 2f6d1d953..7ba4478a3 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -16,7 +16,7 @@ pub mod bump_transaction; -use crate::rgb_utils::ContractId; +use rgb_lib::ContractId; pub use bump_transaction::BumpTransactionEvent; @@ -34,7 +34,6 @@ use crate::offers::invoice::Bolt12Invoice; use crate::offers::invoice_request::InvoiceRequest; use crate::offers::static_invoice::StaticInvoice; use crate::onion_message::messenger::Responder; -use crate::rgb_utils::RgbTransport; use crate::routing::gossip::NetworkUpdate; use crate::routing::router::{BlindedTail, Path, RouteHop, RouteParameters}; use crate::sign::SpendableOutputDescriptor; @@ -786,35 +785,6 @@ pub enum Event { /// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels user_channel_id: u128, }, - /// Requests asynchronous validation of an incoming RGB funding transfer. - /// - /// Call [`ChannelManager::process_pending_rgb_funding_validation`] to validate the transfer - /// using the injected production RGB backend and finish processing `funding_created`, or call - /// [`ChannelManager::reject_pending_rgb_funding_validation`] to reject the pending channel. - /// - /// [`ChannelManager::process_pending_rgb_funding_validation`]: crate::ln::channelmanager::ChannelManager::process_pending_rgb_funding_validation - /// [`ChannelManager::reject_pending_rgb_funding_validation`]: crate::ln::channelmanager::ChannelManager::reject_pending_rgb_funding_validation - RgbFundingValidationRequired { - /// Temporary channel ID awaiting RGB funding validation. - temporary_channel_id: ChannelId, - /// Node that sent the funding message. - counterparty_node_id: PublicKey, - /// Funding transaction ID used to retrieve the consignment. - funding_txid: bitcoin::Txid, - /// Funding output index whose RGB assignment must be validated. - funding_output_index: u16, - /// Endpoint from which the RGB consignment must be retrieved. - consignment_endpoint: RgbTransport, - }, - /// Indicates that prepared RGB transactions are waiting for durable fascia consumption. - /// - /// Call [`ChannelManager::process_pending_rgb_transactions`] before expecting blocked - /// commitment, HTLC, funding, or cooperative-close signatures and messages to resume. - /// - /// This event is regenerated from the persisted RGB KVStore records after reload. - /// - /// [`ChannelManager::process_pending_rgb_transactions`]: crate::ln::channelmanager::ChannelManager::process_pending_rgb_transactions - RgbTransactionPersistenceRequired, /// Used to indicate that the counterparty node has provided the signature(s) required to /// recover our funds in case they go offline. /// @@ -1933,14 +1903,6 @@ impl Writeable for Event { // We never write out FundingGenerationReady events as, upon disconnection, peers // drop any channels which have not yet exchanged funding_signed. }, - &Event::RgbFundingValidationRequired { .. } => { - 55u8.write(writer)?; - // Regenerated from ChannelManager's persisted pending validation records. - }, - &Event::RgbTransactionPersistenceRequired => { - 57u8.write(writer)?; - // Regenerated from persisted pending fascia records. - }, &Event::PaymentClaimable { ref payment_hash, ref amount_msat, @@ -2442,10 +2404,6 @@ impl MaybeReadable for Event { match Readable::read(reader)? { // Note that we do not write a length-prefixed TLV for FundingGenerationReady events. 0u8 => Ok(None), - // Regenerated from ChannelManager's persisted pending validation records. - 55u8 => Ok(None), - // Regenerated from the RGB KVStore's pending fascia records. - 57u8 => Ok(None), 1u8 => { let mut f = || { let mut payment_hash = PaymentHash([0; 32]); diff --git a/lightning/src/lib.rs b/lightning/src/lib.rs index e4fc5da8d..e809279ec 100644 --- a/lightning/src/lib.rs +++ b/lightning/src/lib.rs @@ -23,14 +23,11 @@ //! * `std` - enables functionalities which require `std`, including `std::io` trait implementations and things which utilize time //! * `grind_signatures` - enables generation of [low-r bitcoin signatures](https://bitcoin.stackexchange.com/questions/111660/what-is-signature-grinding), //! which saves 1 byte per signature in 50% of the cases (see [bitcoin PR #13666](https://github.com/bitcoin/bitcoin/pull/13666)) -//! * `rgb-native` - selects the native production RGB wallet backend //! //! Available features are: //! //! * `std` //! * `grind_signatures` -//! * `rgb-native` -//! * `rgb-wasm` - selects the browser WebAssembly production RGB wallet backend #![cfg_attr(not(any(test, fuzzing, feature = "_test_utils")), deny(missing_docs))] #![deny(rustdoc::broken_intra_doc_links)] @@ -46,15 +43,6 @@ #[cfg(all(fuzzing, test))] compile_error!("Tests will always fail with cfg=fuzzing"); -#[cfg(all(feature = "rgb-native", feature = "rgb-wasm"))] -compile_error!("`rgb-native` and `rgb-wasm` are mutually exclusive production RGB backends"); - -#[cfg(not(any(feature = "rgb-native", feature = "rgb-wasm")))] -compile_error!("exactly one production RGB backend must be enabled: `rgb-native` or `rgb-wasm`"); - -#[cfg(all(feature = "rgb-wasm", not(target_arch = "wasm32")))] -compile_error!("the `rgb-wasm` production backend requires a wasm32 target"); - #[macro_use] extern crate alloc; diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index c6e4008fa..f3f084418 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -11,6 +11,7 @@ //! largely of interest for those implementing the traits on [`crate::sign`] by hand. use std::collections::HashMap; +use std::path::PathBuf; use bitcoin::amount::Amount; use bitcoin::constants::WITNESS_SCALE_FACTOR; @@ -33,7 +34,7 @@ use crate::chain::chaininterface::{ }; use crate::chain::package::WEIGHT_REVOKED_OUTPUT; use crate::ln::msgs::DecodeError; -use crate::rgb_utils::{color_htlc, is_tx_colored, RgbBackend}; +use crate::rgb_utils::{color_htlc, is_tx_colored}; use crate::sign::EntropySource; use crate::types::payment::{PaymentHash, PaymentPreimage}; use crate::util::persist::KVStoreSync; @@ -46,7 +47,7 @@ use bitcoin::secp256k1::{ecdsa::Signature, Message, Secp256k1}; use bitcoin::secp256k1::{PublicKey, Scalar, SecretKey}; use bitcoin::{secp256k1, Sequence, Witness}; -use crate::rgb_utils::ContractId; +use rgb_lib::ContractId; use super::channel_keys::{ DelayedPaymentBasepoint, DelayedPaymentKey, HtlcBasepoint, HtlcKey, RevocationBasepoint, @@ -2246,7 +2247,7 @@ impl<'a> TrustedCommitmentTransaction<'a> { #[rustfmt::skip] pub fn get_htlc_sigs( &self, htlc_base_key: &SecretKey, channel_parameters: &DirectedChannelTransactionParameters, - entropy_source: &ES, secp_ctx: &Secp256k1, rgb_backend: &RgbBackend, + entropy_source: &ES, secp_ctx: &Secp256k1, ldk_data_dir: &PathBuf, rgb_kv_store: &dyn KVStoreSync, ) -> Result, ()> where ES::Target: EntropySource { let inner = self.inner; @@ -2259,10 +2260,7 @@ impl<'a> TrustedCommitmentTransaction<'a> { assert!(this_htlc.transaction_output_index.is_some()); let mut htlc_tx = build_htlc_transaction(&txid, inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc, &self.channel_type_features, &keys.broadcaster_delayed_payment_key, &keys.revocation_key); if inner.is_colored() { - if let Err(_e) = color_htlc(&mut htlc_tx, this_htlc, rgb_backend, rgb_kv_store) { - return Err(()); - } - if !rgb_backend.is_transaction_durable(&htlc_tx.compute_txid(), rgb_kv_store) { + if let Err(_e) = color_htlc(&mut htlc_tx, this_htlc, ldk_data_dir, rgb_kv_store) { return Err(()); } } diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index f372a0fc0..7afa028b2 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -7,6 +7,8 @@ // You may not use this file except in accordance with one or both of these // licenses. +use std::path::PathBuf; + use bitcoin::absolute::LockTime; use bitcoin::amount::{Amount, SignedAmount}; use bitcoin::consensus::encode; @@ -28,7 +30,7 @@ use bitcoin::secp256k1::{ecdsa::Signature, Secp256k1}; use bitcoin::secp256k1::{PublicKey, SecretKey}; use bitcoin::{secp256k1, sighash, FeeRate, Sequence, TxIn}; -use crate::rgb_utils::{ContractId, RgbTransport}; +use rgb_lib::{ContractId, RgbTransport}; use crate::blinded_path::message::BlindedMessagePath; use crate::chain::chaininterface::{ @@ -79,7 +81,7 @@ use crate::rgb_utils::{ color_closing, color_commitment, color_htlc, get_rgb_channel_info_pending, holder_validate_install_psbt_output_witness_scripts_hex, holder_validate_take_psbt_output_witness_scripts_hex, is_tx_colored, rename_rgb_files, - update_rgb_channel_amount_pending, RgbBackend, RgbKvStoreExt, + update_rgb_channel_amount_pending, RgbKvStoreExt, }; use crate::routing::gossip::NodeId; use crate::sign::ecdsa::EcdsaChannelSigner; @@ -1562,14 +1564,6 @@ where } } - pub(super) fn as_unfunded_inbound_v1(&self) -> Option<&InboundV1Channel> { - if let ChannelPhase::UnfundedInboundV1(channel) = &self.phase { - Some(channel) - } else { - None - } - } - #[cfg(any(test, feature = "_externalize_tests"))] pub fn is_unfunded_v1(&self) -> bool { matches!( @@ -3206,7 +3200,7 @@ where pub(super) is_colored: bool, - pub(crate) rgb_backend: Arc, + pub(crate) ldk_data_dir: PathBuf, /// KVStore for RGB data persistence pub(crate) rgb_kv_store: Arc, @@ -3238,7 +3232,7 @@ where holder_commitment_point.next_transaction_number(), &holder_commitment_point.next_point(), true, false, logger); if self.context().is_colored() { - color_commitment(&self.context(), &self.funding(), &mut commitment_data.tx, false)?; + color_commitment(&self.context(), &self.funding(), &mut commitment_data.tx, false).expect("successful commitment coloring"); } let initial_commitment_tx = commitment_data.tx; let trusted_tx = initial_commitment_tx.trust(); @@ -3282,7 +3276,7 @@ where context.counterparty_next_commitment_transaction_number, &context.counterparty_next_commitment_point.unwrap(), false, false, logger); if self.context().is_colored() { - color_commitment(&self.context(), &self.funding(), &mut commitment_data.tx, true)?; + color_commitment(&self.context(), &self.funding(), &mut commitment_data.tx, true).unwrap(); } let counterparty_initial_commitment_tx = commitment_data.tx; let counterparty_trusted_tx = counterparty_initial_commitment_tx.trust(); @@ -3319,8 +3313,7 @@ where let temporary_channel_id = context.channel_id; context.channel_id = channel_id; if context.is_colored() && temporary_channel_id != channel_id { - rename_rgb_files(&context.channel_id, &temporary_channel_id, context.rgb_kv_store.as_ref()) - .map_err(|e| ChannelError::close(format!("Failed to rename RGB files: {e}")))?; + rename_rgb_files(&context.channel_id, &temporary_channel_id, context.rgb_kv_store.as_ref()); } assert!(!context.channel_state.is_monitor_update_in_progress()); // We have not had any monitor(s) yet to fail update! @@ -3490,7 +3483,7 @@ where msg_push_msat: u64, open_channel_fields: msgs::CommonOpenChannelFields, push_asset_amount: Option, - rgb_backend: Arc, + ldk_data_dir: PathBuf, rgb_kv_store: Arc, ) -> Result<(FundingScope, ChannelContext), ChannelError> where @@ -3821,7 +3814,7 @@ where interactive_tx_signing_session: None, is_colored: funding.consignment_endpoint.is_some(), - rgb_backend, + ldk_data_dir, rgb_kv_store, }; @@ -3847,7 +3840,7 @@ where holder_signer: ::EcdsaSigner, _logger: L, consignment_endpoint: Option, - rgb_backend: Arc, + ldk_data_dir: PathBuf, push_asset_amount: Option, rgb_kv_store: Arc, ) -> Result<(FundingScope, ChannelContext), APIError> @@ -4070,7 +4063,7 @@ where interactive_tx_signing_session: None, is_colored: funding.consignment_endpoint.is_some(), - rgb_backend, + ldk_data_dir, rgb_kv_store, }; @@ -5143,7 +5136,8 @@ where logger, ); if self.is_colored() { - color_commitment(&self, &funding, &mut commitment_data.tx, false)?; + color_commitment(&self, &funding, &mut commitment_data.tx, false) + .expect("successful commitment coloring"); } let commitment_txid = { let trusted_tx = commitment_data.tx.trust(); @@ -5200,12 +5194,8 @@ where &holder_keys.revocation_key, ); if self.is_colored() && !self.is_trusted_no_broadcast() { - color_htlc( - &mut htlc_tx, - htlc, - self.rgb_backend.as_ref(), - self.rgb_kv_store.as_ref(), - )?; + color_htlc(&mut htlc_tx, htlc, &self.ldk_data_dir, self.rgb_kv_store.as_ref()) + .expect("successful htlc coloring"); } let htlc_redeemscript = @@ -6350,15 +6340,6 @@ where log_trace!(logger, "Initial counterparty tx for channel {} is: txid {} tx {}", &self.channel_id(), counterparty_initial_bitcoin_tx.txid, encode::serialize_hex(&counterparty_initial_bitcoin_tx.transaction)); - if counterparty_initial_commitment_tx.is_colored() - && !self - .rgb_backend - .is_transaction_durable(&counterparty_initial_bitcoin_tx.txid, self.rgb_kv_store.as_ref()) - { - self.signer_pending_funding = true; - return None; - } - // We sign "counterparty" commitment transaction, allowing them to broadcast the tx if they wish. let signature = match &self.holder_signer { // TODO (arik): move match into calling method for Taproot @@ -6482,21 +6463,11 @@ where true, logger, ); - if self.is_colored() - && color_commitment(&self, &funding, &mut commitment_data.tx, true).is_err() - { - // Defer signature production (as the not-yet-durable branch below does) instead of - // panicking if RGB coloring/persistence fails; it is retried when storage recovers. - return None; + if self.is_colored() { + color_commitment(&self, &funding, &mut commitment_data.tx, true) + .expect("successful commitment coloring"); } let counterparty_initial_commitment_tx = commitment_data.tx; - if counterparty_initial_commitment_tx.is_colored() - && !self.rgb_backend.is_transaction_durable( - &counterparty_initial_commitment_tx.trust().built_transaction().txid, - self.rgb_kv_store.as_ref(), - ) { - return None; - } match self.holder_signer { // TODO (taproot|arik): move match into calling method for Taproot ChannelSignerType::Ecdsa(ref ecdsa) => { @@ -7476,9 +7447,10 @@ where color_closing( &self.context.channel_id, &mut closing_transaction, - self.context.rgb_backend.as_ref(), + &self.context.ldk_data_dir, self.context.rgb_kv_store.as_ref(), - )?; + ) + .expect("successful closing TX coloring"); } Ok((closing_transaction, total_fee_satoshis)) } @@ -8209,7 +8181,8 @@ where &pending_splice_funding, &mut counterparty_commitment_tx, true, - )?; + ) + .expect("successful commitment coloring"); } { @@ -9076,10 +9049,7 @@ where rgb_offered_htlc, rgb_received_htlc, self.context.rgb_kv_store.as_ref(), - ) - .map_err(|e| { - ChannelError::close(format!("Failed to update RGB channel amount: {e}")) - })?; + ); } if let Some((feerate, update_state)) = self.context.pending_update_fee { @@ -10815,22 +10785,6 @@ where where L::Target: Logger, { - if closing_tx - .trust() - .built_transaction() - .output - .iter() - .any(|output| output.script_pubkey.is_op_return()) - && !self.context.rgb_backend.is_transaction_durable( - &closing_tx.trust().built_transaction().compute_txid(), - self.context.rgb_kv_store.as_ref(), - ) { - self.context.signer_pending_closing = true; - let fee_range = msgs::ClosingSignedFeeRange { min_fee_satoshis, max_fee_satoshis }; - self.context.last_sent_closing_fee = - Some((fee_satoshis, skip_remote_output, fee_range, None)); - return None; - } let sig = match &self.context.holder_signer { ChannelSignerType::Ecdsa(ecdsa) => ecdsa .sign_closing_transaction( @@ -11909,11 +11863,7 @@ where let were_node_one = node_id.as_slice() < counterparty_node_id.as_slice(); let contract_id = if self.context.is_colored() { - let rgb_info = get_rgb_channel_info_pending( - &self.context.channel_id, - self.context.rgb_kv_store.as_ref(), - ) - .map_err(|e| ChannelError::close(format!("Failed to read RGB channel info: {e}")))?; + let rgb_info = get_rgb_channel_info_pending(&self.context.channel_id, self.context.rgb_kv_store.as_ref()); Some(rgb_info.contract_id) } else { None @@ -13067,10 +13017,7 @@ where } } if self.context.is_colored() && rgb_received_htlc > 0 { - // This commitment builder has no fallible return; a KVStore failure updating the RGB - // channel balance is unrecoverable for the channel (pre-existing behavior). - update_rgb_channel_amount_pending(&self.context.channel_id, 0, rgb_received_htlc, self.context.rgb_kv_store.as_ref()) - .expect("RGB channel amount update"); + update_rgb_channel_amount_pending(&self.context.channel_id, 0, rgb_received_htlc, self.context.rgb_kv_store.as_ref()); } if let Some((feerate, update_state)) = self.context.pending_update_fee { if update_state == FeeUpdateState::AwaitingRemoteRevokeToAnnounce { @@ -13197,16 +13144,6 @@ where color_commitment(&self.context, &self.funding, &mut commitment_data.tx, true)?; } let counterparty_commitment_tx = commitment_data.tx; - if counterparty_commitment_tx.is_colored() - && !self.context.rgb_backend.is_transaction_durable( - &counterparty_commitment_tx.trust().built_transaction().txid, - self.context.rgb_kv_store.as_ref(), - ) - { - return Err(ChannelError::Ignore( - "RGB commitment fascia is awaiting durable persistence".to_owned(), - )); - } match &self.context.holder_signer { ChannelSignerType::Ecdsa(ecdsa) => { @@ -13746,7 +13683,7 @@ where pub fn new( fee_estimator: &LowerBoundedFeeEstimator, entropy_source: &ES, signer_provider: &SP, counterparty_node_id: PublicKey, their_features: &InitFeatures, channel_value_satoshis: u64, push_msat: u64, user_id: u128, config: &UserConfig, current_chain_height: u32, - outbound_scid_alias: u64, temporary_channel_id: Option, logger: L, consignment_endpoint: Option, rgb_backend: Arc, push_asset_amount: Option, + outbound_scid_alias: u64, temporary_channel_id: Option, logger: L, consignment_endpoint: Option, ldk_data_dir: PathBuf, push_asset_amount: Option, rgb_kv_store: Arc, ) -> Result, APIError> where ES::Target: EntropySource, @@ -13788,7 +13725,7 @@ where holder_signer, logger, consignment_endpoint, - rgb_backend, + ldk_data_dir, push_asset_amount, rgb_kv_store, )?; @@ -13809,24 +13746,10 @@ where let mut commitment_data = self.context.build_commitment_transaction(&self.funding, self.context.counterparty_next_commitment_transaction_number, &self.context.counterparty_next_commitment_point.unwrap(), false, false, logger); - if self.context.is_colored() - && color_commitment(&self.context, &self.funding, &mut commitment_data.tx, true).is_err() - { - // Defer (like the not-yet-durable branch below) rather than panicking on an RGB - // coloring/persistence failure. - self.context.signer_pending_funding = true; - return None; + if self.context.is_colored() { + color_commitment(&self.context, &self.funding, &mut commitment_data.tx, true).unwrap(); } let counterparty_initial_commitment_tx = commitment_data.tx; - if counterparty_initial_commitment_tx.is_colored() - && !self.context.rgb_backend.is_transaction_durable( - &counterparty_initial_commitment_tx.trust().built_transaction().txid, - self.context.rgb_kv_store.as_ref(), - ) - { - self.context.signer_pending_funding = true; - return None; - } let signature = match &self.context.holder_signer { // TODO (taproot|arik): move match into calling method for Taproot ChannelSignerType::Ecdsa(ecdsa) => { @@ -13888,8 +13811,7 @@ where let temporary_channel_id = self.context.channel_id; self.context.channel_id = ChannelId::v1_from_funding_outpoint(funding_txo); if self.context.is_colored() { - rename_rgb_files(&self.context.channel_id, &temporary_channel_id, self.context.rgb_kv_store.as_ref()) - .expect("RGB file rename"); + rename_rgb_files(&self.context.channel_id, &temporary_channel_id, self.context.rgb_kv_store.as_ref()); } // If the funding transaction is a coinbase transaction, we need to set the minimum depth to 100. @@ -14099,8 +14021,6 @@ where pub context: ChannelContext, pub unfunded_context: UnfundedChannelContext, pub signer_pending_accept_channel: bool, - pub open_channel_msg: msgs::OpenChannel, - pub is_0conf: bool, } /// Fetches the [`ChannelTypeFeatures`] that will be used for a channel built from a given @@ -14145,7 +14065,7 @@ where fee_estimator: &LowerBoundedFeeEstimator, entropy_source: &ES, signer_provider: &SP, counterparty_node_id: PublicKey, our_supported_features: &ChannelTypeFeatures, their_features: &InitFeatures, msg: &msgs::OpenChannel, user_id: u128, config: &UserConfig, - current_chain_height: u32, logger: &L, is_0conf: bool, is_virtual: bool, rgb_backend: Arc, + current_chain_height: u32, logger: &L, is_0conf: bool, is_virtual: bool, ldk_data_dir: PathBuf, rgb_kv_store: Arc, ) -> Result, ChannelError> where ES::Target: EntropySource, @@ -14188,21 +14108,14 @@ where msg.push_msat, msg.common_fields.clone(), msg.push_asset_amount, - rgb_backend, + ldk_data_dir, rgb_kv_store, )?; let unfunded_context = UnfundedChannelContext { unfunded_channel_age_ticks: 0, holder_commitment_point: HolderCommitmentPoint::new(&context.holder_signer, &context.secp_ctx), }; - let chan = Self { - funding, - context, - unfunded_context, - signer_pending_accept_channel: false, - open_channel_msg: msg.clone(), - is_0conf, - }; + let chan = Self { funding, context, unfunded_context, signer_pending_accept_channel: false }; Ok(chan) } @@ -14397,7 +14310,7 @@ where counterparty_node_id: PublicKey, their_features: &InitFeatures, funding_satoshis: u64, funding_inputs: Vec, user_id: u128, config: &UserConfig, current_chain_height: u32, outbound_scid_alias: u64, funding_confirmation_target: ConfirmationTarget, - logger: L, rgb_backend: Arc, rgb_kv_store: Arc, + logger: L, ldk_data_dir: PathBuf, rgb_kv_store: Arc, ) -> Result where ES::Target: EntropySource, F::Target: FeeEstimator, @@ -14439,7 +14352,7 @@ where logger, // ok to pass consignment_endpoint as None since this method is unused None, - rgb_backend, + ldk_data_dir, None, rgb_kv_store, )?; @@ -14552,7 +14465,7 @@ where holder_node_id: PublicKey, counterparty_node_id: PublicKey, our_supported_features: &ChannelTypeFeatures, their_features: &InitFeatures, msg: &msgs::OpenChannelV2, user_id: u128, config: &UserConfig, current_chain_height: u32, logger: &L, - rgb_backend: Arc, rgb_kv_store: Arc, + ldk_data_dir: PathBuf, rgb_kv_store: Arc, ) -> Result where ES::Target: EntropySource, F::Target: FeeEstimator, @@ -14599,7 +14512,7 @@ where 0 /* push_msat not used in dual-funding */, msg.common_fields.clone(), None, - rgb_backend, + ldk_data_dir, rgb_kv_store, )?; let channel_id = ChannelId::v2_from_revocation_basepoints( @@ -15300,7 +15213,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> &'a ES, &'b SP, &'c ChannelTypeFeatures, - Arc, + PathBuf, Arc, )> for FundedChannel where @@ -15313,11 +15226,11 @@ where &'a ES, &'b SP, &'c ChannelTypeFeatures, - Arc, + PathBuf, Arc, ), ) -> Result { - let (entropy_source, signer_provider, our_supported_features, rgb_backend, rgb_kv_store) = + let (entropy_source, signer_provider, our_supported_features, ldk_data_dir, rgb_kv_store) = args; let ver = read_ver_prefix!(reader, SERIALIZATION_VERSION); if ver <= 2 { @@ -16119,7 +16032,7 @@ where interactive_tx_signing_session, is_colored: consignment_endpoint.is_some(), - rgb_backend, + ldk_data_dir, rgb_kv_store, }, holder_commitment_point, @@ -16135,8 +16048,8 @@ fn duration_since_epoch() -> Option { #[cfg(feature = "std")] let now = Some( - web_time::SystemTime::now() - .duration_since(web_time::SystemTime::UNIX_EPOCH) + std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"), ); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 22b55cb7b..4b082e8b3 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -17,6 +17,8 @@ //! on-chain transactions (it only monitors the chain to watch for any force-closes that might //! imply it needs to fail HTLCs/payments/channels it manages). +use std::path::PathBuf; + use bitcoin::block::Header; use bitcoin::constants::ChainHash; use bitcoin::key::constants::SECRET_KEY_SIZE; @@ -32,9 +34,9 @@ use bitcoin::hex::DisplayHex; use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1::{PublicKey, SecretKey}; -use bitcoin::{secp256k1, ScriptBuf, Sequence, SignedAmount}; +use bitcoin::{secp256k1, Sequence, SignedAmount}; -use crate::rgb_utils::{ContractId, RgbTransport}; +use rgb_lib::{ContractId, RgbTransport}; use crate::blinded_path::message::{ AsyncPaymentsContext, BlindedMessagePath, MessageForwardNode, OffersContext, @@ -117,10 +119,7 @@ use crate::onion_message::messenger::{ MessageRouter, MessageSendInstructions, Responder, ResponseInstruction, }; use crate::onion_message::offers::{OffersMessage, OffersMessageHandler}; -use crate::rgb_utils::{ - is_channel_rgb, persist_funding_validation, RgbBackend, RgbBackendError, RgbFundingValidation, - RgbKvStoreExt, -}; +use crate::rgb_utils::{handle_funding, is_channel_rgb, RgbKvStoreExt}; use crate::routing::router::{ BlindedTail, FixedRouter, InFlightHtlcs, Path, Payee, PaymentParameters, Route, RouteParameters, RouteParametersConfig, Router, @@ -140,8 +139,8 @@ use crate::util::logger::{Level, Logger, WithContext}; use crate::util::persist::KVStoreSync; use crate::util::scid_utils::fake_scid; use crate::util::ser::{ - BigSize, FixedLengthReader, LengthReadable, MaybeReadable, Readable, ReadableArgs, - RequiredWrapper, VecWriter, WithoutLength, Writeable, Writer, + BigSize, FixedLengthReader, LengthReadable, MaybeReadable, Readable, ReadableArgs, VecWriter, + WithoutLength, Writeable, Writer, }; use crate::util::wakers::{Future, Notifier}; @@ -1770,64 +1769,6 @@ struct PendingInboundPayment { min_value_msat: Option, } -#[derive(Clone)] -struct PendingRgbFundingValidation { - counterparty_node_id: PublicKey, - funding_created: msgs::FundingCreated, - consignment_endpoint: RgbTransport, - push_asset_amount: Option, - open_channel_msg: msgs::OpenChannel, - user_channel_id: u128, - their_features: InitFeatures, - is_0conf: bool, - is_manual_broadcast: bool, - is_trusted_no_broadcast: bool, - validated_funding: Option>, - /// P2WSH output script of the funding output, used to derive the P2V recipient_id for the RGB - /// proxy lookup. Stored as `Option` for backward compatibility with persisted states written - /// before this field was added. - funding_output_script: Option, -} - -impl_writeable_tlv_based!(PendingRgbFundingValidation, { - (0, counterparty_node_id, required), - (2, funding_created, required), - (4, consignment_endpoint, required), - (6, push_asset_amount, option), - (8, open_channel_msg, required), - (10, user_channel_id, required), - (12, their_features, required), - (14, is_0conf, required), - (16, is_manual_broadcast, required), - (18, is_trusted_no_broadcast, required), - (20, validated_funding, required), - (22, funding_output_script, option), -}); - -struct ActiveRgbFundingValidationGuard<'a> { - active: &'a Mutex>, - temporary_channel_id: ChannelId, -} - -impl<'a> ActiveRgbFundingValidationGuard<'a> { - fn acquire( - active: &'a Mutex>, temporary_channel_id: ChannelId, - ) -> Result { - if !active.lock().unwrap().insert(temporary_channel_id) { - return Err(APIError::APIMisuseError { - err: format!("RGB funding validation already active for {temporary_channel_id}"), - }); - } - Ok(Self { active, temporary_channel_id }) - } -} - -impl Drop for ActiveRgbFundingValidationGuard<'_> { - fn drop(&mut self) { - self.active.lock().unwrap().remove(&self.temporary_channel_id); - } -} - /// [`SimpleArcChannelManager`] is useful when you need a [`ChannelManager`] with a static lifetime, e.g. /// when you're using `lightning-net-tokio` (since `tokio::spawn` requires parameters with static /// lifetimes). Other times you can afford a reference, which is more efficient, in which case @@ -2079,7 +2020,7 @@ where /// # best_block: lightning::chain::BestBlock, /// # current_timestamp: u32, /// # mut reader: R, -/// # rgb_backend: std::sync::Arc, +/// # ldk_data_dir: std::path::PathBuf, /// # rgb_kv_store: std::sync::Arc, /// # ) -> Result<(), lightning::ln::msgs::DecodeError> { /// // Fresh start with no channels @@ -2091,7 +2032,7 @@ where /// let channel_manager = ChannelManager::new( /// fee_estimator, chain_monitor, tx_broadcaster, router, message_router, logger, /// entropy_source, node_signer, signer_provider, config.clone(), params, current_timestamp, -/// rgb_backend.clone(), rgb_kv_store.clone(), +/// ldk_data_dir, rgb_kv_store, /// ); /// /// // Restart from deserialized data @@ -2099,7 +2040,7 @@ where /// let args = ChannelManagerReadArgs::new( /// entropy_source, node_signer, signer_provider, fee_estimator, chain_monitor, tx_broadcaster, /// router, message_router, logger, config, channel_monitors.iter().collect(), -/// rgb_backend, rgb_kv_store, +/// ldk_data_dir, rgb_kv_store, /// ); /// let (block_hash, channel_manager) = /// <(BlockHash, ChannelManager<_, _, _, _, _, _, _, _, _>)>::read(&mut reader, args)?; @@ -3037,15 +2978,10 @@ pub struct ChannelManager< logger: L, - rgb_backend: Arc, + ldk_data_dir: PathBuf, /// KVStore for RGB data persistence rgb_kv_store: Arc, - - /// Incoming RGB funding messages waiting for asynchronous backend validation. - pending_rgb_funding_validations: Mutex>, - /// Guards against concurrent processing of the same pending RGB funding validation. - active_rgb_funding_validations: Mutex>, } /// Chain-related parameters used to construct a new `ChannelManager`. @@ -4069,7 +4005,7 @@ where pub fn new( fee_est: F, chain_monitor: M, tx_broadcaster: T, router: R, message_router: MR, logger: L, entropy_source: ES, node_signer: NS, signer_provider: SP, config: UserConfig, - params: ChainParameters, current_timestamp: u32, rgb_backend: Arc, + params: ChainParameters, current_timestamp: u32, ldk_data_dir: PathBuf, rgb_kv_store: Arc, ) -> Self where @@ -4142,10 +4078,8 @@ where #[cfg(feature = "_test_utils")] testing_dnssec_proof_offer_resolution_override: Mutex::new(new_hash_map()), - rgb_backend, + ldk_data_dir, rgb_kv_store, - pending_rgb_funding_validations: Mutex::new(new_hash_map()), - active_rgb_funding_validations: Mutex::new(new_hash_set()), } } @@ -4161,242 +4095,6 @@ where *self.config.write().unwrap() = new_config; } - /// Returns the production RGB backend injected into this channel manager. - pub fn rgb_backend(&self) -> &Arc { - &self.rgb_backend - } - - /// Completes an asynchronous incoming RGB funding validation. - /// - /// A successful result is persisted before `funding_created` processing continues and before - /// any `funding_signed` message can be queued. - fn complete_rgb_funding_validation( - &self, temporary_channel_id: ChannelId, - validation_result: Result, - ) -> Result<(), APIError> { - let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); - let pending = self - .pending_rgb_funding_validations - .lock() - .unwrap() - .get(&temporary_channel_id) - .cloned() - .ok_or_else(|| APIError::APIMisuseError { - err: format!("No pending RGB funding validation for {temporary_channel_id}"), - })?; - let validation = match validation_result { - Ok(validation) => validation, - Err(error) => { - if matches!( - error, - RgbBackendError::InvalidConsignment - | RgbBackendError::UnknownSchema(_) - | RgbBackendError::UnsupportedSchema(_) - ) { - let reason = format!("RGB funding validation rejected: {error:?}"); - self.reject_pending_rgb_funding_validation_internal( - temporary_channel_id, - &pending.counterparty_node_id, - reason.clone(), - )?; - return Err(APIError::ChannelUnavailable { err: reason }); - } - return Err(APIError::ChannelUnavailable { - err: format!("RGB funding validation failed and remains pending: {error:?}"), - }); - }, - }; - if let Err(error) = persist_funding_validation( - &temporary_channel_id, - &pending.funding_created.funding_txid.to_string(), - validation, - pending.push_asset_amount, - self.rgb_kv_store.as_ref(), - ) { - if matches!(error, ChannelError::Close(_)) { - let reason = format!("RGB funding validation rejected: {error:?}"); - self.reject_pending_rgb_funding_validation_internal( - temporary_channel_id, - &pending.counterparty_node_id, - reason.clone(), - )?; - return Err(APIError::ChannelUnavailable { err: reason }); - } - return Err(APIError::ChannelUnavailable { - err: format!( - "Failed to persist RGB funding validation; validation remains pending: {error:?}" - ), - }); - } - if self - .internal_funding_created_with_rgb_validation( - &pending.counterparty_node_id, - &pending.funding_created, - true, - ) - .is_err() - { - self.pending_rgb_funding_validations.lock().unwrap().remove(&temporary_channel_id); - return Err(APIError::ChannelUnavailable { - err: "Failed to finish processing RGB funding validation; pending validation was removed" - .to_owned(), - }); - } - self.pending_rgb_funding_validations.lock().unwrap().remove(&temporary_channel_id); - Ok(()) - } - - fn cache_rgb_funding_validation( - &self, temporary_channel_id: ChannelId, validation: &RgbFundingValidation, - ) -> Result<(), APIError> { - let validation = - bincode::serialize(validation).map_err(|error| APIError::ChannelUnavailable { - err: format!("Failed to serialize RGB funding validation: {error}"), - })?; - let mut pending = self.pending_rgb_funding_validations.lock().unwrap(); - let pending = - pending.get_mut(&temporary_channel_id).ok_or_else(|| APIError::APIMisuseError { - err: format!("No pending RGB funding validation for {temporary_channel_id}"), - })?; - pending.validated_funding = Some(validation); - Ok(()) - } - - fn reject_pending_rgb_funding_validation_internal( - &self, temporary_channel_id: ChannelId, counterparty_node_id: &PublicKey, reason: String, - ) -> Result<(), APIError> { - self.force_close_sending_error(&temporary_channel_id, counterparty_node_id, reason)?; - self.pending_rgb_funding_validations.lock().unwrap().remove(&temporary_channel_id); - Ok(()) - } - - /// Runs the injected production RGB backend for a pending funding validation and completes it. - /// - /// Network, missing-consignment, persistence, and unexpected backend failures leave the - /// validation pending so callers can retry. Permanently invalid or unsupported consignments - /// reject and close the unfunded channel. - pub async fn process_pending_rgb_funding_validation( - &self, temporary_channel_id: ChannelId, - ) -> Result<(), APIError> { - let _active_validation = ActiveRgbFundingValidationGuard::acquire( - &self.active_rgb_funding_validations, - temporary_channel_id, - )?; - let pending = self - .pending_rgb_funding_validations - .lock() - .unwrap() - .get(&temporary_channel_id) - .cloned(); - let result = match pending { - Some(pending) => { - let validation = match pending.validated_funding { - Some(validation) => Ok(bincode::deserialize(&validation).map_err(|error| { - APIError::ChannelUnavailable { - err: format!( - "Failed to restore cached RGB funding validation: {error}" - ), - } - })?), - None => { - let validation = self - .rgb_backend - .accept_funding_transfer( - pending.funding_created.funding_txid.to_string(), - pending.funding_created.funding_output_index.into(), - pending.consignment_endpoint, - pending.funding_output_script, - ) - .await; - if let Ok(ref validation) = validation { - self.cache_rgb_funding_validation(temporary_channel_id, validation)?; - } - validation - }, - }; - self.complete_rgb_funding_validation(temporary_channel_id, validation) - }, - None => Err(APIError::APIMisuseError { - err: format!("No pending RGB funding validation for {temporary_channel_id}"), - }), - }; - result - } - - /// Rejects a pending incoming RGB funding validation and closes the unfunded channel. - /// - /// This is the terminal transition for an application-level rejection. It fails if validation - /// is currently active or the temporary channel has no pending RGB funding validation. - pub fn reject_pending_rgb_funding_validation( - &self, temporary_channel_id: ChannelId, reason: String, - ) -> Result<(), APIError> { - let _active_validation = ActiveRgbFundingValidationGuard::acquire( - &self.active_rgb_funding_validations, - temporary_channel_id, - )?; - let pending = self - .pending_rgb_funding_validations - .lock() - .unwrap() - .get(&temporary_channel_id) - .cloned() - .ok_or_else(|| APIError::APIMisuseError { - err: format!("No pending RGB funding validation for {temporary_channel_id}"), - })?; - self.reject_pending_rgb_funding_validation_internal( - temporary_channel_id, - &pending.counterparty_node_id, - reason, - ) - } - - /// Durably consumes all prepared RGB transaction fascia and retries signer-blocked channel - /// operations. - /// - /// This is the asynchronous completion boundary for commitment, HTLC, and cooperative-close - /// RGB transaction construction. No built-in signer signature is released while its - /// transaction has a pending fascia record. Processing may reveal dependent HTLC transactions, - /// so this method repeats until signer retries no longer create pending RGB operations. - pub async fn process_pending_rgb_transactions(&self) -> Result<(), APIError> { - let mut iterations = 0u8; - loop { - self.rgb_backend - .process_pending_transactions(Arc::clone(&self.rgb_kv_store)) - .await - .map_err(|error| APIError::ChannelUnavailable { - err: format!("Failed to persist pending RGB transactions: {error:?}"), - })?; - self.signer_unblocked(None); - if !self.rgb_backend.has_pending_transactions(self.rgb_kv_store.as_ref()).map_err( - |error| APIError::ChannelUnavailable { - err: format!("Failed to inspect pending RGB transactions: {error:?}"), - }, - )? { - return Ok(()); - } - iterations = iterations.saturating_add(1); - if iterations >= 32 { - return Err(APIError::ChannelUnavailable { - err: "RGB transaction persistence did not converge after 32 signer retries" - .to_owned(), - }); - } - } - } - - fn queue_rgb_transaction_persistence_event(&self) { - if !self.rgb_backend.has_pending_transactions(self.rgb_kv_store.as_ref()).unwrap_or(false) { - return; - } - let mut events = self.pending_events.lock().unwrap(); - if !events - .iter() - .any(|(event, _)| matches!(event, events::Event::RgbTransactionPersistenceRequired)) - { - events.push_back((events::Event::RgbTransactionPersistenceRequired, None)); - } - } - #[cfg(test)] pub fn create_and_insert_outbound_scid_alias_for_test(&self) -> u64 { self.create_and_insert_outbound_scid_alias() @@ -4500,7 +4198,7 @@ where }; match OutboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, their_network_key, their_features, channel_value_satoshis, push_msat, user_channel_id, config, - self.best_block.read().unwrap().height, outbound_scid_alias, temporary_channel_id, &*self.logger, consignment_endpoint, Arc::clone(&self.rgb_backend), push_asset_amount, + self.best_block.read().unwrap().height, outbound_scid_alias, temporary_channel_id, &*self.logger, consignment_endpoint, self.ldk_data_dir.clone(), push_asset_amount, Arc::clone(&self.rgb_kv_store)) { Ok(res) => res, @@ -8837,9 +8535,6 @@ where true }, None => { - if self.pending_rgb_funding_validations.lock().unwrap().contains_key(chan_id) { - return true; - } chan.context_mut().maybe_expire_prev_config(); let unfunded_context = chan.unfunded_context_mut().expect("channel should be unfunded"); if unfunded_context.should_expire_unfunded_channel() { @@ -8970,8 +8665,8 @@ where } #[cfg(feature = "std")] - let duration_since_epoch = web_time::SystemTime::now() - .duration_since(web_time::SystemTime::UNIX_EPOCH) + let duration_since_epoch = std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); #[cfg(not(feature = "std"))] let duration_since_epoch = Duration::from_secs( @@ -10542,7 +10237,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ &self.fee_estimator, &self.entropy_source, &self.signer_provider, *counterparty_node_id, &self.channel_type_features(), &peer_state.latest_features, &open_channel_msg, user_channel_id, &config, best_block_height, &self.logger, accept_0conf, - channel_funding_type == ChannelFundingType::Virtual, Arc::clone(&self.rgb_backend), + channel_funding_type == ChannelFundingType::Virtual, self.ldk_data_dir.clone(), Arc::clone(&self.rgb_kv_store), ).map_err(|err| MsgHandleErrInternal::from_chan_no_close(err, *temporary_channel_id) ).map(|mut channel| { @@ -10565,7 +10260,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ &open_channel_msg, user_channel_id, &config, best_block_height, &self.logger, - Arc::clone(&self.rgb_backend), + self.ldk_data_dir.clone(), Arc::clone(&self.rgb_kv_store), ).map_err(|e| { let channel_id = open_channel_msg.common_fields.temporary_channel_id; @@ -10837,7 +10532,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ let mut channel = InboundV1Channel::new( &self.fee_estimator, &self.entropy_source, &self.signer_provider, *counterparty_node_id, &self.channel_type_features(), &peer_state.latest_features, msg, user_channel_id, - &self.config.read().unwrap(), best_block_height, &self.logger, /*is_0conf=*/false, /*is_virtual=*/false, Arc::clone(&self.rgb_backend), + &self.config.read().unwrap(), best_block_height, &self.logger, /*is_0conf=*/false, false, self.ldk_data_dir.clone(), Arc::clone(&self.rgb_kv_store), ).map_err(|e| MsgHandleErrInternal::from_chan_no_close(e, msg.common_fields.temporary_channel_id))?; let logger = WithChannelContext::from(&self.logger, &channel.context, None); @@ -10855,7 +10550,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ self.get_our_node_id(), *counterparty_node_id, &self.channel_type_features(), &peer_state.latest_features, msg, user_channel_id, &self.config.read().unwrap(), best_block_height, &self.logger, - Arc::clone(&self.rgb_backend), + self.ldk_data_dir.clone(), Arc::clone(&self.rgb_kv_store), ).map_err(|e| MsgHandleErrInternal::from_chan_no_close(e, msg.common_fields.temporary_channel_id))?; let message_send_event = MessageSendEvent::SendAcceptChannelV2 { @@ -10923,13 +10618,6 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ #[rustfmt::skip] fn internal_funding_created(&self, counterparty_node_id: &PublicKey, msg: &msgs::FundingCreated) -> Result<(), MsgHandleErrInternal> { - self.internal_funding_created_with_rgb_validation(counterparty_node_id, msg, false) - } - - #[rustfmt::skip] - fn internal_funding_created_with_rgb_validation( - &self, counterparty_node_id: &PublicKey, msg: &msgs::FundingCreated, rgb_validated: bool, - ) -> Result<(), MsgHandleErrInternal> { let best_block = *self.best_block.read().unwrap(); let per_peer_state = self.per_peer_state.read().unwrap(); @@ -10941,64 +10629,24 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; - if !rgb_validated { - if let Some(inbound_chan) = peer_state - .channel_by_id - .get(&msg.temporary_channel_id) - .and_then(Channel::as_unfunded_inbound_v1) - { - if let Some(consignment_endpoint) = - inbound_chan.funding.consignment_endpoint.clone() - { - let funding_output_script = - Some(inbound_chan.funding.get_funding_redeemscript().to_p2wsh()); - let pending = PendingRgbFundingValidation { - counterparty_node_id: *counterparty_node_id, - funding_created: msg.clone(), - consignment_endpoint: consignment_endpoint.clone(), - push_asset_amount: inbound_chan.funding.push_asset_amount, - open_channel_msg: inbound_chan.open_channel_msg.clone(), - user_channel_id: inbound_chan.context.get_user_id(), - their_features: peer_state.latest_features.clone(), - is_0conf: inbound_chan.is_0conf, - is_manual_broadcast: inbound_chan.context.is_manual_broadcast(), - is_trusted_no_broadcast: inbound_chan.context.is_trusted_no_broadcast(), - validated_funding: None, - funding_output_script, - }; - let mut pending_validations = self.pending_rgb_funding_validations.lock().unwrap(); - if let Some(existing) = pending_validations.get(&msg.temporary_channel_id) { - if existing.counterparty_node_id != *counterparty_node_id - || existing.funding_created != *msg - { - return Err(MsgHandleErrInternal::send_err_msg_no_close( - "Conflicting funding_created received while RGB funding validation is pending".to_owned(), - msg.temporary_channel_id, - )); - } - return Ok(()); - } - pending_validations.insert(msg.temporary_channel_id, pending); - self.pending_events.lock().unwrap().push_back(( - events::Event::RgbFundingValidationRequired { - temporary_channel_id: msg.temporary_channel_id, - counterparty_node_id: *counterparty_node_id, - funding_txid: msg.funding_txid, - funding_output_index: msg.funding_output_index, - consignment_endpoint, - }, - None, - )); - return Ok(()); - } - } - } let (mut chan, funding_msg_opt, monitor) = match peer_state.channel_by_id.remove(&msg.temporary_channel_id) .map(Channel::into_unfunded_inbound_v1) { Some(Ok(inbound_chan)) => { let logger = WithChannelContext::from(&self.logger, &inbound_chan.context, None); + if let Some(consignment_endpoint) = &inbound_chan.funding.consignment_endpoint { + match handle_funding(&msg.temporary_channel_id, msg.funding_txid.to_string(), &self.ldk_data_dir, consignment_endpoint.clone(), inbound_chan.funding.push_asset_amount, self.rgb_kv_store.as_ref()) { + Ok(()) => (), + Err(e) => { + // at this point the channel initiator already transitioned its channel to the funded channel ID + let mut chan = Channel::from(inbound_chan); + let funding_txo = OutPoint { txid: msg.funding_txid, index: msg.funding_output_index }; + chan.context_mut().channel_id = ChannelId::v1_from_funding_outpoint(funding_txo); + return Err(convert_channel_err!(self, peer_state, e, &mut chan).1); + }, + } + } match inbound_chan.funding_created(msg, best_block, &self.signer_provider, &&logger) { Ok(res) => res, Err((inbound_chan, err)) => { @@ -13277,7 +12925,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ #[cfg(feature = "std")] let duration_since_epoch = { - use web_time::SystemTime; + use std::time::SystemTime; SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should be after SystemTime::UNIX_EPOCH") }; @@ -13350,14 +12998,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ invoice = invoice.rgb_amount(amt); } - let mut channels = self.list_channels(); - // For RGB invoices, only advertise channels that can receive the asset over their inbound - // side. Vanilla (BTC-only) channels report `inbound_htlc_maximum_rgb == 0`; including them - // as route hints would make the payer attempt to route RGB over a channel with no RGB, - // failing with "Cannot send more than our next-HTLC RGB maximum - 0". - if contract_id.is_some() { - channels.retain(|channel| channel.inbound_htlc_maximum_rgb > 0); - } + let channels = self.list_channels(); let route_hints = super::invoice_utils::sort_and_filter_channels(channels, amount_msats, &self.logger); for hint in route_hints { invoice = invoice.private_route(hint); @@ -14115,8 +13756,8 @@ where #[cfg(not(feature = "std"))] let now = Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64); #[cfg(feature = "std")] - let now = web_time::SystemTime::now() - .duration_since(web_time::SystemTime::UNIX_EPOCH) + let now = std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); now @@ -14455,7 +14096,6 @@ where >( &self, handler: H, ) { - self.queue_rgb_transaction_persistence_event(); let mut ev; process_events_body!(self, ev, { handler(ev).await }); } @@ -14507,10 +14147,7 @@ where let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; let pending_msg_events = &mut peer_state.pending_msg_events; - peer_state.channel_by_id.retain(|channel_id, chan| { - if self.pending_rgb_funding_validations.lock().unwrap().contains_key(channel_id) { - return true; - } + peer_state.channel_by_id.retain(|_, chan| { let logger = WithChannelContext::from(&self.logger, &chan.context(), None); let DisconnectResult { is_resumable, splice_funding_failed } = chan.peer_disconnected_is_resumable(&&logger); @@ -14835,7 +14472,6 @@ where where H::Target: EventHandler, { - self.queue_rgb_transaction_persistence_event(); let mut ev; process_events_body!(self, ev, handler.handle_event(ev)); } @@ -17170,7 +16806,6 @@ where if our_pending_intercepts.len() != 0 { pending_intercepted_htlcs = Some(our_pending_intercepts); } - let pending_rgb_funding_validations = self.pending_rgb_funding_validations.lock().unwrap(); let mut pending_claiming_payments = Some(&claimable_payments.pending_claiming_payments); if pending_claiming_payments.as_ref().unwrap().is_empty() { @@ -17210,7 +16845,6 @@ where (17, in_flight_monitor_updates, option), (19, peer_storage_dir, optional_vec), (21, WithoutLength(&self.flow.writeable_async_receive_offer_cache()), required), - (23, *pending_rgb_funding_validations, required), }); // Remove the SpliceFailed events added earlier. @@ -17384,7 +17018,7 @@ pub struct ChannelManagerReadArgs< HashMap::EcdsaSigner>>, /// LDK data directory - pub rgb_backend: Arc, + pub ldk_data_dir: PathBuf, /// KVStore for RGB data persistence pub rgb_kv_store: Arc, @@ -17421,7 +17055,7 @@ where chain_monitor: M, tx_broadcaster: T, router: R, message_router: MR, logger: L, config: UserConfig, mut channel_monitors: Vec<&'a ChannelMonitor<::EcdsaSigner>>, - rgb_backend: Arc, rgb_kv_store: Arc, + ldk_data_dir: PathBuf, rgb_kv_store: Arc, ) -> Self { Self { entropy_source, @@ -17437,7 +17071,7 @@ where channel_monitors: hash_map_from_iter( channel_monitors.drain(..).map(|monitor| (monitor.channel_id(), monitor)), ), - rgb_backend, + ldk_data_dir, rgb_kv_store, } } @@ -17541,7 +17175,7 @@ where &args.entropy_source, &args.signer_provider, &provided_channel_type_features(&args.config), - Arc::clone(&args.rgb_backend), + args.ldk_data_dir.clone(), Arc::clone(&args.rgb_kv_store), ), )?; @@ -17922,7 +17556,6 @@ where let mut inbound_payment_id_secret = None; let mut peer_storage_dir: Option)>> = None; let mut async_receive_offer_cache: AsyncReceiveOfferCache = AsyncReceiveOfferCache::new(); - let mut pending_rgb_funding_validations = RequiredWrapper(None); read_tlv_fields!(reader, { (1, pending_outbound_payments_no_retry, option), (2, pending_intercepted_htlcs, option), @@ -17941,7 +17574,6 @@ where (17, in_flight_monitor_updates, option), (19, peer_storage_dir, optional_vec), (21, async_receive_offer_cache, (default_value, async_receive_offer_cache)), - (23, pending_rgb_funding_validations, required), }); let mut decode_update_add_htlcs = decode_update_add_htlcs.unwrap_or_else(|| new_hash_map()); let peer_storage_dir: Vec<(PublicKey, Vec)> = peer_storage_dir.unwrap_or_else(Vec::new); @@ -18912,84 +18544,10 @@ where #[cfg(feature = "_test_utils")] testing_dnssec_proof_offer_resolution_override: Mutex::new(new_hash_map()), - rgb_backend: args.rgb_backend, + ldk_data_dir: args.ldk_data_dir, rgb_kv_store: args.rgb_kv_store, - pending_rgb_funding_validations: Mutex::new(pending_rgb_funding_validations.0.unwrap()), - active_rgb_funding_validations: Mutex::new(new_hash_set()), }; - let pending_rgb_recovery_records: Vec = channel_manager - .pending_rgb_funding_validations - .lock() - .unwrap() - .values() - .cloned() - .collect(); - for pending in pending_rgb_recovery_records { - // Phantom-channel guard: if this funding already completed before the crash, its - // channel monitor is persisted under the funded channel id. Reconstructing an - // unfunded inbound channel for the same outpoint would create a duplicate, diverged - // channel, so drop the now-moot pending validation record and skip reconstruction. - let funded_channel_id = ChannelId::v1_from_funding_outpoint(OutPoint { - txid: pending.funding_created.funding_txid, - index: pending.funding_created.funding_output_index, - }); - if args.channel_monitors.contains_key(&funded_channel_id) { - channel_manager - .pending_rgb_funding_validations - .lock() - .unwrap() - .remove(&pending.funding_created.temporary_channel_id); - continue; - } - let mut channel = InboundV1Channel::new( - &channel_manager.fee_estimator, - &channel_manager.entropy_source, - &channel_manager.signer_provider, - pending.counterparty_node_id, - &channel_manager.channel_type_features(), - &pending.their_features, - &pending.open_channel_msg, - pending.user_channel_id, - &channel_manager.config.read().unwrap(), - channel_manager.best_block.read().unwrap().height, - &channel_manager.logger, - pending.is_0conf, - /*is_virtual=*/ false, - Arc::clone(&channel_manager.rgb_backend), - Arc::clone(&channel_manager.rgb_kv_store), - ) - .map_err(|_| DecodeError::InvalidValue)?; - if pending.is_manual_broadcast { - channel.context.set_manual_broadcast(); - } - if pending.is_trusted_no_broadcast { - channel.context.set_trusted_no_broadcast(); - } - let logger = WithChannelContext::from(&channel_manager.logger, &channel.context, None); - channel.accept_inbound_channel(&&logger); - channel_manager - .per_peer_state - .write() - .unwrap() - .entry(pending.counterparty_node_id) - .or_insert_with(|| Mutex::new(empty_peer_state())) - .lock() - .unwrap() - .channel_by_id - .insert(pending.funding_created.temporary_channel_id, Channel::from(channel)); - channel_manager.pending_events.lock().unwrap().push_back(( - events::Event::RgbFundingValidationRequired { - temporary_channel_id: pending.funding_created.temporary_channel_id, - counterparty_node_id: pending.counterparty_node_id, - funding_txid: pending.funding_created.funding_txid, - funding_output_index: pending.funding_created.funding_output_index, - consignment_endpoint: pending.consignment_endpoint.clone(), - }, - None, - )); - } - let mut processed_claims: HashSet> = new_hash_set(); for (_, monitor) in args.channel_monitors.iter() { for (payment_hash, (payment_preimage, payment_claims)) in monitor.get_stored_preimages() diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index b3a074dc7..d5f51288f 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -31,7 +31,7 @@ use bitcoin::secp256k1::ecdsa::Signature; use bitcoin::secp256k1::PublicKey; use bitcoin::{secp256k1, Transaction, Witness}; -use crate::rgb_utils::{ContractId, RgbTransport}; +use rgb_lib::{ContractId, RgbTransport}; use crate::blinded_path::message::BlindedMessagePath; use crate::blinded_path::payment::{ @@ -2302,7 +2302,7 @@ mod fuzzy_internal_msgs { use crate::types::payment::{PaymentPreimage, PaymentSecret}; use bitcoin::secp256k1::PublicKey; - use crate::rgb_utils::ContractId; + use rgb_lib::ContractId; #[allow(unused_imports)] use crate::prelude::*; diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index b1d501bab..c2eea7b28 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -8,7 +8,7 @@ use bitcoin::hashes::Hash; use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::{self, PublicKey, Secp256k1}; -use crate::rgb_utils::ContractId; +use rgb_lib::ContractId; use crate::blinded_path; use crate::blinded_path::payment::{PaymentConstraints, PaymentRelay}; diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 565a89a76..e50ecfeb9 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -38,7 +38,7 @@ use bitcoin::secp256k1; use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey}; -use crate::rgb_utils::ContractId; +use rgb_lib::ContractId; use crate::io::{Cursor, Read}; use core::ops::Deref; diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index fddae0909..db8fcf78a 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -451,7 +451,7 @@ impl Retry { #[rustfmt::skip] pub(super) fn has_expired(route_params: &RouteParameters) -> bool { if let Some(expiry_time) = route_params.payment_params.expiry_time { - if let Ok(elapsed) = web_time::SystemTime::UNIX_EPOCH.elapsed() { + if let Ok(elapsed) = std::time::SystemTime::UNIX_EPOCH.elapsed() { return elapsed > core::time::Duration::from_secs(expiry_time) } } @@ -2982,7 +2982,7 @@ mod tests { let secp_ctx = Secp256k1::new(); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); - let past_expiry_time = web_time::SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() - 2; + let past_expiry_time = std::time::SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() - 2; let payment_params = PaymentParameters::from_node_id( PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()), 0 diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs index d43f0006c..c68680b7c 100644 --- a/lightning/src/ln/peer_handler.rs +++ b/lightning/src/ln/peer_handler.rs @@ -2356,8 +2356,9 @@ where { // Forward ad-hoc gossip if the timestamp range is less than six hours ago. // Otherwise, do a full sync. - let full_sync_threshold = web_time::SystemTime::now() - .duration_since(web_time::UNIX_EPOCH) + use std::time::{SystemTime, UNIX_EPOCH}; + let full_sync_threshold = SystemTime::now() + .duration_since(UNIX_EPOCH) .expect("Time must be > 1970") .as_secs() - 6 * 3600; if (_msg.first_timestamp as u64) > full_sync_threshold { @@ -4020,7 +4021,7 @@ mod tests { // Until we have std::thread::scoped we have to unsafe { turn off the borrow checker }. let peers = Arc::new(create_network(2, unsafe { &*(&*cfgs as *const _) as &'static _ })); - let start_time = web_time::Instant::now(); + let start_time = std::time::Instant::now(); macro_rules! spawn_thread { ($id: expr) => {{ let peers = Arc::clone(&peers); diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 70236ba62..2b115e7d7 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -192,8 +192,8 @@ where #[cfg(not(feature = "std"))] let now = Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64); #[cfg(feature = "std")] - let now = web_time::SystemTime::now() - .duration_since(web_time::SystemTime::UNIX_EPOCH) + let now = std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); now } @@ -931,8 +931,8 @@ where .map_err(|_| Bolt12SemanticError::MissingPaths)?; #[cfg(feature = "std")] - let created_at = web_time::SystemTime::now() - .duration_since(web_time::SystemTime::UNIX_EPOCH) + let created_at = std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); #[cfg(not(feature = "std"))] let created_at = Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64); diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index 488584f2f..39e3ad948 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -165,7 +165,7 @@ use core::time::Duration; use crate::prelude::*; #[cfg(feature = "std")] -use web_time::SystemTime; +use std::time::SystemTime; pub(crate) const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(7200); diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 5b9fd7dd3..2500c98da 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -738,7 +738,7 @@ macro_rules! invoice_request_respond_with_explicit_signing_pubkey_methods { ( $self: ident, $contents: expr, $builder: ty ) => { /// Creates an [`InvoiceBuilder`] for the request with the given required fields and using the - /// [`Duration`] since [`web_time::SystemTime::UNIX_EPOCH`] as the creation time. + /// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time. /// /// See [`InvoiceRequest::respond_with_no_std`] for further details where the aforementioned /// creation time is used for the `created_at` parameter. @@ -748,8 +748,8 @@ macro_rules! invoice_request_respond_with_explicit_signing_pubkey_methods { ( pub fn respond_with( &$self, payment_paths: Vec, payment_hash: PaymentHash ) -> Result<$builder, Bolt12SemanticError> { - let created_at = web_time::SystemTime::now() - .duration_since(web_time::SystemTime::UNIX_EPOCH) + let created_at = std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); $contents.respond_with_no_std(payment_paths, payment_hash, created_at) @@ -1026,8 +1026,8 @@ macro_rules! invoice_request_respond_with_derived_signing_pubkey_methods { ( pub fn respond_using_derived_keys( &$self, payment_paths: Vec, payment_hash: PaymentHash ) -> Result<$builder, Bolt12SemanticError> { - let created_at = web_time::SystemTime::now() - .duration_since(web_time::SystemTime::UNIX_EPOCH) + let created_at = std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); $self.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at) diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index 2b2ab0036..991431c5b 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -114,7 +114,7 @@ use crate::prelude::*; use bitcoin::hex::impl_fmt_traits; #[cfg(feature = "std")] -use web_time::SystemTime; +use std::time::SystemTime; pub(super) const IV_BYTES_WITH_METADATA: &[u8; IV_LEN] = b"LDK Offer ~~~~~~"; pub(super) const IV_BYTES_WITHOUT_METADATA: &[u8; IV_LEN] = b"LDK Offer v2~~~~"; diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index 6a0280edc..e965daf43 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -125,7 +125,7 @@ use crate::offers::invoice::{ use crate::prelude::*; #[cfg(feature = "std")] -use web_time::SystemTime; +use std::time::SystemTime; pub(super) const IV_BYTES_WITH_METADATA: &[u8; IV_LEN] = b"LDK Refund ~~~~~"; pub(super) const IV_BYTES_WITHOUT_METADATA: &[u8; IV_LEN] = b"LDK Refund v2~~~"; @@ -593,7 +593,7 @@ impl Refund { macro_rules! respond_with_explicit_signing_pubkey_methods { ($self: ident, $builder: ty) => { /// Creates an [`InvoiceBuilder`] for the refund with the given required fields and using the - /// [`Duration`] since [`web_time::SystemTime::UNIX_EPOCH`] as the creation time. + /// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time. /// /// See [`Refund::respond_with_no_std`] for further details where the aforementioned creation /// time is used for the `created_at` parameter. @@ -606,8 +606,8 @@ macro_rules! respond_with_explicit_signing_pubkey_methods { ($self: ident, $buil &$self, payment_paths: Vec, payment_hash: PaymentHash, signing_pubkey: PublicKey, ) -> Result<$builder, Bolt12SemanticError> { - let created_at = web_time::SystemTime::now() - .duration_since(web_time::SystemTime::UNIX_EPOCH) + let created_at = std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); $self.respond_with_no_std(payment_paths, payment_hash, signing_pubkey, created_at) @@ -664,8 +664,8 @@ macro_rules! respond_with_derived_signing_pubkey_methods { ($self: ident, $build where ES::Target: EntropySource, { - let created_at = web_time::SystemTime::now() - .duration_since(web_time::SystemTime::UNIX_EPOCH) + let created_at = std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); $self.respond_using_derived_keys_no_std( diff --git a/lightning/src/onion_message/dns_resolution.rs b/lightning/src/onion_message/dns_resolution.rs index 4216534d1..6cb664665 100644 --- a/lightning/src/onion_message/dns_resolution.rs +++ b/lightning/src/onion_message/dns_resolution.rs @@ -487,7 +487,7 @@ impl OMNameResolver { let mut time = self.latest_block_time.load(Ordering::Acquire) as u64; #[cfg(feature = "std")] { - use web_time::{SystemTime, UNIX_EPOCH}; + use std::time::{SystemTime, UNIX_EPOCH}; let now = SystemTime::now().duration_since(UNIX_EPOCH); time = now.expect("Time must be > 1970").as_secs(); } diff --git a/lightning/src/rgb_utils/mod.rs b/lightning/src/rgb_utils/mod.rs index eebf60296..4f6763998 100644 --- a/lightning/src/rgb_utils/mod.rs +++ b/lightning/src/rgb_utils/mod.rs @@ -16,967 +16,45 @@ use bitcoin::blockdata::transaction::Transaction; use bitcoin::hex::DisplayHex; use bitcoin::psbt::{ExtractTxError, Psbt}; use bitcoin::secp256k1::PublicKey; -use bitcoin::{Network, ScriptBuf, TxOut}; -#[cfg(all(feature = "rgb-wasm", not(feature = "rgb-native")))] -use std::collections::HashSet; - -/// RGB contract identifier shared with BOLT11 invoices and both wallet backends. -pub use lightning_invoice::ContractId; -#[cfg(all(feature = "rgb-native", not(feature = "rgb-wasm")))] +use bitcoin::TxOut; use rgb_lib::{ bitcoin::psbt::Psbt as RgbLibPsbt, - utils::recipient_id_from_script_buf as native_recipient_id_from_script_buf, - wallet::{ - rust_only::{ - AssetColoringInfo as NativeAssetColoringInfo, ColoringInfo as NativeColoringInfo, - }, - OnlineOptions, Recipient as NativeRecipient, TransportEndpoint as NativeTransportEndpoint, - Wallet, WitnessData as NativeWitnessData, - }, - AssetSchema as NativeAssetSchema, Assignment as NativeAssignment, - BitcoinNetwork as NativeBitcoinNetwork, ConsignmentExt, Error as RgbLibError, - Fascia as NativeFascia, FileContent, RgbTxid as NativeRgbTxid, WitnessOrd, -}; -#[cfg(all(feature = "rgb-wasm", not(feature = "rgb-native")))] -use rgb_lib_wasm::{ - bitcoin::psbt::Psbt as RgbLibPsbt, - utils::recipient_id_from_script_buf as wasm_recipient_id_from_script_buf, + keys::WitnessVersion, wallet::{ - rust_only::{AssetColoringInfo as WasmAssetColoringInfo, ColoringInfo as WasmColoringInfo}, - Online as WasmOnline, Recipient as WasmRecipient, Wallet as WasmWallet, - WalletData as WasmWalletData, WitnessData as WasmWitnessData, + rust_only::{AssetColoringInfo, ColoringInfo}, + DatabaseType, OnlineOptions, SinglesigKeys, Wallet, WalletData, }, - AssetSchema as WasmAssetSchema, Assignment as WasmAssignment, - BitcoinNetwork as WasmBitcoinNetwork, ConsignmentExt, Error as WasmRgbLibError, FileContent, - WitnessOrd, + AssetSchema, Assignment, BitcoinNetwork, ConsignmentExt, ContractId, Error as RgbLibError, + FileContent, RgbTransfer, RgbTransport, WitnessOrd, }; -/// RGB transport endpoint shared by both wallet backends. -pub use rgbinvoice::RgbTransport; - use serde::{Deserialize, Serialize}; +use tokio::runtime::Handle; use crate::io; -#[cfg(all(feature = "rgb-native", not(feature = "rgb-wasm")))] -use crate::sync::Mutex; -use core::fmt; use core::ops::Deref; use std::cell::RefCell; use std::collections::HashMap; -#[cfg(all(feature = "rgb-wasm", not(feature = "rgb-native")))] -use std::rc::Rc; +use std::path::Path; use std::str::FromStr; use std::sync::Arc; -/// RGB schemas represented independently from a wallet backend. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)] -pub enum AssetSchema { - /// Non-inflatable assets. - Nia, - /// Unique digital assets. - Uda, - /// Collectible fungible assets. - Cfa, - /// Inflatable fungible assets. - Ifa, -} - -impl fmt::Display for AssetSchema { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self:?}") - } -} - -#[cfg(all(feature = "rgb-native", not(feature = "rgb-wasm")))] -impl From for AssetSchema { - fn from(value: NativeAssetSchema) -> Self { - match value { - NativeAssetSchema::Nia => Self::Nia, - NativeAssetSchema::Uda => Self::Uda, - NativeAssetSchema::Cfa => Self::Cfa, - NativeAssetSchema::Ifa => Self::Ifa, - } - } -} - -#[cfg(all(feature = "rgb-native", not(feature = "rgb-wasm")))] -impl From for NativeAssetSchema { - fn from(value: AssetSchema) -> Self { - match value { - AssetSchema::Nia => Self::Nia, - AssetSchema::Uda => Self::Uda, - AssetSchema::Cfa => Self::Cfa, - AssetSchema::Ifa => Self::Ifa, - } - } -} - -/// RGB assignment represented independently from a wallet backend. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Assignment { - /// Fungible value in RGB units. - Fungible(u64), - /// Non-fungible value. - NonFungible, - /// Inflation right. - InflationRight(u64), - /// Any assignment. - Any, -} - -#[cfg(all(feature = "rgb-native", not(feature = "rgb-wasm")))] -impl From for Assignment { - fn from(value: NativeAssignment) -> Self { - match value { - NativeAssignment::Fungible(amount) => Self::Fungible(amount), - NativeAssignment::NonFungible => Self::NonFungible, - NativeAssignment::InflationRight(amount) => Self::InflationRight(amount), - NativeAssignment::Any => Self::Any, - } - } -} - -#[cfg(all(feature = "rgb-wasm", not(feature = "rgb-native")))] -impl From for AssetSchema { - fn from(value: WasmAssetSchema) -> Self { - match value { - WasmAssetSchema::Nia => Self::Nia, - WasmAssetSchema::Ifa => Self::Ifa, - } - } -} - -#[cfg(all(feature = "rgb-wasm", not(feature = "rgb-native")))] -impl From for Assignment { - fn from(value: WasmAssignment) -> Self { - match value { - WasmAssignment::Fungible(amount) => Self::Fungible(amount), - WasmAssignment::NonFungible => Self::NonFungible, - WasmAssignment::InflationRight(amount) => Self::InflationRight(amount), - WasmAssignment::Any => Self::Any, - } - } -} - -/// RGB backend error represented independently from a wallet backend. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum RgbBackendError { - /// The funding consignment is invalid. - InvalidConsignment, - /// The funding consignment could not be found. - NoConsignment, - /// The consignment uses an unknown RGB schema. - UnknownSchema(String), - /// The selected backend does not support the consignment schema. - UnsupportedSchema(AssetSchema), - /// A required network service failed. - Network(String), - /// Durable RGB wallet persistence failed. - Persistence(String), - /// An unexpected backend failure occurred. - Unexpected(String), -} - -#[cfg(all(feature = "rgb-native", not(feature = "rgb-wasm")))] -impl From for RgbBackendError { - fn from(value: RgbLibError) -> Self { - match value { - RgbLibError::InvalidConsignment => Self::InvalidConsignment, - RgbLibError::NoConsignment => Self::NoConsignment, - RgbLibError::UnknownRgbSchema { schema_id } => Self::UnknownSchema(schema_id), - RgbLibError::UnsupportedSchema { asset_schema } => { - Self::UnsupportedSchema(asset_schema.into()) - }, - RgbLibError::InvalidColoringInfo { details } => { - Self::Unexpected(format!("Invalid coloring info: {details}")) - }, - RgbLibError::Indexer { details } - | RgbLibError::InvalidIndexer { details } - | RgbLibError::Network { details } => Self::Network(details), - error => Self::Unexpected(error.to_string()), - } - } -} - -#[cfg(all(feature = "rgb-wasm", not(feature = "rgb-native")))] -impl From for RgbBackendError { - fn from(value: WasmRgbLibError) -> Self { - match value { - WasmRgbLibError::InvalidConsignment => Self::InvalidConsignment, - WasmRgbLibError::NoConsignment => Self::NoConsignment, - WasmRgbLibError::UnknownRgbSchema { schema_id } => Self::UnknownSchema(schema_id), - WasmRgbLibError::UnsupportedSchema { asset_schema } => { - Self::UnsupportedSchema(asset_schema.into()) - }, - WasmRgbLibError::InvalidColoringInfo { details } => { - Self::Unexpected(format!("Invalid coloring info: {details}")) - }, - WasmRgbLibError::Indexer { details } - | WasmRgbLibError::InvalidIndexer { details } - | WasmRgbLibError::Network { details } - | WasmRgbLibError::Proxy { details } => Self::Network(details), - WasmRgbLibError::Persistence { details } => Self::Persistence(details), - error => Self::Unexpected(error.to_string()), - } - } -} - -/// RGB asset-specific transaction coloring data. -#[derive(Clone, Debug)] -pub struct AssetColoringInfo { - /// Map of transaction output indexes to RGB amounts. - pub output_map: HashMap, - /// Static blinding used for deterministic transaction construction. - pub static_blinding: Option, -} - -/// Backend-neutral RGB transaction coloring data. -#[derive(Clone, Debug)] -pub struct ColoringInfo { - /// Asset-specific transaction coloring data. - pub asset_info_map: HashMap, - /// Static blinding used for deterministic transaction construction. - pub static_blinding: Option, - /// Nonce used to order off-chain transactions. - pub nonce: Option, -} - -impl ColoringInfo { - #[cfg(all(feature = "rgb-native", not(feature = "rgb-wasm")))] - fn into_native(self) -> NativeColoringInfo { - NativeColoringInfo { - asset_info_map: self - .asset_info_map - .into_iter() - .map(|(contract_id, info)| { - ( - contract_id, - NativeAssetColoringInfo { - output_map: info.output_map, - static_blinding: info.static_blinding, - }, - ) - }) - .collect(), - static_blinding: self.static_blinding, - nonce: self.nonce, - } - } - - #[cfg(all(feature = "rgb-wasm", not(feature = "rgb-native")))] - fn into_wasm(self) -> WasmColoringInfo { - WasmColoringInfo { - asset_info_map: self - .asset_info_map - .into_iter() - .map(|(contract_id, info)| { - ( - contract_id, - WasmAssetColoringInfo { - output_map: info.output_map, - static_blinding: info.static_blinding, - }, - ) - }) - .collect(), - static_blinding: self.static_blinding, - nonce: self.nonce, - } - } -} - -/// Result of accepting and validating an incoming RGB funding transfer. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct RgbFundingValidation { - /// Serialized validated RGB consignment. - pub consignment: Vec, - /// Contract funded by the consignment. - pub contract_id: ContractId, - /// Asset schema funded by the consignment. - pub schema: AssetSchema, - /// RGB amount assigned to the funding output. - pub received_amount: u64, -} - -/// Parameters for creating an RGB channel funding transfer. -#[derive(Clone, Debug)] -pub struct RgbFundingTransferRequest { - /// Contract allocated to the channel funding output. - pub contract_id: ContractId, - /// Asset schema of the contract. - pub schema: AssetSchema, - /// RGB amount allocated to the channel. - pub amount: u64, - /// Lightning funding output script. - pub output_script: ScriptBuf, - /// Bitcoin amount allocated to the Lightning funding output. - pub channel_value_satoshis: u64, - /// RGB proxy transport advertised to the channel peer. - pub consignment_endpoint: RgbTransport, - /// Bitcoin network used to encode the witness recipient. - pub network: Network, - /// Funding transaction fee rate in sat/vB. - pub fee_rate: u64, - /// Minimum confirmations required for selected wallet inputs. - pub min_confirmations: u8, -} - -/// Prepared RGB funding transaction that is not yet broadcast. -#[derive(Clone, Debug)] -pub struct PreparedRgbFundingTransfer { - /// Signed PSBT retained until LDK reports the channel pending. - pub signed_psbt: String, - /// Signed funding transaction passed to `ChannelManager::funding_transaction_generated`. - pub transaction: Transaction, -} - -#[cfg(all(feature = "rgb-native", not(feature = "rgb-wasm")))] -fn native_bitcoin_network(network: Network) -> NativeBitcoinNetwork { - match network { - Network::Bitcoin => NativeBitcoinNetwork::Mainnet, - Network::Testnet => NativeBitcoinNetwork::Testnet, - Network::Testnet4 => NativeBitcoinNetwork::Testnet4, - Network::Signet => NativeBitcoinNetwork::Signet, - Network::Regtest => NativeBitcoinNetwork::Regtest, - } -} - -#[cfg(all(feature = "rgb-wasm", not(feature = "rgb-native")))] -fn wasm_bitcoin_network(network: Network) -> WasmBitcoinNetwork { - match network { - Network::Bitcoin => WasmBitcoinNetwork::Mainnet, - Network::Testnet => WasmBitcoinNetwork::Testnet, - Network::Testnet4 => WasmBitcoinNetwork::Testnet4, - Network::Signet => WasmBitcoinNetwork::Signet, - Network::Regtest => WasmBitcoinNetwork::Regtest, - } -} - -/// Long-lived native RGB wallet backend. -/// -/// The same instance must be shared by the channel manager, channel signers, and all transaction -/// construction paths for the lifetime of the node. -#[cfg(all(feature = "rgb-native", not(feature = "rgb-wasm")))] -pub struct NativeRgbBackend { - wallet: Mutex, - online_options: OnlineOptions, - transaction_processor: Mutex<()>, -} - -/// The concrete RGB backend selected by the `rgb-native` build. -#[cfg(all(feature = "rgb-native", not(feature = "rgb-wasm")))] -pub type RgbBackend = NativeRgbBackend; - -#[cfg(all(feature = "rgb-native", not(feature = "rgb-wasm")))] -impl NativeRgbBackend { - /// Creates a persistent native RGB backend from an already restored production wallet. - pub fn new(wallet: Wallet, online_options: OnlineOptions) -> Self { - Self { wallet: Mutex::new(wallet), online_options, transaction_processor: Mutex::new(()) } - } - - /// Prepares a deterministic colored transaction and durably consumes its fascia before - /// returning. - /// - /// Native transaction construction is synchronous, and child HTLC transactions may be colored - /// immediately after their parent commitment transaction. Leaving the parent fascia pending - /// until the application event loop runs makes the wallet report zero available assets while - /// coloring that child transaction. - pub fn prepare_transaction( - &self, transaction: Transaction, coloring_info: ColoringInfo, kv_store: &dyn KVStoreSync, - ) -> Result { - let _processor_guard = self.transaction_processor.lock().unwrap(); - let psbt = Psbt::from_unsigned_tx(transaction) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - let mut psbt = RgbLibPsbt::from_str(&psbt.to_string()) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - let wallet = self.wallet.lock().unwrap(); - let (fascia, _) = wallet - .color_psbt(&mut psbt, coloring_info.into_native()) - .map_err(RgbBackendError::from)?; - let psbt = Psbt::from_str(&psbt.to_string()) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - let transaction = match psbt.extract_tx() { - Ok(tx) => Ok(tx), - Err(ExtractTxError::MissingInputValue { tx }) => Ok(tx), - Err(error) => Err(RgbBackendError::Unexpected(error.to_string())), - }?; - let txid = transaction.compute_txid().to_string(); - if kv_store.read(RGB_PRIMARY_NS, RGB_CONSUMED_FASCIA_NS, &txid).is_err() { - let serialized_fascia = serde_json::to_vec(&fascia) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - kv_store - .write(RGB_PRIMARY_NS, RGB_PENDING_FASCIA_NS, &txid, serialized_fascia) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - wallet - .consume_fascia(fascia, Some(WitnessOrd::Ignored)) - .map_err(RgbBackendError::from)?; - kv_store - .write(RGB_PRIMARY_NS, RGB_CONSUMED_FASCIA_NS, &txid, Vec::new()) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - kv_store - .remove(RGB_PRIMARY_NS, RGB_PENDING_FASCIA_NS, &txid, false) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - } - Ok(transaction) - } - - /// Returns whether a colored transaction's RGB fascia is durable. - pub fn is_transaction_durable(&self, txid: &bitcoin::Txid, kv_store: &dyn KVStoreSync) -> bool { - match kv_store.read(RGB_PRIMARY_NS, RGB_PENDING_FASCIA_NS, &txid.to_string()) { - Ok(_) => false, - Err(error) if error.kind() == io::ErrorKind::NotFound => true, - Err(_) => false, - } - } - - /// Returns whether any prepared RGB transaction still requires durable fascia consumption. - pub fn has_pending_transactions( - &self, kv_store: &dyn KVStoreSync, - ) -> Result { - kv_store - .list(RGB_PRIMARY_NS, RGB_PENDING_FASCIA_NS) - .map(|keys| !keys.is_empty()) - .map_err(|error| RgbBackendError::Unexpected(error.to_string())) - } - - /// Durably consume all prepared RGB fascia before signing and message release are retried. - pub async fn process_pending_transactions( - self: &Arc, kv_store: Arc, - ) -> Result<(), RgbBackendError> { - let backend = Arc::clone(self); - tokio::task::spawn_blocking(move || { - let _processor_guard = backend.transaction_processor.lock().unwrap(); - let mut pending = kv_store - .list(RGB_PRIMARY_NS, RGB_PENDING_FASCIA_NS) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - pending.sort(); - while !pending.is_empty() { - let mut deferred = Vec::new(); - let mut last_error = None; - let mut progressed = false; - for txid in pending { - let bytes = kv_store - .read(RGB_PRIMARY_NS, RGB_PENDING_FASCIA_NS, &txid) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - let fascia = serde_json::from_slice(&bytes) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - match backend - .wallet - .lock() - .unwrap() - .consume_fascia(fascia, Some(WitnessOrd::Ignored)) - .map_err(RgbBackendError::from) - { - Ok(()) => { - progressed = true; - kv_store - .write(RGB_PRIMARY_NS, RGB_CONSUMED_FASCIA_NS, &txid, Vec::new()) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - kv_store - .remove(RGB_PRIMARY_NS, RGB_PENDING_FASCIA_NS, &txid, false) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - }, - Err(error) => { - deferred.push(txid); - last_error = Some(error); - }, - } - } - if deferred.is_empty() { - break; - } - if !progressed { - return Err(last_error.expect("deferred fascia has an error")); - } - pending = deferred; - } - Ok(()) - }) - .await - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))? - } - - /// Creates and signs an RGB funding transfer, posts its consignment, and leaves the transfer - /// unbroadcast until [`Self::complete_funding_transfer`] is called. - pub async fn prepare_funding_transfer( - self: &Arc, request: RgbFundingTransferRequest, - ) -> Result { - let backend = Arc::clone(self); - tokio::task::spawn_blocking(move || { - let mut wallet = backend.wallet.lock().unwrap(); - let online = - wallet.go_online(backend.online_options.clone()).map_err(RgbBackendError::from)?; - let assignment = match request.schema { - AssetSchema::Nia | AssetSchema::Cfa | AssetSchema::Ifa => { - NativeAssignment::Fungible(request.amount) - }, - AssetSchema::Uda => NativeAssignment::NonFungible, - }; - let funding_output_script = request.output_script.clone(); - let recipient_id = native_recipient_id_from_script_buf( - request.output_script, - native_bitcoin_network(request.network), - ); - let recipient_map = HashMap::from([( - request.contract_id.to_string(), - vec![NativeRecipient { - recipient_id: recipient_id.clone(), - witness_data: Some(NativeWitnessData { - amount_sat: request.channel_value_satoshis, - blinding: Some(STATIC_BLINDING), - }), - assignment, - transport_endpoints: vec![request.consignment_endpoint.to_string()], - }], - )]); - let begin = wallet - .send_begin( - online, - recipient_map, - true, - request.fee_rate, - request.min_confirmations, - None, - false, - Some(0), - ) - .map_err(RgbBackendError::from)?; - let fascia_bytes = std::fs::read(&begin.details.fascia_path) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - let fascia: NativeFascia = serde_json::from_slice(&fascia_bytes) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - wallet.consume_fascia(fascia, None).map_err(RgbBackendError::from)?; - wallet.create_consignments(begin.psbt.clone()).map_err(RgbBackendError::from)?; - let signed_psbt = wallet.sign_psbt(begin.psbt, None).map_err(RgbBackendError::from)?; - let psbt = Psbt::from_str(&signed_psbt) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - let transaction = psbt - .clone() - .extract_tx() - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - let txid = transaction.compute_txid().to_string(); - // Post the funding consignment under the txid (matching the rgb-lib `accept_transfer` - // flow and the acceptor's txid lookup), not the P2V `recipient_id`. The witness - // `recipient_id` is only used to build the transfer above. - let funding_vout = transaction - .output - .iter() - .position(|output| output.script_pubkey == funding_output_script) - .ok_or_else(|| { - RgbBackendError::Unexpected("funding output missing from colored tx".to_owned()) - })? as u32; - wallet - .upsert_witness( - NativeRgbTxid::from_str(&txid) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?, - WitnessOrd::Tentative, - ) - .map_err(RgbBackendError::from)?; - let proxy_url = NativeTransportEndpoint::new(request.consignment_endpoint.to_string()) - .map_err(RgbBackendError::from)? - .endpoint; - let consignment_path = - wallet.get_send_consignment_path(&request.contract_id.to_string(), &txid); - wallet - .post_consignment( - &proxy_url, - txid.clone(), - consignment_path, - txid, - Some(funding_vout), - ) - .map_err(RgbBackendError::from)?; - Ok(PreparedRgbFundingTransfer { signed_psbt, transaction }) - }) - .await - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))? - } - - /// Completes and broadcasts an RGB funding transfer after LDK reports `ChannelPending`. - /// - /// NOTE: unlike the wasm backend's `complete_funding_transfer`, this native path is not - /// idempotent on replay. If the node restarts after `send_end` succeeds but before the caller - /// records its completion marker, a second call re-invokes `send_end` on an already-sent batch - /// transfer and fails. The wasm backend recovers via `is_batch_transfer_sent`; native - /// `rgb-lib` does not expose that query yet. - /// - /// TODO(rgb-native-idempotency): when `send_end` reports the transfer is already sent, derive - /// the funding txid from `signed_psbt` and confirm via `wallet.list_transfers(None)` that a - /// transfer with that txid is in a non-failed status, returning `Ok(txid)`. This first needs - /// the exact native `send_end` replay error variant identified. - pub async fn complete_funding_transfer( - self: &Arc, signed_psbt: String, - ) -> Result { - let backend = Arc::clone(self); - tokio::task::spawn_blocking(move || { - let mut wallet = backend.wallet.lock().unwrap(); - let online = - wallet.go_online(backend.online_options.clone()).map_err(RgbBackendError::from)?; - wallet - .send_end(online, signed_psbt) - .map(|result| result.txid) - .map_err(RgbBackendError::from) - }) - .await - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))? - } - - /// Accepts and validates an incoming RGB funding transfer without blocking the async caller. - /// - /// `_funding_output_script` is currently unused: `rgb-lib`'s `accept_transfer` resolves the - /// funding consignment by txid. It is retained for deriving a P2V `recipient_id` from the - /// funding output if a hint-based lookup is ever needed. - pub async fn accept_funding_transfer( - self: &Arc, funding_txid: String, funding_vout: u32, - consignment_endpoint: RgbTransport, _funding_output_script: Option, - ) -> Result { - let backend = Arc::clone(self); - tokio::task::spawn_blocking(move || { - let mut wallet = backend.wallet.lock().unwrap(); - wallet.go_online(backend.online_options.clone()).map_err(RgbBackendError::from)?; - let (consignment, assignments) = wallet - .accept_transfer(funding_txid, funding_vout, consignment_endpoint, STATIC_BLINDING) - .map_err(RgbBackendError::from)?; - if assignments.len() != 1 { - return Err(RgbBackendError::Unexpected(format!( - "Unexpected number of RGB assignments: {}", - assignments.len() - ))); - } - let received_amount = match assignments.into_iter().next().unwrap() { - NativeAssignment::Fungible(amount) => amount, - NativeAssignment::NonFungible => 1, - NativeAssignment::InflationRight(_) | NativeAssignment::Any => { - return Err(RgbBackendError::Unexpected( - "Unsupported RGB funding assignment".to_owned(), - )) - }, - }; - let contract_id = consignment.contract_id(); - let schema = NativeAssetSchema::from_schema_id(consignment.schema_id()) - .map(Into::into) - .map_err(RgbBackendError::from)?; - let mut consignment_bytes = Vec::new(); - consignment - .save(&mut consignment_bytes) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - Ok(RgbFundingValidation { - consignment: consignment_bytes, - contract_id, - schema, - received_amount, - }) - }) - .await - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))? - } -} - -/// Long-lived browser RGB wallet backend. -/// -/// Construct this backend only after restoring the wallet from IndexedDB. The backend and every -/// LDK object containing it are intentionally single-threaded because `rgb-lib-wasm::Wallet` -/// contains `Rc` and `RefCell` state. -#[cfg(all(feature = "rgb-wasm", not(feature = "rgb-native")))] -pub struct WasmRgbBackend { - wallet: Rc>, - online: WasmOnline, - transaction_processor: RefCell<()>, - prepared_in_memory: RefCell>, -} - -/// The concrete RGB backend selected by the `rgb-wasm` build. -#[cfg(all(feature = "rgb-wasm", not(feature = "rgb-native")))] -pub type RgbBackend = WasmRgbBackend; - -#[cfg(all(feature = "rgb-wasm", not(feature = "rgb-native")))] -impl WasmRgbBackend { - /// Creates a browser backend that shares ownership of an already-online wallet. - pub fn new(wallet: Rc>, online: WasmOnline) -> Self { - Self { - wallet, - online, - transaction_processor: RefCell::new(()), - prepared_in_memory: RefCell::new(HashSet::new()), - } - } - - /// Restores a production wallet from IndexedDB, connects it to the indexer, and creates the - /// browser backend. - pub async fn restore( - wallet_data: WasmWalletData, skip_consistency_check: bool, indexer_url: String, - ) -> Result { - let mut wallet = WasmWallet::restore(wallet_data).await.map_err(RgbBackendError::from)?; - let online = wallet - .go_online(skip_consistency_check, indexer_url) - .await - .map_err(RgbBackendError::from)?; - Ok(Self::new(Rc::new(RefCell::new(wallet)), online)) - } - - /// Prepares a deterministic colored transaction, consumes its fascia in memory so dependent - /// transactions can be colored immediately, and persists it for asynchronous IndexedDB flush. - pub fn prepare_transaction( - &self, transaction: Transaction, coloring_info: ColoringInfo, kv_store: &dyn KVStoreSync, - ) -> Result { - let psbt = Psbt::from_unsigned_tx(transaction) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - let mut psbt = RgbLibPsbt::from_str(&psbt.to_string()) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - let wallet = self.wallet.try_borrow().map_err(|_| { - RgbBackendError::Unexpected("RGB WASM wallet is already in use".to_owned()) - })?; - let (fascia, _) = wallet - .color_psbt(&mut psbt, coloring_info.into_wasm()) - .map_err(RgbBackendError::from)?; - let psbt = Psbt::from_str(&psbt.to_string()) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - let transaction = match psbt.extract_tx() { - Ok(tx) => Ok(tx), - Err(ExtractTxError::MissingInputValue { tx }) => Ok(tx), - Err(error) => Err(RgbBackendError::Unexpected(error.to_string())), - }?; - let txid = transaction.compute_txid().to_string(); - if kv_store.read(RGB_PRIMARY_NS, RGB_CONSUMED_FASCIA_NS, &txid).is_err() { - let serialized_fascia = serde_json::to_vec(&fascia) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - kv_store - .write(RGB_PRIMARY_NS, RGB_PENDING_FASCIA_NS, &txid, serialized_fascia) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - wallet - .consume_fascia_in_memory(fascia, Some(WitnessOrd::Ignored)) - .map_err(RgbBackendError::from)?; - self.prepared_in_memory.borrow_mut().insert(txid); - } - Ok(transaction) - } - - /// Returns whether a colored transaction's RGB fascia is durable. - pub fn is_transaction_durable(&self, txid: &bitcoin::Txid, kv_store: &dyn KVStoreSync) -> bool { - match kv_store.read(RGB_PRIMARY_NS, RGB_PENDING_FASCIA_NS, &txid.to_string()) { - Ok(_) => false, - Err(error) if error.kind() == io::ErrorKind::NotFound => true, - Err(_) => false, - } - } - - /// Returns whether any prepared RGB transaction still requires durable fascia consumption. - pub fn has_pending_transactions( - &self, kv_store: &dyn KVStoreSync, - ) -> Result { - kv_store - .list(RGB_PRIMARY_NS, RGB_PENDING_FASCIA_NS) - .map(|keys| !keys.is_empty()) - .map_err(|error| RgbBackendError::Unexpected(error.to_string())) - } - - /// Durably consumes all prepared RGB fascia in IndexedDB before signing and message release - /// are retried. - pub async fn process_pending_transactions( - self: &Arc, kv_store: Arc, - ) -> Result<(), RgbBackendError> { - let _processor_guard = self.transaction_processor.try_borrow_mut().map_err(|_| { - RgbBackendError::Unexpected( - "RGB transaction persistence is already being processed".to_owned(), - ) - })?; - let mut pending = kv_store - .list(RGB_PRIMARY_NS, RGB_PENDING_FASCIA_NS) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - pending.sort(); - while !pending.is_empty() { - let mut deferred = Vec::new(); - let mut last_error = None; - let mut progressed = false; - for txid in pending { - let already_consumed = self.prepared_in_memory.borrow().contains(&txid); - let result = { - let wallet = self.wallet.try_borrow().map_err(|_| { - RgbBackendError::Unexpected("RGB WASM wallet is already in use".to_owned()) - })?; - if already_consumed { - wallet.flush().await - } else { - let bytes = kv_store - .read(RGB_PRIMARY_NS, RGB_PENDING_FASCIA_NS, &txid) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - let fascia = serde_json::from_slice(&bytes) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - wallet.consume_fascia(fascia, Some(WitnessOrd::Ignored)).await - } - }; - match result.map_err(RgbBackendError::from) { - Ok(()) => { - progressed = true; - self.prepared_in_memory.borrow_mut().remove(&txid); - kv_store - .write(RGB_PRIMARY_NS, RGB_CONSUMED_FASCIA_NS, &txid, Vec::new()) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - kv_store - .remove(RGB_PRIMARY_NS, RGB_PENDING_FASCIA_NS, &txid, false) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - }, - Err(error) => { - deferred.push(txid); - last_error = Some(error); - }, - } - } - if deferred.is_empty() { - break; - } - if !progressed { - return Err(last_error.expect("deferred fascia has an error")); - } - pending = deferred; - } - Ok(()) - } - - /// Creates and signs an RGB funding transfer, posts its consignment, and leaves the transfer - /// unbroadcast until [`Self::complete_funding_transfer`] is called. - pub async fn prepare_funding_transfer( - self: &Arc, request: RgbFundingTransferRequest, - ) -> Result { - let assignment = match request.schema { - AssetSchema::Nia | AssetSchema::Ifa => WasmAssignment::Fungible(request.amount), - schema => return Err(RgbBackendError::UnsupportedSchema(schema)), - }; - let recipient_id = wasm_recipient_id_from_script_buf( - request.output_script, - wasm_bitcoin_network(request.network), - ); - let recipient_map = HashMap::from([( - request.contract_id.to_string(), - vec![WasmRecipient { - recipient_id, - witness_data: Some(WasmWitnessData { - amount_sat: request.channel_value_satoshis, - blinding: Some(STATIC_BLINDING), - }), - assignment, - transport_endpoints: vec![request.consignment_endpoint.to_string()], - }], - )]); - let mut wallet = self.wallet.try_borrow_mut().map_err(|_| { - RgbBackendError::Unexpected("RGB WASM wallet is already in use".to_owned()) - })?; - let unsigned_psbt = wallet - .send_begin( - self.online.clone(), - recipient_map, - true, - request.fee_rate, - request.min_confirmations, - // Pin the funding tx to a final (height 0) absolute locktime; LDK rejects a - // funding transaction whose absolute timelock is non-final. - Some(0), - ) - .await - .map_err(RgbBackendError::from)?; - let signed_psbt = wallet.sign_psbt(unsigned_psbt, None).map_err(RgbBackendError::from)?; - let psbt = Psbt::from_str(&signed_psbt) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - let transaction = psbt - .clone() - .extract_tx() - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - let funding_txid = transaction.compute_txid().to_string(); - wallet - .post_pending_consignments(funding_txid.clone()) - .await - .map_err(RgbBackendError::from)?; - // Also post the consignment keyed by the txid (mirroring the native backend above), so a - // native acceptor — whose `accept_transfer` resolves the funding consignment by raw txid — - // can fetch it. `post_pending_consignments` only posts under the P2V/witness recipient_id. - wallet.post_consignment_by_txid(funding_txid).await.map_err(RgbBackendError::from)?; - wallet.flush().await.map_err(RgbBackendError::from)?; - Ok(PreparedRgbFundingTransfer { signed_psbt, transaction }) - } - - /// Completes and broadcasts an RGB funding transfer after LDK reports `ChannelPending`. - /// - /// Idempotent: if `send_end` returns `UnknownTransfer` and the transfer is already recorded - /// in the wallet database (non-failed status), we treat the operation as already completed. - /// This handles the crash-inject boundary where the browser reloads after `flush()` but - /// before the KV completion marker is written, causing replay to call `send_end` again. - pub async fn complete_funding_transfer( - self: &Arc, signed_psbt: String, - ) -> Result { - let mut wallet = self.wallet.try_borrow_mut().map_err(|_| { - RgbBackendError::Unexpected("RGB WASM wallet is already in use".to_owned()) - })?; - let result = match wallet.send_end(self.online.clone(), signed_psbt, false).await { - Ok(r) => r, - Err(WasmRgbLibError::UnknownTransfer { ref txid }) => { - if wallet.is_batch_transfer_sent(txid).map_err(RgbBackendError::from)? { - return Ok(txid.clone()); - } - return Err(RgbBackendError::from(WasmRgbLibError::UnknownTransfer { - txid: txid.clone(), - })); - }, - Err(e) => return Err(RgbBackendError::from(e)), - }; - wallet.flush().await.map_err(RgbBackendError::from)?; - Ok(result.txid) - } - - /// Accepts, validates, and durably persists an incoming RGB funding transfer. - /// - /// `_funding_output_script` is currently unused: `accept_transfer` resolves the funding - /// consignment by txid. It is retained for deriving a P2V `recipient_id` from the funding - /// output if a hint-based lookup is ever needed. - pub async fn accept_funding_transfer( - self: &Arc, funding_txid: String, funding_vout: u32, - consignment_endpoint: RgbTransport, _funding_output_script: Option, - ) -> Result { - let mut wallet = self.wallet.try_borrow_mut().map_err(|_| { - RgbBackendError::Unexpected("RGB WASM wallet is already in use".to_owned()) - })?; - let (consignment, assignments) = wallet - .accept_transfer( - self.online.clone(), - funding_txid, - funding_vout, - consignment_endpoint, - STATIC_BLINDING, - ) - .await - .map_err(RgbBackendError::from)?; - if assignments.len() != 1 { - return Err(RgbBackendError::Unexpected(format!( - "Unexpected number of RGB assignments: {}", - assignments.len() - ))); - } - let received_amount = match assignments.into_iter().next().unwrap() { - WasmAssignment::Fungible(amount) => amount, - WasmAssignment::NonFungible => 1, - WasmAssignment::InflationRight(_) | WasmAssignment::Any => { - return Err(RgbBackendError::Unexpected( - "Unsupported RGB funding assignment".to_owned(), - )) - }, - }; - let contract_id = consignment.contract_id(); - let schema = WasmAssetSchema::from_schema_id(consignment.schema_id()) - .map(Into::into) - .map_err(RgbBackendError::from)?; - let mut consignment_bytes = Vec::new(); - consignment - .save(&mut consignment_bytes) - .map_err(|error| RgbBackendError::Unexpected(error.to_string()))?; - Ok(RgbFundingValidation { - consignment: consignment_bytes, - contract_id, - schema, - received_amount, - }) - } -} - /// Static blinding constant (will be removed in the future) pub const STATIC_BLINDING: u64 = 777; +/// Name of the file containing the bitcoin network +pub const BITCOIN_NETWORK_FNAME: &str = "bitcoin_network"; +/// Name of the file containing the electrum URL +pub const INDEXER_URL_FNAME: &str = "indexer_url"; +/// Name of the file containing the wallet fingerprint +pub const WALLET_FINGERPRINT_FNAME: &str = "wallet_fingerprint"; +/// Name of the file containing the account-level xPub of the vanilla-side of the wallet +pub const WALLET_ACCOUNT_XPUB_VANILLA_FNAME: &str = "wallet_account_xpub_vanilla"; +/// Name of the file containing the account-level xPub of the colored-side of the wallet +pub const WALLET_ACCOUNT_XPUB_COLORED_FNAME: &str = "wallet_account_xpub_colored"; +/// Name of the file containing the master fingerprint of the wallet +pub const WALLET_MASTER_FINGERPRINT_FNAME: &str = "wallet_master_fingerprint"; +/// Name of the file containing the wallet reuse_addresses setting +pub const WALLET_REUSE_ADDRESSES_FNAME: &str = "wallet_reuse_addresses"; + // kv_store namespace constants for RGB data persistence /// Primary namespace for all RGB data pub const RGB_PRIMARY_NS: &str = "rgb"; @@ -992,10 +70,9 @@ pub const RGB_PAYMENT_INFO_OUTBOUND_NS: &str = "payment_info_outbound"; pub const RGB_TRANSFER_INFO_NS: &str = "transfer_info"; /// Secondary namespace for consignment data pub const RGB_CONSIGNMENT_NS: &str = "consignment"; -/// Secondary namespace for prepared RGB fascia awaiting durable consumption. -pub const RGB_PENDING_FASCIA_NS: &str = "pending_fascia"; -/// Secondary namespace recording RGB fascia which have been durably consumed. -pub const RGB_CONSUMED_FASCIA_NS: &str = "consumed_fascia"; +/// Secondary namespace for wallet configuration +pub const RGB_WALLET_CONFIG_NS: &str = "wallet_config"; +const VANILLA_SYNC_LOOKBACK: u32 = 20; /// RGB channel info #[derive(Debug, Clone, Deserialize, Serialize)] @@ -1065,6 +142,150 @@ mod contract_id_serde { } } +fn _get_bitcoin_network(kv_store: &dyn KVStoreSync) -> BitcoinNetwork { + let bitcoin_network = + kv_store.read_config(BITCOIN_NETWORK_FNAME).expect("bitcoin_network must be in KVStore"); + BitcoinNetwork::from_str(&bitcoin_network).unwrap() +} + +fn _get_account_xpub_colored(kv_store: &dyn KVStoreSync) -> String { + kv_store + .read_config(WALLET_ACCOUNT_XPUB_COLORED_FNAME) + .expect("account_xpub_colored must be in KVStore") +} + +fn _get_account_xpub_vanilla(kv_store: &dyn KVStoreSync) -> String { + kv_store + .read_config(WALLET_ACCOUNT_XPUB_VANILLA_FNAME) + .expect("account_xpub_vanilla must be in KVStore") +} + +fn _get_master_fingerprint(kv_store: &dyn KVStoreSync) -> String { + kv_store + .read_config(WALLET_MASTER_FINGERPRINT_FNAME) + .expect("master_fingerprint must be in KVStore") +} + +fn _get_indexer_url(kv_store: &dyn KVStoreSync) -> String { + kv_store.read_config(INDEXER_URL_FNAME).expect("indexer_url must be in KVStore") +} + +fn _get_reuse_addresses(kv_store: &dyn KVStoreSync) -> bool { + kv_store.read_config(WALLET_REUSE_ADDRESSES_FNAME).map(|v| v == "true").unwrap_or(false) +} + +fn _new_rgb_wallet( + data_dir: String, bitcoin_network: BitcoinNetwork, account_xpub_vanilla: String, + account_xpub_colored: String, master_fingerprint: String, reuse_addresses: bool, +) -> Wallet { + let keys = SinglesigKeys { + account_xpub_vanilla, + account_xpub_colored, + vanilla_keychain: None, + master_fingerprint, + mnemonic: None, + witness_version: WitnessVersion::Taproot, + }; + Wallet::new( + WalletData { + data_dir, + bitcoin_network, + database_type: DatabaseType::Sqlite, + max_allocations_per_utxo: 1, + supported_schemas: vec![ + AssetSchema::Nia, + AssetSchema::Cfa, + AssetSchema::Uda, + AssetSchema::Ifa, + ], + reuse_addresses, + }, + keys, + ) + .expect("valid rgb-lib wallet") +} + +fn _get_wallet_data( + ldk_data_dir: &Path, kv_store: &dyn KVStoreSync, +) -> (String, BitcoinNetwork, String, String, String, bool) { + let data_dir = ldk_data_dir.parent().unwrap().to_string_lossy().to_string(); + let bitcoin_network = _get_bitcoin_network(kv_store); + let account_xpub_vanilla = _get_account_xpub_vanilla(kv_store); + let account_xpub_colored = _get_account_xpub_colored(kv_store); + let master_fingerprint = _get_master_fingerprint(kv_store); + let reuse_addresses = _get_reuse_addresses(kv_store); + ( + data_dir, + bitcoin_network, + account_xpub_vanilla, + account_xpub_colored, + master_fingerprint, + reuse_addresses, + ) +} + +async fn _get_rgb_wallet(ldk_data_dir: &Path, kv_store: &dyn KVStoreSync) -> Wallet { + let ( + data_dir, + bitcoin_network, + account_xpub_vanilla, + account_xpub_colored, + master_fingerprint, + reuse_addresses, + ) = _get_wallet_data(ldk_data_dir, kv_store); + tokio::task::spawn_blocking(move || { + _new_rgb_wallet( + data_dir, + bitcoin_network, + account_xpub_vanilla, + account_xpub_colored, + master_fingerprint, + reuse_addresses, + ) + }) + .await + .unwrap() +} + +async fn _accept_transfer( + ldk_data_dir: &Path, funding_txid: String, consignment_endpoint: RgbTransport, + kv_store: &dyn KVStoreSync, +) -> Result<(RgbTransfer, Vec), RgbLibError> { + let funding_vout = 1; + let ( + data_dir, + bitcoin_network, + account_xpub_vanilla, + account_xpub_colored, + master_fingerprint, + reuse_addresses, + ) = _get_wallet_data(ldk_data_dir, kv_store); + let indexer_url = _get_indexer_url(kv_store); + tokio::task::spawn_blocking(move || { + let mut wallet = _new_rgb_wallet( + data_dir, + bitcoin_network, + account_xpub_vanilla, + account_xpub_colored, + master_fingerprint, + reuse_addresses, + ); + wallet.go_online(OnlineOptions { + indexer_url, + skip_consistency_check: true, + vanilla_sync_lookback: VANILLA_SYNC_LOOKBACK, + })?; + wallet.accept_transfer( + funding_txid.clone(), + funding_vout, + consignment_endpoint, + STATIC_BLINDING, + ) + }) + .await + .unwrap() +} + fn _counterparty_output_index( outputs: &[TxOut], channel_type_features: &ChannelTypeFeatures, payment_key: &PublicKey, ) -> Option { @@ -1087,12 +308,6 @@ pub fn is_tx_colored(tx: &Transaction) -> bool { op_return_position(tx).is_some() } -/// Maps an RGB coloring KVStore or (de)serialization failure to a channel-closing error, so a -/// storage fault closes the affected channel instead of panicking the whole node. -fn rgb_color_err(error: E) -> ChannelError { - ChannelError::close(format!("RGB coloring persistence failed: {error}")) -} - /// Color commitment transaction pub(crate) fn color_commitment( channel_context: &ChannelContext, funding_scope: &FundingScope, @@ -1102,11 +317,12 @@ where ::Target: SignerProvider, { let channel_id = &channel_context.channel_id; + let ldk_data_dir = channel_context.ldk_data_dir.as_path(); let kv_store = channel_context.rgb_kv_store.as_ref(); let commitment_tx = commitment_transaction.clone().built.transaction; - let rgb_info = get_rgb_channel_info_pending(channel_id, kv_store).map_err(rgb_color_err)?; + let rgb_info = get_rgb_channel_info_pending(channel_id, kv_store); let contract_id = rgb_info.contract_id; let chan_id = channel_id.0.as_hex(); @@ -1134,19 +350,19 @@ where if let Ok(data) = kv_store.read(RGB_PRIMARY_NS, namespace, &pending_key) { let mut rgb_payment_info: RgbPaymentInfo = - bincode::deserialize(&data).map_err(rgb_color_err)?; + bincode::deserialize(&data).expect("valid data"); rgb_payment_info.local_rgb_amount = rgb_info.local_rgb_amount; rgb_payment_info.remote_rgb_amount = rgb_info.remote_rgb_amount; - let data = bincode::serialize(&rgb_payment_info).map_err(rgb_color_err)?; + let data = bincode::serialize(&rgb_payment_info).expect("valid rgb payment info"); kv_store .write(RGB_PRIMARY_NS, namespace, &htlc_proxy_id, data.clone()) - .map_err(rgb_color_err)?; + .expect("able to write rgb payment info"); kv_store .write(RGB_PRIMARY_NS, namespace, &htlc_proxy_id_pending, data) - .map_err(rgb_color_err)?; + .expect("able to write rgb pending payment info"); kv_store .remove(RGB_PRIMARY_NS, namespace, &pending_key, false) - .map_err(rgb_color_err)?; + .expect("able to remove pending payment info"); } // Only accept a stored candidate if it actually describes THIS HTLC on THIS channel. @@ -1160,66 +376,66 @@ where && info.inbound == inbound }; - let cached_payment_info = match kv_store.read(RGB_PRIMARY_NS, namespace, &htlc_proxy_id) { - Ok(data) => Some(bincode::deserialize::(&data).map_err(rgb_color_err)?), - Err(_) => None, - }; - let rgb_payment_info = - if let Some(info) = cached_payment_info.filter(|info| is_compatible(info)) { - // Cache hit on the per-channel record. Preserve stored balances: they were the - // channel's snapshot at the time this HTLC was first coloured, and the later - // `remote_rgb_amount - rgb_received_htlc` (and local/offered) subtraction assumes - // that snapshot — using current `rgb_info` values can underflow if the channel - // state has since moved. Matches pre-KVStore `channel_rgb_payment_info_path` - // behaviour, where `should_persist_channel_info` stayed false on this branch. - info - } else if let Some(mut info) = kv_store - .read_rgb_payment_info(&htlc.payment_hash, inbound) - .ok() - .filter(|info| is_compatible(info)) - { - // Fall back to the canonical payment-hash-keyed record written by the sender/payee - // via `write_rgb_payment_info_file`. Lets a second channel on the same node recover - // the authoritative payment info once the first channel has consumed the - // `_pending` marker. Refresh balances, then persist a per-channel - // copy so subsequent commitment updates hit the cached branch above. - info.local_rgb_amount = rgb_info.local_rgb_amount; - info.remote_rgb_amount = rgb_info.remote_rgb_amount; - let data = bincode::serialize(&info).map_err(rgb_color_err)?; - kv_store - .write(RGB_PRIMARY_NS, namespace, &htlc_proxy_id, data.clone()) - .map_err(rgb_color_err)?; - kv_store - .write(RGB_PRIMARY_NS, namespace, &htlc_proxy_id_pending, data) - .map_err(rgb_color_err)?; - info - } else { - // No compatible record available (e.g. a forwarder that never saw a - // sender/payee-side write). Synthesize from the channel's own RGB info plus the - // HTLC's amount. - let rgb_payment_info = RgbPaymentInfo { - contract_id, - amount: htlc_amount_rgb, - local_rgb_amount: rgb_info.local_rgb_amount, - remote_rgb_amount: rgb_info.remote_rgb_amount, - swap_payment: true, - inbound, - }; - let data = bincode::serialize(&rgb_payment_info).map_err(rgb_color_err)?; - kv_store - .write(RGB_PRIMARY_NS, namespace, &htlc_proxy_id, data.clone()) - .map_err(rgb_color_err)?; - kv_store - .write(RGB_PRIMARY_NS, namespace, &htlc_proxy_id_pending, data) - .map_err(rgb_color_err)?; - rgb_payment_info + let rgb_payment_info = if let Some(info) = kv_store + .read(RGB_PRIMARY_NS, namespace, &htlc_proxy_id) + .ok() + .map(|data| bincode::deserialize::(&data).expect("valid data")) + .filter(|info| is_compatible(info)) + { + // Cache hit on the per-channel record. Preserve stored balances: they were the + // channel's snapshot at the time this HTLC was first coloured, and the later + // `remote_rgb_amount - rgb_received_htlc` (and local/offered) subtraction assumes + // that snapshot — using current `rgb_info` values can underflow if the channel + // state has since moved. Matches pre-KVStore `channel_rgb_payment_info_path` + // behaviour, where `should_persist_channel_info` stayed false on this branch. + info + } else if let Some(mut info) = kv_store + .read_rgb_payment_info(&htlc.payment_hash, inbound) + .ok() + .filter(|info| is_compatible(info)) + { + // Fall back to the canonical payment-hash-keyed record written by the sender/payee + // via `write_rgb_payment_info_file`. Lets a second channel on the same node recover + // the authoritative payment info once the first channel has consumed the + // `_pending` marker. Refresh balances, then persist a per-channel + // copy so subsequent commitment updates hit the cached branch above. + info.local_rgb_amount = rgb_info.local_rgb_amount; + info.remote_rgb_amount = rgb_info.remote_rgb_amount; + let data = bincode::serialize(&info).expect("valid rgb payment info"); + kv_store + .write(RGB_PRIMARY_NS, namespace, &htlc_proxy_id, data.clone()) + .expect("able to write rgb payment info"); + kv_store + .write(RGB_PRIMARY_NS, namespace, &htlc_proxy_id_pending, data) + .expect("able to write rgb pending payment info"); + info + } else { + // No compatible record available (e.g. a forwarder that never saw a + // sender/payee-side write). Synthesize from the channel's own RGB info plus the + // HTLC's amount. + let rgb_payment_info = RgbPaymentInfo { + contract_id, + amount: htlc_amount_rgb, + local_rgb_amount: rgb_info.local_rgb_amount, + remote_rgb_amount: rgb_info.remote_rgb_amount, + swap_payment: true, + inbound, }; + let data = bincode::serialize(&rgb_payment_info).expect("valid rgb payment info"); + kv_store + .write(RGB_PRIMARY_NS, namespace, &htlc_proxy_id, data.clone()) + .expect("able to write rgb payment info"); + kv_store + .write(RGB_PRIMARY_NS, namespace, &htlc_proxy_id_pending, data) + .expect("able to write rgb pending payment info"); + rgb_payment_info + }; if kv_store.read(RGB_PRIMARY_NS, namespace, &htlc_proxy_id_pending).is_err() { - let data = bincode::serialize(&rgb_payment_info).map_err(rgb_color_err)?; + let data = bincode::serialize(&rgb_payment_info).expect("valid rgb payment info"); kv_store .write(RGB_PRIMARY_NS, namespace, &htlc_proxy_id_pending, data) - .map_err(rgb_color_err)?; + .expect("able to write rgb pending payment info"); } if inbound { @@ -1273,30 +489,38 @@ where static_blinding: Some(STATIC_BLINDING), nonce: None, }; - let modified_tx = channel_context - .rgb_backend - .prepare_transaction(commitment_tx.clone(), coloring_info, kv_store) - .map_err(|error| { - ChannelError::close(format!("Failed to color RGB commitment: {error:?}")) - })?; + let psbt = Psbt::from_unsigned_tx(commitment_tx.clone()).unwrap(); + let mut psbt = RgbLibPsbt::from_str(&psbt.to_string()).unwrap(); + let handle = Handle::current(); + let _ = handle.enter(); + let wallet = futures::executor::block_on(_get_rgb_wallet(ldk_data_dir, kv_store)); + let (fascia, _) = wallet.color_psbt(&mut psbt, coloring_info).unwrap(); + let psbt = Psbt::from_str(&psbt.to_string()).unwrap(); + let modified_tx = match psbt.extract_tx() { + Ok(tx) => tx, + Err(ExtractTxError::MissingInputValue { tx }) => tx, + Err(e) => panic!("should never happen: {e}"), + }; let txid = modified_tx.compute_txid(); commitment_transaction.built = BuiltCommitmentTransaction { transaction: modified_tx, txid }; + wallet.consume_fascia(fascia.clone(), Some(WitnessOrd::Ignored)).unwrap(); + let rgb_amount = if counterparty { vout_p2wpkh_amt + rgb_offered_htlc } else { vout_p2wsh_amt + rgb_received_htlc }; let transfer_info = TransferInfo { contract_id, rgb_amount }; - kv_store.write_rgb_transfer_info(&txid.to_string(), &transfer_info).map_err(rgb_color_err)?; + kv_store.write_rgb_transfer_info(&txid.to_string(), &transfer_info); Ok(()) } /// Color HTLC transaction pub(crate) fn color_htlc( - htlc_tx: &mut Transaction, htlc: &HTLCOutputInCommitment, rgb_backend: &RgbBackend, + htlc_tx: &mut Transaction, htlc: &HTLCOutputInCommitment, ldk_data_dir: &Path, kv_store: &dyn KVStoreSync, ) -> Result<(), ChannelError> { if htlc.rgb_payment.is_none_or(|(_, a)| a == 0) { @@ -1304,16 +528,10 @@ pub(crate) fn color_htlc( } let (_, htlc_amount_rgb) = htlc.rgb_payment.expect("this HTLC has RGB assets"); - let consignment_htlc_outpoint = htlc_tx - .input - .first() - .ok_or_else(|| ChannelError::close("HTLC transaction has no inputs".to_owned()))? - .previous_output; + let consignment_htlc_outpoint = htlc_tx.input.first().unwrap().previous_output; let commitment_txid = consignment_htlc_outpoint.txid.to_string(); - let transfer_info = kv_store - .read_rgb_transfer_info(&commitment_txid) - .map_err(|e| ChannelError::close(format!("Failed to read RGB transfer info: {e}")))?; + let transfer_info = kv_store.read_rgb_transfer_info(&commitment_txid); let contract_id = transfer_info.contract_id; let asset_coloring_info = AssetColoringInfo { @@ -1325,29 +543,36 @@ pub(crate) fn color_htlc( static_blinding: Some(STATIC_BLINDING), nonce: Some(1), }; - let modified_tx = rgb_backend - .prepare_transaction(htlc_tx.clone(), coloring_info, kv_store) - .map_err(|error| ChannelError::close(format!("Failed to color RGB HTLC: {error:?}")))?; + let psbt = Psbt::from_unsigned_tx(htlc_tx.clone()).unwrap(); + let mut psbt = RgbLibPsbt::from_str(&psbt.to_string()).unwrap(); + let handle = Handle::current(); + let _ = handle.enter(); + let wallet = futures::executor::block_on(_get_rgb_wallet(ldk_data_dir, kv_store)); + let (fascia, _) = wallet.color_psbt(&mut psbt, coloring_info).unwrap(); + let psbt = Psbt::from_str(&psbt.to_string()).unwrap(); + let modified_tx = match psbt.extract_tx() { + Ok(tx) => tx, + Err(ExtractTxError::MissingInputValue { tx }) => tx, + Err(e) => panic!("should never happen: {e}"), + }; let txid = &modified_tx.compute_txid(); - *htlc_tx = modified_tx; + + wallet.consume_fascia(fascia.clone(), Some(WitnessOrd::Ignored)).unwrap(); let transfer_info = TransferInfo { contract_id, rgb_amount: htlc_amount_rgb }; - kv_store - .write_rgb_transfer_info(&txid.to_string(), &transfer_info) - .map_err(|e| ChannelError::close(format!("Failed to write RGB transfer info: {e}")))?; + kv_store.write_rgb_transfer_info(&txid.to_string(), &transfer_info); Ok(()) } /// Color closing transaction pub(crate) fn color_closing( - channel_id: &ChannelId, closing_transaction: &mut ClosingTransaction, rgb_backend: &RgbBackend, + channel_id: &ChannelId, closing_transaction: &mut ClosingTransaction, ldk_data_dir: &Path, kv_store: &dyn KVStoreSync, ) -> Result<(), ChannelError> { let closing_tx = closing_transaction.clone().built; - let rgb_info = get_rgb_channel_info_pending(channel_id, kv_store) - .map_err(|e| ChannelError::close(format!("Failed to read RGB channel info: {e}")))?; + let rgb_info = get_rgb_channel_info_pending(channel_id, kv_store); let contract_id = rgb_info.contract_id; let holder_vout_amount = rgb_info.local_rgb_amount; @@ -1360,7 +585,7 @@ pub(crate) fn color_closing( .output .iter() .position(|o| &o.script_pubkey == closing_transaction.to_holder_script()) - .ok_or_else(|| ChannelError::close("Missing holder output in closing tx".to_owned()))?; + .unwrap(); output_map.insert(holder_vout as u32, holder_vout_amount); } @@ -1369,9 +594,7 @@ pub(crate) fn color_closing( .output .iter() .position(|o| &o.script_pubkey == closing_transaction.to_counterparty_script()) - .ok_or_else(|| { - ChannelError::close("Missing counterparty output in closing tx".to_owned()) - })?; + .unwrap(); output_map.insert(counterparty_vout as u32, counterparty_vout_amount); } @@ -1382,17 +605,26 @@ pub(crate) fn color_closing( static_blinding: Some(STATIC_BLINDING), nonce: None, }; - let modified_tx = rgb_backend - .prepare_transaction(closing_tx.clone(), coloring_info, kv_store) - .map_err(|error| ChannelError::close(format!("Failed to color RGB close: {error:?}")))?; + let psbt = Psbt::from_unsigned_tx(closing_tx.clone()).unwrap(); + let mut psbt = RgbLibPsbt::from_str(&psbt.to_string()).unwrap(); + let handle = Handle::current(); + let _ = handle.enter(); + let wallet = futures::executor::block_on(_get_rgb_wallet(ldk_data_dir, kv_store)); + let (fascia, _) = wallet.color_psbt(&mut psbt, coloring_info).unwrap(); + let psbt = Psbt::from_str(&psbt.to_string()).unwrap(); + let modified_tx = match psbt.extract_tx() { + Ok(tx) => tx, + Err(ExtractTxError::MissingInputValue { tx }) => tx, + Err(e) => panic!("should never happen: {e}"), + }; let txid = &modified_tx.compute_txid(); closing_transaction.built = modified_tx; + wallet.consume_fascia(fascia.clone(), Some(WitnessOrd::Ignored)).unwrap(); + let transfer_info = TransferInfo { contract_id, rgb_amount: holder_vout_amount }; - kv_store - .write_rgb_transfer_info(&txid.to_string(), &transfer_info) - .map_err(|e| ChannelError::close(format!("Failed to write RGB transfer info: {e}")))?; + kv_store.write_rgb_transfer_info(&txid.to_string(), &transfer_info); Ok(()) } @@ -1400,14 +632,12 @@ pub(crate) fn color_closing( /// Get RgbInfo from KVStore pub(crate) fn get_rgb_channel_info( channel_id: &str, pending: bool, kv_store: &dyn KVStoreSync, -) -> Result { - kv_store.read_rgb_channel_info(channel_id, pending) +) -> RgbInfo { + kv_store.read_rgb_channel_info(channel_id, pending).expect("channel info must exist in KVStore") } /// Get pending RgbInfo from KVStore -pub fn get_rgb_channel_info_pending( - channel_id: &ChannelId, kv_store: &dyn KVStoreSync, -) -> Result { +pub fn get_rgb_channel_info_pending(channel_id: &ChannelId, kv_store: &dyn KVStoreSync) -> RgbInfo { get_rgb_channel_info(&channel_id.0.as_hex().to_string(), true, kv_store) } @@ -1421,7 +651,7 @@ pub fn is_channel_rgb(channel_id: &ChannelId, kv_store: &dyn KVStoreSync) -> boo pub fn write_rgb_payment_info_file( payment_hash: &PaymentHash, contract_id: ContractId, amount_rgb: u64, swap_payment: bool, inbound: bool, kv_store: &Arc, -) -> Result<(), io::Error> { +) { let rgb_payment_info = RgbPaymentInfo { contract_id, amount: amount_rgb, @@ -1430,70 +660,102 @@ pub fn write_rgb_payment_info_file( swap_payment, inbound, }; - kv_store.write_rgb_payment_info(payment_hash, &rgb_payment_info)?; + kv_store.write_rgb_payment_info(payment_hash, &rgb_payment_info); let payment_hash_hex = payment_hash.0.as_hex(); let pending_key = format!("{payment_hash_hex}_pending"); let namespace = if inbound { RGB_PAYMENT_INFO_INBOUND_NS } else { RGB_PAYMENT_INFO_OUTBOUND_NS }; - let data = bincode::serialize(&rgb_payment_info) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?; - kv_store.write(RGB_PRIMARY_NS, namespace, &pending_key, data) + let data = bincode::serialize(&rgb_payment_info).expect("valid rgb payment info"); + kv_store + .write(RGB_PRIMARY_NS, namespace, &pending_key, data) + .expect("able to write rgb payment info pending"); } /// Rename RGB channel info from temporary to final channel ID in KVStore pub(crate) fn rename_rgb_files( channel_id: &ChannelId, temporary_channel_id: &ChannelId, kv_store: &dyn KVStoreSync, -) -> Result<(), io::Error> { +) { let temp_chan_id = temporary_channel_id.0.as_hex().to_string(); let chan_id = channel_id.0.as_hex().to_string(); - let rgb_info = kv_store.read_rgb_channel_info(&temp_chan_id, false)?; - kv_store.write_rgb_channel_info(&chan_id, &rgb_info, false)?; - kv_store.remove_rgb_channel_info(&temp_chan_id, false)?; + let rgb_info = kv_store.read_rgb_channel_info(&temp_chan_id, false).expect("rename ok"); + kv_store.write_rgb_channel_info(&chan_id, &rgb_info, false); + kv_store.remove_rgb_channel_info(&temp_chan_id, false).expect("rename ok"); - let rgb_info = kv_store.read_rgb_channel_info(&temp_chan_id, true)?; - kv_store.write_rgb_channel_info(&chan_id, &rgb_info, true)?; - kv_store.remove_rgb_channel_info(&temp_chan_id, true)?; + let rgb_info = kv_store.read_rgb_channel_info(&temp_chan_id, true).expect("rename ok"); + kv_store.write_rgb_channel_info(&chan_id, &rgb_info, true); + kv_store.remove_rgb_channel_info(&temp_chan_id, true).expect("rename ok"); if let Ok(consignment_data) = kv_store.read_rgb_consignment(&temp_chan_id) { - kv_store.write_rgb_consignment(&chan_id, consignment_data)?; - kv_store.remove_rgb_consignment(&temp_chan_id)?; + kv_store.write_rgb_consignment(&chan_id, consignment_data); + kv_store.remove_rgb_consignment(&temp_chan_id); } - Ok(()) } -/// Persist a successfully validated incoming RGB funding transfer. -pub(crate) fn persist_funding_validation( - temporary_channel_id: &ChannelId, funding_txid: &str, validation: RgbFundingValidation, - push_asset_amount: Option, kv_store: &dyn KVStoreSync, +/// Handle funding on the receiver side +pub(crate) fn handle_funding( + temporary_channel_id: &ChannelId, funding_txid: String, ldk_data_dir: &Path, + consignment_endpoint: RgbTransport, push_asset_amount: Option, kv_store: &dyn KVStoreSync, ) -> Result<(), ChannelError> { - let push_amount = push_asset_amount.unwrap_or(0); - if push_amount > validation.received_amount { + let handle = Handle::current(); + let _ = handle.enter(); + let accept_res = futures::executor::block_on(_accept_transfer( + ldk_data_dir, + funding_txid.clone(), + consignment_endpoint, + kv_store, + )); + let (consignment, remote_rgb_assignments) = match accept_res { + Ok(res) => res, + Err(RgbLibError::InvalidConsignment) => { + return Err(ChannelError::close("Invalid RGB consignment for funding".to_owned())) + }, + Err(RgbLibError::NoConsignment) => { + return Err(ChannelError::close("Failed to find RGB consignment".to_owned())) + }, + Err(RgbLibError::UnknownRgbSchema { schema_id }) => { + return Err(ChannelError::close(format!("Unknown RGB schema: {schema_id}"))) + }, + Err(RgbLibError::UnsupportedSchema { asset_schema }) => { + return Err(ChannelError::close(format!("Unsupported RGB schema: {asset_schema}"))) + }, + Err(RgbLibError::Indexer { details }) + | Err(RgbLibError::InvalidIndexer { details }) + | Err(RgbLibError::Network { details }) => { + return Err(ChannelError::close(format!("Failed to connect to indexer: {details}"))) + }, + Err(e) => return Err(ChannelError::close(format!("Unexpected error: {e}"))), + }; + + let mut consignment_buf = Vec::new(); + consignment.save(&mut consignment_buf).expect("unable to serialize consignment"); + kv_store.write_rgb_consignment(&funding_txid, consignment_buf.clone()); + let temp_chan_id = temporary_channel_id.0.as_hex().to_string(); + kv_store.write_rgb_consignment(&temp_chan_id, consignment_buf); + + if remote_rgb_assignments.len() != 1 { return Err(ChannelError::close(format!( - "RGB push amount {push_amount} exceeds funding amount {}", - validation.received_amount + "Unexpected number of RGB assignments: {}", + remote_rgb_assignments.len() ))); } - let persist = |namespace: &str, key: &str, value: Vec| { - kv_store.write(RGB_PRIMARY_NS, namespace, key, value).map_err(|error| { - ChannelError::Ignore(format!("Failed to persist RGB funding validation: {error}")) - }) + let channel_rgb_amount = match remote_rgb_assignments[0] { + Assignment::Fungible(amt) => amt, + Assignment::NonFungible => 1, + _ => unreachable!("unsupported schema"), }; - persist(RGB_CONSIGNMENT_NS, funding_txid, validation.consignment.clone())?; - let temp_chan_id = temporary_channel_id.0.as_hex().to_string(); - persist(RGB_CONSIGNMENT_NS, &temp_chan_id, validation.consignment)?; + let push_amount = push_asset_amount.unwrap_or(0); let rgb_info = RgbInfo { - contract_id: validation.contract_id, - schema: validation.schema, + contract_id: consignment.contract_id(), + schema: AssetSchema::from_schema_id(consignment.schema_id()).unwrap(), local_rgb_amount: push_amount, - remote_rgb_amount: validation.received_amount - push_amount, + remote_rgb_amount: channel_rgb_amount - push_amount, batch_transfer_idx: None, }; - let rgb_info = bincode::serialize(&rgb_info).map_err(|error| { - ChannelError::Ignore(format!("Failed to serialize RGB funding validation: {error}")) - })?; - persist(RGB_CHANNEL_INFO_PENDING_NS, &temp_chan_id, rgb_info.clone())?; - persist(RGB_CHANNEL_INFO_NS, &temp_chan_id, rgb_info)?; + let temporary_channel_id_str = temporary_channel_id.0.as_hex().to_string(); + + kv_store.write_rgb_channel_info(&temporary_channel_id_str, &rgb_info, true); + kv_store.write_rgb_channel_info(&temporary_channel_id_str, &rgb_info, false); Ok(()) } @@ -1502,8 +764,8 @@ pub(crate) fn persist_funding_validation( pub fn update_rgb_channel_amount( channel_id: &str, rgb_offered_htlc: u64, rgb_received_htlc: u64, pending: bool, kv_store: &dyn KVStoreSync, -) -> Result<(), io::Error> { - let mut rgb_info = get_rgb_channel_info(channel_id, pending, kv_store)?; +) { + let mut rgb_info = get_rgb_channel_info(channel_id, pending, kv_store); if rgb_offered_htlc > rgb_received_htlc { let spent = rgb_offered_htlc - rgb_received_htlc; @@ -1515,14 +777,14 @@ pub fn update_rgb_channel_amount( rgb_info.remote_rgb_amount -= received; } - kv_store.write_rgb_channel_info(channel_id, &rgb_info, pending) + kv_store.write_rgb_channel_info(channel_id, &rgb_info, pending); } /// Update pending RGB channel amount pub(crate) fn update_rgb_channel_amount_pending( channel_id: &ChannelId, rgb_offered_htlc: u64, rgb_received_htlc: u64, kv_store: &dyn KVStoreSync, -) -> Result<(), io::Error> { +) { update_rgb_channel_amount( &channel_id.0.as_hex().to_string(), rgb_offered_htlc, @@ -1535,31 +797,31 @@ pub(crate) fn update_rgb_channel_amount_pending( /// extension trait for RGB-specific KVStore operations pub trait RgbKvStoreExt { /// read transfer info from KVStore - fn read_rgb_transfer_info(&self, txid: &str) -> Result; + fn read_rgb_transfer_info(&self, txid: &str) -> TransferInfo; /// write transfer info to KVStore - fn write_rgb_transfer_info(&self, txid: &str, info: &TransferInfo) -> Result<(), io::Error>; + fn write_rgb_transfer_info(&self, txid: &str, info: &TransferInfo); /// read channel info from KVStore fn read_rgb_channel_info(&self, channel_id: &str, pending: bool) -> Result; /// write channel info to KVStore - fn write_rgb_channel_info( - &self, channel_id: &str, rgb_info: &RgbInfo, pending: bool, - ) -> Result<(), io::Error>; + fn write_rgb_channel_info(&self, channel_id: &str, rgb_info: &RgbInfo, pending: bool); /// read payment info from KVStore fn read_rgb_payment_info( &self, payment_hash: &PaymentHash, inbound: bool, ) -> Result; /// write payment info to KVStore - fn write_rgb_payment_info( - &self, payment_hash: &PaymentHash, info: &RgbPaymentInfo, - ) -> Result<(), io::Error>; + fn write_rgb_payment_info(&self, payment_hash: &PaymentHash, info: &RgbPaymentInfo); /// read consignment from KVStore fn read_rgb_consignment(&self, id: &str) -> Result, io::Error>; /// write consignment to KVStore - fn write_rgb_consignment(&self, id: &str, data: Vec) -> Result<(), io::Error>; + fn write_rgb_consignment(&self, id: &str, data: Vec); /// remove channel info from KVStore fn remove_rgb_channel_info(&self, channel_id: &str, pending: bool) -> Result<(), io::Error>; /// remove consignment from KVStore - fn remove_rgb_consignment(&self, id: &str) -> Result<(), io::Error>; + fn remove_rgb_consignment(&self, id: &str); + /// read a wallet config value from KVStore + fn read_config(&self, key: &str) -> Result; + /// write a wallet config value to KVStore + fn write_config(&self, key: &str, value: &str); /// whether the payment is colored fn is_payment_rgb(&self, payment_hash: &PaymentHash) -> bool; /// filter first hops to only include channels with sufficient RGB assets @@ -1567,32 +829,27 @@ pub trait RgbKvStoreExt { } impl RgbKvStoreExt for K { - fn read_rgb_transfer_info(&self, txid: &str) -> Result { - let data = self.read(RGB_PRIMARY_NS, RGB_TRANSFER_INFO_NS, txid)?; - bincode::deserialize(&data) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string())) + fn read_rgb_transfer_info(&self, txid: &str) -> TransferInfo { + let data = + self.read(RGB_PRIMARY_NS, RGB_TRANSFER_INFO_NS, txid).expect("KVStore read failed"); + bincode::deserialize(&data).expect("valid transfer info") } - fn write_rgb_transfer_info(&self, txid: &str, info: &TransferInfo) -> Result<(), io::Error> { - let data = bincode::serialize(info) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?; - self.write(RGB_PRIMARY_NS, RGB_TRANSFER_INFO_NS, txid, data) + fn write_rgb_transfer_info(&self, txid: &str, info: &TransferInfo) { + let data = bincode::serialize(info).expect("valid transfer info"); + self.write(RGB_PRIMARY_NS, RGB_TRANSFER_INFO_NS, txid, data).expect("KVStore write failed"); } fn read_rgb_channel_info(&self, channel_id: &str, pending: bool) -> Result { let namespace = if pending { RGB_CHANNEL_INFO_PENDING_NS } else { RGB_CHANNEL_INFO_NS }; let data = self.read(RGB_PRIMARY_NS, namespace, channel_id)?; - bincode::deserialize(&data) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string())) + bincode::deserialize(&data).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) } - fn write_rgb_channel_info( - &self, channel_id: &str, rgb_info: &RgbInfo, pending: bool, - ) -> Result<(), io::Error> { + fn write_rgb_channel_info(&self, channel_id: &str, rgb_info: &RgbInfo, pending: bool) { let namespace = if pending { RGB_CHANNEL_INFO_PENDING_NS } else { RGB_CHANNEL_INFO_NS }; - let data = bincode::serialize(rgb_info) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?; - self.write(RGB_PRIMARY_NS, namespace, channel_id, data) + let data = bincode::serialize(rgb_info).expect("valid rgb channel info"); + self.write(RGB_PRIMARY_NS, namespace, channel_id, data).expect("KVStore write failed"); } fn read_rgb_payment_info( @@ -1602,27 +859,23 @@ impl RgbKvStoreExt for K { if inbound { RGB_PAYMENT_INFO_INBOUND_NS } else { RGB_PAYMENT_INFO_OUTBOUND_NS }; let key = payment_hash.0.as_hex().to_string(); let data = self.read(RGB_PRIMARY_NS, namespace, &key)?; - bincode::deserialize(&data) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string())) + bincode::deserialize(&data).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) } - fn write_rgb_payment_info( - &self, payment_hash: &PaymentHash, info: &RgbPaymentInfo, - ) -> Result<(), io::Error> { + fn write_rgb_payment_info(&self, payment_hash: &PaymentHash, info: &RgbPaymentInfo) { let namespace = if info.inbound { RGB_PAYMENT_INFO_INBOUND_NS } else { RGB_PAYMENT_INFO_OUTBOUND_NS }; let key = payment_hash.0.as_hex().to_string(); - let data = bincode::serialize(info) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?; - self.write(RGB_PRIMARY_NS, namespace, &key, data) + let data = bincode::serialize(info).expect("valid rgb payment info"); + self.write(RGB_PRIMARY_NS, namespace, &key, data).expect("KVStore write failed"); } fn read_rgb_consignment(&self, id: &str) -> Result, io::Error> { self.read(RGB_PRIMARY_NS, RGB_CONSIGNMENT_NS, id) } - fn write_rgb_consignment(&self, id: &str, data: Vec) -> Result<(), io::Error> { - self.write(RGB_PRIMARY_NS, RGB_CONSIGNMENT_NS, id, data) + fn write_rgb_consignment(&self, id: &str, data: Vec) { + self.write(RGB_PRIMARY_NS, RGB_CONSIGNMENT_NS, id, data).expect("KVStore write failed"); } fn remove_rgb_channel_info(&self, channel_id: &str, pending: bool) -> Result<(), io::Error> { @@ -1630,8 +883,18 @@ impl RgbKvStoreExt for K { self.remove(RGB_PRIMARY_NS, namespace, channel_id, false) } - fn remove_rgb_consignment(&self, id: &str) -> Result<(), io::Error> { - self.remove(RGB_PRIMARY_NS, RGB_CONSIGNMENT_NS, id, false) + fn remove_rgb_consignment(&self, id: &str) { + self.remove(RGB_PRIMARY_NS, RGB_CONSIGNMENT_NS, id, false).expect("KVStore remove failed"); + } + + fn read_config(&self, key: &str) -> Result { + let data = self.read(RGB_PRIMARY_NS, RGB_WALLET_CONFIG_NS, key)?; + String::from_utf8(data).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) + } + + fn write_config(&self, key: &str, value: &str) { + self.write(RGB_PRIMARY_NS, RGB_WALLET_CONFIG_NS, key, value.as_bytes().to_vec()) + .expect("KVStore write failed"); } fn is_payment_rgb(&self, payment_hash: &PaymentHash) -> bool { @@ -1673,39 +936,3 @@ pub fn holder_validate_install_psbt_output_witness_scripts_hex(hex_scripts: Vec< pub fn holder_validate_take_psbt_output_witness_scripts_hex() -> Option> { HOLDER_VALIDATE_PSBT_WITNESS_SCRIPTS_HEX.with(|c| c.borrow_mut().take()) } - -#[cfg(all(test, feature = "rgb-native"))] -mod tests { - use super::*; - - #[test] - fn native_schemas_convert_to_shared_schemas() { - assert_eq!(AssetSchema::from(NativeAssetSchema::Nia), AssetSchema::Nia); - assert_eq!(AssetSchema::from(NativeAssetSchema::Uda), AssetSchema::Uda); - assert_eq!(AssetSchema::from(NativeAssetSchema::Cfa), AssetSchema::Cfa); - assert_eq!(AssetSchema::from(NativeAssetSchema::Ifa), AssetSchema::Ifa); - } - - #[test] - fn native_assignments_convert_to_shared_assignments() { - assert_eq!( - Assignment::from(NativeAssignment::InflationRight(42)), - Assignment::InflationRight(42) - ); - assert_eq!(Assignment::from(NativeAssignment::NonFungible), Assignment::NonFungible); - } - - #[test] - fn native_errors_convert_to_shared_errors() { - assert_eq!( - RgbBackendError::from(RgbLibError::InvalidConsignment), - RgbBackendError::InvalidConsignment - ); - assert_eq!( - RgbBackendError::from(RgbLibError::UnsupportedSchema { - asset_schema: NativeAssetSchema::Cfa, - }), - RgbBackendError::UnsupportedSchema(AssetSchema::Cfa) - ); - } -} diff --git a/lightning/src/routing/gossip.rs b/lightning/src/routing/gossip.rs index c3ab101a3..2f643a9cc 100644 --- a/lightning/src/routing/gossip.rs +++ b/lightning/src/routing/gossip.rs @@ -21,7 +21,7 @@ use bitcoin::hashes::sha256d::Hash as Sha256dHash; use bitcoin::hashes::Hash; use bitcoin::network::Network; -use crate::rgb_utils::ContractId; +use rgb_lib::ContractId; use crate::ln::msgs; use crate::ln::msgs::{ @@ -59,7 +59,7 @@ use core::{cmp, fmt}; pub use lightning_types::routing::RoutingFees; #[cfg(feature = "std")] -use web_time::{SystemTime, UNIX_EPOCH}; +use std::time::{SystemTime, UNIX_EPOCH}; /// We remove stale channel directional info two weeks after the last update, per BOLT 7's /// suggestion. @@ -3075,7 +3075,7 @@ pub(crate) mod tests { #[cfg(feature = "std")] { - use web_time::{SystemTime, UNIX_EPOCH}; + use std::time::{SystemTime, UNIX_EPOCH}; let tracking_time = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -3491,7 +3491,7 @@ pub(crate) mod tests { // We want to check that this will work even if *one* of the channel updates is recent, // so we should add it with a recent timestamp. assert!(network_graph.read_only().channels().get(&scid).unwrap().one_to_two.is_none()); - use web_time::{SystemTime, UNIX_EPOCH}; + use std::time::{SystemTime, UNIX_EPOCH}; let announcement_time = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("Time must be > 1970") @@ -3527,7 +3527,7 @@ pub(crate) mod tests { #[cfg(feature = "std")] { - use web_time::{SystemTime, UNIX_EPOCH}; + use std::time::{SystemTime, UNIX_EPOCH}; let tracking_time = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -3833,7 +3833,7 @@ pub(crate) mod tests { #[cfg(feature = "std")] fn calling_sync_routing_table() { use crate::ln::msgs::Init; - use web_time::{SystemTime, UNIX_EPOCH}; + use std::time::{SystemTime, UNIX_EPOCH}; let network_graph = create_network_graph(); let (secp_ctx, gossip_sync) = create_gossip_sync(&network_graph); diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index bc4fe0a16..78a5319ad 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -12,7 +12,7 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1}; use lightning_invoice::Bolt11Invoice; -use crate::rgb_utils::ContractId; +use rgb_lib::ContractId; use crate::blinded_path::payment::{ BlindedPaymentPath, ForwardTlvs, PaymentConstraints, PaymentForwardNode, PaymentRelay, diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 6f4de637b..608f551a9 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -38,6 +38,7 @@ use bitcoin::{secp256k1, Psbt, Sequence, Txid, WPubkeyHash, Witness}; use lightning_invoice::RawBolt11Invoice; +use std::path::PathBuf; use std::sync::Arc; use crate::chain::transaction::OutPoint; @@ -63,7 +64,7 @@ use crate::ln::script::ShutdownScript; use crate::offers::invoice::Bolt12Invoice; use crate::offers::invoice::UnsignedBolt12Invoice; use crate::offers::nonce::Nonce; -use crate::rgb_utils::{color_htlc, RgbBackend}; +use crate::rgb_utils::color_htlc; use crate::types::features::ChannelTypeFeatures; use crate::types::payment::PaymentPreimage; use crate::util::async_poll::AsyncResult; @@ -1377,8 +1378,8 @@ pub struct InMemorySigner { channel_keys_id: [u8; 32], /// A source of random bytes. entropy_source: RandomBytes, - /// Long-lived RGB wallet backend. - rgb_backend: Arc, + /// The LDK data directory + ldk_data_dir: PathBuf, /// KVStore for RGB data persistence rgb_kv_store: Arc, } @@ -1410,7 +1411,7 @@ impl Clone for InMemorySigner { commitment_seed: self.commitment_seed.clone(), channel_keys_id: self.channel_keys_id, entropy_source: RandomBytes::new(self.get_secure_random_bytes()), - rgb_backend: Arc::clone(&self.rgb_backend), + ldk_data_dir: self.ldk_data_dir.clone(), rgb_kv_store: Arc::clone(&self.rgb_kv_store), } } @@ -1422,7 +1423,7 @@ impl InMemorySigner { funding_key: SecretKey, revocation_base_key: SecretKey, payment_key_v1: SecretKey, payment_key_v2: SecretKey, v2_remote_key_derivation: bool, delayed_payment_base_key: SecretKey, htlc_base_key: SecretKey, commitment_seed: [u8; 32], - channel_keys_id: [u8; 32], rgb_backend: Arc, rand_bytes_unique_start: [u8; 32], + channel_keys_id: [u8; 32], ldk_data_dir: PathBuf, rand_bytes_unique_start: [u8; 32], rgb_kv_store: Arc, ) -> InMemorySigner { InMemorySigner { @@ -1436,7 +1437,7 @@ impl InMemorySigner { commitment_seed, channel_keys_id, entropy_source: RandomBytes::new(rand_bytes_unique_start), - rgb_backend, + ldk_data_dir, rgb_kv_store, } } @@ -1446,7 +1447,7 @@ impl InMemorySigner { funding_key: SecretKey, revocation_base_key: SecretKey, payment_key_v1: SecretKey, payment_key_v2: SecretKey, v2_remote_key_derivation: bool, delayed_payment_base_key: SecretKey, htlc_base_key: SecretKey, commitment_seed: [u8; 32], - channel_keys_id: [u8; 32], rgb_backend: Arc, rand_bytes_unique_start: [u8; 32], + channel_keys_id: [u8; 32], ldk_data_dir: PathBuf, rand_bytes_unique_start: [u8; 32], rgb_kv_store: Arc, ) -> InMemorySigner { InMemorySigner { @@ -1460,7 +1461,7 @@ impl InMemorySigner { commitment_seed, channel_keys_id, entropy_source: RandomBytes::new(rand_bytes_unique_start), - rgb_backend, + ldk_data_dir, rgb_kv_store, } } @@ -1502,17 +1503,8 @@ impl InMemorySigner { &keys.revocation_key, ); if commitment_tx.is_colored() { - if let Err(_e) = color_htlc( - &mut htlc_tx, - htlc, - self.rgb_backend.as_ref(), - self.rgb_kv_store.as_ref(), - ) { - return Err(()); - } - if !self - .rgb_backend - .is_transaction_durable(&htlc_tx.compute_txid(), self.rgb_kv_store.as_ref()) + if let Err(_e) = + color_htlc(&mut htlc_tx, htlc, &self.ldk_data_dir, self.rgb_kv_store.as_ref()) { return Err(()); } @@ -1786,11 +1778,6 @@ impl EcdsaChannelSigner for InMemorySigner { make_funding_redeemscript(&funding_pubkey, &counterparty_keys.funding_pubkey); let built_tx = trusted_tx.built_transaction(); - if commitment_tx.is_colored() - && !self.rgb_backend.is_transaction_durable(&built_tx.txid, self.rgb_kv_store.as_ref()) - { - return Err(()); - } let commitment_sig = built_tx.sign_counterparty_commitment( &funding_key, &channel_funding_redeemscript, @@ -1822,13 +1809,6 @@ impl EcdsaChannelSigner for InMemorySigner { let funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &counterparty_keys.funding_pubkey); let trusted_tx = commitment_tx.trust(); - if commitment_tx.deref().is_colored() - && !self.rgb_backend.is_transaction_durable( - &trusted_tx.built_transaction().txid, - self.rgb_kv_store.as_ref(), - ) { - return Err(()); - } Ok(trusted_tx.built_transaction().sign_holder_commitment( &funding_key, &funding_redeemscript, @@ -2044,18 +2024,6 @@ impl EcdsaChannelSigner for InMemorySigner { &channel_parameters.counterparty_pubkeys().expect(MISSING_PARAMS_ERR).funding_pubkey; let channel_funding_redeemscript = make_funding_redeemscript(&funding_pubkey, counterparty_funding_key); - if closing_tx - .trust() - .built_transaction() - .output - .iter() - .any(|output| output.script_pubkey.is_op_return()) - && !self.rgb_backend.is_transaction_durable( - &closing_tx.trust().built_transaction().compute_txid(), - self.rgb_kv_store.as_ref(), - ) { - return Err(()); - } Ok(closing_tx.trust().sign( &funding_key, &channel_funding_redeemscript, @@ -2219,7 +2187,7 @@ pub struct KeysManager { seed: [u8; 32], starting_time_secs: u64, starting_time_nanos: u32, - rgb_backend: Arc, + ldk_data_dir: PathBuf, rgb_kv_store: Arc, } @@ -2248,7 +2216,7 @@ impl KeysManager { /// [`ChannelMonitor`]: crate::chain::channelmonitor::ChannelMonitor pub fn new( seed: &[u8; 32], starting_time_secs: u64, starting_time_nanos: u32, - v2_remote_key_derivation: bool, rgb_backend: Arc, + v2_remote_key_derivation: bool, ldk_data_dir: PathBuf, rgb_kv_store: Arc, ) -> Self { // Constants for key derivation path indices used in this function. @@ -2343,7 +2311,7 @@ impl KeysManager { seed: *seed, starting_time_secs, starting_time_nanos, - rgb_backend, + ldk_data_dir, rgb_kv_store, }; let secp_seed = res.get_secure_random_bytes(); @@ -2462,7 +2430,7 @@ impl KeysManager { htlc_base_key, commitment_seed, params.clone(), - Arc::clone(&self.rgb_backend), + self.ldk_data_dir.clone(), prng_seed, Arc::clone(&self.rgb_kv_store), ) @@ -2881,7 +2849,7 @@ impl PhantomKeysManager { /// [phantom node payments]: PhantomKeysManager pub fn new( seed: &[u8; 32], starting_time_secs: u64, starting_time_nanos: u32, - cross_node_seed: &[u8; 32], v2_remote_key_derivation: bool, rgb_backend: Arc, + cross_node_seed: &[u8; 32], v2_remote_key_derivation: bool, ldk_data_dir: PathBuf, rgb_kv_store: Arc, ) -> Self { let inner = KeysManager::new( @@ -2889,7 +2857,7 @@ impl PhantomKeysManager { starting_time_secs, starting_time_nanos, v2_remote_key_derivation, - rgb_backend, + ldk_data_dir, rgb_kv_store, ); let (inbound_key, phantom_key) = hkdf_extract_expand_twice( diff --git a/lightning/src/util/time.rs b/lightning/src/util/time.rs index 7d4827623..c60415435 100644 --- a/lightning/src/util/time.rs +++ b/lightning/src/util/time.rs @@ -4,13 +4,13 @@ // You may not use this file except in accordance with one or both of these // licenses. -//! A simple module which either re-exports [`web_time::Instant`] or a mocked version of it for +//! A simple module which either re-exports [`std::time::Instant`] or a mocked version of it for //! tests. +#[cfg(not(test))] +pub use std::time::Instant; #[cfg(test)] pub use test::Instant; -#[cfg(not(test))] -pub use web_time::Instant; #[cfg(test)] mod test { diff --git a/lightning/tests/rgb_native_backend.rs b/lightning/tests/rgb_native_backend.rs deleted file mode 100644 index f3665ccad..000000000 --- a/lightning/tests/rgb_native_backend.rs +++ /dev/null @@ -1,203 +0,0 @@ -use bitcoin::Txid; -use lightning::io; -use lightning::rgb_utils::{ - AssetSchema as SharedAssetSchema, ContractId, NativeRgbBackend, RgbBackendError, - RgbFundingValidation, RgbTransport, RGB_PENDING_FASCIA_NS, RGB_PRIMARY_NS, -}; -use lightning::util::persist::KVStoreSync; -use rgb_lib::keys::{generate_keys, WitnessVersion}; -use rgb_lib::wallet::{DatabaseType, OnlineOptions, SinglesigKeys, Wallet, WalletData}; -use rgb_lib::{AssetSchema, BitcoinNetwork}; -use std::collections::HashMap; -use std::path::PathBuf; -use std::str::FromStr; -use std::sync::Arc; -use std::sync::Mutex; -use std::time::{SystemTime, UNIX_EPOCH}; - -#[derive(Default)] -struct TestStore(Mutex>>); - -impl KVStoreSync for TestStore { - fn read( - &self, primary_namespace: &str, secondary_namespace: &str, key: &str, - ) -> Result, io::Error> { - self.0 - .lock() - .unwrap() - .get(&(primary_namespace.to_owned(), secondary_namespace.to_owned(), key.to_owned())) - .cloned() - .ok_or_else(|| io::Error::from(io::ErrorKind::NotFound)) - } - - fn write( - &self, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: Vec, - ) -> Result<(), io::Error> { - self.0.lock().unwrap().insert( - (primary_namespace.to_owned(), secondary_namespace.to_owned(), key.to_owned()), - buf, - ); - Ok(()) - } - - fn remove( - &self, primary_namespace: &str, secondary_namespace: &str, key: &str, _lazy: bool, - ) -> Result<(), io::Error> { - self.0.lock().unwrap().remove(&( - primary_namespace.to_owned(), - secondary_namespace.to_owned(), - key.to_owned(), - )); - Ok(()) - } - - fn list( - &self, primary_namespace: &str, secondary_namespace: &str, - ) -> Result, io::Error> { - Ok(self - .0 - .lock() - .unwrap() - .keys() - .filter(|(primary, secondary, _)| { - primary == primary_namespace && secondary == secondary_namespace - }) - .map(|(_, _, key)| key.clone()) - .collect()) - } -} - -fn real_native_backend() -> (Arc, PathBuf) { - let unique = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos(); - let data_dir = std::env::temp_dir() - .join(format!("rust-lightning-rgb-native-{}-{unique}", std::process::id())); - let _ = std::fs::remove_dir_all(&data_dir); - std::fs::create_dir_all(&data_dir).unwrap(); - let keys = generate_keys(BitcoinNetwork::Regtest, WitnessVersion::Taproot); - let wallet = Wallet::new( - WalletData { - data_dir: data_dir.to_string_lossy().into_owned(), - bitcoin_network: BitcoinNetwork::Regtest, - database_type: DatabaseType::Sqlite, - max_allocations_per_utxo: 5, - supported_schemas: vec![ - AssetSchema::Nia, - AssetSchema::Cfa, - AssetSchema::Uda, - AssetSchema::Ifa, - ], - reuse_addresses: false, - }, - SinglesigKeys::from_keys(&keys, None), - ) - .unwrap(); - let backend = Arc::new(NativeRgbBackend::new( - wallet, - OnlineOptions { - indexer_url: "http://127.0.0.1:1".to_owned(), - skip_consistency_check: true, - vanilla_sync_lookback: 20, - }, - )); - (backend, data_dir) -} - -#[tokio::test] -async fn native_backend_is_injected_and_reports_real_network_errors() { - let (backend, data_dir) = real_native_backend(); - let result = backend - .accept_funding_transfer( - "0000000000000000000000000000000000000000000000000000000000000000".to_owned(), - 1, - RgbTransport::RestHttp { tls: false, host: "127.0.0.1:1".to_owned() }, - None, - ) - .await; - assert!(matches!( - result, - Err(RgbBackendError::Network(_)) | Err(RgbBackendError::Unexpected(_)) - )); - - std::fs::remove_dir_all(data_dir).unwrap(); -} - -#[tokio::test] -async fn failed_fascia_consumption_remains_pending_and_non_durable() { - let (backend, data_dir) = real_native_backend(); - let store = Arc::new(TestStore::default()); - let txid = - Txid::from_str("0000000000000000000000000000000000000000000000000000000000000001").unwrap(); - store - .write(RGB_PRIMARY_NS, RGB_PENDING_FASCIA_NS, &txid.to_string(), b"invalid".to_vec()) - .unwrap(); - - assert!(!backend.is_transaction_durable(&txid, store.as_ref())); - assert!(backend.has_pending_transactions(store.as_ref()).unwrap()); - assert!(matches!( - backend.process_pending_transactions(store.clone()).await, - Err(RgbBackendError::Unexpected(_)) - )); - assert!(!backend.is_transaction_durable(&txid, store.as_ref())); - assert!(backend.has_pending_transactions(store.as_ref()).unwrap()); - - std::fs::remove_dir_all(data_dir).unwrap(); -} - -#[test] -fn funding_validation_recovery_record_round_trips() { - let validation = RgbFundingValidation { - consignment: vec![1, 2, 3, 4], - contract_id: ContractId::copy_from_slice(&[42; 32]).unwrap(), - schema: SharedAssetSchema::Nia, - received_amount: 1_000, - }; - let encoded = bincode::serialize(&validation).unwrap(); - let decoded: RgbFundingValidation = bincode::deserialize(&encoded).unwrap(); - assert_eq!(decoded, validation); -} - -/// A [`KVStoreSync`] whose mutating operations always fail, used to verify that the RGB -/// persistence helpers propagate the error instead of panicking. This matters for the browser -/// `localStorage` backend, where writes can fail (e.g. quota exceeded) mid-commitment — a panic -/// there would abort the whole WASM module. -struct FailingStore; - -impl KVStoreSync for FailingStore { - fn read(&self, _: &str, _: &str, _: &str) -> Result, io::Error> { - Err(io::Error::new(io::ErrorKind::Other, "simulated read failure")) - } - fn write(&self, _: &str, _: &str, _: &str, _: Vec) -> Result<(), io::Error> { - Err(io::Error::new(io::ErrorKind::Other, "simulated write failure")) - } - fn remove(&self, _: &str, _: &str, _: &str, _: bool) -> Result<(), io::Error> { - Err(io::Error::new(io::ErrorKind::Other, "simulated remove failure")) - } - fn list(&self, _: &str, _: &str) -> Result, io::Error> { - Ok(Vec::new()) - } -} - -#[test] -fn rgb_persistence_helpers_propagate_store_errors_instead_of_panicking() { - use lightning::rgb_utils::{RgbInfo, RgbKvStoreExt, TransferInfo}; - - let store = FailingStore; - let contract_id = ContractId::copy_from_slice(&[7; 32]).unwrap(); - - // These helpers previously `.expect(...)`-panicked on any KVStore failure. They must now - // surface the error so the coloring path closes the channel instead of aborting the node. - let transfer_info = TransferInfo { contract_id, rgb_amount: 100 }; - assert!(store.write_rgb_transfer_info("txid", &transfer_info).is_err()); - assert!(store.read_rgb_transfer_info("txid").is_err()); - - let rgb_info = RgbInfo { - contract_id, - schema: SharedAssetSchema::Nia, - local_rgb_amount: 10, - remote_rgb_amount: 20, - batch_transfer_idx: None, - }; - assert!(store.write_rgb_channel_info("chan", &rgb_info, true).is_err()); - assert!(store.write_rgb_consignment("chan", vec![1, 2, 3]).is_err()); - assert!(store.remove_rgb_consignment("chan").is_err()); -}