diff --git a/lightning-invoice/src/payment.rs b/lightning-invoice/src/payment.rs index b6431e926e8..5364a908f33 100644 --- a/lightning-invoice/src/payment.rs +++ b/lightning-invoice/src/payment.rs @@ -146,7 +146,7 @@ use bitcoin_hashes::sha256::Hash as Sha256; use crate::prelude::*; use lightning::io; use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; -use lightning::ln::channelmanager::{ChannelDetails, PaymentId, PaymentSendFailure}; +use lightning::ln::channelmanager::{ChannelDetails, CustomOutputId, PaymentId, PaymentSendFailure}; use lightning::ln::msgs::{LightningError, ErrorAction}; use lightning::routing::gossip::NodeId; use lightning::routing::router::{PaymentParameters, Route, RouteHop, RouteParameters, AddCustomOutputRouteDetails}; @@ -266,7 +266,7 @@ pub trait Payer { /// Adds a custom output over the Lightning Network using the given [`Route`]. fn add_custom_output( &self, route_details: AddCustomOutputRouteDetails, - ) -> Result<(), String>; // TODO(10101): Maybe add a `CustomOutputCreatedId`?? + ) -> Result; /// Retries a failed payment path for the [`PaymentId`] using the given [`Route`]. fn retry_payment(&self, route: &Route, payment_id: PaymentId) -> Result<(), PaymentSendFailure>; diff --git a/lightning-invoice/src/utils.rs b/lightning-invoice/src/utils.rs index 412ccaac2bc..1d429d63d35 100644 --- a/lightning-invoice/src/utils.rs +++ b/lightning-invoice/src/utils.rs @@ -10,7 +10,7 @@ use lightning::chain; use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; use lightning::chain::keysinterface::{Recipient, KeysInterface, Sign}; use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; -use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, PaymentId, PaymentSendFailure, MIN_FINAL_CLTV_EXPIRY}; +use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, PaymentId, PaymentSendFailure, MIN_FINAL_CLTV_EXPIRY, CustomOutputId}; #[cfg(feature = "std")] use lightning::ln::channelmanager::{PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA}; use lightning::ln::inbound_payment::{create, create_from_hash, ExpandedKey}; @@ -646,7 +646,7 @@ where fn add_custom_output( &self, route_details: AddCustomOutputRouteDetails, - ) -> Result<(), String> { + ) -> Result { let AddCustomOutputRouteDetails { short_channel_id, pk_counterparty, local_amount_msats: amount_us_msat, amount_counterparty_msat, cltv_expiry } = route_details; self.add_custom_output(short_channel_id, pk_counterparty, amount_us_msat, amount_counterparty_msat, cltv_expiry) diff --git a/lightning/src/ln/chanmon_update_fail_tests.rs b/lightning/src/ln/chanmon_update_fail_tests.rs index e984d2f9271..4ae336e47ce 100644 --- a/lightning/src/ln/chanmon_update_fail_tests.rs +++ b/lightning/src/ln/chanmon_update_fail_tests.rs @@ -302,7 +302,7 @@ fn do_test_monitor_temporary_update_fail(disconnect_count: usize) { let events_2 = nodes[1].node.get_and_clear_pending_msg_events(); assert_eq!(events_2.len(), 1); let (bs_initial_fulfill, bs_initial_commitment_signed) = match events_2[0] { - MessageSendEvent::UpdateCommitmentOutputs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed, ref update_add_custom_output } } => { + MessageSendEvent::UpdateCommitmentOutputs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed, ref update_add_custom_output, ref update_remove_custom_output } } => { assert_eq!(*node_id, nodes[0].node.get_our_node_id()); assert!(update_add_htlcs.is_empty()); assert_eq!(update_fulfill_htlcs.len(), 1); @@ -2686,7 +2686,7 @@ fn double_temp_error() { assert_eq!(events.len(), 1); let (update_fulfill_1, commitment_signed_b1, node_id) = { match &events[0] { - &MessageSendEvent::UpdateCommitmentOutputs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed, ref update_add_custom_output } } => { + &MessageSendEvent::UpdateCommitmentOutputs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed, ref update_add_custom_output, ref update_remove_custom_output } } => { assert!(update_add_htlcs.is_empty()); assert_eq!(update_fulfill_htlcs.len(), 1); assert!(update_fail_htlcs.is_empty()); diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 0bae3c84470..a8e701e33eb 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -27,7 +27,7 @@ use crate::ln::features::{ChannelTypeFeatures, InitFeatures}; use crate::ln::msgs; use crate::ln::msgs::{DecodeError, OptionalField, DataLossProtect}; use crate::ln::script::{self, ShutdownScript}; -use crate::ln::channelmanager::{self, CounterpartyForwardingInfo, PendingHTLCStatus, HTLCSource, HTLCFailReason, HTLCFailureMsg, PendingHTLCInfo, RAACommitmentOrder, BREAKDOWN_TIMEOUT, MIN_CLTV_EXPIRY_DELTA, MAX_LOCAL_BREAKDOWN_TIMEOUT}; +use crate::ln::channelmanager::{self, CounterpartyForwardingInfo, PendingHTLCStatus, HTLCSource, HTLCFailReason, HTLCFailureMsg, PendingHTLCInfo, RAACommitmentOrder, BREAKDOWN_TIMEOUT, MIN_CLTV_EXPIRY_DELTA, MAX_LOCAL_BREAKDOWN_TIMEOUT, CustomOutputId}; use crate::ln::chan_utils::{CounterpartyCommitmentSecrets, TxCreationKeys, HTLCOutputInCommitment, htlc_success_tx_weight, htlc_timeout_tx_weight, make_funding_redeemscript, ChannelPublicKeys, CommitmentTransaction, HolderCommitmentTransaction, ChannelTransactionParameters, CounterpartyChannelTransactionParameters, MAX_HTLCS, get_commitment_transaction_number_obscure_factor, ClosingTransaction, CustomOutputInCommitment}; use crate::ln::chan_utils; use crate::chain::BestBlock; @@ -222,8 +222,10 @@ struct OutboundHTLCOutput { } struct CustomOutput { - custom_output_id: u64, + custom_output_id: CustomOutputId, + /// Initial amount provided by local node amount_local_msat: u64, + /// Initial amount provided by remote node amount_remote_msat: u64, cltv_expiry: u32, state: CustomOutputState, @@ -236,7 +238,14 @@ enum CustomOutputState { RemoteAnnounced, // we received RemoteAnnounced and are waiting for the remote to announce the RAA to us. AwaitingRemoteRevoke, + LocalRemoved { + local_profit: i64, + }, + RemoteRemoved { + local_profit: i64 + }, Committed, + // TODO: I think this can be removed RemoteSettled, AwaitingRemoteRevokeToSettle, AwaitingRemovedRemoteRevoke, @@ -268,6 +277,10 @@ enum CustomOutputUpdateAwaitingAck { amount_local_msat: u64, amount_remote_msat: u64, cltv_expiry: u32, + }, + RemoveCustomOutput { + local_amount_msat: u64, + remote_amount_msat: u64 } } @@ -589,7 +602,7 @@ pub(super) struct Channel { value_to_self_msat: u64, // Excluding all pending_htlcs, excluding fees pending_inbound_htlcs: Vec, pending_outbound_htlcs: Vec, - pending_custom_outputs: Vec, + pending_custom_outputs: HashMap, holding_cell_htlc_updates: Vec, holding_cell_custom_output_updates: Vec, // TODO(10101): Should we merge with previous field? @@ -1034,7 +1047,7 @@ impl Channel { pending_inbound_htlcs: Vec::new(), pending_outbound_htlcs: Vec::new(), - pending_custom_outputs: Vec::new(), + pending_custom_outputs: HashMap::new(), holding_cell_htlc_updates: Vec::new(), holding_cell_custom_output_updates: Vec::new(), pending_update_fee: None, @@ -1368,7 +1381,7 @@ impl Channel { pending_inbound_htlcs: Vec::new(), pending_outbound_htlcs: Vec::new(), - pending_custom_outputs: Vec::new(), + pending_custom_outputs: HashMap::new(), holding_cell_htlc_updates: Vec::new(), holding_cell_custom_output_updates: Vec::new(), pending_update_fee: None, @@ -1636,7 +1649,8 @@ impl Channel { let mut from_self_custom_output_msat = 0; let mut from_remote_custom_output_msat = 0; - for ref custom_output in self.pending_custom_outputs.iter() { + let mut local_settlement_profit_msat = 0; + for (_, ref custom_output) in self.pending_custom_outputs.iter() { let state_name = match custom_output.state { CustomOutputState::LocalAnnounced => "LocalAnnounced", CustomOutputState::RemoteAnnounced => "RemoteAnnounced", @@ -1645,6 +1659,8 @@ impl Channel { CustomOutputState::RemoteSettled => "RemoteSettled", CustomOutputState::AwaitingRemoteRevokeToSettle => "AwaitingRemoteRevokeToSettle", CustomOutputState::AwaitingRemovedRemoteRevoke => "AwaitingRemovedRemoteRevoke", + CustomOutputState::LocalRemoved {..} => {"LocalRemoved"} + CustomOutputState::RemoteRemoved {..} => {"RemoteRemoved"} }; // TODO(10101): Use `generated_by_local` and `custom_output.state` to determine if the output should be included in the transaction? @@ -1652,6 +1668,7 @@ impl Channel { // Ignoring inbound or outbound // Ignoring `self.opt_anchors()` for now? + let custom_output_in_tx = CustomOutputInCommitment { amount_local_msat: custom_output.amount_local_msat, amount_remote_msat: custom_output.amount_remote_msat, @@ -1669,7 +1686,9 @@ impl Channel { custom_output.amount_local_msat + custom_output.amount_remote_msat ); - included_custom_outputs.push(custom_output_in_tx); + if let CustomOutputState::LocalAnnounced | CustomOutputState::RemoteAnnounced | CustomOutputState::AwaitingRemoteRevoke = custom_output.state { + included_custom_outputs.push(custom_output_in_tx); + } // TODO(10101): subtract amounts from each side match custom_output.state { @@ -1686,6 +1705,11 @@ impl Channel { from_self_custom_output_msat += custom_output.amount_remote_msat; from_remote_custom_output_msat += custom_output.amount_local_msat; } + CustomOutputState::LocalRemoved { + local_profit + } | CustomOutputState::RemoteRemoved{ local_profit } => { + local_settlement_profit_msat += local_profit; + } CustomOutputState::AwaitingRemovedRemoteRevoke | CustomOutputState::RemoteSettled | CustomOutputState::Committed | @@ -1695,13 +1719,13 @@ impl Channel { } } - let mut value_to_self_msat: i64 = (self.value_to_self_msat - local_htlc_total_msat - from_self_custom_output_msat) as i64 + value_to_self_msat_offset; + let mut value_to_self_msat: i64 = (self.value_to_self_msat - local_htlc_total_msat - from_self_custom_output_msat) as i64 + value_to_self_msat_offset + local_settlement_profit_msat as i64; assert!(value_to_self_msat >= 0); // Note that in case they have several just-awaiting-last-RAA fulfills in-progress (ie // AwaitingRemoteRevokeToRemove or AwaitingRemovedRemoteRevoke) we may have allowed them to - // "violate" their reserve value by couting those against it. Thus, we have to convert + // "violate" their reserve value by counting those against it. Thus, we have to convert // everything to i64 before subtracting as otherwise we can overflow. - let mut value_to_remote_msat: i64 = (self.channel_value_satoshis * 1000) as i64 - (self.value_to_self_msat) as i64 - (remote_htlc_total_msat as i64) - value_to_self_msat_offset - from_remote_custom_output_msat as i64; + let mut value_to_remote_msat: i64 = (self.channel_value_satoshis * 1000) as i64 - (self.value_to_self_msat) as i64 - (remote_htlc_total_msat as i64) - value_to_self_msat_offset - from_remote_custom_output_msat as i64 - local_settlement_profit_msat; assert!(value_to_remote_msat >= 0); #[cfg(debug_assertions)] @@ -2562,7 +2586,7 @@ impl Channel { fn get_inbound_pending_custom_output_stats(&self) -> CustomOutputInboundStats { let mut sum_msats = 0; - for ref output in self.pending_custom_outputs.iter() { + for (_, ref output) in self.pending_custom_outputs.iter() { // TODO(10101): do we need to handle `< holder_dust_limit_success_sat` here as above? sum_msats += output.amount_remote_msat; } @@ -2575,7 +2599,7 @@ impl Channel { fn get_outbound_pending_custom_output_stats(&self) -> CustomOutputOutboundStats { let mut sum_msats = 0; - for ref output in self.pending_custom_outputs.iter() { + for (_, ref output) in self.pending_custom_outputs.iter() { // TODO(10101): do we need to handle `< holder_dust_limit_success_sat` here as above? sum_msats += output.amount_local_msat; } @@ -2649,7 +2673,7 @@ impl Channel { } balance_msat -= outbound_stats.pending_htlcs_value_msat; - for ref custom_output in self.pending_custom_outputs.iter() { + for (_, ref custom_output) in self.pending_custom_outputs.iter() { match custom_output.state { CustomOutputState::LocalAnnounced => { balance_msat -= custom_output.amount_local_msat; @@ -2664,10 +2688,11 @@ impl Channel { CustomOutputState::Committed | CustomOutputState::RemoteSettled | CustomOutputState::AwaitingRemoteRevokeToSettle | - CustomOutputState::AwaitingRemovedRemoteRevoke => { + CustomOutputState::AwaitingRemovedRemoteRevoke | + CustomOutputState::LocalRemoved { .. } | + CustomOutputState::RemoteRemoved { .. } => { unimplemented!("Not used so far") - }, - + } } } @@ -3077,16 +3102,17 @@ impl Channel { // TODO(10101): Introduce checks similar to what we see in `update_add_htlc` - if self.next_counterparty_custom_output_id != msg.custom_output_id { - return Err(ChannelError::Close(format!("Remote skipped custom output ID (skipped ID: {})", self.next_counterparty_custom_output_id))); - } + // TODO(10101): Do we need this check? + // if self.next_counterparty_custom_output_id != msg.custom_output_id { + // return Err(ChannelError::Close(format!("Remote skipped custom output ID (skipped ID: {})", self.next_counterparty_custom_output_id))); + // } if msg.cltv_expiry >= 500000000 { return Err(ChannelError::Close("Remote provided CLTV expiry in seconds instead of block height".to_owned())); } // Now update local state: self.next_counterparty_custom_output_id += 1; - self.pending_custom_outputs.push( + self.pending_custom_outputs.insert(msg.custom_output_id, CustomOutput { custom_output_id: msg.custom_output_id, amount_local_msat: msg.amount_local_msat, @@ -3309,7 +3335,7 @@ impl Channel { need_commitment = true; } } - for custom_output in self.pending_custom_outputs.iter_mut() { + for (_, custom_output) in self.pending_custom_outputs.iter_mut() { if let CustomOutputState::RemoteAnnounced = custom_output.state { log_trace!(logger, "Updating CustomOutput {} to AwaitingRemoteRevoke due to commitment_signed in channel {}.", custom_output.custom_output_id, log_bytes!(self.channel_id)); @@ -3494,6 +3520,7 @@ impl Channel { update_fee, commitment_signed, update_add_custom_output: Vec::new(), + update_remove_custom_output: Vec::new() }, monitor_update)), htlcs_to_fail)) } else { Ok((None, Vec::new())) @@ -3754,7 +3781,8 @@ impl Channel { update_fail_malformed_htlcs, update_fee: None, commitment_signed, - update_add_custom_output: Vec::new() + update_add_custom_output: Vec::new(), + update_remove_custom_output: Vec::new() }), finalized_claimed_htlcs, accepted_htlcs: to_forward_infos, failed_htlcs: revoked_htlcs, @@ -4118,6 +4146,7 @@ impl Channel { update_add_htlcs, update_fulfill_htlcs, update_fail_htlcs, update_fail_malformed_htlcs, update_fee, commitment_signed: self.send_commitment_no_state_update(logger).expect("It looks like we failed to re-generate a commitment_signed we had previously sent?").0, update_add_custom_output: Vec::new(), + update_remove_custom_output: Vec::new() } } @@ -5872,6 +5901,7 @@ impl Channel { pub fn add_custom_output( &mut self, + custom_output_id: CustomOutputId, amount_us_msat: u64, amount_counterparty_msat: u64, cltv_expiry: u32, logger: &L @@ -5932,9 +5962,10 @@ impl Channel { return Ok(None); } - self.pending_custom_outputs.push( + self.pending_custom_outputs.insert( + custom_output_id, CustomOutput { - custom_output_id: self.next_holder_custom_output_id, + custom_output_id, amount_local_msat: amount_us_msat, amount_remote_msat: amount_counterparty_msat, cltv_expiry, @@ -5944,7 +5975,7 @@ impl Channel { let res = msgs::UpdateAddCustomOutput { channel_id: self.channel_id, - custom_output_id: self.next_holder_custom_output_id, + custom_output_id, amount_local_msat: amount_us_msat, amount_remote_msat: amount_counterparty_msat, cltv_expiry, @@ -5954,6 +5985,65 @@ impl Channel { Ok(Some(res)) } + pub fn remove_custom_output( + &mut self, + custom_output_id: CustomOutputId, + local_settlement_amount_msat: u64, + remote_settlement_amount_msat: u64, + logger: &L + ) -> Result, ChannelError> where L::Target: Logger { + let custom_output = self.pending_custom_outputs.get(&custom_output_id).ok_or(ChannelError::Ignore("Cannot remove custom output which we don't know about".to_owned()))?; + + if (self.channel_state & (ChannelState::ChannelFunded as u32 | BOTH_SIDES_SHUTDOWN_MASK)) != (ChannelState::ChannelFunded as u32) { + return Err(ChannelError::Ignore("Cannot remove custom output until channel is fully established and we haven't started shutting down".to_owned())); + } + + + if (self.channel_state & (ChannelState::PeerDisconnected as u32)) != 0 { + // Note that this should never really happen + return Err(ChannelError::Ignore("Cannot remove custom output while disconnected from channel counterparty".to_owned())); + } + + // We only build this to run checks based on the _current_ commitment transaction i.e. + // before removing the custom output + let keys = self.build_holder_transaction_keys(self.cur_holder_commitment_transaction_number)?; + let commitment_stats = self.build_commitment_transaction(self.cur_holder_commitment_transaction_number, &keys, true, true, logger); + + if (self.channel_state & (ChannelState::AwaitingRemoteRevoke as u32 | ChannelState::MonitorUpdateInProgress as u32)) != 0 { + // TODO: should this be a hashmap as well? + self.holding_cell_custom_output_updates.push( + CustomOutputUpdateAwaitingAck::RemoveCustomOutput { + local_amount_msat: local_settlement_amount_msat, + remote_amount_msat: remote_settlement_amount_msat, + } + ); + return Ok(None); + } + + + self.pending_custom_outputs.insert(custom_output_id, + CustomOutput { + custom_output_id, + amount_local_msat: local_settlement_amount_msat, + amount_remote_msat: remote_settlement_amount_msat, + cltv_expiry: custom_output.cltv_expiry, + state: CustomOutputState::LocalRemoved { + local_profit: local_settlement_amount_msat as i64 - custom_output.amount_local_msat as i64, + } + } + ); + + let res = msgs::UpdateRemoveCustomOutput { + channel_id: self.channel_id, + custom_output_id, + local_amount_msat: local_settlement_amount_msat, + remote_amount_msat: remote_settlement_amount_msat, + }; + + Ok(Some(res)) + } + + /// Creates a signed commitment transaction to send to the remote peer. /// Always returns a ChannelError::Close if an immediately-preceding (read: the /// last call to this Channel) send_htlc returned Ok(Some(_)) and there is an Err. @@ -6126,12 +6216,13 @@ impl Channel { pub fn add_custom_output_and_commit( &mut self, + custom_output_id: CustomOutputId, amount_us_msat: u64, amount_counterparty_msat: u64, cltv_expiry: u32, logger: &L ) -> Result, ChannelError> where L::Target: Logger { - match self.add_custom_output(amount_us_msat, amount_counterparty_msat, cltv_expiry, logger)? { + match self.add_custom_output(custom_output_id, amount_us_msat, amount_counterparty_msat, cltv_expiry, logger)? { Some(update_add_custom_output) => { let (commitment_signed, monitor_update) = self.send_commitment_no_status_check(logger)?; Ok(Some((update_add_custom_output, commitment_signed, monitor_update))) @@ -6140,6 +6231,16 @@ impl Channel { } } + pub fn remove_custom_output_and_commit(&mut self, custom_output_id: CustomOutputId, local_amount: u64, remote_amount: u64, logger: &L) -> Result, ChannelError> where L::Target: Logger{ + match self.remove_custom_output(custom_output_id, local_amount, remote_amount, logger)? { + Some(update_remove_custom_output) => { + let (commitment_signed, monitor_update) = self.send_commitment_no_status_check(logger)?; + Ok(Some((update_remove_custom_output, commitment_signed, monitor_update))) + }, + None => Ok(None), + } + } + /// Get forwarding information for the counterparty. pub fn counterparty_forwarding_info(&self) -> Option { self.counterparty_forwarding_info.clone() @@ -6453,7 +6554,7 @@ impl Writeable for Channel { } (self.pending_custom_outputs.len() as u64).write(writer)?; - for custom_output in self.pending_custom_outputs.iter() { + for (_, custom_output) in self.pending_custom_outputs.iter() { custom_output.custom_output_id.write(writer)?; custom_output.amount_local_msat.write(writer)?; custom_output.amount_remote_msat.write(writer)?; @@ -6480,6 +6581,14 @@ impl Writeable for Channel { CustomOutputState::AwaitingRemovedRemoteRevoke => { 6u8.write(writer)?; }, + CustomOutputState::LocalRemoved { local_profit } => { + 7u8.write(writer)?; + (*local_profit).write(writer)?; + } + CustomOutputState::RemoteRemoved { local_profit } => { + 8u8.write(writer)?; + (*local_profit).write(writer)?; + } } } @@ -6516,6 +6625,12 @@ impl Writeable for Channel { amount_remote_msat.write(writer)?; cltv_expiry.write(writer)?; }, + CustomOutputUpdateAwaitingAck::RemoveCustomOutput { local_amount_msat, remote_amount_msat } => { + 1u8.write(writer)?; + local_amount_msat.write(writer)?; + remote_amount_msat.write(writer)?; + + } } } @@ -6778,6 +6893,8 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<(&'a K, u32)> for Channel }, }); } + // TODO: implement me + let pending_custom_outputs = HashMap::new(); let holding_cell_htlc_update_count: u64 = Readable::read(reader)?; let mut holding_cell_htlc_updates = Vec::with_capacity(cmp::min(holding_cell_htlc_update_count as usize, OUR_MAX_HTLCS as usize * 2)); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 0ce533873a1..2abceda2b63 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -65,6 +65,7 @@ use crate::io; use crate::prelude::*; use core::{cmp, mem}; use core::cell::RefCell; +use core::fmt::{Debug, Display, Formatter}; use crate::io::Read; use crate::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard}; use core::sync::atomic::{AtomicUsize, Ordering}; @@ -191,6 +192,18 @@ struct ClaimableHTLC { #[derive(Hash, Copy, Clone, PartialEq, Eq, Debug)] pub struct PaymentId(pub [u8; 32]); +/// An identifier used to uniquely identify a custom output to LDK. +/// (C-not exported) as we just use [u8; 32] directly +#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug)] +pub struct CustomOutputId(pub [u8; 32]); + +impl Display for CustomOutputId { + + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + std::fmt::Display::fmt(&hex::encode(&self.0), f) + } +} + impl Writeable for PaymentId { fn write(&self, w: &mut W) -> Result<(), io::Error> { self.0.write(w) @@ -203,6 +216,20 @@ impl Readable for PaymentId { Ok(PaymentId(buf)) } } + +impl Writeable for CustomOutputId { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.0.write(w) + } +} + +impl Readable for CustomOutputId { + fn read(r: &mut R) -> Result { + let buf: [u8; 32] = Readable::read(r)?; + Ok(CustomOutputId(buf)) + } +} + /// Tracks the inbound corresponding to an outbound HTLC #[allow(clippy::derive_hash_xor_eq)] // Our Hash is faithful to the data, we just don't have SecretKey::hash #[derive(Clone, PartialEq, Eq, Debug)] @@ -487,6 +514,13 @@ pub(crate) enum PendingOutboundPayment { }, } +struct CustomOutput { + channel_id: [u8;32], + short_channel_id: u64, + local_amount_msat: u64, + remote_amount_msat: u64, +} + impl PendingOutboundPayment { fn is_retryable(&self) -> bool { match self { @@ -747,6 +781,9 @@ pub struct ChannelManager>, + /// Stores alive custom outputs currently asigned to some channel + custom_outputs: Mutex>, + /// SCID/SCID Alias -> forward infos. Key of 0 means payments received. /// /// Note that because we may have an SCID Alias as the key we can have two entries per channel, @@ -1231,6 +1268,17 @@ pub enum PaymentSendFailure { }, } +#[derive(Debug)] +pub enum RemoveCustomOutputError { + CustomOutputNotFound, + ChannelNotFound, + ChannelClosed, + InvalidAmounts, + ChannelNotAvailable, + ChannelFailed, + MonitorInProgress, +} + /// Route hints used in constructing invoices for [phantom node payents]. /// /// [phantom node payments]: crate::chain::keysinterface::PhantomKeysManager @@ -1632,6 +1680,7 @@ impl ChannelMana outbound_scid_aliases: Mutex::new(HashSet::new()), pending_inbound_payments: Mutex::new(HashMap::new()), pending_outbound_payments: Mutex::new(HashMap::new()), + custom_outputs: Mutex::new(Default::default()), forward_htlcs: Mutex::new(HashMap::new()), id_to_peer: Mutex::new(HashMap::new()), @@ -2537,7 +2586,8 @@ impl ChannelMana update_fail_malformed_htlcs: Vec::new(), update_fee: None, commitment_signed, - update_add_custom_output: Vec::new(), + update_add_custom_output: Vec::new(), + update_remove_custom_output: Vec::new() }, }); }, @@ -2682,10 +2732,10 @@ impl ChannelMana &self, short_channel_id: u64, pk_counterparty: PublicKey, - amount_us_msat: u64, - amount_counterparty_msat: u64, + local_amount_msat: u64, + remote_amount_msat: u64, cltv_expiry: u32 - ) -> Result<(), String> { + ) -> Result { let mut channel_lock = self.channel_state.lock().unwrap(); let channel_id = channel_lock .short_to_chan_info @@ -2699,7 +2749,7 @@ impl ChannelMana // something similar for DLCs let channel_holder = &mut *channel_lock; - let mut channel = match channel_holder.by_id.entry(channel_id) { + let mut channel = match channel_holder.by_id.entry(channel_id.clone()) { hash_map::Entry::Occupied(channel) => channel, hash_map::Entry::Vacant(_) => { return Err("No channel available with peer".to_owned()); @@ -2715,24 +2765,32 @@ impl ChannelMana return Err("Peer for first hop currently disconnected/pending monitor update!".to_owned()); } + let custom_output_id = CustomOutputId(self.keys_manager.get_secure_random_bytes()); + + let (update_add, commitment_signed, monitor_update) = - match channel.get_mut().add_custom_output_and_commit( - amount_us_msat, - amount_counterparty_msat, + match channel.get_mut().add_custom_output_and_commit(custom_output_id, + local_amount_msat, + remote_amount_msat, cltv_expiry, &self.logger ) { Ok(Some(res)) => res, Ok(None) => { + self.custom_outputs.lock().unwrap().insert(custom_output_id, CustomOutput { + channel_id, + short_channel_id, + local_amount_msat, + remote_amount_msat + }); // TODO(10101): It's unclear to me what we are supposed to do here. I think `Ok(None)` // should be used like when adding HTLCs: if the channel reports that it is "in // the middle of something" and it should not be modified, we do nothing. The // work should be queued up in `holding_cell_custom_output_updates`, so it can // be picked up later (yet to be implemented. Consider how // `holding_cell_htlc_updates` is used) - - return Ok(()); + return Ok(custom_output_id); } Err(e) => { let (drop, e) = convert_chan_err!(self, e, channel_holder.short_to_chan_info, channel.get_mut(), channel.key()); @@ -2787,9 +2845,119 @@ impl ChannelMana update_fee: None, commitment_signed, update_add_custom_output: vec![update_add], + update_remove_custom_output: Vec::new() + }, + }); + + self.custom_outputs.lock().unwrap().insert(custom_output_id, CustomOutput { + channel_id, + short_channel_id, + local_amount_msat, + remote_amount_msat + }); + Ok(custom_output_id) + } + + pub fn remove_custom_output(&self, custom_output_id: CustomOutputId, local_amount: u64, remote_amount: u64) -> Result<(), RemoveCustomOutputError> { + let guard = self.custom_outputs.lock().unwrap(); + let custom_output = guard.get(&custom_output_id).ok_or(RemoveCustomOutputError::CustomOutputNotFound)?; + + let mut channel_lock = self.channel_state.lock().unwrap(); + let channel_id = custom_output.channel_id; + + if custom_output.local_amount_msat + custom_output.remote_amount_msat != local_amount + remote_amount { + return Err(RemoveCustomOutputError::InvalidAmounts) + } + + let channel_holder = &mut *channel_lock; + let mut channel = match channel_holder.by_id.entry(channel_id.clone()) { + hash_map::Entry::Occupied(channel) => channel, + hash_map::Entry::Vacant(_) => { + return Err(RemoveCustomOutputError::ChannelNotFound); + } + }; + + if !channel.get().is_live() { + return Err(RemoveCustomOutputError::ChannelNotAvailable); + } + + + let pk_counterparty = channel.get().get_counterparty_node_id(); + + let (update_remove_custom_output, commitment_signed, monitor_update) = + match channel.get_mut().remove_custom_output_and_commit(custom_output_id, local_amount, remote_amount, &self.logger) + { + Ok(Some(res)) => res, + Ok(None) => { + self.custom_outputs.lock().unwrap().remove(&custom_output_id); + // TODO(10101): It's unclear to me what we are supposed to do here. I think `Ok(None)` + // should be used like when adding HTLCs: if the channel reports that it is "in + // the middle of something" and it should not be modified, we do nothing. The + // work should be queued up in `holding_cell_custom_output_updates`, so it can + // be picked up later (yet to be implemented. Consider how + // `holding_cell_htlc_updates` is used) + return Ok(()); + } + Err(e) => { + let (drop, e) = convert_chan_err!(self, e, channel_holder.short_to_chan_info, channel.get_mut(), channel.key()); + if drop { + channel.remove_entry(); + } + match handle_error!(self, Result::<(), _>::Err(e), pk_counterparty) { + Ok(_) => unreachable!(), + Err(e) => { + return Err(RemoveCustomOutputError::ChannelNotAvailable); + }, + } + }, + }; + + let update_err = self.chain_monitor.update_channel(channel.get().get_funding_txo().unwrap(), monitor_update); + let channel_id = channel.get().channel_id(); + match (update_err, + handle_monitor_update_res!(self, update_err, channel_holder, channel, + RAACommitmentOrder::CommitmentFirst, false, true)) + { + (ChannelMonitorUpdateStatus::PermanentFailure, Err(e)) => return Err(RemoveCustomOutputError::ChannelFailed), + (ChannelMonitorUpdateStatus::Completed | ChannelMonitorUpdateStatus::InProgress, res) => { + // TODO(10101): Add entry to a `pending_custom_outputs` field in `ChannelManager`? + // let payment = payment_entry.or_insert_with(|| PendingOutboundPayment::Retryable { + // session_privs: HashSet::new(), + // pending_amt_msat: 0, + // pending_fee_msat: Some(0), + // payment_hash: *payment_hash, + // payment_secret: *payment_secret, + // starting_block_height: self.best_block.read().unwrap().height(), + // total_msat: total_value, + // }); + // assert!(payment.insert(session_priv_bytes, path)); + + if res.is_err() { + return Err(RemoveCustomOutputError::MonitorInProgress); + } + }, + _ => unreachable!(), + } + + log_debug!(self.logger, "Adding custom output resulted in a commitment_signed for channel {}", log_bytes!(channel_id)); + + channel_holder.pending_msg_events.push(events::MessageSendEvent::UpdateCommitmentOutputs { + node_id: pk_counterparty, + updates: msgs::CommitmentUpdate { + update_add_htlcs: Vec::new(), + update_fulfill_htlcs: Vec::new(), + update_fail_htlcs: Vec::new(), + update_fail_malformed_htlcs: Vec::new(), + update_fee: None, + commitment_signed, + update_add_custom_output: Vec::new(), + update_remove_custom_output: vec![update_remove_custom_output] }, }); + + self.custom_outputs.lock().unwrap().remove(&custom_output_id); + Ok(()) } @@ -3381,6 +3549,7 @@ impl ChannelMana update_fee: None, commitment_signed: commitment_msg, update_add_custom_output: Vec::new(), + update_remove_custom_output: Vec::new() }, }); } @@ -3649,6 +3818,7 @@ impl ChannelMana update_fee: Some(update_fee), commitment_signed, update_add_custom_output: Vec::new(), + update_remove_custom_output: Vec::new() }, }); Ok(()) @@ -4272,6 +4442,7 @@ impl ChannelMana update_fee: None, commitment_signed, update_add_custom_output: Vec::new(), + update_remove_custom_output: Vec::new() } }); } @@ -4961,6 +5132,24 @@ impl ChannelMana Ok(()) } + + fn internal_update_remove_custom_output(&self, counterparty_node_id: &PublicKey, msg: &msgs::UpdateRemoveCustomOutput) -> Result<(), MsgHandleErrInternal> { + let mut channel_holder = self.channel_state.lock().unwrap(); + let channel_state = &mut *channel_holder; + + match channel_state.by_id.entry(msg.channel_id) { + hash_map::Entry::Occupied(mut channel) => { + if channel.get().get_counterparty_node_id() != *counterparty_node_id { + return Err(MsgHandleErrInternal::send_err_msg_no_close("Got a message for a channel from the wrong node!".to_owned(), msg.channel_id)); + } + try_chan_entry!(self, channel.get_mut().remove_custom_output(msg.custom_output_id, msg.local_amount_msat, msg.remote_amount_msat, &self.logger), channel_state, channel); + }, + hash_map::Entry::Vacant(_) => todo!(), + } + + Ok(()) + } + fn internal_update_fulfill_htlc(&self, counterparty_node_id: &PublicKey, msg: &msgs::UpdateFulfillHTLC) -> Result<(), MsgHandleErrInternal> { let mut channel_lock = self.channel_state.lock().unwrap(); let (htlc_source, forwarded_htlc_value) = { @@ -5052,6 +5241,7 @@ impl ChannelMana update_fee: None, commitment_signed: msg, update_add_custom_output: Vec::new(), + update_remove_custom_output: Vec::new() }, }); } @@ -6143,6 +6333,11 @@ impl let _ = handle_error!(self, self.internal_update_add_custom_output(counterparty_node_id, msg), *counterparty_node_id); } + fn handle_update_remove_custom_output(&self, counterparty_node_id: &PublicKey, msg: &msgs::UpdateRemoveCustomOutput) { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier); + let _ = handle_error!(self, self.internal_update_remove_custom_output(counterparty_node_id, msg), *counterparty_node_id); + } + fn handle_update_fulfill_htlc(&self, counterparty_node_id: &PublicKey, msg: &msgs::UpdateFulfillHTLC) { let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier); let _ = handle_error!(self, self.internal_update_fulfill_htlc(counterparty_node_id, msg), *counterparty_node_id); @@ -7387,6 +7582,9 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> } } + // TODO: implement me! + let custom_outputs = HashMap::new(); + let channel_manager = ChannelManager { genesis_hash, fee_estimator: bounded_fee_estimator, @@ -7405,6 +7603,7 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> pending_inbound_payments: Mutex::new(pending_inbound_payments), pending_outbound_payments: Mutex::new(pending_outbound_payments.unwrap()), + custom_outputs: Mutex::new(custom_outputs), forward_htlcs: Mutex::new(forward_htlcs), outbound_scid_aliases: Mutex::new(outbound_scid_aliases), id_to_peer: Mutex::new(id_to_peer), diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index cddab26c285..d7a79c1b94e 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -1737,7 +1737,7 @@ pub fn do_claim_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, macro_rules! msgs_from_ev { ($ev: expr) => { match $ev { -&MessageSendEvent::UpdateCommitmentOutputs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed, ref update_add_custom_output } } => { +&MessageSendEvent::UpdateCommitmentOutputs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed, ref update_add_custom_output, ref update_remove_custom_output} } => { assert!(update_add_htlcs.is_empty()); assert_eq!(update_fulfill_htlcs.len(), 1); assert!(update_fail_htlcs.is_empty()); @@ -1913,7 +1913,7 @@ pub fn pass_failed_payment_back<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expe assert_eq!(events.len(), expected_paths.len()); for ev in events.iter() { let (update_fail, commitment_signed, node_id) = match ev { - &MessageSendEvent::UpdateCommitmentOutputs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed, ref update_add_custom_output } } => { + &MessageSendEvent::UpdateCommitmentOutputs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed, ref update_add_custom_output, ref update_remove_custom_output } } => { assert!(update_add_htlcs.is_empty()); assert!(update_fulfill_htlcs.is_empty()); assert_eq!(update_fail_htlcs.len(), 1); @@ -1947,7 +1947,7 @@ pub fn pass_failed_payment_back<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expe if update_next_node { assert_eq!(events.len(), 1); match events[0] { - MessageSendEvent::UpdateCommitmentOutputs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed, ref update_add_custom_output } } => { + MessageSendEvent::UpdateCommitmentOutputs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed, ref update_add_custom_output, ref update_remove_custom_output } } => { assert!(update_add_htlcs.is_empty()); assert!(update_fulfill_htlcs.is_empty()); assert_eq!(update_fail_htlcs.len(), 1); diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 938247c1513..4f99c373c9d 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -600,7 +600,7 @@ fn test_update_fee_vanilla() { let events_0 = nodes[0].node.get_and_clear_pending_msg_events(); assert_eq!(events_0.len(), 1); let (update_msg, commitment_signed) = match events_0[0] { - MessageSendEvent::UpdateCommitmentOutputs { node_id:_, updates: msgs::CommitmentUpdate { update_add_htlcs:_, update_fulfill_htlcs:_, update_fail_htlcs:_, update_fail_malformed_htlcs:_, ref update_fee, ref commitment_signed, ref update_add_custom_output } } => { + MessageSendEvent::UpdateCommitmentOutputs { node_id:_, updates: msgs::CommitmentUpdate { update_add_htlcs:_, update_fulfill_htlcs:_, update_fail_htlcs:_, update_fail_malformed_htlcs:_, ref update_fee, ref commitment_signed, ref update_add_custom_output, ref update_remove_custom_output } } => { (update_fee.as_ref(), commitment_signed) }, _ => panic!("Unexpected event"), @@ -771,7 +771,7 @@ fn test_update_fee_with_fundee_update_add_htlc() { let events_0 = nodes[0].node.get_and_clear_pending_msg_events(); assert_eq!(events_0.len(), 1); let (update_msg, commitment_signed) = match events_0[0] { - MessageSendEvent::UpdateCommitmentOutputs { node_id:_, updates: msgs::CommitmentUpdate { update_add_htlcs:_, update_fulfill_htlcs:_, update_fail_htlcs:_, update_fail_malformed_htlcs:_, ref update_fee, ref commitment_signed, ref update_add_custom_output } } => { + MessageSendEvent::UpdateCommitmentOutputs { node_id:_, updates: msgs::CommitmentUpdate { update_add_htlcs:_, update_fulfill_htlcs:_, update_fail_htlcs:_, update_fail_malformed_htlcs:_, ref update_fee, ref commitment_signed, ref update_add_custom_output, ref update_remove_custom_output } } => { (update_fee.as_ref(), commitment_signed) }, _ => panic!("Unexpected event"), @@ -894,7 +894,7 @@ fn test_add_custom_output() { update_fail_malformed_htlcs: _, ref update_fee, ref commitment_signed, - ref update_add_custom_output, + ref update_add_custom_output, ref update_remove_custom_output, }, } => (update_fee.as_ref(), commitment_signed), _ => panic!("Unexpected event"), @@ -1032,7 +1032,7 @@ fn test_add_custom_output() { .clone(); // 2. node1.add_custom_output() (similar to send_payment()?) - nodes[1].node.add_custom_output(short_channel_id, pk_counterparty, amount_node1_msat, amount_node0_msat, cltv_expiry).unwrap(); + let custom_output_id = nodes[1].node.add_custom_output(short_channel_id, pk_counterparty, amount_node1_msat, amount_node0_msat, cltv_expiry).unwrap(); dbg!("Node1 added custom output"); check_added_monitors!(nodes[1], 1); @@ -1085,6 +1085,12 @@ fn test_add_custom_output() { // 11. let custom_output_created = node0.get_pending_events() // 12. let custom_output_created = node1.get_pending_events() + let full_amount = amount_node0_msat + amount_node1_msat; + let final_amount_node0_msat = (full_amount) / 2; + let final_amount_node1_msat = (full_amount) / 2; + nodes[1].node.remove_custom_output(custom_output_id, final_amount_node0_msat, final_amount_node1_msat).unwrap(); + + claim_payment(&nodes[1], &vec!(&nodes[0])[..], our_payment_preimage); send_payment(&nodes[1], &vec!(&nodes[0])[..], 800000); @@ -1130,7 +1136,7 @@ fn test_update_fee() { let events_0 = nodes[0].node.get_and_clear_pending_msg_events(); assert_eq!(events_0.len(), 1); let (update_msg, commitment_signed) = match events_0[0] { - MessageSendEvent::UpdateCommitmentOutputs { node_id:_, updates: msgs::CommitmentUpdate { update_add_htlcs:_, update_fulfill_htlcs:_, update_fail_htlcs:_, update_fail_malformed_htlcs:_, ref update_fee, ref commitment_signed, ref update_add_custom_output } } => { + MessageSendEvent::UpdateCommitmentOutputs { node_id:_, updates: msgs::CommitmentUpdate { update_add_htlcs:_, update_fulfill_htlcs:_, update_fail_htlcs:_, update_fail_malformed_htlcs:_, ref update_fee, ref commitment_signed, ref update_add_custom_output, ref update_remove_custom_output } } => { (update_fee.as_ref(), commitment_signed) }, _ => panic!("Unexpected event"), @@ -1157,7 +1163,7 @@ fn test_update_fee() { let events_0 = nodes[0].node.get_and_clear_pending_msg_events(); assert_eq!(events_0.len(), 1); let (update_msg, commitment_signed) = match events_0[0] { - MessageSendEvent::UpdateCommitmentOutputs { node_id:_, updates: msgs::CommitmentUpdate { update_add_htlcs:_, update_fulfill_htlcs:_, update_fail_htlcs:_, update_fail_malformed_htlcs:_, ref update_fee, ref commitment_signed, ref update_add_custom_output } } => { + MessageSendEvent::UpdateCommitmentOutputs { node_id:_, updates: msgs::CommitmentUpdate { update_add_htlcs:_, update_fulfill_htlcs:_, update_fail_htlcs:_, update_fail_malformed_htlcs:_, ref update_fee, ref commitment_signed, ref update_add_custom_output, ref update_remove_custom_output } } => { (update_fee.as_ref(), commitment_signed) }, _ => panic!("Unexpected event"), @@ -4336,7 +4342,7 @@ fn test_drop_messages_peer_disconnect_dual_htlc() { let events_2 = nodes[1].node.get_and_clear_pending_msg_events(); assert_eq!(events_2.len(), 1); match events_2[0] { - MessageSendEvent::UpdateCommitmentOutputs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed, ref update_add_custom_output } } => { + MessageSendEvent::UpdateCommitmentOutputs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed, ref update_add_custom_output, ref update_remove_custom_output } } => { assert_eq!(*node_id, nodes[0].node.get_our_node_id()); assert!(update_add_htlcs.is_empty()); assert_eq!(update_fulfill_htlcs.len(), 1); @@ -7374,7 +7380,7 @@ fn test_update_fulfill_htlc_bolt2_after_malformed_htlc_message_must_forward_upda assert_eq!(events_3.len(), 1); let update_msg : (msgs::UpdateFailMalformedHTLC, msgs::CommitmentSigned) = { match events_3[0] { - MessageSendEvent::UpdateCommitmentOutputs { node_id: _ , updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed, ref update_add_custom_output } } => { + MessageSendEvent::UpdateCommitmentOutputs { node_id: _ , updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed, ref update_add_custom_output, ref update_remove_custom_output } } => { assert!(update_add_htlcs.is_empty()); assert!(update_fulfill_htlcs.is_empty()); assert!(update_fail_htlcs.is_empty()); @@ -7924,7 +7930,7 @@ fn test_check_htlc_underpaying() { let events = nodes[1].node.get_and_clear_pending_msg_events(); assert_eq!(events.len(), 1); let (update_fail_htlc, commitment_signed) = match events[0] { - MessageSendEvent::UpdateCommitmentOutputs { node_id: _ , updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed, ref update_add_custom_output } } => { + MessageSendEvent::UpdateCommitmentOutputs { node_id: _ , updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed, ref update_add_custom_output, ref update_remove_custom_output } } => { assert!(update_add_htlcs.is_empty()); assert!(update_fulfill_htlcs.is_empty()); assert_eq!(update_fail_htlcs.len(), 1); diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 2b99381969a..ee5bb04a9e5 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -45,6 +45,7 @@ use crate::util::logger; use crate::util::ser::{BigSize, LengthReadable, Readable, ReadableArgs, Writeable, Writer, FixedLengthReader, HighZeroBytesDroppedBigSize, Hostname}; use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret}; +use crate::ln::channelmanager::CustomOutputId; /// 21 million * 10^8 * 1000 pub(crate) const MAX_VALUE_MSAT: u64 = 21_000_000_0000_0000_000; @@ -312,7 +313,7 @@ pub struct UpdateAddCustomOutput { /// The channel ID pub channel_id: [u8; 32], /// The custom output ID - pub custom_output_id: u64, + pub custom_output_id: CustomOutputId, /// The custom output value provided by the local node, in milli-satoshi. pub amount_local_msat: u64, /// The custom output value provided by the remote node, in milli-satoshi. @@ -322,6 +323,19 @@ pub struct UpdateAddCustomOutput { // pub(crate) onion_routing_packet: OnionPacket, TODO(10101): Determine if needed } +/// An update_remove_custom_output message to be sent or received from a peer +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct UpdateRemoveCustomOutput { + /// The channel ID + pub channel_id: [u8; 32], + /// The custom output ID + pub custom_output_id: CustomOutputId, + /// The custom output value given back to the local node, in milli-satoshi. + pub local_amount_msat: u64, + /// The custom output value given back to the remote node, in milli-satoshi. + pub remote_amount_msat: u64, +} + /// An onion message to be sent or received from a peer #[derive(Clone, Debug, PartialEq, Eq)] pub struct OnionMessage { @@ -341,6 +355,7 @@ pub struct UpdateFulfillHTLC { pub payment_preimage: PaymentPreimage, } + /// An update_fail_htlc message to be sent or received from a peer #[derive(Clone, Debug, PartialEq, Eq)] pub struct UpdateFailHTLC { @@ -839,6 +854,8 @@ pub struct CommitmentUpdate { /// TODO(10101): Add docs pub update_add_custom_output: Vec, + /// A list of custom outputs which need to be removed + pub update_remove_custom_output: Vec } /// Messages could have optional fields to use with extended features @@ -882,6 +899,8 @@ pub trait ChannelMessageHandler : MessageSendEventsProvider { fn handle_update_add_htlc(&self, their_node_id: &PublicKey, msg: &UpdateAddHTLC); /// Handle an incoming update_add_custom_output message from the given peer. fn handle_update_add_custom_output(&self, their_node_id: &PublicKey, msg: &UpdateAddCustomOutput); + /// Handle an incoming update_remove_custom_output message from the given peer. + fn handle_update_remove_custom_output(&self, their_node_id: &PublicKey, msg: &UpdateRemoveCustomOutput); /// Handle an incoming update_fulfill_htlc message from the given peer. fn handle_update_fulfill_htlc(&self, their_node_id: &PublicKey, msg: &UpdateFulfillHTLC); /// Handle an incoming update_fail_htlc message from the given peer. @@ -1393,6 +1412,13 @@ impl_writeable_msg!(UpdateFulfillHTLC, { payment_preimage }, {}); +impl_writeable_msg!(UpdateRemoveCustomOutput, { + channel_id, + custom_output_id, + local_amount_msat, + remote_amount_msat +}, {}); + // Note that this is written as a part of ChannelManager objects, and thus cannot change its // serialization format in a way which assumes we know the total serialized length/message end // position. @@ -2011,6 +2037,7 @@ mod tests { use crate::io::{self, Cursor}; use crate::prelude::*; use core::convert::TryFrom; + use crate::ln::channelmanager::CustomOutputId; #[test] fn encoding_channel_reestablish_no_secret() { @@ -2963,11 +2990,25 @@ mod tests { amount_local_msat: 3608586615801332854, amount_remote_msat: 3608586615801332854, cltv_expiry: 821716, - custom_output_id: 42 + custom_output_id: CustomOutputId([4;32]) }; let encoded_value = update_add_custom_output.encode(); - let target_value = hex::decode("0202020202020202020202020202020202020202020202020202020202020202000000000000002a32144668701144763214466870114476000c89d4").unwrap(); + let target_value = hex::decode("0202020202020202020202020202020202020202020202020202020202020202040404040404040404040404040404040404040404040404040404040404040432144668701144763214466870114476000c89d4").unwrap(); assert_eq!(encoded_value, target_value); } + + #[test] + fn encoding_remove_custom_output() { + let remove_custom_output = msgs::UpdateRemoveCustomOutput { + channel_id: [2; 32], + custom_output_id: CustomOutputId([1; 32]), + local_amount_msat: 123, + remote_amount_msat: 456 + }; + let encoded_value = remove_custom_output.encode(); + let target_value = hex::decode("02020202020202020202020202020202020202020202020202020202020202020101010101010101010101010101010101010101010101010101010101010101000000000000007b00000000000001c8").unwrap(); + assert_eq!(encoded_value, target_value); + } + } diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 67fae096aa0..4e529bd1498 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -150,7 +150,7 @@ fn mpp_retry() { // Add the HTLC along the first hop. let fail_path_msgs_1 = events.remove(0); let (update_add, commitment_signed) = match fail_path_msgs_1 { - MessageSendEvent::UpdateCommitmentOutputs { node_id: _, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed, ref update_add_custom_output } } => { + MessageSendEvent::UpdateCommitmentOutputs { node_id: _, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed, ref update_add_custom_output, ref update_remove_custom_output } } => { assert_eq!(update_add_htlcs.len(), 1); assert!(update_fail_htlcs.is_empty()); assert!(update_fulfill_htlcs.is_empty()); diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs index a7da55c9fc3..3a39f79e997 100644 --- a/lightning/src/ln/peer_handler.rs +++ b/lightning/src/ln/peer_handler.rs @@ -19,12 +19,12 @@ use bitcoin::secp256k1::{self, Secp256k1, SecretKey, PublicKey}; use crate::ln::features::{InitFeatures, NodeFeatures}; use crate::ln::msgs; -use crate::ln::msgs::{ChannelMessageHandler, LightningError, NetAddress, OnionMessageHandler, RoutingMessageHandler}; +use crate::ln::msgs::{ChannelMessageHandler, LightningError, NetAddress, OnionMessageHandler, RoutingMessageHandler, UpdateRemoveCustomOutput}; use crate::ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager}; use crate::util::ser::{MaybeReadableArgs, VecWriter, Writeable, Writer}; use crate::ln::peer_channel_encryptor::{PeerChannelEncryptor,NextNoiseStep}; use crate::ln::wire; -use crate::ln::wire::Encode; +use crate::ln::wire::{Encode, Message}; use crate::onion_message::{CustomOnionMessageContents, CustomOnionMessageHandler, SimpleArcOnionMessenger, SimpleRefOnionMessenger}; use crate::routing::gossip::{NetworkGraph, P2PGossipSync}; use crate::util::atomic_counter::AtomicCounter; @@ -202,6 +202,9 @@ impl ChannelMessageHandler for ErroringMessageHandler { fn handle_update_add_custom_output(&self, their_node_id: &PublicKey, msg: &msgs::UpdateAddCustomOutput) { ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); } + fn handle_update_remove_custom_output(&self, their_node_id: &PublicKey, msg: &UpdateRemoveCustomOutput) { + ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); + } fn handle_update_fulfill_htlc(&self, their_node_id: &PublicKey, msg: &msgs::UpdateFulfillHTLC) { ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); } @@ -250,6 +253,7 @@ impl ChannelMessageHandler for ErroringMessageHandler { features.set_zero_conf_optional(); features } + } impl Deref for ErroringMessageHandler { type Target = ErroringMessageHandler; @@ -1451,6 +1455,9 @@ impl { self.custom_message_handler.handle_custom_message(custom, &their_node_id)?; }, + Message::UpdateRemoveCustomOutput(msg) => { + self.message_handler.chan_handler.handle_update_remove_custom_output(&their_node_id, &msg); + } }; Ok(should_forward) } @@ -1648,7 +1655,7 @@ impl { + MessageSendEvent::UpdateCommitmentOutputs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed, ref update_add_custom_output, ref update_remove_custom_output } } => { log_debug!(self.logger, "Handling UpdateCommitmentOutputs event in peer_handler for node {} with {} htlc adds, {} custom adds, {} fulfills, {} fails for channel {}", log_pubkey!(node_id), update_add_htlcs.len(), diff --git a/lightning/src/ln/wire.rs b/lightning/src/ln/wire.rs index 31b791cb0bb..a259b6b89d5 100644 --- a/lightning/src/ln/wire.rs +++ b/lightning/src/ln/wire.rs @@ -64,6 +64,7 @@ pub(crate) enum Message where T: core::fmt::Debug + Type + TestEq { UpdateAddHTLC(msgs::UpdateAddHTLC), UpdateAddCustomOutput(msgs::UpdateAddCustomOutput), UpdateFulfillHTLC(msgs::UpdateFulfillHTLC), + UpdateRemoveCustomOutput(msgs::UpdateRemoveCustomOutput), UpdateFailHTLC(msgs::UpdateFailHTLC), UpdateFailMalformedHTLC(msgs::UpdateFailMalformedHTLC), CommitmentSigned(msgs::CommitmentSigned), @@ -105,6 +106,7 @@ impl Message where T: core::fmt::Debug + Type + TestEq { &Message::OnionMessage(ref msg) => msg.type_id(), &Message::UpdateAddHTLC(ref msg) => msg.type_id(), &Message::UpdateAddCustomOutput(ref msg) => msg.type_id(), + &Message::UpdateRemoveCustomOutput(ref msg) => msg.type_id(), &Message::UpdateFulfillHTLC(ref msg) => msg.type_id(), &Message::UpdateFailHTLC(ref msg) => msg.type_id(), &Message::UpdateFailMalformedHTLC(ref msg) => msg.type_id(), @@ -201,6 +203,9 @@ fn do_read(buffer: &mut R, message_type: u1 msgs::UpdateFulfillHTLC::TYPE => { Ok(Message::UpdateFulfillHTLC(Readable::read(buffer)?)) }, + msgs::UpdateRemoveCustomOutput::TYPE => { + Ok(Message::UpdateRemoveCustomOutput(Readable::read(buffer)?)) + }, msgs::UpdateFailHTLC::TYPE => { Ok(Message::UpdateFailHTLC(Readable::read(buffer)?)) }, @@ -430,6 +435,10 @@ impl Encode for msgs::UpdateAddCustomOutput { const TYPE: u16 = 266; } +impl Encode for msgs::UpdateRemoveCustomOutput { + const TYPE: u16 = 267; +} + #[cfg(test)] mod tests { use super::*; diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs index 1bf30fa9f72..5ee5d9481ce 100644 --- a/lightning/src/util/ser.rs +++ b/lightning/src/util/ser.rs @@ -466,6 +466,7 @@ macro_rules! impl_writeable_primitive { impl_writeable_primitive!(u64, 8); impl_writeable_primitive!(u32, 4); impl_writeable_primitive!(u16, 2); +impl_writeable_primitive!(i64, 8); impl Writeable for u8 { #[inline] diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index c27dccba86e..d5c07a97c0c 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -53,6 +53,7 @@ use crate::chain::keysinterface::{InMemorySigner, Recipient, KeyMaterial}; #[cfg(feature = "std")] use std::time::{SystemTime, UNIX_EPOCH}; use bitcoin::Sequence; +use crate::ln::msgs::UpdateRemoveCustomOutput; pub struct TestVecWriter(pub Vec); impl Writer for TestVecWriter { @@ -368,6 +369,10 @@ impl msgs::ChannelMessageHandler for TestChannelMessageHandler { fn provided_init_features(&self, _their_init_features: &PublicKey) -> InitFeatures { channelmanager::provided_init_features() } + + fn handle_update_remove_custom_output(&self, their_node_id: &PublicKey, msg: &UpdateRemoveCustomOutput) { + self.received_msg(wire::Message::UpdateRemoveCustomOutput(msg.clone())); + } } impl events::MessageSendEventsProvider for TestChannelMessageHandler {