-
Notifications
You must be signed in to change notification settings - Fork 46
Check for spendable_msat on trampoline pay
#585
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 |
|---|---|---|
|
|
@@ -32,6 +32,8 @@ const PAY_UNPARSEABLE_ONION_MSG: &str = "Malformed error reply"; | |
| const PAY_UNPARSEABLE_ONION_CODE: i32 = 202; | ||
| // How long do we wait for channels to re-establish? | ||
| const AWAIT_CHANNELS_TIMEOUT_SEC: u64 = 20; | ||
| // Minimum amount we can send through a channel. | ||
| const MIN_HTLC_AMOUNT: u64 = 1; | ||
|
|
||
| fn feature_guard(features: impl Into<Vec<u8>>, feature_bit: usize) -> Result<()> { | ||
| let mut features = features.into(); | ||
|
|
@@ -215,9 +217,20 @@ pub async fn trampolinepay( | |
| return None; | ||
| } | ||
| }; | ||
| let min_htlc_out_msat = match ch.minimum_htlc_out_msat { | ||
| Some(m) => m.msat(), | ||
| None => { | ||
| warn!( | ||
| "Missing missing minimum_htlc_out_msat on channel with scid={}", | ||
| short_channel_id.to_string() | ||
| ); | ||
| return None; | ||
| } | ||
| }; | ||
| return Some(ChannelData { | ||
| short_channel_id, | ||
| spendable_msat, | ||
| min_htlc_out_msat, | ||
| }); | ||
| }) | ||
| .collect(); | ||
|
|
@@ -231,35 +244,14 @@ pub async fn trampolinepay( | |
|
|
||
| // Await and filter out re-established channels. | ||
| let deadline = Instant::now() + Duration::from_secs(AWAIT_CHANNELS_TIMEOUT_SEC); | ||
| let mut channels = | ||
| let channels = | ||
| reestablished_channels(channels, node_id, rpc_path.as_ref().to_path_buf(), deadline) | ||
| .await?; | ||
|
|
||
| // Note: We can also do this inside the reestablished_channels function | ||
| // but as we want to be greedy picking our channels we don't want to | ||
| // introduce a race of the choosen channels for now. | ||
| let mut acc = 0; | ||
| let mut choosen = vec![]; | ||
| while let Some(channel) = channels.pop() { | ||
| if acc == amount_msat { | ||
| break; | ||
| } | ||
|
|
||
| if (channel.spendable_msat + acc) <= amount_msat { | ||
| choosen.push((channel.short_channel_id, channel.spendable_msat)); | ||
| acc += channel.spendable_msat; | ||
| } else { | ||
| let rest = amount_msat - acc; | ||
| choosen.push((channel.short_channel_id, rest)); | ||
| acc += rest; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| // Check that we found enough spendables | ||
| if acc < amount_msat { | ||
| return Err(anyhow!("missing balance {}msat<{}msat", acc, amount_msat)); | ||
| } | ||
| let choosen = pick_channels(amount_msat, channels)?; | ||
|
|
||
| // FIXME should not be neccessary as we already check on the amount. | ||
| let parts = choosen.len(); | ||
|
|
@@ -347,6 +339,45 @@ pub async fn trampolinepay( | |
| } | ||
| } | ||
|
|
||
| fn pick_channels( | ||
| amount_msat: u64, | ||
| mut channels: Vec<ChannelData>, | ||
| ) -> Result<Vec<(cln_rpc::primitives::ShortChannelId, u64)>> { | ||
| let mut acc = 0; | ||
| let mut choosen = vec![]; | ||
| while let Some(channel) = channels.pop() { | ||
| if acc == amount_msat { | ||
| break; | ||
| } | ||
|
|
||
| // Filter out channels that lack minimum funds and can not send an htlc. | ||
| if std::cmp::max(MIN_HTLC_AMOUNT, channel.min_htlc_out_msat) > channel.spendable_msat { | ||
| debug!("Skip channel {}: has spendable_msat={} and minimum_htlc_out_msat={} and can not send htlc.", | ||
| channel.short_channel_id, | ||
| channel.spendable_msat, | ||
| channel.min_htlc_out_msat, | ||
| ); | ||
| continue; | ||
| } | ||
|
|
||
| if (channel.spendable_msat + acc) <= amount_msat { | ||
| choosen.push((channel.short_channel_id, channel.spendable_msat)); | ||
| acc += channel.spendable_msat; | ||
| } else { | ||
| let rest = amount_msat - acc; | ||
| choosen.push((channel.short_channel_id, rest)); | ||
| acc += rest; | ||
|
Comment on lines
+367
to
+369
Collaborator
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. There's still a case where
Member
Author
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. Right, we may want to check and skip those channels as well. 🤔 |
||
| break; | ||
| } | ||
| } | ||
|
|
||
| if acc < amount_msat { | ||
| return Err(anyhow!("missing balance {}msat<{}msat", acc, amount_msat)); | ||
| } | ||
|
|
||
| Ok(choosen) | ||
| } | ||
|
|
||
| async fn do_pay( | ||
| rpc: &mut ClnRpc, | ||
| node_id: PublicKey, | ||
|
|
@@ -463,6 +494,7 @@ async fn reestablished_channels( | |
| struct ChannelData { | ||
| short_channel_id: cln_rpc::primitives::ShortChannelId, | ||
| spendable_msat: u64, | ||
| min_htlc_out_msat: u64, | ||
| } | ||
|
|
||
| #[derive(Clone, Debug, Deserialize, Serialize)] | ||
|
|
@@ -488,3 +520,49 @@ pub struct SendpayRequest { | |
| pub payment_hash: cln_rpc::primitives::Sha256, | ||
| pub route: Vec<cln_rpc::model::requests::SendpayRoute>, | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn test_picking_channels() { | ||
| let scid1 = ShortChannelId::from_str("100000x100x0").unwrap(); | ||
| let scid2 = ShortChannelId::from_str("100000x101x0").unwrap(); | ||
| let scid3 = ShortChannelId::from_str("100000x102x0").unwrap(); | ||
| let scid4 = ShortChannelId::from_str("100000x103x0").unwrap(); | ||
| let channels = vec![ | ||
| ChannelData { | ||
| short_channel_id: scid1, | ||
| spendable_msat: 100000, | ||
| min_htlc_out_msat: 0, | ||
| }, | ||
| // Below MIN_HTLC_AMOUNT. | ||
| ChannelData { | ||
| short_channel_id: scid2, | ||
| spendable_msat: 0, | ||
| min_htlc_out_msat: 0, | ||
| }, | ||
| // min_htlc_out_msat is larger than spendable_msat. | ||
| ChannelData { | ||
| short_channel_id: scid3, | ||
| spendable_msat: 1, | ||
| min_htlc_out_msat: 2, | ||
| }, | ||
| ChannelData { | ||
| short_channel_id: scid4, | ||
| spendable_msat: 55000, | ||
| min_htlc_out_msat: 55000, | ||
| }, | ||
| ]; | ||
|
|
||
| let amount_msat = 150000; | ||
| let choosen = pick_channels(amount_msat, channels).unwrap(); | ||
|
|
||
| assert_eq!(choosen.len(), 2); | ||
| assert_eq!(choosen[0].0, scid4); | ||
| assert_eq!(choosen[0].1, 55000); | ||
| assert_eq!(choosen[1].0, scid1); | ||
| assert_eq!(choosen[1].1, 95000); | ||
| } | ||
| } | ||
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.
Simpler, but let's keep this as a cleanup, merge this one so we address the issue asap:
And we can also add a second
.filter()that filters out the ones that havespendable_msatbelowmin_htlc_out_msat, no need to mix those conditions either.