-
Notifications
You must be signed in to change notification settings - Fork 405
Track the amount spent on fees as payments are retried #1142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,7 +45,7 @@ use chain::transaction::{OutPoint, TransactionData}; | |
use ln::{PaymentHash, PaymentPreimage, PaymentSecret}; | ||
use ln::channel::{Channel, ChannelError, ChannelUpdateStatus, UpdateFulfillCommitFetch}; | ||
use ln::features::{InitFeatures, NodeFeatures}; | ||
use routing::router::{Payee, Route, RouteHop, RouteParameters}; | ||
use routing::router::{Payee, Route, RouteHop, RoutePath, RouteParameters}; | ||
use ln::msgs; | ||
use ln::msgs::NetAddress; | ||
use ln::onion_utils; | ||
|
@@ -436,6 +436,8 @@ pub(crate) enum PendingOutboundPayment { | |
payment_hash: PaymentHash, | ||
payment_secret: Option<PaymentSecret>, | ||
pending_amt_msat: u64, | ||
/// Used to track the fee paid. Only present if the payment was serialized on 0.0.103+. | ||
pending_fee_msat: Option<u64>, | ||
/// The total payment amount across all paths, used to verify that a retry is not overpaying. | ||
total_msat: u64, | ||
/// Our best known block height at the time this payment was initiated. | ||
|
@@ -462,6 +464,12 @@ impl PendingOutboundPayment { | |
_ => false, | ||
} | ||
} | ||
fn get_pending_fee_msat(&self) -> Option<u64> { | ||
match self { | ||
PendingOutboundPayment::Retryable { pending_fee_msat, .. } => pending_fee_msat.clone(), | ||
_ => None, | ||
} | ||
} | ||
|
||
fn mark_fulfilled(&mut self) { | ||
let mut session_privs = HashSet::new(); | ||
|
@@ -474,8 +482,8 @@ impl PendingOutboundPayment { | |
*self = PendingOutboundPayment::Fulfilled { session_privs }; | ||
} | ||
|
||
/// panics if part_amt_msat is None and !self.is_fulfilled | ||
fn remove(&mut self, session_priv: &[u8; 32], part_amt_msat: Option<u64>) -> bool { | ||
/// panics if path is None and !self.is_fulfilled | ||
fn remove(&mut self, session_priv: &[u8; 32], path: Option<&Vec<RouteHop>>) -> bool { | ||
let remove_res = match self { | ||
PendingOutboundPayment::Legacy { session_privs } | | ||
PendingOutboundPayment::Retryable { session_privs, .. } | | ||
|
@@ -484,14 +492,19 @@ impl PendingOutboundPayment { | |
} | ||
}; | ||
if remove_res { | ||
if let PendingOutboundPayment::Retryable { ref mut pending_amt_msat, .. } = self { | ||
*pending_amt_msat -= part_amt_msat.expect("We must only not provide an amount if the payment was already fulfilled"); | ||
if let PendingOutboundPayment::Retryable { ref mut pending_amt_msat, ref mut pending_fee_msat, .. } = self { | ||
let path = path.expect("Fulfilling a payment should always come with a path"); | ||
let path_last_hop = path.last().expect("Outbound payments must have had a valid path"); | ||
*pending_amt_msat -= path_last_hop.fee_msat; | ||
if let Some(fee_msat) = pending_fee_msat.as_mut() { | ||
*fee_msat -= path.get_path_fees(); | ||
} | ||
} | ||
} | ||
remove_res | ||
} | ||
|
||
fn insert(&mut self, session_priv: [u8; 32], part_amt_msat: u64) -> bool { | ||
fn insert(&mut self, session_priv: [u8; 32], path: &Vec<RouteHop>) -> bool { | ||
let insert_res = match self { | ||
PendingOutboundPayment::Legacy { session_privs } | | ||
PendingOutboundPayment::Retryable { session_privs, .. } => { | ||
|
@@ -500,8 +513,12 @@ impl PendingOutboundPayment { | |
PendingOutboundPayment::Fulfilled { .. } => false | ||
}; | ||
if insert_res { | ||
if let PendingOutboundPayment::Retryable { ref mut pending_amt_msat, .. } = self { | ||
*pending_amt_msat += part_amt_msat; | ||
if let PendingOutboundPayment::Retryable { ref mut pending_amt_msat, ref mut pending_fee_msat, .. } = self { | ||
let path_last_hop = path.last().expect("Outbound payments must have had a valid path"); | ||
*pending_amt_msat += path_last_hop.fee_msat; | ||
if let Some(fee_msat) = pending_fee_msat.as_mut() { | ||
*fee_msat += path.get_path_fees(); | ||
} | ||
} | ||
} | ||
insert_res | ||
|
@@ -2081,12 +2098,13 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana | |
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.last().unwrap().fee_msat)); | ||
assert!(payment.insert(session_priv_bytes, path)); | ||
|
||
send_res | ||
} { | ||
|
@@ -3107,11 +3125,9 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana | |
session_priv_bytes.copy_from_slice(&session_priv[..]); | ||
let mut outbounds = self.pending_outbound_payments.lock().unwrap(); | ||
if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(payment_id) { | ||
let path_last_hop = path.last().expect("Outbound payments must have had a valid path"); | ||
if payment.get_mut().remove(&session_priv_bytes, Some(path_last_hop.fee_msat)) && | ||
!payment.get().is_fulfilled() | ||
{ | ||
if payment.get_mut().remove(&session_priv_bytes, Some(&path)) && !payment.get().is_fulfilled() { | ||
let retry = if let Some(payee_data) = payee { | ||
let path_last_hop = path.last().expect("Outbound payments must have had a valid path"); | ||
Some(RouteParameters { | ||
payee: payee_data, | ||
final_value_msat: path_last_hop.fee_msat, | ||
|
@@ -3164,9 +3180,8 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana | |
session_priv_bytes.copy_from_slice(&session_priv[..]); | ||
let mut outbounds = self.pending_outbound_payments.lock().unwrap(); | ||
let mut all_paths_failed = false; | ||
let path_last_hop = path.last().expect("Outbound payments must have had a valid path"); | ||
if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(payment_id) { | ||
if !payment.get_mut().remove(&session_priv_bytes, Some(path_last_hop.fee_msat)) { | ||
if !payment.get_mut().remove(&session_priv_bytes, Some(&path)) { | ||
log_trace!(self.logger, "Received duplicative fail for HTLC with payment_hash {}", log_bytes!(payment_hash.0)); | ||
return; | ||
} | ||
|
@@ -3183,6 +3198,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana | |
} | ||
mem::drop(channel_state_lock); | ||
let retry = if let Some(payee_data) = payee { | ||
let path_last_hop = path.last().expect("Outbound payments must have had a valid path"); | ||
Some(RouteParameters { | ||
payee: payee_data.clone(), | ||
final_value_msat: path_last_hop.fee_msat, | ||
|
@@ -3457,8 +3473,9 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana | |
let mut session_priv_bytes = [0; 32]; | ||
session_priv_bytes.copy_from_slice(&session_priv[..]); | ||
let mut outbounds = self.pending_outbound_payments.lock().unwrap(); | ||
let found_payment = if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(payment_id) { | ||
if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(payment_id) { | ||
let found_payment = !payment.get().is_fulfilled(); | ||
let fee_paid_msat = payment.get().get_pending_fee_msat(); | ||
payment.get_mut().mark_fulfilled(); | ||
if from_onchain { | ||
// We currently immediately remove HTLCs which were fulfilled on-chain. | ||
|
@@ -3467,22 +3484,22 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana | |
// restart. | ||
// TODO: We should have a second monitor event that informs us of payments | ||
// irrevocably fulfilled. | ||
payment.get_mut().remove(&session_priv_bytes, Some(path.last().unwrap().fee_msat)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the last two params here should be I think it'd be a little more readable if we dropped the lock then called There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These actually should diverge more, see the TODO here - we'd really like to mark the payment fulfilled but not remove the HTLC until after the commitment signed dance completes. |
||
payment.get_mut().remove(&session_priv_bytes, Some(&path)); | ||
if payment.get().remaining_parts() == 0 { | ||
payment.remove(); | ||
} | ||
} | ||
found_payment | ||
} else { false }; | ||
if found_payment { | ||
let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner()); | ||
self.pending_events.lock().unwrap().push( | ||
events::Event::PaymentSent { | ||
payment_id: Some(payment_id), | ||
payment_preimage, | ||
payment_hash: payment_hash | ||
} | ||
); | ||
if found_payment { | ||
let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner()); | ||
self.pending_events.lock().unwrap().push( | ||
events::Event::PaymentSent { | ||
payment_id: Some(payment_id), | ||
payment_preimage, | ||
payment_hash: payment_hash, | ||
fee_paid_msat, | ||
} | ||
); | ||
} | ||
} else { | ||
log_trace!(self.logger, "Received duplicative fulfill for HTLC with payment_preimage {}", log_bytes!(payment_preimage.0)); | ||
} | ||
|
@@ -5495,6 +5512,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, | |
}, | ||
(2, Retryable) => { | ||
(0, session_privs, required), | ||
(1, pending_fee_msat, option), | ||
(2, payment_hash, required), | ||
(4, payment_secret, option), | ||
(6, total_msat, required), | ||
|
@@ -5951,16 +5969,18 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> | |
session_priv_bytes[..].copy_from_slice(&session_priv[..]); | ||
match pending_outbound_payments.as_mut().unwrap().entry(payment_id) { | ||
hash_map::Entry::Occupied(mut entry) => { | ||
let newly_added = entry.get_mut().insert(session_priv_bytes, path_amt); | ||
let newly_added = entry.get_mut().insert(session_priv_bytes, &path); | ||
log_info!(args.logger, "{} a pending payment path for {} msat for session priv {} on an existing pending payment with payment hash {}", | ||
if newly_added { "Added" } else { "Had" }, path_amt, log_bytes!(session_priv_bytes), log_bytes!(htlc.payment_hash.0)); | ||
}, | ||
hash_map::Entry::Vacant(entry) => { | ||
let path_fee = path.get_path_fees(); | ||
entry.insert(PendingOutboundPayment::Retryable { | ||
session_privs: [session_priv_bytes].iter().map(|a| *a).collect(), | ||
payment_hash: htlc.payment_hash, | ||
payment_secret, | ||
pending_amt_msat: path_amt, | ||
pending_fee_msat: Some(path_fee), | ||
total_msat: path_amt, | ||
starting_block_height: best_block_height, | ||
}); | ||
|
@@ -6259,7 +6279,7 @@ mod tests { | |
// further events will be generated for subsequence path successes. | ||
let events = nodes[0].node.get_and_clear_pending_events(); | ||
match events[0] { | ||
Event::PaymentSent { payment_id: ref id, payment_preimage: ref preimage, payment_hash: ref hash } => { | ||
Event::PaymentSent { payment_id: ref id, payment_preimage: ref preimage, payment_hash: ref hash, .. } => { | ||
assert_eq!(Some(payment_id), *id); | ||
assert_eq!(payment_preimage, *preimage); | ||
assert_eq!(our_payment_hash, *hash); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggested wording: "Used to track the fees from paths that have not yet succeeded or failed."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm? No, we use it to track the full fees, we just do that by only tracking the pending fees and then deciding that's the fees actually paid when we succeed, since it will be unless someone gave us free money.