From f2d07dbccd258adb857cf0a62fd860f33014292c Mon Sep 17 00:00:00 2001 From: zealsham Date: Wed, 25 Feb 2026 22:43:41 +0100 Subject: [PATCH] Implement Directory and its db as a tower-service This pr addresses a part of #941 consigned with making Db a tower service. It also removes the payjoin-directory crate and moves all directory functionality into the payjoin-mailroom crate as a tower service. --- .github/ISSUE_TEMPLATE/bug-general.yml | 6 +- .github/ISSUE_TEMPLATE/feature-request.yml | 2 +- .github/ISSUE_TEMPLATE/general.yml | 2 +- .github/ISSUE_TEMPLATE/good-first-issue.yml | 2 +- .github/workflows/cron-directory-monitor.yml | 2 +- Cargo-minimal.lock | 35 +-- Cargo-recent.lock | 35 +-- Cargo.toml | 2 - contrib/test.sh | 2 +- contrib/test_local.sh | 2 +- flake.nix | 1 - payjoin-directory/CHANGELOG.md | 21 -- payjoin-directory/Cargo.toml | 49 --- payjoin-directory/Dockerfile | 36 --- payjoin-directory/README.md | 18 -- payjoin-directory/contrib/test.sh | 4 - payjoin-directory/docker-compose.yml | 48 --- payjoin-directory/src/cli.rs | 59 ---- payjoin-directory/src/config.rs | 106 ------- payjoin-directory/src/db/mod.rs | 84 ----- payjoin-directory/src/main.rs | 55 ---- payjoin-ffi/dart/native/Cargo.toml | 1 - payjoin-ffi/javascript/test-utils/Cargo.toml | 3 - .../javascript/wasm-manifest-patch.toml | 2 +- payjoin-mailroom/Cargo.toml | 18 +- .../contrib/health-check.sh | 0 payjoin-mailroom/src/access_control.rs | 2 +- .../src/db/files.rs | 71 ++--- payjoin-mailroom/src/db/mod.rs | 237 ++++++++++++++ .../src/directory.rs | 288 ++++-------------- .../src/key_config.rs | 4 +- payjoin-mailroom/src/lib.rs | 32 +- 32 files changed, 380 insertions(+), 849 deletions(-) delete mode 100644 payjoin-directory/CHANGELOG.md delete mode 100644 payjoin-directory/Cargo.toml delete mode 100644 payjoin-directory/Dockerfile delete mode 100644 payjoin-directory/README.md delete mode 100755 payjoin-directory/contrib/test.sh delete mode 100644 payjoin-directory/docker-compose.yml delete mode 100644 payjoin-directory/src/cli.rs delete mode 100644 payjoin-directory/src/config.rs delete mode 100644 payjoin-directory/src/db/mod.rs delete mode 100644 payjoin-directory/src/main.rs rename {payjoin-directory => payjoin-mailroom}/contrib/health-check.sh (100%) rename {payjoin-directory => payjoin-mailroom}/src/db/files.rs (94%) create mode 100644 payjoin-mailroom/src/db/mod.rs rename payjoin-directory/src/lib.rs => payjoin-mailroom/src/directory.rs (77%) rename {payjoin-directory => payjoin-mailroom}/src/key_config.rs (97%) diff --git a/.github/ISSUE_TEMPLATE/bug-general.yml b/.github/ISSUE_TEMPLATE/bug-general.yml index aae61d291..c6ba4329a 100644 --- a/.github/ISSUE_TEMPLATE/bug-general.yml +++ b/.github/ISSUE_TEMPLATE/bug-general.yml @@ -7,7 +7,7 @@ body: value: | ### This issue tracker is only for technical issues related to the following crates: - [`payjoin-cli`](https://github.com/payjoin/rust-payjoin/tree/master/payjoin-cli) - - [`payjoin-directory`](https://github.com/payjoin/rust-payjoin/tree/master/payjoin-directory) + - [`payjoin-mailroom`](https://github.com/payjoin/rust-payjoin/tree/master/payjoin-mailroom) - [`payjoin-test-utils`](https://github.com/payjoin/rust-payjoin/tree/master/payjoin-test-utils) - [`payjoin-ffi`](https://github.com/payjoin/rust-payjoin/tree/master/payjoin-ffi) @@ -27,7 +27,7 @@ body: multiple: false options: - payjoin-cli - - payjoin-directory + - payjoin-mailroom - payjoin-test-utils - payjoin-ffi - ohttp-relay @@ -56,7 +56,7 @@ body: attributes: label: What version of the selected crate are you using? description: - Run `payjoin-cli --version` or `payjoin-directory --version` for the binaries. + Run `payjoin-cli --version` or `payjoin-mailroom --version` for the binaries. For the library crates (`payjoin-test-utils`, and `payjoin-ffi`), check your respective package manager file to see which version you have installed. placeholder: e.g. payjoin-0.23.0 or master@ceef77b diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index a09ae0a31..87b40db00 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -9,7 +9,7 @@ body: options: - payjoin - payjoin-cli - - payjoin-directory + - payjoin-mailroom - payjoin-test-utils - payjoin-ffi - ohttp-relay diff --git a/.github/ISSUE_TEMPLATE/general.yml b/.github/ISSUE_TEMPLATE/general.yml index 1bf746832..d2761f81e 100644 --- a/.github/ISSUE_TEMPLATE/general.yml +++ b/.github/ISSUE_TEMPLATE/general.yml @@ -8,7 +8,7 @@ body: options: - payjoin - payjoin-cli - - payjoin-directory + - payjoin-mailroom - payjoin-test-utils - payjoin-ffi - ohttp-relay diff --git a/.github/ISSUE_TEMPLATE/good-first-issue.yml b/.github/ISSUE_TEMPLATE/good-first-issue.yml index e5867b42d..e825eeb46 100644 --- a/.github/ISSUE_TEMPLATE/good-first-issue.yml +++ b/.github/ISSUE_TEMPLATE/good-first-issue.yml @@ -9,7 +9,7 @@ body: options: - payjoin - payjoin-cli - - payjoin-directory + - payjoin-mailroom - payjoin-test-utils - payjoin-ffi - ohttp-relay diff --git a/.github/workflows/cron-directory-monitor.yml b/.github/workflows/cron-directory-monitor.yml index 345c932c8..4dff763cc 100644 --- a/.github/workflows/cron-directory-monitor.yml +++ b/.github/workflows/cron-directory-monitor.yml @@ -8,4 +8,4 @@ jobs: steps: - uses: actions/checkout@v4 - name: Check /health endpoint - run: cd payjoin-directory && bash contrib/health-check.sh + run: cd payjoin-mailroom && bash contrib/health-check.sh diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index 7009fcb40..39d041a70 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -2825,34 +2825,6 @@ dependencies = [ "url", ] -[[package]] -name = "payjoin-directory" -version = "0.0.3" -dependencies = [ - "anyhow", - "bhttp", - "bitcoin 0.32.8", - "bitcoin-ohttp", - "clap 4.5.46", - "config", - "futures", - "http-body-util", - "hyper", - "hyper-util", - "ohttp-relay", - "payjoin", - "rand 0.8.5", - "serde", - "tempfile", - "tokio", - "tokio-rustls", - "tokio-rustls-acme", - "tokio-stream", - "tower", - "tracing", - "tracing-subscriber", -] - [[package]] name = "payjoin-ffi" version = "0.24.0" @@ -2892,16 +2864,21 @@ dependencies = [ "anyhow", "axum", "axum-server", + "bhttp", + "bitcoin 0.32.8", + "bitcoin-ohttp", "clap 4.5.46", "config", "flate2", + "futures", + "http-body-util", "ipnet", "maxminddb", "ohttp-relay", "opentelemetry", "opentelemetry-otlp", "opentelemetry_sdk", - "payjoin-directory", + "payjoin", "payjoin-test-utils", "rand 0.8.5", "reqwest", diff --git a/Cargo-recent.lock b/Cargo-recent.lock index 7009fcb40..39d041a70 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -2825,34 +2825,6 @@ dependencies = [ "url", ] -[[package]] -name = "payjoin-directory" -version = "0.0.3" -dependencies = [ - "anyhow", - "bhttp", - "bitcoin 0.32.8", - "bitcoin-ohttp", - "clap 4.5.46", - "config", - "futures", - "http-body-util", - "hyper", - "hyper-util", - "ohttp-relay", - "payjoin", - "rand 0.8.5", - "serde", - "tempfile", - "tokio", - "tokio-rustls", - "tokio-rustls-acme", - "tokio-stream", - "tower", - "tracing", - "tracing-subscriber", -] - [[package]] name = "payjoin-ffi" version = "0.24.0" @@ -2892,16 +2864,21 @@ dependencies = [ "anyhow", "axum", "axum-server", + "bhttp", + "bitcoin 0.32.8", + "bitcoin-ohttp", "clap 4.5.46", "config", "flate2", + "futures", + "http-body-util", "ipnet", "maxminddb", "ohttp-relay", "opentelemetry", "opentelemetry-otlp", "opentelemetry_sdk", - "payjoin-directory", + "payjoin", "payjoin-test-utils", "rand 0.8.5", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index be04f81e8..1dc5f407c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,6 @@ members = [ "ohttp-relay", "payjoin", "payjoin-cli", - "payjoin-directory", "payjoin-test-utils", "payjoin-ffi", "payjoin-mailroom", @@ -15,7 +14,6 @@ resolver = "2" payjoin-fuzz = { path = "fuzz" } ohttp-relay = { path = "ohttp-relay" } payjoin = { path = "payjoin" } -payjoin-directory = { path = "payjoin-directory" } payjoin-mailroom = { path = "payjoin-mailroom" } payjoin-test-utils = { path = "payjoin-test-utils" } diff --git a/contrib/test.sh b/contrib/test.sh index 04fe89a44..6c4f77e9f 100755 --- a/contrib/test.sh +++ b/contrib/test.sh @@ -24,7 +24,7 @@ if [ -f "$LOCKFILE" ]; then fi DEPS="recent minimal" -CRATES="ohttp-relay payjoin payjoin-cli payjoin-directory payjoin-ffi payjoin-mailroom" +CRATES="ohttp-relay payjoin payjoin-cli payjoin-ffi payjoin-mailroom" for dep in $DEPS; do cargo --version diff --git a/contrib/test_local.sh b/contrib/test_local.sh index 910ff75e3..a1950896e 100755 --- a/contrib/test_local.sh +++ b/contrib/test_local.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -e -CRATES="ohttp-relay payjoin payjoin-cli payjoin-directory payjoin-ffi payjoin-mailroom" +CRATES="ohttp-relay payjoin payjoin-cli payjoin-ffi payjoin-mailroom" cargo --version rustc --version diff --git a/flake.nix b/flake.nix index a56acbbd5..33f314179 100644 --- a/flake.nix +++ b/flake.nix @@ -166,7 +166,6 @@ { "payjoin" = "--features v2"; "payjoin-cli" = "--features v1,v2"; - "payjoin-directory" = ""; "ohttp-relay" = ""; "payjoin-mailroom" = "--features access-control,acme,telemetry"; }; diff --git a/payjoin-directory/CHANGELOG.md b/payjoin-directory/CHANGELOG.md deleted file mode 100644 index 42e9c0159..000000000 --- a/payjoin-directory/CHANGELOG.md +++ /dev/null @@ -1,21 +0,0 @@ -# payjoin-directory Changelog - -## 0.0.3 - -- Update links to reference BIP-77 document [#733](https://github.com/payjoin/rust-payjoin/pull/733) -- Fix uninline format clippy violations [#667](https://github.com/payjoin/rust-payjoin/pull/667) -- Serve a page on / for payjoin-directory [#824](https://github.com/payjoin/rust-payjoin/pull/824) - -## 0.0.2 - -- Do not log ERROR on directory validation errors [#628](https://github.com/payjoin/rust-payjoin/pull/628) -- Use payjoin 0.23.0 (056a39b8a8849451ee605dc7ae786f9cda31ace5) -- Announce allowed purposes (6282ffb2c76a93e1849ecc1a84c9f54ccf152cc5) -- Serve `/.well-known/ohttp-gateway` as per RFC 9540 (6282ffb2c76a93e1849ecc1a84c9f54ccf152cc5) -- Rely on `payjoin/directory` feature module [#502](https://github.com/payjoin/rust-payjoin/pull/502) -- Introduce db-module-specific `Result` [#488](https://github.com/payjoin/rust-payjoin/pull/488) -- Return bound port on listen for test stability (d4fa3d440abd102fcbb061b721480dee14ff91dc) - -## 0.0.1 - -- Release initial payjoin-directory to store and forward payjoin payloads using secp256k1 hpke diff --git a/payjoin-directory/Cargo.toml b/payjoin-directory/Cargo.toml deleted file mode 100644 index 5b70dda3c..000000000 --- a/payjoin-directory/Cargo.toml +++ /dev/null @@ -1,49 +0,0 @@ -[package] -name = "payjoin-directory" -version = "0.0.3" -authors = ["Dan Gould "] -description = "A store-and-forward and Oblivious Gateway Resource directory server for Async Payjoin" -repository = "https://github.com/payjoin/rust-payjoin" -readme = "README.md" -keywords = ["bip78", "bip77", "payjoin", "bitcoin", "ohttp"] -categories = ["cryptography::cryptocurrencies", "network-programming"] -license = "MITNFA" -edition = "2021" -rust-version = "1.85" -resolver = "2" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[features] -_manual-tls = ["tokio-rustls"] -acme = ["tokio-rustls-acme"] - -[dependencies] -anyhow = "1.0.99" -bhttp = { version = "0.6.1", features = ["http"] } -bitcoin = { version = "0.32.7", features = ["base64", "rand-std"] } -clap = { version = "4.5.45", features = ["derive", "env"] } -config = "0.15.14" -futures = "0.3.31" -http-body-util = "0.1.3" -hyper = { version = "1.6.0", features = ["http1", "server"] } -hyper-util = { version = "0.1.16", features = ["tokio", "service"] } -ohttp = { package = "bitcoin-ohttp", version = "0.6.0" } -ohttp-relay = { path = "../ohttp-relay" } -payjoin = { version = "1.0.0-rc.2", features = [ - "directory", -], default-features = false } -rand = "0.8" -serde = { version = "1.0.219", features = ["derive"] } -tokio = { version = "1.47.1", features = ["full"] } -tokio-rustls = { version = "0.26.2", features = [ - "ring", -], default-features = false, optional = true } -tokio-rustls-acme = { version = "0.9.0", optional = true } -tokio-stream = { version = "0.1.17", features = ["net"] } -tower = "0.5" -tracing = "0.1.41" -tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } - -[dev-dependencies] -tempfile = "3.20.0" diff --git a/payjoin-directory/Dockerfile b/payjoin-directory/Dockerfile deleted file mode 100644 index c5e8004be..000000000 --- a/payjoin-directory/Dockerfile +++ /dev/null @@ -1,36 +0,0 @@ -# Use the official Rust image as the builder -FROM --platform=linux/amd64 rust:1.81-slim as builder - -WORKDIR /usr/src/payjoin-directory - -# Install cross-compilation dependencies -RUN apt-get update && \ - apt-get install -y \ - build-essential \ - musl-tools \ - musl-dev \ - pkg-config \ - gcc-multilib \ - && rm -rf /var/lib/apt/lists/* - -# Set the linker -ENV CC_x86_64_unknown_linux_musl=musl-gcc -ENV AR_x86_64_unknown_linux_musl=ar - -# Add the x86_64-unknown-linux-musl target -RUN rustup target add x86_64-unknown-linux-musl - -# Copy the workspace manifest and source code -COPY . . - -# Build the binary -RUN cargo build --bin payjoin-directory --release --target x86_64-unknown-linux-musl - -# Create final minimal image -FROM --platform=linux/amd64 alpine:latest - -# Copy the binary from builder -COPY --from=builder /usr/src/payjoin-directory/target/x86_64-unknown-linux-musl/release/payjoin-directory ./ - -# Run the binary -ENTRYPOINT ["./payjoin-directory"] \ No newline at end of file diff --git a/payjoin-directory/README.md b/payjoin-directory/README.md deleted file mode 100644 index d040cf653..000000000 --- a/payjoin-directory/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Payjoin Directory - -[BIP 77](https://github.com/bitcoin/bips/blob/master/bip-0077.md) Async Payjoin (v2) -peers store and forward HTTP client messages via a directory server in order to -make asynchronous Payjoin transactions. This is a reference implementation of -such a server - -V2 clients encapsulate requests using -[Oblivious HTTP](https://www.ietf.org/rfc/rfc9458.html) (OHTTP) which allows -them to make payjoins without the directory being able to link payjoins to -specific client IP. Payjoin Directory is therefore an [Oblivious Gateway -Resource](https://www.ietf.org/rfc/rfc9458.html#dfn-gateway). - -Payjoin Directory also behaves as an [unsecured public-facing HTTP -server](https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki#unsecured-payjoin-server) -in order to provide backwards-compatible support for [BIP -78](https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki) Payjoin (v1) -clients. diff --git a/payjoin-directory/contrib/test.sh b/payjoin-directory/contrib/test.sh deleted file mode 100755 index d1dec41be..000000000 --- a/payjoin-directory/contrib/test.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -set -e - -cargo test --locked --package payjoin-directory --verbose --all-features --lib diff --git a/payjoin-directory/docker-compose.yml b/payjoin-directory/docker-compose.yml deleted file mode 100644 index e62704261..000000000 --- a/payjoin-directory/docker-compose.yml +++ /dev/null @@ -1,48 +0,0 @@ -services: - nginx: - image: nginx:latest - ports: - - "80:80" - - "443:443" - volumes: - - ./nginx/logs:/var/log/nginx - - ./nginx/conf.d:/etc/nginx/conf.d - - ./nginx/certs:/etc/ssl/certs - - ./nginx/html:/var/www/html - networks: - - payjoin-network - - certbot: - image: certbot/certbot - volumes: - - ./nginx/certs:/etc/letsencrypt - - ./nginx/html:/var/www/html - entrypoint: /bin/sh -c 'trap exit TERM; while :; do certbot renew --webroot -w /var/www/html --deploy-hook "nginx -s reload"; sleep 12h & wait $${!}; done;' - depends_on: - - nginx - networks: - - payjoin-network - - payjoin-directory: - image: dangould/payjoin-directory:0.0.1 - environment: - RUST_LOG: "trace" - PJ_DB_HOST: "redis:6379" - PJ_DIR_PORT: "8080" - depends_on: - - redis - networks: - - payjoin-network - - redis: - image: redis:latest - volumes: - - redis-data:/data - networks: - - payjoin-network - -networks: - payjoin-network: - -volumes: - redis-data: diff --git a/payjoin-directory/src/cli.rs b/payjoin-directory/src/cli.rs deleted file mode 100644 index 114baada0..000000000 --- a/payjoin-directory/src/cli.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::env; -use std::path::PathBuf; - -use clap::Parser; - -#[derive(Debug, Parser)] -#[command( - version = env!("CARGO_PKG_VERSION"), - about = "Payjoin Directory Server", - long_about = None, -)] -pub struct Cli { - #[arg(long, short = 'p', env = "PJ_DIR_PORT", help = "The port to bind [default: 8080]")] - pub port: Option, // TODO tokio_listener::ListenerAddressLFlag - - #[cfg(feature = "acme")] - #[clap(flatten)] - pub acme: AcmeCli, - - #[arg( - long, - env = "PJ_DIR_TIMEOUT_SECS", - help = "The timeout for long polling operations [default: 30]" - )] - pub timeout: Option, - - #[arg( - long = "storage-dir", - env = "PJ_STORAGE_DIR", - help = "A directory for writing mailbox data." - )] - pub storage_dir: Option, - - #[arg( - long = "ohttp-keys", - env = "PJ_OHTTP_KEY_DIR", - help = "The ohttp key config file path [default: ohttp_keys]" - )] - pub ohttp_keys: Option, -} - -#[cfg(feature = "acme")] -#[derive(Debug, Parser)] -pub struct AcmeCli { - #[arg(long = "acme-domain", help = "The domain for which to request a certificate using ACME")] - pub domain: Option, - - #[arg( - long = "acme-contact", - help = "Contact information for ACME usage (e.g. 'mailto:admin@example.com')" - )] - pub contact: Option, - - #[arg(long, help = "Whether to use the staging environment [default: production]")] - pub lets_encrypt_staging: Option, - - #[arg(long = "acme-cache-dir", help = "What directory to use for the ACME cache")] - pub cache_dir: Option, -} diff --git a/payjoin-directory/src/config.rs b/payjoin-directory/src/config.rs deleted file mode 100644 index 558a79b85..000000000 --- a/payjoin-directory/src/config.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::path::PathBuf; -use std::time::Duration; - -use anyhow::Result; -use config::builder::DefaultState; -use config::{ConfigError, File, FileFormat}; -use serde::Deserialize; - -type Builder = config::builder::ConfigBuilder; - -use crate::cli::Cli; - -#[derive(Debug, Clone, Deserialize)] -pub struct Config { - pub listen_addr: String, // TODO tokio_listener::ListenerAddressLFlag - pub timeout: Duration, - pub storage_dir: PathBuf, - pub ohttp_keys: PathBuf, // TODO OhttpConfig struct with rotation params, etc - #[serde(default)] - pub enable_v1: bool, - #[cfg(feature = "acme")] - pub acme: Option, -} - -#[cfg(feature = "acme")] -#[derive(Debug, Clone, Deserialize)] -pub struct AcmeConfig { - pub domain: String, - pub contact: String, - pub lets_encrypt_staging: bool, - pub cache_dir: PathBuf, -} - -#[cfg(feature = "acme")] -impl From for tokio_rustls_acme::AcmeConfig { - fn from(acme_config: AcmeConfig) -> Self { - tokio_rustls_acme::AcmeConfig::new([acme_config.domain]) - .contact_push(acme_config.contact) - .cache(tokio_rustls_acme::caches::DirCache::new(acme_config.cache_dir)) - .directory_lets_encrypt(!acme_config.lets_encrypt_staging) - } -} - -impl Config { - pub fn new(cli: &Cli) -> Result { - let mut config = config::Config::builder(); - config = add_defaults(config, cli)?; - - // what directory should this reside in? require explicit --config-file? ~/.config? /etc? - config = config.add_source(File::new("config.toml", FileFormat::Toml).required(false)); - - let built_config = config.build()?; - - Ok(Config { - listen_addr: built_config.get("listen_addr")?, - timeout: Duration::from_secs(built_config.get("timeout")?), - storage_dir: built_config.get("storage_dir")?, - ohttp_keys: built_config.get("ohttp_keys")?, - enable_v1: built_config.get("enable_v1").unwrap_or(false), - #[cfg(feature = "acme")] - acme: if built_config.get_table("acme").is_ok() { - Some(AcmeConfig { - domain: built_config.get("acme.domain")?, - contact: built_config.get("acme.contact")?, - lets_encrypt_staging: built_config.get("acme.lets_encrypt_staging")?, - cache_dir: built_config.get("acme.cache_dir")?, - }) - } else { - None - }, - }) - } -} - -fn add_defaults(config: Builder, cli: &Cli) -> Result { - let config = config - .set_default("listen_addr", "[::]:8080")? - .set_override_option("listen_addr", cli.port.map(|port| format!("[::]:{}", port)))? - .set_default("timeout", Some(30))? - .set_override_option("timeout", cli.timeout)? - .set_default("ohttp_keys", "ohttp_keys")? - .set_override_option( - "ohttp_keys", - cli.ohttp_keys.clone().map(|s| s.to_string_lossy().into_owned()), - )? - .set_override_option( - "storage_dir", - cli.storage_dir.clone().map(|s| s.to_string_lossy().into_owned()), - )?; - - #[cfg(feature = "acme")] - let config = if cli.acme.domain.is_some() { - config - .set_override_option("acme.domain", cli.acme.domain.clone())? - .set_override_option("acme.contact", cli.acme.contact.clone())? - .set_override_option("acme.lets_encrypt_staging", cli.acme.lets_encrypt_staging)? - .set_override_option( - "acme.cache_dir", - cli.acme.cache_dir.clone().map(|s| s.to_string_lossy().into_owned()), - )? - } else { - config - }; - - Ok(config) -} diff --git a/payjoin-directory/src/db/mod.rs b/payjoin-directory/src/db/mod.rs deleted file mode 100644 index 4adfdb296..000000000 --- a/payjoin-directory/src/db/mod.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::future::Future; -use std::result::Result; -use std::sync::Arc; - -use payjoin::directory::ShortId; - -pub(crate) mod files; - -pub trait SendableError: - std::error::Error + std::marker::Send + std::marker::Sync + std::convert::Into -{ -} - -#[derive(Debug)] -pub enum Error { - Operational(OperationalError), - Timeout(tokio::time::error::Elapsed), - OverCapacity, - AlreadyRead, - V1SenderUnavailable, -} - -impl SendableError for tokio::time::error::Elapsed {} -impl SendableError for std::io::Error {} - -impl From for Error { - fn from(value: E) -> Self { Error::Operational(value) } -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use Error::*; - match &self { - Operational(error) => write!(f, "Db error: {error}"), - Timeout(timeout) => write!(f, "Timeout: {timeout}"), - OverCapacity => "Database over capacity".fmt(f), - AlreadyRead => "Mailbox payload already read".fmt(f), - V1SenderUnavailable => "Sender no longer connected".fmt(f), - } - } -} - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use Error::*; - match self { - Operational(e) => Some(e), - Timeout(e) => Some(e), - _ => None, - } - } -} - -// TODO split into v1 and v2 traits -pub trait Db: Clone + Send + Sync + 'static { - type OperationalError: SendableError + 'static; - - /// Store a v2 payload. - fn post_v2_payload( - &self, - mailbox_id: &ShortId, - data: Vec, - ) -> impl Future, Error>> + Send; - - /// Read a stored v1 request or v2 payload, waiting if not yet posted. - fn wait_for_v2_payload( - &self, - mailbox_id: &ShortId, - ) -> impl Future>, Error>> + Send; - - /// Write a v1 response payload. - fn post_v1_response( - &self, - mailbox_id: &ShortId, - data: Vec, - ) -> impl Future>> + Send; - - /// Store a v1 request payload, waiting for any response. - fn post_v1_request_and_wait_for_response( - &self, - mailbox_id: &ShortId, - data: Vec, - ) -> impl Future>, Error>> + Send; -} diff --git a/payjoin-directory/src/main.rs b/payjoin-directory/src/main.rs deleted file mode 100644 index 2d7ae3554..000000000 --- a/payjoin-directory/src/main.rs +++ /dev/null @@ -1,55 +0,0 @@ -use clap::Parser; -use ohttp_relay::SentinelTag; -use payjoin_directory::*; -use tokio::net::TcpListener; -use tracing_subscriber::filter::LevelFilter; -use tracing_subscriber::EnvFilter; - -#[tokio::main] -async fn main() -> Result<(), BoxError> { - init_logging(); - - let cli = cli::Cli::parse(); - let config = config::Config::new(&cli)?; - - let key_dir = config.ohttp_keys; - std::fs::create_dir_all(&key_dir).expect("Failed to create key directory"); - - let ohttp = match key_config::read_server_config(&key_dir) { - Ok(config) => config, - Err(_) => { - let ohttp_config = key_config::gen_ohttp_server_config()?; - let path = key_config::persist_new_key_config(ohttp_config, &key_dir)?; - println!("Generated new key configuration at {}", path.display()); - key_config::read_server_config(&key_dir).expect("Failed to read newly generated config") - } - }; - - let db = payjoin_directory::FilesDb::init(config.timeout, config.storage_dir) - .await - .expect("Failed to initialize persistent storage"); - - let v1 = if config.enable_v1 { Some(V1::new(None)) } else { None }; - let service = Service::new(db, ohttp.into(), SentinelTag::new([0u8; 32]), v1); - - let listener = TcpListener::bind(config.listen_addr).await?; - - #[cfg(feature = "acme")] - if let Some(acme_config) = config.acme { - service.serve_acme(listener, acme_config.into()).await; - return Ok(()); - } - - service.serve_tcp(listener).await; - - Ok(()) -} - -fn init_logging() { - let env_filter = - EnvFilter::builder().with_default_directive(LevelFilter::INFO.into()).from_env_lossy(); - - tracing_subscriber::fmt().with_target(true).with_level(true).with_env_filter(env_filter).init(); - - println!("Logging initialized"); -} diff --git a/payjoin-ffi/dart/native/Cargo.toml b/payjoin-ffi/dart/native/Cargo.toml index 2a614b342..37ad21e29 100644 --- a/payjoin-ffi/dart/native/Cargo.toml +++ b/payjoin-ffi/dart/native/Cargo.toml @@ -26,6 +26,5 @@ payjoin-ffi = { path = "../.." } [patch.crates-io] ohttp-relay = { path = "../../../ohttp-relay" } payjoin = { path = "../../../payjoin" } -payjoin-directory = { path = "../../../payjoin-directory" } payjoin-mailroom = { path = "../../../payjoin-mailroom" } payjoin-test-utils = { path = "../../../payjoin-test-utils" } diff --git a/payjoin-ffi/javascript/test-utils/Cargo.toml b/payjoin-ffi/javascript/test-utils/Cargo.toml index 6d0c0f1bc..0580a4bef 100644 --- a/payjoin-ffi/javascript/test-utils/Cargo.toml +++ b/payjoin-ffi/javascript/test-utils/Cargo.toml @@ -24,9 +24,6 @@ path = "../../../ohttp-relay" [patch.crates-io.payjoin] path = "../../../payjoin" -[patch.crates-io.payjoin-directory] -path = "../../../payjoin-directory" - [patch.crates-io.payjoin-mailroom] path = "../../../payjoin-mailroom" diff --git a/payjoin-ffi/javascript/wasm-manifest-patch.toml b/payjoin-ffi/javascript/wasm-manifest-patch.toml index f601cac33..3717d63dd 100644 --- a/payjoin-ffi/javascript/wasm-manifest-patch.toml +++ b/payjoin-ffi/javascript/wasm-manifest-patch.toml @@ -18,5 +18,5 @@ features = ["wasm-unstable-single-threaded"] [patch.crates-io] payjoin = { path = "../../../../payjoin" } -payjoin-directory = { path = "../../../../payjoin-directory" } +payjoin-mailroom = { path = "../../../../payjoin-mailroom" } payjoin-test-utils = { path = "../../../../payjoin-test-utils" } diff --git a/payjoin-mailroom/Cargo.toml b/payjoin-mailroom/Cargo.toml index 482cb66b7..b3e096793 100644 --- a/payjoin-mailroom/Cargo.toml +++ b/payjoin-mailroom/Cargo.toml @@ -16,12 +16,7 @@ rust-version = "1.85.0" [features] default = [] _manual-tls = ["dep:axum-server", "dep:rustls", "ohttp-relay/_test-util"] -acme = [ - "dep:tokio-rustls-acme", - "dep:axum-server", - "dep:rustls", - "dep:tokio-stream", -] +acme = ["dep:tokio-rustls-acme", "dep:axum-server", "dep:rustls"] access-control = ["dep:flate2", "dep:ipnet", "dep:maxminddb", "dep:reqwest"] telemetry = ["dep:opentelemetry-otlp"] @@ -31,18 +26,25 @@ axum = "0.8" axum-server = { version = "0.8", features = [ "tls-rustls-no-provider", ], optional = true } +bhttp = { version = "0.6.1", features = ["http"] } +bitcoin = { version = "0.32.7", features = ["base64", "rand-std"] } clap = { version = "4.5", features = ["derive", "env"] } config = "0.15" flate2 = { version = "1.1", optional = true } +futures = "0.3" +http-body-util = "0.1.3" ipnet = { version = "2", optional = true } maxminddb = { version = "0.27", optional = true } +ohttp = { package = "bitcoin-ohttp", version = "0.6.0" } ohttp-relay = { path = "../ohttp-relay", features = ["bootstrap"] } opentelemetry = "0.31" opentelemetry-otlp = { version = "0.31", optional = true, features = [ "reqwest-rustls", ] } opentelemetry_sdk = "0.31" -payjoin-directory = { path = "../payjoin-directory" } +payjoin = { version = "1.0.0-rc.2", features = [ + "directory", +], default-features = false } rand = "0.8" reqwest = { version = "0.12", default-features = false, features = [ "rustls-tls", @@ -54,7 +56,7 @@ serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.47", features = ["full"] } tokio-listener = { version = "0.5", features = ["axum08", "serde"] } tokio-rustls-acme = { version = "0.9.0", features = ["axum"], optional = true } -tokio-stream = { version = "0.1.17", optional = true } +tokio-stream = { version = "0.1.17", features = ["net"] } tower = "0.5" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } diff --git a/payjoin-directory/contrib/health-check.sh b/payjoin-mailroom/contrib/health-check.sh similarity index 100% rename from payjoin-directory/contrib/health-check.sh rename to payjoin-mailroom/contrib/health-check.sh diff --git a/payjoin-mailroom/src/access_control.rs b/payjoin-mailroom/src/access_control.rs index f8bc39594..102e217b3 100644 --- a/payjoin-mailroom/src/access_control.rs +++ b/payjoin-mailroom/src/access_control.rs @@ -99,7 +99,7 @@ pub fn spawn_address_list_updater( url: String, refresh: std::time::Duration, cache_path: std::path::PathBuf, - blocked: payjoin_directory::BlockedAddresses, + blocked: crate::directory::BlockedAddresses, ) { tokio::spawn(async move { loop { diff --git a/payjoin-directory/src/db/files.rs b/payjoin-mailroom/src/db/files.rs similarity index 94% rename from payjoin-directory/src/db/files.rs rename to payjoin-mailroom/src/db/files.rs index 4ac022c39..0494cf3de 100644 --- a/payjoin-directory/src/db/files.rs +++ b/payjoin-mailroom/src/db/files.rs @@ -13,7 +13,7 @@ use tokio::io::{self, AsyncReadExt, AsyncWriteExt}; use tokio::sync::{oneshot, Mutex}; use tracing::trace; -use super::Db as DbTrait; +use crate::db::{Db as DbTrait, Error as DbError}; /// The maximum number of pending or populated mailbox entries. /// @@ -220,12 +220,12 @@ impl Mailboxes { } #[derive(Clone, Debug)] -pub struct Db { +pub struct FilesDb { timeout: Duration, - mailboxes: Arc>, + pub(crate) mailboxes: Arc>, } -impl Db { +impl FilesDb { pub async fn init(timeout: Duration, path: PathBuf) -> io::Result { Ok(Self { timeout, mailboxes: Arc::new(Mutex::new(Mailboxes::init(path).await?)) }) } @@ -245,13 +245,13 @@ impl Db { } } -impl DbTrait for Db { +impl DbTrait for FilesDb { type OperationalError = io::Error; async fn post_v2_payload( &self, id: &ShortId, payload: Vec, - ) -> Result, super::Error> { + ) -> Result, DbError> { let mut guard = self.mailboxes.lock().await; Ok(guard.post_v2(id, payload).await?) } @@ -259,7 +259,7 @@ impl DbTrait for Db { async fn wait_for_v2_payload( &self, id: &ShortId, - ) -> Result>, super::Error> { + ) -> Result>, DbError> { let receiver = { let mut guard = self.mailboxes.lock().await; @@ -272,7 +272,7 @@ impl DbTrait for Db { let ret = match tokio::time::timeout(self.timeout, receiver).await { Ok(payload) => Ok((payload.expect("receiver must not fail")).clone()), - Err(elapsed) => Err(super::Error::Timeout(elapsed)), + Err(elapsed) => Err(DbError::Timeout(elapsed)), }; self.mailboxes.lock().await.maybe_cleanup_v2_waitmap(id); @@ -284,21 +284,21 @@ impl DbTrait for Db { &self, id: &ShortId, payload: Vec, - ) -> Result>, super::Error> { + ) -> Result>, DbError> { let receiver = { self.mailboxes .lock() .await .post_v1_req_and_wait(id, payload) .await? - .ok_or(super::Error::OverCapacity)? + .ok_or(DbError::OverCapacity)? }; trace!("v1 sender waiting for v2 receiver's response"); let ret = match tokio::time::timeout(self.timeout, receiver).await { Ok(payload) => Ok(Arc::new(payload.expect("receiver must not fail"))), - Err(elapsed) => Err(super::Error::Timeout(elapsed)), + Err(elapsed) => Err(DbError::Timeout(elapsed)), }; // unconditionally clear the pending v1 entry. on timeout, the sender @@ -313,7 +313,7 @@ impl DbTrait for Db { &self, id: &ShortId, payload: Vec, - ) -> Result<(), super::Error> { + ) -> Result<(), DbError> { let mut guard = self.mailboxes.lock().await; Ok(guard.post_v1_res(id, payload).await?) } @@ -488,12 +488,6 @@ impl Mailboxes { // Prune any fully expired mailboxes, whether read or unread while let Some((created, id)) = self.insert_order.front().cloned() { - println!( - "checking if {id} elapsed: {:?} < {:?} = {}", - (created + self.unread_ttl_below_capacity), - now, - (created + self.unread_ttl_below_capacity) < now, - ); if created + self.unread_ttl_below_capacity < now { debug_assert!(self.insert_order.len() >= self.early_removal_count); _ = self.insert_order.pop_front(); @@ -513,14 +507,7 @@ impl Mailboxes { // So long as there expired read mailboxes, prune those. Stop when a // mailbox within the TTL is encountered. while let Some((read, id)) = self.read_order.front().cloned() { - println!( - "checking if {id} elapsed (read ttl): {:?} < {:?} = {}", - (read + self.read_ttl), - now, - (read + self.read_ttl) < now, - ); if read + self.read_ttl < now { - println!("removing"); _ = self.read_order.pop_front(); if self.remove(&id).await?.is_some() { self.early_removal_count += 1; @@ -594,13 +581,13 @@ impl From for Error { } // FIXME why isn't this sufficient for ?, necessitating ugly map_err(into)? -impl From for super::Error { - fn from(val: Error) -> super::Error { +impl From for DbError { + fn from(val: Error) -> DbError { match val { - Error::V1SenderUnavailable => super::Error::V1SenderUnavailable, - Error::OverCapacity => super::Error::OverCapacity, - Error::AlreadyRead => super::Error::AlreadyRead, - Error::IO(e) => super::Error::Operational(e), + Error::V1SenderUnavailable => DbError::V1SenderUnavailable, + Error::OverCapacity => DbError::OverCapacity, + Error::AlreadyRead => DbError::AlreadyRead, + Error::IO(e) => DbError::Operational(e), } } } @@ -626,7 +613,7 @@ impl std::fmt::Display for Error { } } -impl super::SendableError for Error {} +impl crate::db::SendableError for Error {} #[tokio::test] async fn test_disk_storage_initialization() -> std::io::Result<()> { @@ -767,7 +754,7 @@ async fn test_disk_storage_mailboxes() -> std::io::Result<()> { async fn test_mailbox_storage() -> std::io::Result<()> { let dir = tempfile::tempdir()?; - let db = Db::init(Duration::from_millis(10), dir.path().to_owned()) + let db = FilesDb::init(Duration::from_millis(10), dir.path().to_owned()) .await .expect("initializing mailbox database should succeed"); @@ -788,7 +775,7 @@ async fn test_mailbox_storage() -> std::io::Result<()> { async fn test_v2_wait() -> std::io::Result<()> { let dir = tempfile::tempdir()?; - let db = Db::init(Duration::from_millis(1), dir.path().to_owned()) + let db = FilesDb::init(Duration::from_millis(1), dir.path().to_owned()) .await .expect("initializing mailbox database should succeed"); @@ -796,7 +783,7 @@ async fn test_v2_wait() -> std::io::Result<()> { let contents = b"foo bar"; match db.wait_for_v2_payload(&id).await { - Err(super::Error::Timeout(_)) => {} + Err(DbError::Timeout(_)) => {} res => panic!("expected timeout, got {res:?}"), } @@ -845,7 +832,7 @@ async fn test_v1_wait() -> std::io::Result<()> { let dir = tempfile::tempdir()?; let db = Arc::new( - Db::init(Duration::from_millis(1), dir.path().to_owned()) + FilesDb::init(Duration::from_millis(1), dir.path().to_owned()) .await .expect("initializing mailbox database should succeed"), ); @@ -863,7 +850,7 @@ async fn test_v1_wait() -> std::io::Result<()> { assert!( matches!( db.post_v1_request_and_wait_for_response(&id, b"different request".to_vec()).await, - Err(super::Error::OverCapacity), + Err(DbError::OverCapacity), ), "second v1 sender with the same shortid should be rejected while request is in flight", ); @@ -879,7 +866,7 @@ async fn test_v1_wait() -> std::io::Result<()> { assert!( matches!( db.post_v1_response(&id, b"response".to_vec()).await, - Err(super::Error::V1SenderUnavailable) + Err(DbError::V1SenderUnavailable) ), "posting without a v1 sender waiting should fail" ); @@ -892,7 +879,7 @@ async fn test_v1_data_minimization() -> std::io::Result<()> { let dir = tempfile::tempdir()?; let db = Arc::new( - Db::init(Duration::from_millis(500), dir.path().to_owned()) + FilesDb::init(Duration::from_millis(500), dir.path().to_owned()) .await .expect("initializing mailbox database should succeed"), ); @@ -914,7 +901,7 @@ async fn test_v1_data_minimization() -> std::io::Result<()> { // Subsequent reads should not return the plaintext payload again. assert!( - matches!(db.wait_for_v2_payload(&id).await, Err(super::Error::AlreadyRead)), + matches!(db.wait_for_v2_payload(&id).await, Err(DbError::AlreadyRead)), "subsequent reads should indicate the payload was already consumed" ); @@ -947,7 +934,7 @@ async fn test_v1_data_minimization() -> std::io::Result<()> { async fn test_prune() -> std::io::Result<()> { let dir = tempfile::tempdir()?; - let db = Db::init(Duration::from_millis(2), dir.path().to_owned()) + let db = FilesDb::init(Duration::from_millis(2), dir.path().to_owned()) .await .expect("initializing mailbox database should succeed"); @@ -980,7 +967,7 @@ async fn test_prune() -> std::io::Result<()> { assert_eq!(db.mailboxes.lock().await.len(), 1); match read_task1.await.expect("joining should succeed") { - Err(super::Error::Timeout(_)) => {} + Err(DbError::Timeout(_)) => {} res => panic!("expected timeout, got {res:?}"), } diff --git a/payjoin-mailroom/src/db/mod.rs b/payjoin-mailroom/src/db/mod.rs new file mode 100644 index 000000000..5ee61e35e --- /dev/null +++ b/payjoin-mailroom/src/db/mod.rs @@ -0,0 +1,237 @@ +use std::future::Future; +use std::io; +use std::result::Result; +use std::sync::Arc; + +use payjoin::directory::ShortId; +use tower::util::BoxCloneSyncService; +use tower::{Service, ServiceExt}; + +pub mod files; + +pub trait SendableError: + std::error::Error + std::marker::Send + std::marker::Sync + std::convert::Into +{ +} + +#[derive(Debug)] +pub enum Error { + Operational(OperationalError), + Timeout(tokio::time::error::Elapsed), + OverCapacity, + AlreadyRead, + V1SenderUnavailable, +} + +impl SendableError for tokio::time::error::Elapsed {} +impl SendableError for std::io::Error {} + +impl From for Error { + fn from(value: E) -> Self { Error::Operational(value) } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use Error::*; + match &self { + Operational(error) => write!(f, "Db error: {error}"), + Timeout(timeout) => write!(f, "Timeout: {timeout}"), + OverCapacity => "Database over capacity".fmt(f), + AlreadyRead => "Mailbox payload already read".fmt(f), + V1SenderUnavailable => "Sender no longer connected".fmt(f), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use Error::*; + match self { + Operational(e) => Some(e), + Timeout(e) => Some(e), + _ => None, + } + } +} + +// TODO split into v1 and v2 traits +pub trait Db: Clone + Send + Sync + 'static { + type OperationalError: SendableError + 'static; + + /// Store a v2 payload. + fn post_v2_payload( + &self, + mailbox_id: &ShortId, + data: Vec, + ) -> impl Future, Error>> + Send; + + /// Read a stored v1 request or v2 payload, waiting if not yet posted. + fn wait_for_v2_payload( + &self, + mailbox_id: &ShortId, + ) -> impl Future>, Error>> + Send; + + /// Write a v1 response payload. + fn post_v1_response( + &self, + mailbox_id: &ShortId, + data: Vec, + ) -> impl Future>> + Send; + + /// Store a v1 request payload, waiting for any response. + fn post_v1_request_and_wait_for_response( + &self, + mailbox_id: &ShortId, + data: Vec, + ) -> impl Future>, Error>> + Send; +} + +#[derive(Clone, Debug)] +pub enum DbRequest { + PostV2Payload { mailbox_id: ShortId, payload: Vec }, + WaitForV2Payload { mailbox_id: ShortId }, + PostV1Response { mailbox_id: ShortId, payload: Vec }, + PostV1RequestAndWaitForResponse { mailbox_id: ShortId, payload: Vec }, +} + +#[derive(Clone, Debug)] +pub enum DbResponse { + PostV2Payload(Option<()>), + WaitForV2Payload(Arc>), + PostV1Response(()), + PostV1RequestAndWaitForResponse(Arc>), +} + +#[derive(Clone, Debug)] +pub struct FilesDbService { + db: FilesDb, +} + +impl FilesDbService { + pub fn new(db: FilesDb) -> Self { Self { db } } +} + +impl Service for FilesDbService { + type Response = DbResponse; + type Error = Error; + type Future = std::pin::Pin< + Box> + Send>, + >; + + fn poll_ready( + &mut self, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) + } + + fn call(&mut self, request: DbRequest) -> Self::Future { + let db = self.db.clone(); + Box::pin(async move { + match request { + DbRequest::PostV2Payload { mailbox_id, payload } => + Ok(DbResponse::PostV2Payload(db.post_v2_payload(&mailbox_id, payload).await?)), + DbRequest::WaitForV2Payload { mailbox_id } => + Ok(DbResponse::WaitForV2Payload(db.wait_for_v2_payload(&mailbox_id).await?)), + DbRequest::PostV1Response { mailbox_id, payload } => { + db.post_v1_response(&mailbox_id, payload).await?; + Ok(DbResponse::PostV1Response(())) + } + DbRequest::PostV1RequestAndWaitForResponse { mailbox_id, payload } => + Ok(DbResponse::PostV1RequestAndWaitForResponse( + db.post_v1_request_and_wait_for_response(&mailbox_id, payload).await?, + )), + } + }) + } +} + +#[derive(Clone)] +pub struct DbServiceAdapter { + inner: BoxCloneSyncService>, +} + +impl DbServiceAdapter { + pub fn new(files_db: FilesDb) -> Self { + let service = FilesDbService::new(files_db); + Self { inner: BoxCloneSyncService::new(service) } + } + + fn invalid_response(operation: &str) -> Error { + Error::Operational(io::Error::other(format!( + "invalid db response for operation {operation}" + ))) + } +} + +impl Db for DbServiceAdapter { + type OperationalError = io::Error; + + async fn post_v2_payload( + &self, + mailbox_id: &ShortId, + data: Vec, + ) -> Result, Error> { + let response = self + .inner + .clone() + .oneshot(DbRequest::PostV2Payload { mailbox_id: *mailbox_id, payload: data }) + .await?; + match response { + DbResponse::PostV2Payload(result) => Ok(result), + _ => Err(Self::invalid_response("post_v2_payload")), + } + } + + async fn wait_for_v2_payload( + &self, + mailbox_id: &ShortId, + ) -> Result>, Error> { + let response = self + .inner + .clone() + .oneshot(DbRequest::WaitForV2Payload { mailbox_id: *mailbox_id }) + .await?; + match response { + DbResponse::WaitForV2Payload(result) => Ok(result), + _ => Err(Self::invalid_response("wait_for_v2_payload")), + } + } + + async fn post_v1_response( + &self, + mailbox_id: &ShortId, + data: Vec, + ) -> Result<(), Error> { + let response = self + .inner + .clone() + .oneshot(DbRequest::PostV1Response { mailbox_id: *mailbox_id, payload: data }) + .await?; + match response { + DbResponse::PostV1Response(()) => Ok(()), + _ => Err(Self::invalid_response("post_v1_response")), + } + } + + async fn post_v1_request_and_wait_for_response( + &self, + mailbox_id: &ShortId, + data: Vec, + ) -> Result>, Error> { + let response = self + .inner + .clone() + .oneshot(DbRequest::PostV1RequestAndWaitForResponse { + mailbox_id: *mailbox_id, + payload: data, + }) + .await?; + match response { + DbResponse::PostV1RequestAndWaitForResponse(result) => Ok(result), + _ => Err(Self::invalid_response("post_v1_request_and_wait_for_response")), + } + } +} + +pub use files::FilesDb; diff --git a/payjoin-directory/src/lib.rs b/payjoin-mailroom/src/directory.rs similarity index 77% rename from payjoin-directory/src/lib.rs rename to payjoin-mailroom/src/directory.rs index b7a14309c..99c222160 100644 --- a/payjoin-directory/src/lib.rs +++ b/payjoin-mailroom/src/directory.rs @@ -4,29 +4,15 @@ use std::sync::Arc; use std::task::{Context, Poll}; use anyhow::Result; -use futures::StreamExt; -use http_body_util::combinators::BoxBody; -use http_body_util::{BodyExt, Empty, Full}; -use hyper::body::{Body, Bytes}; -use hyper::header::{HeaderValue, ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE}; -use hyper::server::conn::http1; -use hyper::{Method, Request, Response, StatusCode, Uri}; -use hyper_util::rt::TokioIo; -use hyper_util::service::TowerToHyperService; +use axum::body::{Body, Bytes}; +use axum::http::header::{HeaderValue, ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE}; +use axum::http::{Method, Request, Response, StatusCode, Uri}; +use http_body_util::BodyExt; +use ohttp_relay::SentinelTag; use payjoin::directory::{ShortId, ShortIdError, ENCAPSULATED_MESSAGE_BYTES}; -use tokio::net::TcpListener; -#[cfg(feature = "acme")] -use tokio_rustls_acme::AcmeConfig; -use tokio_stream::wrappers::TcpListenerStream; -use tokio_stream::Stream; use tracing::{debug, error, trace, warn}; -pub use crate::db::files::Db as FilesDb; -use crate::db::Db; -pub mod key_config; -use ohttp_relay::SentinelTag; - -pub use crate::key_config::*; +use crate::db::{Db, Error as DbError, SendableError}; const CHACHA20_POLY1305_NONCE_LEN: usize = 32; // chacha20poly1305 n_k const POLY1305_TAG_SIZE: usize = 16; @@ -40,31 +26,8 @@ const V1_UNAVAILABLE_RES_JSON: &str = r#"{{"errorCode": "unavailable", "message" const V1_VERSION_UNSUPPORTED_RES_JSON: &str = r#"{"errorCode": "version-unsupported", "supported": [2], "message": "V1 is not supported"}"#; -pub(crate) mod db; - -pub mod cli; -pub mod config; - pub type BoxError = Box; -#[cfg(feature = "_manual-tls")] -fn init_tls_acceptor(cert_key: (Vec, Vec)) -> Result { - use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer}; - use tokio_rustls::rustls::ServerConfig; - use tokio_rustls::TlsAcceptor; - let (cert, key) = cert_key; - let cert = CertificateDer::from(cert); - let key = - PrivateKeyDer::try_from(key).map_err(|e| anyhow::anyhow!("Could not parse key: {}", e))?; - - let mut server_config = ServerConfig::builder() - .with_no_client_auth() - .with_single_cert(vec![cert], key) - .map_err(|e| anyhow::anyhow!("TLS error: {}", e))?; - server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()]; - Ok(TlsAcceptor::from(std::sync::Arc::new(server_config))) -} - /// Opaque blocklist of Bitcoin addresses stored as script pubkeys. /// /// Addresses are converted to `ScriptBuf` at parse time so that @@ -72,7 +35,7 @@ fn init_tls_acceptor(cert_key: (Vec, Vec)) -> Result>>, + pub(crate) Arc>>, ); impl BlockedAddresses { @@ -135,10 +98,10 @@ pub struct Service { impl tower::Service> for Service where - B: Body + Send + 'static, + B: axum::body::HttpBody + Send + 'static, B::Error: Into, { - type Response = Response>; + type Response = Response; type Error = anyhow::Error; type Future = Pin> + Send>>; @@ -158,101 +121,14 @@ impl Service { Self { db, ohttp, sentinel_tag, v1 } } - #[cfg(feature = "_manual-tls")] - pub async fn serve_tls( - self, - listener: TcpListener, - tls_config: (Vec, Vec), - ) -> Result<(), BoxError> { - let tls_acceptor = init_tls_acceptor(tls_config)?; - // Spawn the connection handling loop in a separate task - - while let Ok((stream, _)) = listener.accept().await { - let tls_acceptor = tls_acceptor.clone(); - let service = self.clone(); - tokio::spawn(async move { - let tls_stream = match tls_acceptor.accept(stream).await { - Ok(tls_stream) => tls_stream, - Err(e) => { - error!("TLS accept error: {}", e); - return; - } - }; - let hyper_service = TowerToHyperService::new(service); - if let Err(err) = http1::Builder::new() - .serve_connection(TokioIo::new(tls_stream), hyper_service) - .with_upgrades() - .await - { - error!("Error serving connection: {:?}", err); - } - }); - } - Ok(()) - } - - #[cfg(feature = "acme")] - pub async fn serve_acme(self, listener: TcpListener, acme_config: AcmeConfig) + async fn serve_request(&self, req: Request) -> Result> where - EC: 'static + std::fmt::Debug, - EA: 'static + std::fmt::Debug, - { - let tcp_incoming = TcpListenerStream::new(listener); - - let tls_incoming = acme_config.incoming(tcp_incoming, Vec::new()); - - self.serve_connections(tls_incoming).await; - } - - pub async fn serve_tcp(self, listener: TcpListener) { - let tcp_incoming = TcpListenerStream::new(listener); - self.serve_connections(tcp_incoming).await; - } - - async fn serve_connections(self, mut incoming_connections: S) - where - S: Stream> + Unpin, - I: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + Unpin + 'static, - { - while let Some(conn) = incoming_connections.next().await { - match conn { - Ok(stream) => { - let service = self.clone(); - tokio::spawn(async move { service.serve_connection(stream).await }); - } - Err(err) => { - error!("Accept error: {err}") - } - } - } - } - - async fn serve_connection(&self, stream: I) - where - I: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + Unpin + 'static, - { - let hyper_service = TowerToHyperService::new(self.clone()); - if let Err(err) = http1::Builder::new() - .serve_connection(TokioIo::new(stream), hyper_service) - .with_upgrades() - .await - { - error!("Error serving connection: {:?}", err); - } - } - - async fn serve_request( - &self, - req: Request, - ) -> Result>> - where - B: Body + Send + 'static, + B: axum::body::HttpBody + Send + 'static, B::Error: Into, { let path = req.uri().path().to_string(); let query = req.uri().query().unwrap_or_default().to_string(); let (parts, body) = req.into_parts(); - let path_segments: Vec<&str> = path.split('/').collect(); debug!("Service::serve_request: {:?}", &path_segments); @@ -286,7 +162,6 @@ impl Service { // Allow CORS for third-party access response.headers_mut().insert(ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*")); - Ok(response) } @@ -296,9 +171,9 @@ impl Service { id: &str, query: String, body: B, - ) -> Result>, HandlerError> + ) -> Result, HandlerError> where - B: Body + Send + 'static, + B: axum::body::HttpBody + Send + 'static, B::Error: Into, { if self.v1.is_some() { @@ -312,20 +187,18 @@ impl Service { } /// Handle an encapsulated OHTTP request and return an encapsulated response - async fn handle_ohttp_gateway( - &self, - body: B, - ) -> Result>, HandlerError> + async fn handle_ohttp_gateway(&self, body: B) -> Result, HandlerError> where - B: Body + Send + 'static, + B: axum::body::HttpBody + Send + 'static, B::Error: Into, { - // Decapsulate OHTTP request let ohttp_body = body .collect() .await .map_err(|e| HandlerError::BadRequest(anyhow::anyhow!(e.into())))? .to_bytes(); + + // Decapsulate OHTTP request let (bhttp_req, res_ctx) = self .ohttp .decapsulate(&ohttp_body) @@ -378,11 +251,10 @@ impl Service { async fn handle_decapsulated_request( &self, - req: Request>, - ) -> Result>, HandlerError> { + req: Request, + ) -> Result, HandlerError> { let path = req.uri().path().to_string(); let (parts, body) = req.into_parts(); - let path_segments: Vec<&str> = path.split('/').collect(); debug!("handle_v2: {:?}", &path_segments); match (parts.method, path_segments.as_slice()) { @@ -393,16 +265,10 @@ impl Service { } } - async fn post_mailbox( - &self, - id: &str, - body: BoxBody, - ) -> Result>, HandlerError> { + async fn post_mailbox(&self, id: &str, body: Body) -> Result, HandlerError> { let none_response = Response::builder().status(StatusCode::OK).body(empty())?; trace!("post_mailbox"); - let id = ShortId::from_str(id)?; - let req = body .collect() .await @@ -411,17 +277,13 @@ impl Service { if req.len() > V1_MAX_BUFFER_SIZE { return Err(HandlerError::PayloadTooLarge); } - match self.db.post_v2_payload(&id, req.into()).await { Ok(_) => Ok(none_response), Err(e) => Err(HandlerError::InternalServerError(e.into())), } } - async fn get_mailbox( - &self, - id: &str, - ) -> Result>, HandlerError> { + async fn get_mailbox(&self, id: &str) -> Result, HandlerError> { trace!("get_mailbox"); let id = ShortId::from_str(id)?; let timeout_response = Response::builder().status(StatusCode::ACCEPTED).body(empty())?; @@ -442,23 +304,16 @@ impl Service { ))); } ScreenResult::Clean => {} - ScreenResult::ParseError(e) => { - warn!("Could not parse V1 PSBT: {e}"); - } + ScreenResult::ParseError(e) => warn!("Could not parse V1 PSBT: {e}"), } } } Ok(()) } - async fn put_payjoin_v1( - &self, - id: &str, - body: BoxBody, - ) -> Result>, HandlerError> { + async fn put_payjoin_v1(&self, id: &str, body: Body) -> Result, HandlerError> { trace!("Put_payjoin_v1"); let ok_response = Response::builder().status(StatusCode::OK).body(empty())?; - let id = ShortId::from_str(id)?; let req = body .collect() @@ -483,9 +338,9 @@ impl Service { id: &str, query: String, body: B, - ) -> Result>, HandlerError> + ) -> Result, HandlerError> where - B: Body + Send + 'static, + B: axum::body::HttpBody + Send + 'static, B::Error: Into, { trace!("Post fallback v1"); @@ -499,12 +354,10 @@ impl Service { Ok(bytes) => bytes.to_bytes(), Err(_) => return Ok(bad_request_body_res), }; - let body_str = match String::from_utf8(body_bytes.to_vec()) { Ok(body_str) => body_str, Err(_) => return Ok(bad_request_body_res), }; - self.check_v1_blocklist(&body_str).await?; let v2_compat_body = format!("{body_str}\n{query}"); @@ -515,17 +368,14 @@ impl Service { ) } - async fn handle_ohttp_gateway_get( - &self, - query: &str, - ) -> Result>, HandlerError> { + async fn handle_ohttp_gateway_get(&self, query: &str) -> Result, HandlerError> { match query { "allowed_purposes" => Ok(self.get_ohttp_allowed_purposes().await), _ => self.get_ohttp_keys().await, } } - async fn get_ohttp_keys(&self) -> Result>, HandlerError> { + async fn get_ohttp_keys(&self) -> Result, HandlerError> { let ohttp_keys = self .ohttp .config() @@ -536,7 +386,7 @@ impl Service { Ok(res) } - async fn get_ohttp_allowed_purposes(&self) -> Response> { + async fn get_ohttp_allowed_purposes(&self) -> Response { // Encode the magic string in the same format as a TLS ALPN protocol list (a // U16BE length encoded list of U8 length encoded strings). // @@ -546,49 +396,44 @@ impl Service { let mut res = Response::new(full(Bytes::from_static( b"\x00\x01\x2aBIP77 454403bb-9f7b-4385-b31f-acd2dae20b7e", ))); - res.headers_mut() .insert(CONTENT_TYPE, HeaderValue::from_static("application/x-ohttp-allowed-purposes")); - res } + + async fn health_check(&self) -> Result, HandlerError> { + let versions = if self.v1.is_some() { "[1,2]" } else { "[2]" }; + let body = format!(r#"{{"versions":{versions}}}"#); + let mut res = Response::new(full(body)); + res.headers_mut().insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); + Ok(res) + } } -fn handle_peek( - result: Result>, db::Error>, - timeout_response: Response>, -) -> Result>, HandlerError> { +fn handle_peek( + result: Result>, DbError>, + timeout_response: Response, +) -> Result, HandlerError> { match result { Ok(payload) => Ok(Response::new(full((*payload).clone()))), // TODO Bytes instead of Arc> Err(e) => match e { - db::Error::Operational(err) => { + DbError::Operational(err) => { error!("Storage error: {err}"); Err(HandlerError::InternalServerError(anyhow::Error::msg("Internal server error"))) } - db::Error::Timeout(_) => Ok(timeout_response), - db::Error::OverCapacity => Err(HandlerError::ServiceUnavailable(anyhow::Error::msg( + DbError::Timeout(_) => Ok(timeout_response), + DbError::OverCapacity => Err(HandlerError::ServiceUnavailable(anyhow::Error::msg( "mailbox storage at capacity", ))), - db::Error::AlreadyRead => Ok(timeout_response), - db::Error::V1SenderUnavailable => Err(HandlerError::SenderGone(anyhow::Error::msg( + DbError::AlreadyRead => Ok(timeout_response), + DbError::V1SenderUnavailable => Err(HandlerError::SenderGone(anyhow::Error::msg( "Sender is unavailable try a new request", ))), }, } } -impl Service { - async fn health_check(&self) -> Result>, HandlerError> { - let versions = if self.v1.is_some() { "[1,2]" } else { "[2]" }; - let body = format!(r#"{{"versions":{versions}}}"#); - let mut res = Response::new(full(body)); - res.headers_mut().insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); - Ok(res) - } -} - -async fn handle_directory_home_path() -> Result>, HandlerError> -{ +async fn handle_directory_home_path() -> Result, HandlerError> { let mut res = Response::new(empty()); *res.status_mut() = StatusCode::OK; res.headers_mut().insert(CONTENT_TYPE, HeaderValue::from_static("text/html")); @@ -662,7 +507,7 @@ enum HandlerError { } impl HandlerError { - fn to_response(&self) -> Response> { + fn to_response(&self) -> Response { let mut res = Response::new(empty()); match self { HandlerError::PayloadTooLarge => *res.status_mut() = StatusCode::PAYLOAD_TOO_LARGE, @@ -680,7 +525,6 @@ impl HandlerError { } HandlerError::OhttpKeyRejection(e) => { const OHTTP_KEY_REJECTION_RES_JSON: &str = r#"{"type":"https://iana.org/assignments/http-problem-types#ohttp-key", "title": "key identifier unknown"}"#; - warn!("Bad request: Key configuration rejected: {}", e); *res.status_mut() = StatusCode::BAD_REQUEST; res.headers_mut() @@ -700,14 +544,13 @@ impl HandlerError { warn!("Forbidden: {}", e); *res.status_mut() = StatusCode::FORBIDDEN } - }; - + } res } } -impl From for HandlerError { - fn from(e: hyper::http::Error) -> Self { HandlerError::InternalServerError(e.into()) } +impl From for HandlerError { + fn from(e: axum::http::Error) -> Self { HandlerError::InternalServerError(e.into()) } } impl From for HandlerError { @@ -716,19 +559,15 @@ impl From for HandlerError { } } -fn not_found() -> Response> { +fn not_found() -> Response { let mut res = Response::default(); *res.status_mut() = StatusCode::NOT_FOUND; res } -fn empty() -> BoxBody { - Empty::::new().map_err(|never| match never {}).boxed() -} +fn empty() -> Body { Body::empty() } -fn full>(chunk: T) -> BoxBody { - Full::new(chunk.into()).map_err(|never| match never {}).boxed() -} +fn full>(chunk: T) -> Body { Body::from(chunk.into()) } enum ScreenResult { Blocked, @@ -747,7 +586,6 @@ fn screen_v1_addresses( Ok(b) => b, Err(e) => return ScreenResult::ParseError(format!("base64 decode: {e}")), }; - let psbt = match Psbt::deserialize(&psbt_bytes) { Ok(p) => p, Err(e) => return ScreenResult::ParseError(format!("PSBT deserialize: {e}")), @@ -777,7 +615,6 @@ fn screen_v1_addresses( } } } - ScreenResult::Clean } @@ -786,18 +623,17 @@ mod tests { use std::time::Duration; use http_body_util::BodyExt; - use hyper::body::Bytes; - use hyper::{Method, Request, StatusCode}; use ohttp_relay::SentinelTag; use payjoin::directory::ShortId; use super::*; + use crate::db::FilesDb; async fn test_service(v1: Option) -> Service { let dir = tempfile::tempdir().expect("tempdir"); let db = FilesDb::init(Duration::from_millis(100), dir.keep()).await.expect("db init"); let ohttp: ohttp::Server = - key_config::gen_ohttp_server_config().expect("ohttp config").into(); + crate::key_config::gen_ohttp_server_config().expect("ohttp config").into(); Service::new(db, ohttp, SentinelTag::new([0u8; 32]), v1) } @@ -807,7 +643,7 @@ mod tests { id.to_string() } - async fn collect_body(res: Response>) -> (StatusCode, String) { + async fn collect_body(res: Response) -> (StatusCode, String) { let (parts, body) = res.into_parts(); let bytes = body.collect().await.unwrap().to_bytes(); (parts.status, String::from_utf8(bytes.to_vec()).unwrap()) @@ -822,7 +658,7 @@ mod tests { let req = Request::builder() .method(Method::POST) .uri(format!("http://localhost/{id}")) - .body(Full::new(Bytes::from("base64-psbt"))) + .body(Body::from("base64-psbt")) .unwrap(); let res = tower::Service::call(&mut svc, req).await.unwrap(); @@ -839,7 +675,7 @@ mod tests { let req = Request::builder() .method(Method::POST) .uri(format!("http://localhost/{id}")) - .body(Full::new(Bytes::from(vec![0xFF, 0xFE]))) + .body(Body::from(vec![0xFF, 0xFE])) .unwrap(); let res = tower::Service::call(&mut svc, req).await.unwrap(); @@ -856,7 +692,7 @@ mod tests { let req = Request::builder() .method(Method::POST) .uri(format!("http://localhost/{id}")) - .body(Full::new(Bytes::from("base64-psbt"))) + .body(Body::from("base64-psbt")) .unwrap(); let res = tower::Service::call(&mut svc, req).await.unwrap(); @@ -904,7 +740,7 @@ mod tests { let req = Request::builder() .method(Method::POST) .uri(format!("http://localhost/{id}")) - .body(Full::new(Bytes::from(psbt_b64))) + .body(Body::from(psbt_b64)) .unwrap(); let res = tower::Service::call(&mut svc, req).await.unwrap(); @@ -977,7 +813,7 @@ mod tests { let req = Request::builder() .method(Method::GET) .uri("http://localhost/health") - .body(Full::new(Bytes::new())) + .body(Body::empty()) .unwrap(); let res = tower::Service::call(&mut svc, req).await.unwrap(); @@ -993,7 +829,7 @@ mod tests { let req = Request::builder() .method(Method::GET) .uri("http://localhost/health") - .body(Full::new(Bytes::new())) + .body(Body::empty()) .unwrap(); let res = tower::Service::call(&mut svc, req).await.unwrap(); diff --git a/payjoin-directory/src/key_config.rs b/payjoin-mailroom/src/key_config.rs similarity index 97% rename from payjoin-directory/src/key_config.rs rename to payjoin-mailroom/src/key_config.rs index a4d2d1c8c..0326eebc9 100644 --- a/payjoin-directory/src/key_config.rs +++ b/payjoin-mailroom/src/key_config.rs @@ -41,7 +41,6 @@ pub fn persist_new_key_config(ohttp_config: ServerKeyConfig, dir: &Path) -> Resu use std::io::Write; let key_path = key_path(dir); - let mut file = OpenOptions::new() .write(true) .create_new(true) @@ -50,8 +49,7 @@ pub fn persist_new_key_config(ohttp_config: ServerKeyConfig, dir: &Path) -> Resu file.write_all(&ohttp_config.ikm) .map_err(|e| anyhow!("Failed to write OHTTP keys to file: {}", e))?; - info!("Saved OHTTP Key Configuration to {}", &key_path.display()); - + info!("Saved OHTTP Key Configuration to {}", key_path.display()); Ok(key_path) } diff --git a/payjoin-mailroom/src/lib.rs b/payjoin-mailroom/src/lib.rs index 08273bdba..e2bd2b51a 100644 --- a/payjoin-mailroom/src/lib.rs +++ b/payjoin-mailroom/src/lib.rs @@ -18,6 +18,9 @@ use tracing::info; pub mod access_control; pub mod cli; pub mod config; +pub mod db; +pub mod directory; +pub mod key_config; pub mod metrics; pub mod middleware; @@ -26,7 +29,7 @@ use crate::middleware::{track_connections, track_metrics}; #[derive(Clone)] struct Services { - directory: payjoin_directory::Service, + directory: crate::directory::Service, relay: ohttp_relay::Service, metrics: MetricsService, #[cfg(feature = "access-control")] @@ -209,9 +212,10 @@ impl Connected> for middleware::MaybePeerIp { async fn init_directory( config: &Config, sentinel_tag: SentinelTag, -) -> anyhow::Result> { - let db = payjoin_directory::FilesDb::init(config.timeout, config.storage_dir.clone()).await?; - db.spawn_background_prune().await; +) -> anyhow::Result> { + let files_db = crate::db::FilesDb::init(config.timeout, config.storage_dir.clone()).await?; + files_db.spawn_background_prune().await; + let db = crate::db::DbServiceAdapter::new(files_db); let ohttp_keys_dir = config.storage_dir.join("ohttp-keys"); let ohttp_config = init_ohttp_config(&ohttp_keys_dir)?; @@ -221,11 +225,11 @@ async fn init_directory( let blocked = init_blocked_addresses(config).await?; #[cfg(not(feature = "access-control"))] let blocked = None; - Some(payjoin_directory::V1::new(blocked)) + Some(crate::directory::V1::new(blocked)) } else { None }; - Ok(payjoin_directory::Service::new(db, ohttp_config.into(), sentinel_tag, v1)) + Ok(crate::directory::Service::new(db, ohttp_config.into(), sentinel_tag, v1)) } #[cfg(feature = "access-control")] @@ -245,7 +249,7 @@ async fn init_geoip( #[cfg(feature = "access-control")] async fn init_blocked_addresses( config: &Config, -) -> anyhow::Result> { +) -> anyhow::Result> { let v1_config = match &config.v1 { Some(c) => c, None => return Ok(None), @@ -260,11 +264,11 @@ async fn init_blocked_addresses( let blocked = match &v1_config.blocked_addresses_path { Some(path) => { let text = access_control::load_blocked_address_text(path)?; - let ba = payjoin_directory::BlockedAddresses::from_address_lines(&text); + let ba = crate::directory::BlockedAddresses::from_address_lines(&text); info!("Loaded blocked addresses from {}", path.display()); ba } - None => payjoin_directory::BlockedAddresses::empty(), + None => crate::directory::BlockedAddresses::empty(), }; // If URL configured, try initial fetch and spawn background updater @@ -309,7 +313,7 @@ async fn init_blocked_addresses( #[cfg(feature = "access-control")] async fn load_address_cache( cache_path: &std::path::Path, - blocked: &payjoin_directory::BlockedAddresses, + blocked: &crate::directory::BlockedAddresses, ) { if cache_path.exists() { match access_control::load_blocked_address_text(cache_path) { @@ -324,13 +328,13 @@ async fn load_address_cache( fn init_ohttp_config( ohttp_keys_dir: &std::path::Path, -) -> anyhow::Result { +) -> anyhow::Result { std::fs::create_dir_all(ohttp_keys_dir)?; - match payjoin_directory::read_server_config(ohttp_keys_dir) { + match crate::key_config::read_server_config(ohttp_keys_dir) { Ok(config) => Ok(config), Err(_) => { - let config = payjoin_directory::gen_ohttp_server_config()?; - payjoin_directory::persist_new_key_config(config.clone(), ohttp_keys_dir)?; + let config = crate::key_config::gen_ohttp_server_config()?; + crate::key_config::persist_new_key_config(config.clone(), ohttp_keys_dir)?; Ok(config) } }