diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 7775ae40..f17ffe01 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -74,6 +74,10 @@ jobs: run: forge install working-directory: ./contracts/evm - - name: Test + - name: Go Integration Test run: make tests-integration + - name: Rust Integration Test + run: cargo test --features it-tests + working-directory: ./tests/e2e/e2e_tests + diff --git a/Cargo.lock b/Cargo.lock index d4e162bc..7833256b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2228,6 +2228,56 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bollard" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41711ad46fda47cd701f6908e59d1bd6b9a2b7464c0d0aeab95c6d37096ff8a" +dependencies = [ + "base64 0.22.1", + "bollard-stubs", + "bytes", + "futures-core", + "futures-util", + "hex", + "home", + "http 1.1.0", + "http-body-util", + "hyper 1.4.1", + "hyper-named-pipe", + "hyper-rustls", + "hyper-util", + "hyperlocal", + "log", + "pin-project-lite", + "rustls", + "rustls-native-certs 0.7.3", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "serde_urlencoded", + "thiserror", + "tokio", + "tokio-util", + "tower-service", + "url", + "winapi", +] + +[[package]] +name = "bollard-stubs" +version = "1.45.0-rc.26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7c5415e3a6bc6d3e99eff6268e488fd4ee25e7b28c10f08fa6760bd9de16e4" +dependencies = [ + "serde", + "serde_repr", + "serde_with", +] + [[package]] name = "borsh" version = "0.9.3" @@ -3347,6 +3397,17 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "docker_credential" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31951f49556e34d90ed28342e1df7e1cb7a229c4cab0aecc627b5d91edd41d07" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + [[package]] name = "doctest-file" version = "1.0.0" @@ -3418,6 +3479,16 @@ dependencies = [ "memmap2", ] +[[package]] +name = "e2e_tests" +version = "0.0.1" +dependencies = [ + "eyre", + "testcontainers", + "testcontainers-modules", + "tokio", +] + [[package]] name = "easy-ext" version = "0.2.9" @@ -3580,6 +3651,17 @@ dependencies = [ "libc", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if 1.0.0", + "home", + "windows-sys 0.48.0", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -3675,6 +3757,18 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "libredox", + "windows-sys 0.59.0", +] + [[package]] name = "finite-wasm" version = "0.5.0" @@ -4309,6 +4403,21 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-named-pipe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" +dependencies = [ + "hex", + "hyper 1.4.1", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", +] + [[package]] name = "hyper-rustls" version = "0.27.3" @@ -4400,6 +4509,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "hyperlocal" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" +dependencies = [ + "hex", + "http-body-util", + "hyper 1.4.1", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -4816,6 +4940,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", + "redox_syscall 0.5.7", ] [[package]] @@ -6829,6 +6954,31 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "parse-display" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" +dependencies = [ + "parse-display-derive", + "regex", + "regex-syntax 0.8.5", +] + +[[package]] +name = "parse-display-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "regex-syntax 0.8.5", + "structmeta", + "syn 2.0.79", +] + [[package]] name = "paste" version = "1.0.15" @@ -7607,6 +7757,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.7" @@ -8794,6 +8953,29 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "structmeta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" +dependencies = [ + "proc-macro2", + "quote", + "structmeta-derive", + "syn 2.0.79", +] + +[[package]] +name = "structmeta-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "strum" version = "0.24.1" @@ -9035,6 +9217,44 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "testcontainers" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f40cc2bd72e17f328faf8ca7687fe337e61bccd8acf9674fa78dd3792b045e1" +dependencies = [ + "async-trait", + "bollard", + "bollard-stubs", + "bytes", + "docker_credential", + "either", + "etcetera", + "futures", + "log", + "memchr", + "parse-display", + "pin-project-lite", + "serde", + "serde_json", + "serde_with", + "thiserror", + "tokio", + "tokio-stream", + "tokio-tar", + "tokio-util", + "url", +] + +[[package]] +name = "testcontainers-modules" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064a2677e164cad39ef3c1abddb044d5a25c49d27005804563d8c4227aac8bd0" +dependencies = [ + "testcontainers", +] + [[package]] name = "thiserror" version = "1.0.64" @@ -9267,6 +9487,21 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "tokio-tar" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75" +dependencies = [ + "filetime", + "futures-core", + "libc", + "redox_syscall 0.3.5", + "tokio", + "tokio-stream", + "xattr", +] + [[package]] name = "tokio-tungstenite" version = "0.24.0" @@ -9691,6 +9926,7 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -10698,6 +10934,17 @@ dependencies = [ "time", ] +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys 0.4.14", + "rustix 0.38.37", +] + [[package]] name = "xml-rs" version = "0.8.22" diff --git a/Cargo.toml b/Cargo.toml index 6667c961..0616a226 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "indexer", "contracts/evm/test/ffi/bls-utils", "offchain", + "tests/e2e/e2e_tests", ] [workspace.package] diff --git a/tests/e2e/e2e_tests/Cargo.toml b/tests/e2e/e2e_tests/Cargo.toml new file mode 100644 index 00000000..70b888c6 --- /dev/null +++ b/tests/e2e/e2e_tests/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "e2e_tests" +version.workspace = true +edition.workspace = true +repository.workspace = true + +[features] +it-tests = [] + +[dependencies] +eyre = {workspace = true} +tokio = {workspace = true} +testcontainers = "0.23.1" +testcontainers-modules = { version = "0.11.4", features = ["rabbitmq", "blocking"] } diff --git a/tests/e2e/e2e_tests/src/lib.rs b/tests/e2e/e2e_tests/src/lib.rs new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e/e2e_tests/tests/containers.rs b/tests/e2e/e2e_tests/tests/containers.rs new file mode 100644 index 00000000..18656180 --- /dev/null +++ b/tests/e2e/e2e_tests/tests/containers.rs @@ -0,0 +1,177 @@ +use std::path::PathBuf; +use testcontainers::core::WaitFor; +use testcontainers::{ContainerRequest, GenericImage, ImageExt}; +use testcontainers_modules::rabbitmq::RabbitMq; +const SHELL_ENTRYPOINT: &str = "sh"; +const ANVIL_ENTRYPOINT: &str = "anvil"; +const NETWORK_NAME: &str = "nffl"; +const ANVIL_STATE_PATH: &str = "../../anvil/data/avs-and-eigenlayer-deployed-anvil-state.json"; +const NEAR_KEYS: &str = "near_cli_keys"; +const CURRENT_DIR: &str = "./"; + +#[cfg(test)] +pub fn rabbitmq() -> ContainerRequest { + RabbitMq::default() + .with_env_var("RABBITMQ_DEFAULT_USER", "guest") + .with_env_var("RABBITMQ_DEFAULT_PASSWORD", "guest") + .with_network(NETWORK_NAME) +} + +#[cfg(test)] +pub fn anvil_node() -> ContainerRequest { + GenericImage::new( + "ghcr.io/foundry-rs/foundry", + "latest@sha256:8b843eb65cc7b155303b316f65d27173c862b37719dc095ef3a2ef27ce8d3c00", + ) + .with_wait_for(WaitFor::message_on_stdout("Listening on 0.0.0.0:8545")) + .with_entrypoint(ANVIL_ENTRYPOINT) + .with_copy_to("/root/.anvil/state.json", PathBuf::from(ANVIL_STATE_PATH)) + .with_cmd(vec![ + "--host", + "0.0.0.0", + "--port", + "8545", + "--chain-id", + "1", + "--block-time", + "5", + "--load-state", + "/root/.anvil/state.json", + ]) + .with_network(NETWORK_NAME) +} + +#[cfg(test)] +pub fn anvil_node_setup() -> ContainerRequest { + GenericImage::new( + "ghcr.io/foundry-rs/foundry", + "latest@sha256:8b843eb65cc7b155303b316f65d27173c862b37719dc095ef3a2ef27ce8d3c00", + ) + .with_entrypoint(SHELL_ENTRYPOINT) + .with_cmd(vec![ + "sh", + "-c", + "cast rpc anvil_setBalance 0xD5A0359da7B310917d7760385516B2426E86ab7f 0x8ac7230489e80000 \\ \ + cast rpc anvil_setBalance 0x9441540E8183d416f2Dc1901AB2034600f17B65a 0x8ac7230489e80000", + ]) + .with_env_var("ETH_RPC_URL", "http://mainnet-anvil:8545") + .with_network(NETWORK_NAME) + // Execute after start + // cast rpc anvil_setBalance 0xD5A0359da7B310917d7760385516B2426E86ab7f 0x8ac7230489e80000 + // cast rpc anvil_setBalance 0x9441540E8183d416f2Dc1901AB2034600f17B65a 0x8ac7230489e80000 +} + +#[cfg(test)] +pub fn anvil_rollup_node(port: i32) -> ContainerRequest { + GenericImage::new( + "ghcr.io/foundry-rs/foundry", + "latest@sha256:8b843eb65cc7b155303b316f65d27173c862b37719dc095ef3a2ef27ce8d3c00", + ) + .with_entrypoint(ANVIL_ENTRYPOINT) + .with_cmd(vec![ + "--host", + "0.0.0.0", + "--port", + port.to_string().as_str(), + "--chain-id", + "2", + "--block-time", + "5", + "--load-state", + "/root/.anvil/state.json", + ]) + .with_copy_to("/root/.anvil/state.json", PathBuf::from(ANVIL_STATE_PATH)) + .with_network(NETWORK_NAME) +} + +#[cfg(test)] +pub fn near_da_deployer(indexer_port: i32) -> ContainerRequest { + GenericImage::new("node", "16") + .with_entrypoint(SHELL_ENTRYPOINT) + .with_cmd(vec![ + "sh", "-c", + "npm i -g near-cli@3.0.0 \\\ + near create-account da2.test.near --masterAccount test.near \\\ + near deploy da2.test.near /nffl/tests/near/near_da_blob_store.wasm --initFunction new --initArgs {} --masterAccount test.near -f \\\ + near create-account da3.test.near --masterAccount test.near \\\ + near deploy da3.test.near /nffl/tests/near/near_da_blob_store.wasm --initFunction new --initArgs {} --masterAccount test.near -f"]) + .with_env_var("NEAR_ENV", "localhost") + .with_env_var("NEAR_CLI_LOCALNET_NETWORK_ID", "localhost") + .with_env_var("NEAR_CLI_LOCALNET_KEY_PATH", "/near-cli/validator_key.json") + .with_env_var("NEAR_HELPER_ACCOUNT", "near") + .with_env_var("NEAR_NODE_URL", format!("http://nffl-indexer:{indexer_port}").as_str()) + .with_network(NETWORK_NAME) +} + +#[cfg(all(test, target_arch = "x86_64"))] +pub fn rollup_relayer(rollup_node_port: i32) -> ContainerRequest { + GenericImage::new("ghcr.io/nuffle-labs/nffl/relayer", "66dcb37e32e34f552a63c1e638a57dd251846f63") + .with_cmd(vec![ + "--rpc-url", + format!("ws://rollup0-anvil:{rollup_node_port}").as_str(), + "--da-account-id", + "da2.test.near", + "--key-path", + "/root/.near-credentials/localnet/da2.test.near.json", + "--network", + "http://nffl-indexer:3030", + "--metrics-ip-port-address", + "rollup0-relayer:9091", + ]) + .with_copy_to("/root/.near-credentials", PathBuf::from(ANVIL_STATE_PATH)) + .with_network(NETWORK_NAME) +} + +#[cfg(all(test, target_arch = "x86_64"))] +pub fn indexer() -> ContainerRequest { + GenericImage::new( + "ghcr.io/nuffle-labs/nffl/indexer", + "66dcb37e32e34f552a63c1e638a57dd251846f63", + ) + .with_cmd(vec![ + "--rmq-address", + "amqp://rmq:5672", + "--da-contract-ids", + "da2.test.near", + "--da-contract-ids", + "da3.test.near", + "--rollup-ids", + "2", + "--rollup-ids", + "3", + ]) + .with_network(NETWORK_NAME) +} + +#[cfg(all(test, target_arch = "x86_64"))] +pub fn aggregator() -> ContainerRequest { + GenericImage::new( + "ghcr.io/nuffle-labs/nffl/aggregator", + "66dcb37e32e34f552a63c1e638a57dd251846f63", + ) + .with_copy_to("/nffl", PathBuf::from(CURRENT_DIR)) + .with_working_dir("/nffl") + .with_cmd(vec![ + "--config", + "config-files/aggregator-docker-compose.yaml", + "--nffl-deployment", + "contracts/evm/script/output/31337/sffl_avs_deployment_output.json", + "--ecdsa-private-key", + "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6", + ]) + .with_network(NETWORK_NAME) +} + +#[cfg(all(test, target_arch = "x86_64"))] +pub fn operator(config_path: &str) -> ContainerRequest { + GenericImage::new( + "ghcr.io/nuffle-labs/nffl/operator", + "66dcb37e32e34f552a63c1e638a57dd251846f63", + ) + .with_cmd(vec!["--config", config_path]) + .with_copy_to("/nffl", PathBuf::from(CURRENT_DIR)) + .with_working_dir("/nffl") + .with_env_var("OPERATOR_ECDSA_KEY_PASSWORD", "EnJuncq01CiVk9UbuBYl") + .with_env_var("OPERATOR_BLS_KEY_PASSWORD", "fDUMDLmBROwlzzPXyIcy") + .with_network(NETWORK_NAME) +} diff --git a/tests/e2e/e2e_tests/tests/ctr_availability_test.rs b/tests/e2e/e2e_tests/tests/ctr_availability_test.rs new file mode 100644 index 00000000..c4bcd1aa --- /dev/null +++ b/tests/e2e/e2e_tests/tests/ctr_availability_test.rs @@ -0,0 +1,77 @@ +/// End-to-end test suite for verifying container availability +/// +/// # Prerequisites +/// - Docker daemon must be running +/// - Sufficient system resources for multiple containers +/// - Required ports must be available (8546, 3030) +/// +/// # Cleanup +/// Containers are automatically cleaned up after each test, +/// but manual cleanup may be required if tests fail +mod containers; + +use testcontainers::runners::AsyncRunner; +use crate::containers::*; + +#[cfg(all(test, feature = "it-tests"))] +#[tokio::test] +pub async fn check_rabbitmq_available() { + let rabbitmq = rabbitmq(); + rabbitmq.start().await.expect(""); +} + +#[cfg(all(test, feature = "it-tests"))] +#[tokio::test] +pub async fn check_anvil_node_available() { + let anvil_node = anvil_node(); + anvil_node.start().await.expect(""); +} + +#[cfg(all(test, feature = "it-tests"))] +#[tokio::test] +pub async fn check_anvil_node_setup_available() { + let setup = anvil_node_setup(); + setup.start().await.expect(""); +} + +#[cfg(all(test, feature = "it-tests"))] +#[tokio::test] +pub async fn check_anvil_rollup_node_available() { + let rollup_node = anvil_rollup_node(8546); // Example port number + rollup_node.start().await.expect(""); +} + +#[cfg(all(test, feature = "it-tests"))] +#[tokio::test] +pub async fn check_near_da_deployer_available() { + let deployer = near_da_deployer(3030); // Example indexer port number + deployer.start().await.expect(""); +} + +#[cfg(all(test, target_arch = "x86_64", feature = "it-tests"))] +#[tokio::test] +pub async fn check_rollup_relayer_available() { + let relayer = rollup_relayer(8546); // Example rollup node port number + relayer.start().await.expect(""); +} + +#[cfg(all(test, target_arch = "x86_64", feature = "it-tests"))] +#[tokio::test] +pub async fn check_indexer_available() { + let indexer = indexer(); + indexer.start().await.expect(""); +} + +#[cfg(all(test, target_arch = "x86_64", feature = "it-tests"))] +#[tokio::test] +pub async fn check_aggregator_available() { + let aggregator = aggregator(); + aggregator.start().await.expect(""); +} + +#[cfg(all(test, target_arch = "x86_64", feature = "it-tests"))] +#[tokio::test] +pub async fn check_operator_available() { + let operator = operator("../../../config-files/operator1-docker-compose.anvil.yaml"); // Example config path + operator.start().await.expect(""); +}