Skip to content

Commit 01f1b9e

Browse files
committed
Introduce PaymentStore to store external payment information
1 parent 9d03752 commit 01f1b9e

8 files changed

Lines changed: 208 additions & 4 deletions

File tree

‎libs/sdk-core/src/breez_services.rs‎

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ use crate::models::{
3737
};
3838
use crate::node_api::NodeAPI;
3939
use crate::persist::db::SqliteStorage;
40+
use crate::persist::payment_store::PaymentStore;
4041
use crate::persist::swap::SwapStorage;
4142
use crate::persist::transactions::PaymentStorage;
4243
use crate::receiver::{PaymentReceiver, Receiver};
@@ -144,6 +145,7 @@ pub struct BreezServices {
144145
config: Config,
145146
started: Mutex<bool>,
146147
node_api: Arc<dyn NodeAPI>,
148+
payment_store: Arc<dyn PaymentStore>,
147149
lsp_api: Arc<dyn LspAPI>,
148150
receiver: Arc<dyn Receiver>,
149151
fiat_api: Arc<dyn FiatAPI>,
@@ -297,6 +299,17 @@ impl BreezServices {
297299
return Err(SendPaymentError::AlreadyPaid);
298300
}
299301

302+
let info = LnPaymentInfo {
303+
bolt11: parsed_invoice.bolt11.clone(),
304+
payment_hash: parsed_invoice.payment_hash.clone(),
305+
destination_pubkey: parsed_invoice.payee_pubkey.clone(),
306+
description: parsed_invoice.description.clone(),
307+
keysend: false,
308+
};
309+
self.payment_store
310+
.set_ln_info(&parsed_invoice.payment_hash, &info)
311+
.await?;
312+
300313
self.persist_pending_payment(&parsed_invoice, amount_msat)?;
301314

302315
debug!("attempting normal payment");
@@ -414,9 +427,24 @@ impl BreezServices {
414427

415428
let lnurl_pay_domain = match req.data.ln_address {
416429
Some(_) => None,
417-
None => Some(req.data.domain),
430+
None => Some(req.data.domain.clone()),
418431
};
419432
// Store SA (if available) + LN Address in separate table, associated to payment_hash
433+
let target = match &req.data.ln_address {
434+
Some(address) => LnUrlPayTarget::LnAddress(address.clone()),
435+
None => LnUrlPayTarget::Domain(req.data.domain),
436+
};
437+
let info = LnUrlPayInfo {
438+
target,
439+
metadata: req.data.metadata_str.clone(),
440+
comment: req.comment.clone(),
441+
success_action: maybe_sa_processed.clone(),
442+
};
443+
let info = LnUrlInfo::Pay(info);
444+
self.payment_store
445+
.set_lnurl_info(&invoice.payment_hash, &info)
446+
.await?;
447+
420448
self.persister.insert_payment_external_info(
421449
&details.payment_hash,
422450
PaymentExternalInfo {
@@ -465,6 +493,14 @@ impl BreezServices {
465493
let res = validate_lnurl_withdraw(self.rest_client.as_ref(), req.data, invoice).await?;
466494

467495
if let LnUrlWithdrawResult::Ok { ref data } = res {
496+
let info = LnUrlWithdrawInfo {
497+
endpoint: lnurl_w_endpoint.clone(),
498+
};
499+
let info = LnUrlInfo::Withdraw(info);
500+
self.payment_store
501+
.set_lnurl_info(&data.invoice.payment_hash, &info)
502+
.await?;
503+
468504
// If endpoint was successfully called, store the LNURL-withdraw endpoint URL as metadata linked to the invoice
469505
self.persister.insert_payment_external_info(
470506
&data.invoice.payment_hash,
@@ -1887,6 +1923,7 @@ struct BreezServicesBuilder {
18871923
node_api: Option<Arc<dyn NodeAPI>>,
18881924
backup_transport: Option<Arc<dyn BackupTransport>>,
18891925
seed: Option<Vec<u8>>,
1926+
payment_store: Option<Arc<dyn PaymentStore>>,
18901927
lsp_api: Option<Arc<dyn LspAPI>>,
18911928
fiat_api: Option<Arc<dyn FiatAPI>>,
18921929
persister: Option<Arc<SqliteStorage>>,
@@ -1908,6 +1945,7 @@ impl BreezServicesBuilder {
19081945
config,
19091946
node_api: None,
19101947
seed: None,
1948+
payment_store: None,
19111949
lsp_api: None,
19121950
fiat_api: None,
19131951
persister: None,
@@ -1927,6 +1965,11 @@ impl BreezServicesBuilder {
19271965
self
19281966
}
19291967

1968+
pub fn payment_store(&mut self, payment_store: Arc<dyn PaymentStore>) -> &mut Self {
1969+
self.payment_store = Some(payment_store);
1970+
self
1971+
}
1972+
19301973
#[cfg(test)]
19311974
pub fn lsp_api(&mut self, lsp_api: Arc<dyn LspAPI>) -> &mut Self {
19321975
self.lsp_api = Some(lsp_api.clone());
@@ -2023,6 +2066,7 @@ impl BreezServicesBuilder {
20232066
let mut node_api = self.node_api.clone();
20242067
let mut backup_transport = self.backup_transport.clone();
20252068
let mut lsp_api = self.lsp_api.clone();
2069+
let mut payment_store = self.payment_store.clone();
20262070
if node_api.is_none() {
20272071
let node_impls = node_builder::build_node(
20282072
self.config.clone(),
@@ -2034,7 +2078,11 @@ impl BreezServicesBuilder {
20342078
node_api = Some(node_impls.node);
20352079
backup_transport = backup_transport.or(Some(node_impls.backup_transport));
20362080
lsp_api = lsp_api.or(node_impls.lsp);
2081+
payment_store = payment_store.or(Some(node_impls.payment_store));
20372082
}
2083+
let payment_store = payment_store.ok_or(ConnectError::Generic {
2084+
err: "Payment store should be provided".into(),
2085+
})?;
20382086
let lsp_api = lsp_api.ok_or(ConnectError::Generic {
20392087
err: "LSP Api should be provided".into(),
20402088
})?;
@@ -2149,6 +2197,7 @@ impl BreezServicesBuilder {
21492197
config: self.config.clone(),
21502198
started: Mutex::new(false),
21512199
node_api: unwrapped_node_api.clone(),
2200+
payment_store,
21522201
lsp_api,
21532202
receiver,
21542203
fiat_api: self
@@ -2542,6 +2591,7 @@ pub(crate) mod tests {
25422591
.fiat_api(Arc::new(MockBreezServer {}))
25432592
.node_api(node_api)
25442593
.persister(persister)
2594+
.payment_store(Arc::new(MockPaymentStore::default()))
25452595
.backup_transport(Arc::new(MockBackupTransport::new()))
25462596
.build(None, None)
25472597
.await?;
@@ -2705,6 +2755,7 @@ pub(crate) mod tests {
27052755
.persister(persister)
27062756
.node_api(node_api)
27072757
.rest_client(rest_client)
2758+
.payment_store(Arc::new(MockPaymentStore::default()))
27082759
.backup_transport(Arc::new(MockBackupTransport::new()))
27092760
.build(None, None)
27102761
.await?;

‎libs/sdk-core/src/ldk/node_api.rs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ use crate::models::{Config, LspAPI, NodeState, OpeningFeeParams, OpeningFeeParam
3838
use crate::node_api::{
3939
CreateInvoiceRequest, FetchBolt11Result, IncomingPayment, NodeAPI, NodeError, NodeResult,
4040
};
41+
use crate::persist::payment_store::PaymentStore;
4142
use crate::{
4243
CustomMessage, LspInformation, MaxChannelAmount, Payment, PaymentResponse,
4344
PrepareRedeemOnchainFundsRequest, PrepareRedeemOnchainFundsResponse, RouteHintHop, TlvEntry,
@@ -129,6 +130,10 @@ impl Ldk {
129130
remote_lock_shutdown_tx,
130131
})
131132
}
133+
134+
pub fn get_payment_store(&self) -> Arc<dyn PaymentStore> {
135+
Arc::new(self.store.clone())
136+
}
132137
}
133138

134139
#[tonic::async_trait]

‎libs/sdk-core/src/ldk/store/kv_store.rs‎

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
use core::convert::TryInto;
2+
use std::collections::HashMap;
23
use std::sync::Arc;
34

45
use bitcoin::io::{Error, ErrorKind};
5-
use ldk_node::lightning::util::persist::KVStoreSync;
6+
use ldk_node::lightning::util::persist::{KVStore as KVStoreAsync, KVStoreSync};
67
use ldk_node::lightning_types::payment::{PaymentHash, PaymentPreimage};
78
use ldk_node::DynStore;
89

10+
use crate::error::{SdkError, SdkResult};
911
use crate::ldk::utils::Hex;
12+
use crate::persist::payment_store::PaymentStore;
13+
use crate::{LnPaymentInfo, LnUrlInfo};
1014

1115
pub(crate) const BREEZ_NS: &str = "breez";
1216
pub(crate) const BOLT11_NS: &str = "bolt11";
1317
pub(crate) const PREIMAGES_NS: &str = "preimages";
18+
pub(crate) const LN_INFOS_NS: &str = "ln_infos";
19+
pub(crate) const LNURL_INFOS_NS: &str = "lnurl_infos";
1420

1521
pub(crate) type KVStore = Arc<DynStore>;
1622

@@ -69,3 +75,57 @@ impl Store {
6975
}
7076
}
7177
}
78+
79+
#[tonic::async_trait]
80+
impl PaymentStore for Store {
81+
async fn set_ln_info(&self, payment_id: &str, info: &LnPaymentInfo) -> SdkResult<()> {
82+
let info = serde_json::to_vec(info)?;
83+
KVStoreAsync::write(
84+
self.kv_store.as_ref(),
85+
BREEZ_NS,
86+
LN_INFOS_NS,
87+
payment_id,
88+
info,
89+
)
90+
.await
91+
.map_err(Into::into)
92+
}
93+
94+
async fn set_lnurl_info(&self, payment_id: &str, info: &LnUrlInfo) -> SdkResult<()> {
95+
let info = serde_json::to_vec(info)?;
96+
KVStoreAsync::write(
97+
self.kv_store.as_ref(),
98+
BREEZ_NS,
99+
LNURL_INFOS_NS,
100+
payment_id,
101+
info,
102+
)
103+
.await
104+
.map_err(Into::into)
105+
}
106+
107+
async fn get_info(&self, payment_ids: &[&str]) -> SdkResult<HashMap<String, LnPaymentInfo>> {
108+
let mut infos = HashMap::new();
109+
for payment_id in payment_ids {
110+
match KVStoreAsync::read(self.kv_store.as_ref(), BREEZ_NS, LN_INFOS_NS, payment_id)
111+
.await
112+
{
113+
Ok(raw) => {
114+
let info = serde_json::from_slice::<LnPaymentInfo>(&raw)?;
115+
infos.insert((*payment_id).to_string(), info);
116+
}
117+
Err(err) if err.kind() == ErrorKind::NotFound => {}
118+
Err(err) => return Err(SdkError::generic(&err.to_string())),
119+
}
120+
}
121+
Ok(infos)
122+
}
123+
}
124+
125+
impl From<Error> for SdkError {
126+
fn from(err: Error) -> Self {
127+
SdkError::Generic {
128+
err: err.to_string(),
129+
}
130+
}
131+
}

‎libs/sdk-core/src/models.rs‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,40 @@ pub(crate) struct PaymentExternalInfo {
672672
pub attempted_error: Option<String>,
673673
}
674674

675+
#[derive(Clone, Serialize, Deserialize)]
676+
pub(crate) enum LnUrlPayTarget {
677+
LnAddress(String),
678+
Domain(String),
679+
}
680+
681+
#[derive(Clone, Serialize, Deserialize)]
682+
pub(crate) struct LnUrlPayInfo {
683+
pub target: LnUrlPayTarget,
684+
pub metadata: String,
685+
pub comment: Option<String>,
686+
pub success_action: Option<SuccessActionProcessed>,
687+
}
688+
689+
#[derive(Clone, Serialize, Deserialize)]
690+
pub(crate) struct LnUrlWithdrawInfo {
691+
pub endpoint: String,
692+
}
693+
694+
#[derive(Clone, Serialize, Deserialize)]
695+
pub(crate) enum LnUrlInfo {
696+
Pay(LnUrlPayInfo),
697+
Withdraw(LnUrlWithdrawInfo),
698+
}
699+
700+
#[derive(Clone, Serialize, Deserialize)]
701+
pub(crate) struct LnPaymentInfo {
702+
pub bolt11: String,
703+
pub payment_hash: String,
704+
pub destination_pubkey: String,
705+
pub description: Option<String>,
706+
pub keysend: bool,
707+
}
708+
675709
/// Represents a list payments request.
676710
#[derive(Default)]
677711
pub struct ListPaymentsRequest {

‎libs/sdk-core/src/node_builder.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ use crate::ldk::{Ldk, LdkBackupTransport};
55
use crate::models::{Config, LspAPI};
66
use crate::node_api::{NodeAPI, NodeResult};
77
use crate::persist::db::SqliteStorage;
8+
use crate::persist::payment_store::PaymentStore;
89

910
pub struct NodeImpls {
1011
pub node: Arc<dyn NodeAPI>,
1112
pub backup_transport: Arc<dyn BackupTransport>,
1213
pub lsp: Option<Arc<dyn LspAPI>>,
14+
pub payment_store: Arc<dyn PaymentStore>,
1315
}
1416

1517
#[allow(unused_variables)]
@@ -23,9 +25,11 @@ pub async fn build_node(
2325
let ldk = Ldk::build(config, &seed, restore_only).await?;
2426
let ldk = Arc::new(ldk);
2527
let lsp: Option<Arc<dyn LspAPI>> = Some(ldk.clone());
28+
let payment_store = ldk.get_payment_store();
2629
Ok(NodeImpls {
2730
node: ldk,
2831
backup_transport,
2932
lsp,
33+
payment_store,
3034
})
3135
}

‎libs/sdk-core/src/persist/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub(crate) mod cache;
22
pub(crate) mod db;
33
pub(crate) mod error;
44
pub(crate) mod migrations;
5+
pub(crate) mod payment_store;
56
pub(crate) mod reverseswap;
67
pub(crate) mod send_pays;
78
pub(crate) mod settings;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use std::collections::HashMap;
2+
3+
use crate::error::SdkResult;
4+
use crate::models::LnPaymentInfo;
5+
use crate::LnUrlInfo;
6+
7+
#[tonic::async_trait]
8+
pub trait PaymentStore: Send + Sync {
9+
async fn set_ln_info(&self, payment_id: &str, info: &LnPaymentInfo) -> SdkResult<()>;
10+
async fn set_lnurl_info(&self, payment_id: &str, info: &LnUrlInfo) -> SdkResult<()>;
11+
async fn get_info(&self, payment_ids: &[&str]) -> SdkResult<HashMap<String, LnPaymentInfo>>;
12+
}

‎libs/sdk-core/src/test_utils.rs‎

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,13 @@ use crate::lightning::bitcoin::secp256k1 as ldk_secp256k1;
4141
use crate::lightning_invoice::{Currency, InvoiceBuilder, PaymentSecret, RawBolt11Invoice};
4242
use crate::lsp::LspInformation;
4343
use crate::models::{
44-
LnPaymentDetails, LspAPI, NodeState, Payment, PaymentDetails, PaymentStatus, PaymentType,
45-
ReverseSwapServiceAPI, SwapperAPI, TlvEntry,
44+
LnPaymentDetails, LnPaymentInfo, LnUrlInfo, LspAPI, NodeState, Payment, PaymentDetails,
45+
PaymentStatus, PaymentType, ReverseSwapServiceAPI, SwapperAPI, TlvEntry,
4646
};
4747
use crate::node_api::{
4848
CreateInvoiceRequest, FetchBolt11Result, IncomingPayment, NodeAPI, NodeError, NodeResult,
4949
};
50+
use crate::persist::payment_store::PaymentStore;
5051
use crate::swap_in::TaprootSwapperAPI;
5152
use crate::swap_out::boltzswap::{BoltzApiCreateReverseSwapResponse, BoltzApiReverseSwapStatus};
5253
use crate::swap_out::error::{ReverseSwapError, ReverseSwapResult};
@@ -164,6 +165,42 @@ impl ReverseSwapServiceAPI for MockReverseSwapperAPI {
164165
}
165166
}
166167

168+
#[derive(Default)]
169+
pub struct MockPaymentStore {
170+
ln_info: std::sync::Mutex<HashMap<String, LnPaymentInfo>>,
171+
lnurl_info: std::sync::Mutex<HashMap<String, LnUrlInfo>>,
172+
}
173+
174+
#[tonic::async_trait]
175+
impl PaymentStore for MockPaymentStore {
176+
async fn set_ln_info(&self, payment_id: &str, info: &LnPaymentInfo) -> SdkResult<()> {
177+
self.ln_info
178+
.lock()
179+
.unwrap()
180+
.insert(payment_id.to_string(), info.clone());
181+
Ok(())
182+
}
183+
184+
async fn set_lnurl_info(&self, payment_id: &str, info: &LnUrlInfo) -> SdkResult<()> {
185+
self.lnurl_info
186+
.lock()
187+
.unwrap()
188+
.insert(payment_id.to_string(), info.clone());
189+
Ok(())
190+
}
191+
192+
async fn get_info(&self, payment_ids: &[&str]) -> SdkResult<HashMap<String, LnPaymentInfo>> {
193+
let ln_info = self.ln_info.lock().unwrap();
194+
let mut res = HashMap::new();
195+
for payment_id in payment_ids {
196+
if let Some(info) = ln_info.get(*payment_id) {
197+
res.insert((*payment_id).to_string(), info.clone());
198+
}
199+
}
200+
Ok(res)
201+
}
202+
}
203+
167204
#[derive(Clone)]
168205
pub struct MockChainService {
169206
pub tip: u32,

0 commit comments

Comments
 (0)