1#![doc = include_str!("../README.md")]
4
5use std::fmt;
6use std::net::SocketAddr;
7use std::pin::Pin;
8use std::sync::atomic::{AtomicBool, Ordering};
9use std::sync::Arc;
10use std::time::Duration;
11
12use async_trait::async_trait;
13use bip39::Mnemonic;
14use cdk_common::common::FeeReserve;
15use cdk_common::database::DynKVStore;
16use cdk_common::payment::{self, *};
17use cdk_common::redact::url_for_logs;
18use cdk_common::util::{hex, unix_time};
19use cdk_common::{Amount, CurrencyUnit, MeltOptions, MeltQuoteState, QuoteId};
20use futures::{Stream, StreamExt};
21use ldk_node::bitcoin::hashes::Hash;
22use ldk_node::bitcoin::Network;
23use ldk_node::lightning::ln::channelmanager::PaymentId;
24use ldk_node::lightning::ln::msgs::SocketAddress;
25use ldk_node::lightning::routing::router::RouteParametersConfig;
26use ldk_node::lightning_invoice::{Bolt11InvoiceDescription, Description};
27use ldk_node::lightning_types::payment::PaymentHash;
28use ldk_node::logger::{LogLevel, LogWriter};
29use ldk_node::payment::{PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus};
30use ldk_node::{Builder, Event, Node};
31use tokio_stream::wrappers::BroadcastStream;
32use tokio_util::sync::CancellationToken;
33use tracing::instrument;
34
35use crate::error::Error;
36use crate::log::StdoutLogWriter;
37
38mod error;
39mod log;
40mod web;
41
42const LDK_KV_PRIMARY_NAMESPACE: &str = "cdk_ldk_node_lightning_backend";
44const LDK_KV_BOLT12_OUTGOING_SECONDARY_NAMESPACE: &str = "bolt12_outgoing_payments";
47const PAYMENT_WAIT_TIMEOUT: Duration = Duration::from_secs(10);
49const PAYMENT_EVENT_CHANNEL_CAPACITY: usize = 64;
51const LDK_KV_BOLT12_CLEANUP_MARKER: &[u8] = b"cleanup-in-progress";
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55enum Bolt12QuotePaymentIdLookup {
56 Found(PaymentId),
58 Dispatching,
62 Missing,
64 Malformed,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69enum Bolt12QuotePaymentIdResolution {
70 PaymentId(PaymentId),
71 Status(MeltQuoteState),
72}
73
74impl Bolt12QuotePaymentIdLookup {
75 fn resolve(self) -> Bolt12QuotePaymentIdResolution {
76 match self {
77 Self::Found(payment_id) => Bolt12QuotePaymentIdResolution::PaymentId(payment_id),
78 Self::Dispatching => Bolt12QuotePaymentIdResolution::Status(MeltQuoteState::Pending),
82 Self::Missing => Bolt12QuotePaymentIdResolution::Status(MeltQuoteState::Unpaid),
84 Self::Malformed => Bolt12QuotePaymentIdResolution::Status(MeltQuoteState::Unknown),
86 }
87 }
88}
89
90fn bolt12_send_error_has_ambiguous_dispatch(err: &ldk_node::NodeError) -> bool {
97 matches!(err, ldk_node::NodeError::PersistenceFailed)
98}
99
100fn bolt11_send_error_is_explicit_terminal_failure(err: &ldk_node::NodeError) -> bool {
109 matches!(
110 err,
111 ldk_node::NodeError::NotRunning
112 | ldk_node::NodeError::InvalidAmount
113 | ldk_node::NodeError::InvalidInvoice
114 | ldk_node::NodeError::PaymentSendingFailed
115 )
116}
117
118fn outgoing_payment_failure_response(
119 unit: &CurrencyUnit,
120 payment_lookup_id: PaymentIdentifier,
121) -> MakePaymentResponse {
122 MakePaymentResponse {
123 payment_lookup_id,
124 payment_proof: None,
125 status: MeltQuoteState::Failed,
126 total_spent: Amount::new(0, unit.clone()),
127 }
128}
129
130#[derive(Clone)]
135pub struct CdkLdkNode {
136 inner: Arc<Node>,
137 fee_reserve: FeeReserve,
138 kv_store: DynKVStore,
139 wait_invoice_cancel_token: CancellationToken,
140 wait_invoice_is_active: Arc<AtomicBool>,
141 sender: tokio::sync::broadcast::Sender<WaitPaymentResponse>,
142 receiver: Arc<tokio::sync::broadcast::Receiver<WaitPaymentResponse>>,
143 outgoing_payment_sender: tokio::sync::broadcast::Sender<PaymentId>,
144 events_cancel_token: CancellationToken,
145 web_addr: Option<SocketAddr>,
146}
147
148impl fmt::Debug for CdkLdkNode {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 f.debug_struct("CdkLdkNode")
151 .field("fee_reserve", &self.fee_reserve)
152 .field("web_addr", &self.web_addr)
153 .finish_non_exhaustive()
154 }
155}
156
157#[derive(Clone)]
161pub struct BitcoinRpcConfig {
162 pub host: String,
164 pub port: u16,
166 pub user: String,
168 pub password: String,
170}
171
172impl fmt::Debug for BitcoinRpcConfig {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 f.debug_struct("BitcoinRpcConfig")
175 .field("host", &self.host)
176 .field("port", &self.port)
177 .field("user", &self.user)
178 .field("password", &"[REDACTED]")
179 .finish()
180 }
181}
182
183#[derive(Clone)]
188pub enum ChainSource {
189 Esplora(String),
193 Electrum(String),
197 BitcoinRpc(BitcoinRpcConfig),
201}
202
203impl fmt::Debug for ChainSource {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 match self {
206 Self::Esplora(url) => f.debug_tuple("Esplora").field(&url_for_logs(url)).finish(),
207 Self::Electrum(url) => f.debug_tuple("Electrum").field(&url_for_logs(url)).finish(),
208 Self::BitcoinRpc(config) => f.debug_tuple("BitcoinRpc").field(config).finish(),
209 }
210 }
211}
212
213#[derive(Clone)]
218pub enum GossipSource {
219 P2P,
223 RapidGossipSync(String),
227}
228
229impl fmt::Debug for GossipSource {
230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231 match self {
232 Self::P2P => f.write_str("P2P"),
233 Self::RapidGossipSync(url) => f
234 .debug_tuple("RapidGossipSync")
235 .field(&url_for_logs(url))
236 .finish(),
237 }
238 }
239}
240pub struct CdkLdkNodeBuilder {
242 network: Network,
243 chain_source: ChainSource,
244 gossip_source: GossipSource,
245 log_dir_path: Option<String>,
246 storage_dir_path: String,
247 fee_reserve: FeeReserve,
248 kv_store: DynKVStore,
249 listening_addresses: Vec<SocketAddress>,
250 seed: Option<Mnemonic>,
251 announcement_addresses: Option<Vec<SocketAddress>>,
252}
253
254impl std::fmt::Debug for CdkLdkNodeBuilder {
255 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256 f.debug_struct("CdkLdkNodeBuilder")
257 .field("network", &self.network)
258 .field("chain_source", &self.chain_source)
259 .field("gossip_source", &self.gossip_source)
260 .field("log_dir_path", &self.log_dir_path)
261 .field("storage_dir_path", &self.storage_dir_path)
262 .field("fee_reserve", &self.fee_reserve)
263 .field("listening_addresses", &self.listening_addresses)
264 .field("announcement_addresses", &self.announcement_addresses)
265 .finish_non_exhaustive()
266 }
267}
268
269impl CdkLdkNodeBuilder {
270 pub fn new(
272 network: Network,
273 chain_source: ChainSource,
274 gossip_source: GossipSource,
275 storage_dir_path: String,
276 fee_reserve: FeeReserve,
277 listening_addresses: Vec<SocketAddress>,
278 kv_store: DynKVStore,
279 ) -> Self {
280 Self {
281 network,
282 chain_source,
283 gossip_source,
284 storage_dir_path,
285 fee_reserve,
286 kv_store,
287 listening_addresses,
288 seed: None,
289 announcement_addresses: None,
290 log_dir_path: None,
291 }
292 }
293
294 pub fn with_seed(mut self, seed: Mnemonic) -> Self {
296 self.seed = Some(seed);
297 self
298 }
299 pub fn with_announcement_address(mut self, announcement_addresses: Vec<SocketAddress>) -> Self {
301 self.announcement_addresses = Some(announcement_addresses);
302 self
303 }
304 pub fn with_log_dir_path(mut self, log_dir_path: String) -> Self {
306 self.log_dir_path = Some(log_dir_path);
307 self
308 }
309
310 pub fn build(self) -> Result<CdkLdkNode, Error> {
315 let mut ldk = Builder::new();
316 ldk.set_network(self.network);
317 tracing::info!("Storage dir of node is {}", self.storage_dir_path);
318 ldk.set_storage_dir_path(self.storage_dir_path);
319
320 match self.chain_source {
321 ChainSource::Esplora(esplora_url) => {
322 ldk.set_chain_source_esplora(esplora_url, None);
323 }
324 ChainSource::Electrum(electrum_url) => {
325 ldk.set_chain_source_electrum(electrum_url, None);
326 }
327 ChainSource::BitcoinRpc(BitcoinRpcConfig {
328 host,
329 port,
330 user,
331 password,
332 }) => {
333 ldk.set_chain_source_bitcoind_rpc(host, port, user, password);
334 }
335 }
336
337 match self.gossip_source {
338 GossipSource::P2P => {
339 ldk.set_gossip_source_p2p();
340 }
341 GossipSource::RapidGossipSync(rgs_url) => {
342 ldk.set_gossip_source_rgs(rgs_url);
343 }
344 }
345
346 ldk.set_listening_addresses(self.listening_addresses)?;
347 if self.log_dir_path.is_some() {
348 ldk.set_filesystem_logger(self.log_dir_path, Some(LogLevel::Info));
349 } else {
350 ldk.set_custom_logger(Arc::new(StdoutLogWriter));
351 }
352
353 ldk.set_node_alias("cdk-ldk-node".to_string())?;
354 if let Some(seed) = self.seed {
356 ldk.set_entropy_bip39_mnemonic(seed, None);
357 }
358 if let Some(announcement_addresses) = self.announcement_addresses {
360 ldk.set_announcement_addresses(announcement_addresses)?;
361 }
362
363 let node = ldk.build()?;
364
365 tracing::info!("Creating tokio channel for payment notifications");
366 let (sender, receiver) = tokio::sync::broadcast::channel(8);
367 let (outgoing_payment_sender, _) =
368 tokio::sync::broadcast::channel(PAYMENT_EVENT_CHANNEL_CAPACITY);
369
370 let id = node.node_id();
371
372 let adr = node.announcement_addresses();
373
374 tracing::info!(
375 "Created node {} with address {:?} on network {}",
376 id,
377 adr,
378 self.network
379 );
380
381 Ok(CdkLdkNode {
382 inner: node.into(),
383 fee_reserve: self.fee_reserve,
384 kv_store: self.kv_store,
385 wait_invoice_cancel_token: CancellationToken::new(),
386 wait_invoice_is_active: Arc::new(AtomicBool::new(false)),
387 sender,
388 receiver: Arc::new(receiver),
389 outgoing_payment_sender,
390 events_cancel_token: CancellationToken::new(),
391 web_addr: None,
392 })
393 }
394}
395
396impl CdkLdkNode {
397 pub fn set_web_addr(&mut self, addr: Option<SocketAddr>) {
402 self.web_addr = addr;
403 }
404
405 pub fn default_web_addr() -> SocketAddr {
410 SocketAddr::from(([127, 0, 0, 1], 8091))
411 }
412
413 async fn cleanup_bolt12_dispatch_binding(
416 &self,
417 quote_id: &QuoteId,
418 payment_id: Option<&PaymentId>,
419 ) {
420 match delete_bolt12_quote_payment_id_if_equals(&self.kv_store, quote_id, payment_id).await {
421 Ok(true) => {}
422 Ok(false) => {
423 tracing::debug!(
424 quote_id = %quote_id,
425 "BOLT12 dispatch binding changed before cleanup"
426 );
427 }
428 Err(err) => {
429 tracing::warn!(
430 quote_id = %quote_id,
431 "Could not release BOLT12 dispatch binding: {err}"
432 );
433 }
434 }
435 }
436
437 fn make_payment_response_from_details(
438 unit: &CurrencyUnit,
439 payment_lookup_id: PaymentIdentifier,
440 payment_details: &PaymentDetails,
441 ) -> Result<MakePaymentResponse, payment::Error> {
442 let status = match payment_details.status {
443 PaymentStatus::Pending => MeltQuoteState::Pending,
444 PaymentStatus::Succeeded => MeltQuoteState::Paid,
445 PaymentStatus::Failed => MeltQuoteState::Failed,
446 };
447
448 let payment_proof = match &payment_details.kind {
449 PaymentKind::Bolt11 { preimage, .. } => preimage.map(|p| p.to_string()),
450 PaymentKind::Bolt12Offer { preimage, .. } => preimage.map(|p| p.to_string()),
451 _ => return Err(Error::UnexpectedPaymentKind.into()),
452 };
453
454 let total_spent = if status == MeltQuoteState::Paid {
455 let total_spent = payment_details
456 .amount_msat
457 .ok_or(Error::CouldNotGetAmountSpent)?
458 + payment_details.fee_paid_msat.unwrap_or_default();
459 Amount::new(total_spent, CurrencyUnit::Msat).convert_to(unit)?
460 } else {
461 Amount::new(0, unit.clone())
462 };
463
464 Ok(MakePaymentResponse {
465 payment_lookup_id,
466 payment_proof,
467 status,
468 total_spent,
469 })
470 }
471
472 fn select_bolt11_payment_details(
473 payment_details: impl IntoIterator<Item = PaymentDetails>,
474 ) -> Option<PaymentDetails> {
475 payment_details.into_iter().min_by_key(|details| {
476 let status_order = match details.status {
477 PaymentStatus::Succeeded => 0_u8,
478 PaymentStatus::Pending => 1,
479 PaymentStatus::Failed => 2,
480 };
481
482 (
483 status_order,
484 std::cmp::Reverse(details.latest_update_timestamp),
485 )
486 })
487 }
488
489 async fn wait_for_terminal_payment_event(
490 receiver: &mut tokio::sync::broadcast::Receiver<PaymentId>,
491 payment_id: PaymentId,
492 ) -> Result<(), tokio::sync::broadcast::error::RecvError> {
493 loop {
494 match receiver.recv().await {
495 Ok(completed_payment_id) if completed_payment_id == payment_id => return Ok(()),
496 Ok(_) => continue,
497 Err(tokio::sync::broadcast::error::RecvError::Lagged(skipped)) => {
498 tracing::warn!(
499 payment_id = %payment_id,
500 skipped,
501 "Terminal payment event receiver lagged; continuing to wait"
502 );
503 }
504 Err(err) => return Err(err),
505 }
506 }
507 }
508
509 async fn wait_for_payment_terminal_status(
510 &self,
511 payment_id: PaymentId,
512 mut receiver: tokio::sync::broadcast::Receiver<PaymentId>,
513 ) -> Result<PaymentDetails, payment::Error> {
514 let payment_details = self
515 .inner
516 .payment(&payment_id)
517 .ok_or(Error::PaymentNotFound)?;
518
519 if payment_details.status != PaymentStatus::Pending {
520 return Ok(payment_details);
521 }
522
523 match tokio::time::timeout(
524 PAYMENT_WAIT_TIMEOUT,
525 Self::wait_for_terminal_payment_event(&mut receiver, payment_id),
526 )
527 .await
528 {
529 Ok(Ok(())) => {}
530 Ok(Err(err)) => {
531 tracing::warn!(
532 payment_id = %payment_id,
533 "Could not wait for terminal LDK payment event: {err}"
534 );
535 }
536 Err(_) => {
537 tracing::warn!(
538 payment_id = %payment_id,
539 "Payment did not reach a terminal state within {} seconds",
540 PAYMENT_WAIT_TIMEOUT.as_secs()
541 );
542 }
543 }
544
545 let payment_details = self
546 .inner
547 .payment(&payment_id)
548 .ok_or(Error::PaymentNotFound)?;
549
550 if payment_details.status == PaymentStatus::Pending {
551 tracing::debug!(
552 payment_id = %payment_id,
553 "Payment remains pending after waiting for a terminal event"
554 );
555 }
556
557 Ok(payment_details)
558 }
559
560 pub fn start_ldk_node(&self) -> Result<(), Error> {
571 tracing::info!("Starting cdk-ldk node");
572 self.inner.start()?;
573 let node_config = self.inner.config();
574
575 tracing::info!("Starting node with network {}", node_config.network);
576
577 tracing::info!("Node status: {:?}", self.inner.status());
578
579 self.handle_events()?;
580
581 Ok(())
582 }
583
584 pub fn start_web_server(&self, web_addr: SocketAddr) -> Result<(), Error> {
599 let web_server = crate::web::WebServer::new(Arc::new(self.clone()));
600
601 tokio::spawn(async move {
602 if let Err(e) = web_server.serve(web_addr).await {
603 tracing::error!("Web server error: {}", e);
604 }
605 });
606
607 Ok(())
608 }
609
610 pub fn stop_ldk_node(&self) -> Result<(), Error> {
624 tracing::info!("Stopping CdkLdkNode");
625 tracing::info!("Cancelling event handler");
627 self.events_cancel_token.cancel();
628
629 if self.is_payment_event_stream_active() {
631 tracing::info!("Cancelling payment event stream");
632 self.wait_invoice_cancel_token.cancel();
633 }
634
635 tracing::info!("Stopping LDK node");
637 self.inner.stop()?;
638 tracing::info!("CdkLdkNode stopped successfully");
639 Ok(())
640 }
641
642 async fn handle_payment_received(
644 node: &Arc<Node>,
645 sender: &tokio::sync::broadcast::Sender<WaitPaymentResponse>,
646 payment_id: Option<PaymentId>,
647 payment_hash: PaymentHash,
648 amount_msat: u64,
649 ) {
650 tracing::info!(
651 "Received payment for hash={} of amount={} msat",
652 payment_hash,
653 amount_msat
654 );
655
656 let payment_id = match payment_id {
657 Some(id) => id,
658 None => {
659 tracing::warn!("Received payment without payment_id");
660 return;
661 }
662 };
663
664 let payment_id_hex = hex::encode(payment_id.0);
665
666 if amount_msat == 0 {
667 tracing::warn!("Payment of no amount");
668 return;
669 }
670
671 tracing::info!(
672 "Processing payment notification: id={}, amount={} msats",
673 payment_id_hex,
674 amount_msat
675 );
676
677 let payment_details = match node.payment(&payment_id) {
678 Some(details) => details,
679 None => {
680 tracing::error!("Could not find payment details for id={}", payment_id_hex);
681 return;
682 }
683 };
684
685 let (payment_identifier, payment_id) = match payment_details.kind {
686 PaymentKind::Bolt11 { hash, .. } => {
687 (PaymentIdentifier::PaymentHash(hash.0), hash.to_string())
688 }
689 PaymentKind::Bolt12Offer { hash, offer_id, .. } => match hash {
690 Some(h) => (
691 PaymentIdentifier::OfferId(offer_id.to_string()),
692 h.to_string(),
693 ),
694 None => {
695 tracing::error!("Bolt12 payment missing hash");
696 return;
697 }
698 },
699 k => {
700 tracing::warn!("Received payment of kind {:?} which is not supported", k);
701 return;
702 }
703 };
704
705 let wait_payment_response = WaitPaymentResponse {
706 payment_identifier,
707 payment_amount: Amount::new(amount_msat, CurrencyUnit::Msat),
708 payment_id,
709 };
710
711 match sender.send(wait_payment_response) {
712 Ok(_) => tracing::info!("Successfully sent payment notification to stream"),
713 Err(err) => tracing::error!(
714 "Could not send payment received notification on channel: {}",
715 err
716 ),
717 }
718 }
719
720 pub fn handle_events(&self) -> Result<(), Error> {
722 let node = self.inner.clone();
723 let sender = self.sender.clone();
724 let outgoing_payment_sender = self.outgoing_payment_sender.clone();
725 let cancel_token = self.events_cancel_token.clone();
726
727 tracing::info!("Starting event handler task");
728
729 tokio::spawn(async move {
730 tracing::info!("Event handler loop started");
731 loop {
732 tokio::select! {
733 _ = cancel_token.cancelled() => {
734 tracing::info!("Event handler cancelled");
735 break;
736 }
737 event = node.next_event_async() => {
738 match event {
739 Event::PaymentReceived {
740 payment_id,
741 payment_hash,
742 amount_msat,
743 custom_records: _
744 } => {
745 Self::handle_payment_received(
746 &node,
747 &sender,
748 payment_id,
749 payment_hash,
750 amount_msat
751 ).await;
752 }
753 Event::PaymentSuccessful {
754 payment_id,
755 payment_hash,
756 payment_preimage: _,
757 fee_paid_msat: _,
758 } => {
759 tracing::info!(
760 payment_id = ?payment_id,
761 payment_hash = %payment_hash,
762 "LDK node payment succeeded"
763 );
764 if let Some(payment_id) = payment_id {
765 let _ = outgoing_payment_sender.send(payment_id);
766 }
767 }
768 Event::PaymentFailed {
769 payment_id,
770 payment_hash,
771 reason,
772 } => {
773 tracing::error!(
774 payment_id = ?payment_id,
775 payment_hash = ?payment_hash,
776 reason = ?reason,
777 "LDK node payment failed"
778 );
779 if let Some(payment_id) = payment_id {
780 let _ = outgoing_payment_sender.send(payment_id);
781 }
782 }
783 event => {
784 tracing::debug!("Received other ldk node event: {:?}", event);
785 }
786 }
787
788 if let Err(err) = node.event_handled() {
789 tracing::error!("Error handling node event: {}", err);
790 } else {
791 tracing::debug!("Successfully handled node event");
792 }
793 }
794 }
795 }
796 tracing::info!("Event handler loop terminated");
797 });
798
799 tracing::info!("Event handler task spawned");
800 Ok(())
801 }
802
803 pub fn node(&self) -> Arc<Node> {
805 Arc::clone(&self.inner)
806 }
807}
808
809#[async_trait]
811impl MintPayment for CdkLdkNode {
812 type Err = payment::Error;
813
814 async fn start(&self) -> Result<(), Self::Err> {
817 self.start_ldk_node().map_err(|e| {
818 tracing::error!("Failed to start CdkLdkNode: {}", e);
819 e
820 })?;
821
822 tracing::info!("CdkLdkNode payment processor started successfully");
823
824 if let Some(web_addr) = self.web_addr {
826 tracing::info!("Starting LDK Node web interface on {}", web_addr);
827 self.start_web_server(web_addr).map_err(|e| {
828 tracing::error!("Failed to start web server: {}", e);
829 e
830 })?;
831 } else {
832 tracing::info!("No web server address configured, skipping web interface");
833 }
834
835 Ok(())
836 }
837
838 async fn stop(&self) -> Result<(), Self::Err> {
841 self.stop_ldk_node().map_err(|e| {
842 tracing::error!("Failed to stop CdkLdkNode: {}", e);
843 e.into()
844 })
845 }
846
847 async fn get_settings(&self) -> Result<SettingsResponse, Self::Err> {
849 let settings = SettingsResponse {
850 unit: CurrencyUnit::Msat.to_string(),
851 bolt11: Some(payment::Bolt11Settings {
852 mpp: false,
853 amountless: true,
854 invoice_description: true,
855 }),
856 bolt12: Some(payment::Bolt12Settings {
857 amountless: true,
858 invoice_description: true,
859 }),
860 onchain: None,
861 custom: std::collections::HashMap::new(),
862 };
863 Ok(settings)
864 }
865
866 #[instrument(skip(self))]
868 async fn create_incoming_payment_request(
869 &self,
870 options: IncomingPaymentOptions,
871 ) -> Result<CreateIncomingPaymentResponse, Self::Err> {
872 match options {
873 IncomingPaymentOptions::Bolt11(bolt11_options) => {
874 let amount_msat: Amount = bolt11_options
875 .amount
876 .convert_to(&CurrencyUnit::Msat)?
877 .into();
878 let description = bolt11_options.description.unwrap_or_default();
879 let time = match bolt11_options.unix_expiry {
880 Some(t) => t
881 .checked_sub(unix_time())
882 .ok_or(payment::Error::InvalidExpiry)?,
883 None => 36000,
884 };
885
886 let description = Bolt11InvoiceDescription::Direct(
887 Description::new(description).map_err(|_| Error::InvalidDescription)?,
888 );
889
890 let payment = self
891 .inner
892 .bolt11_payment()
893 .receive(amount_msat.into(), &description, time as u32)
894 .map_err(Error::LdkNode)?;
895
896 let payment_hash = payment.payment_hash().to_string();
897 let payment_identifier = PaymentIdentifier::PaymentHash(
898 hex::decode(&payment_hash)?
899 .try_into()
900 .map_err(|_| Error::InvalidPaymentHashLength)?,
901 );
902
903 Ok(CreateIncomingPaymentResponse {
904 request_lookup_id: payment_identifier,
905 request: payment.to_string(),
906 expiry: Some(unix_time() + time),
907 extra_json: None,
908 })
909 }
910 IncomingPaymentOptions::Bolt12(bolt12_options) => {
911 let Bolt12IncomingPaymentOptions {
912 description,
913 amount,
914 unix_expiry,
915 } = *bolt12_options;
916
917 let time = unix_expiry
918 .map(|t| {
919 t.checked_sub(unix_time())
920 .ok_or(payment::Error::InvalidExpiry)
921 .map(|t| t as u32)
922 })
923 .transpose()?;
924
925 let offer = match amount {
926 Some(amount) => {
927 let amount_msat: Amount = amount.convert_to(&CurrencyUnit::Msat)?.into();
928
929 self.inner
930 .bolt12_payment()
931 .receive(
932 amount_msat.into(),
933 &description.unwrap_or("".to_string()),
934 time,
935 None,
936 )
937 .map_err(Error::LdkNode)?
938 }
939 None => self
940 .inner
941 .bolt12_payment()
942 .receive_variable_amount(&description.unwrap_or("".to_string()), time)
943 .map_err(Error::LdkNode)?,
944 };
945 let payment_identifier = PaymentIdentifier::OfferId(offer.id().to_string());
946
947 Ok(CreateIncomingPaymentResponse {
948 request_lookup_id: payment_identifier,
949 request: offer.to_string(),
950 expiry: unix_expiry,
951 extra_json: None,
952 })
953 }
954 IncomingPaymentOptions::Custom(_) | IncomingPaymentOptions::Onchain(_) => {
955 Err(cdk_common::payment::Error::UnsupportedPaymentOption)
956 }
957 }
958 }
959
960 #[instrument(skip_all)]
963 async fn get_payment_quote(
964 &self,
965 unit: &CurrencyUnit,
966 options: OutgoingPaymentOptions,
967 ) -> Result<PaymentQuoteResponse, Self::Err> {
968 match options {
969 cdk_common::payment::OutgoingPaymentOptions::Custom(_) => {
970 Err(cdk_common::payment::Error::UnsupportedPaymentOption)
971 }
972 OutgoingPaymentOptions::Bolt11(bolt11_options) => {
973 let bolt11 = bolt11_options.bolt11;
974
975 let amount_msat = match bolt11_options.melt_options {
976 Some(MeltOptions::Amountless { amountless }) => {
977 let amount_msat = amountless.amount_msat;
978
979 if let Some(invoice_amount) = bolt11.amount_milli_satoshis() {
980 if invoice_amount != u64::from(amount_msat) {
981 return Err(payment::Error::AmountMismatch);
982 }
983 }
984
985 amount_msat
986 }
987 Some(MeltOptions::Mpp { mpp }) => mpp.amount,
988 None => bolt11
989 .amount_milli_satoshis()
990 .ok_or(Error::UnknownInvoiceAmount)?
991 .into(),
992 };
993
994 let amount =
995 Amount::new(amount_msat.into(), CurrencyUnit::Msat).convert_to(unit)?;
996
997 let relative_fee_reserve =
998 (self.fee_reserve.percent_fee_reserve * amount.value() as f32) as u64;
999
1000 let absolute_fee_reserve: u64 = self.fee_reserve.min_fee_reserve.into();
1001
1002 let fee = match relative_fee_reserve > absolute_fee_reserve {
1003 true => relative_fee_reserve,
1004 false => absolute_fee_reserve,
1005 };
1006
1007 let payment_hash = bolt11.payment_hash().to_string();
1008 let payment_hash_bytes = hex::decode(&payment_hash)?
1009 .try_into()
1010 .map_err(|_| Error::InvalidPaymentHashLength)?;
1011
1012 Ok(PaymentQuoteResponse {
1013 request_lookup_id: Some(PaymentIdentifier::PaymentHash(payment_hash_bytes)),
1014 amount,
1015 fee: Amount::new(fee, unit.clone()),
1016 state: MeltQuoteState::Unpaid,
1017 extra_json: None,
1018 estimated_blocks: None,
1019 fee_options: None,
1020 })
1021 }
1022 OutgoingPaymentOptions::Bolt12(bolt12_options) => {
1023 let offer = bolt12_options.offer;
1024
1025 let amount_msat = match bolt12_options.melt_options {
1026 Some(melt_options) => melt_options.amount_msat(),
1027 None => {
1028 let amount = offer.amount().ok_or(payment::Error::AmountMismatch)?;
1029
1030 match amount {
1031 ldk_node::lightning::offers::offer::Amount::Bitcoin {
1032 amount_msats,
1033 } => amount_msats.into(),
1034 _ => return Err(payment::Error::AmountMismatch),
1035 }
1036 }
1037 };
1038 let amount =
1039 Amount::new(amount_msat.into(), CurrencyUnit::Msat).convert_to(unit)?;
1040
1041 let relative_fee_reserve =
1042 (self.fee_reserve.percent_fee_reserve * amount.value() as f32) as u64;
1043
1044 let absolute_fee_reserve: u64 = self.fee_reserve.min_fee_reserve.into();
1045
1046 let fee = match relative_fee_reserve > absolute_fee_reserve {
1047 true => relative_fee_reserve,
1048 false => absolute_fee_reserve,
1049 };
1050
1051 Ok(PaymentQuoteResponse {
1052 request_lookup_id: Some(PaymentIdentifier::QuoteId(
1053 bolt12_options.quote_id.clone(),
1054 )),
1055 amount,
1056 fee: Amount::new(fee, unit.clone()),
1057 state: MeltQuoteState::Unpaid,
1058 extra_json: None,
1059 estimated_blocks: None,
1060 fee_options: None,
1061 })
1062 }
1063 OutgoingPaymentOptions::Onchain(_) => {
1064 Err(cdk_common::payment::Error::UnsupportedPaymentOption)
1065 }
1066 }
1067 }
1068
1069 #[instrument(skip(self, options))]
1071 async fn make_payment(
1072 &self,
1073 unit: &CurrencyUnit,
1074 options: OutgoingPaymentOptions,
1075 ) -> Result<MakePaymentResponse, Self::Err> {
1076 match options {
1077 cdk_common::payment::OutgoingPaymentOptions::Custom(options) => {
1078 Ok(outgoing_payment_failure_response(
1079 unit,
1080 PaymentIdentifier::QuoteId(options.quote_id),
1081 ))
1082 }
1083 OutgoingPaymentOptions::Bolt11(bolt11_options) => {
1084 let bolt11 = bolt11_options.bolt11;
1085 let payment_lookup_id =
1086 PaymentIdentifier::PaymentHash(bolt11.payment_hash().to_byte_array());
1087
1088 let send_params = match bolt11_options
1089 .max_fee_amount
1090 .map(|f| {
1091 f.convert_to(&CurrencyUnit::Msat)
1092 .map(|amount_msat| RouteParametersConfig {
1093 max_total_routing_fee_msat: Some(amount_msat.value()),
1094 ..Default::default()
1095 })
1096 })
1097 .transpose()
1098 {
1099 Ok(params) => params,
1100 Err(err) => {
1101 tracing::error!("Failed to convert fee amount: {}", err);
1102 return Ok(outgoing_payment_failure_response(unit, payment_lookup_id));
1103 }
1104 };
1105
1106 let payment_event_receiver = self.outgoing_payment_sender.subscribe();
1109
1110 let payment_id = match bolt11_options.melt_options {
1111 Some(MeltOptions::Amountless { amountless }) => {
1112 if let Some(invoice_amount) = bolt11.amount_milli_satoshis() {
1113 if invoice_amount != u64::from(amountless.amount_msat) {
1114 return Ok(outgoing_payment_failure_response(
1115 unit,
1116 payment_lookup_id,
1117 ));
1118 }
1119 }
1120
1121 self.inner.bolt11_payment().send_using_amount(
1122 &bolt11,
1123 amountless.amount_msat.into(),
1124 send_params,
1125 )
1126 }
1127 None => self.inner.bolt11_payment().send(&bolt11, send_params),
1128 _ => {
1129 return Ok(outgoing_payment_failure_response(unit, payment_lookup_id));
1130 }
1131 };
1132
1133 let payment_id = match payment_id {
1134 Ok(payment_id) => payment_id,
1135 Err(err) if bolt11_send_error_is_explicit_terminal_failure(&err) => {
1136 tracing::warn!(
1137 payment_hash = %bolt11.payment_hash(),
1138 "LDK rejected BOLT11 payment before dispatch: {err}"
1139 );
1140 return Ok(outgoing_payment_failure_response(unit, payment_lookup_id));
1141 }
1142 Err(err) => {
1143 tracing::warn!(
1144 payment_hash = %bolt11.payment_hash(),
1145 "LDK BOLT11 send outcome is indeterminate: {err}"
1146 );
1147 return Err(Error::LdkNode(err).into());
1148 }
1149 };
1150
1151 let payment_details = self
1152 .wait_for_payment_terminal_status(payment_id, payment_event_receiver)
1153 .await?;
1154
1155 if payment_details.status == PaymentStatus::Failed {
1156 tracing::error!(payment_id = %payment_id, "Bolt11 payment failed");
1157 }
1158
1159 Self::make_payment_response_from_details(unit, payment_lookup_id, &payment_details)
1160 }
1161 OutgoingPaymentOptions::Bolt12(bolt12_options) => {
1162 let offer = bolt12_options.offer;
1163 let quote_id = bolt12_options.quote_id.clone();
1164 let quote_payment_identifier = PaymentIdentifier::QuoteId(quote_id.clone());
1165
1166 let send_params = match bolt12_options
1167 .max_fee_amount
1168 .map(|f| {
1169 f.convert_to(&CurrencyUnit::Msat)
1170 .map(|amount_msat| RouteParametersConfig {
1171 max_total_routing_fee_msat: Some(amount_msat.value()),
1172 ..Default::default()
1173 })
1174 })
1175 .transpose()
1176 {
1177 Ok(params) => params,
1178 Err(err) => {
1179 tracing::error!("Failed to convert fee amount: {}", err);
1180 return Ok(outgoing_payment_failure_response(
1181 unit,
1182 quote_payment_identifier,
1183 ));
1184 }
1185 };
1186
1187 if let Err(err) =
1195 write_bolt12_quote_payment_id(&self.kv_store, "e_id, None).await
1196 {
1197 tracing::error!(
1198 quote_id = %quote_id,
1199 "Could not persist BOLT12 dispatch claim before sending: {err}"
1200 );
1201 return Ok(outgoing_payment_failure_response(
1202 unit,
1203 quote_payment_identifier,
1204 ));
1205 }
1206
1207 let payment_event_receiver = self.outgoing_payment_sender.subscribe();
1210
1211 let payment_id = match bolt12_options.melt_options {
1212 Some(MeltOptions::Amountless { amountless }) => {
1213 self.inner.bolt12_payment().send_using_amount(
1214 &offer,
1215 amountless.amount_msat.into(),
1216 None,
1217 None,
1218 send_params,
1219 )
1220 }
1221 None => self
1222 .inner
1223 .bolt12_payment()
1224 .send(&offer, None, None, send_params),
1225 _ => {
1226 self.cleanup_bolt12_dispatch_binding("e_id, None).await;
1227 return Ok(outgoing_payment_failure_response(
1228 unit,
1229 quote_payment_identifier,
1230 ));
1231 }
1232 };
1233
1234 let payment_id = match payment_id {
1235 Ok(payment_id) => payment_id,
1236 Err(err) => {
1237 match bolt12_send_error_has_ambiguous_dispatch(&err) {
1238 true => {
1239 tracing::warn!(
1240 quote_id = %quote_id,
1241 "LDK payment persistence failed after BOLT12 send; retaining \
1242 the dispatch sentinel because the payment may have been dispatched"
1243 );
1244 }
1245 false => {
1246 self.cleanup_bolt12_dispatch_binding("e_id, None).await;
1247 tracing::warn!(
1248 quote_id = %quote_id,
1249 "LDK rejected BOLT12 payment before dispatch: {err}"
1250 );
1251 return Ok(outgoing_payment_failure_response(
1252 unit,
1253 quote_payment_identifier,
1254 ));
1255 }
1256 }
1257 return Err(Error::LdkNode(err).into());
1258 }
1259 };
1260
1261 if let Err(err) =
1267 write_bolt12_quote_payment_id(&self.kv_store, "e_id, Some(&payment_id))
1268 .await
1269 {
1270 tracing::error!(
1271 "Could not record BOLT12 payment id for quote {quote_id}: {err}. \
1272 The payment will remain Pending until manual intervention."
1273 );
1274 }
1275
1276 let payment_details = self
1277 .wait_for_payment_terminal_status(payment_id, payment_event_receiver)
1278 .await?;
1279
1280 if payment_details.status == PaymentStatus::Failed {
1281 tracing::error!(
1282 payment_id = %payment_id,
1283 amount_msat = ?payment_details.amount_msat,
1284 fee_paid_msat = ?payment_details.fee_paid_msat,
1285 payment_kind = ?payment_details.kind,
1286 "Bolt12 payment failed"
1287 );
1288 self.cleanup_bolt12_dispatch_binding("e_id, Some(&payment_id))
1289 .await;
1290 }
1291
1292 Self::make_payment_response_from_details(
1293 unit,
1294 quote_payment_identifier,
1295 &payment_details,
1296 )
1297 }
1298 OutgoingPaymentOptions::Onchain(options) => Ok(outgoing_payment_failure_response(
1299 unit,
1300 PaymentIdentifier::QuoteId(options.quote_id),
1301 )),
1302 }
1303 }
1304
1305 #[instrument(skip(self))]
1308 async fn wait_payment_event(
1309 &self,
1310 ) -> Result<Pin<Box<dyn Stream<Item = cdk_common::payment::Event> + Send>>, Self::Err> {
1311 tracing::info!("Starting stream for invoices - wait_any_incoming_payment called");
1312
1313 self.wait_invoice_is_active.store(true, Ordering::SeqCst);
1315 tracing::debug!("wait_invoice_is_active set to true");
1316
1317 let receiver = self.receiver.clone();
1318
1319 tracing::info!("Receiver obtained successfully, creating response stream");
1320
1321 let response_stream = BroadcastStream::new(receiver.resubscribe());
1323
1324 let response_stream = response_stream.filter_map(|result| async move {
1326 match result {
1327 Ok(payment) => Some(cdk_common::payment::Event::PaymentReceived(payment)),
1328 Err(err) => {
1329 tracing::warn!("Error in broadcast stream: {}", err);
1330 None
1331 }
1332 }
1333 });
1334
1335 let cancel_token = self.wait_invoice_cancel_token.clone();
1337 let is_active = self.wait_invoice_is_active.clone();
1338
1339 let stream = Box::pin(response_stream);
1340
1341 tokio::spawn(async move {
1343 cancel_token.cancelled().await;
1344 tracing::info!("wait_invoice stream cancelled");
1345 is_active.store(false, Ordering::SeqCst);
1346 });
1347
1348 tracing::info!("wait_any_incoming_payment returning stream");
1349 Ok(stream)
1350 }
1351
1352 fn is_payment_event_stream_active(&self) -> bool {
1354 self.wait_invoice_is_active.load(Ordering::SeqCst)
1355 }
1356
1357 fn cancel_payment_event_stream(&self) {
1359 self.wait_invoice_cancel_token.cancel()
1360 }
1361
1362 async fn check_incoming_payment_status(
1364 &self,
1365 payment_identifier: &PaymentIdentifier,
1366 ) -> Result<Vec<WaitPaymentResponse>, Self::Err> {
1367 if let PaymentIdentifier::OfferId(offer_id) = payment_identifier {
1370 let payments = self.inner.list_payments_with_filter(|p| {
1371 p.direction == PaymentDirection::Inbound
1372 && p.status == PaymentStatus::Succeeded
1373 && matches!(
1374 &p.kind,
1375 PaymentKind::Bolt12Offer { offer_id: oid, .. } if oid.to_string() == *offer_id
1376 )
1377 });
1378
1379 return Ok(payments
1380 .into_iter()
1381 .filter_map(|p| {
1382 let payment_id = match &p.kind {
1383 PaymentKind::Bolt12Offer {
1384 hash: Some(hash), ..
1385 } => hash.to_string(),
1386 _ => {
1387 tracing::warn!("Bolt12 payment for offer {} missing hash", offer_id);
1388 return None;
1389 }
1390 };
1391
1392 Some(WaitPaymentResponse {
1393 payment_identifier: payment_identifier.clone(),
1394 payment_amount: Amount::new(p.amount_msat?, CurrencyUnit::Msat),
1395 payment_id,
1396 })
1397 })
1398 .collect());
1399 }
1400
1401 let payment_id_str = match payment_identifier {
1402 PaymentIdentifier::PaymentHash(hash) => hex::encode(hash),
1403 PaymentIdentifier::CustomId(id) => id.clone(),
1404 _ => return Err(Error::UnsupportedPaymentIdentifierType.into()),
1405 };
1406
1407 let payment_id = PaymentId(
1408 hex::decode(&payment_id_str)?
1409 .try_into()
1410 .map_err(|_| Error::InvalidPaymentIdLength)?,
1411 );
1412
1413 let payment_details = self
1414 .inner
1415 .payment(&payment_id)
1416 .ok_or(Error::PaymentNotFound)?;
1417
1418 if payment_details.direction == PaymentDirection::Outbound {
1419 return Err(Error::InvalidPaymentDirection.into());
1420 }
1421
1422 let amount = if payment_details.status == PaymentStatus::Succeeded {
1423 payment_details
1424 .amount_msat
1425 .ok_or(Error::CouldNotGetPaymentAmount)?
1426 } else {
1427 return Ok(vec![]);
1428 };
1429
1430 let response = WaitPaymentResponse {
1431 payment_identifier: payment_identifier.clone(),
1432 payment_amount: Amount::new(amount, CurrencyUnit::Msat),
1433 payment_id: payment_id_str,
1434 };
1435
1436 Ok(vec![response])
1437 }
1438
1439 async fn check_outgoing_payment(
1441 &self,
1442 request_lookup_id: &PaymentIdentifier,
1443 ) -> Result<MakePaymentResponse, Self::Err> {
1444 let payment_details = match request_lookup_id {
1445 PaymentIdentifier::PaymentHash(id_hash) => {
1446 Self::select_bolt11_payment_details(self.inner.list_payments_with_filter(|p| {
1447 p.direction == PaymentDirection::Outbound
1448 && matches!(&p.kind, PaymentKind::Bolt11 { hash, .. } if &hash.0 == id_hash)
1449 }))
1450 }
1451 PaymentIdentifier::PaymentId(id) => self.inner.payment(&PaymentId(*id)),
1452 PaymentIdentifier::QuoteId(quote_id) => {
1453 match read_bolt12_quote_payment_id(&self.kv_store, quote_id)
1454 .await?
1455 .resolve()
1456 {
1457 Bolt12QuotePaymentIdResolution::PaymentId(payment_id) => {
1458 self.inner.payment(&payment_id)
1459 }
1460 Bolt12QuotePaymentIdResolution::Status(status) => {
1461 return Ok(MakePaymentResponse {
1462 payment_lookup_id: request_lookup_id.clone(),
1463 payment_proof: None,
1464 status,
1465 total_spent: Amount::new(0, CurrencyUnit::Msat),
1466 });
1467 }
1468 }
1469 }
1470 _ => {
1471 return Ok(MakePaymentResponse {
1472 payment_lookup_id: request_lookup_id.clone(),
1473 payment_proof: None,
1474 status: MeltQuoteState::Unknown,
1475 total_spent: Amount::new(0, CurrencyUnit::Msat),
1476 });
1477 }
1478 }
1479 .ok_or(Error::PaymentNotFound)?;
1480
1481 if payment_details.direction != PaymentDirection::Outbound {
1482 return Err(Error::InvalidPaymentDirection.into());
1483 }
1484
1485 if payment_details.status == PaymentStatus::Failed {
1486 if let PaymentIdentifier::QuoteId(quote_id) = request_lookup_id {
1487 self.cleanup_bolt12_dispatch_binding(quote_id, Some(&payment_details.id))
1488 .await;
1489 }
1490 }
1491
1492 Self::make_payment_response_from_details(
1493 &CurrencyUnit::Msat,
1494 request_lookup_id.clone(),
1495 &payment_details,
1496 )
1497 }
1498}
1499
1500impl Drop for CdkLdkNode {
1501 fn drop(&mut self) {
1502 tracing::info!("Drop called on CdkLdkNode");
1503 self.wait_invoice_cancel_token.cancel();
1504 tracing::debug!("Cancelled wait_invoice token in drop");
1505 }
1506}
1507
1508fn bolt12_quote_payment_id_key(quote_id: &QuoteId) -> Result<String, Error> {
1510 match quote_id {
1511 QuoteId::UUID(uuid) => Ok(uuid.to_string()),
1512 QuoteId::BASE64(_) => Err(Error::InvalidQuoteId),
1513 }
1514}
1515
1516async fn write_bolt12_quote_payment_id(
1523 kv_store: &DynKVStore,
1524 quote_id: &QuoteId,
1525 payment_id: Option<&PaymentId>,
1526) -> Result<(), Error> {
1527 let key = bolt12_quote_payment_id_key(quote_id)?;
1528 let value = payment_id.map(|id| hex::encode(id.0)).unwrap_or_default();
1529 let mut tx = kv_store
1530 .begin_transaction()
1531 .await
1532 .map_err(|e| Error::Database(e.to_string()))?;
1533
1534 let written = match payment_id {
1535 None => {
1536 tx.kv_write_if_absent(
1537 LDK_KV_PRIMARY_NAMESPACE,
1538 LDK_KV_BOLT12_OUTGOING_SECONDARY_NAMESPACE,
1539 &key,
1540 value.as_bytes(),
1541 )
1542 .await
1543 }
1544 Some(_) => {
1545 tx.kv_write_if_equals(
1546 LDK_KV_PRIMARY_NAMESPACE,
1547 LDK_KV_BOLT12_OUTGOING_SECONDARY_NAMESPACE,
1548 &key,
1549 b"",
1550 value.as_bytes(),
1551 )
1552 .await
1553 }
1554 }
1555 .map_err(|e| Error::Database(e.to_string()))?;
1556
1557 if written {
1558 tx.commit()
1559 .await
1560 .map_err(|e| Error::Database(e.to_string()))?;
1561 return Ok(());
1562 }
1563
1564 let existing = tx
1565 .kv_read(
1566 LDK_KV_PRIMARY_NAMESPACE,
1567 LDK_KV_BOLT12_OUTGOING_SECONDARY_NAMESPACE,
1568 &key,
1569 )
1570 .await
1571 .map_err(|e| Error::Database(e.to_string()))?;
1572 tx.rollback()
1573 .await
1574 .map_err(|e| Error::Database(e.to_string()))?;
1575
1576 match existing {
1577 Some(existing) if payment_id.is_some() && existing.as_slice() == value.as_bytes() => Ok(()),
1578 _ => Err(Error::Bolt12QuoteAlreadyClaimed {
1579 quote_id: quote_id.to_string(),
1580 }),
1581 }
1582}
1583
1584async fn read_bolt12_quote_payment_id(
1586 kv_store: &DynKVStore,
1587 quote_id: &QuoteId,
1588) -> Result<Bolt12QuotePaymentIdLookup, Error> {
1589 let key = bolt12_quote_payment_id_key(quote_id)?;
1590 let Some(stored) = kv_store
1591 .kv_read(
1592 LDK_KV_PRIMARY_NAMESPACE,
1593 LDK_KV_BOLT12_OUTGOING_SECONDARY_NAMESPACE,
1594 &key,
1595 )
1596 .await
1597 .map_err(|e| Error::Database(e.to_string()))?
1598 else {
1599 return Ok(Bolt12QuotePaymentIdLookup::Missing);
1600 };
1601
1602 if stored.is_empty() {
1603 return Ok(Bolt12QuotePaymentIdLookup::Dispatching);
1604 }
1605
1606 let payment_id_hex = match String::from_utf8(stored) {
1607 Ok(payment_id_hex) => payment_id_hex,
1608 Err(err) => {
1609 tracing::warn!(
1610 "LDK: invalid UTF-8 in BOLT12 payment id mapping for quote {quote_id}: {err}"
1611 );
1612 return Ok(Bolt12QuotePaymentIdLookup::Malformed);
1613 }
1614 };
1615
1616 let payment_id_bytes = match hex::decode(&payment_id_hex) {
1617 Ok(bytes) => bytes,
1618 Err(err) => {
1619 tracing::warn!(
1620 "LDK: invalid hex in BOLT12 payment id mapping for quote {quote_id}: {err}"
1621 );
1622 return Ok(Bolt12QuotePaymentIdLookup::Malformed);
1623 }
1624 };
1625
1626 let payment_id: [u8; 32] = match payment_id_bytes.try_into() {
1627 Ok(payment_id) => payment_id,
1628 Err(_) => {
1629 tracing::warn!("LDK: invalid payment id length in BOLT12 mapping for quote {quote_id}");
1630 return Ok(Bolt12QuotePaymentIdLookup::Malformed);
1631 }
1632 };
1633
1634 Ok(Bolt12QuotePaymentIdLookup::Found(PaymentId(payment_id)))
1635}
1636
1637async fn delete_bolt12_quote_payment_id_if_equals(
1640 kv_store: &DynKVStore,
1641 quote_id: &QuoteId,
1642 payment_id: Option<&PaymentId>,
1643) -> Result<bool, Error> {
1644 let key = bolt12_quote_payment_id_key(quote_id)?;
1645 let expected = payment_id.map(|id| hex::encode(id.0)).unwrap_or_default();
1646 let mut tx = kv_store
1647 .begin_transaction()
1648 .await
1649 .map_err(|e| Error::Database(e.to_string()))?;
1650
1651 let claimed = tx
1652 .kv_write_if_equals(
1653 LDK_KV_PRIMARY_NAMESPACE,
1654 LDK_KV_BOLT12_OUTGOING_SECONDARY_NAMESPACE,
1655 &key,
1656 expected.as_bytes(),
1657 LDK_KV_BOLT12_CLEANUP_MARKER,
1658 )
1659 .await
1660 .map_err(|e| Error::Database(e.to_string()))?;
1661
1662 if !claimed {
1663 tx.rollback()
1664 .await
1665 .map_err(|e| Error::Database(e.to_string()))?;
1666 return Ok(false);
1667 }
1668
1669 tx.kv_remove(
1670 LDK_KV_PRIMARY_NAMESPACE,
1671 LDK_KV_BOLT12_OUTGOING_SECONDARY_NAMESPACE,
1672 &key,
1673 )
1674 .await
1675 .map_err(|e| Error::Database(e.to_string()))?;
1676 tx.commit()
1677 .await
1678 .map_err(|e| Error::Database(e.to_string()))?;
1679
1680 Ok(true)
1681}
1682
1683#[cfg(test)]
1684mod tests {
1685 use super::*;
1686
1687 #[test]
1688 fn bitcoin_rpc_debug_redacts_password() {
1689 let source = ChainSource::BitcoinRpc(BitcoinRpcConfig {
1690 host: "127.0.0.1".to_string(),
1691 port: 8332,
1692 user: "rpc-user".to_string(),
1693 password: "rpc-password-secret".to_string(),
1694 });
1695
1696 let debug = format!("{source:?}");
1697
1698 assert!(debug.contains("127.0.0.1"));
1699 assert!(debug.contains("rpc-user"));
1700 assert!(debug.contains("[REDACTED]"));
1701 assert!(!debug.contains("rpc-password-secret"));
1702 }
1703
1704 #[test]
1705 fn chain_source_debug_redacts_url_credentials() {
1706 for source in [
1707 ChainSource::Esplora("https://esplora-user:esplora-secret@example.com/api".to_string()),
1708 ChainSource::Electrum(
1709 "ssl://electrum-user:electrum-secret@example.com:50002".to_string(),
1710 ),
1711 ] {
1712 let debug = format!("{source:?}");
1713
1714 assert!(debug.contains("example.com"));
1715 assert!(!debug.contains("-user"));
1716 assert!(!debug.contains("-secret"));
1717 }
1718 }
1719
1720 #[test]
1721 fn gossip_source_debug_redacts_url_credentials() {
1722 let source = GossipSource::RapidGossipSync(
1723 "https://rgs-user:rgs-secret@example.com/snapshot".to_string(),
1724 );
1725
1726 let debug = format!("{source:?}");
1727
1728 assert!(debug.contains("https://example.com/snapshot"));
1729 assert!(!debug.contains("rgs-user"));
1730 assert!(!debug.contains("rgs-secret"));
1731 }
1732
1733 fn test_payment_details(status: PaymentStatus, amount_msat: Option<u64>) -> PaymentDetails {
1734 PaymentDetails {
1735 id: PaymentId([2; 32]),
1736 kind: PaymentKind::Bolt11 {
1737 hash: PaymentHash([1; 32]),
1738 preimage: None,
1739 secret: None,
1740 },
1741 amount_msat,
1742 fee_paid_msat: None,
1743 direction: PaymentDirection::Outbound,
1744 status,
1745 latest_update_timestamp: 0,
1746 }
1747 }
1748
1749 fn test_payment_details_with_id(
1750 id: [u8; 32],
1751 status: PaymentStatus,
1752 latest_update_timestamp: u64,
1753 ) -> PaymentDetails {
1754 PaymentDetails {
1755 id: PaymentId(id),
1756 latest_update_timestamp,
1757 ..test_payment_details(status, None)
1758 }
1759 }
1760
1761 #[test]
1762 fn failed_payment_response_does_not_require_amount() {
1763 let details = test_payment_details(PaymentStatus::Failed, None);
1764
1765 let response = CdkLdkNode::make_payment_response_from_details(
1766 &CurrencyUnit::Msat,
1767 PaymentIdentifier::PaymentId([2; 32]),
1768 &details,
1769 )
1770 .expect("failed payment details should map without amount");
1771
1772 assert_eq!(response.status, MeltQuoteState::Failed);
1773 assert_eq!(response.total_spent, Amount::new(0, CurrencyUnit::Msat));
1774 }
1775
1776 #[test]
1777 fn pending_payment_response_does_not_require_amount() {
1778 let details = test_payment_details(PaymentStatus::Pending, None);
1779
1780 let response = CdkLdkNode::make_payment_response_from_details(
1781 &CurrencyUnit::Msat,
1782 PaymentIdentifier::PaymentId([2; 32]),
1783 &details,
1784 )
1785 .expect("pending payment details should map without amount");
1786
1787 assert_eq!(response.status, MeltQuoteState::Pending);
1788 assert_eq!(response.total_spent, Amount::new(0, CurrencyUnit::Msat));
1789 }
1790
1791 #[test]
1792 fn paid_payment_response_requires_amount() {
1793 let details = test_payment_details(PaymentStatus::Succeeded, None);
1794
1795 let err = CdkLdkNode::make_payment_response_from_details(
1796 &CurrencyUnit::Msat,
1797 PaymentIdentifier::PaymentId([2; 32]),
1798 &details,
1799 )
1800 .expect_err("paid payment details without amount should fail");
1801
1802 assert!(matches!(err, payment::Error::Backend(_)));
1803 }
1804
1805 #[test]
1806 fn bolt11_payment_selection_prefers_pending_over_failed() {
1807 let failed = test_payment_details_with_id([1; 32], PaymentStatus::Failed, 2);
1808 let pending = test_payment_details_with_id([2; 32], PaymentStatus::Pending, 1);
1809
1810 let selected = CdkLdkNode::select_bolt11_payment_details([failed, pending])
1811 .expect("payment details should be selected");
1812
1813 assert_eq!(selected.id, PaymentId([2; 32]));
1814 assert_eq!(selected.status, PaymentStatus::Pending);
1815 }
1816
1817 #[test]
1818 fn bolt11_payment_selection_prefers_succeeded_over_pending() {
1819 let pending = test_payment_details_with_id([1; 32], PaymentStatus::Pending, 2);
1820 let succeeded = PaymentDetails {
1821 amount_msat: Some(1000),
1822 ..test_payment_details_with_id([2; 32], PaymentStatus::Succeeded, 1)
1823 };
1824
1825 let selected = CdkLdkNode::select_bolt11_payment_details([pending, succeeded])
1826 .expect("payment details should be selected");
1827
1828 assert_eq!(selected.id, PaymentId([2; 32]));
1829 assert_eq!(selected.status, PaymentStatus::Succeeded);
1830 }
1831
1832 #[test]
1833 fn bolt11_payment_selection_uses_latest_failed_when_all_failed() {
1834 let older_failed = test_payment_details_with_id([1; 32], PaymentStatus::Failed, 1);
1835 let newer_failed = test_payment_details_with_id([2; 32], PaymentStatus::Failed, 2);
1836
1837 let selected = CdkLdkNode::select_bolt11_payment_details([older_failed, newer_failed])
1838 .expect("payment details should be selected");
1839
1840 assert_eq!(selected.id, PaymentId([2; 32]));
1841 assert_eq!(selected.status, PaymentStatus::Failed);
1842 }
1843
1844 #[tokio::test]
1845 async fn terminal_payment_event_wait_ignores_other_payments() {
1846 let (sender, mut receiver) = tokio::sync::broadcast::channel(4);
1847 let payment_id = PaymentId([2; 32]);
1848
1849 sender
1852 .send(PaymentId([1; 32]))
1853 .expect("receiver should be subscribed");
1854 sender
1855 .send(payment_id)
1856 .expect("receiver should be subscribed");
1857
1858 CdkLdkNode::wait_for_terminal_payment_event(&mut receiver, payment_id)
1859 .await
1860 .expect("matching terminal event should wake the waiter");
1861 }
1862
1863 #[tokio::test]
1864 async fn terminal_payment_event_wait_recovers_from_lagged_channel() {
1865 let (sender, mut receiver) = tokio::sync::broadcast::channel(2);
1866 let payment_id = PaymentId([3; 32]);
1867
1868 sender
1869 .send(PaymentId([1; 32]))
1870 .expect("receiver should be subscribed");
1871 sender
1872 .send(PaymentId([2; 32]))
1873 .expect("receiver should be subscribed");
1874 sender
1875 .send(PaymentId([4; 32]))
1876 .expect("receiver should be subscribed");
1877 sender
1878 .send(payment_id)
1879 .expect("receiver should be subscribed");
1880
1881 CdkLdkNode::wait_for_terminal_payment_event(&mut receiver, payment_id)
1882 .await
1883 .expect("receiver lag should not prevent a matching event from waking the waiter");
1884 }
1885
1886 #[tokio::test]
1887 async fn terminal_payment_event_wait_reports_closed_channel() {
1888 let (sender, mut receiver) = tokio::sync::broadcast::channel(1);
1889 drop(sender);
1890
1891 let err = CdkLdkNode::wait_for_terminal_payment_event(&mut receiver, PaymentId([2; 32]))
1892 .await
1893 .expect_err("a closed event channel should stop the wait");
1894
1895 assert!(matches!(
1896 err,
1897 tokio::sync::broadcast::error::RecvError::Closed
1898 ));
1899 }
1900
1901 #[test]
1902 fn bolt12_persistence_failure_has_ambiguous_dispatch() {
1903 assert!(bolt12_send_error_has_ambiguous_dispatch(
1904 &ldk_node::NodeError::PersistenceFailed
1905 ));
1906
1907 for not_dispatched in [
1908 ldk_node::NodeError::NotRunning,
1909 ldk_node::NodeError::UnsupportedCurrency,
1910 ldk_node::NodeError::InvalidOffer,
1911 ldk_node::NodeError::InvalidAmount,
1912 ldk_node::NodeError::DuplicatePayment,
1913 ldk_node::NodeError::InvoiceRequestCreationFailed,
1914 ldk_node::NodeError::PaymentSendingFailed,
1915 ] {
1916 assert!(
1917 !bolt12_send_error_has_ambiguous_dispatch(¬_dispatched),
1918 "{not_dispatched} must be treated as not dispatched"
1919 );
1920 }
1921 }
1922
1923 #[test]
1924 fn bolt11_send_errors_only_classify_explicit_rejections_as_terminal() {
1925 for terminal_error in [
1926 ldk_node::NodeError::NotRunning,
1927 ldk_node::NodeError::InvalidAmount,
1928 ldk_node::NodeError::InvalidInvoice,
1929 ldk_node::NodeError::PaymentSendingFailed,
1930 ] {
1931 assert!(
1932 bolt11_send_error_is_explicit_terminal_failure(&terminal_error),
1933 "{terminal_error} must be treated as a definitive failure"
1934 );
1935 }
1936
1937 for ambiguous_error in [
1938 ldk_node::NodeError::PersistenceFailed,
1939 ldk_node::NodeError::DuplicatePayment,
1940 ] {
1941 assert!(
1942 !bolt11_send_error_is_explicit_terminal_failure(&ambiguous_error),
1943 "{ambiguous_error} must not authorize proof release"
1944 );
1945 }
1946 }
1947
1948 #[test]
1949 fn authoritative_outgoing_failure_response_is_terminal_and_spends_nothing() {
1950 let payment_lookup_id = PaymentIdentifier::PaymentHash([42; 32]);
1951 let response =
1952 outgoing_payment_failure_response(&CurrencyUnit::Msat, payment_lookup_id.clone());
1953
1954 assert_eq!(response.payment_lookup_id, payment_lookup_id);
1955 assert_eq!(response.status, MeltQuoteState::Failed);
1956 assert_eq!(response.total_spent, Amount::new(0, CurrencyUnit::Msat));
1957 assert!(response.payment_proof.is_none());
1958 }
1959
1960 #[test]
1961 fn bolt12_quote_payment_id_lookup_resolution_is_safe() {
1962 assert_eq!(
1963 Bolt12QuotePaymentIdLookup::Dispatching.resolve(),
1964 Bolt12QuotePaymentIdResolution::Status(MeltQuoteState::Pending),
1965 "an indeterminate dispatch must keep melt proofs reserved"
1966 );
1967 assert_eq!(
1968 Bolt12QuotePaymentIdLookup::Missing.resolve(),
1969 Bolt12QuotePaymentIdResolution::Status(MeltQuoteState::Unpaid),
1970 "a missing sentinel means the payment was never dispatched"
1971 );
1972 assert_eq!(
1973 Bolt12QuotePaymentIdLookup::Malformed.resolve(),
1974 Bolt12QuotePaymentIdResolution::Status(MeltQuoteState::Unknown),
1975 "corrupt bookkeeping must remain indeterminate"
1976 );
1977 }
1978
1979 async fn test_kv_store() -> DynKVStore {
1980 std::sync::Arc::new(cdk_sqlite::mint::memory::empty().await.unwrap())
1981 }
1982
1983 #[tokio::test]
1987 async fn bolt12_quote_payment_id_mapping_lifecycle() {
1988 let kv_store = test_kv_store().await;
1989 let quote_id = QuoteId::new();
1990
1991 assert_eq!(
1992 read_bolt12_quote_payment_id(&kv_store, "e_id)
1993 .await
1994 .unwrap(),
1995 Bolt12QuotePaymentIdLookup::Missing,
1996 "no record must resolve as never dispatched"
1997 );
1998
1999 write_bolt12_quote_payment_id(&kv_store, "e_id, None)
2001 .await
2002 .unwrap();
2003 assert_eq!(
2004 read_bolt12_quote_payment_id(&kv_store, "e_id)
2005 .await
2006 .unwrap(),
2007 Bolt12QuotePaymentIdLookup::Dispatching,
2008 "sentinel must resolve as indeterminate, never terminal"
2009 );
2010
2011 assert!(
2012 delete_bolt12_quote_payment_id_if_equals(&kv_store, "e_id, None)
2013 .await
2014 .unwrap(),
2015 "an unambiguous pre-dispatch failure should release its sentinel"
2016 );
2017 write_bolt12_quote_payment_id(&kv_store, "e_id, None)
2018 .await
2019 .expect("a retry should reclaim the quote after sentinel cleanup");
2020
2021 let payment_id = PaymentId([7; 32]);
2023 write_bolt12_quote_payment_id(&kv_store, "e_id, Some(&payment_id))
2024 .await
2025 .unwrap();
2026 assert_eq!(
2027 read_bolt12_quote_payment_id(&kv_store, "e_id)
2028 .await
2029 .unwrap(),
2030 Bolt12QuotePaymentIdLookup::Found(payment_id)
2031 );
2032
2033 assert!(
2035 delete_bolt12_quote_payment_id_if_equals(&kv_store, "e_id, Some(&payment_id))
2036 .await
2037 .unwrap()
2038 );
2039 assert_eq!(
2040 read_bolt12_quote_payment_id(&kv_store, "e_id)
2041 .await
2042 .unwrap(),
2043 Bolt12QuotePaymentIdLookup::Missing
2044 );
2045 }
2046
2047 #[tokio::test]
2048 async fn bolt12_quote_payment_id_binding_is_write_once() {
2049 let kv_store = test_kv_store().await;
2050 let quote_id = QuoteId::new();
2051 let payment_id = PaymentId([7; 32]);
2052 let conflicting_payment_id = PaymentId([9; 32]);
2053
2054 write_bolt12_quote_payment_id(&kv_store, "e_id, None)
2055 .await
2056 .expect("first dispatch should claim the quote");
2057 write_bolt12_quote_payment_id(&kv_store, "e_id, Some(&payment_id))
2058 .await
2059 .expect("the claim owner should record its payment id");
2060 write_bolt12_quote_payment_id(&kv_store, "e_id, Some(&payment_id))
2061 .await
2062 .expect("repeating the same payment id must be idempotent");
2063
2064 let duplicate_dispatch = write_bolt12_quote_payment_id(&kv_store, "e_id, None)
2065 .await
2066 .expect_err("a dispatched quote must not be claimed again");
2067 assert!(
2068 matches!(duplicate_dispatch, Error::Bolt12QuoteAlreadyClaimed { .. }),
2069 "unexpected error: {duplicate_dispatch}"
2070 );
2071
2072 let conflicting_binding =
2073 write_bolt12_quote_payment_id(&kv_store, "e_id, Some(&conflicting_payment_id))
2074 .await
2075 .expect_err("a conflicting payment id must be rejected");
2076 assert!(
2077 matches!(conflicting_binding, Error::Bolt12QuoteAlreadyClaimed { .. }),
2078 "unexpected error: {conflicting_binding}"
2079 );
2080
2081 assert_eq!(
2082 read_bolt12_quote_payment_id(&kv_store, "e_id)
2083 .await
2084 .expect("payment id should remain readable"),
2085 Bolt12QuotePaymentIdLookup::Found(payment_id),
2086 "a duplicate dispatch must not redirect recovery"
2087 );
2088 }
2089
2090 #[tokio::test]
2091 async fn failed_bolt12_binding_can_be_released_without_removing_a_retry() {
2092 let kv_store = test_kv_store().await;
2093 let quote_id = QuoteId::new();
2094 let failed_payment_id = PaymentId([7; 32]);
2095 let retry_payment_id = PaymentId([9; 32]);
2096
2097 write_bolt12_quote_payment_id(&kv_store, "e_id, None)
2098 .await
2099 .expect("failed dispatch should claim the quote");
2100 write_bolt12_quote_payment_id(&kv_store, "e_id, Some(&failed_payment_id))
2101 .await
2102 .expect("failed payment id should be recorded");
2103 assert!(delete_bolt12_quote_payment_id_if_equals(
2104 &kv_store,
2105 "e_id,
2106 Some(&failed_payment_id),
2107 )
2108 .await
2109 .expect("failed binding should be released"));
2110
2111 write_bolt12_quote_payment_id(&kv_store, "e_id, None)
2112 .await
2113 .expect("retry should claim the released quote");
2114 write_bolt12_quote_payment_id(&kv_store, "e_id, Some(&retry_payment_id))
2115 .await
2116 .expect("retry payment id should be recorded");
2117 assert!(!delete_bolt12_quote_payment_id_if_equals(
2118 &kv_store,
2119 "e_id,
2120 Some(&failed_payment_id),
2121 )
2122 .await
2123 .expect("stale cleanup should be checked atomically"));
2124
2125 assert_eq!(
2126 read_bolt12_quote_payment_id(&kv_store, "e_id)
2127 .await
2128 .expect("retry binding should remain readable"),
2129 Bolt12QuotePaymentIdLookup::Found(retry_payment_id),
2130 "stale failed-payment cleanup must not remove a newer retry"
2131 );
2132 }
2133
2134 #[tokio::test]
2135 async fn bolt12_quote_dispatch_concurrent_claims_have_single_winner() {
2136 let kv_store = test_kv_store().await;
2137 let quote_id = QuoteId::new();
2138
2139 let (first_result, second_result) = tokio::join!(
2140 write_bolt12_quote_payment_id(&kv_store, "e_id, None),
2141 write_bolt12_quote_payment_id(&kv_store, "e_id, None),
2142 );
2143
2144 let outcomes = [first_result, second_result];
2145 let winners = outcomes.iter().filter(|result| result.is_ok()).count();
2146 let conflicts = outcomes
2147 .iter()
2148 .filter(|result| matches!(result, Err(Error::Bolt12QuoteAlreadyClaimed { .. })))
2149 .count();
2150
2151 assert_eq!(winners, 1, "exactly one dispatch may claim the quote");
2152 assert_eq!(conflicts, 1, "the losing dispatch must be rejected");
2153 }
2154
2155 #[tokio::test]
2156 async fn bolt12_quote_payment_id_concurrent_resolution_has_single_winner() {
2157 let kv_store = test_kv_store().await;
2158 let quote_id = QuoteId::new();
2159 let first_payment_id = PaymentId([7; 32]);
2160 let second_payment_id = PaymentId([9; 32]);
2161
2162 write_bolt12_quote_payment_id(&kv_store, "e_id, None)
2163 .await
2164 .expect("dispatch should claim the quote");
2165
2166 let (first_result, second_result) = tokio::join!(
2167 write_bolt12_quote_payment_id(&kv_store, "e_id, Some(&first_payment_id)),
2168 write_bolt12_quote_payment_id(&kv_store, "e_id, Some(&second_payment_id)),
2169 );
2170
2171 let outcomes = [&first_result, &second_result];
2172 let winners = outcomes.iter().filter(|result| result.is_ok()).count();
2173 let conflicts = outcomes
2174 .iter()
2175 .filter(|result| matches!(result, Err(Error::Bolt12QuoteAlreadyClaimed { .. })))
2176 .count();
2177
2178 assert_eq!(winners, 1, "exactly one payment id may resolve the claim");
2179 assert_eq!(conflicts, 1, "the losing resolution must be rejected");
2180
2181 let winner = if first_result.is_ok() {
2182 first_payment_id
2183 } else {
2184 second_payment_id
2185 };
2186 assert_eq!(
2187 read_bolt12_quote_payment_id(&kv_store, "e_id)
2188 .await
2189 .expect("payment id should remain readable"),
2190 Bolt12QuotePaymentIdLookup::Found(winner)
2191 );
2192 }
2193
2194 #[tokio::test]
2197 async fn bolt12_quote_payment_id_mapping_malformed_is_indeterminate() {
2198 let kv_store = test_kv_store().await;
2199 let quote_id = QuoteId::new();
2200 let key = bolt12_quote_payment_id_key("e_id).unwrap();
2201
2202 for corrupt in ["not-hex", "0102", "zz"] {
2203 let mut tx = kv_store.begin_transaction().await.unwrap();
2204 tx.kv_write(
2205 LDK_KV_PRIMARY_NAMESPACE,
2206 LDK_KV_BOLT12_OUTGOING_SECONDARY_NAMESPACE,
2207 &key,
2208 corrupt.as_bytes(),
2209 )
2210 .await
2211 .unwrap();
2212 tx.commit().await.unwrap();
2213
2214 assert_eq!(
2215 read_bolt12_quote_payment_id(&kv_store, "e_id)
2216 .await
2217 .unwrap(),
2218 Bolt12QuotePaymentIdLookup::Malformed,
2219 "corrupt value {corrupt} must be indeterminate"
2220 );
2221 }
2222 }
2223}