From c4ffcc65c3a3f6aef87b16a0a3257cfd65fb0b87 Mon Sep 17 00:00:00 2001 From: DanGould Date: Mon, 3 Jun 2024 13:59:16 -0400 Subject: [PATCH] Replace Send/Receive Stores with a Database table --- payjoin-cli/src/app/v2.rs | 112 +++------------------------ payjoin-cli/src/{db.rs => db/mod.rs} | 3 + payjoin-cli/src/db/v2.rs | 54 +++++++++++++ payjoin/src/send/mod.rs | 6 ++ 4 files changed, 75 insertions(+), 100 deletions(-) rename payjoin-cli/src/{db.rs => db/mod.rs} (95%) create mode 100644 payjoin-cli/src/db/v2.rs diff --git a/payjoin-cli/src/app/v2.rs b/payjoin-cli/src/app/v2.rs index 508e59d2..41ace3c9 100644 --- a/payjoin-cli/src/app/v2.rs +++ b/payjoin-cli/src/app/v2.rs @@ -1,15 +1,11 @@ -use std::fs::OpenOptions; use std::str::FromStr; -use std::sync::Arc; use anyhow::{anyhow, Context, Result}; -use bitcoincore_rpc::jsonrpc::serde_json; use bitcoincore_rpc::RpcApi; use payjoin::bitcoin::consensus::encode::serialize_hex; use payjoin::bitcoin::psbt::Psbt; use payjoin::bitcoin::Amount; use payjoin::{base64, bitcoin, Error, PjUriBuilder}; -use tokio::sync::Mutex as AsyncMutex; use super::config::AppConfig; use super::App as AppTrait; @@ -18,8 +14,6 @@ use crate::db::Database; pub(crate) struct App { config: AppConfig, - receive_store: Arc>, - send_store: Arc>, db: Database, } @@ -27,9 +21,7 @@ pub(crate) struct App { impl AppTrait for App { fn new(config: AppConfig) -> Result { let db = Database::create(&config.db_path)?; - let receive_store = Arc::new(AsyncMutex::new(ReceiveStore::new()?)); - let send_store = Arc::new(AsyncMutex::new(SendStore::new()?)); - let app = Self { config, receive_store, send_store, db }; + let app = Self { config, db }; app.bitcoind()? .get_blockchain_info() .context("Failed to connect to bitcoind. Check config RPC connection.")?; @@ -54,21 +46,19 @@ impl AppTrait for App { } async fn send_payjoin(&self, bip21: &str, fee_rate: &f32, is_retry: bool) -> Result<()> { - let mut session = self.send_store.lock().await; - let req_ctx = if is_retry { + let mut req_ctx = if is_retry { log::debug!("Resuming session"); // Get a reference to RequestContext - session.req_ctx.as_mut().expect("RequestContext is missing") + self.db.get_send_session()?.ok_or(anyhow!("No session found"))? } else { - let req_ctx = self.create_pj_request(bip21, fee_rate)?; - session.write(req_ctx)?; - log::debug!("Writing req_ctx"); - session.req_ctx.as_mut().expect("RequestContext is missing") + let mut req_ctx = self.create_pj_request(bip21, fee_rate)?; + self.db.insert_send_session(&mut req_ctx)?; + req_ctx }; log::debug!("Awaiting response"); - let res = self.long_poll_post(req_ctx).await?; + let res = self.long_poll_post(&mut req_ctx).await?; self.process_pj_response(res)?; - session.clear()?; + self.db.clear_send_session()?; Ok(()) } @@ -97,12 +87,12 @@ impl AppTrait for App { let enrolled = enroller .process_res(ohttp_response.bytes().await?.to_vec().as_slice(), ctx) .map_err(|_| anyhow!("Enrollment failed"))?; - self.receive_store.lock().await.write(enrolled.clone())?; + self.db.insert_recv_session(enrolled.clone())?; enrolled } else { - let session = self.receive_store.lock().await; + let session = self.db.get_recv_session()?; println!("Resuming Payjoin session"); // TODO include session pubkey / payjoin directory - session.session.clone().ok_or(anyhow!("No session found"))? + session.ok_or(anyhow!("No session found"))? }; println!("Receive session established"); @@ -137,7 +127,7 @@ impl AppTrait for App { "Response successful. Watch mempool for successful Payjoin. TXID: {}", payjoin_psbt.extract_tx().clone().txid() ); - self.receive_store.lock().await.clear()?; + self.db.clear_recv_session()?; Ok(()) } } @@ -339,81 +329,3 @@ fn map_reqwest_err(e: reqwest::Error) -> anyhow::Error { None => anyhow!("No HTTP response: {}", e), } } - -struct SendStore { - req_ctx: Option, - file: std::fs::File, -} - -impl SendStore { - fn new() -> Result { - let mut file = - OpenOptions::new().write(true).read(true).create(true).open("send_store.json")?; - let session = match serde_json::from_reader(&mut file) { - Ok(session) => Some(session), - Err(e) => { - log::debug!("error reading send session store: {}", e); - None - } - }; - - Ok(Self { req_ctx: session, file }) - } - - fn write( - &mut self, - session: payjoin::send::RequestContext, - ) -> Result<&mut payjoin::send::RequestContext> { - use std::io::Write; - - let session = self.req_ctx.insert(session); - let serialized = serde_json::to_string(session)?; - self.file.write_all(serialized.as_bytes())?; - Ok(session) - } - - fn clear(&mut self) -> Result<()> { - let file = OpenOptions::new().write(true).open("send_store.json")?; - file.set_len(0)?; - Ok(()) - } -} - -struct ReceiveStore { - session: Option, - file: std::fs::File, -} - -impl ReceiveStore { - fn new() -> Result { - let mut file = - OpenOptions::new().write(true).read(true).create(true).open("receive_store.json")?; - let session = match serde_json::from_reader(&mut file) { - Ok(session) => Some(session), - Err(e) => { - log::debug!("error reading receive session store: {}", e); - None - } - }; - - Ok(Self { session, file }) - } - - fn write( - &mut self, - session: payjoin::receive::v2::Enrolled, - ) -> Result<&mut payjoin::receive::v2::Enrolled> { - use std::io::Write; - - let session = self.session.insert(session); - let serialized = serde_json::to_string(session)?; - self.file.write_all(serialized.as_bytes())?; - Ok(session) - } - - fn clear(&mut self) -> Result<()> { - let file = OpenOptions::new().write(true).open("receive_store.json")?; - file.set_len(0)?; - Ok(()) - } -} diff --git a/payjoin-cli/src/db.rs b/payjoin-cli/src/db/mod.rs similarity index 95% rename from payjoin-cli/src/db.rs rename to payjoin-cli/src/db/mod.rs index 9a596456..ff8bfb76 100644 --- a/payjoin-cli/src/db.rs +++ b/payjoin-cli/src/db/mod.rs @@ -22,3 +22,6 @@ impl Database { Ok(was_seen_before) } } + +#[cfg(feature = "v2")] +mod v2; diff --git a/payjoin-cli/src/db/v2.rs b/payjoin-cli/src/db/v2.rs new file mode 100644 index 00000000..dcc856dd --- /dev/null +++ b/payjoin-cli/src/db/v2.rs @@ -0,0 +1,54 @@ +use bitcoincore_rpc::jsonrpc::serde_json; +use payjoin::receive::v2::Enrolled; +use payjoin::send::RequestContext; +use sled::IVec; + +use super::*; + +impl Database { + pub fn insert_recv_session(&self, session: Enrolled) -> Result<()> { + let key = &session.public_key().serialize(); + let value = serde_json::to_string(&session)?; + self.0.insert(key.as_slice(), IVec::from(value.as_str()))?; + self.0.flush()?; + Ok(()) + } + + pub fn get_recv_session(&self) -> Result> { + if let Some(ivec) = self.0.get("recv_sessions")? { + let session: Enrolled = serde_json::from_slice(&ivec)?; + Ok(Some(session)) + } else { + Ok(None) + } + } + + pub fn clear_recv_session(&self) -> Result<()> { + self.0.remove("recv_sessions")?; + self.0.flush()?; + Ok(()) + } + + pub fn insert_send_session(&self, session: &mut RequestContext) -> Result<()> { + let key = &session.public_key().serialize(); + let value = serde_json::to_string(session)?; + self.0.insert(key.as_slice(), IVec::from(value.as_str()))?; + self.0.flush()?; + Ok(()) + } + + pub fn get_send_session(&self) -> Result> { + if let Some(ivec) = self.0.get("send_sessions")? { + let session: RequestContext = serde_json::from_slice(&ivec)?; + Ok(Some(session)) + } else { + Ok(None) + } + } + + pub fn clear_send_session(&self) -> Result<()> { + self.0.remove("send_sessions")?; + self.0.flush()?; + Ok(()) + } +} diff --git a/payjoin/src/send/mod.rs b/payjoin/src/send/mod.rs index a03fe751..b1503c73 100644 --- a/payjoin/src/send/mod.rs +++ b/payjoin/src/send/mod.rs @@ -390,6 +390,12 @@ impl RequestContext { Ok(bitcoin::secp256k1::PublicKey::from_slice(&pubkey_bytes) .map_err(InternalCreateRequestError::SubdirectoryInvalidPubkey)?) } + + #[cfg(feature = "v2")] + pub fn public_key(&self) -> PublicKey { + let secp = bitcoin::secp256k1::Secp256k1::new(); + self.e.public_key(&secp) + } } #[cfg(feature = "v2")]