diff --git a/lightning-liquidity/src/lsps4/client.rs b/lightning-liquidity/src/lsps4/client.rs index e026eb10417..0f5b96f2ea8 100644 --- a/lightning-liquidity/src/lsps4/client.rs +++ b/lightning-liquidity/src/lsps4/client.rs @@ -83,11 +83,15 @@ where /// Requests the LSP to register the node. /// + /// `fee_claim` is an optional lowercase-hex signed grant for a non-standard fee policy; the LSP + /// verifies it against its configured issuer keys. `None` (or an unverifiable claim) leaves the + /// node on the standard policy. + /// /// The user will receive the LSP's response via an [`InvoiceParametersReady`] event. /// /// [`InvoiceParametersReady`]: crate::lsps4::event::LSPS4ClientEvent::InvoiceParametersReady pub fn register_node( - &self, counterparty_node_id: PublicKey + &self, counterparty_node_id: PublicKey, fee_claim: Option, ) -> Result { let request_id = crate::utils::generate_request_id(&self.entropy_source); @@ -109,7 +113,7 @@ where } } - let request = LSPS4Request::RegisterNode(RegisterNodeRequest { fee_claim: None }); + let request = LSPS4Request::RegisterNode(RegisterNodeRequest { fee_claim }); let msg = LSPS4Message::Request(request_id.clone(), request).into(); let mut message_queue_notifier = self.pending_messages.notifier(); message_queue_notifier.enqueue(&counterparty_node_id, msg); @@ -199,4 +203,72 @@ where } #[cfg(test)] -mod tests {} +mod tests { + use super::*; + use crate::lsps0::ser::LSPSMessage; + use bitcoin::secp256k1::{Secp256k1, SecretKey}; + use core::sync::atomic::{AtomicU64, Ordering}; + use lightning::util::persist::KVStoreSyncWrapper; + use lightning::util::test_utils::TestStore; + use lightning::util::wakers::Notifier; + use std::collections::VecDeque; + + struct CountingEntropy { + counter: AtomicU64, + } + + impl EntropySource for CountingEntropy { + fn get_secure_random_bytes(&self) -> [u8; 32] { + let counter = self.counter.fetch_add(1, Ordering::SeqCst); + let mut bytes = [0u8; 32]; + bytes[0..8].copy_from_slice(&counter.to_be_bytes()); + bytes + } + } + + type TestKVStore = Arc>>; + + fn setup() -> (LSPS4ClientHandler, TestKVStore>, Arc, PublicKey) + { + let entropy = Arc::new(CountingEntropy { counter: AtomicU64::new(1) }); + let message_queue = Arc::new(MessageQueue::new(Arc::new(Notifier::new()))); + let kv_store = Arc::new(KVStoreSyncWrapper(Arc::new(TestStore::new(false)))); + let event_queue = + Arc::new(EventQueue::new(VecDeque::new(), kv_store, Arc::new(Notifier::new()))); + let client = LSPS4ClientHandler::new( + entropy, + Arc::clone(&message_queue), + event_queue, + LSPS4ClientConfig::default(), + ); + let peer = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&[42u8; 32]).unwrap()); + (client, message_queue, peer) + } + + /// The single enqueued message must be a `register_node` request; returns its `fee_claim`. + fn sole_request_fee_claim(message_queue: &MessageQueue) -> Option { + let mut pending = message_queue.get_and_clear_pending_msgs(); + assert_eq!(pending.len(), 1); + match pending.pop().unwrap().1 { + LSPSMessage::LSPS4(LSPS4Message::Request(_, LSPS4Request::RegisterNode(req))) => { + req.fee_claim + }, + other => panic!("expected a register_node request, got {:?}", other), + } + } + + #[test] + fn register_node_carries_the_claim() { + let (client, message_queue, peer) = setup(); + let claim = "deadbeef".to_string(); + client.register_node(peer, Some(claim.clone())).unwrap(); + assert_eq!(sole_request_fee_claim(&message_queue), Some(claim)); + } + + #[test] + fn register_node_without_a_claim_omits_it() { + let (client, message_queue, peer) = setup(); + client.register_node(peer, None).unwrap(); + assert_eq!(sole_request_fee_claim(&message_queue), None); + } +}