feat: persist invoice rather than amount (#66)

Co-authored-by: ok300 <106775972+ok300@users.noreply.github.com>
This commit is contained in:
yse
2024-04-12 15:51:16 +02:00
committed by GitHub
parent efc970d1e0
commit 85e8a6d1a3
6 changed files with 135 additions and 58 deletions

View File

@@ -1,5 +1,7 @@
use std::borrow::Cow::{self, Owned};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use anyhow::Result;
use clap::{arg, Parser};
@@ -15,7 +17,13 @@ use serde_json::to_string_pretty;
#[derive(Parser, Debug, Clone, PartialEq)]
pub(crate) enum Command {
/// Send lbtc and receive btc through a swap
SendPayment { bolt11: String },
SendPayment {
bolt11: String,
/// Delay for the send, in seconds
#[arg(short, long)]
delay: Option<u64>,
},
/// Receive lbtc and send btc through a swap
ReceivePayment {
#[arg(short, long)]
@@ -76,10 +84,22 @@ pub(crate) fn handle_command(
qr2term::print_qr(response.invoice.clone())?;
command_result!(response)
}
Command::SendPayment { bolt11 } => {
Command::SendPayment { bolt11, delay } => {
let prepare_response = wallet.prepare_payment(&bolt11)?;
let response = wallet.send_payment(&prepare_response)?;
command_result!(response)
if let Some(delay) = delay {
let wallet_cloned = wallet.clone();
let prepare_cloned = prepare_response.clone();
thread::spawn(move || {
thread::sleep(Duration::from_secs(delay));
wallet_cloned.send_payment(&prepare_cloned).unwrap();
});
command_result!(prepare_response)
} else {
let response = wallet.send_payment(&prepare_response)?;
command_result!(response)
}
}
Command::GetInfo => {
command_result!(wallet.get_info(true)?)

View File

@@ -8,6 +8,7 @@ pub use wallet::*;
// To avoid sendrawtransaction error "min relay fee not met"
const CLAIM_ABSOLUTE_FEES: u64 = 134;
const DEFAULT_DATA_DIR: &str = ".data";
const MAIN_DB_FILE: &str = "storage.sql";
#[macro_export]
macro_rules! ensure_sdk {

View File

@@ -69,7 +69,7 @@ pub struct ReceivePaymentResponse {
pub invoice: String,
}
#[derive(Debug)]
#[derive(Debug, Clone, Serialize)]
pub struct PreparePaymentResponse {
pub id: String,
pub funding_amount: u64,
@@ -101,14 +101,26 @@ pub enum PaymentError {
#[error("The generated preimage is not valid")]
InvalidPreimage,
#[error("The specified funds have already been claimed")]
AlreadyClaimed,
#[error("Generic boltz error: {err}")]
BoltzGeneric { err: String },
}
impl From<Error> for PaymentError {
fn from(err: Error) -> Self {
PaymentError::BoltzGeneric {
err: format!("{err:?}"),
match err {
Error::Protocol(msg) => {
if msg == "Could not find utxos for script" {
return PaymentError::AlreadyClaimed;
}
PaymentError::BoltzGeneric { err: msg }
}
_ => PaymentError::BoltzGeneric {
err: format!("{err:?}"),
},
}
}
}
@@ -126,13 +138,14 @@ pub(crate) enum OngoingSwap {
id: String,
amount_sat: u64,
funding_address: String,
invoice: String,
},
Receive {
id: String,
preimage: String,
redeem_script: String,
blinding_key: String,
invoice_amount_sat: u64,
invoice: String,
onchain_amount_sat: u64,
},
}
@@ -152,24 +165,35 @@ pub struct Payment {
pub amount_sat: u64,
#[serde(rename(serialize = "type"))]
pub payment_type: PaymentType,
/// Only for [PaymentType::PendingReceive]
pub invoice: Option<String>,
}
impl From<OngoingSwap> for Payment {
fn from(swap: OngoingSwap) -> Self {
match swap {
OngoingSwap::Send { amount_sat, .. } => Payment {
OngoingSwap::Send {
amount_sat,
invoice,
..
} => Payment {
id: None,
timestamp: None,
payment_type: PaymentType::PendingSend,
amount_sat,
invoice: Some(invoice),
},
OngoingSwap::Receive {
onchain_amount_sat, ..
onchain_amount_sat,
invoice,
..
} => Payment {
id: None,
timestamp: None,
payment_type: PaymentType::PendingReceive,
amount_sat: onchain_amount_sat,
invoice: Some(invoice),
},
}
}

View File

@@ -5,7 +5,7 @@ pub(crate) fn current_migrations() -> Vec<&'static str> {
preimage TEXT NOT NULL,
redeem_script TEXT NOT NULL,
blinding_key TEXT NOT NULL,
invoice_amount_sat INTEGER NOT NULL,
invoice TEXT NOT NULL,
onchain_amount_sat INTEGER NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
) STRICT;",
@@ -13,6 +13,7 @@ pub(crate) fn current_migrations() -> Vec<&'static str> {
id TEXT NOT NULL PRIMARY KEY,
amount_sat INTEGER NOT NULL,
funding_address TEXT NOT NULL,
invoice TEXT NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
) STRICT;",
]

View File

@@ -1,26 +1,30 @@
mod migrations;
use std::{fs::create_dir_all, path::PathBuf, str::FromStr};
use anyhow::Result;
use rusqlite::{params, Connection};
use rusqlite_migration::{Migrations, M};
use crate::OngoingSwap;
use crate::{OngoingSwap, MAIN_DB_FILE};
use migrations::current_migrations;
pub(crate) struct Persister {
main_db_file: String,
pub(crate) main_db_dir: PathBuf,
}
impl Persister {
pub fn new(working_dir: &str) -> Self {
let main_db_file = format!("{}/storage.sql", working_dir);
Persister { main_db_file }
pub fn new(working_dir: &str) -> Result<Self> {
let main_db_dir = PathBuf::from_str(working_dir)?;
if !main_db_dir.exists() {
create_dir_all(&main_db_dir)?;
}
Ok(Persister { main_db_dir })
}
pub(crate) fn get_connection(&self) -> Result<Connection> {
let con = Connection::open(self.main_db_file.clone())?;
Ok(con)
Ok(Connection::open(self.main_db_dir.join(MAIN_DB_FILE))?)
}
pub fn init(&self) -> Result<()> {
@@ -44,26 +48,28 @@ impl Persister {
id,
funding_address,
amount_sat,
invoice,
} => {
let mut stmt = con.prepare(
"
INSERT INTO ongoing_send_swaps (
id,
amount_sat,
funding_address
funding_address,
invoice
)
VALUES (?, ?, ?)
VALUES (?, ?, ?, ?)
",
)?;
_ = stmt.execute((&id, &amount_sat, &funding_address))?
_ = stmt.execute((&id, &amount_sat, &funding_address, invoice))?
}
OngoingSwap::Receive {
id,
preimage,
redeem_script,
blinding_key,
invoice_amount_sat,
invoice,
onchain_amount_sat,
} => {
let mut stmt = con.prepare(
@@ -73,7 +79,7 @@ impl Persister {
preimage,
redeem_script,
blinding_key,
invoice_amount_sat,
invoice,
onchain_amount_sat
)
VALUES (?, ?, ?, ?, ?, ?)
@@ -85,7 +91,7 @@ impl Persister {
&preimage,
&redeem_script,
&blinding_key,
&invoice_amount_sat,
&invoice,
&onchain_amount_sat,
))?
}
@@ -123,6 +129,7 @@ impl Persister {
id,
amount_sat,
funding_address,
invoice,
created_at
FROM ongoing_send_swaps
ORDER BY created_at
@@ -135,6 +142,7 @@ impl Persister {
id: row.get(0)?,
amount_sat: row.get(1)?,
funding_address: row.get(2)?,
invoice: row.get(3)?,
})
})?
.map(|i| i.unwrap())
@@ -151,7 +159,7 @@ impl Persister {
preimage,
redeem_script,
blinding_key,
invoice_amount_sat,
invoice,
onchain_amount_sat,
created_at
FROM ongoing_receive_swaps
@@ -166,7 +174,7 @@ impl Persister {
preimage: row.get(1)?,
redeem_script: row.get(2)?,
blinding_key: row.get(3)?,
invoice_amount_sat: row.get(4)?,
invoice: row.get(4)?,
onchain_amount_sat: row.get(5)?,
})
})?

View File

@@ -10,7 +10,10 @@ use anyhow::{anyhow, Result};
use boltz_client::{
network::electrum::ElectrumConfig,
swaps::{
boltz::{BoltzApiClient, CreateSwapRequest, BOLTZ_MAINNET_URL, BOLTZ_TESTNET_URL},
boltz::{
BoltzApiClient, CreateSwapRequest, SubSwapStates, SwapStatusRequest, BOLTZ_MAINNET_URL,
BOLTZ_TESTNET_URL,
},
liquid::{LBtcSwapScript, LBtcSwapTx},
},
util::secrets::{LBtcReverseRecovery, LiquidSwapKey, Preimage, SwapKey},
@@ -46,7 +49,7 @@ pub struct Wallet {
network: Network,
wallet: Arc<Mutex<LwkWollet>>,
active_address: Option<u32>,
swap_persister: Persister,
persister: Persister,
data_dir_path: String,
}
@@ -80,8 +83,8 @@ impl Wallet {
fs::create_dir_all(&data_dir_path)?;
let swap_persister = Persister::new(&data_dir_path);
swap_persister.init()?;
let persister = Persister::new(&data_dir_path)?;
persister.init()?;
let wallet = Arc::new(Wallet {
wallet,
@@ -89,11 +92,11 @@ impl Wallet {
electrum_url,
signer: opts.signer,
active_address: None,
swap_persister,
persister,
data_dir_path,
});
Wallet::track_claims(&wallet)?;
Wallet::track_pending_swaps(&wallet)?;
Ok(wallet)
}
@@ -110,25 +113,52 @@ impl Wallet {
Ok(descriptor_str.parse()?)
}
fn track_claims(self: &Arc<Wallet>) -> Result<()> {
fn track_pending_swaps(self: &Arc<Wallet>) -> Result<()> {
let cloned = self.clone();
let client = self.boltz_client();
thread::spawn(move || loop {
thread::sleep(Duration::from_secs(5));
let ongoing_swaps = cloned.swap_persister.list_ongoing_swaps().unwrap();
let ongoing_swaps = cloned.persister.list_ongoing_swaps().unwrap();
for swap in ongoing_swaps {
if let OngoingSwap::Receive {
id,
preimage,
redeem_script,
blinding_key,
..
} = swap
{
match cloned.try_claim(&preimage, &redeem_script, &blinding_key, None) {
Ok(_) => cloned.swap_persister.resolve_ongoing_swap(&id).unwrap(),
Err(e) => warn!("Could not claim yet. Err: {e}"),
match swap {
OngoingSwap::Receive {
id,
preimage,
redeem_script,
blinding_key,
..
} => match cloned.try_claim(&preimage, &redeem_script, &blinding_key, None) {
Ok(_) => cloned
.persister
.resolve_ongoing_swap(&id)
.unwrap_or_else(|err| {
warn!("Could not write to database. Err: {err:?}")
}),
Err(err) => {
if let PaymentError::AlreadyClaimed = err {
warn!("Funds already claimed");
cloned.persister.resolve_ongoing_swap(&id).unwrap()
}
warn!("Could not claim yet. Err: {err}");
}
},
OngoingSwap::Send { id, .. } => {
let Ok(status_response) =
client.swap_status(SwapStatusRequest { id: id.clone() })
else {
continue;
};
if status_response.status == SubSwapStates::TransactionClaimed.to_string() {
cloned
.persister
.resolve_ongoing_swap(&id)
.unwrap_or_else(|err| {
warn!("Could not write to database. Err: {err:?}")
});
}
}
}
}
@@ -242,11 +272,12 @@ impl Wallet {
let funding_amount = swap_response.get_funding_amount()?;
let funding_address = swap_response.get_funding_address()?;
self.swap_persister
self.persister
.insert_ongoing_swap(&[OngoingSwap::Send {
id: id.clone(),
amount_sat,
funding_address: funding_address.clone(),
invoice: invoice.to_string(),
}])
.map_err(|_| PaymentError::PersistError)?;
@@ -269,10 +300,6 @@ impl Wallet {
err: err.to_string(),
})?;
self.swap_persister
.resolve_ongoing_swap(&res.id)
.map_err(|_| PaymentError::PersistError)?;
Ok(SendPaymentResponse { txid })
}
@@ -396,18 +423,13 @@ impl Wallet {
return Err(PaymentError::InvalidInvoice);
};
let invoice_amount_sat = invoice
.amount_milli_satoshis()
.ok_or(PaymentError::InvalidInvoice)?
/ 1000;
self.swap_persister
self.persister
.insert_ongoing_swap(dbg!(&[OngoingSwap::Receive {
id: swap_id.clone(),
preimage: preimage_str,
blinding_key: blinding_str,
redeem_script,
invoice_amount_sat,
invoice: invoice.to_string(),
onchain_amount_sat,
}]))
.map_err(|_| PaymentError::PersistError)?;
@@ -438,12 +460,13 @@ impl Wallet {
true => PaymentType::Received,
false => PaymentType::Sent,
},
invoice: None,
}
})
.collect();
if include_pending {
for swap in self.swap_persister.list_ongoing_swaps()? {
for swap in self.persister.list_ongoing_swaps()? {
payments.insert(0, swap.into());
}
}