From 06509b7a27952fc42ad6a519bbecd39cd64e7107 Mon Sep 17 00:00:00 2001 From: amackillop Date: Thu, 11 Jun 2026 06:24:24 -0700 Subject: [PATCH] Carry a configured fee claim into register_node Bump the rust-lightning pin to 9b5f40f00, which brings the fee-claim wire field, the LSP-side verifier, and the new register_node parameter. That bump forces three coupled changes, so they land together. Client side: LSPS4ClientConfig and the internal LSPS4Client now hold an optional fee_claim, set_liquidity_source_lsps4 takes it as a third argument, and lsps4_register_node relays it on every registration. mdkd is the only caller, so widening the signature is contained. The value is opaque here, a lowercase-hex signed grant that only the LSP decodes. Service side: the bumped LdkLSPS4ServiceConfig grew an issuer_pubkeys field with no Default, so the struct literal no longer compiles without naming it. LSPS4ServiceConfig surfaces it; an empty Vec (the inert default for a node acting only as a client) honours no claim and keeps every peer on the standard policy. With no claim configured the request carries None, the LSP resolves the standard policy, and behaviour is unchanged. --- Cargo.toml | 24 ++++++++++++------------ src/builder.rs | 39 +++++++++++++++++++++++++++++++++++---- src/liquidity.rs | 27 +++++++++++++++++++-------- 3 files changed, 66 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5ff645c3e7..ec996f4d67 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,17 +42,17 @@ default = [] # lightning-macros = { version = "0.2.0" } # Branch: https://github.com/moneydevkit/rust-lightning/tree/lsp-0.2.0_accept-underpaying-htlcs_with_timing_logs -lightning = { git = "https://github.com/moneydevkit/rust-lightning", rev = "f56f47fe6c874771cd079faaacf96c42b32fbe72", features = ["std"] } -lightning-types = { git = "https://github.com/moneydevkit/rust-lightning", rev = "f56f47fe6c874771cd079faaacf96c42b32fbe72" } -lightning-invoice = { git = "https://github.com/moneydevkit/rust-lightning", rev = "f56f47fe6c874771cd079faaacf96c42b32fbe72", features = ["std"] } -lightning-net-tokio = { git = "https://github.com/moneydevkit/rust-lightning", rev = "f56f47fe6c874771cd079faaacf96c42b32fbe72", features = ["socks"] } -lightning-persister = { git = "https://github.com/moneydevkit/rust-lightning", rev = "f56f47fe6c874771cd079faaacf96c42b32fbe72", features = ["tokio"] } -lightning-background-processor = { git = "https://github.com/moneydevkit/rust-lightning", rev = "f56f47fe6c874771cd079faaacf96c42b32fbe72" } -lightning-rapid-gossip-sync = { git = "https://github.com/moneydevkit/rust-lightning", rev = "f56f47fe6c874771cd079faaacf96c42b32fbe72" } -lightning-block-sync = { git = "https://github.com/moneydevkit/rust-lightning", rev = "f56f47fe6c874771cd079faaacf96c42b32fbe72", features = ["rest-client", "rpc-client", "tokio"] } -lightning-transaction-sync = { git = "https://github.com/moneydevkit/rust-lightning", rev = "f56f47fe6c874771cd079faaacf96c42b32fbe72", features = ["esplora-async-https", "time", "electrum-rustls-ring"] } -lightning-liquidity = { git = "https://github.com/moneydevkit/rust-lightning", rev = "f56f47fe6c874771cd079faaacf96c42b32fbe72", features = ["std"] } -lightning-macros = { git = "https://github.com/moneydevkit/rust-lightning", rev = "f56f47fe6c874771cd079faaacf96c42b32fbe72" } +lightning = { git = "https://github.com/moneydevkit/rust-lightning", rev = "9b5f40f00a623ff5e1cbe2540d79c3665589be6e", features = ["std"] } +lightning-types = { git = "https://github.com/moneydevkit/rust-lightning", rev = "9b5f40f00a623ff5e1cbe2540d79c3665589be6e" } +lightning-invoice = { git = "https://github.com/moneydevkit/rust-lightning", rev = "9b5f40f00a623ff5e1cbe2540d79c3665589be6e", features = ["std"] } +lightning-net-tokio = { git = "https://github.com/moneydevkit/rust-lightning", rev = "9b5f40f00a623ff5e1cbe2540d79c3665589be6e", features = ["socks"] } +lightning-persister = { git = "https://github.com/moneydevkit/rust-lightning", rev = "9b5f40f00a623ff5e1cbe2540d79c3665589be6e", features = ["tokio"] } +lightning-background-processor = { git = "https://github.com/moneydevkit/rust-lightning", rev = "9b5f40f00a623ff5e1cbe2540d79c3665589be6e" } +lightning-rapid-gossip-sync = { git = "https://github.com/moneydevkit/rust-lightning", rev = "9b5f40f00a623ff5e1cbe2540d79c3665589be6e" } +lightning-block-sync = { git = "https://github.com/moneydevkit/rust-lightning", rev = "9b5f40f00a623ff5e1cbe2540d79c3665589be6e", features = ["rest-client", "rpc-client", "tokio"] } +lightning-transaction-sync = { git = "https://github.com/moneydevkit/rust-lightning", rev = "9b5f40f00a623ff5e1cbe2540d79c3665589be6e", features = ["esplora-async-https", "time", "electrum-rustls-ring"] } +lightning-liquidity = { git = "https://github.com/moneydevkit/rust-lightning", rev = "9b5f40f00a623ff5e1cbe2540d79c3665589be6e", features = ["std"] } +lightning-macros = { git = "https://github.com/moneydevkit/rust-lightning", rev = "9b5f40f00a623ff5e1cbe2540d79c3665589be6e" } #lightning = { path = "../rust-lightning/lightning", features = ["std"] } #lightning-types = { path = "../rust-lightning/lightning-types" } @@ -101,7 +101,7 @@ winapi = { version = "0.3", features = ["winbase"] } [dev-dependencies] # lightning = { version = "0.2.0", features = ["std", "_test_utils"] } # Branch: https://github.com/moneydevkit/rust-lightning/commits/lsp-0.2.0_accept-underpaying-htlcs_with_timing_logs -lightning = { git = "https://github.com/moneydevkit/rust-lightning", rev = "f56f47fe6c874771cd079faaacf96c42b32fbe72", features = ["std", "_test_utils"] } +lightning = { git = "https://github.com/moneydevkit/rust-lightning", rev = "9b5f40f00a623ff5e1cbe2540d79c3665589be6e", features = ["std", "_test_utils"] } #lightning = { path = "../rust-lightning/lightning", features = ["std", "_test_utils"] } proptest = "1.0.0" regex = "1.5.6" diff --git a/src/builder.rs b/src/builder.rs index 79c2569e1f..ce47538a40 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -489,14 +489,14 @@ impl NodeBuilder { /// /// Will mark the LSP as trusted for 0-confirmation channels, see [`Config::trusted_peers_0conf`]. pub fn set_liquidity_source_lsps4( - &mut self, node_id: PublicKey, address: SocketAddress, + &mut self, node_id: PublicKey, address: SocketAddress, fee_claim: Option, ) -> &mut Self { // Mark the LSP as trusted for 0conf self.config.trusted_peers_0conf.push(node_id.clone()); let liquidity_source_config = self.liquidity_source_config.get_or_insert(LiquiditySourceConfig::default()); - let lsps4_client_config = LSPS4ClientConfig { node_id, address }; + let lsps4_client_config = LSPS4ClientConfig { node_id, address, fee_claim }; liquidity_source_config.lsps4_client = Some(lsps4_client_config); self } @@ -1873,7 +1873,11 @@ fn build_with_store_internal( }); lsc.lsps4_client.as_ref().map(|config| { - liquidity_source_builder.lsps4_client(config.node_id, config.address.clone()) + liquidity_source_builder.lsps4_client( + config.node_id, + config.address.clone(), + config.fee_claim.clone(), + ) }); let promise_secret = { @@ -2152,7 +2156,34 @@ pub(crate) fn sanitize_alias(alias_str: &str) -> Result { #[cfg(test)] mod tests { - use super::{sanitize_alias, BuildError, NodeAlias}; + use super::{sanitize_alias, BuildError, NodeAlias, NodeBuilder}; + use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; + use lightning::ln::msgs::SocketAddress; + + fn dummy_lsp() -> (PublicKey, SocketAddress) { + let node_id = + PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&[1u8; 32]).unwrap()); + let address = SocketAddress::TcpIpV4 { addr: [127, 0, 0, 1], port: 9735 }; + (node_id, address) + } + + #[test] + fn lsps4_source_stores_the_fee_claim() { + let (node_id, address) = dummy_lsp(); + let mut builder = NodeBuilder::new(); + builder.set_liquidity_source_lsps4(node_id, address, Some("deadbeef".to_string())); + let config = builder.liquidity_source_config.unwrap().lsps4_client.unwrap(); + assert_eq!(config.fee_claim, Some("deadbeef".to_string())); + } + + #[test] + fn lsps4_source_without_a_claim_stores_none() { + let (node_id, address) = dummy_lsp(); + let mut builder = NodeBuilder::new(); + builder.set_liquidity_source_lsps4(node_id, address, None); + let config = builder.liquidity_source_config.unwrap().lsps4_client.unwrap(); + assert_eq!(config.fee_claim, None); + } #[test] fn sanitize_empty_node_alias() { diff --git a/src/liquidity.rs b/src/liquidity.rs index fb725435b8..4e019ba5d7 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -13,7 +13,7 @@ use std::sync::{Arc, Mutex, RwLock}; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use bitcoin::hashes::{sha256, Hash}; -use bitcoin::secp256k1::{PublicKey, Secp256k1}; +use bitcoin::secp256k1::{PublicKey, Secp256k1, XOnlyPublicKey}; use bitcoin::Transaction; use chrono::Utc; use lightning::events::HTLCHandlingFailureType; @@ -155,6 +155,7 @@ struct LSPS4Client { lsp_node_id: PublicKey, lsp_address: SocketAddress, ldk_client_config: LdkLSPS4ClientConfig, + fee_claim: Option, pending_register_node_requests: Mutex>>, } @@ -163,6 +164,9 @@ struct LSPS4Client { pub(crate) struct LSPS4ClientConfig { pub node_id: PublicKey, pub address: SocketAddress, + /// Opaque lowercase-hex signed grant presented on `register_node`; the LSP verifies it against + /// its configured issuer keys. `None` leaves the node on the standard fee policy. + pub fee_claim: Option, } struct LSPS4Service { service_config: LSPS4ServiceConfig, @@ -189,6 +193,10 @@ pub struct LSPS4ServiceConfig { /// channels with the peer. The first entry applies when there are no channels, the second /// when there is already one, and so on. If empty, no tiering is applied. pub channel_size_tiers: Vec, + /// X-only public keys whose signatures grant a non-standard fee policy. A `register_node` + /// fee claim is honoured only if it verifies against one of these keys. Empty means no claim + /// is ever honoured and every peer stays on the standard policy. + pub issuer_pubkeys: Vec, } pub(crate) struct LiquiditySourceBuilder @@ -284,7 +292,7 @@ where } pub(crate) fn lsps4_client( - &mut self, lsp_node_id: PublicKey, lsp_address: SocketAddress, + &mut self, lsp_node_id: PublicKey, lsp_address: SocketAddress, fee_claim: Option, ) -> &mut Self { let ldk_client_config = LdkLSPS4ClientConfig {}; let pending_register_node_requests = Mutex::new(HashMap::new()); @@ -292,6 +300,7 @@ where lsp_node_id, lsp_address, ldk_client_config, + fee_claim, pending_register_node_requests, }); self @@ -306,12 +315,12 @@ where } pub(crate) fn lsps4_service(&mut self, service_config: LSPS4ServiceConfig) -> &mut Self { - let ldk_service_config = - LdkLSPS4ServiceConfig { - cltv_expiry_delta: LSPS2_CHANNEL_CLTV_EXPIRY_DELTA, - forwarding_fee_proportional_millionths: service_config + let ldk_service_config = LdkLSPS4ServiceConfig { + cltv_expiry_delta: LSPS2_CHANNEL_CLTV_EXPIRY_DELTA, + forwarding_fee_proportional_millionths: service_config .forwarding_fee_proportional_millionths, - }; + issuer_pubkeys: service_config.issuer_pubkeys.clone(), + }; self.lsps4_service = Some(LSPS4Service { service_config, ldk_service_config }); self } @@ -1684,7 +1693,9 @@ where { let mut pending_register_node_requests_lock = lsps4_client.pending_register_node_requests.lock().unwrap(); - let request_id = client_handler.register_node(lsps4_client.lsp_node_id).unwrap(); + let request_id = client_handler + .register_node(lsps4_client.lsp_node_id, lsps4_client.fee_claim.clone()) + .unwrap(); pending_register_node_requests_lock.insert(request_id, register_node_sender); } log_info!(self.logger, "TIMING: lsps4_register_node() sent request, waiting for response...");