Skip to content

Commit cf94655

Browse files
authored
Persistable payjoin types (payjoin#552)
Introduce persistence as a first class abstraction in payjoin crate payjoin#477 Persistence is implemented for both the sender and reciever. The trait design is meant to be generic over the Value persisted. For example if a application is expecting to save the state at init and finalize and non of the intermediate steps that will be an option. And for the reciever I would advocate in a seperate PR that we persist This takes a step toward completing payjoin#336
2 parents d674bd4 + 9e137e3 commit cf94655

File tree

11 files changed

+361
-74
lines changed

11 files changed

+361
-74
lines changed

payjoin-cli/src/app/v2.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use anyhow::{anyhow, Context, Result};
44
use payjoin::bitcoin::consensus::encode::serialize_hex;
55
use payjoin::bitcoin::psbt::Psbt;
66
use payjoin::bitcoin::{Amount, FeeRate};
7-
use payjoin::receive::v2::{Receiver, UncheckedProposal};
7+
use payjoin::receive::v2::{NewReceiver, Receiver, UncheckedProposal};
88
use payjoin::receive::{Error, ImplementationError, ReplyableError};
99
use payjoin::send::v2::{Sender, SenderBuilder};
1010
use payjoin::Uri;
@@ -14,6 +14,7 @@ use super::config::Config;
1414
use super::wallet::BitcoindWallet;
1515
use super::App as AppTrait;
1616
use crate::app::{handle_interrupt, http_agent};
17+
use crate::db::v2::{ReceiverPersister, SenderPersister};
1718
use crate::db::Database;
1819

1920
#[derive(Clone)]
@@ -52,11 +53,15 @@ impl AppTrait for App {
5253
Some(send_session) => send_session,
5354
None => {
5455
let psbt = self.create_original_psbt(&uri, fee_rate)?;
55-
let mut req_ctx = SenderBuilder::new(psbt, uri.clone())
56+
let mut persister = SenderPersister::new(self.db.clone());
57+
let new_sender = SenderBuilder::new(psbt, uri.clone())
5658
.build_recommended(fee_rate)
5759
.with_context(|| "Failed to build payjoin request")?;
58-
self.db.insert_send_session(&mut req_ctx, url)?;
59-
req_ctx
60+
let storage_token = new_sender
61+
.persist(&mut persister)
62+
.map_err(|e| anyhow!("Failed to persist sender: {}", e))?;
63+
Sender::load(storage_token, &persister)
64+
.map_err(|e| anyhow!("Failed to load sender: {}", e))?
6065
}
6166
};
6267
self.spawn_payjoin_sender(req_ctx).await
@@ -65,13 +70,18 @@ impl AppTrait for App {
6570
async fn receive_payjoin(&self, amount: Amount) -> Result<()> {
6671
let address = self.wallet().get_new_address()?;
6772
let ohttp_keys = unwrap_ohttp_keys_or_else_fetch(&self.config).await?;
68-
let session = Receiver::new(
73+
let mut persister = ReceiverPersister::new(self.db.clone());
74+
let new_receiver = NewReceiver::new(
6975
address,
7076
self.config.v2()?.pj_directory.clone(),
7177
ohttp_keys.clone(),
7278
None,
7379
)?;
74-
self.db.insert_recv_session(session.clone())?;
80+
let storage_token = new_receiver
81+
.persist(&mut persister)
82+
.map_err(|e| anyhow!("Failed to persist receiver: {}", e))?;
83+
let session = Receiver::load(storage_token, &persister)
84+
.map_err(|e| anyhow!("Failed to load receiver: {}", e))?;
7585
self.spawn_payjoin_receiver(session, Some(amount)).await
7686
}
7787

payjoin-cli/src/db/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub(crate) enum Error {
1313
Serialize(serde_json::Error),
1414
#[cfg(feature = "v2")]
1515
Deserialize(serde_json::Error),
16+
#[cfg(feature = "v2")]
17+
NotFound(String),
1618
}
1719

1820
impl fmt::Display for Error {
@@ -23,6 +25,8 @@ impl fmt::Display for Error {
2325
Error::Serialize(e) => write!(f, "Serialization failed: {}", e),
2426
#[cfg(feature = "v2")]
2527
Error::Deserialize(e) => write!(f, "Deserialization failed: {}", e),
28+
#[cfg(feature = "v2")]
29+
Error::NotFound(key) => write!(f, "Key not found: {}", key),
2630
}
2731
}
2832
}

payjoin-cli/src/db/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ impl Database {
2727
}
2828

2929
#[cfg(feature = "v2")]
30-
mod v2;
30+
pub(crate) mod v2;

payjoin-cli/src/db/v2.rs

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,62 @@
1+
use std::sync::Arc;
2+
13
use bitcoincore_rpc::jsonrpc::serde_json;
2-
use payjoin::receive::v2::Receiver;
3-
use payjoin::send::v2::Sender;
4-
use sled::{IVec, Tree};
4+
use payjoin::persist::{Persister, Value};
5+
use payjoin::receive::v2::{Receiver, ReceiverToken};
6+
use payjoin::send::v2::{Sender, SenderToken};
7+
use sled::Tree;
58
use url::Url;
69

710
use super::*;
811

9-
impl Database {
10-
pub(crate) fn insert_recv_session(&self, session: Receiver) -> Result<()> {
11-
let recv_tree = self.0.open_tree("recv_sessions")?;
12-
let key = &session.id();
13-
let value = serde_json::to_string(&session).map_err(Error::Serialize)?;
14-
recv_tree.insert(key.as_slice(), IVec::from(value.as_str()))?;
12+
pub(crate) struct SenderPersister(Arc<Database>);
13+
impl SenderPersister {
14+
pub fn new(db: Arc<Database>) -> Self { Self(db) }
15+
}
16+
17+
impl Persister<Sender> for SenderPersister {
18+
type Token = SenderToken;
19+
type Error = crate::db::error::Error;
20+
fn save(&mut self, value: Sender) -> std::result::Result<SenderToken, Self::Error> {
21+
let send_tree = self.0 .0.open_tree("send_sessions")?;
22+
let key = value.key();
23+
let value = serde_json::to_vec(&value).map_err(Error::Serialize)?;
24+
send_tree.insert(key.clone(), value.as_slice())?;
25+
send_tree.flush()?;
26+
Ok(key)
27+
}
28+
29+
fn load(&self, key: SenderToken) -> std::result::Result<Sender, Self::Error> {
30+
let send_tree = self.0 .0.open_tree("send_sessions")?;
31+
let value = send_tree.get(key.as_ref())?.ok_or(Error::NotFound(key.to_string()))?;
32+
serde_json::from_slice(&value).map_err(Error::Deserialize)
33+
}
34+
}
35+
36+
pub(crate) struct ReceiverPersister(Arc<Database>);
37+
impl ReceiverPersister {
38+
pub fn new(db: Arc<Database>) -> Self { Self(db) }
39+
}
40+
41+
impl Persister<Receiver> for ReceiverPersister {
42+
type Token = ReceiverToken;
43+
type Error = crate::db::error::Error;
44+
fn save(&mut self, value: Receiver) -> std::result::Result<ReceiverToken, Self::Error> {
45+
let recv_tree = self.0 .0.open_tree("recv_sessions")?;
46+
let key = value.key();
47+
let value = serde_json::to_vec(&value).map_err(Error::Serialize)?;
48+
recv_tree.insert(key.clone(), value.as_slice())?;
1549
recv_tree.flush()?;
16-
Ok(())
50+
Ok(key)
1751
}
52+
fn load(&self, key: ReceiverToken) -> std::result::Result<Receiver, Self::Error> {
53+
let recv_tree = self.0 .0.open_tree("recv_sessions")?;
54+
let value = recv_tree.get(key.as_ref())?.ok_or(Error::NotFound(key.to_string()))?;
55+
serde_json::from_slice(&value).map_err(Error::Deserialize)
56+
}
57+
}
1858

59+
impl Database {
1960
pub(crate) fn get_recv_sessions(&self) -> Result<Vec<Receiver>> {
2061
let recv_tree = self.0.open_tree("recv_sessions")?;
2162
let mut sessions = Vec::new();
@@ -34,14 +75,6 @@ impl Database {
3475
Ok(())
3576
}
3677

37-
pub(crate) fn insert_send_session(&self, session: &mut Sender, pj_url: &Url) -> Result<()> {
38-
let send_tree: Tree = self.0.open_tree("send_sessions")?;
39-
let value = serde_json::to_string(session).map_err(Error::Serialize)?;
40-
send_tree.insert(pj_url.to_string(), IVec::from(value.as_str()))?;
41-
send_tree.flush()?;
42-
Ok(())
43-
}
44-
4578
pub(crate) fn get_send_sessions(&self) -> Result<Vec<Sender>> {
4679
let send_tree: Tree = self.0.open_tree("send_sessions")?;
4780
let mut sessions = Vec::new();
@@ -55,7 +88,7 @@ impl Database {
5588

5689
pub(crate) fn get_send_session(&self, pj_url: &Url) -> Result<Option<Sender>> {
5790
let send_tree = self.0.open_tree("send_sessions")?;
58-
if let Some(val) = send_tree.get(pj_url.to_string())? {
91+
if let Some(val) = send_tree.get(pj_url.as_str())? {
5992
let session: Sender = serde_json::from_slice(&val).map_err(Error::Deserialize)?;
6093
Ok(Some(session))
6194
} else {
@@ -65,7 +98,7 @@ impl Database {
6598

6699
pub(crate) fn clear_send_session(&self, pj_url: &Url) -> Result<()> {
67100
let send_tree: Tree = self.0.open_tree("send_sessions")?;
68-
send_tree.remove(pj_url.to_string())?;
101+
send_tree.remove(pj_url.as_str())?;
69102
send_tree.flush()?;
70103
Ok(())
71104
}

payjoin/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ pub mod receive;
2828
#[cfg(feature = "_core")]
2929
pub mod send;
3030

31+
#[cfg(feature = "v2")]
32+
pub mod persist;
33+
3134
#[cfg(feature = "v2")]
3235
pub(crate) mod hpke;
3336
#[cfg(feature = "v2")]

payjoin/src/persist/mod.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use std::fmt::Display;
2+
3+
/// Types that can generate their own keys for persistent storage
4+
pub trait Value: serde::Serialize + serde::de::DeserializeOwned + Sized + Clone {
5+
type Key: AsRef<[u8]> + Clone + Display;
6+
7+
/// Unique identifier for this persisted value
8+
fn key(&self) -> Self::Key;
9+
}
10+
11+
/// Implemented types that should be persisted by the application.
12+
pub trait Persister<V: Value> {
13+
type Token: From<V>;
14+
type Error: std::error::Error + Send + Sync + 'static;
15+
16+
fn save(&mut self, value: V) -> Result<Self::Token, Self::Error>;
17+
fn load(&self, token: Self::Token) -> Result<V, Self::Error>;
18+
}
19+
20+
/// A key type that stores the value itself for no-op persistence
21+
#[derive(Debug, Clone, serde::Serialize)]
22+
pub struct NoopToken<V: Value>(V);
23+
24+
impl<V: Value> AsRef<[u8]> for NoopToken<V> {
25+
fn as_ref(&self) -> &[u8] {
26+
// Since this is a no-op implementation, we can return an empty slice
27+
// as we never actually need to use the bytes
28+
&[]
29+
}
30+
}
31+
32+
impl<'de, V: Value> serde::Deserialize<'de> for NoopToken<V> {
33+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
34+
where
35+
D: serde::Deserializer<'de>,
36+
{
37+
Ok(NoopToken(V::deserialize(deserializer)?))
38+
}
39+
}
40+
41+
impl<V: Value> Value for NoopToken<V> {
42+
type Key = V::Key;
43+
44+
fn key(&self) -> Self::Key { self.0.key() }
45+
}
46+
47+
/// A persister that does nothing but store values in memory
48+
#[derive(Debug, Clone)]
49+
pub struct NoopPersister;
50+
51+
impl<V: Value> From<V> for NoopToken<V> {
52+
fn from(value: V) -> Self { NoopToken(value) }
53+
}
54+
impl<V: Value> Persister<V> for NoopPersister {
55+
type Token = NoopToken<V>;
56+
type Error = std::convert::Infallible;
57+
58+
fn save(&mut self, value: V) -> Result<Self::Token, Self::Error> { Ok(NoopToken(value)) }
59+
60+
fn load(&self, token: Self::Token) -> Result<V, Self::Error> { Ok(token.0) }
61+
}

payjoin/src/receive/v2/mod.rs

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//! Receive BIP 77 Payjoin v2
2+
use std::fmt::{self, Display};
23
use std::str::FromStr;
34
use std::time::{Duration, SystemTime};
45

@@ -19,6 +20,7 @@ use super::{
1920
use crate::hpke::{decrypt_message_a, encrypt_message_b, HpkeKeyPair, HpkePublicKey};
2021
use crate::ohttp::{ohttp_decapsulate, ohttp_encapsulate, OhttpEncapsulationError, OhttpKeys};
2122
use crate::output_substitution::OutputSubstitution;
23+
use crate::persist::{self, Persister};
2224
use crate::receive::{parse_payload, InputPair};
2325
use crate::uri::ShortId;
2426
use crate::{IntoUrl, IntoUrlError, Request};
@@ -71,12 +73,12 @@ fn subdir_path_from_pubkey(pubkey: &HpkePublicKey) -> ShortId {
7173

7274
/// A payjoin V2 receiver, allowing for polled requests to the
7375
/// payjoin directory and response processing.
74-
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75-
pub struct Receiver {
76+
#[derive(Debug)]
77+
pub struct NewReceiver {
7678
context: SessionContext,
7779
}
7880

79-
impl Receiver {
81+
impl NewReceiver {
8082
/// Creates a new `Receiver` with the provided parameters.
8183
///
8284
/// # Parameters
@@ -96,7 +98,7 @@ impl Receiver {
9698
ohttp_keys: OhttpKeys,
9799
expire_after: Option<Duration>,
98100
) -> Result<Self, IntoUrlError> {
99-
Ok(Self {
101+
let receiver = Self {
100102
context: SessionContext {
101103
address,
102104
directory: directory.into_url()?,
@@ -107,9 +109,53 @@ impl Receiver {
107109
s: HpkeKeyPair::gen_keypair(),
108110
e: None,
109111
},
110-
})
112+
};
113+
Ok(receiver)
114+
}
115+
116+
pub fn persist<P: Persister<Receiver>>(
117+
&self,
118+
persister: &mut P,
119+
) -> Result<P::Token, ImplementationError> {
120+
let receiver = Receiver { context: self.context.clone() };
121+
Ok(persister.save(receiver)?)
111122
}
123+
}
124+
125+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
126+
pub struct Receiver {
127+
context: SessionContext,
128+
}
112129

130+
/// Opaque key type for the receiver
131+
#[derive(Debug, Clone, PartialEq, Eq)]
132+
pub struct ReceiverToken(ShortId);
133+
134+
impl Display for ReceiverToken {
135+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) }
136+
}
137+
138+
impl From<Receiver> for ReceiverToken {
139+
fn from(receiver: Receiver) -> Self { ReceiverToken(id(&receiver.context.s)) }
140+
}
141+
142+
impl AsRef<[u8]> for ReceiverToken {
143+
fn as_ref(&self) -> &[u8] { self.0.as_bytes() }
144+
}
145+
146+
impl persist::Value for Receiver {
147+
type Key = ReceiverToken;
148+
149+
fn key(&self) -> Self::Key { ReceiverToken(id(&self.context.s)) }
150+
}
151+
152+
impl Receiver {
153+
pub fn load<P: Persister<Receiver>>(
154+
token: P::Token,
155+
persister: &P,
156+
) -> Result<Self, ImplementationError> {
157+
persister.load(token).map_err(ImplementationError::from)
158+
}
113159
/// Extract an OHTTP Encapsulated HTTP GET request for the Original PSBT
114160
pub fn extract_req(
115161
&mut self,

0 commit comments

Comments
 (0)