diff --git a/Cargo.lock b/Cargo.lock index 7e16f158..9d162211 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,9 +104,9 @@ dependencies = [ [[package]] name = "alloy-chains" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e9e31d834fe25fe991b8884e4b9f0e59db4a97d86e05d1464d6899c013cd62" +checksum = "84e0378e959aa6a885897522080a990e80eb317f1e9a222a604492ea50e13096" dependencies = [ "alloy-primitives", "num_enum", @@ -373,13 +373,13 @@ dependencies = [ "derive_more", "foldhash 0.2.0", "hashbrown 0.16.1", - "indexmap 2.13.1", + "indexmap 2.14.0", "itoa", "k256", "keccak-asm", "paste", "proptest", - "rand 0.9.3", + "rand 0.9.4", "rapidhash", "ruint", "rustc-hash", @@ -581,7 +581,7 @@ dependencies = [ "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.13.1", + "indexmap 2.14.0", "proc-macro-error2", "proc-macro2", "quote", @@ -1175,9 +1175,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", "bytes", @@ -1326,9 +1326,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "bitvec" @@ -1398,7 +1398,7 @@ dependencies = [ "log", "num", "pin-project-lite", - "rand 0.9.3", + "rand 0.9.4", "rustls", "rustls-native-certs", "rustls-pki-types", @@ -1592,9 +1592,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.59" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "jobserver", @@ -1855,15 +1855,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "core2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" -dependencies = [ - "memchr", -] - [[package]] name = "cpufeatures" version = "0.2.17" @@ -2187,7 +2178,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -2527,9 +2518,9 @@ dependencies = [ [[package]] name = "ethereum_ssz" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2128a84f7a3850d54ee343334e3392cca61f9f6aa9441eec481b9394b43c238b" +checksum = "368a4a4e4273b0135111fe9464e35465067766a8f664615b5a86338b73864407" dependencies = [ "alloy-primitives", "ethereum_serde_utils", @@ -2607,7 +2598,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb330bbd4cb7a5b9f559427f06f98a4f853a137c8298f3bd3f8ca57663e21986" dependencies = [ "portable-atomic", - "rand 0.9.3", + "rand 0.9.4", "web-time", ] @@ -2999,7 +2990,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.13.1", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -3054,6 +3045,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + [[package]] name = "hashlink" version = "0.9.1" @@ -3136,7 +3133,7 @@ dependencies = [ "idna", "ipnet", "once_cell", - "rand 0.9.3", + "rand 0.9.4", "ring", "socket2 0.5.10", "thiserror 2.0.18", @@ -3159,7 +3156,7 @@ dependencies = [ "moka", "once_cell", "parking_lot", - "rand 0.9.3", + "rand 0.9.4", "resolv-conf", "smallvec", "thiserror 2.0.18", @@ -3284,15 +3281,14 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", @@ -3554,7 +3550,7 @@ dependencies = [ "hyper", "hyper-util", "log", - "rand 0.9.3", + "rand 0.9.4", "tokio", "url", "xmltree", @@ -3593,12 +3589,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -3736,9 +3732,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.94" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ "cfg-if", "futures-util", @@ -3794,9 +3790,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.184" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libgit2-sys" @@ -4530,14 +4526,14 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ "bitflags", "libc", "plain", - "redox_syscall 0.7.3", + "redox_syscall 0.7.4", ] [[package]] @@ -4591,9 +4587,9 @@ dependencies = [ [[package]] name = "lru" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" dependencies = [ "hashbrown 0.16.1", ] @@ -4750,11 +4746,11 @@ dependencies = [ [[package]] name = "multihash" -version = "0.19.3" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" +checksum = "89ace881e3f514092ce9efbcb8f413d0ad9763860b828981c2de51ddc666936c" dependencies = [ - "core2", + "no_std_io2", "serde", "unsigned-varint 0.8.0", ] @@ -4856,6 +4852,15 @@ dependencies = [ "libc", ] +[[package]] +name = "no_std_io2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a3564ce7035b1e4778d8cb6cacebb5d766b5e8fe5a75b9e441e33fb61a872c6" +dependencies = [ + "memchr", +] + [[package]] name = "nohash-hasher" version = "0.2.0" @@ -5046,7 +5051,7 @@ dependencies = [ "eventsource-stream", "futures-core", "http", - "indexmap 2.13.1", + "indexmap 2.14.0", "oas3", "prettyplease", "proc-macro2", @@ -5103,9 +5108,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.76" +version = "0.10.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +checksum = "bfe4646e360ec77dff7dde40ed3d6c5fee52d156ef4a62f53973d38294dad87f" dependencies = [ "bitflags", "cfg-if", @@ -5135,9 +5140,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.112" +version = "0.9.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +checksum = "ad2f2c0eba47118757e4c6d2bff2838f3e0523380021356e7875e858372ce644" dependencies = [ "cc", "libc", @@ -5319,7 +5324,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", "hashbrown 0.15.5", - "indexmap 2.13.1", + "indexmap 2.14.0", ] [[package]] @@ -5366,9 +5371,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plain" @@ -5995,7 +6000,7 @@ dependencies = [ "bit-vec", "bitflags", "num-traits", - "rand 0.9.3", + "rand 0.9.4", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", @@ -6156,7 +6161,7 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.3", + "rand 0.9.4", "ring", "rustc-hash", "rustls", @@ -6236,9 +6241,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -6332,9 +6337,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -6374,9 +6379,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" dependencies = [ "bitflags", ] @@ -6589,7 +6594,7 @@ dependencies = [ "primitive-types", "proptest", "rand 0.8.5", - "rand 0.9.3", + "rand 0.9.4", "rlp", "ruint-macro", "serde_core", @@ -6657,9 +6662,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ "aws-lc-rs", "log", @@ -6722,9 +6727,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" dependencies = [ "aws-lc-rs", "ring", @@ -6974,7 +6979,7 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -7035,7 +7040,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.13.1", + "indexmap 2.14.0", "schemars 0.9.0", "schemars 1.2.1", "serde_core", @@ -7062,7 +7067,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "itoa", "ryu", "serde", @@ -7628,9 +7633,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.51.0" +version = "1.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" +checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776" dependencies = [ "bytes", "libc", @@ -7735,7 +7740,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "serde", "serde_spanned", "toml_datetime 0.6.11", @@ -7749,7 +7754,7 @@ version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "winnow 1.0.1", @@ -7818,7 +7823,7 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.13.1", + "indexmap 2.14.0", "pin-project-lite", "slab", "sync_wrapper", @@ -8311,9 +8316,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -8324,9 +8329,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.67" +version = "0.4.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" dependencies = [ "js-sys", "wasm-bindgen", @@ -8334,9 +8339,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8344,9 +8349,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", @@ -8357,9 +8362,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] @@ -8381,7 +8386,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.1", + "indexmap 2.14.0", "wasm-encoder", "wasmparser", ] @@ -8407,7 +8412,7 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags", "hashbrown 0.15.5", - "indexmap 2.13.1", + "indexmap 2.14.0", "semver 1.0.28", ] @@ -8427,9 +8432,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.94" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" dependencies = [ "js-sys", "wasm-bindgen", @@ -8974,7 +8979,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap 2.13.1", + "indexmap 2.14.0", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -9005,7 +9010,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags", - "indexmap 2.13.1", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -9024,7 +9029,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.1", + "indexmap 2.14.0", "log", "semver 1.0.28", "serde", @@ -9129,7 +9134,7 @@ dependencies = [ "nohash-hasher", "parking_lot", "pin-project", - "rand 0.9.3", + "rand 0.9.4", "static_assertions", "web-time", ] diff --git a/crates/cli/src/commands/test/mod.rs b/crates/cli/src/commands/test/mod.rs index 0bcb1d08..84e7dd1a 100644 --- a/crates/cli/src/commands/test/mod.rs +++ b/crates/cli/src/commands/test/mod.rs @@ -69,18 +69,13 @@ pub struct TestConfigArgs { } /// Lists available test case names for a given test category. -/// TODO: Fill with enums TestCases of each category fn list_test_cases(category: TestCategory) -> Vec { // Returns available test case names for each category. match category { - TestCategory::Validator => { - // From validator::supported_validator_test_cases() - vec![ - "Ping".to_string(), - "PingMeasure".to_string(), - "PingLoad".to_string(), - ] - } + TestCategory::Validator => validator::ValidatorTestCase::all() + .iter() + .map(|tc| tc.to_string()) + .collect(), TestCategory::Beacon => beacon::test_case_names(), TestCategory::Mev => { vec![ diff --git a/crates/cli/src/commands/test/validator.rs b/crates/cli/src/commands/test/validator.rs index 29254403..e727cae3 100644 --- a/crates/cli/src/commands/test/validator.rs +++ b/crates/cli/src/commands/test/validator.rs @@ -1,9 +1,61 @@ //! Validator client connectivity tests. -use super::{TestConfigArgs, helpers::TestCategoryResult}; -use crate::error::Result; +use std::{fmt, io::Write, time::Duration}; + use clap::Args; -use std::{io::Write, time::Duration}; +use rand::Rng; +use tokio::{ + net::TcpStream, + sync::mpsc, + time::{Instant, timeout}, +}; +use tokio_util::sync::CancellationToken; + +use super::{ + AllCategoriesResult, TestCategory, TestCategoryResult, TestConfigArgs, TestResult, TestVerdict, +}; +use crate::{ + duration::Duration as CliDuration, + error::{CliError, Result}, +}; + +// Thresholds (from Go implementation) +const THRESHOLD_MEASURE_AVG: Duration = Duration::from_millis(50); +const THRESHOLD_MEASURE_POOR: Duration = Duration::from_millis(240); +const THRESHOLD_LOAD_AVG: Duration = Duration::from_millis(50); +const THRESHOLD_LOAD_POOR: Duration = Duration::from_millis(240); + +/// Validator test cases. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ValidatorTestCase { + /// TCP connectivity check. + Ping, + /// TCP round-trip time measurement. + PingMeasure, + /// Sustained TCP load test. + PingLoad, +} + +impl ValidatorTestCase { + /// Returns all validator test cases. + pub fn all() -> &'static [ValidatorTestCase] { + &[ + ValidatorTestCase::Ping, + ValidatorTestCase::PingMeasure, + ValidatorTestCase::PingLoad, + ] + } +} + +impl fmt::Display for ValidatorTestCase { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + ValidatorTestCase::Ping => "Ping", + ValidatorTestCase::PingMeasure => "PingMeasure", + ValidatorTestCase::PingLoad => "PingLoad", + }) + } +} /// Arguments for the validator test command. #[derive(Args, Clone, Debug)] @@ -30,10 +82,243 @@ pub struct TestValidatorArgs { } /// Runs the validator client tests. -pub async fn run(_args: TestValidatorArgs, _writer: &mut dyn Write) -> Result { - // TODO: Implement validator tests - // - Ping - // - PingMeasure - // - PingLoad - unimplemented!("validator test not yet implemented") +pub async fn run( + args: TestValidatorArgs, + writer: &mut dyn Write, + ct: CancellationToken, +) -> Result { + pluto_tracing::init( + &pluto_tracing::TracingConfig::builder() + .with_default_console() + .build(), + ) + .expect("Failed to initialize tracing"); + + tracing::info!("Starting validator client test"); + + // Get and filter test cases + let queued_tests: Vec = if let Some(ref filter) = args.test_config.test_cases + { + ValidatorTestCase::all() + .iter() + .filter(|tc| filter.contains(&tc.to_string())) + .copied() + .collect() + } else { + ValidatorTestCase::all().to_vec() + }; + + if queued_tests.is_empty() { + return Err(CliError::TestCaseNotSupported); + } + + let start_time = Instant::now(); + let test_results = run_tests_with_timeout(&args, &queued_tests, ct).await; + let elapsed = start_time.elapsed(); + + let score = super::calculate_score(&test_results); + + let mut res = TestCategoryResult::new(TestCategory::Validator); + res.targets.insert(args.api_address.clone(), test_results); + res.execution_time = Some(CliDuration::new(elapsed)); + res.score = Some(score); + + if !args.test_config.quiet { + super::write_result_to_writer(&res, writer)?; + } + + if !args.test_config.output_json.is_empty() { + super::write_result_to_file(&res, args.test_config.output_json.as_ref()).await?; + } + + if args.test_config.publish { + let all = AllCategoriesResult { + validator: Some(res.clone()), + ..Default::default() + }; + super::publish_result_to_obol_api( + all, + &args.test_config.publish_addr, + &args.test_config.publish_private_key_file, + ) + .await?; + } + + Ok(res) +} + +/// Runs tests with timeout, keeping completed tests on timeout. +async fn run_tests_with_timeout( + args: &TestValidatorArgs, + tests: &[ValidatorTestCase], + ct: CancellationToken, +) -> Vec { + let mut results = Vec::new(); + let start = Instant::now(); + + for &test_case in tests { + let remaining = args.test_config.timeout.saturating_sub(start.elapsed()); + + tokio::select! { + result = run_single_test(args, test_case) => { + results.push(result); + } + _ = tokio::time::sleep(remaining) => { + results.push( + TestResult::new(test_case.to_string()) + .fail(CliError::TimeoutInterrupted), + ); + break; + } + _ = ct.cancelled() => { + results.push( + TestResult::new(test_case.to_string()) + .fail(CliError::TimeoutInterrupted), + ); + break; + } + } + } + + results +} + +/// Runs a single test case. +async fn run_single_test(args: &TestValidatorArgs, test_case: ValidatorTestCase) -> TestResult { + match test_case { + ValidatorTestCase::Ping => ping_test(args).await, + ValidatorTestCase::PingMeasure => ping_measure_test(args).await, + ValidatorTestCase::PingLoad => ping_load_test(args).await, + } +} + +async fn ping_test(args: &TestValidatorArgs) -> TestResult { + let mut result = TestResult::new(ValidatorTestCase::Ping.to_string()); + + match timeout( + Duration::from_secs(1), + TcpStream::connect(&args.api_address), + ) + .await + { + Ok(Ok(_conn)) => { + result.verdict = TestVerdict::Ok; + } + Ok(Err(e)) => { + return result.fail(e); + } + Err(_) => { + return result.fail(std::io::Error::new( + std::io::ErrorKind::TimedOut, + "connection timeout", + )); + } + } + + result +} + +async fn ping_measure_test(args: &TestValidatorArgs) -> TestResult { + let mut result = TestResult::new(ValidatorTestCase::PingMeasure.to_string()); + let before = Instant::now(); + + match timeout( + Duration::from_secs(1), + TcpStream::connect(&args.api_address), + ) + .await + { + Ok(Ok(_conn)) => { + let rtt = before.elapsed(); + result = + super::evaluate_rtt(rtt, result, THRESHOLD_MEASURE_AVG, THRESHOLD_MEASURE_POOR); + } + Ok(Err(e)) => { + return result.fail(e); + } + Err(_) => { + return result.fail(std::io::Error::new( + std::io::ErrorKind::TimedOut, + "connection timeout", + )); + } + } + + result +} + +async fn ping_load_test(args: &TestValidatorArgs) -> TestResult { + tracing::info!( + duration = ?args.load_test_duration, + target = %args.api_address, + "Running ping load tests..." + ); + + let mut result = TestResult::new(ValidatorTestCase::PingLoad.to_string()); + let (tx, mut rx) = mpsc::channel::(i16::MAX as usize); + let address = args.api_address.clone(); + let duration = args.load_test_duration; + + { + let start = Instant::now(); + let mut interval = tokio::time::interval(Duration::from_secs(1)); + let mut workers = tokio::task::JoinSet::new(); + + interval.tick().await; + while start.elapsed() < duration { + interval.tick().await; + + let tx = tx.clone(); + let addr = address.clone(); + let remaining = duration.saturating_sub(start.elapsed()); + + workers.spawn(ping_continuously(addr, tx, remaining)); + } + + // Drop the scheduler's clone so only workers hold senders + drop(tx); + + // Wait for all spawned ping workers to finish + workers.join_all().await; + } + + tracing::info!(target = %args.api_address, "Ping load tests finished"); + + // All senders dropped, collect all RTTs + rx.close(); + let mut rtts = Vec::new(); + while let Some(rtt) = rx.recv().await { + rtts.push(rtt); + } + + result = super::evaluate_highest_rtt(rtts, result, THRESHOLD_LOAD_AVG, THRESHOLD_LOAD_POOR); + + result +} + +async fn ping_continuously( + address: impl AsRef, + tx: mpsc::Sender, + max_duration: Duration, +) { + let address = address.as_ref(); + let start = Instant::now(); + + while start.elapsed() < max_duration { + let before = Instant::now(); + + match timeout(Duration::from_secs(1), TcpStream::connect(address)).await { + Ok(Ok(_conn)) => { + let rtt = before.elapsed(); + if tx.send(rtt).await.is_err() { + return; + } + } + Ok(Err(_)) | Err(_) => { + return; + } + } + let sleep_ms = rand::thread_rng().gen_range(0..100); + tokio::time::sleep(Duration::from_millis(sleep_ms)).await; + } } diff --git a/crates/cli/src/error.rs b/crates/cli/src/error.rs index d59ac1b9..d7163982 100644 --- a/crates/cli/src/error.rs +++ b/crates/cli/src/error.rs @@ -64,11 +64,11 @@ pub enum CliError { /// Test timeout or interrupted. #[error("timeout/interrupted")] - _TimeoutInterrupted, + TimeoutInterrupted, /// Test case not supported. #[error("test case not supported")] - _TestCaseNotSupported, + TestCaseNotSupported, /// Relay P2P error. #[error("Relay P2P error: {0}")] diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 65551bc0..74387402 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -72,7 +72,7 @@ async fn run() -> std::result::Result<(), CliError> { .map(|_| ()) } TestCommands::Validator(args) => { - commands::test::validator::run(args, &mut stdout) + commands::test::validator::run(args, &mut stdout, ct.clone()) .await .map(|_| ()) }