From 40f7f34ef39924ac95189fe8f787b0e1a5147980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zoe=20Faltib=C3=A0?= Date: Wed, 15 Jul 2026 16:36:39 +0200 Subject: [PATCH 01/14] lint code with rust 1.97.0 --- lightning/src/ln/channel.rs | 6 +++--- lightning/src/ln/channelmanager.rs | 2 +- lightning/src/ln/inbound_payment.rs | 2 +- lightning/src/ln/outbound_payment.rs | 3 +-- lightning/src/ln/peer_channel_encryptor.rs | 4 ++-- lightning/src/ln/peer_handler.rs | 8 ++++---- lightning/src/routing/gossip.rs | 8 ++++---- 7 files changed, 16 insertions(+), 17 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index a3f8114e3..0558442ba 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -11901,12 +11901,12 @@ where if self.context.secp_ctx.verify_ecdsa(&msghash, &msg.node_signature, &self.context.get_counterparty_node_id()).is_err() { return Err(ChannelError::close(format!( "Bad announcement_signatures. Failed to verify node_signature. UnsignedChannelAnnouncement used for verification is {:?}. their_node_key is {:?}", - &announcement, self.context.get_counterparty_node_id()))); + announcement, self.context.get_counterparty_node_id()))); } if self.context.secp_ctx.verify_ecdsa(&msghash, &msg.bitcoin_signature, self.funding.counterparty_funding_pubkey()).is_err() { return Err(ChannelError::close(format!( "Bad announcement_signatures. Failed to verify bitcoin_signature. UnsignedChannelAnnouncement used for verification is {:?}. their_bitcoin_key is ({:?})", - &announcement, self.funding.counterparty_funding_pubkey()))); + announcement, self.funding.counterparty_funding_pubkey()))); } self.context.announcement_sigs = Some((msg.node_signature, msg.bitcoin_signature)); @@ -13715,7 +13715,7 @@ where debug_assert!(self.funding.funding_transaction.is_none()); self.funding.funding_transaction = Some(funding_transaction); - self.context.is_batch_funding = Some(()).filter(|_| is_batch_funding); + self.context.is_batch_funding = is_batch_funding.then_some(()); let funding_created = self.get_funding_created_msg(logger); Ok(funding_created) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index f0bd1c6bd..1454a6a6a 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -10634,7 +10634,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ "Lost channel state for channel {}.\n\ Received peer storage with a more recent state than what our node had.\n\ Use the FundRecoverer to initiate a force close and sweep the funds.", - &mon_holder.channel_id + mon_holder.channel_id ); } } diff --git a/lightning/src/ln/inbound_payment.rs b/lightning/src/ln/inbound_payment.rs index 17c2526e7..69931b756 100644 --- a/lightning/src/ln/inbound_payment.rs +++ b/lightning/src/ln/inbound_payment.rs @@ -458,7 +458,7 @@ pub(super) fn get_payment_preimage( |bad_preimage_bytes| APIError::APIMisuseError { err: format!( "Payment hash {} did not match decoded preimage {}", - &payment_hash, + payment_hash, log_bytes!(bad_preimage_bytes) ), }, diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 1178b4477..3a4fd4ff9 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -1002,8 +1002,7 @@ impl OutboundPayments { let payment_params = PaymentParameters::from_bolt11_invoice(invoice) .with_user_config_ignoring_fee_limit(route_params_config); - let rgb_payment = - invoice.rgb_amount().and_then(|amt| invoice.rgb_contract_id().map(|cid| (cid, amt))); + let rgb_payment = invoice.rgb_contract_id().zip(invoice.rgb_amount()); let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, amount, rgb_payment); diff --git a/lightning/src/ln/peer_channel_encryptor.rs b/lightning/src/ln/peer_channel_encryptor.rs index 09b970a9a..fbf8193e2 100644 --- a/lightning/src/ln/peer_channel_encryptor.rs +++ b/lightning/src/ln/peer_channel_encryptor.rs @@ -264,7 +264,7 @@ impl PeerChannelEncryptor { let their_pub = match PublicKey::from_slice(&act[1..34]) { Err(_) => { return Err(LightningError { - err: format!("Invalid public key {}", &act[1..34].as_hex()), + err: format!("Invalid public key {}", act[1..34].as_hex()), action: msgs::ErrorAction::DisconnectPeer { msg: None }, }) }, @@ -481,7 +481,7 @@ impl PeerChannelEncryptor { Ok(key) => key, Err(_) => { return Err(LightningError { - err: format!("Bad node_id from peer, {}", &their_node_id.as_hex()), + err: format!("Bad node_id from peer, {}", their_node_id.as_hex()), action: msgs::ErrorAction::DisconnectPeer { msg: None }, }) }, diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs index c68680b7c..8ae548b3b 100644 --- a/lightning/src/ln/peer_handler.rs +++ b/lightning/src/ln/peer_handler.rs @@ -2230,7 +2230,7 @@ where if peer_lock.message_batch.is_some() { let error = format!( "Peer {} sent start_batch for channel {} before previous batch completed", - their_node_id, &msg.channel_id + their_node_id, msg.channel_id ); log_debug!(logger, "{}", error); return Err(LightningError { @@ -2246,7 +2246,7 @@ where if batch_size <= 1 { let error = format!( "Peer {} sent start_batch for channel {} not strictly greater than 1", - their_node_id, &msg.channel_id + their_node_id, msg.channel_id ); log_debug!(logger, "{}", error); return Err(LightningError { @@ -2263,7 +2263,7 @@ where if batch_size > BATCH_SIZE_LIMIT { let error = format!( "Peer {} sent start_batch for channel {} exceeding the limit", - their_node_id, &msg.channel_id + their_node_id, msg.channel_id ); log_debug!(logger, "{}", error); return Err(LightningError { @@ -2303,7 +2303,7 @@ where &mut message_batch.messages; if msg.channel_id != message_batch.channel_id { - let error = format!("Peer {} sent batched commitment_signed for the wrong channel (expected: {}, actual: {})", their_node_id, message_batch.channel_id, &msg.channel_id); + let error = format!("Peer {} sent batched commitment_signed for the wrong channel (expected: {}, actual: {})", their_node_id, message_batch.channel_id, msg.channel_id); log_debug!(logger, "{}", error); return Err(LightningError { err: error.clone(), diff --git a/lightning/src/routing/gossip.rs b/lightning/src/routing/gossip.rs index 2f643a9cc..f063ac080 100644 --- a/lightning/src/routing/gossip.rs +++ b/lightning/src/routing/gossip.rs @@ -1119,9 +1119,9 @@ impl fmt::Display for ChannelInfo { f, "features: {}, node_one: {}, one_to_two: {:?}, node_two: {}, two_to_one: {:?}", log_bytes!(self.features.encode()), - &self.node_one, + self.node_one, self.one_to_two, - &self.node_two, + self.node_two, self.two_to_one )?; Ok(()) @@ -1748,7 +1748,7 @@ where } writeln!(f, "[Nodes]")?; for (&node_id, val) in self.nodes.read().unwrap().unordered_iter() { - writeln!(f, " {}: {}", &node_id, val)?; + writeln!(f, " {}: {}", node_id, val)?; } Ok(()) } @@ -2209,7 +2209,7 @@ where || removed_nodes.contains_key(&msg.node_id_2) { return Err(LightningError{ - err: format!("Channel with SCID {} or one of its nodes was removed from our network graph recently", &msg.short_channel_id), + err: format!("Channel with SCID {} or one of its nodes was removed from our network graph recently", msg.short_channel_id), action: ErrorAction::IgnoreAndLog(Level::Gossip)}); } } From 2cafc7d27a5fffa68043c8243034f9482466f803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zoe=20Faltib=C3=A0?= Date: Fri, 17 Jul 2026 18:03:02 +0200 Subject: [PATCH 02/14] pin rgb-lib to exact version --- lightning-invoice/Cargo.toml | 2 +- lightning/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml index 101a92c76..67c4044c2 100644 --- a/lightning-invoice/Cargo.toml +++ b/lightning-invoice/Cargo.toml @@ -24,7 +24,7 @@ serde = { version = "1.0", optional = true, default-features = false, features = bitcoin = { version = "0.32.2", default-features = false, features = ["secp-recovery"] } # RGB and related -rgb-lib = { version = "0.3.0-beta.6", features = [ +rgb-lib = { version = "=0.3.0-beta.6", features = [ "electrum", "esplora", ] } diff --git a/lightning/Cargo.toml b/lightning/Cargo.toml index e19edeb1b..5cf228ed1 100644 --- a/lightning/Cargo.toml +++ b/lightning/Cargo.toml @@ -53,7 +53,7 @@ inventory = { version = "0.3", optional = true } # RGB and related futures = "0.3" -rgb-lib = { version = "0.3.0-beta.6", features = [ +rgb-lib = { version = "=0.3.0-beta.6", features = [ "electrum", "esplora", ] } From 2a5b63cb0c038c5fa54b9c4b2ccadec2c1e62ae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zoe=20Faltib=C3=A0?= Date: Tue, 23 Jun 2026 15:25:53 +0200 Subject: [PATCH 03/14] consignment/media for LN ops via p2p --- lightning-invoice/Cargo.toml | 2 +- lightning/Cargo.toml | 2 +- lightning/src/ln/channel.rs | 104 ++++++++-------- lightning/src/ln/channelmanager.rs | 10 +- lightning/src/ln/msgs.rs | 57 +++------ lightning/src/rgb_utils/mod.rs | 192 +++++++++++++---------------- 6 files changed, 164 insertions(+), 203 deletions(-) diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml index 67c4044c2..8780e1b79 100644 --- a/lightning-invoice/Cargo.toml +++ b/lightning-invoice/Cargo.toml @@ -24,7 +24,7 @@ serde = { version = "1.0", optional = true, default-features = false, features = bitcoin = { version = "0.32.2", default-features = false, features = ["secp-recovery"] } # RGB and related -rgb-lib = { version = "=0.3.0-beta.6", features = [ +rgb-lib = { version = "=0.3.0-beta.7", features = [ "electrum", "esplora", ] } diff --git a/lightning/Cargo.toml b/lightning/Cargo.toml index 5cf228ed1..e281c5b58 100644 --- a/lightning/Cargo.toml +++ b/lightning/Cargo.toml @@ -53,7 +53,7 @@ inventory = { version = "0.3", optional = true } # RGB and related futures = "0.3" -rgb-lib = { version = "=0.3.0-beta.6", features = [ +rgb-lib = { version = "=0.3.0-beta.7", features = [ "electrum", "esplora", ] } diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 0558442ba..bf0936ca8 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -30,7 +30,7 @@ use bitcoin::secp256k1::{ecdsa::Signature, Secp256k1}; use bitcoin::secp256k1::{PublicKey, SecretKey}; use bitcoin::{secp256k1, sighash, FeeRate, Sequence, TxIn}; -use rgb_lib::{ContractId, RgbTransport}; +use rgb_lib::ContractId; use crate::blinded_path::message::BlindedMessagePath; use crate::chain::chaininterface::{ @@ -79,8 +79,8 @@ use crate::ln::LN_MAX_MSG_LEN; use crate::offers::static_invoice::StaticInvoice; use crate::rgb_utils::{ color_closing, color_commitment, color_htlc, get_rgb_channel_info_path, - get_rgb_channel_info_pending, parse_rgb_channel_info, rename_rgb_files, - update_rgb_channel_amount_pending, + get_rgb_channel_info_pending, is_asset_known, parse_rgb_channel_info, rename_rgb_files, + set_counterparty_knows_asset, update_rgb_channel_amount_pending, }; use crate::routing::gossip::NodeId; use crate::sign::ecdsa::EcdsaChannelSigner; @@ -2358,11 +2358,9 @@ pub(crate) struct FundingScope { /// [`ChannelContext::minimum_depth`]. minimum_depth_override: Option, - /// The consignment endpoint used to exchange the RGB consignment. - pub(super) consignment_endpoint: Option, - - /// The RGB asset amount to push to the counterparty on channel open. - pub(super) push_asset_amount: Option, + /// The RGB asset this channel is for, if it is a colored channel: the contract ID of the asset + /// plus the amount of it pushed to the counterparty on channel open, if any. + pub(super) rgb_asset: Option<(ContractId, Option)>, } impl Writeable for FundingScope { @@ -2377,8 +2375,7 @@ impl Writeable for FundingScope { (13, self.funding_tx_confirmation_height, required), (15, self.short_channel_id, option), (17, self.minimum_depth_override, option), - (19, self.consignment_endpoint, option), - (21, self.push_asset_amount, option), + (21, self.rgb_asset, option), }); Ok(()) } @@ -2396,8 +2393,7 @@ impl Readable for FundingScope { let mut funding_tx_confirmation_height = RequiredWrapper(None); let mut short_channel_id = None; let mut minimum_depth_override = None; - let mut consignment_endpoint = None; - let mut push_asset_amount = None; + let mut rgb_asset: Option<(ContractId, Option)> = None; read_tlv_fields!(reader, { (1, value_to_self_msat, required), @@ -2409,8 +2405,7 @@ impl Readable for FundingScope { (13, funding_tx_confirmation_height, required), (15, short_channel_id, option), (17, minimum_depth_override, option), - (19, consignment_endpoint, option), - (21, push_asset_amount, option), + (21, rgb_asset, option), }); Ok(Self { @@ -2427,8 +2422,7 @@ impl Readable for FundingScope { funding_tx_confirmation_height: funding_tx_confirmation_height.0.unwrap(), short_channel_id, minimum_depth_override, - consignment_endpoint, - push_asset_amount, + rgb_asset, #[cfg(any(test, fuzzing))] next_local_fee: Mutex::new(PredictedNextFee::default()), #[cfg(any(test, fuzzing))] @@ -2446,6 +2440,21 @@ impl FundingScope { self.value_to_self_msat } + /// Whether this is an RGB (colored) channel. + pub(crate) fn is_colored(&self) -> bool { + self.rgb_asset.is_some() + } + + /// The RGB contract ID of the asset this channel is for, if it is a colored channel. + pub(crate) fn contract_id(&self) -> Option { + self.rgb_asset.map(|(contract_id, _)| contract_id) + } + + /// The RGB asset amount pushed to the counterparty on channel open, if any. + pub(crate) fn push_asset_amount(&self) -> Option { + self.rgb_asset.and_then(|(_, push_asset_amount)| push_asset_amount) + } + pub fn get_holder_counterparty_selected_channel_reserve_satoshis(&self) -> (u64, Option) { ( self.holder_selected_channel_reserve_satoshis, @@ -2616,8 +2625,7 @@ impl FundingScope { funding_tx_confirmed_in: None, minimum_depth_override: None, short_channel_id: None, - consignment_endpoint: None, - push_asset_amount: None, + rgb_asset: None, } } @@ -3415,7 +3423,7 @@ where msg_channel_reserve_satoshis: u64, msg_push_msat: u64, open_channel_fields: msgs::CommonOpenChannelFields, - push_asset_amount: Option, + rgb_asset: Option<(ContractId, Option)>, ldk_data_dir: PathBuf, ) -> Result<(FundingScope, ChannelContext), ChannelError> where @@ -3624,8 +3632,7 @@ where funding_tx_confirmation_height: 0, short_channel_id: None, minimum_depth_override: None, - consignment_endpoint: open_channel_fields.consignment_endpoint.clone(), - push_asset_amount, + rgb_asset, }; let channel_context = ChannelContext { user_id, @@ -3739,7 +3746,7 @@ where interactive_tx_signing_session: None, - is_colored: funding.consignment_endpoint.is_some(), + is_colored: funding.is_colored(), ldk_data_dir, }; @@ -3764,9 +3771,8 @@ where channel_keys_id: [u8; 32], holder_signer: ::EcdsaSigner, _logger: L, - consignment_endpoint: Option, + rgb_asset: Option<(ContractId, Option)>, ldk_data_dir: PathBuf, - push_asset_amount: Option, ) -> Result<(FundingScope, ChannelContext), APIError> where ES::Target: EntropySource, @@ -3872,8 +3878,7 @@ where funding_tx_confirmation_height: 0, short_channel_id: None, minimum_depth_override: None, - consignment_endpoint: consignment_endpoint.clone(), - push_asset_amount, + rgb_asset, }; let channel_context = Self { user_id, @@ -3985,7 +3990,7 @@ where interactive_tx_signing_session: None, - is_colored: funding.consignment_endpoint.is_some(), + is_colored: funding.is_colored(), ldk_data_dir, }; @@ -13578,7 +13583,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, ldk_data_dir: PathBuf, push_asset_amount: Option, + outbound_scid_alias: u64, temporary_channel_id: Option, logger: L, rgb_asset: Option<(ContractId, Option)>, ldk_data_dir: PathBuf, ) -> Result, APIError> where ES::Target: EntropySource, F::Target: FeeEstimator, @@ -13616,9 +13621,8 @@ where channel_keys_id, holder_signer, logger, - consignment_endpoint, + rgb_asset, ldk_data_dir, - push_asset_amount, )?; let unfunded_context = UnfundedChannelContext { unfunded_channel_age_ticks: 0, @@ -13796,11 +13800,10 @@ where None => Builder::new().into_script(), }), channel_type: Some(self.funding.get_channel_type().clone()), - consignment_endpoint: self.funding.consignment_endpoint.clone(), }, push_msat: self.funding.get_value_satoshis() * 1000 - self.funding.value_to_self_msat, channel_reserve_satoshis: self.funding.holder_selected_channel_reserve_satoshis, - push_asset_amount: self.funding.push_asset_amount, + rgb_asset: self.funding.rgb_asset, }) } @@ -13815,7 +13818,13 @@ where their_features, &msg.common_fields, msg.channel_reserve_satoshis, - ) + )?; + + if self.funding.is_colored() && msg.known_asset { + set_counterparty_knows_asset(&self.context.channel_id, &self.context.ldk_data_dir); + } + + Ok(()) } /// Handles a funding_signed message from the remote end. @@ -13996,7 +14005,7 @@ where msg.channel_reserve_satoshis, msg.push_msat, msg.common_fields.clone(), - msg.push_asset_amount, + msg.rgb_asset, ldk_data_dir, )?; let unfunded_context = UnfundedChannelContext { @@ -14053,6 +14062,11 @@ where }; let keys = self.funding.get_holder_pubkeys(); + let known_asset = match self.funding.contract_id() { + Some(contract_id) => is_asset_known(contract_id, &self.context.ldk_data_dir), + None => false, + }; + Some(msgs::AcceptChannel { common_fields: msgs::CommonAcceptChannelFields { temporary_channel_id: self.context.channel_id, @@ -14075,6 +14089,7 @@ where channel_type: Some(self.funding.get_channel_type().clone()), }, channel_reserve_satoshis: self.funding.holder_selected_channel_reserve_satoshis, + known_asset, #[cfg(taproot)] next_local_nonce: None, }) @@ -14238,10 +14253,9 @@ where channel_keys_id, holder_signer, logger, - // ok to pass consignment_endpoint as None since this method is unused + // ok to pass rgb_asset as None since this method is unused None, ldk_data_dir, - None, )?; let unfunded_context = UnfundedChannelContext { unfunded_channel_age_ticks: 0, @@ -14333,7 +14347,6 @@ where None => Builder::new().into_script(), }), channel_type: Some(self.funding.get_channel_type().clone()), - consignment_endpoint: self.funding.consignment_endpoint.clone(), }, funding_feerate_sat_per_1000_weight: self.context.feerate_per_kw, second_per_commitment_point, @@ -15081,8 +15094,7 @@ where (65, self.quiescent_action, option), // Added in 0.2 (67, pending_outbound_held_htlc_flags, optional_vec), // Added in 0.2 (69, holding_cell_held_htlc_flags, optional_vec), // Added in 0.2 - (71, self.funding.consignment_endpoint, option), - (73, self.funding.push_asset_amount, option), + (73, self.funding.rgb_asset, option), }); Ok(()) @@ -15418,8 +15430,7 @@ where let mut channel_keys_id = [0u8; 32]; let mut temporary_channel_id: Option = None; let mut holder_max_accepted_htlcs: Option = None; - let mut consignment_endpoint: Option = None; - let mut push_asset_amount: Option = None; + let mut rgb_asset: Option<(ContractId, Option)> = None; let mut blocked_monitor_updates = Some(Vec::new()); @@ -15502,8 +15513,7 @@ where (65, quiescent_action, upgradable_option), // Added in 0.2 (67, pending_outbound_held_htlc_flags_opt, optional_vec), // Added in 0.2 (69, holding_cell_held_htlc_flags_opt, optional_vec), // Added in 0.2 - (71, consignment_endpoint, option), - (73, push_asset_amount, option), + (73, rgb_asset, option), }); let holder_signer = signer_provider.derive_channel_signer(channel_keys_id); @@ -15782,8 +15792,7 @@ where funding_tx_confirmation_height, short_channel_id, minimum_depth_override, - consignment_endpoint: consignment_endpoint.clone(), - push_asset_amount, + rgb_asset, }, context: ChannelContext { user_id, @@ -15895,7 +15904,7 @@ where is_manual_broadcast: is_manual_broadcast.unwrap_or(false), interactive_tx_signing_session, - is_colored: consignment_endpoint.is_some(), + is_colored: rgb_asset.is_some(), ldk_data_dir, }, holder_commitment_point, @@ -17886,8 +17895,7 @@ mod tests { funding_tx_confirmation_height: 0, short_channel_id: None, minimum_depth_override: None, - consignment_endpoint: None, - push_asset_amount: None, + rgb_asset: None, }; let post_channel_value = funding.compute_post_splice_value(our_funding_contribution, their_funding_contribution); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 1454a6a6a..6a9a2cc32 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -36,7 +36,7 @@ use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1::{PublicKey, SecretKey}; use bitcoin::{secp256k1, Sequence, SignedAmount}; -use rgb_lib::{ContractId, RgbTransport}; +use rgb_lib::ContractId; use crate::blinded_path::message::{ AsyncPaymentsContext, BlindedMessagePath, MessageForwardNode, OffersContext, @@ -4144,7 +4144,7 @@ where /// [`Event::FundingGenerationReady::temporary_channel_id`]: events::Event::FundingGenerationReady::temporary_channel_id /// [`Event::ChannelClosed::channel_id`]: events::Event::ChannelClosed::channel_id #[rustfmt::skip] - pub fn create_channel(&self, their_network_key: PublicKey, channel_value_satoshis: u64, push_msat: u64, user_channel_id: u128, temporary_channel_id: Option, override_config: Option, consignment_endpoint: Option, push_asset_amount: Option) -> Result { + pub fn create_channel(&self, their_network_key: PublicKey, channel_value_satoshis: u64, push_msat: u64, user_channel_id: u128, temporary_channel_id: Option, override_config: Option, rgb_asset: Option<(ContractId, Option)>) -> Result { if channel_value_satoshis < 1000 { return Err(APIError::APIMisuseError { err: format!("Channel value must be at least 1000 satoshis. It was {}", channel_value_satoshis) }); } @@ -4180,7 +4180,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, self.ldk_data_dir.clone(), push_asset_amount) + self.best_block.read().unwrap().height, outbound_scid_alias, temporary_channel_id, &*self.logger, rgb_asset, self.ldk_data_dir.clone()) { Ok(res) => res, Err(e) => { @@ -10463,8 +10463,8 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ { 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) { + if inbound_chan.funding.is_colored() { + match handle_funding(&msg.temporary_channel_id, msg.funding_txid.to_string(), &self.ldk_data_dir, inbound_chan.funding.push_asset_amount()) { Ok(()) => (), Err(e) => { // at this point the channel initiator already transitioned its channel to the funded channel ID diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 3f92baefa..bbd8522f7 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 rgb_lib::{ContractId, RgbTransport}; +use rgb_lib::ContractId; use crate::blinded_path::message::BlindedMessagePath; use crate::blinded_path::payment::{ @@ -247,8 +247,6 @@ pub struct CommonOpenChannelFields { /// The channel type that this channel will represent. As defined in the latest /// specification, this field is required. However, it is an `Option` for legacy reasons. pub channel_type: Option, - /// The consignment endpoint used to exchange the RGB consignment - pub consignment_endpoint: Option, } impl CommonOpenChannelFields { @@ -299,8 +297,9 @@ pub struct OpenChannel { pub push_msat: u64, /// The minimum value unencumbered by HTLCs for the counterparty to keep in the channel pub channel_reserve_satoshis: u64, - /// The amount of RGB assets to push to the counterparty as part of the open. - pub push_asset_amount: Option, + /// The RGB asset this channel is for, if it is a colored channel: the contract ID of the asset + /// plus the amount of it to push to the counterparty as part of the open, if any. + pub rgb_asset: Option<(ContractId, Option)>, } /// An [`open_channel2`] message to be sent by or received from the channel initiator. @@ -379,6 +378,9 @@ pub struct AcceptChannel { pub common_fields: CommonAcceptChannelFields, /// The minimum value unencumbered by HTLCs for the counterparty to keep in the channel pub channel_reserve_satoshis: u64, + /// Whether we already know the RGB asset offered in [`OpenChannel::contract_id`], i.e. we hold + /// its contract and every media file it declares. + pub known_asset: bool, #[cfg(taproot)] /// Next nonce the channel initiator should use to create a funding output signature against pub next_local_nonce: Option, @@ -2664,15 +2666,18 @@ impl Writeable for AcceptChannel { self.common_fields.delayed_payment_basepoint.write(w)?; self.common_fields.htlc_basepoint.write(w)?; self.common_fields.first_per_commitment_point.write(w)?; + let known_asset_marker = if self.known_asset { Some(()) } else { None }; #[cfg(not(taproot))] encode_tlv_stream!(w, { (0, self.common_fields.shutdown_scriptpubkey.as_ref().map(|s| WithoutLength(s)), option), // Don't encode length twice. (1, self.common_fields.channel_type, option), + (3, known_asset_marker, option), }); #[cfg(taproot)] encode_tlv_stream!(w, { (0, self.common_fields.shutdown_scriptpubkey.as_ref().map(|s| WithoutLength(s)), option), // Don't encode length twice. (1, self.common_fields.channel_type, option), + (3, known_asset_marker, option), (4, self.next_local_nonce, option), }); Ok(()) @@ -2698,10 +2703,12 @@ impl LengthReadable for AcceptChannel { let mut shutdown_scriptpubkey: Option = None; let mut channel_type: Option = None; + let mut known_asset_marker: Option<()> = None; #[cfg(not(taproot))] decode_tlv_stream!(r, { (0, shutdown_scriptpubkey, (option, encoding: (ScriptBuf, WithoutLength))), (1, channel_type, option), + (3, known_asset_marker, option), }); #[cfg(taproot)] let mut next_local_nonce: Option = None; @@ -2709,6 +2716,7 @@ impl LengthReadable for AcceptChannel { decode_tlv_stream!(r, { (0, shutdown_scriptpubkey, (option, encoding: (ScriptBuf, WithoutLength))), (1, channel_type, option), + (3, known_asset_marker, option), (4, next_local_nonce, option), }); @@ -2731,6 +2739,7 @@ impl LengthReadable for AcceptChannel { channel_type, }, channel_reserve_satoshis, + known_asset: known_asset_marker.is_some(), #[cfg(taproot)] next_local_nonce, }) @@ -3136,8 +3145,7 @@ impl Writeable for OpenChannel { encode_tlv_stream!(w, { (0, self.common_fields.shutdown_scriptpubkey.as_ref().map(|s| WithoutLength(s)), option), // Don't encode length twice. (1, self.common_fields.channel_type, option), - (2, self.common_fields.consignment_endpoint, option), - (3, self.push_asset_amount, option), + (3, self.rgb_asset, option), }); Ok(()) } @@ -3166,13 +3174,11 @@ impl LengthReadable for OpenChannel { let mut shutdown_scriptpubkey: Option = None; let mut channel_type: Option = None; - let mut consignment_endpoint: Option = None; - let mut push_asset_amount: Option = None; + let mut rgb_asset: Option<(ContractId, Option)> = None; decode_tlv_stream!(r, { (0, shutdown_scriptpubkey, (option, encoding: (ScriptBuf, WithoutLength))), (1, channel_type, option), - (2, consignment_endpoint, option), - (3, push_asset_amount, option), + (3, rgb_asset, option), }); Ok(OpenChannel { common_fields: CommonOpenChannelFields { @@ -3194,11 +3200,10 @@ impl LengthReadable for OpenChannel { channel_flags, shutdown_scriptpubkey, channel_type, - consignment_endpoint, }, push_msat, channel_reserve_satoshis, - push_asset_amount, + rgb_asset, }) } } @@ -3228,7 +3233,6 @@ impl Writeable for OpenChannelV2 { (0, self.common_fields.shutdown_scriptpubkey.as_ref().map(|s| WithoutLength(s)), option), // Don't encode length twice. (1, self.common_fields.channel_type, option), (2, self.require_confirmed_inputs, option), - (3, self.common_fields.consignment_endpoint, option), }); Ok(()) } @@ -3259,12 +3263,10 @@ impl LengthReadable for OpenChannelV2 { let mut shutdown_scriptpubkey: Option = None; let mut channel_type: Option = None; let mut require_confirmed_inputs: Option<()> = None; - let mut consignment_endpoint: Option = None; decode_tlv_stream!(r, { (0, shutdown_scriptpubkey, (option, encoding: (ScriptBuf, WithoutLength))), (1, channel_type, option), (2, require_confirmed_inputs, option), - (3, consignment_endpoint, option), }); Ok(OpenChannelV2 { common_fields: CommonOpenChannelFields { @@ -3286,7 +3288,6 @@ impl LengthReadable for OpenChannelV2 { channel_flags, shutdown_scriptpubkey, channel_type, - consignment_endpoint, }, funding_feerate_sat_per_1000_weight, locktime, @@ -3296,28 +3297,6 @@ impl LengthReadable for OpenChannelV2 { } } -impl Readable for RgbTransport { - fn read(r: &mut R) -> Result { - let sz: usize = ::read(r)? as usize; - let mut consignment_endpoint_str_vec = Vec::with_capacity(sz); - consignment_endpoint_str_vec.resize(sz, 0); - r.read_exact(&mut consignment_endpoint_str_vec)?; - match String::from_utf8(consignment_endpoint_str_vec) { - Ok(s) => return Ok(RgbTransport::from_str(&s).unwrap()), - Err(_) => return Err(DecodeError::InvalidValue), - } - } -} - -impl Writeable for RgbTransport { - fn write(&self, w: &mut W) -> Result<(), io::Error> { - let consignment_endpoint_str = format!("{self}"); - (consignment_endpoint_str.len() as u16).write(w)?; - w.write_all(consignment_endpoint_str.as_bytes())?; - Ok(()) - } -} - #[cfg(not(taproot))] impl_writeable_msg!(RevokeAndACK, { channel_id, diff --git a/lightning/src/rgb_utils/mod.rs b/lightning/src/rgb_utils/mod.rs index 52d79e38a..e3c99aa6a 100644 --- a/lightning/src/rgb_utils/mod.rs +++ b/lightning/src/rgb_utils/mod.rs @@ -12,41 +12,33 @@ use crate::types::features::ChannelTypeFeatures; use crate::types::payment::PaymentHash; use bitcoin::blockdata::transaction::Transaction; +use bitcoin::hashes::{sha256, Hash}; use bitcoin::hex::DisplayHex; use bitcoin::psbt::{ExtractTxError, Psbt}; use bitcoin::secp256k1::PublicKey; use bitcoin::TxOut; use rgb_lib::{ bitcoin::psbt::Psbt as RgbLibPsbt, - keys::WitnessVersion, wallet::{ rust_only::{AssetColoringInfo, ColoringInfo}, - DatabaseType, OnlineOptions, SinglesigKeys, Wallet, WalletData, + OnlineOptions, RgbWalletOpsOffline, Wallet, }, - AssetSchema, Assignment, BitcoinNetwork, ConsignmentExt, ContractId, Error as RgbLibError, - FileContent, RgbTransfer, RgbTransport, WitnessOrd, + AssetSchema, Assignment, ConsignmentExt, ContractId, Error as RgbLibError, RgbTransfer, + WitnessOrd, }; use serde::{Deserialize, Serialize}; use tokio::runtime::Handle; use core::ops::Deref; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fs; use std::path::{Path, PathBuf}; use std::str::FromStr; /// Static blinding costant (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"; const INBOUND_EXT: &str = "inbound"; @@ -68,6 +60,9 @@ pub struct RgbInfo { /// Batch transfer index from rgb-lib (set after rgb_send_begin) #[serde(default, skip_serializing_if = "Option::is_none")] pub batch_transfer_idx: Option, + /// Whether the channel acceptor told us (in `accept_channel`) that it already knows the asset + #[serde(default)] + pub counterparty_knows_asset: bool, } /// RGB payment info @@ -127,24 +122,6 @@ fn _read_file_in_parent(ldk_data_dir: &Path, fname: &str) -> String { fs::read_to_string(_get_file_in_parent(ldk_data_dir, fname)).unwrap() } -fn _get_rgb_wallet_dir(ldk_data_dir: &Path) -> PathBuf { - let fingerprint = _read_file_in_parent(ldk_data_dir, WALLET_FINGERPRINT_FNAME); - _get_file_in_parent(ldk_data_dir, &fingerprint) -} - -fn _get_bitcoin_network(ldk_data_dir: &Path) -> BitcoinNetwork { - let bitcoin_network = _read_file_in_parent(ldk_data_dir, BITCOIN_NETWORK_FNAME); - BitcoinNetwork::from_str(&bitcoin_network).unwrap() -} - -fn _get_account_xpub_colored(ldk_data_dir: &Path) -> String { - _read_file_in_parent(ldk_data_dir, WALLET_ACCOUNT_XPUB_COLORED_FNAME) -} - -fn _get_account_xpub_vanilla(ldk_data_dir: &Path) -> String { - _read_file_in_parent(ldk_data_dir, WALLET_ACCOUNT_XPUB_VANILLA_FNAME) -} - fn _get_master_fingerprint(ldk_data_dir: &Path) -> String { _read_file_in_parent(ldk_data_dir, WALLET_MASTER_FINGERPRINT_FNAME) } @@ -153,87 +130,53 @@ fn _get_indexer_url(ldk_data_dir: &Path) -> String { _read_file_in_parent(ldk_data_dir, INDEXER_URL_FNAME) } -fn _new_rgb_wallet( - data_dir: String, bitcoin_network: BitcoinNetwork, account_xpub_vanilla: String, - account_xpub_colored: String, master_fingerprint: String, -) -> 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, - ], - }, - keys, - ) - .expect("valid rgb-lib wallet") +fn _load_rgb_wallet(data_dir: String, master_fingerprint: String) -> Wallet { + Wallet::load(&data_dir, &master_fingerprint, None).expect("valid rgb-lib wallet") } -fn _get_wallet_data(ldk_data_dir: &Path) -> (String, BitcoinNetwork, String, String, String) { +fn _get_wallet_data(ldk_data_dir: &Path) -> (String, String) { let data_dir = ldk_data_dir.parent().unwrap().to_string_lossy().to_string(); - let bitcoin_network = _get_bitcoin_network(ldk_data_dir); - let account_xpub_vanilla = _get_account_xpub_vanilla(ldk_data_dir); - let account_xpub_colored = _get_account_xpub_colored(ldk_data_dir); let master_fingerprint = _get_master_fingerprint(ldk_data_dir); - (data_dir, bitcoin_network, account_xpub_vanilla, account_xpub_colored, master_fingerprint) + (data_dir, master_fingerprint) } async fn _get_rgb_wallet(ldk_data_dir: &Path) -> Wallet { - let (data_dir, bitcoin_network, account_xpub_vanilla, account_xpub_colored, master_fingerprint) = - _get_wallet_data(ldk_data_dir); - tokio::task::spawn_blocking(move || { - _new_rgb_wallet( - data_dir, - bitcoin_network, - account_xpub_vanilla, - account_xpub_colored, - master_fingerprint, - ) - }) - .await - .unwrap() + let (data_dir, master_fingerprint) = _get_wallet_data(ldk_data_dir); + tokio::task::spawn_blocking(move || _load_rgb_wallet(data_dir, master_fingerprint)) + .await + .unwrap() +} + +pub(crate) fn is_asset_known(contract_id: ContractId, ldk_data_dir: &Path) -> bool { + let handle = Handle::current(); + let _ = handle.enter(); + let wallet = futures::executor::block_on(_get_rgb_wallet(ldk_data_dir)); + wallet.is_asset_known(contract_id).unwrap_or(false) } async fn _accept_transfer( - ldk_data_dir: &Path, funding_txid: String, consignment_endpoint: RgbTransport, -) -> Result<(RgbTransfer, Vec), RgbLibError> { + ldk_data_dir: &Path, funding_txid: String, +) -> Result<(RgbTransfer, Vec, HashSet, PathBuf), RgbLibError> { let funding_vout = 1; - let (data_dir, bitcoin_network, account_xpub_vanilla, account_xpub_colored, master_fingerprint) = - _get_wallet_data(ldk_data_dir); + let (data_dir, master_fingerprint) = _get_wallet_data(ldk_data_dir); let indexer_url = _get_indexer_url(ldk_data_dir); + // the consignment is received from the channel counterparty over the p2p link and written to disk + let consignment_path = ldk_data_dir.join(format!("consignment_{funding_txid}")); tokio::task::spawn_blocking(move || { - let mut wallet = _new_rgb_wallet( - data_dir, - bitcoin_network, - account_xpub_vanilla, - account_xpub_colored, - master_fingerprint, - ); - wallet.go_online(OnlineOptions { + let mut wallet = _load_rgb_wallet(data_dir, master_fingerprint); + let online = wallet.go_online(OnlineOptions { indexer_url, skip_consistency_check: true, vanilla_sync_lookback: VANILLA_SYNC_LOOKBACK, })?; - wallet.accept_transfer( + let (consignment, assignments, media_digests) = wallet.accept_transfer_consignment( + online, + consignment_path, funding_txid.clone(), funding_vout, - consignment_endpoint, STATIC_BLINDING, - ) + )?; + Ok((consignment, assignments, media_digests, wallet.get_media_dir())) }) .await .unwrap() @@ -645,27 +588,23 @@ pub(crate) fn rename_rgb_files( get_rgb_channel_info_path(&chan_id, ldk_data_dir, true), ) .expect("rename ok"); +} - let funding_consignment_tmp = ldk_data_dir.join(format!("consignment_{}", temp_chan_id)); - if funding_consignment_tmp.exists() { - let funding_consignment = ldk_data_dir.join(format!("consignment_{}", chan_id)); - fs::rename(funding_consignment_tmp, funding_consignment).expect("rename ok"); - } +/// Directory holding the media received for a funding, before the contract has vouched for it. +pub fn get_media_staging_dir(ldk_data_dir: &Path, funding_txid: &str) -> PathBuf { + ldk_data_dir.join(format!("media_staging_{funding_txid}")) } /// 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, + push_asset_amount: Option, ) -> Result<(), ChannelError> { let handle = Handle::current(); let _ = handle.enter(); - let accept_res = futures::executor::block_on(_accept_transfer( - ldk_data_dir, - funding_txid.clone(), - consignment_endpoint, - )); - let (consignment, remote_rgb_assignments) = match accept_res { + let accept_res = + futures::executor::block_on(_accept_transfer(ldk_data_dir, funding_txid.clone())); + let (consignment, remote_rgb_assignments, media_digests, media_dir) = match accept_res { Ok(res) => res, Err(RgbLibError::InvalidConsignment) => { return Err(ChannelError::close("Invalid RGB consignment for funding".to_owned())) @@ -687,11 +626,31 @@ pub(crate) fn handle_funding( Err(e) => return Err(ChannelError::close(format!("Unexpected error: {e}"))), }; - let consignment_path = ldk_data_dir.join(format!("consignment_{}", funding_txid)); - consignment.save_file(consignment_path).expect("unable to write file"); - let consignment_path = - ldk_data_dir.join(format!("consignment_{}", temporary_channel_id.0.as_hex())); - consignment.save_file(consignment_path).expect("unable to write file"); + let staging_dir = get_media_staging_dir(ldk_data_dir, &funding_txid); + for digest in media_digests { + let media_path = media_dir.join(&digest); + if media_path.exists() { + continue; + } + let staged_path = staging_dir.join(&digest); + let Ok(media_bytes) = fs::read(&staged_path) else { + return Err(ChannelError::close(format!( + "Missing RGB media file {digest} for funding" + ))); + }; + if sha256::Hash::hash(&media_bytes).to_string() != digest { + return Err(ChannelError::close(format!( + "Corrupt RGB media file {digest} for funding" + ))); + } + if let Err(e) = fs::rename(&staged_path, &media_path) { + return Err(ChannelError::close(format!( + "Failed to store RGB media file {digest} for funding: {e}" + ))); + } + } + // on the error paths above the staging directory is left for the file transfer handler's sweep + let _ = fs::remove_dir_all(&staging_dir); if remote_rgb_assignments.len() != 1 { return Err(ChannelError::close(format!( @@ -711,6 +670,8 @@ pub(crate) fn handle_funding( local_rgb_amount: push_amount, remote_rgb_amount: channel_rgb_amount - push_amount, batch_transfer_idx: None, + // only meaningful on the initiator side, which is the one that sends media + counterparty_knows_asset: false, }; let temporary_channel_id_str = temporary_channel_id.0.as_hex().to_string(); write_rgb_channel_info( @@ -725,6 +686,19 @@ pub(crate) fn handle_funding( Ok(()) } +pub(crate) fn set_counterparty_knows_asset(channel_id: &ChannelId, ldk_data_dir: &Path) { + let channel_id = channel_id.0.as_hex().to_string(); + for pending in [true, false] { + let info_file_path = get_rgb_channel_info_path(&channel_id, ldk_data_dir, pending); + if !info_file_path.exists() { + continue; + } + let mut rgb_info = parse_rgb_channel_info(&info_file_path); + rgb_info.counterparty_knows_asset = true; + write_rgb_channel_info(&info_file_path, &rgb_info); + } +} + /// Update RGB channel amount pub fn update_rgb_channel_amount( channel_id: &str, rgb_offered_htlc: u64, rgb_received_htlc: u64, ldk_data_dir: &Path, From c675550b2709350eda0919b40f0588266d749841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zoe=20Faltib=C3=A0?= Date: Tue, 21 Jul 2026 18:27:20 +0200 Subject: [PATCH 04/14] TransferInfo: track output_map instead of rgb_amount --- lightning/src/rgb_utils/mod.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lightning/src/rgb_utils/mod.rs b/lightning/src/rgb_utils/mod.rs index e3c99aa6a..2973bc762 100644 --- a/lightning/src/rgb_utils/mod.rs +++ b/lightning/src/rgb_utils/mod.rs @@ -89,8 +89,8 @@ pub struct TransferInfo { /// Transfer contract ID #[serde(with = "contract_id_serde")] pub contract_id: ContractId, - /// Transfer RGB amount - pub rgb_amount: u64, + /// RGB amount assigned to each output of the transaction, by vout + pub output_map: HashMap, } mod contract_id_serde { @@ -333,8 +333,10 @@ where output_map.insert(vout_p2wsh as u32, vout_p2wsh_amt); } - let asset_coloring_info = - AssetColoringInfo { output_map, static_blinding: Some(STATIC_BLINDING) }; + let asset_coloring_info = AssetColoringInfo { + output_map: output_map.clone(), + static_blinding: Some(STATIC_BLINDING), + }; let coloring_info = ColoringInfo { asset_info_map: HashMap::from_iter([(contract_id, asset_coloring_info)]), static_blinding: Some(STATIC_BLINDING), @@ -359,12 +361,7 @@ where wallet.consume_fascia(fascia.clone(), Some(WitnessOrd::Ignored)).unwrap(); // save RGB transfer data to disk - 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 }; + let transfer_info = TransferInfo { contract_id, output_map }; let transfer_info_path = ldk_data_dir.join(format!("{txid}_transfer_info")); write_rgb_transfer_info(&transfer_info_path, &transfer_info); @@ -387,8 +384,9 @@ pub(crate) fn color_htlc( let transfer_info = read_rgb_transfer_info(&transfer_info_path); let contract_id = transfer_info.contract_id; + let output_map = HashMap::from([(0, htlc_amount_rgb)]); let asset_coloring_info = AssetColoringInfo { - output_map: HashMap::from([(0, htlc_amount_rgb)]), + output_map: output_map.clone(), static_blinding: Some(STATIC_BLINDING), }; let coloring_info = ColoringInfo { @@ -413,7 +411,7 @@ pub(crate) fn color_htlc( wallet.consume_fascia(fascia.clone(), Some(WitnessOrd::Ignored)).unwrap(); // save RGB transfer data to disk - let transfer_info = TransferInfo { contract_id, rgb_amount: htlc_amount_rgb }; + let transfer_info = TransferInfo { contract_id, output_map }; let transfer_info_path = ldk_data_dir.join(format!("{txid}_transfer_info")); write_rgb_transfer_info(&transfer_info_path, &transfer_info); @@ -452,8 +450,10 @@ pub(crate) fn color_closing( output_map.insert(counterparty_vout as u32, counterparty_vout_amount); } - let asset_coloring_info = - AssetColoringInfo { output_map, static_blinding: Some(STATIC_BLINDING) }; + let asset_coloring_info = AssetColoringInfo { + output_map: output_map.clone(), + static_blinding: Some(STATIC_BLINDING), + }; let coloring_info = ColoringInfo { asset_info_map: HashMap::from_iter([(contract_id, asset_coloring_info)]), static_blinding: Some(STATIC_BLINDING), @@ -478,7 +478,7 @@ pub(crate) fn color_closing( wallet.consume_fascia(fascia.clone(), Some(WitnessOrd::Ignored)).unwrap(); // save RGB transfer data to disk - let transfer_info = TransferInfo { contract_id, rgb_amount: holder_vout_amount }; + let transfer_info = TransferInfo { contract_id, output_map }; let transfer_info_path = ldk_data_dir.join(format!("{txid}_transfer_info")); write_rgb_transfer_info(&transfer_info_path, &transfer_info); From e0e7aabe5ce000c247340055b66a146f34c6471c Mon Sep 17 00:00:00 2001 From: dcorral Date: Fri, 24 Jul 2026 13:24:37 +0200 Subject: [PATCH 05/14] write rgb_payment for pending HTLCs to match deserialization --- lightning/src/ln/channel.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index bf0936ca8..79ab65373 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -14749,6 +14749,7 @@ where } }, } + htlc.rgb_payment.write(writer)?; } // The elements of this vector will always be `Some` starting in 0.2, @@ -14798,6 +14799,7 @@ where reason.write(writer)?; }, } + htlc.rgb_payment.write(writer)?; pending_outbound_skimmed_fees.push(htlc.skimmed_fee_msat); pending_outbound_blinding_points.push(htlc.blinding_point); pending_outbound_held_htlc_flags.push(htlc.hold_htlc); From dd15b5385b3c46f5f9d3ef167dfe972fde966dfd Mon Sep 17 00:00:00 2001 From: 0xaudron Date: Tue, 28 Jul 2026 10:31:04 +0600 Subject: [PATCH 06/14] Reject out-of-range push_asset_amount in handle_funding --- lightning/src/rgb_utils/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lightning/src/rgb_utils/mod.rs b/lightning/src/rgb_utils/mod.rs index 2973bc762..b83579ad3 100644 --- a/lightning/src/rgb_utils/mod.rs +++ b/lightning/src/rgb_utils/mod.rs @@ -664,11 +664,16 @@ pub(crate) fn handle_funding( _ => unreachable!("unsupported schema"), }; let push_amount = push_asset_amount.unwrap_or(0); + let remote_rgb_amount = channel_rgb_amount.checked_sub(push_amount).ok_or_else(|| { + ChannelError::close(format!( + "push_asset_amount {push_amount} exceeds channel asset amount {channel_rgb_amount}" + )) + })?; let rgb_info = RgbInfo { contract_id: consignment.contract_id(), schema: AssetSchema::from_schema_id(consignment.schema_id()).unwrap(), local_rgb_amount: push_amount, - remote_rgb_amount: channel_rgb_amount - push_amount, + remote_rgb_amount, batch_transfer_idx: None, // only meaningful on the initiator side, which is the one that sends media counterparty_knows_asset: false, From 66884165edfc26f9c1ec80d9a310fd948e49bb1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zoe=20Faltib=C3=A0?= Date: Tue, 4 Aug 2026 18:24:09 +0200 Subject: [PATCH 07/14] use rgb-lib electrum/esplora features --- .github/workflows/build.yml | 2 +- ci/check-lint.sh | 2 +- lightning-invoice/Cargo.toml | 5 +---- lightning/Cargo.toml | 8 ++++---- lightning/src/rgb_utils/mod.rs | 5 +++++ 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0ebb1bf72..cb0cf6d5a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,7 +37,7 @@ jobs: run: echo "RUSTFLAGS=-C linker=rust-lld" >> "$GITHUB_ENV" - name: Build the workspace shell: bash # Default on Winblows is powershell - run: cargo check --workspace --verbose --color always + run: cargo check --workspace --features lightning/electrum --verbose --color always linting: runs-on: ubuntu-latest diff --git a/ci/check-lint.sh b/ci/check-lint.sh index 9f4ec48e4..a0a15e815 100755 --- a/ci/check-lint.sh +++ b/ci/check-lint.sh @@ -114,4 +114,4 @@ CLIPPY() { -A clippy::uninlined-format-args } -CLIPPY +CLIPPY "--features lightning/electrum" diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml index 8780e1b79..dc6369cb9 100644 --- a/lightning-invoice/Cargo.toml +++ b/lightning-invoice/Cargo.toml @@ -24,10 +24,7 @@ serde = { version = "1.0", optional = true, default-features = false, features = bitcoin = { version = "0.32.2", default-features = false, features = ["secp-recovery"] } # RGB and related -rgb-lib = { version = "=0.3.0-beta.7", features = [ - "electrum", - "esplora", -] } +rgb-lib = { version = "=0.3.0-beta.7", default-features = false } [dev-dependencies] serde_json = { version = "1"} diff --git a/lightning/Cargo.toml b/lightning/Cargo.toml index e281c5b58..350478414 100644 --- a/lightning/Cargo.toml +++ b/lightning/Cargo.toml @@ -28,6 +28,9 @@ std = [] dnssec = ["dnssec-prover/validation"] +electrum = ["rgb-lib/electrum"] +esplora = ["rgb-lib/esplora"] + # Generates low-r bitcoin signatures, which saves 1 byte in 50% of the cases grind_signatures = [] @@ -53,10 +56,7 @@ inventory = { version = "0.3", optional = true } # RGB and related futures = "0.3" -rgb-lib = { version = "=0.3.0-beta.7", features = [ - "electrum", - "esplora", -] } +rgb-lib = { version = "=0.3.0-beta.7", default-features = false } serde = { version = "^1.0", features = [ "derive", ] } diff --git a/lightning/src/rgb_utils/mod.rs b/lightning/src/rgb_utils/mod.rs index b83579ad3..922550885 100644 --- a/lightning/src/rgb_utils/mod.rs +++ b/lightning/src/rgb_utils/mod.rs @@ -1,5 +1,10 @@ //! A module to provide RGB functionality +// this module uses the online APIs of rgb-lib, which are only available if rgb-lib has been built +// with support for at least one indexer protocol +#[cfg(not(any(feature = "electrum", feature = "esplora")))] +compile_error!("at least one of the `electrum` and `esplora` features needs to be enabled"); + use crate::ln::chan_utils::{ get_countersigner_payment_script, BuiltCommitmentTransaction, ClosingTransaction, CommitmentTransaction, HTLCOutputInCommitment, From e2a0b8e24dc919ccef289f103fd1f8e1974fcc1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zoe=20Faltib=C3=A0?= Date: Fri, 7 Aug 2026 22:28:00 +0200 Subject: [PATCH 08/14] fix(electrum): do not pick unindexed outputs for history lookup --- lightning-transaction-sync/src/electrum.rs | 61 ++++++++++++++++++---- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/lightning-transaction-sync/src/electrum.rs b/lightning-transaction-sync/src/electrum.rs index 1162b9c00..c42da5d67 100644 --- a/lightning-transaction-sync/src/electrum.rs +++ b/lightning-transaction-sync/src/electrum.rs @@ -18,6 +18,8 @@ use lightning::util::logger::Logger; use lightning::{log_debug, log_error, log_trace}; use bitcoin::block::Header; +use bitcoin::opcodes::all::OP_RETURN; +use bitcoin::opcodes::OP_FALSE; use bitcoin::{BlockHash, Script, Transaction, Txid}; use std::collections::HashSet; @@ -289,16 +291,55 @@ where continue; } - watched_txs.push((txid, tx.clone())); - if let Some(tx_out) = tx.output.first() { - // We watch an arbitrary output of the transaction of interest in order to - // retrieve the associated script history, before narrowing down our search - // through `filter`ing by `txid` below. - watched_script_pubkeys.push(tx_out.script_pubkey.clone()); - } else { - debug_assert!(false, "Failed due to retrieving invalid tx data."); - log_error!(self.logger, "Failed due to retrieving invalid tx data."); - return Err(InternalError::Failed); + // We watch an arbitrary output of the transaction of interest in order to + // retrieve the associated script history, before narrowing down our search + // through `filter`ing by `txid` below. Electrum servers don't index + // provably-unspendable outputs, so picking one of those would always yield an + // empty history. + let mut spk = tx + .output + .iter() + .find(|txo| { + !txo.script_pubkey.is_op_return() + && !txo + .script_pubkey + .as_bytes() + .starts_with(&[OP_FALSE.to_u8(), OP_RETURN.to_u8()]) + }) + .map(|txo| txo.script_pubkey.clone()); + + // Fall back to the script of an input's previous output: its history includes + // this transaction, as we spend from it. + if spk.is_none() && !tx.is_coinbase() { + for txin in &tx.input { + match self.client.transaction_get(&txin.previous_output.txid) { + Ok(parent) => { + if let Some(prev_out) = + parent.output.get(txin.previous_output.vout as usize) + { + spk = Some(prev_out.script_pubkey.clone()); + break; + } + }, + Err(electrum_client::Error::Protocol(_)) => continue, + Err(e) => { + log_error!( + self.logger, + "Failed to look up transaction {}: {}.", + txin.previous_output.txid, + e + ); + return Err(InternalError::Failed); + }, + } + } + } + + // Nothing we could query a history for, e.g. a coinbase whose outputs are all + // unindexed. Skip it rather than failing the whole sync. + if let Some(spk) = spk { + watched_txs.push((txid, tx.clone())); + watched_script_pubkeys.push(spk); } }, Err(electrum_client::Error::Protocol(_)) => { From c6b5f9b219706f475d44474c7409eaad5fd8ba27 Mon Sep 17 00:00:00 2001 From: dcorral Date: Wed, 19 Aug 2026 19:45:45 +0200 Subject: [PATCH 09/14] point rgb-lib at the upstream sync branch --- lightning-invoice/Cargo.toml | 4 +++- lightning/Cargo.toml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml index 9a5239fa3..9e6803640 100644 --- a/lightning-invoice/Cargo.toml +++ b/lightning-invoice/Cargo.toml @@ -24,7 +24,9 @@ serde = { version = "1.0", optional = true, default-features = false, features = bitcoin = { version = "0.32.2", default-features = false, features = ["secp-recovery"] } # RGB and related -rgb-lib = { git = "https://github.com/UTEXO-Protocol/rgb-lib.git", tag = "v0.3.0-beta.32", features = [ +# Temporary: rgb-lib PR #91 (upstream sync) is unmerged and untagged. Must match the +# rgb-lightning-node pin exactly, and flip back to a UTEXO-Protocol tag (beta.34+) together. +rgb-lib = { git = "https://github.com/dcorral/rgb-lib.git", rev = "94b6221ea9bf04562d89c21c9f074f4ecfbddd5d", features = [ "electrum", "esplora", ] } diff --git a/lightning/Cargo.toml b/lightning/Cargo.toml index 41878b02e..57bbc792e 100644 --- a/lightning/Cargo.toml +++ b/lightning/Cargo.toml @@ -56,7 +56,9 @@ amplify = "4.8" bincode = "1.3" rgb-strict-encoding = "1.0.1" futures = "0.3" -rgb-lib = { git = "https://github.com/UTEXO-Protocol/rgb-lib.git", tag = "v0.3.0-beta.32", features = [ +# Temporary: rgb-lib PR #91 (upstream sync) is unmerged and untagged. Must match the +# rgb-lightning-node pin exactly, and flip back to a UTEXO-Protocol tag (beta.34+) together. +rgb-lib = { git = "https://github.com/dcorral/rgb-lib.git", rev = "94b6221ea9bf04562d89c21c9f074f4ecfbddd5d", features = [ "electrum", "esplora", ] } From 621eb243a6a1a55ca03f34f95fc325a89a3767af Mon Sep 17 00:00:00 2001 From: dcorral Date: Mon, 24 Aug 2026 11:25:14 +0200 Subject: [PATCH 10/14] refuse to read pre-sync colored channels after TLV format change --- lightning/src/ln/channel.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 192cd0cc9..8d2a2ba53 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -2412,6 +2412,24 @@ pub(crate) struct FundingScope { pub(super) rgb_asset: Option<(ContractId, Option)>, } +/// Detection-only reader for the legacy pre-sync colored-channel marker. +/// +/// Before the TLV format change the colored-channel marker was an `RgbTransport` +/// (`consignment_endpoint`), serialized as a u16-length-prefixed UTF-8 string, stored at TLV 19 +/// in `FundingScope` and TLV 71 in the channel context. Those odd TLVs are no longer in the read +/// lists and would otherwise be silently skipped; we read them back only to detect their presence +/// so a pre-sync colored channel is refused instead of being read as non-colored (asset loss). +struct LegacyColoredMarker; + +impl Readable for LegacyColoredMarker { + fn read(reader: &mut R) -> Result { + let len: u16 = Readable::read(reader)?; + let mut buf = vec![0u8; len as usize]; + reader.read_exact(&mut buf)?; + Ok(LegacyColoredMarker) + } +} + impl Writeable for FundingScope { fn write(&self, writer: &mut W) -> Result<(), io::Error> { write_tlv_fields!(writer, { @@ -2442,6 +2460,7 @@ impl Readable for FundingScope { let mut funding_tx_confirmation_height = RequiredWrapper(None); let mut short_channel_id = None; let mut minimum_depth_override = None; + let mut legacy_colored_marker: Option = None; let mut rgb_asset: Option<(ContractId, Option)> = None; read_tlv_fields!(reader, { @@ -2454,9 +2473,17 @@ impl Readable for FundingScope { (13, funding_tx_confirmation_height, required), (15, short_channel_id, option), (17, minimum_depth_override, option), + (19, legacy_colored_marker, option), (21, rgb_asset, option), }); + // Tripwire: a pre-sync build persisted the colored-channel marker at TLV 19 + // (`consignment_endpoint`); the marker now lives in `rgb_asset` at TLV 21 and is not + // migrated. Refuse to read rather than silently drop the asset (is_colored=false). + if legacy_colored_marker.is_some() && rgb_asset.is_none() { + return Err(DecodeError::DangerousValue); + } + Ok(Self { value_to_self_msat: value_to_self_msat.0.unwrap(), counterparty_selected_channel_reserve_satoshis, @@ -15570,6 +15597,7 @@ where let mut channel_keys_id = [0u8; 32]; let mut temporary_channel_id: Option = None; let mut holder_max_accepted_htlcs: Option = None; + let mut legacy_colored_marker: Option = None; let mut rgb_asset: Option<(ContractId, Option)> = None; let mut blocked_monitor_updates = Some(Vec::new()); @@ -15654,10 +15682,18 @@ where (65, quiescent_action, upgradable_option), // Added in 0.2 (67, pending_outbound_held_htlc_flags_opt, optional_vec), // Added in 0.2 (69, holding_cell_held_htlc_flags_opt, optional_vec), // Added in 0.2 + (71, legacy_colored_marker, option), (73, rgb_asset, option), (75, trusted_no_broadcast, option), }); + // Tripwire: a pre-sync build persisted the colored-channel marker at TLV 71 + // (`consignment_endpoint`); the marker now lives in `rgb_asset` at TLV 73 and is not + // migrated. Refuse to read rather than silently drop the asset (is_colored=false). + if legacy_colored_marker.is_some() && rgb_asset.is_none() { + return Err(DecodeError::DangerousValue); + } + let holder_signer = signer_provider.derive_channel_signer(channel_keys_id); let mut iter = preimages.into_iter(); From 25ef6c1efdb5c68b62f53ef01fae61928d9e465a Mon Sep 17 00:00:00 2001 From: dcorral Date: Mon, 24 Aug 2026 11:25:43 +0200 Subject: [PATCH 11/14] validate RGB funding consignment before persisting media --- lightning/src/rgb_utils/mod.rs | 37 ++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/lightning/src/rgb_utils/mod.rs b/lightning/src/rgb_utils/mod.rs index d6cce69bb..b66b027cd 100644 --- a/lightning/src/rgb_utils/mod.rs +++ b/lightning/src/rgb_utils/mod.rs @@ -768,6 +768,26 @@ pub(crate) fn handle_funding( Err(e) => return Err(ChannelError::close(format!("Unexpected error: {e}"))), }; + // Validate before persisting anything: an invalid consignment must not leave orphaned + // media/consignment behind in the wallet dirs. + if remote_rgb_assignments.len() != 1 { + return Err(ChannelError::close(format!( + "Unexpected number of RGB assignments: {}", + remote_rgb_assignments.len() + ))); + } + let channel_rgb_amount = match remote_rgb_assignments[0] { + Assignment::Fungible(amt) => amt, + Assignment::NonFungible => 1, + _ => unreachable!("unsupported schema"), + }; + let push_amount = push_asset_amount.unwrap_or(0); + let remote_rgb_amount = channel_rgb_amount.checked_sub(push_amount).ok_or_else(|| { + ChannelError::close(format!( + "push_asset_amount {push_amount} exceeds channel asset amount {channel_rgb_amount}" + )) + })?; + 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()); @@ -800,23 +820,6 @@ pub(crate) fn handle_funding( // on the error paths above the staging directory is left for the file transfer handler's sweep let _ = fs::remove_dir_all(&staging_dir); - if remote_rgb_assignments.len() != 1 { - return Err(ChannelError::close(format!( - "Unexpected number of RGB assignments: {}", - remote_rgb_assignments.len() - ))); - } - let channel_rgb_amount = match remote_rgb_assignments[0] { - Assignment::Fungible(amt) => amt, - Assignment::NonFungible => 1, - _ => unreachable!("unsupported schema"), - }; - let push_amount = push_asset_amount.unwrap_or(0); - let remote_rgb_amount = channel_rgb_amount.checked_sub(push_amount).ok_or_else(|| { - ChannelError::close(format!( - "push_asset_amount {push_amount} exceeds channel asset amount {channel_rgb_amount}" - )) - })?; let rgb_info = RgbInfo { contract_id: consignment.contract_id(), schema: AssetSchema::from_schema_id(consignment.schema_id()).unwrap(), From 861fe7624d70583a153c69c19c384e43cbbef15f Mon Sep 17 00:00:00 2001 From: dcorral Date: Mon, 24 Aug 2026 11:26:02 +0200 Subject: [PATCH 12/14] drop inert serde(default) on RgbInfo counterparty_knows_asset --- lightning/src/rgb_utils/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/lightning/src/rgb_utils/mod.rs b/lightning/src/rgb_utils/mod.rs index b66b027cd..4f95ad4df 100644 --- a/lightning/src/rgb_utils/mod.rs +++ b/lightning/src/rgb_utils/mod.rs @@ -100,7 +100,6 @@ pub struct RgbInfo { /// serialized unconditionally to stay in sync on read. pub batch_transfer_idx: Option, /// Whether the channel acceptor told us (in `accept_channel`) that it already knows the asset - #[serde(default)] pub counterparty_knows_asset: bool, } From 14e143e35717542c1ad7491b5073cece45310bc8 Mon Sep 17 00:00:00 2001 From: dcorral Date: Mon, 24 Aug 2026 13:54:28 +0200 Subject: [PATCH 13/14] bump rgb-lib pin to include the consignment replay fixes --- lightning-invoice/Cargo.toml | 2 +- lightning/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml index aa5afd6bb..9d1196596 100644 --- a/lightning-invoice/Cargo.toml +++ b/lightning-invoice/Cargo.toml @@ -26,7 +26,7 @@ bitcoin = { version = "0.32.2", default-features = false, features = ["secp-reco # RGB and related # Temporary: rgb-lib PR #91 (upstream sync) is unmerged and untagged. Must match the # rgb-lightning-node pin exactly, and flip back to a UTEXO-Protocol tag (beta.34+) together. -rgb-lib = { git = "https://github.com/dcorral/rgb-lib.git", rev = "94b6221ea9bf04562d89c21c9f074f4ecfbddd5d", default-features = false } +rgb-lib = { git = "https://github.com/dcorral/rgb-lib.git", rev = "c097984fd37ff9767758f1e5e37eca27307934f4", default-features = false } [dev-dependencies] serde_json = { version = "1"} diff --git a/lightning/Cargo.toml b/lightning/Cargo.toml index 26c0e46fe..1e9d827ae 100644 --- a/lightning/Cargo.toml +++ b/lightning/Cargo.toml @@ -61,7 +61,7 @@ rgb-strict-encoding = "1.0.1" futures = "0.3" # Temporary: rgb-lib PR #91 (upstream sync) is unmerged and untagged. Must match the # rgb-lightning-node pin exactly, and flip back to a UTEXO-Protocol tag (beta.34+) together. -rgb-lib = { git = "https://github.com/dcorral/rgb-lib.git", rev = "94b6221ea9bf04562d89c21c9f074f4ecfbddd5d", default-features = false } +rgb-lib = { git = "https://github.com/dcorral/rgb-lib.git", rev = "c097984fd37ff9767758f1e5e37eca27307934f4", default-features = false } serde = { version = "^1.0", features = [ "derive", ] } From 78efbe4a70b109e92ede4bfa0591dd8d36df4372 Mon Sep 17 00:00:00 2001 From: dcorral Date: Tue, 25 Aug 2026 15:26:52 +0200 Subject: [PATCH 14/14] flip rgb-lib pin to the released UTEXO tag v0.3.0-beta.34 --- lightning-invoice/Cargo.toml | 4 +--- lightning/Cargo.toml | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml index 9d1196596..7ec904be2 100644 --- a/lightning-invoice/Cargo.toml +++ b/lightning-invoice/Cargo.toml @@ -24,9 +24,7 @@ serde = { version = "1.0", optional = true, default-features = false, features = bitcoin = { version = "0.32.2", default-features = false, features = ["secp-recovery"] } # RGB and related -# Temporary: rgb-lib PR #91 (upstream sync) is unmerged and untagged. Must match the -# rgb-lightning-node pin exactly, and flip back to a UTEXO-Protocol tag (beta.34+) together. -rgb-lib = { git = "https://github.com/dcorral/rgb-lib.git", rev = "c097984fd37ff9767758f1e5e37eca27307934f4", default-features = false } +rgb-lib = { git = "https://github.com/UTEXO-Protocol/rgb-lib.git", tag = "v0.3.0-beta.34", default-features = false } [dev-dependencies] serde_json = { version = "1"} diff --git a/lightning/Cargo.toml b/lightning/Cargo.toml index 1e9d827ae..39703d760 100644 --- a/lightning/Cargo.toml +++ b/lightning/Cargo.toml @@ -59,9 +59,7 @@ amplify = "4.8" bincode = "1.3" rgb-strict-encoding = "1.0.1" futures = "0.3" -# Temporary: rgb-lib PR #91 (upstream sync) is unmerged and untagged. Must match the -# rgb-lightning-node pin exactly, and flip back to a UTEXO-Protocol tag (beta.34+) together. -rgb-lib = { git = "https://github.com/dcorral/rgb-lib.git", rev = "c097984fd37ff9767758f1e5e37eca27307934f4", default-features = false } +rgb-lib = { git = "https://github.com/UTEXO-Protocol/rgb-lib.git", tag = "v0.3.0-beta.34", default-features = false } serde = { version = "^1.0", features = [ "derive", ] }