@@ -11,7 +11,10 @@ use clap::ArgMatches;
1111use config:: { Config , File , FileFormat } ;
1212use payjoin:: bitcoin:: psbt:: Psbt ;
1313use payjoin:: bitcoin:: { self , base64} ;
14- use payjoin:: receive:: { Error , PayjoinProposal , ProvisionalProposal , UncheckedProposal } ;
14+ use payjoin:: receive:: {
15+ EnrollContext , Error , PayjoinProposal , ProvisionalProposal , UncheckedProposal ,
16+ } ;
17+ use payjoin:: send:: RequestContext ;
1518#[ cfg( not( feature = "v2" ) ) ]
1619use rouille:: { Request , Response } ;
1720use serde:: { Deserialize , Serialize } ;
@@ -44,64 +47,61 @@ impl App {
4447
4548 #[ cfg( feature = "v2" ) ]
4649 pub fn send_payjoin ( & self , bip21 : & str , fee_rate : & f32 ) -> Result < ( ) > {
47- let ( req , ctx ) = self . create_pj_request ( bip21, fee_rate) ?;
50+ let req_ctx = self . create_pj_request ( bip21, fee_rate) ?;
4851
4952 let client = reqwest:: blocking:: Client :: builder ( )
5053 . danger_accept_invalid_certs ( self . config . danger_accept_invalid_certs )
5154 . build ( )
5255 . with_context ( || "Failed to build reqwest http client" ) ?;
5356
5457 log:: debug!( "Awaiting response" ) ;
55- let res = Self :: long_poll_post ( & client, req) ?;
56- let mut res = std:: io:: Cursor :: new ( & res) ;
57- self . process_pj_response ( ctx, & mut res) ?;
58+ let res = self . long_poll_post ( & client, req_ctx) ?;
59+ self . process_pj_response ( res) ?;
5860 Ok ( ( ) )
5961 }
6062
6163 #[ cfg( feature = "v2" ) ]
6264 fn long_poll_post (
65+ & self ,
6366 client : & reqwest:: blocking:: Client ,
64- req : payjoin:: send:: Request ,
65- ) -> Result < Vec < u8 > , reqwest :: Error > {
67+ req_ctx : payjoin:: send:: RequestContext < ' _ > ,
68+ ) -> Result < Psbt > {
6669 loop {
70+ let ( req, ctx) = req_ctx. extract_v2 ( & self . config . ohttp_proxy ) ?;
6771 let response = client
68- . post ( req. url . as_str ( ) )
72+ . post ( req. url )
6973 . body ( req. body . clone ( ) )
7074 . header ( "Content-Type" , "text/plain" )
71- . header ( "Async" , "true" )
7275 . send ( ) ?;
7376
74- if response. status ( ) == reqwest:: StatusCode :: OK {
75- let body = response. bytes ( ) ?. to_vec ( ) ;
76- return Ok ( body) ;
77- } else if response. status ( ) == reqwest:: StatusCode :: ACCEPTED {
77+ let bytes = response. bytes ( ) ?;
78+ let mut cursor = std:: io:: Cursor :: new ( bytes) ;
79+ let psbt = ctx. process_response ( & mut cursor) ?;
80+ if let Some ( psbt) = psbt {
81+ return Ok ( psbt) ;
82+ } else {
7883 log:: info!( "No response yet for POST payjoin request, retrying some seconds" ) ;
7984 std:: thread:: sleep ( std:: time:: Duration :: from_secs ( 5 ) ) ;
80- } else {
81- log:: error!( "Unexpected response status: {}" , response. status( ) ) ;
82- // TODO handle error
83- panic ! ( "Unexpected response status: {}" , response. status( ) )
8485 }
8586 }
8687 }
8788
8889 #[ cfg( feature = "v2" ) ]
8990 fn long_poll_get (
91+ & self ,
9092 client : & reqwest:: blocking:: Client ,
91- url : & str ,
92- ) -> Result < reqwest :: blocking :: Response , reqwest:: Error > {
93+ enroll_context : & mut EnrollContext ,
94+ ) -> Result < UncheckedProposal , reqwest:: Error > {
9395 loop {
94- let response = client. get ( url) . send ( ) ?;
95-
96- if response. status ( ) == reqwest:: StatusCode :: OK {
97- return Ok ( response) ;
98- } else if response. status ( ) == reqwest:: StatusCode :: ACCEPTED {
99- log:: info!( "No response yet for GET payjoin request, retrying in 5 seconds" ) ;
100- std:: thread:: sleep ( std:: time:: Duration :: from_secs ( 5 ) ) ;
101- } else {
102- log:: error!( "Unexpected response status: {}" , response. status( ) ) ;
103- // TODO handle error
104- panic ! ( "Unexpected response status: {}" , response. status( ) )
96+ let ( payjoin_get_body, context) = enroll_context. payjoin_get_body ( ) ;
97+ let ohttp_response =
98+ client. post ( & self . config . ohttp_proxy ) . body ( payjoin_get_body) . send ( ) ?;
99+ let ohttp_response = ohttp_response. bytes ( ) ?;
100+ let proposal =
101+ enroll_context. parse_relay_response ( ohttp_response. as_ref ( ) , context) . unwrap ( ) ;
102+ match proposal {
103+ Some ( proposal) => return Ok ( proposal) ,
104+ None => std:: thread:: sleep ( std:: time:: Duration :: from_secs ( 5 ) ) ,
105105 }
106106 }
107107 }
@@ -125,11 +125,7 @@ impl App {
125125 Ok ( ( ) )
126126 }
127127
128- fn create_pj_request (
129- & self ,
130- bip21 : & str ,
131- fee_rate : & f32 ,
132- ) -> Result < ( payjoin:: send:: Request , payjoin:: send:: Context ) > {
128+ fn create_pj_request < ' a > ( & self , bip21 : & ' a str , fee_rate : & f32 ) -> Result < RequestContext < ' a > > {
133129 let uri = payjoin:: Uri :: try_from ( bip21)
134130 . map_err ( |e| anyhow ! ( "Failed to create URI from BIP21: {}" , e) ) ?;
135131
@@ -170,22 +166,16 @@ impl App {
170166 . psbt ;
171167 let psbt = Psbt :: from_str ( & psbt) . with_context ( || "Failed to load PSBT from base64" ) ?;
172168 log:: debug!( "Original psbt: {:#?}" , psbt) ;
173-
174- let ( req, ctx) = payjoin:: send:: RequestBuilder :: from_psbt_and_uri ( psbt, uri)
169+ let req_ctx = payjoin:: send:: RequestBuilder :: from_psbt_and_uri ( psbt, uri)
175170 . with_context ( || "Failed to build payjoin request" ) ?
176171 . build_recommended ( fee_rate)
177172 . with_context ( || "Failed to build payjoin request" ) ?;
178173
179- Ok ( ( req , ctx ) )
174+ Ok ( req_ctx )
180175 }
181176
182- fn process_pj_response (
183- & self ,
184- ctx : payjoin:: send:: Context ,
185- response : & mut impl std:: io:: Read ,
186- ) -> Result < bitcoin:: Txid > {
177+ fn process_pj_response ( & self , psbt : Psbt ) -> Result < bitcoin:: Txid > {
187178 // TODO display well-known errors and log::debug the rest
188- let psbt = ctx. process_response ( response) . with_context ( || "Failed to process response" ) ?;
189179 log:: debug!( "Proposed psbt: {:#?}" , psbt) ;
190180 let psbt = self
191181 . bitcoind
@@ -221,7 +211,11 @@ impl App {
221211
222212 #[ cfg( feature = "v2" ) ]
223213 pub fn receive_payjoin ( self , amount_arg : & str ) -> Result < ( ) > {
224- let context = payjoin:: receive:: ProposalContext :: new ( ) ;
214+ let mut context = EnrollContext :: from_relay_config (
215+ & self . config . pj_endpoint ,
216+ & self . config . ohttp_config ,
217+ & self . config . ohttp_proxy ,
218+ ) ;
225219 let pj_uri_string =
226220 self . construct_payjoin_uri ( amount_arg, Some ( & context. subdirectory ( ) ) ) ?;
227221 println ! (
@@ -235,25 +229,25 @@ impl App {
235229 . build ( )
236230 . with_context ( || "Failed to build reqwest http client" ) ?;
237231 log:: debug!( "Awaiting request" ) ;
238- let _enroll = client. post ( & self . config . pj_endpoint ) . body ( pubkey_base64. clone ( ) ) . send ( ) ?;
239-
240- let receive_endpoint = format ! ( "{}/{}" , self . config. pj_endpoint, context. receive_subdir( ) ) ;
241- let res = Self :: long_poll_get ( & client, & receive_endpoint) ?;
232+ let _enroll = client. post ( & self . config . pj_endpoint ) . body ( context. enroll_body ( ) ) . send ( ) ?;
242233
234+ log:: debug!( "Awaiting proposal" ) ;
235+ let res = self . long_poll_get ( & client, & mut context) ?;
243236 log:: debug!( "Received request" ) ;
244- let proposal = context
245- . parse_relay_response ( res)
246- . map_err ( |e| anyhow ! ( "Failed to parse into UncheckedProposal {}" , e) ) ?;
247237 let payjoin_proposal = self
248238 . process_proposal ( proposal)
249239 . map_err ( |e| anyhow ! ( "Failed to process UncheckedProposal {}" , e) ) ?;
250240 let payjoin_endpoint = format ! ( "{}/{}/receive" , self . config. pj_endpoint, pubkey_base64) ;
251- let body = payjoin_proposal. serialize_body ( ) ;
252- let _ = client
253- . post ( payjoin_endpoint)
241+ let ( body, ohttp_ctx) =
242+ payjoin_proposal. extract_v2_req ( & self . config . ohttp_config , & payjoin_endpoint) ;
243+ let res = client
244+ . post ( & self . config . ohttp_proxy )
254245 . body ( body)
255246 . send ( )
256247 . with_context ( || "HTTP request failed" ) ?;
248+ let res = res. bytes ( ) ?;
249+ let res = payjoin_proposal. deserialize_res ( res. to_vec ( ) , ohttp_ctx) ;
250+ log:: debug!( "Received response {:?}" , res) ;
257251 Ok ( ( ) )
258252 }
259253
@@ -289,14 +283,15 @@ impl App {
289283 let pj_receiver_address = self . bitcoind . get_new_address ( None , None ) ?. assume_checked ( ) ;
290284 let amount = Amount :: from_sat ( amount_arg. parse ( ) ?) ;
291285 let pj_uri_string = format ! (
292- "{}?amount={}&pj={}" ,
286+ "{}?amount={}&pj={}&ohttp={} " ,
293287 pj_receiver_address. to_qr_uri( ) ,
294288 amount. to_btc( ) ,
295289 format!(
296290 "{}{}" ,
297291 self . config. pj_endpoint,
298292 pubkey. map_or( String :: from( "" ) , |s| format!( "/{}" , s) )
299- )
293+ ) ,
294+ self . config. ohttp_config,
300295 ) ;
301296
302297 // to check uri validity
@@ -475,6 +470,19 @@ impl App {
475470 }
476471}
477472
473+ fn serialize_request_to_bytes ( req : reqwest:: Request ) -> Vec < u8 > {
474+ let mut serialized_request =
475+ format ! ( "{} {} HTTP/1.1\r \n " , req. method( ) , req. url( ) ) . into_bytes ( ) ;
476+
477+ for ( name, value) in req. headers ( ) . iter ( ) {
478+ let header_line = format ! ( "{}: {}\r \n " , name. as_str( ) , value. to_str( ) . unwrap( ) ) ;
479+ serialized_request. extend ( header_line. as_bytes ( ) ) ;
480+ }
481+
482+ serialized_request. extend ( b"\r \n " ) ;
483+ serialized_request
484+ }
485+
478486struct SeenInputs {
479487 set : OutPointSet ,
480488 file : std:: fs:: File ,
@@ -514,6 +522,8 @@ pub(crate) struct AppConfig {
514522 pub bitcoind_cookie : Option < String > ,
515523 pub bitcoind_rpcuser : String ,
516524 pub bitcoind_rpcpass : String ,
525+ pub ohttp_config : String ,
526+ pub ohttp_proxy : String ,
517527
518528 // send-only
519529 pub danger_accept_invalid_certs : bool ,
@@ -547,6 +557,16 @@ impl AppConfig {
547557 "bitcoind_rpcpass" ,
548558 matches. get_one :: < String > ( "rpcpass" ) . map ( |s| s. as_str ( ) ) ,
549559 ) ?
560+ . set_default ( "ohttp_config" , "" ) ?
561+ . set_override_option (
562+ "ohttp_config" ,
563+ matches. get_one :: < String > ( "ohttp_config" ) . map ( |s| s. as_str ( ) ) ,
564+ ) ?
565+ . set_default ( "ohttp_proxy" , "" ) ?
566+ . set_override_option (
567+ "ohttp_proxy" ,
568+ matches. get_one :: < String > ( "ohttp_proxy" ) . map ( |s| s. as_str ( ) ) ,
569+ ) ?
550570 // Subcommand defaults without which file serialization fails.
551571 . set_default ( "danger_accept_invalid_certs" , false ) ?
552572 . set_default ( "pj_host" , "0.0.0.0:3000" ) ?
0 commit comments