From 6449e1b8e829a4038512946897dc380179422d87 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 Mirror of the same change on the variant branch, applied here to the plain lsp-0.7.0 branch so the two ldk-node lines stay identical. The only difference is the rust-lightning pin: this branch tracks the plain lsp-0.2.0 lineage, so it moves aad7c226 -> bdcdf57de (M1+M2+A1) rather than the variant rev. Both tips now carry the fee-claim field, the verifier, and the new register_node parameter. The 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. 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. This is the field the LSP role on this branch actually uses; mdkd's integration test stands up an in-process LSP from it and seeds the test-vector issuer key. An empty Vec 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 c1cb3e58fd..fe07cdffdf 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/commits/lsp-0.2.0/ -lightning = { git = "https://github.com/moneydevkit/rust-lightning", rev = "aad7c226d7e41f319bfb9d79be4219e2abc9f823", features = ["std"] } -lightning-types = { git = "https://github.com/moneydevkit/rust-lightning", rev = "aad7c226d7e41f319bfb9d79be4219e2abc9f823" } -lightning-invoice = { git = "https://github.com/moneydevkit/rust-lightning", rev = "aad7c226d7e41f319bfb9d79be4219e2abc9f823", features = ["std"] } -lightning-net-tokio = { git = "https://github.com/moneydevkit/rust-lightning", rev = "aad7c226d7e41f319bfb9d79be4219e2abc9f823" } -lightning-persister = { git = "https://github.com/moneydevkit/rust-lightning", rev = "aad7c226d7e41f319bfb9d79be4219e2abc9f823", features = ["tokio"] } -lightning-background-processor = { git = "https://github.com/moneydevkit/rust-lightning", rev = "aad7c226d7e41f319bfb9d79be4219e2abc9f823" } -lightning-rapid-gossip-sync = { git = "https://github.com/moneydevkit/rust-lightning", rev = "aad7c226d7e41f319bfb9d79be4219e2abc9f823" } -lightning-block-sync = { git = "https://github.com/moneydevkit/rust-lightning", rev = "aad7c226d7e41f319bfb9d79be4219e2abc9f823", features = ["rest-client", "rpc-client", "tokio"] } -lightning-transaction-sync = { git = "https://github.com/moneydevkit/rust-lightning", rev = "aad7c226d7e41f319bfb9d79be4219e2abc9f823", features = ["esplora-async-https", "time", "electrum-rustls-ring"] } -lightning-liquidity = { git = "https://github.com/moneydevkit/rust-lightning", rev = "aad7c226d7e41f319bfb9d79be4219e2abc9f823", features = ["std"] } -lightning-macros = { git = "https://github.com/moneydevkit/rust-lightning", rev = "aad7c226d7e41f319bfb9d79be4219e2abc9f823" } +lightning = { git = "https://github.com/moneydevkit/rust-lightning", rev = "bdcdf57de38baeabce4bf8486769bc60d0aa0f8d", features = ["std"] } +lightning-types = { git = "https://github.com/moneydevkit/rust-lightning", rev = "bdcdf57de38baeabce4bf8486769bc60d0aa0f8d" } +lightning-invoice = { git = "https://github.com/moneydevkit/rust-lightning", rev = "bdcdf57de38baeabce4bf8486769bc60d0aa0f8d", features = ["std"] } +lightning-net-tokio = { git = "https://github.com/moneydevkit/rust-lightning", rev = "bdcdf57de38baeabce4bf8486769bc60d0aa0f8d" } +lightning-persister = { git = "https://github.com/moneydevkit/rust-lightning", rev = "bdcdf57de38baeabce4bf8486769bc60d0aa0f8d", features = ["tokio"] } +lightning-background-processor = { git = "https://github.com/moneydevkit/rust-lightning", rev = "bdcdf57de38baeabce4bf8486769bc60d0aa0f8d" } +lightning-rapid-gossip-sync = { git = "https://github.com/moneydevkit/rust-lightning", rev = "bdcdf57de38baeabce4bf8486769bc60d0aa0f8d" } +lightning-block-sync = { git = "https://github.com/moneydevkit/rust-lightning", rev = "bdcdf57de38baeabce4bf8486769bc60d0aa0f8d", features = ["rest-client", "rpc-client", "tokio"] } +lightning-transaction-sync = { git = "https://github.com/moneydevkit/rust-lightning", rev = "bdcdf57de38baeabce4bf8486769bc60d0aa0f8d", features = ["esplora-async-https", "time", "electrum-rustls-ring"] } +lightning-liquidity = { git = "https://github.com/moneydevkit/rust-lightning", rev = "bdcdf57de38baeabce4bf8486769bc60d0aa0f8d", features = ["std"] } +lightning-macros = { git = "https://github.com/moneydevkit/rust-lightning", rev = "bdcdf57de38baeabce4bf8486769bc60d0aa0f8d" } #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/ -lightning = { git = "https://github.com/moneydevkit/rust-lightning", rev = "aad7c226d7e41f319bfb9d79be4219e2abc9f823", features = ["std", "_test_utils"] } +lightning = { git = "https://github.com/moneydevkit/rust-lightning", rev = "bdcdf57de38baeabce4bf8486769bc60d0aa0f8d", 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 5328b368c2..8c358767c8 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 } @@ -1774,7 +1774,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 = { @@ -2053,7 +2057,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 843ffb3421..495e2b5e9c 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -13,7 +13,7 @@ use std::sync::{Arc, Mutex, RwLock}; use std::time::{Duration, 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 bitcoin::Amount; @@ -164,6 +164,7 @@ struct LSPS4Client { lsp_node_id: PublicKey, lsp_address: SocketAddress, ldk_client_config: LdkLSPS4ClientConfig, + fee_claim: Option, pending_register_node_requests: Mutex>>, } @@ -172,6 +173,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, @@ -198,6 +202,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 @@ -304,7 +312,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()); @@ -312,6 +320,7 @@ where lsp_node_id, lsp_address, ldk_client_config, + fee_claim, pending_register_node_requests, }); self @@ -326,12 +335,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 } @@ -1826,7 +1835,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); }