use std::sync::Arc;
use std::sync::Mutex;
use std::time::{SystemTime, UNIX_EPOCH};
use async_trait::async_trait;
use bitcoin::hashes::{sha256, Hash};
use bitcoin::secp256k1::{Secp256k1, SecretKey};
use lightning_invoice::{Currency, InvoiceBuilder, PaymentSecret};
use rand::RngCore;
use sha2::{Digest, Sha256};
use crate::error::Error;
use crate::lightning::{
Invoice, InvoiceCreateRequest, InvoiceLookup, LightningNode, PaymentResult,
};
const SIGNING_KEY_HEX: &str = "e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734";
#[derive(Debug, Clone)]
struct Entry {
amount_msat: u64,
preimage: [u8; 32],
bolt11: String,
settled: bool,
#[allow(dead_code)]
payee: String,
}
#[derive(Default)]
pub struct MemoryLedger {
invoices: Mutex<std::collections::HashMap<String, Entry>>,
}
impl MemoryLedger {
pub fn new() -> Arc<Self> {
Arc::new(Self {
invoices: Mutex::new(std::collections::HashMap::new()),
})
}
}
pub struct MemoryNode {
ledger: Arc<MemoryLedger>,
name: String,
}
impl MemoryNode {
pub fn new(ledger: Arc<MemoryLedger>, name: impl Into<String>) -> Self {
Self {
ledger,
name: name.into(),
}
}
}
#[async_trait]
impl LightningNode for MemoryNode {
async fn create_invoice(&self, req: InvoiceCreateRequest) -> Result<Invoice, Error> {
let secp = Secp256k1::new();
let sk_bytes = hex::decode(SIGNING_KEY_HEX)
.map_err(|e| Error::Lightning(format!("signing key hex: {e}")))?;
let sk = SecretKey::from_slice(&sk_bytes)
.map_err(|e| Error::Lightning(format!("secret key: {e}")))?;
let mut preimage = [0u8; 32];
rand::thread_rng().fill_bytes(&mut preimage);
let payment_hash = sha256::Hash::hash(&preimage);
let payment_hash_hex = hex::encode(payment_hash.to_byte_array());
let mut secret_bytes = [0u8; 32];
rand::thread_rng().fill_bytes(&mut secret_bytes);
let payment_secret = PaymentSecret(secret_bytes);
let ts = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|e| Error::Lightning(format!("time: {e}")))?;
let memo = req.memo.unwrap_or_default();
let expiry = req.expiry_seconds.unwrap_or(300);
let inv = InvoiceBuilder::new(Currency::Regtest)
.description(memo)
.payment_hash(payment_hash)
.payment_secret(payment_secret)
.amount_milli_satoshis(req.amount_msat)
.duration_since_epoch(ts)
.min_final_cltv_expiry_delta(10)
.expiry_time(std::time::Duration::from_secs(expiry))
.build_signed(|h| secp.sign_ecdsa_recoverable(h, &sk))
.map_err(|e| Error::Lightning(format!("bolt11 build: {e:?}")))?;
let bolt11_str = inv.to_string();
let mut map = self.ledger.invoices.lock().unwrap();
map.insert(
payment_hash_hex.clone(),
Entry {
amount_msat: req.amount_msat,
preimage,
bolt11: bolt11_str.clone(),
settled: false,
payee: self.name.clone(),
},
);
Ok(Invoice {
bolt11: bolt11_str,
payment_hash: payment_hash_hex,
})
}
async fn lookup_invoice(&self, payment_hash: &str) -> Result<InvoiceLookup, Error> {
let map = self.ledger.invoices.lock().unwrap();
let entry = map
.get(payment_hash)
.ok_or_else(|| Error::Lightning(format!("unknown payment_hash: {payment_hash}")))?;
Ok(InvoiceLookup {
settled: entry.settled,
amount_msat: entry.amount_msat,
preimage: if entry.settled {
Some(entry.preimage.to_vec())
} else {
None
},
})
}
async fn pay_invoice(&self, bolt11: &str) -> Result<PaymentResult, Error> {
let mut map = self.ledger.invoices.lock().unwrap();
for entry in map.values_mut() {
if entry.bolt11 == bolt11 {
if entry.settled {
return Err(Error::Lightning("invoice already settled".into()));
}
entry.settled = true;
return Ok(PaymentResult {
preimage: entry.preimage.to_vec(),
fee_msat: 0,
});
}
}
Err(Error::Lightning(format!(
"unknown bolt11: {}...",
&bolt11[..32.min(bolt11.len())]
)))
}
}
#[allow(dead_code)]
fn _dummy_sha256() {
let mut h = Sha256::new();
h.update([0u8; 0]);
let _ = h.finalize();
}