Skip to content

Commit d6fe5ef

Browse files
committed
Use payjoin-service in test utils
This replaces the direct dependencies on ohttp-relay and payjoin-directory with a dependency on payjoin-service. The test services still spin up two instances of the payjoin-service to simulate a relay and directory running on isolated infrastructure.
1 parent 7a0f111 commit d6fe5ef

File tree

7 files changed

+124
-34
lines changed

7 files changed

+124
-34
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ members = [
66
"payjoin-directory",
77
"payjoin-test-utils",
88
"payjoin-ffi",
9+
"payjoin-service",
910
]
1011
resolver = "2"
1112

@@ -14,3 +15,4 @@ ohttp-relay = { path = "ohttp-relay" }
1415
payjoin = { path = "payjoin" }
1516
payjoin-directory = { path = "payjoin-directory" }
1617
payjoin-test-utils = { path = "payjoin-test-utils" }
18+
payjoin-service = { path = "payjoin-service" }

payjoin-ffi/dart/native/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ payjoin-ffi = { git = "https://github.com/payjoin/rust-payjoin.git", branch = "m
2424
payjoin-ffi = { path = "../.." }
2525

2626
[patch.crates-io]
27+
ohttp-relay = { path = "../../../ohttp-relay" }
2728
payjoin = { path = "../../../payjoin" }
2829
payjoin-directory = { path = "../../../payjoin-directory" }
30+
payjoin-service = { path = "../../../payjoin-service" }
2931
payjoin-test-utils = { path = "../../../payjoin-test-utils" }

payjoin-ffi/javascript/test-utils/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,18 @@ path = "../../../payjoin-test-utils"
1818
[build-dependencies]
1919
napi-build = "=2.2.4"
2020

21+
[patch.crates-io.ohttp-relay]
22+
path = "../../../ohttp-relay"
23+
2124
[patch.crates-io.payjoin]
2225
path = "../../../payjoin"
2326

2427
[patch.crates-io.payjoin-directory]
2528
path = "../../../payjoin-directory"
2629

30+
[patch.crates-io.payjoin-service]
31+
path = "../../../payjoin-service"
32+
2733
[patch.crates-io.payjoin-test-utils]
2834
path = "../../../payjoin-test-utils"
2935

payjoin-service/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
[workspace]
2-
31
[package]
42
name = "payjoin-service"
53
version = "0.0.1"
@@ -17,14 +15,17 @@ rust-version = "1.85.0"
1715

1816
[features]
1917
default = []
18+
_manual-tls = ["dep:axum-server", "dep:rustls", "ohttp-relay/_test-util"]
2019

2120
[dependencies]
2221
anyhow = "1.0"
2322
axum = "0.8"
23+
axum-server = { version = "0.7", features = ["tls-rustls-no-provider"], optional = true }
2424
clap = { version = "4.5", features = ["derive", "env"] }
2525
config = "0.15"
2626
ohttp-relay = { path = "../ohttp-relay", features = ["bootstrap"] }
2727
payjoin-directory = { path = "../payjoin-directory" }
28+
rustls = { version = "0.23", default-features = false, features = ["ring"], optional = true }
2829
serde = { version = "1.0", features = ["derive"] }
2930
tokio = { version = "1.47", features = ["full"] }
3031
tower = "0.5"

payjoin-service/src/lib.rs

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,87 @@ struct Services {
1717
}
1818

1919
pub async fn serve(config: Config) -> anyhow::Result<()> {
20+
let services = Services {
21+
directory: init_directory(&config).await?,
22+
relay: init_ohttp_relay(&config, None).await?,
23+
};
24+
let app = Router::new().fallback(route_request).with_state(services);
25+
26+
let addr = SocketAddr::from((Ipv6Addr::UNSPECIFIED, config.port));
27+
let listener = tokio::net::TcpListener::bind(addr).await?;
28+
info!("Payjoin service listening on {}", addr);
29+
axum::serve(listener, app).await?;
30+
31+
Ok(())
32+
}
33+
34+
/// Serves payjoin-service with manual TLS configuration.
35+
///
36+
/// Binds to `config.port` (use 0 to let the OS assign a free port) and returns
37+
/// the actual bound port along with a task handle.
38+
///
39+
/// If `tls_config` is provided, the server will use TLS for incoming connections.
40+
/// The `root_store` is used for outgoing relay connections to the gateway.
41+
#[cfg(feature = "_manual-tls")]
42+
pub async fn serve_manual_tls(
43+
config: Config,
44+
tls_config: Option<axum_server::tls_rustls::RustlsConfig>,
45+
root_store: rustls::RootCertStore,
46+
) -> anyhow::Result<(u16, tokio::task::JoinHandle<anyhow::Result<()>>)> {
47+
let services = Services {
48+
directory: init_directory(&config).await?,
49+
relay: init_ohttp_relay(&config, Some(root_store)).await?,
50+
};
51+
let app = Router::new().fallback(route_request).with_state(services);
52+
53+
let addr = SocketAddr::from((Ipv6Addr::UNSPECIFIED, config.port));
54+
let listener = tokio::net::TcpListener::bind(addr).await?;
55+
let port = listener.local_addr()?.port();
56+
57+
let handle = match tls_config {
58+
Some(tls) => {
59+
info!("Payjoin service listening on port {} with TLS", port);
60+
tokio::spawn(async move {
61+
axum_server::from_tcp_rustls(listener.into_std()?, tls)
62+
.serve(app.into_make_service())
63+
.await
64+
.map_err(Into::into)
65+
})
66+
}
67+
None => {
68+
info!("Payjoin service listening on port {} (HTTP)", port);
69+
tokio::spawn(async move { axum::serve(listener, app).await.map_err(Into::into) })
70+
}
71+
};
72+
73+
Ok((port, handle))
74+
}
75+
76+
async fn init_directory(
77+
config: &Config,
78+
) -> anyhow::Result<payjoin_directory::Service<payjoin_directory::FilesDb>> {
2079
let db = payjoin_directory::FilesDb::init(config.timeout, config.storage_dir.clone()).await?;
2180
db.spawn_background_prune().await;
2281

2382
let ohttp_keys_dir = config.storage_dir.join("ohttp-keys");
2483
let ohttp_config = init_ohttp_config(&ohttp_keys_dir)?;
2584
let metrics = payjoin_directory::metrics::Metrics::new();
2685

27-
let directory = payjoin_directory::Service::new(db, ohttp_config.into(), metrics);
86+
Ok(payjoin_directory::Service::new(db, ohttp_config.into(), metrics))
87+
}
2888

89+
async fn init_ohttp_relay(
90+
config: &Config,
91+
root_store: Option<rustls::RootCertStore>,
92+
) -> anyhow::Result<ohttp_relay::Service> {
2993
// TODO: A gateway should no longer need to be specified, but ohttp-relay currently requires it.
3094
// See https://github.com/payjoin/ohttp-relay/issues/73
3195
let gateway =
3296
config.gateway_origin.parse::<ohttp_relay::GatewayUri>().map_err(|e| anyhow::anyhow!(e))?;
33-
let relay = ohttp_relay::Service::new_with_gateway(gateway).await;
34-
35-
let services = Services { directory, relay };
36-
let app = Router::new().fallback(route_request).with_state(services);
37-
38-
let addr = SocketAddr::from((Ipv6Addr::UNSPECIFIED, config.port));
39-
let listener = tokio::net::TcpListener::bind(addr).await?;
40-
info!("Payjoin service listening on {}", addr);
41-
axum::serve(listener, app).await?;
42-
43-
Ok(())
97+
Ok(match root_store {
98+
Some(roots) => ohttp_relay::Service::new_with_gateway_and_roots(gateway, roots).await,
99+
None => ohttp_relay::Service::new_with_gateway(gateway).await,
100+
})
44101
}
45102

46103
fn init_ohttp_config(

payjoin-test-utils/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,18 @@ rust-version = "1.85"
99
license = "MIT"
1010

1111
[dependencies]
12+
axum-server = { version = "0.7", features = ["tls-rustls-no-provider"] }
1213
bitcoin = { version = "0.32.7", features = ["base64"] }
1314
corepc-node = { version = "0.10.0", features = ["download", "29_0"] }
1415
http = "1.3.1"
1516
ohttp = { package = "bitcoin-ohttp", version = "0.6.0" }
16-
ohttp-relay = { version = "0.0.11", features = ["_test-util"] }
1717
once_cell = "1.21.3"
1818
payjoin = { version = "1.0.0-rc.1", features = [
1919
"io",
2020
"_manual-tls",
2121
"_test-utils",
2222
] }
23-
payjoin-directory = { version = "0.0.3", features = ["_manual-tls"] }
23+
payjoin-service = { path = "../payjoin-service", features = ["_manual-tls"] }
2424
rcgen = "0.14.3"
2525
reqwest = { version = "0.12.23", default-features = false, features = [
2626
"rustls-tls",

payjoin-test-utils/src/lib.rs

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
21
use std::result::Result;
32
use std::str::FromStr;
43
use std::sync::Arc;
54
use std::time::Duration;
65

6+
use axum_server::tls_rustls::RustlsConfig;
77
use bitcoin::{Amount, Psbt};
88
pub use corepc_node; // re-export for convenience
99
use corepc_node::AddressType;
@@ -18,7 +18,6 @@ use reqwest::{Client, ClientBuilder};
1818
use rustls::pki_types::CertificateDer;
1919
use rustls::RootCertStore;
2020
use tempfile::tempdir;
21-
use tokio::net::TcpListener;
2221
use tokio::task::JoinHandle;
2322
use tracing::Level;
2423
use tracing_subscriber::{EnvFilter, FmtSubscriber};
@@ -61,11 +60,9 @@ impl TestServices {
6160
let mut root_store = RootCertStore::empty();
6261
root_store.add(CertificateDer::from(cert.cert.der().to_vec())).unwrap();
6362

64-
let directory = init_directory(cert_key).await?;
63+
let directory = init_directory(cert_key, root_store.clone()).await?;
64+
let ohttp_relay = init_ohttp_relay(directory.0, root_store).await?;
6565

66-
let gateway_origin =
67-
ohttp_relay::GatewayUri::from_str(&format!("https://localhost:{}", directory.0))?;
68-
let ohttp_relay = ohttp_relay::listen_tcp_on_free_port(gateway_origin, root_store).await?;
6966
let http_agent: Arc<Client> = Arc::new(http_agent(cert_der)?);
7067

7168
Ok(Self {
@@ -114,33 +111,58 @@ impl TestServices {
114111

115112
pub async fn init_directory(
116113
local_cert_key: (Vec<u8>, Vec<u8>),
114+
root_store: RootCertStore,
117115
) -> std::result::Result<
118116
(u16, tokio::task::JoinHandle<std::result::Result<(), BoxSendSyncError>>),
119117
BoxSendSyncError,
120118
> {
121-
let timeout = Duration::from_secs(2);
122-
let ohttp_server = payjoin_directory::gen_ohttp_server_config()?;
123-
124-
let metrics = payjoin_directory::metrics::Metrics::new();
125119
let tempdir = tempdir()?;
126-
let db = payjoin_directory::FilesDb::init(timeout, tempdir.path().to_path_buf()).await?;
120+
let config = payjoin_service::config::Config {
121+
port: 0, // let OS assign a free port
122+
storage_dir: tempdir.path().to_path_buf(),
123+
timeout: Duration::from_secs(2),
124+
gateway_origin: String::from("https://localhost"),
125+
};
127126

128-
let service = payjoin_directory::Service::new(db, ohttp_server.into(), metrics);
127+
let tls_config = RustlsConfig::from_der(vec![local_cert_key.0], local_cert_key.1).await?;
129128

130-
let listener = bind_free_port().await?;
131-
let port = listener.local_addr()?.port();
129+
let (port, handle) = payjoin_service::serve_manual_tls(config, Some(tls_config), root_store)
130+
.await
131+
.map_err(|e| e.to_string())?;
132132

133133
let handle = tokio::spawn(async move {
134134
let _tempdir = tempdir; // keep the tempdir until the directory shuts down
135-
service.serve_tls(listener, local_cert_key).await
135+
handle.await.map_err(|e| e.to_string())?.map_err(|e| e.to_string().into())
136136
});
137137

138138
Ok((port, handle))
139139
}
140140

141-
async fn bind_free_port() -> Result<tokio::net::TcpListener, std::io::Error> {
142-
let bind_addr = SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0);
143-
TcpListener::bind(bind_addr).await
141+
async fn init_ohttp_relay(
142+
directory_port: u16,
143+
root_store: RootCertStore,
144+
) -> std::result::Result<
145+
(u16, tokio::task::JoinHandle<std::result::Result<(), BoxSendSyncError>>),
146+
BoxSendSyncError,
147+
> {
148+
let tempdir = tempdir()?;
149+
let config = payjoin_service::config::Config {
150+
port: 0, // let OS assign a free port
151+
storage_dir: tempdir.path().to_path_buf(),
152+
timeout: Duration::from_secs(2),
153+
gateway_origin: format!("https://localhost:{}", directory_port),
154+
};
155+
156+
let (port, handle) = payjoin_service::serve_manual_tls(config, None, root_store)
157+
.await
158+
.map_err(|e| e.to_string())?;
159+
160+
let handle = tokio::spawn(async move {
161+
let _tempdir = tempdir; // keep the tempdir until the relay shuts down
162+
handle.await.map_err(|e| e.to_string())?.map_err(|e| e.to_string().into())
163+
});
164+
165+
Ok((port, handle))
144166
}
145167

146168
/// generate or get a DER encoded localhost cert and key.

0 commit comments

Comments
 (0)