Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions Cargo-minimal.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand All @@ -1483,7 +1482,6 @@ dependencies = [
"hyper-rustls",
"hyper-tungstenite",
"hyper-util",
"once_cell",
"rustls 0.22.4",
"tokio",
"tokio-tungstenite",
Expand Down Expand Up @@ -1686,6 +1684,7 @@ dependencies = [
"payjoin-directory",
"rcgen",
"reqwest",
"rustls 0.22.4",
"testcontainers",
"testcontainers-modules",
"tokio",
Expand Down
9 changes: 4 additions & 5 deletions Cargo-recent.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand All @@ -1483,7 +1482,6 @@ dependencies = [
"hyper-rustls",
"hyper-tungstenite",
"hyper-util",
"once_cell",
"rustls 0.22.4",
"tokio",
"tokio-tungstenite",
Expand Down Expand Up @@ -1686,6 +1684,7 @@ dependencies = [
"payjoin-directory",
"rcgen",
"reqwest",
"rustls 0.22.4",
"testcontainers",
"testcontainers-modules",
"tokio",
Expand Down
11 changes: 5 additions & 6 deletions payjoin-cli/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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())
Expand All @@ -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")
Expand Down
3 changes: 2 additions & 1 deletion payjoin-test-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
39 changes: 24 additions & 15 deletions payjoin-test-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -39,7 +42,7 @@ pub fn init_tracing() {
}

pub struct TestServices {
cert_key: (Vec<u8>, Vec<u8>),
cert: Certificate,
/// redis is an implicit dependency of the directory service
#[allow(dead_code)]
redis: (u16, Container<'static, Redis>),
Expand All @@ -50,23 +53,33 @@ pub struct TestServices {

impl TestServices {
pub async fn initialize() -> Result<Self, BoxSendSyncError> {
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<Client> = 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<Client> = 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)),
http_agent,
})
}

pub fn cert(&self) -> Vec<u8> { self.cert_key.0.clone() }
pub fn cert(&self) -> Vec<u8> { 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")
Expand Down Expand Up @@ -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<u8>, Vec<u8>) {
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<bitcoind::BitcoinD, BoxError> {
Expand Down
74 changes: 55 additions & 19 deletions payjoin/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
Expand All @@ -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:
Expand Down Expand Up @@ -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)?;
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -483,12 +499,17 @@ mod integration {
let agent_clone: Arc<Client> = agent.clone();
let receiver: Arc<bitcoincore_rpc::Client> = 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
Expand All @@ -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())?;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down