Skip to content

Commit 9b5f40f

Browse files
committed
Plumb an optional fee_claim through register_node
MDK-980 added the fee_claim field to RegisterNodeRequest and the verifier that reads it, but the client still hardcoded None, so no node could ever present a claim. This lets the caller pass one. The claim rides as a per-call argument rather than a field on LSPS4ClientConfig: that config derives Copy, which an Option<String> would break, and the value belongs to the layer above (ldk-node), which already holds it and relays it on every registration. There is a single call site, so threading it as a parameter is the smaller, honest change. The value is opaque here: a lowercase-hex signed grant the LSP alone decodes and verifies. Passing None reproduces today's behavior exactly, so nothing changes until a node is configured with a claim.
1 parent 0a01ca2 commit 9b5f40f

1 file changed

Lines changed: 75 additions & 3 deletions

File tree

lightning-liquidity/src/lsps4/client.rs

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,15 @@ where
8484

8585
/// Requests the LSP to register the node.
8686
///
87+
/// `fee_claim` is an optional lowercase-hex signed grant for a non-standard fee policy; the LSP
88+
/// verifies it against its configured issuer keys. `None` (or an unverifiable claim) leaves the
89+
/// node on the standard policy.
90+
///
8791
/// The user will receive the LSP's response via an [`InvoiceParametersReady`] event.
8892
///
8993
/// [`InvoiceParametersReady`]: crate::lsps4::event::LSPS4ClientEvent::InvoiceParametersReady
9094
pub fn register_node(
91-
&self, counterparty_node_id: PublicKey
95+
&self, counterparty_node_id: PublicKey, fee_claim: Option<String>,
9296
) -> Result<LSPSRequestId, APIError> {
9397
let fn_start = Instant::now();
9498
eprintln!("TIMING: [LSPS4 Client] register_node START for peer {}", counterparty_node_id);
@@ -115,7 +119,7 @@ where
115119
}
116120
}
117121

118-
let request = LSPS4Request::RegisterNode(RegisterNodeRequest { fee_claim: None });
122+
let request = LSPS4Request::RegisterNode(RegisterNodeRequest { fee_claim });
119123
let msg = LSPS4Message::Request(request_id.clone(), request).into();
120124
let mut message_queue_notifier = self.pending_messages.notifier();
121125
message_queue_notifier.enqueue(&counterparty_node_id, msg);
@@ -210,4 +214,72 @@ where
210214
}
211215

212216
#[cfg(test)]
213-
mod tests {}
217+
mod tests {
218+
use super::*;
219+
use crate::lsps0::ser::LSPSMessage;
220+
use bitcoin::secp256k1::{Secp256k1, SecretKey};
221+
use core::sync::atomic::{AtomicU64, Ordering};
222+
use lightning::util::persist::KVStoreSyncWrapper;
223+
use lightning::util::test_utils::TestStore;
224+
use lightning::util::wakers::Notifier;
225+
use std::collections::VecDeque;
226+
227+
struct CountingEntropy {
228+
counter: AtomicU64,
229+
}
230+
231+
impl EntropySource for CountingEntropy {
232+
fn get_secure_random_bytes(&self) -> [u8; 32] {
233+
let counter = self.counter.fetch_add(1, Ordering::SeqCst);
234+
let mut bytes = [0u8; 32];
235+
bytes[0..8].copy_from_slice(&counter.to_be_bytes());
236+
bytes
237+
}
238+
}
239+
240+
type TestKVStore = Arc<KVStoreSyncWrapper<Arc<TestStore>>>;
241+
242+
fn setup() -> (LSPS4ClientHandler<Arc<CountingEntropy>, TestKVStore>, Arc<MessageQueue>, PublicKey)
243+
{
244+
let entropy = Arc::new(CountingEntropy { counter: AtomicU64::new(1) });
245+
let message_queue = Arc::new(MessageQueue::new(Arc::new(Notifier::new())));
246+
let kv_store = Arc::new(KVStoreSyncWrapper(Arc::new(TestStore::new(false))));
247+
let event_queue =
248+
Arc::new(EventQueue::new(VecDeque::new(), kv_store, Arc::new(Notifier::new())));
249+
let client = LSPS4ClientHandler::new(
250+
entropy,
251+
Arc::clone(&message_queue),
252+
event_queue,
253+
LSPS4ClientConfig::default(),
254+
);
255+
let peer = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&[42u8; 32]).unwrap());
256+
(client, message_queue, peer)
257+
}
258+
259+
/// The single enqueued message must be a `register_node` request; returns its `fee_claim`.
260+
fn sole_request_fee_claim(message_queue: &MessageQueue) -> Option<String> {
261+
let mut pending = message_queue.get_and_clear_pending_msgs();
262+
assert_eq!(pending.len(), 1);
263+
match pending.pop().unwrap().1 {
264+
LSPSMessage::LSPS4(LSPS4Message::Request(_, LSPS4Request::RegisterNode(req))) => {
265+
req.fee_claim
266+
},
267+
other => panic!("expected a register_node request, got {:?}", other),
268+
}
269+
}
270+
271+
#[test]
272+
fn register_node_carries_the_claim() {
273+
let (client, message_queue, peer) = setup();
274+
let claim = "deadbeef".to_string();
275+
client.register_node(peer, Some(claim.clone())).unwrap();
276+
assert_eq!(sole_request_fee_claim(&message_queue), Some(claim));
277+
}
278+
279+
#[test]
280+
fn register_node_without_a_claim_omits_it() {
281+
let (client, message_queue, peer) = setup();
282+
client.register_node(peer, None).unwrap();
283+
assert_eq!(sole_request_fee_claim(&message_queue), None);
284+
}
285+
}

0 commit comments

Comments
 (0)