|
| 1 | +use crate::prelude::Vec; |
| 2 | +use lightning::ln::channelmanager::InterceptId; |
| 3 | +use lightning::ln::PaymentHash; |
| 4 | + |
| 5 | +/// Holds payments with the corresponding HTLCs until it is possible to pay the fee. |
| 6 | +/// When the fee is successfully paid with a forwarded payment, the queue should be consumed and the |
| 7 | +/// remaining payments forwarded. |
| 8 | +#[derive(Clone, PartialEq, Eq, Debug)] |
| 9 | +pub(crate) struct PaymentQueue { |
| 10 | + payments: Vec<(PaymentHash, Vec<InterceptedHTLC>)>, |
| 11 | +} |
| 12 | + |
| 13 | +#[derive(Copy, Clone, PartialEq, Eq, Debug)] |
| 14 | +pub(crate) struct InterceptedHTLC { |
| 15 | + pub(crate) intercept_id: InterceptId, |
| 16 | + pub(crate) expected_outbound_amount_msat: u64, |
| 17 | + pub(crate) payment_hash: PaymentHash, |
| 18 | +} |
| 19 | + |
| 20 | +impl PaymentQueue { |
| 21 | + pub(crate) fn new() -> PaymentQueue { |
| 22 | + PaymentQueue { payments: Vec::new() } |
| 23 | + } |
| 24 | + |
| 25 | + pub(crate) fn add_htlc(&mut self, new_htlc: InterceptedHTLC) -> (u64, usize) { |
| 26 | + let payment = self.payments.iter_mut().find(|(p, _)| p == &new_htlc.payment_hash); |
| 27 | + if let Some((payment_hash, htlcs)) = payment { |
| 28 | + // HTLCs within a payment should have the same payment hash. |
| 29 | + debug_assert!(htlcs.iter().all(|htlc| htlc.payment_hash == *payment_hash)); |
| 30 | + // The given HTLC should not already be present. |
| 31 | + debug_assert!(htlcs.iter().all(|htlc| htlc.intercept_id != new_htlc.intercept_id)); |
| 32 | + htlcs.push(new_htlc); |
| 33 | + let total_expected_outbound_amount_msat = |
| 34 | + htlcs.iter().map(|htlc| htlc.expected_outbound_amount_msat).sum(); |
| 35 | + (total_expected_outbound_amount_msat, htlcs.len()) |
| 36 | + } else { |
| 37 | + let expected_outbound_amount_msat = new_htlc.expected_outbound_amount_msat; |
| 38 | + self.payments.push((new_htlc.payment_hash, vec![new_htlc])); |
| 39 | + (expected_outbound_amount_msat, 1) |
| 40 | + } |
| 41 | + } |
| 42 | + |
| 43 | + pub(crate) fn pop_greater_than_msat( |
| 44 | + &mut self, amount_msat: u64, |
| 45 | + ) -> Option<(PaymentHash, Vec<InterceptedHTLC>)> { |
| 46 | + let position = self.payments.iter().position(|(_payment_hash, htlcs)| { |
| 47 | + htlcs.iter().map(|htlc| htlc.expected_outbound_amount_msat).sum::<u64>() >= amount_msat |
| 48 | + }); |
| 49 | + position.map(|position| self.payments.remove(position)) |
| 50 | + } |
| 51 | + |
| 52 | + pub(crate) fn clear(&mut self) -> Vec<InterceptedHTLC> { |
| 53 | + self.payments.drain(..).map(|(_k, v)| v).flatten().collect() |
| 54 | + } |
| 55 | +} |
| 56 | + |
| 57 | +#[cfg(test)] |
| 58 | +mod tests { |
| 59 | + use super::*; |
| 60 | + |
| 61 | + #[test] |
| 62 | + fn test_payment_queue() { |
| 63 | + let mut payment_queue = PaymentQueue::new(); |
| 64 | + assert_eq!( |
| 65 | + payment_queue.add_htlc(InterceptedHTLC { |
| 66 | + intercept_id: InterceptId([0; 32]), |
| 67 | + expected_outbound_amount_msat: 200_000_000, |
| 68 | + payment_hash: PaymentHash([100; 32]), |
| 69 | + }), |
| 70 | + (200_000_000, 1), |
| 71 | + ); |
| 72 | + assert_eq!(payment_queue.pop_greater_than_msat(500_000_000), None); |
| 73 | + |
| 74 | + assert_eq!( |
| 75 | + payment_queue.add_htlc(InterceptedHTLC { |
| 76 | + intercept_id: InterceptId([1; 32]), |
| 77 | + expected_outbound_amount_msat: 300_000_000, |
| 78 | + payment_hash: PaymentHash([101; 32]), |
| 79 | + }), |
| 80 | + (300_000_000, 1), |
| 81 | + ); |
| 82 | + assert_eq!(payment_queue.pop_greater_than_msat(500_000_000), None); |
| 83 | + |
| 84 | + assert_eq!( |
| 85 | + payment_queue.add_htlc(InterceptedHTLC { |
| 86 | + intercept_id: InterceptId([2; 32]), |
| 87 | + expected_outbound_amount_msat: 300_000_000, |
| 88 | + payment_hash: PaymentHash([100; 32]), |
| 89 | + }), |
| 90 | + (500_000_000, 2), |
| 91 | + ); |
| 92 | + assert_eq!( |
| 93 | + payment_queue.pop_greater_than_msat(500_000_000), |
| 94 | + Some(( |
| 95 | + PaymentHash([100; 32]), |
| 96 | + vec![ |
| 97 | + InterceptedHTLC { |
| 98 | + intercept_id: InterceptId([0; 32]), |
| 99 | + expected_outbound_amount_msat: 200_000_000, |
| 100 | + payment_hash: PaymentHash([100; 32]), |
| 101 | + }, |
| 102 | + InterceptedHTLC { |
| 103 | + intercept_id: InterceptId([2; 32]), |
| 104 | + expected_outbound_amount_msat: 300_000_000, |
| 105 | + payment_hash: PaymentHash([100; 32]), |
| 106 | + }, |
| 107 | + ] |
| 108 | + )) |
| 109 | + ); |
| 110 | + assert_eq!( |
| 111 | + payment_queue.clear(), |
| 112 | + vec![InterceptedHTLC { |
| 113 | + intercept_id: InterceptId([1; 32]), |
| 114 | + expected_outbound_amount_msat: 300_000_000, |
| 115 | + payment_hash: PaymentHash([101; 32]), |
| 116 | + }] |
| 117 | + ); |
| 118 | + } |
| 119 | +} |
0 commit comments