diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index bc7319ca1..ca59ec80f 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -408,9 +408,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bzip2" @@ -1473,8 +1473,7 @@ dependencies = [ [[package]] name = "ohttp-relay" version = "0.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f8e8aef13b8327b680aaaca807aa11ba5979fc5858203e7b77c68128ede61a2" +source = "git+https://github.com/payjoin/ohttp-relay.git?rev=b8153e5c290560be58e1395a8e50ea610e146f6d#b8153e5c290560be58e1395a8e50ea610e146f6d" dependencies = [ "futures", "http", @@ -1483,7 +1482,6 @@ dependencies = [ "hyper-rustls", "hyper-tungstenite", "hyper-util", - "once_cell", "rustls 0.22.4", "tokio", "tokio-tungstenite", @@ -1686,6 +1684,7 @@ dependencies = [ "payjoin-directory", "rcgen", "reqwest", + "rustls 0.22.4", "testcontainers", "testcontainers-modules", "tokio", diff --git a/Cargo-recent.lock b/Cargo-recent.lock index bc7319ca1..ca59ec80f 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -408,9 +408,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bzip2" @@ -1473,8 +1473,7 @@ dependencies = [ [[package]] name = "ohttp-relay" version = "0.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f8e8aef13b8327b680aaaca807aa11ba5979fc5858203e7b77c68128ede61a2" +source = "git+https://github.com/payjoin/ohttp-relay.git?rev=b8153e5c290560be58e1395a8e50ea610e146f6d#b8153e5c290560be58e1395a8e50ea610e146f6d" dependencies = [ "futures", "http", @@ -1483,7 +1482,6 @@ dependencies = [ "hyper-rustls", "hyper-tungstenite", "hyper-util", - "once_cell", "rustls 0.22.4", "tokio", "tokio-tungstenite", @@ -1686,6 +1684,7 @@ dependencies = [ "payjoin-directory", "rcgen", "reqwest", + "rustls 0.22.4", "testcontainers", "testcontainers-modules", "tokio", diff --git a/payjoin-cli/tests/e2e.rs b/payjoin-cli/tests/e2e.rs index 22b308f70..33ed3d771 100644 --- a/payjoin-cli/tests/e2e.rs +++ b/payjoin-cli/tests/e2e.rs @@ -201,8 +201,7 @@ mod e2e { let payjoin_cli = env!("CARGO_BIN_EXE_payjoin-cli"); let directory = &services.directory_url().to_string(); - // Mock ohttp_relay since the ohttp_relay's http client doesn't have the certificate for the directory - let mock_ohttp_relay = &services.ohttp_gateway_url().to_string(); + let ohttp_relay = &services.ohttp_relay_url().to_string(); let cli_receive_initiator = Command::new(payjoin_cli) .arg("--rpchost") @@ -212,7 +211,7 @@ mod e2e { .arg("--db-path") .arg(&receiver_db_path) .arg("--ohttp-relay") - .arg(mock_ohttp_relay) + .arg(ohttp_relay) .arg("receive") .arg(RECEIVE_SATS) .arg("--pj-directory") @@ -232,7 +231,7 @@ mod e2e { .arg("--db-path") .arg(&sender_db_path) .arg("--ohttp-relay") - .arg(mock_ohttp_relay) + .arg(ohttp_relay) .arg("send") .arg(&bip21) .arg("--fee-rate") @@ -251,7 +250,7 @@ mod e2e { .arg("--db-path") .arg(&receiver_db_path) .arg("--ohttp-relay") - .arg(mock_ohttp_relay) + .arg(ohttp_relay) .arg("resume") .stdout(Stdio::piped()) .stderr(Stdio::inherit()) @@ -267,7 +266,7 @@ mod e2e { .arg("--db-path") .arg(&sender_db_path) .arg("--ohttp-relay") - .arg(mock_ohttp_relay) + .arg(ohttp_relay) .arg("send") .arg(&bip21) .arg("--fee-rate") diff --git a/payjoin-test-utils/Cargo.toml b/payjoin-test-utils/Cargo.toml index a70c80bf3..2dc543233 100644 --- a/payjoin-test-utils/Cargo.toml +++ b/payjoin-test-utils/Cargo.toml @@ -13,11 +13,12 @@ bitcoind = { version = "0.36.0", features = ["0_21_2"] } http = "1" log = "0.4.7" ohttp = { package = "bitcoin-ohttp", version = "0.6.0" } -ohttp-relay = { version = "0.0.9", features = ["_test-util"] } +ohttp-relay = { git = "https://github.com/payjoin/ohttp-relay.git", rev = "b8153e5c290560be58e1395a8e50ea610e146f6d", version = "0.0.9", features = ["_test-util"] } once_cell = "1" payjoin = { path = "../payjoin", features = ["io", "_danger-local-https"] } payjoin-directory = { path = "../payjoin-directory", features = ["_danger-local-https"] } rcgen = "0.11" +rustls = "0.22" reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] } testcontainers = "0.15.0" testcontainers-modules = { version = "0.3.7", features = ["redis"] } diff --git a/payjoin-test-utils/src/lib.rs b/payjoin-test-utils/src/lib.rs index 1608213ac..c392ff2b8 100644 --- a/payjoin-test-utils/src/lib.rs +++ b/payjoin-test-utils/src/lib.rs @@ -7,14 +7,17 @@ use std::time::Duration; use bitcoin::{Amount, Psbt}; use bitcoind::bitcoincore_rpc::json::AddressType; use bitcoind::bitcoincore_rpc::{self, RpcApi}; -use http::{StatusCode, Uri}; +use http::StatusCode; use log::{log_enabled, Level}; use ohttp::hpke::{Aead, Kdf, Kem}; use ohttp::{KeyId, SymmetricSuite}; use once_cell::sync::{Lazy, OnceCell}; use payjoin::io::{fetch_ohttp_keys_with_cert, Error as IOError}; use payjoin::OhttpKeys; +use rcgen::Certificate; use reqwest::{Client, ClientBuilder}; +use rustls::pki_types::CertificateDer; +use rustls::RootCertStore; use testcontainers::{clients, Container}; use testcontainers_modules::redis::{Redis, REDIS_PORT}; use tokio::task::JoinHandle; @@ -39,7 +42,7 @@ pub fn init_tracing() { } pub struct TestServices { - cert_key: (Vec, Vec), + cert: Certificate, /// redis is an implicit dependency of the directory service #[allow(dead_code)] redis: (u16, Container<'static, Redis>), @@ -50,15 +53,25 @@ pub struct TestServices { impl TestServices { pub async fn initialize() -> Result { - let cert_key = local_cert_key(); + // TODO add a UUID, and cleanup guard to delete after on successful run + let cert = local_cert_key(); + let cert_der = cert.serialize_der().expect("Failed to serialize cert"); + let key_der = cert.serialize_private_key_der(); + let cert_key = (cert_der.clone(), key_der); + + let mut root_store = RootCertStore::empty(); + root_store.add(CertificateDer::from(cert.serialize_der().unwrap())).unwrap(); + let redis = init_redis(); let db_host = format!("127.0.0.1:{}", redis.0); let directory = init_directory(db_host, cert_key.clone()).await?; - let gateway_origin = Uri::from_str(&format!("https://localhost:{}", directory.0))?; - let ohttp_relay = ohttp_relay::listen_tcp_on_free_port(gateway_origin).await?; - let http_agent: Arc = Arc::new(http_agent(cert_key.0.clone())?); + let gateway_origin = + ohttp_relay::GatewayUri::from_str(&format!("https://localhost:{}", directory.0))?; + let ohttp_relay = ohttp_relay::listen_tcp_on_free_port(gateway_origin, root_store).await?; + let http_agent: Arc = Arc::new(http_agent(cert_der)?); + Ok(Self { - cert_key, + cert, redis, directory: (directory.0, Some(directory.1)), ohttp_relay: (ohttp_relay.0, Some(ohttp_relay.1)), @@ -66,7 +79,7 @@ impl TestServices { }) } - pub fn cert(&self) -> Vec { self.cert_key.0.clone() } + pub fn cert(&self) -> Vec { self.cert.serialize_der().expect("Failed to serialize cert") } pub fn directory_url(&self) -> Url { Url::parse(&format!("https://localhost:{}", self.directory.0)).expect("invalid URL") @@ -122,13 +135,9 @@ pub async fn init_directory( } /// generate or get a DER encoded localhost cert and key. -pub fn local_cert_key() -> (Vec, Vec) { - let cert = - rcgen::generate_simple_self_signed(vec!["0.0.0.0".to_string(), "localhost".to_string()]) - .expect("Failed to generate cert"); - let cert_der = cert.serialize_der().expect("Failed to serialize cert"); - let key_der = cert.serialize_private_key_der(); - (cert_der, key_der) +pub fn local_cert_key() -> rcgen::Certificate { + rcgen::generate_simple_self_signed(vec!["0.0.0.0".to_string(), "localhost".to_string()]) + .expect("Failed to generate cert") } pub fn init_bitcoind() -> Result { diff --git a/payjoin/tests/integration.rs b/payjoin/tests/integration.rs index 0ba602fd6..646907059 100644 --- a/payjoin/tests/integration.rs +++ b/payjoin/tests/integration.rs @@ -200,13 +200,19 @@ mod integration { let agent = services.http_agent(); services.wait_for_services_ready().await?; let directory = services.directory_url(); - let mock_ohttp_relay = services.ohttp_gateway_url(); + let ohttp_relay = services.ohttp_relay_url(); let mock_address = Address::from_str("tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4")? .assume_checked(); let mut bad_initializer = Receiver::new(mock_address, directory, bad_ohttp_keys, None)?; - let (req, _ctx) = bad_initializer.extract_req(&mock_ohttp_relay)?; - agent.post(req.url).body(req.body).send().await.map_err(|e| e.into()) + let (req, _ctx) = bad_initializer.extract_req(&ohttp_relay)?; + agent + .post(req.url) + .header("Content-Type", req.content_type) + .body(req.body) + .send() + .await + .map_err(|e| e.into()) } Ok(()) @@ -228,7 +234,7 @@ mod integration { let (_bitcoind, sender, receiver) = init_bitcoind_sender_receiver(None, None)?; services.wait_for_services_ready().await?; let directory = services.directory_url(); - let ohttp_relay = services.ohttp_gateway_url(); + let ohttp_relay = services.ohttp_relay_url(); let ohttp_keys = services.fetch_ohttp_keys().await?; // ********************** // Inside the Receiver: @@ -290,9 +296,14 @@ mod integration { Receiver::new(address.clone(), directory.clone(), ohttp_keys.clone(), None)?; println!("session: {:#?}", &session); // Poll receive request - let mock_ohttp_relay = services.ohttp_gateway_url(); - let (req, ctx) = session.extract_req(&mock_ohttp_relay)?; - let response = agent.post(req.url).body(req.body).send().await?; + let ohttp_relay = services.ohttp_relay_url(); + let (req, ctx) = session.extract_req(&ohttp_relay)?; + let response = agent + .post(req.url) + .header("Content-Type", req.content_type) + .body(req.body) + .send() + .await?; assert!(response.status().is_success(), "error response: {}", response.status()); let response_body = session.process_res(response.bytes().await?.to_vec().as_slice(), ctx)?; @@ -311,7 +322,7 @@ mod integration { let req_ctx = SenderBuilder::new(psbt.clone(), pj_uri.clone()) .build_recommended(FeeRate::BROADCAST_MIN)?; let (Request { url, body, content_type, .. }, send_ctx) = - req_ctx.extract_v2(mock_ohttp_relay.to_owned())?; + req_ctx.extract_v2(ohttp_relay.to_owned())?; let response = agent .post(url.clone()) .header("Content-Type", content_type) @@ -327,15 +338,20 @@ mod integration { // Inside the Receiver: // GET fallback psbt - let (req, ctx) = session.extract_req(&mock_ohttp_relay)?; - let response = agent.post(req.url).body(req.body).send().await?; + let (req, ctx) = session.extract_req(&ohttp_relay)?; + let response = agent + .post(req.url) + .header("Content-Type", req.content_type) + .body(req.body) + .send() + .await?; // POST payjoin let proposal = session .process_res(response.bytes().await?.to_vec().as_slice(), ctx)? .expect("proposal should exist"); let mut payjoin_proposal = handle_directory_proposal(&receiver, proposal, None)?; assert!(!payjoin_proposal.is_output_substitution_disabled()); - let (req, ctx) = payjoin_proposal.extract_v2_req(&mock_ohttp_relay)?; + let (req, ctx) = payjoin_proposal.extract_v2_req(&ohttp_relay)?; let response = agent .post(req.url) .header("Content-Type", req.content_type) @@ -349,7 +365,7 @@ mod integration { // Sender checks, signs, finalizes, extracts, and broadcasts // Replay post fallback to get the response let (Request { url, body, content_type, .. }, ohttp_ctx) = - send_ctx.extract_req(mock_ohttp_relay.to_owned())?; + send_ctx.extract_req(ohttp_relay.to_owned())?; let response = agent .post(url.clone()) .header("Content-Type", content_type) @@ -483,12 +499,17 @@ mod integration { let agent_clone: Arc = agent.clone(); let receiver: Arc = Arc::new(receiver); let receiver_clone = receiver.clone(); - let mock_ohttp_relay = services.ohttp_gateway_url(); + let ohttp_relay = services.ohttp_relay_url(); let receiver_loop = tokio::task::spawn(async move { let agent_clone = agent_clone.clone(); let proposal = loop { - let (req, ctx) = session.extract_req(&mock_ohttp_relay)?; - let response = agent_clone.post(req.url).body(req.body).send().await?; + let (req, ctx) = session.extract_req(&ohttp_relay)?; + let response = agent_clone + .post(req.url) + .header("Content-Type", req.content_type) + .body(req.body) + .send() + .await?; if response.status() == 200 { if let Some(proposal) = session @@ -511,8 +532,13 @@ mod integration { assert!(payjoin_proposal.is_output_substitution_disabled()); // 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_v2_req(&mock_ohttp_relay)?; - let response = agent_clone.post(req.url).body(req.body).send().await?; + let (req, ctx) = payjoin_proposal.extract_v2_req(&ohttp_relay)?; + let response = agent_clone + .post(req.url) + .header("Content-Type", req.content_type) + .body(req.body) + .send() + .await?; payjoin_proposal .process_res(&response.bytes().await?, ctx) .map_err(|e| e.to_string())?; @@ -747,7 +773,12 @@ mod integration { 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(&directory)?; - let response = agent.post(req.url).body(req.body).send().await?; + let response = agent + .post(req.url) + .header("Content-Type", req.content_type) + .body(req.body) + .send() + .await?; assert!(response.status().is_success()); let res = response.bytes().await?.to_vec(); let proposal = receiver_session @@ -816,7 +847,12 @@ mod integration { 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(&directory)?; - let response = agent.post(req.url).body(req.body).send().await?; + let response = agent + .post(req.url) + .header("Content-Type", req.content_type) + .body(req.body) + .send() + .await?; assert!(response.status().is_success()); let finalized_response = receiver_session