diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5b669ebc0..a2f63deb4 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -40,6 +40,10 @@ jobs: cargo build --manifest-path=roles/Cargo.toml cargo build --manifest-path=utils/Cargo.toml + - name: Roles Integration Tests + run: | + cargo test --manifest-path=roles/Cargo.toml --verbose --test '*' -- --nocapture + - name: Run sv1-client-and-server example run: | cargo run --manifest-path=examples/sv1-client-and-server/Cargo.toml --bin client_and_server -- 60 diff --git a/roles/Cargo.lock b/roles/Cargo.lock index 5ac551a15..19c49ee8b 100644 --- a/roles/Cargo.lock +++ b/roles/Cargo.lock @@ -1359,6 +1359,7 @@ dependencies = [ "once_cell", "pool_sv2", "tar", + "tokio", ] [[package]] diff --git a/roles/tests-integration/Cargo.toml b/roles/tests-integration/Cargo.toml index 20a38fa8f..795dd4c81 100644 --- a/roles/tests-integration/Cargo.toml +++ b/roles/tests-integration/Cargo.toml @@ -11,6 +11,7 @@ once_cell = "1.20.0" tar = "0.4.41" pool_sv2 = { version = "0.1.0", path = "../pool" } key-utils = { version = "1.0.0", path = "../../utils/key-utils" } +tokio = { version = "1.0.1", features = ["full"] } [lib] path = "tests/common/mod.rs" diff --git a/roles/tests-integration/tests/common/mod.rs b/roles/tests-integration/tests/common/mod.rs index cb47e6f1c..a32f0d531 100644 --- a/roles/tests-integration/tests/common/mod.rs +++ b/roles/tests-integration/tests/common/mod.rs @@ -1,18 +1,20 @@ use bitcoind::{bitcoincore_rpc::RpcApi, BitcoinD, Conf}; use flate2::read::GzDecoder; use key_utils::{Secp256k1PublicKey, Secp256k1SecretKey}; +use once_cell::sync::Lazy; use pool_sv2::PoolSv2; use std::{ + collections::HashSet, env, fs::{create_dir_all, File}, io::{BufReader, Read}, - net::TcpListener, + net::{SocketAddr, TcpListener}, path::{Path, PathBuf}, + str::FromStr, + sync::Mutex, + time::Duration, }; use tar::Archive; -use once_cell::sync::Lazy; -use std::collections::HashSet; -use std::sync::Mutex; // prevents get_available_port from ever returning the same port twice static UNIQUE_PORTS: Lazy>> = Lazy::new(|| Mutex::new(HashSet::new())); @@ -163,10 +165,7 @@ impl TemplateProvider { } pub fn is_port_open(address: std::net::SocketAddr) -> bool { - match TcpListener::bind(address) { - Ok(_) => false, - Err(_) => true, - } + TcpListener::bind(address).is_err() } pub fn get_available_port() -> u16 { @@ -186,14 +185,17 @@ pub fn get_available_port() -> u16 { } #[derive(Debug)] -pub struct TestPoolSv2; +pub struct TestPoolSv2 { + pub pool: PoolSv2, + pub pool_port: u16, +} impl TestPoolSv2 { pub fn new( listening_address: std::net::SocketAddr, coinbase_outputs: Option>, template_provider_address: Option, - ) -> PoolSv2 { + ) -> Self { use pool_sv2::mining_pool::{CoinbaseOutput, Configuration}; let authority_public_key = Secp256k1PublicKey::try_from( "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72".to_string(), @@ -234,6 +236,59 @@ impl TestPoolSv2 { coinbase_outputs, ); let pool = PoolSv2::new(config); - pool + + Self { + pool, + pool_port: listening_address.port(), + } + } +} + +pub async fn start_template_provider() -> (TemplateProvider, u16) { + let template_provider_port = get_available_port(); + let template_provider = TemplateProvider::start(template_provider_port); + template_provider.generate_blocks(16); + (template_provider, template_provider_port) +} + +pub async fn setup_poolsv2( + pool_listening_address: Option, + pool_coinbase_outputs: Option>, + template_provider_port: u16, +) -> (TestPoolSv2, u16) { + let pool_port = get_available_port(); + let pool_listening_address = pool_listening_address + .unwrap_or(SocketAddr::from_str(&format!("127.0.0.1:{}", pool_port)).unwrap()); + let is_pool_port_open = is_port_open(pool_listening_address); + assert_eq!(is_pool_port_open, false); + let pool = TestPoolSv2::new( + pool_listening_address, + pool_coinbase_outputs, + Some(SocketAddr::from_str(&format!("127.0.0.1:{}", template_provider_port)).unwrap()), + ); + (pool, pool_port) +} + +pub async fn start_template_provider_and_pool() -> Result<(), ()> { + let (template_provider, template_provider_port) = start_template_provider().await; + let (test_pool, pool_port) = setup_poolsv2(None, None, template_provider_port).await; + let pool = test_pool.pool; + let state = pool.state().await.safe_lock(|s| s.clone()).unwrap(); + assert_eq!(state, pool_sv2::PoolState::Initial); + let _pool = pool.clone(); + tokio::task::spawn(async move { + assert!(_pool.start().await.is_ok()); + }); + // Wait for the pool to start. + tokio::time::sleep(Duration::from_secs(1)).await; + let pool_listening_address = SocketAddr::from_str(&format!("127.0.0.1:{}", pool_port)).unwrap(); + loop { + if is_port_open(pool_listening_address) { + break; + } } + let state = pool.state().await.safe_lock(|s| s.clone()).unwrap(); + assert_eq!(state, pool_sv2::PoolState::Running); + template_provider.stop(); + Ok(()) } diff --git a/roles/tests-integration/tests/pool_integration.rs b/roles/tests-integration/tests/pool_integration.rs new file mode 100644 index 000000000..39b7919bc --- /dev/null +++ b/roles/tests-integration/tests/pool_integration.rs @@ -0,0 +1,36 @@ +use std::{net::SocketAddr, str::FromStr}; + +use crate::common::{get_available_port, is_port_open}; + +mod common; + +#[tokio::test] +async fn success_pool_template_provider_connection() { + assert!(common::start_template_provider_and_pool().await.is_ok()); +} + +#[tokio::test] +async fn pool_bad_coinbase_output() { + let (template_provider, template_provider_port) = common::start_template_provider().await; + let pool_port = get_available_port(); + let pool_listening_address = SocketAddr::from_str(&format!("127.0.0.1:{}", pool_port)).unwrap(); + let is_pool_port_open = is_port_open(pool_listening_address); + assert_eq!(is_pool_port_open, false); + let invalid_coinbase_output = vec![pool_sv2::mining_pool::CoinbaseOutput::new( + "P2PK".to_string(), + "04466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276728176c3c6431f8eeda4538dc37c865e2784f3a9e77d044f33e407797e1278".to_string(), + )]; + let (test_pool, _pool_port) = common::setup_poolsv2( + Some(pool_listening_address), + Some(invalid_coinbase_output), + template_provider_port, + ) + .await; + let pool = test_pool.pool; + let state = pool.state().await.safe_lock(|s| s.clone()).unwrap(); + assert_eq!(state, pool_sv2::PoolState::Initial); + assert!(pool.start().await.is_err()); + let state = pool.state().await.safe_lock(|s| s.clone()).unwrap(); + assert_eq!(state, pool_sv2::PoolState::Initial); + template_provider.stop(); +}