1use std::sync::Arc;
4use std::sync::Mutex;
5use std::time::{SystemTime, UNIX_EPOCH};
6
7use async_trait::async_trait;
8use bitcoin::hashes::{sha256, Hash};
9use bitcoin::secp256k1::{Secp256k1, SecretKey};
10use lightning_invoice::{Currency, InvoiceBuilder, PaymentSecret};
11use rand::RngCore;
12use sha2::{Digest, Sha256};
13
14use crate::error::Error;
15use crate::lightning::{
16 Invoice, InvoiceCreateRequest, InvoiceLookup, LightningNode, PaymentResult,
17};
18
19const SIGNING_KEY_HEX: &str = "e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734";
20
21#[derive(Debug, Clone)]
22struct Entry {
23 amount_msat: u64,
24 preimage: [u8; 32],
25 bolt11: String,
26 settled: bool,
27 #[allow(dead_code)]
28 payee: String,
29}
30
31#[derive(Default)]
32pub struct MemoryLedger {
33 invoices: Mutex<std::collections::HashMap<String, Entry>>,
34}
35
36impl MemoryLedger {
37 pub fn new() -> Arc<Self> {
38 Arc::new(Self {
39 invoices: Mutex::new(std::collections::HashMap::new()),
40 })
41 }
42}
43
44pub struct MemoryNode {
45 ledger: Arc<MemoryLedger>,
46 name: String,
47}
48
49impl MemoryNode {
50 pub fn new(ledger: Arc<MemoryLedger>, name: impl Into<String>) -> Self {
51 Self {
52 ledger,
53 name: name.into(),
54 }
55 }
56}
57
58#[async_trait]
59impl LightningNode for MemoryNode {
60 async fn create_invoice(&self, req: InvoiceCreateRequest) -> Result<Invoice, Error> {
61 let secp = Secp256k1::new();
62 let sk_bytes = hex::decode(SIGNING_KEY_HEX)
63 .map_err(|e| Error::Lightning(format!("signing key hex: {e}")))?;
64 let sk = SecretKey::from_slice(&sk_bytes)
65 .map_err(|e| Error::Lightning(format!("secret key: {e}")))?;
66
67 let mut preimage = [0u8; 32];
68 rand::thread_rng().fill_bytes(&mut preimage);
69 let payment_hash = sha256::Hash::hash(&preimage);
70 let payment_hash_hex = hex::encode(payment_hash.to_byte_array());
71
72 let mut secret_bytes = [0u8; 32];
73 rand::thread_rng().fill_bytes(&mut secret_bytes);
74 let payment_secret = PaymentSecret(secret_bytes);
75
76 let ts = SystemTime::now()
77 .duration_since(UNIX_EPOCH)
78 .map_err(|e| Error::Lightning(format!("time: {e}")))?;
79 let memo = req.memo.unwrap_or_default();
80 let expiry = req.expiry_seconds.unwrap_or(300);
81
82 let inv = InvoiceBuilder::new(Currency::Regtest)
83 .description(memo)
84 .payment_hash(payment_hash)
85 .payment_secret(payment_secret)
86 .amount_milli_satoshis(req.amount_msat)
87 .duration_since_epoch(ts)
88 .min_final_cltv_expiry_delta(10)
89 .expiry_time(std::time::Duration::from_secs(expiry))
90 .build_signed(|h| secp.sign_ecdsa_recoverable(h, &sk))
91 .map_err(|e| Error::Lightning(format!("bolt11 build: {e:?}")))?;
92 let bolt11_str = inv.to_string();
93
94 let mut map = self.ledger.invoices.lock().unwrap();
95 map.insert(
96 payment_hash_hex.clone(),
97 Entry {
98 amount_msat: req.amount_msat,
99 preimage,
100 bolt11: bolt11_str.clone(),
101 settled: false,
102 payee: self.name.clone(),
103 },
104 );
105 Ok(Invoice {
106 bolt11: bolt11_str,
107 payment_hash: payment_hash_hex,
108 })
109 }
110
111 async fn lookup_invoice(&self, payment_hash: &str) -> Result<InvoiceLookup, Error> {
112 let map = self.ledger.invoices.lock().unwrap();
113 let entry = map
114 .get(payment_hash)
115 .ok_or_else(|| Error::Lightning(format!("unknown payment_hash: {payment_hash}")))?;
116 Ok(InvoiceLookup {
117 settled: entry.settled,
118 amount_msat: entry.amount_msat,
119 preimage: if entry.settled {
120 Some(entry.preimage.to_vec())
121 } else {
122 None
123 },
124 })
125 }
126
127 async fn pay_invoice(&self, bolt11: &str) -> Result<PaymentResult, Error> {
128 let mut map = self.ledger.invoices.lock().unwrap();
129 for entry in map.values_mut() {
130 if entry.bolt11 == bolt11 {
131 if entry.settled {
132 return Err(Error::Lightning("invoice already settled".into()));
133 }
134 entry.settled = true;
135 return Ok(PaymentResult {
136 preimage: entry.preimage.to_vec(),
137 fee_msat: 0,
138 });
139 }
140 }
141 Err(Error::Lightning(format!(
142 "unknown bolt11: {}...",
143 &bolt11[..32.min(bolt11.len())]
144 )))
145 }
146}
147
148#[allow(dead_code)]
150fn _dummy_sha256() {
151 let mut h = Sha256::new();
152 h.update([0u8; 0]);
153 let _ = h.finalize();
154}