Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose test fixtures and fns at payjoin-test-utils #425

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
17 changes: 17 additions & 0 deletions Cargo-minimal.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1590,6 +1590,7 @@ dependencies = [
"ohttp-relay",
"once_cell",
"payjoin-directory",
"payjoin-test-utils",
"rcgen",
"reqwest",
"rustls 0.22.4",
Expand Down Expand Up @@ -1624,6 +1625,7 @@ dependencies = [
"once_cell",
"payjoin",
"payjoin-directory",
"payjoin-test-utils",
"rcgen",
"reqwest",
"rustls 0.22.4",
Expand Down Expand Up @@ -1659,6 +1661,21 @@ dependencies = [
"tracing-subscriber",
]

[[package]]
name = "payjoin-test-utils"
version = "0.1.0"
dependencies = [
"bitcoincore-rpc",
"bitcoind",
"http",
"log",
"ohttp-relay",
"payjoin-directory",
"rcgen",
"testcontainers",
"testcontainers-modules",
]

[[package]]
name = "pbkdf2"
version = "0.11.0"
Expand Down
17 changes: 17 additions & 0 deletions Cargo-recent.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1590,6 +1590,7 @@ dependencies = [
"ohttp-relay",
"once_cell",
"payjoin-directory",
"payjoin-test-utils",
"rcgen",
"reqwest",
"rustls 0.22.4",
Expand Down Expand Up @@ -1624,6 +1625,7 @@ dependencies = [
"once_cell",
"payjoin",
"payjoin-directory",
"payjoin-test-utils",
"rcgen",
"reqwest",
"rustls 0.22.4",
Expand Down Expand Up @@ -1659,6 +1661,21 @@ dependencies = [
"tracing-subscriber",
]

[[package]]
name = "payjoin-test-utils"
version = "0.1.0"
dependencies = [
"bitcoincore-rpc",
"bitcoind",
"http",
"log",
"ohttp-relay",
"payjoin-directory",
"rcgen",
"testcontainers",
"testcontainers-modules",
]

[[package]]
name = "pbkdf2"
version = "0.11.0"
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["payjoin", "payjoin-cli", "payjoin-directory"]
members = ["payjoin", "payjoin-cli", "payjoin-directory", "payjoin-test-utils"]
resolver = "2"

[patch.crates-io.payjoin]
Expand Down
1 change: 1 addition & 0 deletions payjoin-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ http = "1"
ohttp-relay = "0.0.8"
once_cell = "1"
payjoin-directory = { path = "../payjoin-directory", features = ["_danger-local-https"] }
payjoin-test-utils = { path = "../payjoin-test-utils" }
testcontainers = "0.15.0"
testcontainers-modules = { version = "0.1.3", features = ["redis"] }
tokio = { version = "1.12.0", features = ["full"] }
Expand Down
111 changes: 20 additions & 91 deletions payjoin-cli/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,24 @@ mod e2e {
use std::process::Stdio;

use bitcoincore_rpc::json::AddressType;
use bitcoind::bitcoincore_rpc::RpcApi;
use log::{log_enabled, Level};
use payjoin::bitcoin::Amount;
use payjoin_test_utils::*;
use tokio::fs;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::process::Command;

const RECEIVE_SATS: &str = "54321";

type Error = Box<dyn std::error::Error + 'static>;
type Result<T> = std::result::Result<T, Error>;

#[cfg(not(feature = "v2"))]
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn send_receive_payjoin() {
let bitcoind_exe = env::var("BITCOIND_EXE")
.ok()
.or_else(|| bitcoind::downloaded_exe_path().ok())
.expect("version feature or env BITCOIND_EXE is required for tests");
let mut conf = bitcoind::Conf::default();
conf.view_stdout = log_enabled!(Level::Debug);
let bitcoind = bitcoind::BitcoinD::with_conf(bitcoind_exe, &conf).unwrap();
let receiver = bitcoind.create_wallet("receiver").unwrap();
let receiver_address =
receiver.get_new_address(None, Some(AddressType::Bech32)).unwrap().assume_checked();
let sender = bitcoind.create_wallet("sender").unwrap();
let sender_address =
sender.get_new_address(None, Some(AddressType::Bech32)).unwrap().assume_checked();
bitcoind.client.generate_to_address(1, &receiver_address).unwrap();
bitcoind.client.generate_to_address(101, &sender_address).unwrap();

assert_eq!(
Amount::from_btc(50.0).unwrap(),
receiver.get_balances().unwrap().mine.trusted,
"receiver doesn't own bitcoin"
);

assert_eq!(
Amount::from_btc(50.0).unwrap(),
sender.get_balances().unwrap().mine.trusted,
"sender doesn't own bitcoin"
);
async fn send_receive_payjoin() -> Result<()> {
// _sender and _receiver are called by the payjoin-cli using RPC directly
let (bitcoind, _sender, _receiver) = payjoin_test_utils::init_bitcoind_sender_receiver(
Some(AddressType::Bech32),
Some(AddressType::Bech32),
)?;

let temp_dir = env::temp_dir();
let receiver_db_path = temp_dir.join("receiver_db");
Expand Down Expand Up @@ -151,6 +130,7 @@ mod e2e {
payjoin_sent.unwrap().unwrap_or(Some(false)).unwrap(),
"Payjoin send was not detected"
);
Ok(())
}

#[cfg(feature = "v2")]
Expand All @@ -164,20 +144,15 @@ mod e2e {
use http::StatusCode;
use once_cell::sync::{Lazy, OnceCell};
use reqwest::{Client, ClientBuilder};
use testcontainers::clients::Cli;
use testcontainers_modules::redis::Redis;
use tokio::process::Child;
use url::Url;

type Error = Box<dyn std::error::Error + 'static>;
type Result<T> = std::result::Result<T, Error>;

static INIT_TRACING: OnceCell<()> = OnceCell::new();
static TESTS_TIMEOUT: Lazy<Duration> = Lazy::new(|| Duration::from_secs(20));
static WAIT_SERVICE_INTERVAL: Lazy<Duration> = Lazy::new(|| Duration::from_secs(3));

init_tracing();
let (cert, key) = local_cert_key();
let (cert, key) = payjoin_test_utils::local_cert_key();
let ohttp_relay_port = find_free_port();
let ohttp_relay = Url::parse(&format!("http://localhost:{}", ohttp_relay_port)).unwrap();
let directory_port = find_free_port();
Expand All @@ -189,7 +164,7 @@ mod e2e {
let sender_db_path = temp_dir.join("sender_db");
let result: Result<()> = tokio::select! {
res = ohttp_relay::listen_tcp(ohttp_relay_port, gateway_origin) => Err(format!("Ohttp relay is long running: {:?}", res).into()),
res = init_directory(directory_port, (cert.clone(), key)) => Err(format!("Directory server is long running: {:?}", res).into()),
res = payjoin_test_utils::init_directory(directory_port, (cert.clone(), key)) => Err(format!("Directory server is long running: {:?}", res).into()),
res = send_receive_cli_async(ohttp_relay, directory, cert, receiver_db_path.clone(), sender_db_path.clone()) => res.map_err(|e| format!("send_receive failed: {:?}", e).into()),
};

Expand All @@ -204,33 +179,13 @@ mod e2e {
receiver_db_path: PathBuf,
sender_db_path: PathBuf,
) -> Result<()> {
let bitcoind_exe = env::var("BITCOIND_EXE")
.ok()
.or_else(|| bitcoind::downloaded_exe_path().ok())
.expect("version feature or env BITCOIND_EXE is required for tests");
let mut conf = bitcoind::Conf::default();
conf.view_stdout = log_enabled!(Level::Debug);
let bitcoind = bitcoind::BitcoinD::with_conf(bitcoind_exe, &conf)?;
let receiver = bitcoind.create_wallet("receiver")?;
let receiver_address =
receiver.get_new_address(None, Some(AddressType::Bech32))?.assume_checked();
let sender = bitcoind.create_wallet("sender")?;
let sender_address =
sender.get_new_address(None, Some(AddressType::Bech32))?.assume_checked();
bitcoind.client.generate_to_address(1, &receiver_address)?;
bitcoind.client.generate_to_address(101, &sender_address)?;

assert_eq!(
Amount::from_btc(50.0)?,
receiver.get_balances()?.mine.trusted,
"receiver doesn't own bitcoin"
);

assert_eq!(
Amount::from_btc(50.0)?,
sender.get_balances()?.mine.trusted,
"sender doesn't own bitcoin"
);
// _sender and _receiver are called by the payjoin-cli using RPC directly
let (bitcoind, _sender, _receiver) = payjoin_test_utils::init_bitcoind_sender_receiver(
Some(AddressType::Bech32),
Some(AddressType::Bech32),
)
.unwrap();

let temp_dir = env::temp_dir();
let cert_path = temp_dir.join("localhost.der");
tokio::fs::write(&cert_path, cert.clone()).await?;
Expand Down Expand Up @@ -476,27 +431,6 @@ mod e2e {
Err("Timeout waiting for service to be ready".into())
}

async fn init_directory(port: u16, local_cert_key: (Vec<u8>, Vec<u8>)) -> Result<()> {
let docker: Cli = Cli::default();
let timeout = Duration::from_secs(2);
let db = docker.run(Redis);
let db_host = format!("127.0.0.1:{}", db.get_host_port_ipv4(6379));
println!("Database running on {}", db.get_host_port_ipv4(6379));
payjoin_directory::listen_tcp_with_tls(port, db_host, timeout, local_cert_key).await
}

// generates or gets a DER encoded localhost cert and key.
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)
}

fn http_agent(cert_der: Vec<u8>) -> Result<Client> {
Ok(http_agent_builder(cert_der)?.build()?)
}
Expand All @@ -521,11 +455,6 @@ mod e2e {
}
}

fn find_free_port() -> u16 {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
listener.local_addr().unwrap().port()
}

async fn cleanup_temp_file(path: &std::path::Path) {
if let Err(e) = fs::remove_dir_all(path).await {
eprintln!("Failed to remove {:?}: {}", path, e);
Expand Down
18 changes: 18 additions & 0 deletions payjoin-test-utils/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "payjoin-test-utils"
version = "0.1.0"
edition = "2021"
authors = ["Dan Gould <[email protected]>"]
rust-version = "1.63"
license = "MIT"

[dependencies]
bitcoincore-rpc = "0.19.0"
bitcoind = { version = "0.36.0", features = ["0_21_2"] }
http = "1"
log = "0.4.7"
ohttp-relay = "0.0.8"
payjoin-directory = { path = "../payjoin-directory", features = ["_danger-local-https"] }
rcgen = "0.11"
testcontainers = "0.15.0"
testcontainers-modules = { version = "0.1.3", features = ["redis"] }
77 changes: 77 additions & 0 deletions payjoin-test-utils/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use std::time::Duration;

use bitcoincore_rpc::bitcoin::Amount;
use bitcoincore_rpc::json::AddressType;
use bitcoincore_rpc::RpcApi;
use testcontainers::clients::Cli;
use testcontainers_modules::redis::Redis;

type Error = Box<dyn std::error::Error + 'static>;

pub fn init_bitcoind() -> Result<bitcoind::BitcoinD, Error> {
let bitcoind_exe = std::env::var("BITCOIND_EXE")
.ok()
.or_else(|| bitcoind::downloaded_exe_path().ok())
.unwrap();
let mut conf = bitcoind::Conf::default();
conf.view_stdout = log::log_enabled!(log::Level::Debug);
let bitcoind = bitcoind::BitcoinD::with_conf(bitcoind_exe, &conf)?;
Ok(bitcoind)
}

pub fn init_bitcoind_sender_receiver(
sender_address_type: Option<AddressType>,
receiver_address_type: Option<AddressType>,
) -> Result<(bitcoind::BitcoinD, bitcoincore_rpc::Client, bitcoincore_rpc::Client), Error> {
let bitcoind = init_bitcoind()?;
let receiver = bitcoind.create_wallet("receiver")?;
let receiver_address = receiver.get_new_address(None, receiver_address_type)?.assume_checked();
let sender = bitcoind.create_wallet("sender")?;
let sender_address = sender.get_new_address(None, sender_address_type)?.assume_checked();
bitcoind.client.generate_to_address(1, &receiver_address)?;
bitcoind.client.generate_to_address(101, &sender_address)?;

assert_eq!(
Amount::from_btc(50.0)?,
receiver.get_balances()?.mine.trusted,
"receiver doesn't own bitcoin"
);

assert_eq!(
Amount::from_btc(50.0)?,
sender.get_balances()?.mine.trusted,
"sender doesn't own bitcoin"
);
Ok((bitcoind, sender, receiver))
}

pub async fn init_directory(port: u16, local_cert_key: (Vec<u8>, Vec<u8>)) -> Result<(), Error> {
let docker: Cli = Cli::default();
let timeout = Duration::from_secs(2);
let db = docker.run(Redis);
let db_host = format!("127.0.0.1:{}", db.get_host_port_ipv4(6379));
println!("Database running on {}", db.get_host_port_ipv4(6379));
payjoin_directory::listen_tcp_with_tls(port, db_host, timeout, local_cert_key).await
}

pub async fn init_ohttp_relay(
port: u16,
gateway_origin: http::Uri,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
ohttp_relay::listen_tcp(port, gateway_origin).await
}

// generates or gets 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 find_free_port() -> u16 {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
listener.local_addr().unwrap().port()
}
1 change: 1 addition & 0 deletions payjoin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ serde_json = "1.0.108"
bitcoind = { version = "0.36.0", features = ["0_21_2"] }
http = "1"
payjoin-directory = { path = "../payjoin-directory", features = ["_danger-local-https"] }
payjoin-test-utils = { path = "../payjoin-test-utils" }
ohttp-relay = "0.0.8"
once_cell = "1"
rcgen = { version = "0.11" }
Expand Down
Loading
Loading