From 5428ab9ed37a65f9599f66e0e6632c8082fb70ff Mon Sep 17 00:00:00 2001 From: oyindamola oladapo Date: Fri, 27 Jun 2025 14:04:46 +0100 Subject: [PATCH 1/2] Rename sender extract methods for clarity - Rename `extract_v1` to `create_v1_post_request` to better reflect behavior - Rename `extract_v2` to `create_v2_post_request` for naming consistency - Rename `extract_req` to `create_poll_request` across receiver typestates - Rename all internal uses of `subdir`/`subdirectory` to `mailbox` - Includes variables, function names, and documentation comments --- payjoin-cli/src/app/v1.rs | 2 +- payjoin-cli/src/app/v2/mod.rs | 17 +++-- payjoin-directory/src/db.rs | 45 +++++------ payjoin-directory/src/lib.rs | 14 ++-- .../test/test_payjoin_integration_test.py | 6 +- payjoin-ffi/src/receive/mod.rs | 23 +++--- payjoin-ffi/src/receive/uni.rs | 32 +++++--- payjoin-ffi/src/send/mod.rs | 14 ++-- payjoin-ffi/src/send/uni.rs | 14 ++-- payjoin-ffi/tests/bdk_integration_test.rs | 21 +++--- payjoin/src/core/receive/v2/mod.rs | 51 +++++++------ payjoin/src/core/receive/v2/session.rs | 2 +- payjoin/src/core/send/multiparty/mod.rs | 8 +- payjoin/src/core/send/v1.rs | 4 +- payjoin/src/core/send/v2/mod.rs | 28 +++---- payjoin/tests/integration.rs | 75 ++++++++++--------- 16 files changed, 185 insertions(+), 171 deletions(-) diff --git a/payjoin-cli/src/app/v1.rs b/payjoin-cli/src/app/v1.rs index 9fd4f7fa5..16f15595b 100644 --- a/payjoin-cli/src/app/v1.rs +++ b/payjoin-cli/src/app/v1.rs @@ -69,7 +69,7 @@ impl AppTrait for App { let (req, ctx) = SenderBuilder::new(psbt, uri.clone()) .build_recommended(fee_rate) .with_context(|| "Failed to build payjoin request")? - .extract_v1(); + .create_v1_post_request(); let http = http_agent()?; let body = String::from_utf8(req.body.clone()).unwrap(); println!("Sending fallback request to {}", &req.url); diff --git a/payjoin-cli/src/app/v2/mod.rs b/payjoin-cli/src/app/v2/mod.rs index c9489e816..9f30a810c 100644 --- a/payjoin-cli/src/app/v2/mod.rs +++ b/payjoin-cli/src/app/v2/mod.rs @@ -182,7 +182,7 @@ impl App { match self.post_original_proposal(context.clone(), persister).await { Ok(()) => (), Err(_) => { - let (req, v1_ctx) = context.extract_v1(); + let (req, v1_ctx) = context.create_v1_post_request(); let response = post_request(req).await?; let psbt = Arc::new( v1_ctx.process_response(response.bytes().await?.to_vec().as_slice())?, @@ -208,8 +208,9 @@ impl App { sender: Sender, persister: &SenderPersister, ) -> Result<()> { - let (req, ctx) = sender - .extract_v2(self.unwrap_relay_or_else_fetch(Some(sender.endpoint().clone())).await?)?; + let (req, ctx) = sender.create_v2_post_request( + self.unwrap_relay_or_else_fetch(Some(sender.endpoint().clone())).await?, + )?; let response = post_request(req).await?; println!("Posted original proposal..."); let sender = sender.process_response(&response.bytes().await?, ctx).save(persister)?; @@ -224,7 +225,7 @@ impl App { let mut session = sender.clone(); // Long poll until we get a response loop { - let (req, ctx) = session.extract_req( + let (req, ctx) = session.create_poll_request( self.unwrap_relay_or_else_fetch(Some(session.endpoint().clone())).await?, )?; let response = post_request(req).await?; @@ -260,11 +261,11 @@ impl App { let mut session = session; loop { - let (req, context) = session.extract_req(&ohttp_relay)?; + let (req, context) = session.create_poll_request(&ohttp_relay)?; println!("Polling receive request..."); let ohttp_response = post_request(req).await?; let state_transition = session - .process_res(ohttp_response.bytes().await?.to_vec().as_slice(), context) + .process_response(ohttp_response.bytes().await?.to_vec().as_slice(), context) .save(persister); match state_transition { Ok(OptionalTransitionOutcome::Progress(next_state)) => { @@ -439,11 +440,11 @@ impl App { persister: &ReceiverPersister, ) -> Result<()> { let (req, ohttp_ctx) = proposal - .extract_req(&self.unwrap_relay_or_else_fetch(None).await?) + .create_post_request(&self.unwrap_relay_or_else_fetch(None).await?) .map_err(|e| anyhow!("v2 req extraction failed {}", e))?; let res = post_request(req).await?; let payjoin_psbt = proposal.psbt().clone(); - proposal.process_res(&res.bytes().await?, ohttp_ctx).save(persister)?; + proposal.process_response(&res.bytes().await?, ohttp_ctx).save(persister)?; println!( "Response successful. Watch mempool for successful Payjoin. TXID: {}", payjoin_psbt.extract_tx_unchecked_fee_rate().compute_txid() diff --git a/payjoin-directory/src/db.rs b/payjoin-directory/src/db.rs index 80c00ae78..b6f2c4bc1 100644 --- a/payjoin-directory/src/db.rs +++ b/payjoin-directory/src/db.rs @@ -54,42 +54,33 @@ impl DbPool { } /// Peek using [`DEFAULT_COLUMN`] as the channel type. - pub async fn push_default(&self, subdirectory_id: &ShortId, data: Vec) -> Result<()> { - self.push(subdirectory_id, DEFAULT_COLUMN, data).await + pub async fn push_default(&self, mailbox_id: &ShortId, data: Vec) -> Result<()> { + self.push(mailbox_id, DEFAULT_COLUMN, data).await } - pub async fn peek_default(&self, subdirectory_id: &ShortId) -> Result> { - self.peek_with_timeout(subdirectory_id, DEFAULT_COLUMN).await + pub async fn peek_default(&self, mailbox_id: &ShortId) -> Result> { + self.peek_with_timeout(mailbox_id, DEFAULT_COLUMN).await } - pub async fn push_v1(&self, subdirectory_id: &ShortId, data: Vec) -> Result<()> { - self.push(subdirectory_id, PJ_V1_COLUMN, data).await + pub async fn push_v1(&self, mailbox_id: &ShortId, data: Vec) -> Result<()> { + self.push(mailbox_id, PJ_V1_COLUMN, data).await } /// Peek using [`PJ_V1_COLUMN`] as the channel type. - pub async fn peek_v1(&self, subdirectory_id: &ShortId) -> Result> { - self.peek_with_timeout(subdirectory_id, PJ_V1_COLUMN).await + pub async fn peek_v1(&self, mailbox_id: &ShortId) -> Result> { + self.peek_with_timeout(mailbox_id, PJ_V1_COLUMN).await } - async fn push( - &self, - subdirectory_id: &ShortId, - channel_type: &str, - data: Vec, - ) -> Result<()> { + async fn push(&self, mailbox_id: &ShortId, channel_type: &str, data: Vec) -> Result<()> { let mut conn = self.client.get_async_connection().await?; - let key = channel_name(subdirectory_id, channel_type); + let key = channel_name(mailbox_id, channel_type); () = conn.set(&key, data.clone()).await?; () = conn.publish(&key, "updated").await?; Ok(()) } - async fn peek_with_timeout( - &self, - subdirectory_id: &ShortId, - channel_type: &str, - ) -> Result> { - match tokio::time::timeout(self.timeout, self.peek(subdirectory_id, channel_type)).await { + async fn peek_with_timeout(&self, mailbox_id: &ShortId, channel_type: &str) -> Result> { + match tokio::time::timeout(self.timeout, self.peek(mailbox_id, channel_type)).await { Ok(redis_result) => match redis_result { Ok(result) => Ok(result), Err(redis_err) => Err(Error::Redis(redis_err)), @@ -98,11 +89,11 @@ impl DbPool { } } - async fn peek(&self, subdirectory_id: &ShortId, channel_type: &str) -> RedisResult> { + async fn peek(&self, mailbox_id: &ShortId, channel_type: &str) -> RedisResult> { let mut conn = self.client.get_async_connection().await?; - let key = channel_name(subdirectory_id, channel_type); + let key = channel_name(mailbox_id, channel_type); - // Attempt to fetch existing content for the given subdirectory_id and channel_type + // Attempt to fetch existing content for the given mailbox_id and channel_type if let Ok(data) = conn.get::<_, Vec>(&key).await { if !data.is_empty() { return Ok(data); @@ -112,7 +103,7 @@ impl DbPool { // Set up a temporary listener for changes let mut pubsub_conn = self.client.get_async_connection().await?.into_pubsub(); - let channel_name = channel_name(subdirectory_id, channel_type); + let channel_name = channel_name(mailbox_id, channel_type); pubsub_conn.subscribe(&channel_name).await?; // Use a block to limit the scope of the mutable borrow @@ -146,6 +137,6 @@ impl DbPool { } } -fn channel_name(subdirectory_id: &ShortId, channel_type: &str) -> Vec { - (subdirectory_id.to_string() + channel_type).into_bytes() +fn channel_name(mailbox_id: &ShortId, channel_type: &str) -> Vec { + (mailbox_id.to_string() + channel_type).into_bytes() } diff --git a/payjoin-directory/src/lib.rs b/payjoin-directory/src/lib.rs index e992ee6c5..bc8df2672 100644 --- a/payjoin-directory/src/lib.rs +++ b/payjoin-directory/src/lib.rs @@ -257,8 +257,8 @@ async fn handle_v2( let path_segments: Vec<&str> = path.split('/').collect(); debug!("handle_v2: {:?}", &path_segments); match (parts.method, path_segments.as_slice()) { - (Method::POST, &["", id]) => post_subdir(id, body, pool).await, - (Method::GET, &["", id]) => get_subdir(id, pool).await, + (Method::POST, &["", id]) => post_mailbox(id, body, pool).await, + (Method::GET, &["", id]) => get_mailbox(id, pool).await, (Method::PUT, &["", id]) => put_payjoin_v1(id, body, pool).await, _ => Ok(not_found()), } @@ -371,7 +371,7 @@ impl From for HandlerError { impl From for HandlerError { fn from(_: ShortIdError) -> Self { - HandlerError::BadRequest(anyhow::anyhow!("subdirectory ID must be 13 bech32 characters")) + HandlerError::BadRequest(anyhow::anyhow!("mailbox ID must be 13 bech32 characters")) } } @@ -443,13 +443,13 @@ async fn put_payjoin_v1( } } -async fn post_subdir( +async fn post_mailbox( id: &str, body: BoxBody, pool: DbPool, ) -> Result>, HandlerError> { let none_response = Response::builder().status(StatusCode::OK).body(empty())?; - trace!("post_subdir"); + trace!("post_mailbox"); let id = ShortId::from_str(id)?; @@ -465,11 +465,11 @@ async fn post_subdir( } } -async fn get_subdir( +async fn get_mailbox( id: &str, pool: DbPool, ) -> Result>, HandlerError> { - trace!("get_subdir"); + trace!("get_mailbox"); let id = ShortId::from_str(id)?; let timeout_response = Response::builder().status(StatusCode::ACCEPTED).body(empty())?; handle_peek(pool.peek_default(&id).await, timeout_response) diff --git a/payjoin-ffi/python/test/test_payjoin_integration_test.py b/payjoin-ffi/python/test/test_payjoin_integration_test.py index 06ddf4edf..4280dfd32 100644 --- a/payjoin-ffi/python/test/test_payjoin_integration_test.py +++ b/payjoin-ffi/python/test/test_payjoin_integration_test.py @@ -154,7 +154,7 @@ async def test_integration_v2_to_v2(self): pj_uri = session.pj_uri() psbt = build_sweep_psbt(self.sender, pj_uri) req_ctx: WithReplyKey = SenderBuilder(psbt, pj_uri).build_recommended(1000).save(sender_persister) - request: RequestV2PostContext = req_ctx.extract_v2(ohttp_relay.as_string()) + request: RequestV2PostContext = req_ctx.create_v2_post_request(ohttp_relay.as_string()) response = await agent.post( url=request.request.url.as_string(), headers={"Content-Type": request.request.content_type}, @@ -172,7 +172,7 @@ async def test_integration_v2_to_v2(self): self.assertEqual(payjoin_proposal.is_PAYJOIN_PROPOSAL(), True) payjoin_proposal = payjoin_proposal.inner - request: RequestResponse = payjoin_proposal.extract_req(ohttp_relay.as_string()) + request: RequestResponse = payjoin_proposal.create_post_request(ohttp_relay.as_string()) response = await agent.post( url=request.request.url.as_string(), headers={"Content-Type": request.request.content_type}, @@ -184,7 +184,7 @@ async def test_integration_v2_to_v2(self): # Inside the Sender: # Sender checks, signs, finalizes, extracts, and broadcasts # Replay post fallback to get the response - request: RequestOhttpContext = send_ctx.extract_req(ohttp_relay.as_string()) + request: RequestOhttpContext = send_ctx.create_poll_request(ohttp_relay.as_string()) response = await agent.post( url=request.request.url.as_string(), headers={"Content-Type": request.request.content_type}, diff --git a/payjoin-ffi/src/receive/mod.rs b/payjoin-ffi/src/receive/mod.rs index ba072fc11..4595802c8 100644 --- a/payjoin-ffi/src/receive/mod.rs +++ b/payjoin-ffi/src/receive/mod.rs @@ -214,21 +214,22 @@ impl } impl Initialized { - pub fn extract_req( + /// Construct an OHTTP encapsulated GET request, polling the mailbox for the Original PSBT + pub fn create_poll_request( &self, ohttp_relay: String, ) -> Result<(Request, ClientResponse), ReceiverError> { self.0 .clone() - .extract_req(ohttp_relay) + .create_poll_request(ohttp_relay) .map(|(req, ctx)| (req.into(), ctx.into())) .map_err(Into::into) } - ///The response can either be an UncheckedProposal or an ACCEPTED message indicating no UncheckedProposal is available yet. - pub fn process_res(&self, body: &[u8], ctx: &ClientResponse) -> InitializedTransition { + /// The response can either be an UncheckedProposal or an ACCEPTED message indicating no UncheckedProposal is available yet. + pub fn process_response(&self, body: &[u8], ctx: &ClientResponse) -> InitializedTransition { InitializedTransition(Arc::new(RwLock::new(Some( - self.0.clone().process_res(body, ctx.into()), + self.0.clone().process_response(body, ctx.into()), )))) } @@ -845,30 +846,30 @@ impl PayjoinProposal { .to_string() } - /// Extract an OHTTP Encapsulated HTTP POST request for the Proposal PSBT - pub fn extract_req( + /// Construct an OHTTP Encapsulated HTTP POST request for the Proposal PSBT + pub fn create_post_request( &self, ohttp_relay: String, ) -> Result<(Request, ClientResponse), ReceiverError> { self.0 .clone() - .extract_req(ohttp_relay) + .create_post_request(ohttp_relay) .map_err(Into::into) .map(|(req, ctx)| (req.into(), ctx.into())) } - ///Processes the response for the final POST message from the receiver client in the v2 Payjoin protocol. + /// Processes the response for the final POST message from the receiver client in the v2 Payjoin protocol. /// /// This function decapsulates the response using the provided OHTTP context. If the response status is successful, it indicates that the Payjoin proposal has been accepted. Otherwise, it returns an error with the status code. /// /// After this function is called, the receiver can either wait for the Payjoin transaction to be broadcast or choose to broadcast the original PSBT. - pub fn process_res( + pub fn process_response( &self, body: &[u8], ohttp_context: &ClientResponse, ) -> PayjoinProposalTransition { PayjoinProposalTransition(Arc::new(RwLock::new(Some( - self.0.clone().process_res(body, ohttp_context.into()), + self.0.clone().process_response(body, ohttp_context.into()), )))) } } diff --git a/payjoin-ffi/src/receive/uni.rs b/payjoin-ffi/src/receive/uni.rs index 05d82cca4..3612a18a8 100644 --- a/payjoin-ffi/src/receive/uni.rs +++ b/payjoin-ffi/src/receive/uni.rs @@ -129,7 +129,7 @@ impl SessionHistory { self.0 .0.fallback_tx().map(|tx| Arc::new(tx.into())) } - /// Extract the error request to be posted on the directory if an error occurred. + /// Construct the error request to be posted on the directory if an error occurred. /// To process the response, use [process_err_res] pub fn extract_err_req( &self, @@ -267,19 +267,26 @@ impl InitializedTransitionOutcome { #[uniffi::export] impl Initialized { - /// The contents of the `&pj=` query parameter including the base64url-encoded public key receiver subdirectory. + /// The contents of the `&pj=` query parameter including the base64url-encoded public key receiver mailbox. /// This identifies a session at the payjoin directory server. pub fn pj_uri(&self) -> crate::PjUri { self.0.pj_uri() } - pub fn extract_req(&self, ohttp_relay: String) -> Result { + pub fn create_poll_request( + &self, + ohttp_relay: String, + ) -> Result { self.0 - .extract_req(ohttp_relay) + .create_poll_request(ohttp_relay) .map(|(request, ctx)| RequestResponse { request, client_response: Arc::new(ctx) }) } ///The response can either be an UncheckedProposal or an ACCEPTED message indicating no UncheckedProposal is available yet. - pub fn process_res(&self, body: &[u8], context: Arc) -> InitializedTransition { - InitializedTransition(self.0.process_res(body, &context)) + pub fn process_response( + &self, + body: &[u8], + context: Arc, + ) -> InitializedTransition { + InitializedTransition(self.0.process_response(body, &context)) } } @@ -707,19 +714,22 @@ impl PayjoinProposal { pub fn psbt(&self) -> String { self.0.psbt() } - /// Extract an OHTTP Encapsulated HTTP POST request for the Proposal PSBT - pub fn extract_req(&self, ohttp_relay: String) -> Result { - let (req, res) = self.0.extract_req(ohttp_relay)?; + /// Construct an OHTTP Encapsulated HTTP POST request for the Proposal PSBT + pub fn create_post_request( + &self, + ohttp_relay: String, + ) -> Result { + let (req, res) = self.0.create_post_request(ohttp_relay)?; Ok(RequestResponse { request: req, client_response: Arc::new(res) }) } - ///Processes the response for the final POST message from the receiver client in the v2 Payjoin protocol. + /// Processes the response for the final POST message from the receiver client in the v2 Payjoin protocol. /// /// This function decapsulates the response using the provided OHTTP context. If the response status is successful, it indicates that the Payjoin proposal has been accepted. Otherwise, it returns an error with the status code. /// /// After this function is called, the receiver can either wait for the Payjoin transaction to be broadcast or choose to broadcast the original PSBT. pub fn process_res(&self, body: &[u8], ctx: Arc) -> PayjoinProposalTransition { - PayjoinProposalTransition(self.0.process_res(body, ctx.as_ref())) + PayjoinProposalTransition(self.0.process_response(body, ctx.as_ref())) } } diff --git a/payjoin-ffi/src/send/mod.rs b/payjoin-ffi/src/send/mod.rs index ab9dabaf3..0883bfbd7 100644 --- a/payjoin-ffi/src/send/mod.rs +++ b/payjoin-ffi/src/send/mod.rs @@ -222,21 +222,21 @@ impl WithReplyKeyTransition { } impl WithReplyKey { - pub fn extract_v1(&self) -> (Request, V1Context) { - let (req, ctx) = self.0.clone().extract_v1(); + pub fn create_v1_post_request(&self) -> (Request, V1Context) { + let (req, ctx) = self.0.clone().create_v1_post_request(); (req.into(), ctx.into()) } - /// Extract serialized Request and Context from a Payjoin Proposal. + /// Construct serialized Request and Context from a Payjoin Proposal. /// /// Important: This request must not be retried or reused on failure. /// Retransmitting the same ciphertext breaks OHTTP privacy properties. /// The specific concern is that the relay can see that a request is being retried. - pub fn extract_v2( + pub fn create_v2_post_request( &self, ohttp_relay: String, ) -> Result<(Request, V2PostContext), CreateRequestError> { - match self.0.extract_v2(ohttp_relay) { + match self.0.create_v2_post_request(ohttp_relay) { Ok((req, ctx)) => Ok((req.into(), ctx.into())), Err(e) => Err(e.into()), } @@ -368,12 +368,12 @@ impl V2GetContextTransition { } impl V2GetContext { - pub fn extract_req( + pub fn create_poll_request( &self, ohttp_relay: String, ) -> Result<(Request, ClientResponse), CreateRequestError> { self.0 - .extract_req(ohttp_relay) + .create_poll_request(ohttp_relay) .map(|(req, ctx)| (req.into(), ctx.into())) .map_err(|e| e.into()) } diff --git a/payjoin-ffi/src/send/uni.rs b/payjoin-ffi/src/send/uni.rs index 0bed9b08d..77a013ec3 100644 --- a/payjoin-ffi/src/send/uni.rs +++ b/payjoin-ffi/src/send/uni.rs @@ -222,12 +222,12 @@ impl WithReplyKeyTransition { #[uniffi::export] impl WithReplyKey { - pub fn extract_v1(&self) -> RequestV1Context { - let (req, ctx) = self.0.extract_v1(); + pub fn create_v1_post_request(&self) -> RequestV1Context { + let (req, ctx) = self.0.create_v1_post_request(); RequestV1Context { request: req, context: Arc::new(ctx.into()) } } - /// Extract serialized Request and Context from a Payjoin Proposal. + /// Construct serialized Request and Context from a Payjoin Proposal. /// /// Important: This request must not be retried or reused on failure. /// Retransmitting the same ciphertext breaks OHTTP privacy properties. @@ -236,11 +236,11 @@ impl WithReplyKey { /// /// This method requires the `rs` pubkey to be extracted from the endpoint /// and has no fallback to v1. - pub fn extract_v2( + pub fn create_v2_post_request( &self, ohttp_relay_url: String, ) -> Result { - match self.0.extract_v2(ohttp_relay_url) { + match self.0.create_v2_post_request(ohttp_relay_url) { Ok((req, ctx)) => Ok(RequestV2PostContext { request: req, context: Arc::new(ctx.into()) }), Err(e) => Err(e), @@ -344,12 +344,12 @@ impl From for V2GetContext { #[uniffi::export] impl V2GetContext { - pub fn extract_req( + pub fn create_poll_request( &self, ohttp_relay: String, ) -> Result { self.0 - .extract_req(ohttp_relay) + .create_poll_request(ohttp_relay) .map(|(request, ctx)| RequestOhttpContext { request, ohttp_ctx: Arc::new(ctx) }) } diff --git a/payjoin-ffi/tests/bdk_integration_test.rs b/payjoin-ffi/tests/bdk_integration_test.rs index f49a09892..a7df8189f 100644 --- a/payjoin-ffi/tests/bdk_integration_test.rs +++ b/payjoin-ffi/tests/bdk_integration_test.rs @@ -265,7 +265,8 @@ mod v2 { .save(&recv_session_persister)?; let ohttp_relay = services.ohttp_relay_url(); // Poll receive request - let (request, client_response) = session.extract_req(ohttp_relay.to_string())?; + let (request, client_response) = + session.create_poll_request(ohttp_relay.to_string())?; let response = agent .post(request.url.as_string()) .header("Content-Type", request.content_type) @@ -274,7 +275,7 @@ mod v2 { .await?; assert!(response.status().is_success()); let response_body = session - .process_res(&response.bytes().await?, &client_response) + .process_response(&response.bytes().await?, &client_response) .save(&recv_session_persister) .unwrap(); // No proposal yet since sender has not responded @@ -291,7 +292,8 @@ mod v2 { let req_ctx = SenderBuilder::new(psbt.to_string(), pj_uri)? .build_recommended(payjoin::bitcoin::FeeRate::BROADCAST_MIN.to_sat_per_kwu()) .save(&sender_session_persister)?; - let (request, context) = req_ctx.extract_v2(ohttp_relay.to_owned().into())?; + let (request, context) = + req_ctx.create_v2_post_request(ohttp_relay.to_owned().into())?; let response = agent .post(request.url.as_string()) .header("Content-Type", request.content_type) @@ -308,7 +310,8 @@ mod v2 { // Inside the Receiver: // GET fallback psbt - let (request, client_response) = session.extract_req(ohttp_relay.to_string())?; + let (request, client_response) = + session.create_poll_request(ohttp_relay.to_string())?; let response = agent .post(request.url.as_string()) .header("Content-Type", request.content_type) @@ -316,13 +319,13 @@ mod v2 { .send() .await?; let proposal = session - .process_res(&response.bytes().await?, &client_response) + .process_response(&response.bytes().await?, &client_response) .save(&recv_session_persister)? .success() .expect("proposal should exist"); let payjoin_proposal = handle_directory_proposal(receiver, proposal); let (request, client_response) = - payjoin_proposal.extract_req(ohttp_relay.to_string())?; + payjoin_proposal.create_post_request(ohttp_relay.to_string())?; let response = agent .post(request.url.as_string()) .header("Content-Type", request.content_type) @@ -330,15 +333,15 @@ mod v2 { .send() .await?; payjoin_proposal - .process_res(&response.bytes().await?, &client_response) + .process_response(&response.bytes().await?, &client_response) .save(&recv_session_persister)?; // ********************** // Inside the Sender: - // Sender checks, signs, finalizes, extracts, and broadcasts + // Sender checks, signs, finalizes, constructs, and broadcasts // Replay post fallback to get the response let (Request { url, body, content_type, .. }, ohttp_ctx) = - send_ctx.extract_req(ohttp_relay.to_string())?; + send_ctx.create_poll_request(ohttp_relay.to_string())?; let response = agent .post(url.as_string()) .header("Content-Type", content_type) diff --git a/payjoin/src/core/receive/v2/mod.rs b/payjoin/src/core/receive/v2/mod.rs index d6fa2c168..27be77c38 100644 --- a/payjoin/src/core/receive/v2/mod.rs +++ b/payjoin/src/core/receive/v2/mod.rs @@ -67,7 +67,7 @@ pub struct SessionContext { #[serde(deserialize_with = "deserialize_address_assume_checked")] address: Address, directory: url::Url, - subdirectory: Option, + mailbox: Option, ohttp_keys: OhttpKeys, expiry: SystemTime, s: HpkeKeyPair, @@ -103,7 +103,7 @@ where Ok(address.assume_checked()) } -fn subdir_path_from_pubkey(pubkey: &HpkePublicKey) -> ShortId { +fn short_id_from_pubkey(pubkey: &HpkePublicKey) -> ShortId { sha256::Hash::hash(&pubkey.to_compressed_bytes()).into() } @@ -214,7 +214,7 @@ impl core::ops::DerefMut for Receiver { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.state } } -/// Extract an OHTTP Encapsulated HTTP POST request to return +/// Construct an OHTTP Encapsulated HTTP POST request to return /// a Receiver Error Response fn extract_err_req( err: &JsonReply, @@ -224,11 +224,11 @@ fn extract_err_req( if SystemTime::now() > session_context.expiry { return Err(InternalSessionError::Expired(session_context.expiry).into()); } - let subdir = subdir(&session_context.directory, &session_context.id()); + let mailbox = mailbox_endpoint(&session_context.directory, &session_context.id()); let (body, ohttp_ctx) = ohttp_encapsulate( &mut session_context.ohttp_keys.0.clone(), "POST", - subdir.as_str(), + mailbox.as_str(), Some(err.to_json().to_string().as_bytes()), ) .map_err(InternalSessionError::OhttpEncapsulation)?; @@ -274,7 +274,7 @@ impl Receiver { let session_context = SessionContext { address, directory, - subdirectory: None, + mailbox: None, ohttp_keys, expiry: SystemTime::now() + expire_after.unwrap_or(TWENTY_FOUR_HOURS_DEFAULT_EXPIRY), s: HpkeKeyPair::gen_keypair(), @@ -295,8 +295,8 @@ pub struct Initialized { impl State for Initialized {} impl Receiver { - /// Extract an OHTTP Encapsulated HTTP GET request for the Original PSBT - pub fn extract_req( + /// construct an OHTTP Encapsulated HTTP GET request for the Original PSBT + pub fn create_poll_request( &mut self, ohttp_relay: impl IntoUrl, ) -> Result<(Request, ohttp::ClientResponse), Error> { @@ -311,7 +311,7 @@ impl Receiver { /// The response can either be an UncheckedProposal or an ACCEPTED message /// indicating no UncheckedProposal is available yet. - pub fn process_res( + pub fn process_response( &mut self, body: &[u8], context: ohttp::ClientResponse, @@ -368,7 +368,7 @@ impl Receiver { ([u8; crate::directory::ENCAPSULATED_MESSAGE_BYTES], ohttp::ClientResponse), OhttpEncapsulationError, > { - let fallback_target = subdir(&self.context.directory, &self.context.id()); + let fallback_target = mailbox_endpoint(&self.context.directory, &self.context.id()); ohttp_encapsulate(&mut self.context.ohttp_keys, "GET", fallback_target.as_str(), None) } @@ -448,6 +448,11 @@ impl Receiver { } } +/// The sender's original PSBT and optional parameters +/// +/// This type is used to process the request. It is returned by +/// [`Receiver::process_response()`]. +/// #[derive(Debug, Clone, PartialEq)] pub struct UncheckedProposal { pub(crate) v1: v1::UncheckedProposal, @@ -887,7 +892,7 @@ impl State for PayjoinProposal {} impl PayjoinProposal { #[cfg(feature = "_multiparty")] - // TODO hack to get multi party working. A better solution would be to allow extract_req to be separate from the rest of the v2 context + // TODO hack to get multi party working. A better solution would be to allow create_poll_request to be separate from the rest of the v2 context pub(crate) fn new(v1: v1::PayjoinProposal, context: SessionContext) -> Self { Self { v1, context } } @@ -907,8 +912,8 @@ impl Receiver { /// The Payjoin Proposal PSBT. pub fn psbt(&self) -> &Psbt { self.v1.psbt() } - /// Extract an OHTTP Encapsulated HTTP POST request for the Proposal PSBT - pub fn extract_req( + /// Construct an OHTTP Encapsulated HTTP POST request for the Proposal PSBT + pub fn create_post_request( &mut self, ohttp_relay: impl IntoUrl, ) -> Result<(Request, ohttp::ClientResponse), Error> { @@ -919,22 +924,22 @@ impl Receiver { if let Some(e) = &self.context.e { // Prepare v2 payload let payjoin_bytes = self.v1.psbt().serialize(); - let sender_subdir = subdir_path_from_pubkey(e); + let sender_mailbox = short_id_from_pubkey(e); target_resource = self .context .directory - .join(&sender_subdir.to_string()) + .join(&sender_mailbox.to_string()) .map_err(|e| ReplyableError::Implementation(e.into()))?; body = encrypt_message_b(payjoin_bytes, &self.context.s, e)?; method = "POST"; } else { // Prepare v2 wrapped and backwards-compatible v1 payload body = self.v1.psbt().to_string().as_bytes().to_vec(); - let receiver_subdir = subdir_path_from_pubkey(self.context.s.public_key()); + let receiver_mailbox = short_id_from_pubkey(self.context.s.public_key()); target_resource = self .context .directory - .join(&receiver_subdir.to_string()) + .join(&receiver_mailbox.to_string()) .map_err(|e| ReplyableError::Implementation(e.into()))?; method = "PUT"; } @@ -957,7 +962,7 @@ impl Receiver { /// /// After this function is called, the receiver can either wait for the Payjoin transaction to be broadcast or /// choose to broadcast the original PSBT. - pub fn process_res( + pub fn process_response( &self, res: &[u8], ohttp_context: ohttp::ClientResponse, @@ -971,9 +976,9 @@ impl Receiver { } } -/// The subdirectory for this Payjoin receiver session. +/// Derive a mailbox endpoint on a directory given a [`ShortId`]. /// It consists of a directory URL and the session ShortID in the path. -fn subdir(directory: &Url, id: &ShortId) -> Url { +fn mailbox_endpoint(directory: &Url, id: &ShortId) -> Url { let mut url = directory.clone(); { let mut path_segments = @@ -990,7 +995,7 @@ pub(crate) fn pj_uri<'a>( ) -> crate::PjUri<'a> { use crate::uri::{PayjoinExtras, UrlExt}; let id = session_context.id(); - let mut pj = subdir(&session_context.directory, &id); + let mut pj = mailbox_endpoint(&session_context.directory, &id).clone(); pj.set_receiver_pubkey(session_context.s.public_key().clone()); pj.set_ohttp(session_context.ohttp_keys.clone()); pj.set_exp(session_context.expiry); @@ -1019,7 +1024,7 @@ pub mod test { .expect("valid address") .assume_checked(), directory: EXAMPLE_URL.clone(), - subdirectory: None, + mailbox: None, ohttp_keys: OhttpKeys( ohttp::KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).expect("valid key config"), ), @@ -1033,7 +1038,7 @@ pub mod test { .expect("valid address") .assume_checked(), directory: EXAMPLE_URL.clone(), - subdirectory: None, + mailbox: None, ohttp_keys: OhttpKeys( ohttp::KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).expect("valid key config"), ), diff --git a/payjoin/src/core/receive/v2/session.rs b/payjoin/src/core/receive/v2/session.rs index ae2beb7e2..3239baecf 100644 --- a/payjoin/src/core/receive/v2/session.rs +++ b/payjoin/src/core/receive/v2/session.rs @@ -110,7 +110,7 @@ impl SessionHistory { }) } - /// Extract the error request to be posted on the directory if an error occurred. + /// Construct the error request to be posted on the directory if an error occurred. /// To process the response, use [crate::receive::v2::process_err_res] pub fn extract_err_req( &self, diff --git a/payjoin/src/core/send/multiparty/mod.rs b/payjoin/src/core/send/multiparty/mod.rs index d4fb59182..6a136b09f 100644 --- a/payjoin/src/core/send/multiparty/mod.rs +++ b/payjoin/src/core/send/multiparty/mod.rs @@ -42,7 +42,7 @@ impl Sender { /// Retransmitting the same ciphertext breaks OHTTP privacy properties. /// The specific concern is that the relay can see that a request is being retried, /// which leaks that it's all the same request. - pub fn extract_v2( + pub fn create_v2_post_request( &self, ohttp_relay: impl IntoUrl, ) -> Result<(Request, PostContext), CreateRequestError> { @@ -127,12 +127,12 @@ pub struct PostContext(v2::V2PostContext); pub struct GetContext(v2::Sender); impl GetContext { - /// Extract the GET request that will give us the psbt to be finalized - pub fn extract_req( + /// Construct the GET request that will give us the psbt to be finalized + pub fn create_poll_request( &self, ohttp_relay: impl IntoUrl, ) -> Result<(Request, ohttp::ClientResponse), crate::send::v2::CreateRequestError> { - self.0.extract_req(ohttp_relay) + self.0.create_poll_request(ohttp_relay) } /// Process the response from the directory. Provide a closure to finalize the inputs diff --git a/payjoin/src/core/send/v1.rs b/payjoin/src/core/send/v1.rs index 73077014e..de0f6f5db 100644 --- a/payjoin/src/core/send/v1.rs +++ b/payjoin/src/core/send/v1.rs @@ -234,8 +234,8 @@ pub struct Sender { } impl Sender { - /// Extract serialized V1 Request and Context from a Payjoin Proposal - pub fn extract_v1(&self) -> (Request, V1Context) { + /// Construct serialized V1 Request and Context from a Payjoin Proposal + pub fn create_v1_post_request(&self) -> (Request, V1Context) { let url = serialize_url( self.endpoint.clone(), self.output_substitution, diff --git a/payjoin/src/core/send/v2/mod.rs b/payjoin/src/core/send/v2/mod.rs index 563ed518f..388f08987 100644 --- a/payjoin/src/core/send/v2/mod.rs +++ b/payjoin/src/core/send/v2/mod.rs @@ -211,10 +211,12 @@ pub struct WithReplyKey { impl State for WithReplyKey {} impl Sender { - /// Extract serialized V1 Request and Context from a Payjoin Proposal. - pub fn extract_v1(&self) -> (Request, v1::V1Context) { self.v1.extract_v1() } + /// Construct serialized V1 Request and Context from a Payjoin Proposal + pub fn create_v1_post_request(&self) -> (Request, v1::V1Context) { + self.v1.create_v1_post_request() + } - /// Extract serialized Request and Context from a Payjoin Proposal. + /// Construct serialized Request and Context from a Payjoin Proposal. /// /// Important: This request must not be retried or reused on failure. /// Retransmitting the same ciphertext breaks OHTTP privacy properties. @@ -223,7 +225,7 @@ impl Sender { /// /// This method requires the `rs` pubkey to be extracted from the endpoint /// and has no fallback to v1. - pub fn extract_v2( + pub fn create_v2_post_request( &self, ohttp_relay: impl IntoUrl, ) -> Result<(Request, V2PostContext), CreateRequestError> { @@ -396,18 +398,18 @@ pub struct V2GetContext { impl State for V2GetContext {} impl Sender { - /// Extract an OHTTP Encapsulated HTTP GET request for the Proposal PSBT - pub fn extract_req( + /// Construct an OHTTP Encapsulated HTTP GET request for the Proposal PSBT + pub fn create_poll_request( &self, ohttp_relay: impl IntoUrl, ) -> Result<(Request, ohttp::ClientResponse), CreateRequestError> { let base_url = self.endpoint.clone(); - // TODO unify with receiver's fn subdir_path_from_pubkey + // TODO unify with receiver's fn short_id_from_pubkey let hash = sha256::Hash::hash(&self.hpke_ctx.reply_pair.public_key().to_compressed_bytes()); - let subdir: ShortId = hash.into(); + let mailbox: ShortId = hash.into(); let url = base_url - .join(&subdir.to_string()) + .join(&mailbox.to_string()) .map_err(|e| InternalCreateRequestError::Url(e.into()))?; let body = encrypt_message_a( Vec::new(), @@ -556,7 +558,7 @@ mod test { fn test_extract_v2_success() -> Result<(), BoxError> { let sender = create_sender_context()?; let ohttp_relay = EXAMPLE_URL.clone(); - let result = sender.extract_v2(ohttp_relay); + let result = sender.create_v2_post_request(ohttp_relay); let (request, context) = result.expect("Result should be ok"); assert!(!request.body.is_empty(), "Request body should not be empty"); assert_eq!( @@ -578,7 +580,7 @@ mod test { ohttp::KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).expect("valid key config"), )); let ohttp_relay = EXAMPLE_URL.clone(); - let result = sender.extract_v2(ohttp_relay); + let result = sender.create_v2_post_request(ohttp_relay); assert!(result.is_err(), "Extract v2 expected receiver pubkey error, but it succeeded"); match result { @@ -596,7 +598,7 @@ mod test { sender.v1.endpoint.set_exp(SystemTime::now() + Duration::from_secs(60)); sender.v1.endpoint.set_receiver_pubkey(HpkeKeyPair::gen_keypair().1); let ohttp_relay = EXAMPLE_URL.clone(); - let result = sender.extract_v2(ohttp_relay); + let result = sender.create_v2_post_request(ohttp_relay); assert!(result.is_err(), "Extract v2 expected missing ohttp error, but it succeeded"); match result { @@ -613,7 +615,7 @@ mod test { let exp_time = std::time::SystemTime::now(); sender.v1.endpoint.set_exp(exp_time); let ohttp_relay = EXAMPLE_URL.clone(); - let result = sender.extract_v2(ohttp_relay); + let result = sender.create_v2_post_request(ohttp_relay); assert!(result.is_err(), "Extract v2 expected expiry error, but it succeeded"); match result { diff --git a/payjoin/tests/integration.rs b/payjoin/tests/integration.rs index 2ba50154b..45604da17 100644 --- a/payjoin/tests/integration.rs +++ b/payjoin/tests/integration.rs @@ -88,7 +88,7 @@ mod integration { debug!("Original psbt: {psbt:#?}"); let (req, ctx) = SenderBuilder::new(psbt, uri) .build_with_additional_fee(Amount::from_sat(10000), None, FeeRate::ZERO, false)? - .extract_v1(); + .create_v1_post_request(); let headers = HeaderMock::new(&req.body, req.content_type); // ********************** @@ -153,7 +153,7 @@ mod integration { debug!("Original psbt: {psbt:#?}"); let (req, _ctx) = SenderBuilder::new(psbt, uri) .build_with_additional_fee(Amount::from_sat(10000), None, FeeRate::ZERO, false)? - .extract_v1(); + .create_v1_post_request(); let headers = HeaderMock::new(&req.body, req.content_type); // ********************** @@ -213,7 +213,7 @@ mod integration { let mut bad_initializer = Receiver::create_session(mock_address, directory, bad_ohttp_keys, None) .save(&noop_persister)?; - let (req, _ctx) = bad_initializer.extract_req(&ohttp_relay)?; + let (req, _ctx) = bad_initializer.create_poll_request(&ohttp_relay)?; agent .post(req.url) .header("Content-Type", req.content_type) @@ -257,7 +257,7 @@ mod integration { Some(Duration::from_secs(0)), ) .save(&recv_noop_persister)?; - match expired_receiver.extract_req(&ohttp_relay) { + match expired_receiver.create_poll_request(&ohttp_relay) { // Internal error types are private, so check against a string Err(err) => assert!(err.to_string().contains("expired")), _ => panic!("Expired receive session should error"), @@ -271,7 +271,7 @@ mod integration { .build_non_incentivizing(FeeRate::BROADCAST_MIN) .save(&send_noop_persister)?; - match expired_req_ctx.extract_v2(ohttp_relay) { + match expired_req_ctx.create_v2_post_request(ohttp_relay) { // Internal error types are private, so check against a string Err(err) => assert!(err.to_string().contains("expired")), _ => panic!("Expired send session should error"), @@ -316,7 +316,7 @@ mod integration { println!("session: {:#?}", &session); // Poll receive request let ohttp_relay = services.ohttp_relay_url(); - let (req, ctx) = session.extract_req(&ohttp_relay)?; + let (req, ctx) = session.create_poll_request(&ohttp_relay)?; let response = agent .post(req.url) .header("Content-Type", req.content_type) @@ -325,7 +325,7 @@ mod integration { .await?; assert!(response.status().is_success(), "error response: {}", response.status()); let response_body = session - .process_res(response.bytes().await?.to_vec().as_slice(), ctx) + .process_response(response.bytes().await?.to_vec().as_slice(), ctx) .save(&persister)?; // No proposal yet since sender has not responded assert!(response_body.is_none()); @@ -343,7 +343,7 @@ mod integration { .build_recommended(FeeRate::BROADCAST_MIN) .save(&sender_persister)?; let (Request { url, body, content_type, .. }, _send_ctx) = - req_ctx.extract_v2(ohttp_relay.to_owned())?; + req_ctx.create_v2_post_request(ohttp_relay.to_owned())?; let response = agent .post(url.clone()) .header("Content-Type", content_type) @@ -358,7 +358,7 @@ mod integration { // Inside the Receiver: // GET fallback psbt - let (req, ctx) = session.extract_req(&ohttp_relay)?; + let (req, ctx) = session.create_poll_request(&ohttp_relay)?; let response = agent .post(req.url) .header("Content-Type", req.content_type) @@ -367,7 +367,7 @@ mod integration { .await?; // POST payjoin let outcome = session - .process_res(response.bytes().await?.to_vec().as_slice(), ctx) + .process_response(response.bytes().await?.to_vec().as_slice(), ctx) .save(&persister)?; let proposal = outcome.success().expect("proposal should exist").clone(); @@ -442,7 +442,7 @@ mod integration { println!("session: {:#?}", &session); // Poll receive request let ohttp_relay = services.ohttp_relay_url(); - let (req, ctx) = session.extract_req(&ohttp_relay)?; + let (req, ctx) = session.create_poll_request(&ohttp_relay)?; let response = agent .post(req.url) .header("Content-Type", req.content_type) @@ -451,7 +451,7 @@ mod integration { .await?; assert!(response.status().is_success(), "error response: {}", response.status()); let response_body = session - .process_res(response.bytes().await?.to_vec().as_slice(), ctx) + .process_response(response.bytes().await?.to_vec().as_slice(), ctx) .save(&recv_persister)?; // No proposal yet since sender has not responded assert!(response_body.is_none()); @@ -469,7 +469,7 @@ mod integration { .build_recommended(FeeRate::BROADCAST_MIN) .save(&send_persister)?; let (Request { url, body, content_type, .. }, send_ctx) = - req_ctx.extract_v2(ohttp_relay.to_owned())?; + req_ctx.create_v2_post_request(ohttp_relay.to_owned())?; let response = agent .post(url.clone()) .header("Content-Type", content_type) @@ -487,7 +487,7 @@ mod integration { // Inside the Receiver: // GET fallback psbt - let (req, ctx) = session.extract_req(&ohttp_relay)?; + let (req, ctx) = session.create_poll_request(&ohttp_relay)?; let response = agent .post(req.url) .header("Content-Type", req.content_type) @@ -496,11 +496,11 @@ mod integration { .await?; // POST payjoin let outcome = session - .process_res(response.bytes().await?.to_vec().as_slice(), ctx) + .process_response(response.bytes().await?.to_vec().as_slice(), ctx) .save(&recv_persister)?; let proposal = outcome.success().expect("proposal should exist").clone(); let mut payjoin_proposal = handle_directory_proposal(&receiver, proposal, None)?; - let (req, ctx) = payjoin_proposal.extract_req(&ohttp_relay)?; + let (req, ctx) = payjoin_proposal.create_post_request(&ohttp_relay)?; let response = agent .post(req.url) .header("Content-Type", req.content_type) @@ -508,15 +508,15 @@ mod integration { .send() .await?; payjoin_proposal - .process_res(&response.bytes().await?, ctx) + .process_response(&response.bytes().await?, ctx) .save(&recv_persister)?; // ********************** // Inside the Sender: - // Sender checks, signs, finalizes, extracts, and broadcasts + // Sender checks, signs, finalizes, constructs, and broadcasts // Replay post fallback to get the response let (Request { url, body, content_type, .. }, ohttp_ctx) = - send_ctx.extract_req(ohttp_relay.to_owned())?; + send_ctx.create_poll_request(ohttp_relay.to_owned())?; let response = agent .post(url.clone()) .header("Content-Type", content_type) @@ -573,7 +573,7 @@ mod integration { let req_ctx = SenderBuilder::new(psbt, pj_uri) .build_recommended(FeeRate::BROADCAST_MIN) .save(&send_persister)?; - let (req, ctx) = req_ctx.extract_v1(); + let (req, ctx) = req_ctx.create_v1_post_request(); let headers = HeaderMock::new(&req.body, req.content_type); // ********************** @@ -584,7 +584,7 @@ mod integration { // ********************** // Inside the Sender: - // Sender checks, signs, finalizes, extracts, and broadcasts + // Sender checks, signs, finalizes, constructs, and broadcasts let checked_payjoin_proposal_psbt = ctx.process_response(response.as_bytes())?; let payjoin_tx = extract_pj_tx(&sender, checked_payjoin_proposal_psbt)?; sender.send_raw_transaction(&payjoin_tx)?; @@ -638,7 +638,8 @@ mod integration { let req_ctx = SenderBuilder::new(psbt, pj_uri) .build_with_additional_fee(Amount::from_sat(10000), None, FeeRate::ZERO, false) .save(&send_persister)?; - let (Request { url, body, content_type, .. }, send_ctx) = req_ctx.extract_v1(); + let (Request { url, body, content_type, .. }, send_ctx) = + req_ctx.create_v1_post_request(); log::info!("send fallback v1 to offline receiver fail"); let res = agent .post(url.clone()) @@ -657,7 +658,7 @@ mod integration { let receiver_loop = tokio::task::spawn(async move { let agent_clone = agent_clone.clone(); let proposal = loop { - let (req, ctx) = session.extract_req(&ohttp_relay)?; + let (req, ctx) = session.create_poll_request(&ohttp_relay)?; let response = agent_clone .post(req.url) .header("Content-Type", req.content_type) @@ -668,7 +669,7 @@ mod integration { if response.status() == 200 { let proposal = session .clone() - .process_res(response.bytes().await?.to_vec().as_slice(), ctx) + .process_response(response.bytes().await?.to_vec().as_slice(), ctx) .save(&recv_persister)?; if let Some(unchecked_proposal) = proposal.success() { break unchecked_proposal.clone(); @@ -687,7 +688,7 @@ mod integration { .map_err(|e| e.to_string())?; // Respond with payjoin psbt within the time window the sender is willing to wait // this response would be returned as http response to the sender - let (req, ctx) = payjoin_proposal.extract_req(&ohttp_relay)?; + let (req, ctx) = payjoin_proposal.create_post_request(&ohttp_relay)?; let response = agent_clone .post(req.url) .header("Content-Type", req.content_type) @@ -695,7 +696,7 @@ mod integration { .send() .await?; payjoin_proposal - .process_res(&response.bytes().await?, ctx) + .process_response(&response.bytes().await?, ctx) .save(&recv_persister) .map_err(|e| e.to_string())?; Ok::<_, BoxSendSyncError>(()) @@ -906,7 +907,7 @@ mod integration { // ********************** // Inside the Senders + Receiver: // G enerate N different addresses and set up the receiver sessions - // Senders will generate a sweep psbt and send PSBT to receiver subdir + // Senders will generate a sweep psbt and send PSBT to receiver mailbox for sender in senders.iter() { let address = receiver.get_new_address(None, None)?.assume_checked(); let receiver_session = Receiver::create_session( @@ -921,7 +922,7 @@ mod integration { let req_ctx = MultiPartySenderBuilder::new(psbt.clone(), pj_uri.clone()) .build_recommended(FeeRate::BROADCAST_MIN)?; let (Request { url, body, content_type, .. }, send_post_ctx) = - req_ctx.extract_v2(ohttp_relay.to_owned())?; + req_ctx.create_v2_post_request(ohttp_relay.to_owned())?; let response = agent .post(url.clone()) .header("Content-Type", content_type) @@ -948,7 +949,7 @@ mod integration { payjoin::receive::multiparty::UncheckedProposalBuilder::new(); for sender_sesssion in inner_sender_test_sessions.iter() { let mut receiver_session = sender_sesssion.receiver_session.clone(); - let (req, reciever_ctx) = receiver_session.extract_req(&ohttp_relay)?; + let (req, reciever_ctx) = receiver_session.create_poll_request(&ohttp_relay)?; let response = agent .post(req.url) .header("Content-Type", req.content_type) @@ -958,7 +959,7 @@ mod integration { assert!(response.status().is_success()); let res = response.bytes().await?.to_vec(); let proposal = receiver_session - .process_res(&res, reciever_ctx) + .process_response(&res, reciever_ctx) .save(&recv_persister)? .success() .expect("proposal should exist") @@ -972,7 +973,7 @@ mod integration { // Send the payjoin proposals to the senders for mut proposal in multi_sender_payjoin_proposal.sender_iter() { - let (req, ctx) = proposal.extract_req(&ohttp_relay)?; + let (req, ctx) = proposal.create_post_request(&ohttp_relay)?; let response = agent .post(req.url) .header("Content-Type", req.content_type) @@ -982,7 +983,7 @@ mod integration { assert!(response.status().is_success()); let res = response.bytes().await?.to_vec(); - proposal.process_res(&res, ctx).save(&recv_persister)?; + proposal.process_response(&res, ctx).save(&recv_persister)?; } // ********************** @@ -990,7 +991,7 @@ mod integration { for (i, sender_sesssion) in inner_sender_test_sessions.iter().enumerate() { let sender_get_ctx = &sender_sesssion.sender_get_ctx; let (Request { url, body, content_type, .. }, ohttp_response_ctx) = - sender_get_ctx.extract_req(ohttp_relay.to_owned())?; + sender_get_ctx.create_poll_request(ohttp_relay.to_owned())?; let response = agent .post(url.clone()) .header("Content-Type", content_type) @@ -1022,7 +1023,7 @@ mod integration { payjoin::receive::multiparty::FinalizedProposal::new(); for sender_sesssion in inner_sender_test_sessions.iter() { let mut receiver_session = sender_sesssion.receiver_session.clone(); - let (req, reciever_ctx) = receiver_session.extract_req(&ohttp_relay)?; + let (req, reciever_ctx) = receiver_session.create_poll_request(&ohttp_relay)?; let response = agent .post(req.url) .header("Content-Type", req.content_type) @@ -1032,7 +1033,7 @@ mod integration { assert!(response.status().is_success()); let finalized_response = receiver_session - .process_res(response.bytes().await?.to_vec().as_slice(), reciever_ctx) + .process_response(response.bytes().await?.to_vec().as_slice(), reciever_ctx) .save(&recv_persister)? .success() .expect("proposal should exist") @@ -1183,7 +1184,7 @@ mod integration { let max_additional_fee = Amount::from_sat(1000); let (req, ctx) = SenderBuilder::new(psbt.clone(), uri) .build_with_additional_fee(max_additional_fee, None, FeeRate::ZERO, false)? - .extract_v1(); + .create_v1_post_request(); let headers = HeaderMock::new(&req.body, req.content_type); // ********************** @@ -1260,7 +1261,7 @@ mod integration { log::debug!("Original psbt: {psbt:#?}"); let (req, ctx) = SenderBuilder::new(psbt.clone(), uri) .build_with_additional_fee(Amount::from_sat(10000), None, FeeRate::ZERO, false)? - .extract_v1(); + .create_v1_post_request(); let headers = HeaderMock::new(&req.body, req.content_type); // ********************** From 5ec16de8bd9ccb22129723d165a2a5804a2c5d16 Mon Sep 17 00:00:00 2001 From: DanGould Date: Mon, 14 Jul 2025 12:42:00 -0400 Subject: [PATCH 2/2] Remove errant whitespace Co-authored-by: oyindamola oladapo --- .../test/test_payjoin_integration_test.py | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/payjoin-ffi/python/test/test_payjoin_integration_test.py b/payjoin-ffi/python/test/test_payjoin_integration_test.py index 4280dfd32..12e302f1f 100644 --- a/payjoin-ffi/python/test/test_payjoin_integration_test.py +++ b/payjoin-ffi/python/test/test_payjoin_integration_test.py @@ -61,7 +61,7 @@ async def process_receiver_proposal(self, receiver: ReceiveSession, recv_persist if res is None: return None return res - + if receiver.is_UNCHECKED_PROPOSAL(): return await self.process_unchecked_proposal(receiver.inner, recv_persister) if receiver.is_MAYBE_INPUTS_OWNED(): @@ -78,56 +78,55 @@ async def process_receiver_proposal(self, receiver: ReceiveSession, recv_persist return await self.process_provisional_proposal(receiver.inner, recv_persister) if receiver.is_PAYJOIN_PROPOSAL(): return receiver - + raise Exception(f"Unknown receiver state: {receiver}") - - + def create_receiver_context(self, receiver_address: bitcoinffi.Address, directory: Url, ohttp_keys: OhttpKeys, recv_persister: InMemoryReceiverSessionEventLog) -> Initialized: receiver = UninitializedReceiver().create_session(address=receiver_address, directory=directory.as_string(), ohttp_keys=ohttp_keys, expire_after=None).save(recv_persister) return receiver - + async def retrieve_receiver_proposal(self, receiver: Initialized, recv_persister: InMemoryReceiverSessionEventLog, ohttp_relay: Url): agent = httpx.AsyncClient() - request: RequestResponse = receiver.extract_req(ohttp_relay.as_string()) + request: RequestResponse = receiver.create_poll_request(ohttp_relay.as_string()) response = await agent.post( url=request.request.url.as_string(), headers={"Content-Type": request.request.content_type}, content=request.request.body ) - res = receiver.process_res(response.content, request.client_response).save(recv_persister) + res = receiver.process_response(response.content, request.client_response).save(recv_persister) if res.is_none(): return None proposal = res.success() return await self.process_unchecked_proposal(proposal, recv_persister) - + async def process_unchecked_proposal(self, proposal: UncheckedProposal, recv_persister: InMemoryReceiverSessionEventLog) : receiver = proposal.check_broadcast_suitability(None, MempoolAcceptanceCallback(self.receiver)).save(recv_persister) return await self.process_maybe_inputs_owned(receiver, recv_persister) - + async def process_maybe_inputs_owned(self, proposal: MaybeInputsOwned, recv_persister: InMemoryReceiverSessionEventLog): maybe_inputs_owned = proposal.check_inputs_not_owned(IsScriptOwnedCallback(self.receiver)).save(recv_persister) return await self.process_maybe_inputs_seen(maybe_inputs_owned, recv_persister) - + async def process_maybe_inputs_seen(self, proposal: MaybeInputsSeen, recv_persister: InMemoryReceiverSessionEventLog): outputs_unknown = proposal.check_no_inputs_seen_before(CheckInputsNotSeenCallback(self.receiver)).save(recv_persister) return await self.process_outputs_unknown(outputs_unknown, recv_persister) - + async def process_outputs_unknown(self, proposal: OutputsUnknown, recv_persister: InMemoryReceiverSessionEventLog): wants_outputs = proposal.identify_receiver_outputs(IsScriptOwnedCallback(self.receiver)).save(recv_persister) return await self.process_wants_outputs(wants_outputs, recv_persister) - + async def process_wants_outputs(self, proposal: WantsOutputs, recv_persister: InMemoryReceiverSessionEventLog): wants_inputs = proposal.commit_outputs().save(recv_persister) return await self.process_wants_inputs(wants_inputs, recv_persister) - + async def process_wants_inputs(self, proposal: WantsInputs, recv_persister: InMemoryReceiverSessionEventLog): provisional_proposal = proposal.contribute_inputs(get_inputs(self.receiver)).commit_inputs().save(recv_persister) return await self.process_provisional_proposal(provisional_proposal, recv_persister) - + async def process_provisional_proposal(self, proposal: ProvisionalProposal, recv_persister: InMemoryReceiverSessionEventLog): payjoin_proposal = proposal.finalize_proposal(ProcessPsbtCallback(self.receiver), 1, 10).save(recv_persister) return ReceiveSession.PAYJOIN_PROPOSAL(payjoin_proposal) - + async def test_integration_v2_to_v2(self): try: receiver_address = bitcoinffi.Address(json.loads(self.receiver.call("getnewaddress", [])), bitcoinffi.Network.REGTEST) @@ -254,7 +253,7 @@ def callback(self, tx): return res except Exception as e: print(f"An error occurred: {e}") - return None + return None class IsScriptOwnedCallback(IsScriptOwned): def __init__(self, connection: RpcClient): @@ -280,7 +279,7 @@ def __init__(self, connection: RpcClient): self.connection = connection def callback(self, psbt: str): - res = json.loads(self.connection.call("walletprocesspsbt", [psbt])) + res = json.loads(self.connection.call("walletprocesspsbt", [psbt])) return res['psbt'] if __name__ == "__main__":