diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d8adfba90..137b2d7d4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,7 +38,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 cbbb0fd1a..ef46e28ab 100755 --- a/ci/check-lint.sh +++ b/ci/check-lint.sh @@ -116,4 +116,4 @@ CLIPPY() { -A clippy::useless-borrows-in-formatting } -CLIPPY +CLIPPY "--features lightning/electrum" diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml index 9a5239fa3..7ec904be2 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 = { git = "https://github.com/UTEXO-Protocol/rgb-lib.git", tag = "v0.3.0-beta.32", features = [ - "electrum", - "esplora", -] } +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-transaction-sync/src/electrum.rs b/lightning-transaction-sync/src/electrum.rs index 695e8c7c4..be48f43d0 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,26 +291,56 @@ where continue; } - watched_txs.push((txid, tx.clone())); - // We watch an 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. We skip OP_RETURN outputs as Electrum servers - // don't index provably-unspendable scripts (e.g. the RGB commitment at vout 0 - // of a colored funding tx), so their history is empty and the tx would never - // be seen as confirmed. We fall back to the first output to keep this lookup - // aligned with `watched_txs`. - let script_pubkey = tx + // 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 (e.g. the RGB commitment at vout 0 of a + // colored funding tx), so picking one of those would always yield an empty + // history and the tx would never be seen as confirmed. + let mut spk = tx .output .iter() - .find(|o| !o.script_pubkey.is_op_return()) - .or_else(|| tx.output.first()) - .map(|o| o.script_pubkey.clone()); - if let Some(script_pubkey) = script_pubkey { - watched_script_pubkeys.push(script_pubkey); - } 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); + .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(_)) => { diff --git a/lightning/Cargo.toml b/lightning/Cargo.toml index 41878b02e..39703d760 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 = [] @@ -56,10 +59,7 @@ 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 = [ - "electrum", - "esplora", -] } +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", ] } diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 7afa028b2..8d2a2ba53 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::{ @@ -80,8 +80,9 @@ use crate::offers::static_invoice::StaticInvoice; 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, RgbKvStoreExt, + holder_validate_take_psbt_output_witness_scripts_hex, is_asset_known, is_tx_colored, + rename_rgb_files, set_counterparty_knows_asset, update_rgb_channel_amount_pending, + RgbKvStoreExt, }; use crate::routing::gossip::NodeId; use crate::sign::ecdsa::EcdsaChannelSigner; @@ -116,8 +117,7 @@ use super::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint, RevocationBase fn rgb_install_holder_validate_psbt_witness_scripts_if_colored( funding: &FundingScope, commitment_tx: &CommitmentTransaction, ) -> Result<(), ChannelError> { - let is_rgb_channel = - funding.push_asset_amount.is_some() || funding.consignment_endpoint.is_some(); + let is_rgb_channel = funding.is_colored(); if !is_rgb_channel && !is_tx_colored(&commitment_tx.trust().built_transaction().transaction) { return Ok(()); } @@ -2407,11 +2407,27 @@ 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 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)>, +} - /// The RGB asset amount to push to the counterparty on channel open. - pub(super) push_asset_amount: 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 { @@ -2426,8 +2442,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(()) } @@ -2445,8 +2460,8 @@ 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 legacy_colored_marker: Option = None; + let mut rgb_asset: Option<(ContractId, Option)> = None; read_tlv_fields!(reader, { (1, value_to_self_msat, required), @@ -2458,10 +2473,17 @@ 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), + (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, @@ -2476,8 +2498,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))] @@ -2495,6 +2516,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, @@ -2665,8 +2701,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, } } @@ -3482,7 +3517,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, rgb_kv_store: Arc, ) -> Result<(FundingScope, ChannelContext), ChannelError> @@ -3697,8 +3732,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, @@ -3813,7 +3847,7 @@ where interactive_tx_signing_session: None, - is_colored: funding.consignment_endpoint.is_some(), + is_colored: funding.is_colored(), ldk_data_dir, rgb_kv_store, }; @@ -3839,9 +3873,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, rgb_kv_store: Arc, ) -> Result<(FundingScope, ChannelContext), APIError> where @@ -3948,8 +3981,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, @@ -4062,7 +4094,7 @@ where interactive_tx_signing_session: None, - is_colored: funding.consignment_endpoint.is_some(), + is_colored: funding.is_colored(), ldk_data_dir, rgb_kv_store, }; @@ -12006,12 +12038,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)); @@ -13683,7 +13715,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, rgb_kv_store: Arc, ) -> Result, APIError> where ES::Target: EntropySource, @@ -13724,9 +13756,8 @@ where channel_keys_id, holder_signer, logger, - consignment_endpoint, + rgb_asset, ldk_data_dir, - push_asset_amount, rgb_kv_store, )?; let unfunded_context = UnfundedChannelContext { @@ -13824,7 +13855,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) @@ -13905,11 +13936,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, }) } @@ -13924,7 +13954,16 @@ 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.rgb_kv_store.as_ref(), + ); + } + + Ok(()) } /// Handles a funding_signed message from the remote end. @@ -14107,7 +14146,7 @@ where msg.channel_reserve_satoshis, msg.push_msat, msg.common_fields.clone(), - msg.push_asset_amount, + msg.rgb_asset, ldk_data_dir, rgb_kv_store, )?; @@ -14165,6 +14204,15 @@ 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, + self.context.rgb_kv_store.as_ref(), + ), + None => false, + }; + Some(msgs::AcceptChannel { common_fields: msgs::CommonAcceptChannelFields { temporary_channel_id: self.context.channel_id, @@ -14187,6 +14235,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, }) @@ -14350,10 +14399,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, rgb_kv_store, )?; let unfunded_context = UnfundedChannelContext { @@ -14446,7 +14494,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, @@ -15199,8 +15246,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), (75, trusted_no_broadcast, option), }); @@ -15551,8 +15597,8 @@ 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 legacy_colored_marker: Option = None; + let mut rgb_asset: Option<(ContractId, Option)> = None; let mut blocked_monitor_updates = Some(Vec::new()); @@ -15636,11 +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, consignment_endpoint, option), - (73, push_asset_amount, option), + (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(); @@ -15917,8 +15970,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, @@ -16031,7 +16083,7 @@ where trusted_no_broadcast: trusted_no_broadcast.unwrap_or(false), interactive_tx_signing_session, - is_colored: consignment_endpoint.is_some(), + is_colored: rgb_asset.is_some(), ldk_data_dir, rgb_kv_store, }, @@ -18023,8 +18075,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 4b082e8b3..acb49e598 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, @@ -4162,7 +4162,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, is_virtual: bool) -> 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)>, is_virtual: bool) -> 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) }); } @@ -4198,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, 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(), Arc::clone(&self.rgb_kv_store)) { Ok(res) => res, @@ -10635,8 +10635,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, self.rgb_kv_store.as_ref()) { + 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(), self.rgb_kv_store.as_ref()) { Ok(()) => (), Err(e) => { // at this point the channel initiator already transitioned its channel to the funded channel ID @@ -10798,7 +10798,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 00bcfda1f..ca518b612 100644 --- a/lightning/src/ln/inbound_payment.rs +++ b/lightning/src/ln/inbound_payment.rs @@ -487,7 +487,7 @@ pub(crate) 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/msgs.rs b/lightning/src/ln/msgs.rs index d5f51288f..164d2b19a 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::{ @@ -246,8 +246,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 { @@ -298,8 +296,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. @@ -378,6 +377,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, @@ -2663,15 +2665,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(()) @@ -2697,10 +2702,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; @@ -2708,6 +2715,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), }); @@ -2730,6 +2738,7 @@ impl LengthReadable for AcceptChannel { channel_type, }, channel_reserve_satoshis, + known_asset: known_asset_marker.is_some(), #[cfg(taproot)] next_local_nonce, }) @@ -3135,8 +3144,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(()) } @@ -3165,13 +3173,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 { @@ -3193,11 +3199,10 @@ impl LengthReadable for OpenChannel { channel_flags, shutdown_scriptpubkey, channel_type, - consignment_endpoint, }, push_msat, channel_reserve_satoshis, - push_asset_amount, + rgb_asset, }) } } @@ -3227,7 +3232,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(()) } @@ -3258,12 +3262,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 { @@ -3285,7 +3287,6 @@ impl LengthReadable for OpenChannelV2 { channel_flags, shutdown_scriptpubkey, channel_type, - consignment_endpoint, }, funding_feerate_sat_per_1000_weight, locktime, @@ -3295,28 +3296,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/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/rgb_utils/mod.rs b/lightning/src/rgb_utils/mod.rs index b3c4035e5..4f95ad4df 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, @@ -13,6 +18,7 @@ use crate::types::payment::PaymentHash; use crate::util::persist::KVStoreSync; use bitcoin::blockdata::transaction::Transaction; +use bitcoin::hashes::{sha256, Hash}; use bitcoin::hex::DisplayHex; use bitcoin::psbt::{ExtractTxError, Psbt}; use bitcoin::secp256k1::PublicKey; @@ -22,10 +28,10 @@ use rgb_lib::{ keys::WitnessVersion, wallet::{ rust_only::{AssetColoringInfo, ColoringInfo}, - DatabaseType, OnlineOptions, SinglesigKeys, Wallet, WalletData, + DatabaseType, OnlineOptions, RgbWalletOpsOffline, SinglesigKeys, Wallet, WalletData, }, AssetSchema, Assignment, BitcoinNetwork, ConsignmentExt, ContractId, Error as RgbLibError, - Fascia, FileContent, RgbTransfer, RgbTransport, WitnessOrd, + Fascia, FileContent, RgbTransfer, WitnessOrd, }; use serde::{Deserialize, Serialize}; use strict_encoding::{StrictDeserialize, StrictSerialize}; @@ -34,8 +40,9 @@ use tokio::runtime::Handle; use crate::io; use core::ops::Deref; use std::cell::RefCell; -use std::collections::HashMap; -use std::path::Path; +use std::collections::{HashMap, HashSet}; +use std::fs; +use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::Arc; @@ -45,8 +52,6 @@ pub const STATIC_BLINDING: u64 = 777; 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 @@ -94,6 +99,8 @@ pub struct RgbInfo { /// bincode (a positional, non-self-describing format), so the field must be /// 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 + pub counterparty_knows_asset: bool, } /// RGB payment info @@ -120,8 +127,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 { @@ -250,10 +257,18 @@ async fn _get_rgb_wallet(ldk_data_dir: &Path, kv_store: &dyn KVStoreSync) -> Wal .unwrap() } +pub(crate) fn is_asset_known( + contract_id: ContractId, ldk_data_dir: &Path, kv_store: &dyn KVStoreSync, +) -> bool { + let handle = Handle::current(); + let _ = handle.enter(); + let wallet = futures::executor::block_on(_get_rgb_wallet(ldk_data_dir, kv_store)); + wallet.is_asset_known(contract_id).unwrap_or(false) +} + async fn _accept_transfer( - ldk_data_dir: &Path, funding_txid: String, consignment_endpoint: RgbTransport, - kv_store: &dyn KVStoreSync, -) -> Result<(RgbTransfer, Vec), RgbLibError> { + ldk_data_dir: &Path, funding_txid: String, kv_store: &dyn KVStoreSync, +) -> Result<(RgbTransfer, Vec, HashSet, PathBuf), RgbLibError> { let funding_vout = 1; let ( data_dir, @@ -264,6 +279,8 @@ async fn _accept_transfer( reuse_addresses, ) = _get_wallet_data(ldk_data_dir, kv_store); let indexer_url = _get_indexer_url(kv_store); + // 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, @@ -273,17 +290,19 @@ async fn _accept_transfer( master_fingerprint, reuse_addresses, ); - wallet.go_online(OnlineOptions { + 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() @@ -493,8 +512,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), @@ -527,12 +548,7 @@ where .write(RGB_PRIMARY_NS, RGB_COMMITMENT_FASCIA_NS, &fascia_key, fascia_bytes) .expect("KVStore write failed"); - 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 }; kv_store.write_rgb_transfer_info(&txid.to_string(), &transfer_info); Ok(()) @@ -554,8 +570,9 @@ pub(crate) fn color_htlc( let transfer_info = kv_store.read_rgb_transfer_info(&commitment_txid); 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 { @@ -579,7 +596,7 @@ pub(crate) fn color_htlc( wallet.consume_fascia(fascia.clone(), Some(WitnessOrd::Ignored)).unwrap(); - let transfer_info = TransferInfo { contract_id, rgb_amount: htlc_amount_rgb }; + let transfer_info = TransferInfo { contract_id, output_map }; kv_store.write_rgb_transfer_info(&txid.to_string(), &transfer_info); Ok(()) @@ -618,8 +635,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), @@ -643,7 +662,7 @@ pub(crate) fn color_closing( wallet.consume_fascia(fascia.clone(), Some(WitnessOrd::Ignored)).unwrap(); - let transfer_info = TransferInfo { contract_id, rgb_amount: holder_vout_amount }; + let transfer_info = TransferInfo { contract_id, output_map }; kv_store.write_rgb_transfer_info(&txid.to_string(), &transfer_info); Ok(()) @@ -712,20 +731,21 @@ pub(crate) fn rename_rgb_files( } } +/// 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, kv_store: &dyn KVStoreSync, + push_asset_amount: Option, kv_store: &dyn KVStoreSync, ) -> 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, - kv_store, - )); - let (consignment, remote_rgb_assignments) = match accept_res { + let accept_res = + futures::executor::block_on(_accept_transfer(ldk_data_dir, funding_txid.clone(), kv_store)); + 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())) @@ -747,12 +767,8 @@ pub(crate) fn handle_funding( 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); - + // 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: {}", @@ -765,12 +781,52 @@ 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 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); + + 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); + 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, }; let temporary_channel_id_str = temporary_channel_id.0.as_hex().to_string(); @@ -780,6 +836,16 @@ pub(crate) fn handle_funding( Ok(()) } +pub(crate) fn set_counterparty_knows_asset(channel_id: &ChannelId, kv_store: &dyn KVStoreSync) { + let channel_id = channel_id.0.as_hex().to_string(); + for pending in [true, false] { + if let Ok(mut rgb_info) = kv_store.read_rgb_channel_info(&channel_id, pending) { + rgb_info.counterparty_knows_asset = true; + kv_store.write_rgb_channel_info(&channel_id, &rgb_info, pending); + } + } +} + /// Update RGB channel amount in KVStore pub fn update_rgb_channel_amount( channel_id: &str, rgb_offered_htlc: u64, rgb_received_htlc: u64, pending: bool, 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)}); } }