fedimint_recurringd/
db.rs

1use std::collections::HashMap;
2
3use fedimint_core::config::FederationId;
4use fedimint_core::core::OperationId;
5use fedimint_core::db::{AutocommitError, Database, IDatabaseTransactionOpsCoreTyped};
6use fedimint_core::encoding::{Decodable, Encodable};
7use fedimint_core::secp256k1::rand::thread_rng;
8use fedimint_core::{impl_db_lookup, impl_db_record};
9use fedimint_ln_client::recurring::{PaymentCodeId, PaymentCodeRootKey, RecurringPaymentProtocol};
10use futures::stream::StreamExt;
11use rand::Rng;
12
13use crate::PaymentCodeInvoice;
14
15#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Encodable, Decodable)]
16pub struct FederationDbPrefix([u8; 16]);
17
18impl FederationDbPrefix {
19    pub fn random() -> FederationDbPrefix {
20        FederationDbPrefix(thread_rng().r#gen())
21    }
22
23    fn prepend(&self, byte: u8) -> Vec<u8> {
24        let mut full_prefix = Vec::with_capacity(17);
25        full_prefix.push(byte);
26        full_prefix.extend(&self.0);
27        full_prefix
28    }
29}
30
31async fn load_federation_clients(db: &Database) -> Vec<(FederationId, FederationDbPrefix)> {
32    let mut dbtx = db.begin_transaction_nc().await;
33    dbtx.find_by_prefix(&FederationClientPrefix)
34        .await
35        .map(|(k, v)| (k.federation_id, v.db_prefix))
36        .collect::<Vec<_>>()
37        .await
38}
39
40pub fn open_client_db(db: &Database, db_prefix: FederationDbPrefix) -> Database {
41    db.with_prefix(db_prefix.prepend(DbKeyPrefix::ClientDB as u8))
42}
43
44pub async fn try_add_federation_database(
45    db: &Database,
46    federation_id: FederationId,
47    db_prefix: FederationDbPrefix,
48) -> Result<(), FederationDbPrefix> {
49    db.autocommit(
50        |dbtx, _| {
51            Box::pin(async move {
52                if let Some(federation_db_entry) =
53                    dbtx.get_value(&FederationClientKey { federation_id }).await
54                {
55                    return Err(federation_db_entry.db_prefix);
56                }
57
58                dbtx.insert_new_entry(
59                    &FederationClientKey { federation_id },
60                    &FederationClientEntry { db_prefix },
61                )
62                .await;
63
64                Ok(())
65            })
66        },
67        None,
68    )
69    .await
70    .map_err(|e| match e {
71        AutocommitError::CommitFailed { .. } => unreachable!("will keep retrying"),
72        AutocommitError::ClosureError { error, .. } => {
73            // TODO: clean up DB once parallel joins are enabled
74            error
75        }
76    })
77}
78
79pub async fn load_federation_client_databases(db: &Database) -> HashMap<FederationId, Database> {
80    load_federation_clients(db)
81        .await
82        .into_iter()
83        .map(|(federation_id, db_prefix)| (federation_id, open_client_db(db, db_prefix)))
84        .collect()
85}
86
87#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
88enum DbKeyPrefix {
89    ClientList = 0x00,
90    ClientDB = 0x01,
91    PaymentCodes = 0x02,
92    PaymentCodeNextInvoiceIndex = 0x03,
93    PaymentCodeInvoices = 0x04,
94}
95
96#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable)]
97pub struct FederationClientKey {
98    pub federation_id: FederationId,
99}
100
101#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable)]
102pub struct FederationClientPrefix;
103
104impl_db_record!(
105    key = FederationClientKey,
106    value = FederationClientEntry,
107    db_prefix = DbKeyPrefix::ClientList,
108);
109impl_db_lookup!(
110    key = FederationClientKey,
111    query_prefix = FederationClientPrefix
112);
113
114#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable)]
115pub struct FederationClientEntry {
116    pub db_prefix: FederationDbPrefix,
117}
118
119#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable)]
120pub struct PaymentCodeKey {
121    pub payment_code_id: PaymentCodeId,
122}
123
124#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable)]
125pub struct PaymentCodePrefix;
126
127#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable)]
128pub enum PaymentCodeVariant {
129    Lnurl { meta: String },
130}
131
132#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable)]
133pub struct PaymentCodeEntry {
134    pub root_key: PaymentCodeRootKey,
135    pub federation_id: FederationId,
136    pub protocol: RecurringPaymentProtocol,
137    pub payment_code: String,
138    pub variant: PaymentCodeVariant,
139}
140
141impl_db_record!(
142    key = PaymentCodeKey,
143    value = PaymentCodeEntry,
144    db_prefix = DbKeyPrefix::PaymentCodes,
145);
146impl_db_lookup!(key = PaymentCodeKey, query_prefix = PaymentCodePrefix);
147
148#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable)]
149pub struct PaymentCodeNextInvoiceIndexKey {
150    pub payment_code_id: PaymentCodeId,
151}
152
153impl_db_record!(
154    key = PaymentCodeNextInvoiceIndexKey,
155    value = u64,
156    db_prefix = DbKeyPrefix::PaymentCodeNextInvoiceIndex,
157);
158
159#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable)]
160pub struct PaymentCodeInvoiceKey {
161    pub payment_code_id: PaymentCodeId,
162    pub index: u64,
163}
164
165#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable)]
166pub struct PaymentCodeInvoicePrefix {
167    payment_code_id: PaymentCodeId,
168}
169
170#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable)]
171pub struct PaymentCodeInvoiceEntry {
172    pub operation_id: OperationId,
173    pub invoice: PaymentCodeInvoice,
174}
175
176impl_db_record!(
177    key = PaymentCodeInvoiceKey,
178    value = PaymentCodeInvoiceEntry,
179    db_prefix = DbKeyPrefix::PaymentCodeInvoices,
180);
181impl_db_lookup!(
182    key = PaymentCodeInvoiceKey,
183    query_prefix = PaymentCodeInvoicePrefix
184);