From 779a91eb17c03fde556c0c50cb5dd5dfb932ceb9 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 12:54:32 -0600 Subject: [PATCH 01/19] chore: rename runner/primitives crates and move reth-rpc to shared --- Cargo.lock | 238 +++++++++--------- Cargo.toml | 16 +- Justfile | 2 +- bin/node/Cargo.toml | 8 +- bin/node/src/cli.rs | 4 +- bin/node/src/main.rs | 6 +- .../Cargo.toml | 2 +- .../README.md | 4 +- .../src/config.rs | 0 .../src/extension.rs | 0 .../src/lib.rs | 0 .../src/types.rs | 0 .../{runner => client-runner}/Cargo.toml | 8 +- .../{runner => client-runner}/README.md | 6 +- .../{runner => client-runner}/src/config.rs | 6 +- .../{runner => client-runner}/src/context.rs | 0 .../{runner => client-runner}/src/handle.rs | 0 .../{runner => client-runner}/src/lib.rs | 0 .../{runner => client-runner}/src/runner.rs | 2 +- crates/client/flashblocks/Cargo.toml | 6 +- .../flashblocks/benches/pending_state.rs | 4 +- crates/client/flashblocks/src/extension.rs | 2 +- .../client/flashblocks/src/rpc_extension.rs | 2 +- crates/client/flashblocks/tests/README.md | 2 +- .../client/flashblocks/tests/eip7702_tests.rs | 2 +- .../flashblocks/tests/eth_call_erc20.rs | 2 +- .../flashblocks/tests/flashblocks_rpc.rs | 2 +- crates/client/flashblocks/tests/state.rs | 4 +- crates/client/metering/Cargo.toml | 6 +- crates/client/metering/README.md | 2 +- crates/client/metering/src/extension.rs | 2 +- crates/client/metering/tests/meter.rs | 4 +- crates/client/metering/tests/meter_block.rs | 4 +- crates/client/metering/tests/meter_rpc.rs | 4 +- crates/client/test-utils/Cargo.toml | 4 +- crates/client/test-utils/README.md | 20 +- crates/client/test-utils/src/node.rs | 4 +- crates/client/txpool/Cargo.toml | 2 +- crates/client/txpool/README.md | 2 +- crates/client/txpool/src/extension.rs | 4 +- crates/client/txpool/src/rpc_extension.rs | 2 +- .../reth-rpc-types/Cargo.toml | 0 .../reth-rpc-types/README.md | 0 .../reth-rpc-types/src/lib.rs | 0 44 files changed, 190 insertions(+), 198 deletions(-) rename crates/client/{primitives => client-primitives}/Cargo.toml (92%) rename crates/client/{primitives => client-primitives}/README.md (92%) rename crates/client/{primitives => client-primitives}/src/config.rs (100%) rename crates/client/{primitives => client-primitives}/src/extension.rs (100%) rename crates/client/{primitives => client-primitives}/src/lib.rs (100%) rename crates/client/{primitives => client-primitives}/src/types.rs (100%) rename crates/client/{runner => client-runner}/Cargo.toml (81%) rename crates/client/{runner => client-runner}/README.md (92%) rename crates/client/{runner => client-runner}/src/config.rs (90%) rename crates/client/{runner => client-runner}/src/context.rs (100%) rename crates/client/{runner => client-runner}/src/handle.rs (100%) rename crates/client/{runner => client-runner}/src/lib.rs (100%) rename crates/client/{runner => client-runner}/src/runner.rs (97%) rename crates/{client => shared}/reth-rpc-types/Cargo.toml (100%) rename crates/{client => shared}/reth-rpc-types/README.md (100%) rename crates/{client => shared}/reth-rpc-types/src/lib.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 31d5857b..4510f5d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,9 +110,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41e46a465e50a339a817070ec23f06eb3fc9fbb8af71612868367b875a9d49e3" +checksum = "8e30ab0d3e3c32976f67fc1a96179989e45a69594af42003a6663332f9b0bb9d" dependencies = [ "alloy-eips", "alloy-primitives", @@ -138,9 +138,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07001b1693af794c7526aab400b42e38075f986ef8fef78841e5ebc745473e56" +checksum = "c20736b1f9d927d875d8777ef0c2250d4c57ea828529a9dbfa2c628db57b911e" dependencies = [ "alloy-consensus", "alloy-eips", @@ -153,9 +153,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef1b07c3ff5bf4fab5b8e6c46190cd40b2f2fd2cd72b5b02527a38125d0bff4" +checksum = "008aba161fce2a0d94956ae09d7d7a09f8fbdf18acbef921809ef126d6cdaf97" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -250,9 +250,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707337efeb051ddbaece17a73eaec5150945a5a5541112f4146508248edc2e40" +checksum = "15b85157b7be31fc4adf6acfefcb0d4308cba5dbd7a8d8e62bcc02ff37d6131a" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -299,9 +299,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ba7afffa225272cf50c62ff04ac574adc7bfa73af2370db556340f26fcff5c" +checksum = "a838301c4e2546c96db1848f18ffe9f722f2fccd9715b83d4bf269a2cf00b5a1" dependencies = [ "alloy-eips", "alloy-primitives", @@ -340,9 +340,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48562f9b4c4e1514cab54af16feaffc18194a38216bbd0c23004ec4667ad696b" +checksum = "60f045b69b5e80b8944b25afe74ae6b974f3044d84b4a7a113da04745b2524cc" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -355,9 +355,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "364a5eaa598437d7a57bcbcb4b7fcb0518e192cf809a19b09b2b5cf73b9ba1cd" +checksum = "2b314ed5bdc7f449c53853125af2db5ac4d3954a9f4b205e7d694f02fc1932d1" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -381,9 +381,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21af5255bd276e528ee625d97033884916e879a1c6edcd5b70a043bd440c0710" +checksum = "5e9762ac5cca67b0f6ab614f7f8314942eead1c8eeef61511ea43a6ff048dbe0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -455,9 +455,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc919fe241f9dd28c4c7f7dcff9e66e550c280bafe3545e1019622e1239db38" +checksum = "ea8f7ca47514e7f552aa9f3f141ab17351332c6637e3bf00462d8e7c5f10f51f" dependencies = [ "alloy-chains", "alloy-consensus", @@ -482,7 +482,7 @@ dependencies = [ "either", "futures", "futures-utils-wasm", - "lru 0.13.0", + "lru 0.16.3", "parking_lot", "pin-project", "reqwest", @@ -497,9 +497,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a0778833917a71a9e0065e0409bfc00cddef55ca962b3453472be38ebe7035" +checksum = "4082778c908aa801a1f9fdc85d758812842ab4b2aaba58e9dbe7626d708ab7e1" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -541,9 +541,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b587e63d8c4af437b0a7830dc12d24cb495e956cc8ecbf93e96d62c9cb55b13" +checksum = "26dd083153d2cb73cce1516f5a3f9c3af74764a2761d901581a355777468bd8f" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -567,9 +567,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3000edc72a300048cf461df94bfa29fc5d7760ddd88ca7d56ea6fc8b28729" +checksum = "8c998214325cfee1fbe61e5abaed3a435f4ca746ac7399b46feb57c364552452" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", @@ -580,9 +580,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-admin" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebb98103316e6f4a1ebc6e71328c2d18426cdd79fc999c44afd9f0f4e9f5edd6" +checksum = "730a38742dc0753f25b8ce7330c2fa88d79f165c5fc2f19f3d35291739c42e83" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -592,9 +592,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1207e852f30297d6918f91df3e76f758fa7b519ea1e49fbd7d961ce796663f9" +checksum = "a2b03d65fcf579fbf17d3aac32271f99e2b562be04097436cd6e766b3e06613b" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -604,9 +604,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ebc96cf29095c10a183fb7106a097fe12ca8dd46733895582da255407f54b29" +checksum = "4b4a6f49d161ef83354d5ba3c8bc83c8ee464cb90182b215551d5c4b846579be" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -615,9 +615,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cea7c1c22628b13b25d31fd63fa5dfa7fac0b0b78f1c89a5068102b653ff65c" +checksum = "3b6654644613f33fd2e6f333f4ce8ad0a26f036c0513699d7bc168bba18d412d" dependencies = [ "alloy-eips", "alloy-primitives", @@ -635,9 +635,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e1a6b13b6f95b80d3ff770998f81e61811264eb1d18b88dfa11c80180acdc1b" +checksum = "467025b916f32645f322a085d0017f2996d0200ac89dd82a4fc2bf0f17b9afa3" dependencies = [ "alloy-primitives", "derive_more", @@ -647,9 +647,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f35af673cc14e89813ab33671d79b6e73fe38788c5f3a8ec3a75476b58225f53" +checksum = "933aaaace9faa6d7efda89472add89a8bfd15270318c47a2be8bb76192c951e2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -667,9 +667,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cc3f354a5079480acca0a6533d1d3838177a03ea494ef0ae8d1679efea88274" +checksum = "11920b16ab7c86052f990dcb4d25312fb2889faf506c4ee13dc946b450536989" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -689,9 +689,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-mev" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10fbd905c35f780926ff0c4c2a74d3ce7d50576cb0e9997dc783ac99c6fd7afb" +checksum = "1826454c2890af6d642bf052909e0162ad7f261d172e56ef2e936d479960699c" dependencies = [ "alloy-consensus", "alloy-eips", @@ -704,9 +704,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d782d80221dfaa5a2f8a7bf277370bdec10e4e8119f5a60d2e2b1adb2e806ca" +checksum = "498375e6a13b6edd04422a13d2b1a6187183e5a3aa14c5907b4c566551248bab" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -718,9 +718,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3076c226bb4365f9c3ac0cd4082ba86208aaa1485cbf664383a90aba7c36b26" +checksum = "6d9123d321ecd70925646eb2c60b1d9b7a965f860fbd717643e2c20fcf85d48d" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -730,9 +730,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a438ce4cd49ec4bc213868c1fe94f2fe103d4c3f22f6a42073db974f9c0962da" +checksum = "d1a0d2d5c64881f3723232eaaf6c2d9f4f88b061c63e87194b2db785ff3aa31f" dependencies = [ "alloy-primitives", "arbitrary", @@ -742,9 +742,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389372d6ae4d62b88c8dca8238e4f7d0a7727b66029eb8a5516a908a03161450" +checksum = "5ea4ac9765e5a7582877ca53688e041fe184880fe75f16edf0945b24a319c710" dependencies = [ "alloy-primitives", "async-trait", @@ -757,9 +757,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69c260e78b9c104c444f8a202f283d5e8c6637e6fa52a83f649ad6aaa0b91fd0" +checksum = "3c9d85b9f7105ab5ce7dae7b0da33cd9d977601a48f759e1c82958978dd1a905" dependencies = [ "alloy-consensus", "alloy-network", @@ -849,9 +849,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f01c27edb3c0926919586a231d99e06284f9239da6044b5682033ef781e1cc62" +checksum = "4e72f5c4ba505ebead6a71144d72f21a70beadfb2d84e0a560a985491ecb71de" dependencies = [ "alloy-json-rpc", "auto_impl", @@ -872,9 +872,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc57657fd3249fc8324cbbc8edbb7d5114af5fbc7c6c32dff944d6b5922f400" +checksum = "400dc298aaabdbd48be05448c4a19eaa38416c446043f3e54561249149269c32" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -887,9 +887,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92a5a36d4ca1261a29dd1d791cd89c21b71d7465211910e43b0862d1c067a211" +checksum = "ba22ff961cf99495ee4fdbaf4623f8d5483d408ca2c6e1b1a54ef438ca87f8dd" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -907,9 +907,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e81effa6a2db6b2152eefb244b4aa6334b1c42819d0eca8d5a91826ec7a9fdba" +checksum = "c38b4472f2bbd96a27f393de9e2f12adca0dc1075fb4d0f7c8f3557c5c600392" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -944,9 +944,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99dac443033e83b14f68fac56e8c27e76421f1253729574197ceccd06598f3ef" +checksum = "e2183706e24173309b0ab0e34d3e53cf3163b71a419803b2b3b0c1fb7ff7a941" dependencies = [ "darling 0.21.3", "proc-macro2", @@ -1380,13 +1380,12 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37" +checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40" dependencies = [ "compression-codecs", "compression-core", - "futures-core", "pin-project-lite", "tokio", ] @@ -1556,33 +1555,35 @@ dependencies = [ ] [[package]] -name = "base-flashtypes" +name = "base-client-primitives" version = "0.2.1" dependencies = [ - "alloy-primitives", - "alloy-rpc-types-engine", - "alloy-rpc-types-eth", - "alloy-serde", - "brotli", - "bytes", - "derive_more", - "rstest", - "serde", - "serde_json", + "eyre", + "reth", + "reth-db", + "reth-optimism-node", ] [[package]] -name = "base-primitives" +name = "base-client-runner" version = "0.2.1" dependencies = [ + "base-client-primitives", + "base-flashblocks", + "base-metering", + "base-txpool", + "derive_more", "eyre", + "futures-util", "reth", "reth-db", + "reth-optimism-chainspec", "reth-optimism-node", + "tracing", ] [[package]] -name = "base-reth-flashblocks" +name = "base-flashblocks" version = "0.2.1" dependencies = [ "alloy-consensus", @@ -1599,9 +1600,9 @@ dependencies = [ "alloy-sol-macro", "alloy-sol-types", "arc-swap", + "base-client-primitives", "base-flashtypes", - "base-primitives", - "base-reth-test-utils", + "base-test-utils", "criterion", "eyre", "futures-util", @@ -1648,7 +1649,23 @@ dependencies = [ ] [[package]] -name = "base-reth-metering" +name = "base-flashtypes" +version = "0.2.1" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "alloy-serde", + "brotli", + "bytes", + "derive_more", + "rstest", + "serde", + "serde_json", +] + +[[package]] +name = "base-metering" version = "0.2.1" dependencies = [ "alloy-consensus", @@ -1657,8 +1674,8 @@ dependencies = [ "alloy-primitives", "alloy-rpc-client", "base-bundles", - "base-primitives", - "base-reth-test-utils", + "base-client-primitives", + "base-test-utils", "eyre", "jsonrpsee", "op-alloy-consensus", @@ -1685,10 +1702,10 @@ name = "base-reth-node" version = "0.2.1" dependencies = [ "base-cli-utils", - "base-primitives", - "base-reth-flashblocks", - "base-reth-metering", - "base-reth-runner", + "base-client-primitives", + "base-client-runner", + "base-flashblocks", + "base-metering", "base-txpool", "clap", "once_cell", @@ -1706,25 +1723,7 @@ dependencies = [ ] [[package]] -name = "base-reth-runner" -version = "0.2.1" -dependencies = [ - "base-primitives", - "base-reth-flashblocks", - "base-reth-metering", - "base-txpool", - "derive_more", - "eyre", - "futures-util", - "reth", - "reth-db", - "reth-optimism-chainspec", - "reth-optimism-node", - "tracing", -] - -[[package]] -name = "base-reth-test-utils" +name = "base-test-utils" version = "0.2.1" dependencies = [ "alloy-consensus", @@ -1740,8 +1739,8 @@ dependencies = [ "alloy-signer-local", "alloy-sol-macro", "alloy-sol-types", + "base-flashblocks", "base-flashtypes", - "base-reth-flashblocks", "chrono", "derive_more", "eyre", @@ -1778,7 +1777,7 @@ name = "base-txpool" version = "0.2.1" dependencies = [ "alloy-primitives", - "base-primitives", + "base-client-primitives", "chrono", "derive_more", "eyre", @@ -2556,9 +2555,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.35" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2" +checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" dependencies = [ "brotli", "compression-core", @@ -3045,15 +3044,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "data-encoding-macro" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47ce6c96ea0102f01122a185683611bd5ac8d99e62bc59dd12e6bda344ee673d" +checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -3061,9 +3060,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" +checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", "syn 2.0.114", @@ -5327,15 +5326,6 @@ dependencies = [ "hashbrown 0.15.5", ] -[[package]] -name = "lru" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" -dependencies = [ - "hashbrown 0.15.5", -] - [[package]] name = "lru" version = "0.16.3" diff --git a/Cargo.toml b/Cargo.toml index 71d3c104..908fea81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,17 +52,17 @@ codegen-units = 1 [workspace.dependencies] # Shared base-access-lists = { path = "crates/shared/access-lists" } -base-flashtypes = { path = "crates/shared/flashtypes" } +base-bundles = { path = "crates/shared/bundles" } base-cli-utils = { path = "crates/shared/cli-utils" } +base-flashtypes = { path = "crates/shared/flashtypes" } +base-reth-rpc-types = { path = "crates/shared/reth-rpc-types" } # Client -base-primitives = { path = "crates/client/primitives" } -base-bundles = { path = "crates/shared/bundles" } -base-reth-metering = { path = "crates/client/metering" } -base-reth-rpc-types = { path = "crates/client/reth-rpc-types" } +base-client-primitives = { path = "crates/client/client-primitives" } +base-metering = { path = "crates/client/metering" } base-txpool = { path = "crates/client/txpool" } -base-reth-runner = { path = "crates/client/runner" } -base-reth-test-utils = { path = "crates/client/test-utils" } -base-reth-flashblocks = { path = "crates/client/flashblocks" } +base-client-runner = { path = "crates/client/client-runner" } +base-test-utils = { path = "crates/client/test-utils" } +base-flashblocks = { path = "crates/client/flashblocks" } # reth reth = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } diff --git a/Justfile b/Justfile index 5ff85189..78bc8207 100644 --- a/Justfile +++ b/Justfile @@ -108,4 +108,4 @@ benches: # Runs flashblocks pending state benchmarks bench-flashblocks: - cargo bench -p base-reth-flashblocks --bench pending_state + cargo bench -p base-flashblocks --bench pending_state diff --git a/bin/node/Cargo.toml b/bin/node/Cargo.toml index 3f22f493..2bbefffc 100644 --- a/bin/node/Cargo.toml +++ b/bin/node/Cargo.toml @@ -15,10 +15,10 @@ workspace = true [dependencies] # internal base-cli-utils.workspace = true -base-primitives.workspace = true -base-reth-flashblocks.workspace = true -base-reth-metering.workspace = true -base-reth-runner.workspace = true +base-client-primitives.workspace = true +base-flashblocks.workspace = true +base-metering.workspace = true +base-client-runner.workspace = true base-txpool.workspace = true # reth diff --git a/bin/node/src/cli.rs b/bin/node/src/cli.rs index 08a0e1a0..a42d83c1 100644 --- a/bin/node/src/cli.rs +++ b/bin/node/src/cli.rs @@ -2,8 +2,8 @@ use std::sync::Arc; -use base_primitives::{FlashblocksConfig, TracingConfig}; -use base_reth_runner::{BaseNodeConfig, RunnerFlashblocksCell}; +use base_client_primitives::{FlashblocksConfig, TracingConfig}; +use base_client_runner::{BaseNodeConfig, RunnerFlashblocksCell}; use once_cell::sync::OnceCell; use reth_optimism_node::args::RollupArgs; diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index f7a247c9..0a3d5f48 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -5,9 +5,9 @@ pub mod cli; -use base_reth_flashblocks::{FlashblocksCanonExtension, FlashblocksRpcExtension}; -use base_reth_metering::MeteringRpcExtension; -use base_reth_runner::BaseNodeRunner; +use base_client_runner::BaseNodeRunner; +use base_flashblocks::{FlashblocksCanonExtension, FlashblocksRpcExtension}; +use base_metering::MeteringRpcExtension; use base_txpool::{TransactionStatusRpcExtension, TransactionTracingExtension}; #[global_allocator] diff --git a/crates/client/primitives/Cargo.toml b/crates/client/client-primitives/Cargo.toml similarity index 92% rename from crates/client/primitives/Cargo.toml rename to crates/client/client-primitives/Cargo.toml index ed12581e..1fc3f0cb 100644 --- a/crates/client/primitives/Cargo.toml +++ b/crates/client/client-primitives/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "base-primitives" +name = "base-client-primitives" description = "Primitive types and traits for Base node runner extensions" version.workspace = true edition.workspace = true diff --git a/crates/client/primitives/README.md b/crates/client/client-primitives/README.md similarity index 92% rename from crates/client/primitives/README.md rename to crates/client/client-primitives/README.md index aadf5ece..f9c8987f 100644 --- a/crates/client/primitives/README.md +++ b/crates/client/client-primitives/README.md @@ -26,7 +26,7 @@ base-primitives = { git = "https://github.com/base/node-reth" } Implement a custom node extension: ```rust,ignore -use base_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; +use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; use eyre::Result; #[derive(Debug)] @@ -51,7 +51,7 @@ impl ConfigurableBaseNodeExtension for MyExtension { Use configuration types: ```rust,ignore -use base_primitives::{FlashblocksConfig, TracingConfig}; +use base_client_primitives::{FlashblocksConfig, TracingConfig}; let flashblocks_config = FlashblocksConfig { websocket_url: "ws://localhost:8545".to_string(), diff --git a/crates/client/primitives/src/config.rs b/crates/client/client-primitives/src/config.rs similarity index 100% rename from crates/client/primitives/src/config.rs rename to crates/client/client-primitives/src/config.rs diff --git a/crates/client/primitives/src/extension.rs b/crates/client/client-primitives/src/extension.rs similarity index 100% rename from crates/client/primitives/src/extension.rs rename to crates/client/client-primitives/src/extension.rs diff --git a/crates/client/primitives/src/lib.rs b/crates/client/client-primitives/src/lib.rs similarity index 100% rename from crates/client/primitives/src/lib.rs rename to crates/client/client-primitives/src/lib.rs diff --git a/crates/client/primitives/src/types.rs b/crates/client/client-primitives/src/types.rs similarity index 100% rename from crates/client/primitives/src/types.rs rename to crates/client/client-primitives/src/types.rs diff --git a/crates/client/runner/Cargo.toml b/crates/client/client-runner/Cargo.toml similarity index 81% rename from crates/client/runner/Cargo.toml rename to crates/client/client-runner/Cargo.toml index c367a166..ced3cfac 100644 --- a/crates/client/runner/Cargo.toml +++ b/crates/client/client-runner/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "base-reth-runner" +name = "base-client-runner" version.workspace = true edition.workspace = true rust-version.workspace = true @@ -13,9 +13,9 @@ workspace = true [dependencies] # internal -base-primitives.workspace = true -base-reth-flashblocks.workspace = true -base-reth-metering.workspace = true +base-client-primitives.workspace = true +base-flashblocks.workspace = true +base-metering.workspace = true base-txpool.workspace = true # reth diff --git a/crates/client/runner/README.md b/crates/client/client-runner/README.md similarity index 92% rename from crates/client/runner/README.md rename to crates/client/client-runner/README.md index 2bd4f7fa..6888dfd3 100644 --- a/crates/client/runner/README.md +++ b/crates/client/client-runner/README.md @@ -1,4 +1,4 @@ -# `base-reth-runner` +# `base-client-runner` CI MIT License @@ -27,13 +27,13 @@ Add the dependency to your `Cargo.toml`: ```toml [dependencies] -base-reth-runner = { git = "https://github.com/base/node-reth" } +base-client-runner = { git = "https://github.com/base/node-reth" } ``` Build and run a Base node: ```rust,ignore -use base_reth_runner::{BaseNodeBuilder, BaseNodeConfig, FlashblocksConfig}; +use base_client_runner::{BaseNodeBuilder, BaseNodeConfig, FlashblocksConfig}; let config = BaseNodeConfig { flashblocks: FlashblocksConfig { diff --git a/crates/client/runner/src/config.rs b/crates/client/client-runner/src/config.rs similarity index 90% rename from crates/client/runner/src/config.rs rename to crates/client/client-runner/src/config.rs index 42f522ae..c6600ccc 100644 --- a/crates/client/runner/src/config.rs +++ b/crates/client/client-runner/src/config.rs @@ -1,8 +1,8 @@ //! Contains the Base node configuration structures. -use base_primitives::{FlashblocksConfig, OpProvider, TracingConfig}; -use base_reth_flashblocks::{FlashblocksCanonConfig, FlashblocksCell, FlashblocksState}; -use base_reth_metering::MeteringRpcConfig; +use base_client_primitives::{FlashblocksConfig, OpProvider, TracingConfig}; +use base_flashblocks::{FlashblocksCanonConfig, FlashblocksCell, FlashblocksState}; +use base_metering::MeteringRpcConfig; use base_txpool::{TransactionStatusRpcConfig, TransactionTracingConfig}; use reth_optimism_node::args::RollupArgs; diff --git a/crates/client/runner/src/context.rs b/crates/client/client-runner/src/context.rs similarity index 100% rename from crates/client/runner/src/context.rs rename to crates/client/client-runner/src/context.rs diff --git a/crates/client/runner/src/handle.rs b/crates/client/client-runner/src/handle.rs similarity index 100% rename from crates/client/runner/src/handle.rs rename to crates/client/client-runner/src/handle.rs diff --git a/crates/client/runner/src/lib.rs b/crates/client/client-runner/src/lib.rs similarity index 100% rename from crates/client/runner/src/lib.rs rename to crates/client/client-runner/src/lib.rs diff --git a/crates/client/runner/src/runner.rs b/crates/client/client-runner/src/runner.rs similarity index 97% rename from crates/client/runner/src/runner.rs rename to crates/client/client-runner/src/runner.rs index a7a9d35f..2500fcd8 100644 --- a/crates/client/runner/src/runner.rs +++ b/crates/client/client-runner/src/runner.rs @@ -1,6 +1,6 @@ //! Contains the [`BaseNodeRunner`], which is responsible for configuring and launching a Base node. -use base_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension}; +use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension}; use eyre::Result; use reth::{ builder::{EngineNodeLauncher, Node, NodeHandleFor, TreeConfig}, diff --git a/crates/client/flashblocks/Cargo.toml b/crates/client/flashblocks/Cargo.toml index 82166911..d95e17ad 100644 --- a/crates/client/flashblocks/Cargo.toml +++ b/crates/client/flashblocks/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "base-reth-flashblocks" +name = "base-flashblocks" description = "Flashblocks State Management" version.workspace = true edition.workspace = true @@ -14,7 +14,7 @@ workspace = true [dependencies] # workspace base-flashtypes.workspace = true -base-primitives.workspace = true +base-client-primitives.workspace = true # reth reth.workspace = true @@ -90,7 +90,7 @@ reth-transaction-pool = { workspace = true, features = ["test-utils"] } tokio-tungstenite.workspace = true tracing-subscriber.workspace = true reth-e2e-test-utils.workspace = true -base-reth-test-utils.workspace = true +base-test-utils.workspace = true serde_json.workspace = true futures-util.workspace = true criterion = { version = "0.5", features = ["async_tokio"] } diff --git a/crates/client/flashblocks/benches/pending_state.rs b/crates/client/flashblocks/benches/pending_state.rs index 7b201576..a38df250 100644 --- a/crates/client/flashblocks/benches/pending_state.rs +++ b/crates/client/flashblocks/benches/pending_state.rs @@ -8,11 +8,11 @@ use std::{ use alloy_eips::{BlockHashOrNumber, Encodable2718}; use alloy_primitives::{Address, B256, BlockNumber, Bytes, U256, bytes, hex::FromHex}; use alloy_rpc_types_engine::PayloadId; +use base_flashblocks::{FlashblocksAPI, FlashblocksReceiver, FlashblocksState}; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_reth_flashblocks::{FlashblocksAPI, FlashblocksReceiver, FlashblocksState}; -use base_reth_test_utils::{LocalNodeProvider, TestAccounts, TestHarness}; +use base_test_utils::{LocalNodeProvider, TestAccounts, TestHarness}; use criterion::{BatchSize, Criterion, Throughput, criterion_group, criterion_main}; use reth::{ chainspec::{ChainSpecProvider, EthChainSpec}, diff --git a/crates/client/flashblocks/src/extension.rs b/crates/client/flashblocks/src/extension.rs index 0f1ea1d3..0ba8c3bd 100644 --- a/crates/client/flashblocks/src/extension.rs +++ b/crates/client/flashblocks/src/extension.rs @@ -3,7 +3,7 @@ use std::sync::Arc; -use base_primitives::{ +use base_client_primitives::{ BaseNodeExtension, ConfigurableBaseNodeExtension, FlashblocksConfig, OpBuilder, OpProvider, }; use futures_util::TryStreamExt; diff --git a/crates/client/flashblocks/src/rpc_extension.rs b/crates/client/flashblocks/src/rpc_extension.rs index e1163e11..fd5fcde7 100644 --- a/crates/client/flashblocks/src/rpc_extension.rs +++ b/crates/client/flashblocks/src/rpc_extension.rs @@ -3,7 +3,7 @@ use std::sync::Arc; -use base_primitives::{ +use base_client_primitives::{ BaseNodeExtension, ConfigurableBaseNodeExtension, FlashblocksConfig, OpBuilder, OpProvider, }; use tracing::info; diff --git a/crates/client/flashblocks/tests/README.md b/crates/client/flashblocks/tests/README.md index d7152a24..3a822c9a 100644 --- a/crates/client/flashblocks/tests/README.md +++ b/crates/client/flashblocks/tests/README.md @@ -1,6 +1,6 @@ ## Flashblocks RPC Integration Tests -The suites under this directory exercise `base-reth-flashblocks-rpc` the same way external +The suites under this directory exercise `base-flashblocks-rpc` the same way external consumers do — by linking against the published library instead of the crate's `cfg(test)` build. Running them from `tests/` ensures: diff --git a/crates/client/flashblocks/tests/eip7702_tests.rs b/crates/client/flashblocks/tests/eip7702_tests.rs index e4ab15c9..1bfc1c55 100644 --- a/crates/client/flashblocks/tests/eip7702_tests.rs +++ b/crates/client/flashblocks/tests/eip7702_tests.rs @@ -11,7 +11,7 @@ use alloy_sol_types::SolCall; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_reth_test_utils::{ +use base_test_utils::{ Account, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, Minimal7702Account, SignerSync, }; use eyre::Result; diff --git a/crates/client/flashblocks/tests/eth_call_erc20.rs b/crates/client/flashblocks/tests/eth_call_erc20.rs index de846fc4..2c62d223 100644 --- a/crates/client/flashblocks/tests/eth_call_erc20.rs +++ b/crates/client/flashblocks/tests/eth_call_erc20.rs @@ -19,7 +19,7 @@ use alloy_sol_types::{SolConstructor, SolValue}; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_reth_test_utils::{ +use base_test_utils::{ FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, MockERC20, TransparentUpgradeableProxy, }; use eyre::Result; diff --git a/crates/client/flashblocks/tests/flashblocks_rpc.rs b/crates/client/flashblocks/tests/flashblocks_rpc.rs index fc8fa418..d589a568 100644 --- a/crates/client/flashblocks/tests/flashblocks_rpc.rs +++ b/crates/client/flashblocks/tests/flashblocks_rpc.rs @@ -14,7 +14,7 @@ use alloy_rpc_types_eth::{TransactionInput, error::EthRpcErrorCode}; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_reth_test_utils::{DoubleCounter, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX}; +use base_test_utils::{DoubleCounter, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX}; use eyre::Result; use futures_util::{SinkExt, StreamExt}; use op_alloy_network::{Optimism, ReceiptResponse, TransactionResponse}; diff --git a/crates/client/flashblocks/tests/state.rs b/crates/client/flashblocks/tests/state.rs index 8f998125..6102952c 100644 --- a/crates/client/flashblocks/tests/state.rs +++ b/crates/client/flashblocks/tests/state.rs @@ -8,11 +8,11 @@ use alloy_primitives::{ Address, B256, BlockNumber, Bytes, U256, hex::FromHex, map::foldhash::HashMap, }; use alloy_rpc_types_engine::PayloadId; +use base_flashblocks::{FlashblocksAPI, FlashblocksState, PendingBlocksAPI}; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_reth_flashblocks::{FlashblocksAPI, FlashblocksState, PendingBlocksAPI}; -use base_reth_test_utils::{ +use base_test_utils::{ FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, LocalNodeProvider, TestAccounts, }; diff --git a/crates/client/metering/Cargo.toml b/crates/client/metering/Cargo.toml index 0c975fca..8bbbed3a 100644 --- a/crates/client/metering/Cargo.toml +++ b/crates/client/metering/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "base-reth-metering" +name = "base-metering" version.workspace = true edition.workspace = true rust-version.workspace = true @@ -14,7 +14,7 @@ workspace = true [dependencies] # workspace base-bundles.workspace = true -base-primitives.workspace = true +base-client-primitives.workspace = true # reth reth.workspace = true @@ -39,7 +39,7 @@ eyre.workspace = true serde.workspace = true [dev-dependencies] -base-reth-test-utils.workspace = true +base-test-utils.workspace = true reth-db = { workspace = true, features = ["test-utils"] } reth-db-common.workspace = true reth-optimism-node.workspace = true diff --git a/crates/client/metering/README.md b/crates/client/metering/README.md index e79d4da8..3d1c679a 100644 --- a/crates/client/metering/README.md +++ b/crates/client/metering/README.md @@ -1,4 +1,4 @@ -# base-reth-metering +# base-metering Metering RPC for Base node. Provides RPC methods for measuring transaction and block execution timing. diff --git a/crates/client/metering/src/extension.rs b/crates/client/metering/src/extension.rs index 578523d1..17771a08 100644 --- a/crates/client/metering/src/extension.rs +++ b/crates/client/metering/src/extension.rs @@ -1,7 +1,7 @@ //! Contains the [`MeteringRpcExtension`] which wires up the metering RPC surface //! on the Base node builder. -use base_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; +use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; use tracing::info; use crate::{MeteringApiImpl, MeteringApiServer}; diff --git a/crates/client/metering/tests/meter.rs b/crates/client/metering/tests/meter.rs index 371be97a..e014691b 100644 --- a/crates/client/metering/tests/meter.rs +++ b/crates/client/metering/tests/meter.rs @@ -7,8 +7,8 @@ use alloy_eips::Encodable2718; use alloy_genesis::GenesisAccount; use alloy_primitives::{Address, B256, Bytes, U256, keccak256}; use base_bundles::{Bundle, ParsedBundle}; -use base_reth_metering::meter_bundle; -use base_reth_test_utils::create_provider_factory; +use base_metering::meter_bundle; +use base_test_utils::create_provider_factory; use eyre::Context; use op_alloy_consensus::OpTxEnvelope; use rand::{SeedableRng, rngs::StdRng}; diff --git a/crates/client/metering/tests/meter_block.rs b/crates/client/metering/tests/meter_block.rs index 42af07b3..0ad45a0d 100644 --- a/crates/client/metering/tests/meter_block.rs +++ b/crates/client/metering/tests/meter_block.rs @@ -5,8 +5,8 @@ use std::sync::Arc; use alloy_consensus::{BlockHeader, Header, crypto::secp256k1::public_key_to_address}; use alloy_genesis::GenesisAccount; use alloy_primitives::{Address, B256, U256}; -use base_reth_metering::meter_block; -use base_reth_test_utils::create_provider_factory; +use base_metering::meter_block; +use base_test_utils::create_provider_factory; use eyre::Context; use rand::{SeedableRng, rngs::StdRng}; use reth::{api::NodeTypesWithDBAdapter, chainspec::EthChainSpec}; diff --git a/crates/client/metering/tests/meter_rpc.rs b/crates/client/metering/tests/meter_rpc.rs index 5ffe598c..dd99a9dc 100644 --- a/crates/client/metering/tests/meter_rpc.rs +++ b/crates/client/metering/tests/meter_rpc.rs @@ -6,8 +6,8 @@ use alloy_eips::Encodable2718; use alloy_primitives::{Bytes, U256, address, b256, bytes}; use alloy_rpc_client::RpcClient; use base_bundles::{Bundle, MeterBundleResponse}; -use base_reth_metering::{MeteringApiImpl, MeteringApiServer}; -use base_reth_test_utils::{init_silenced_tracing, load_genesis}; +use base_metering::{MeteringApiImpl, MeteringApiServer}; +use base_test_utils::{init_silenced_tracing, load_genesis}; use op_alloy_consensus::OpTxEnvelope; use reth::{ args::{DiscoveryArgs, NetworkArgs, RpcServerArgs}, diff --git a/crates/client/test-utils/Cargo.toml b/crates/client/test-utils/Cargo.toml index d66222f9..64f3b529 100644 --- a/crates/client/test-utils/Cargo.toml +++ b/crates/client/test-utils/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "base-reth-test-utils" +name = "base-test-utils" version.workspace = true edition.workspace = true rust-version.workspace = true @@ -14,7 +14,7 @@ workspace = true [dependencies] # Project base-flashtypes.workspace = true -base-reth-flashblocks.workspace = true +base-flashblocks.workspace = true # reth reth.workspace = true diff --git a/crates/client/test-utils/README.md b/crates/client/test-utils/README.md index 2b735f2c..1a425a2a 100644 --- a/crates/client/test-utils/README.md +++ b/crates/client/test-utils/README.md @@ -15,7 +15,7 @@ This crate provides reusable testing utilities for integration tests across the ## Quick Start ```rust,ignore -use base_reth_test_utils::TestHarness; +use base_test_utils::TestHarness; #[tokio::test] async fn test_example() -> eyre::Result<()> { @@ -68,7 +68,7 @@ The framework follows a three-layer architecture: The main entry point for integration tests that only need canonical chain control. Combines node, engine, and accounts into a single interface. ```rust,ignore -use base_reth_test_utils::TestHarness; +use base_test_utils::TestHarness; use alloy_primitives::Bytes; #[tokio::test] @@ -117,7 +117,7 @@ async fn test_harness() -> eyre::Result<()> { In-process Optimism node with Base Sepolia configuration. ```rust,ignore -use base_reth_test_utils::{LocalNode, default_launcher}; +use base_test_utils::{LocalNode, default_launcher}; #[tokio::test] async fn test_node() -> eyre::Result<()> { @@ -140,7 +140,7 @@ async fn test_node() -> eyre::Result<()> { For flashblocks-enabled nodes, use `FlashblocksLocalNode`: ```rust,ignore -use base_reth_test_utils::FlashblocksLocalNode; +use base_test_utils::FlashblocksLocalNode; let node = FlashblocksLocalNode::new().await?; let pending_state = node.flashblocks_state(); @@ -154,7 +154,7 @@ node.send_flashblock(flashblock).await?; Type-safe Engine API client wrapping raw CL operations. ```rust,ignore -use base_reth_test_utils::EngineApi; +use base_test_utils::EngineApi; use alloy_primitives::B256; use op_alloy_rpc_types_engine::OpPayloadAttributes; @@ -179,7 +179,7 @@ let status = engine.new_payload(payload, vec![], parent_root, requests).await?; Hardcoded test accounts with deterministic addresses (Anvil-compatible). ```rust,ignore -use base_reth_test_utils::{TestAccounts, TestHarness}; +use base_test_utils::{TestAccounts, TestHarness}; let accounts = TestAccounts::new(); @@ -210,7 +210,7 @@ Each account includes: Use `FlashblocksHarness` when you need `send_flashblock` and access to the in-memory pending state. ```rust,ignore -use base_reth_test_utils::FlashblocksHarness; +use base_test_utils::FlashblocksHarness; #[tokio::test] async fn test_flashblocks() -> eyre::Result<()> { @@ -234,7 +234,7 @@ Test flashblocks delivery without WebSocket connections by constructing payloads Key constants are exported from the crate root: ```rust,ignore -use base_reth_test_utils::{ +use base_test_utils::{ BASE_CHAIN_ID, // Chain ID for Base Sepolia (84532) BLOCK_TIME_SECONDS, // Base L2 block time (2 seconds) GAS_LIMIT, // Default gas limit (200M) @@ -273,13 +273,13 @@ Add to `dev-dependencies`: ```toml [dev-dependencies] -base-reth-test-utils.workspace = true +base-test-utils.workspace = true ``` Import in tests: ```rust,ignore -use base_reth_test_utils::TestHarness; +use base_test_utils::TestHarness; #[tokio::test] async fn my_test() -> eyre::Result<()> { diff --git a/crates/client/test-utils/src/node.rs b/crates/client/test-utils/src/node.rs index b943abe2..d62c1fa8 100644 --- a/crates/client/test-utils/src/node.rs +++ b/crates/client/test-utils/src/node.rs @@ -10,11 +10,11 @@ use std::{ use alloy_genesis::Genesis; use alloy_provider::RootProvider; use alloy_rpc_client::RpcClient; -use base_flashtypes::Flashblock; -use base_reth_flashblocks::{ +use base_flashblocks::{ EthApiExt, EthApiOverrideServer, EthPubSub, EthPubSubApiServer, FlashblocksReceiver, FlashblocksState, }; +use base_flashtypes::Flashblock; use eyre::Result; use futures_util::Future; use once_cell::sync::OnceCell; diff --git a/crates/client/txpool/Cargo.toml b/crates/client/txpool/Cargo.toml index 5fe839b5..3d7806c6 100644 --- a/crates/client/txpool/Cargo.toml +++ b/crates/client/txpool/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] # workspace -base-primitives.workspace = true +base-client-primitives.workspace = true # reth reth.workspace = true diff --git a/crates/client/txpool/README.md b/crates/client/txpool/README.md index 3dbc4aa0..d115bb60 100644 --- a/crates/client/txpool/README.md +++ b/crates/client/txpool/README.md @@ -27,7 +27,7 @@ cargo run -p node --release -- \ From code, wire the ExEx into the node builder: ```rust,ignore -use base_reth_runner::{TracingConfig, extensions::TransactionTracingExtension}; +use base_client_runner::{TracingConfig, extensions::TransactionTracingExtension}; let tracing = TracingConfig { enabled: true, logs_enabled: true }; let builder = TransactionTracingExtension::new(tracing).apply(builder); diff --git a/crates/client/txpool/src/extension.rs b/crates/client/txpool/src/extension.rs index f8bdce28..e9dab016 100644 --- a/crates/client/txpool/src/extension.rs +++ b/crates/client/txpool/src/extension.rs @@ -1,7 +1,9 @@ //! Contains the [`TransactionTracingExtension`] which wires up the `tracex` //! execution extension on the Base node builder. -use base_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder, TracingConfig}; +use base_client_primitives::{ + BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder, TracingConfig, +}; use crate::tracex_exex; diff --git a/crates/client/txpool/src/rpc_extension.rs b/crates/client/txpool/src/rpc_extension.rs index 3c3270e2..05276225 100644 --- a/crates/client/txpool/src/rpc_extension.rs +++ b/crates/client/txpool/src/rpc_extension.rs @@ -1,7 +1,7 @@ //! Contains the [`TransactionStatusRpcExtension`] which wires up the transaction status //! RPC surface on the Base node builder. -use base_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; +use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; use crate::{TransactionStatusApiImpl, TransactionStatusApiServer}; diff --git a/crates/client/reth-rpc-types/Cargo.toml b/crates/shared/reth-rpc-types/Cargo.toml similarity index 100% rename from crates/client/reth-rpc-types/Cargo.toml rename to crates/shared/reth-rpc-types/Cargo.toml diff --git a/crates/client/reth-rpc-types/README.md b/crates/shared/reth-rpc-types/README.md similarity index 100% rename from crates/client/reth-rpc-types/README.md rename to crates/shared/reth-rpc-types/README.md diff --git a/crates/client/reth-rpc-types/src/lib.rs b/crates/shared/reth-rpc-types/src/lib.rs similarity index 100% rename from crates/client/reth-rpc-types/src/lib.rs rename to crates/shared/reth-rpc-types/src/lib.rs From 2b4da5c45c72db58fe3e0a5096032cf9df5d999c Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 13:09:39 -0600 Subject: [PATCH 02/19] chore: unify extensions to be one per crate --- Cargo.lock | 1 - bin/node/Cargo.toml | 1 - bin/node/src/cli.rs | 3 +- bin/node/src/main.rs | 18 ++-- crates/client/client-primitives/README.md | 28 ++---- crates/client/client-primitives/src/config.rs | 19 ---- crates/client/client-primitives/src/lib.rs | 3 - crates/client/client-runner/README.md | 37 ++++---- crates/client/client-runner/src/config.rs | 26 +++--- crates/client/flashblocks/src/extension.rs | 90 +++++++++++++++---- crates/client/flashblocks/src/lib.rs | 7 +- .../client/flashblocks/src/rpc_extension.rs | 89 ------------------ crates/client/metering/src/extension.rs | 18 ++-- crates/client/metering/src/lib.rs | 2 +- crates/client/txpool/README.md | 7 +- crates/client/txpool/src/extension.rs | 66 +++++++++----- crates/client/txpool/src/lib.rs | 5 +- crates/client/txpool/src/rpc_extension.rs | 50 ----------- 18 files changed, 179 insertions(+), 291 deletions(-) delete mode 100644 crates/client/client-primitives/src/config.rs delete mode 100644 crates/client/flashblocks/src/rpc_extension.rs delete mode 100644 crates/client/txpool/src/rpc_extension.rs diff --git a/Cargo.lock b/Cargo.lock index 4510f5d6..da70177d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1702,7 +1702,6 @@ name = "base-reth-node" version = "0.2.1" dependencies = [ "base-cli-utils", - "base-client-primitives", "base-client-runner", "base-flashblocks", "base-metering", diff --git a/bin/node/Cargo.toml b/bin/node/Cargo.toml index 2bbefffc..afb492c9 100644 --- a/bin/node/Cargo.toml +++ b/bin/node/Cargo.toml @@ -15,7 +15,6 @@ workspace = true [dependencies] # internal base-cli-utils.workspace = true -base-client-primitives.workspace = true base-flashblocks.workspace = true base-metering.workspace = true base-client-runner.workspace = true diff --git a/bin/node/src/cli.rs b/bin/node/src/cli.rs index a42d83c1..7bcd3a34 100644 --- a/bin/node/src/cli.rs +++ b/bin/node/src/cli.rs @@ -2,8 +2,9 @@ use std::sync::Arc; -use base_client_primitives::{FlashblocksConfig, TracingConfig}; use base_client_runner::{BaseNodeConfig, RunnerFlashblocksCell}; +use base_flashblocks::FlashblocksConfig; +use base_txpool::TracingConfig; use once_cell::sync::OnceCell; use reth_optimism_node::args::RollupArgs; diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index 0a3d5f48..c729c98a 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -6,9 +6,9 @@ pub mod cli; use base_client_runner::BaseNodeRunner; -use base_flashblocks::{FlashblocksCanonExtension, FlashblocksRpcExtension}; -use base_metering::MeteringRpcExtension; -use base_txpool::{TransactionStatusRpcExtension, TransactionTracingExtension}; +use base_flashblocks::FlashblocksExtension; +use base_metering::MeteringExtension; +use base_txpool::TxPoolExtension; #[global_allocator] static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator(); @@ -26,14 +26,10 @@ fn main() { cli.run(|builder, args| async move { let mut runner = BaseNodeRunner::new(args); - // ExEx extensions - runner.install_ext::()?; - runner.install_ext::()?; - - // RPC extensions (FlashblocksRpcExtension must be last - uses replace_configured) - runner.install_ext::()?; - runner.install_ext::()?; - runner.install_ext::()?; + // Feature extensions (FlashblocksExtension must be last - uses replace_configured) + runner.install_ext::()?; + runner.install_ext::()?; + runner.install_ext::()?; let handle = runner.run(builder); handle.await diff --git a/crates/client/client-primitives/README.md b/crates/client/client-primitives/README.md index f9c8987f..225e06b4 100644 --- a/crates/client/client-primitives/README.md +++ b/crates/client/client-primitives/README.md @@ -1,26 +1,28 @@ -# `base-primitives` +# `base-client-primitives` CI MIT License -Primitive types and traits for Base node runner extensions. Provides configuration types, extension traits, and type aliases for building modular node extensions. +Primitive types and traits for Base node runner extensions. Provides extension traits and type aliases for building modular node extensions. ## Overview -- **`FlashblocksConfig`**: Configuration for flashblocks streaming, including websocket endpoint and pending block depth. -- **`TracingConfig`**: Configuration for the transaction tracing ExEx, with toggles for enabling tracing and logs. - **`BaseNodeExtension`**: Trait for node builder extensions that can apply additional wiring to the builder. - **`ConfigurableBaseNodeExtension`**: Trait for extensions that can be constructed from a configuration type. - **`OpBuilder`**: Type alias for the OP node builder with launch context. - **`OpProvider`**: Type alias for the blockchain provider instance. +Configuration types are located in their respective feature crates: +- **`FlashblocksConfig`**: in `base-flashblocks` crate +- **`TracingConfig`**: in `base-txpool` crate + ## Usage Add the dependency to your `Cargo.toml`: ```toml [dependencies] -base-primitives = { git = "https://github.com/base/node-reth" } +base-client-primitives = { git = "https://github.com/base/node-reth" } ``` Implement a custom node extension: @@ -47,19 +49,3 @@ impl ConfigurableBaseNodeExtension for MyExtension { } } ``` - -Use configuration types: - -```rust,ignore -use base_client_primitives::{FlashblocksConfig, TracingConfig}; - -let flashblocks_config = FlashblocksConfig { - websocket_url: "ws://localhost:8545".to_string(), - max_pending_blocks_depth: 10, -}; - -let tracing_config = TracingConfig { - enabled: true, - logs_enabled: false, -}; -``` diff --git a/crates/client/client-primitives/src/config.rs b/crates/client/client-primitives/src/config.rs deleted file mode 100644 index 67a102d5..00000000 --- a/crates/client/client-primitives/src/config.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Configuration types for extensions. - -/// Flashblocks-specific configuration knobs. -#[derive(Debug, Clone)] -pub struct FlashblocksConfig { - /// The websocket endpoint that streams flashblock updates. - pub websocket_url: String, - /// Maximum number of pending flashblocks to retain in memory. - pub max_pending_blocks_depth: u64, -} - -/// Transaction tracing toggles. -#[derive(Debug, Clone, Copy)] -pub struct TracingConfig { - /// Enables the transaction tracing ExEx. - pub enabled: bool, - /// Emits `info`-level logs for the tracing ExEx when enabled. - pub logs_enabled: bool, -} diff --git a/crates/client/client-primitives/src/lib.rs b/crates/client/client-primitives/src/lib.rs index 7fcd87c3..8306c677 100644 --- a/crates/client/client-primitives/src/lib.rs +++ b/crates/client/client-primitives/src/lib.rs @@ -3,9 +3,6 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -mod config; -pub use config::{FlashblocksConfig, TracingConfig}; - mod extension; pub use extension::{BaseNodeExtension, ConfigurableBaseNodeExtension}; diff --git a/crates/client/client-runner/README.md b/crates/client/client-runner/README.md index 6888dfd3..816845c1 100644 --- a/crates/client/client-runner/README.md +++ b/crates/client/client-runner/README.md @@ -11,15 +11,14 @@ Base-specific node launcher that wires together the Optimism node components, ex - **`BaseNodeRunner`**: Runs the Base node with all configured extensions. - **`BaseNodeHandle`**: Handle to the running node, providing access to providers and state. - **`BaseNodeConfig`**: Configuration options for the Base node. -- **`FlashblocksConfig`**: Configuration for flashblocks WebSocket subscription. -- **`TracingConfig`**: Configuration for transaction tracing extension. ## Extensions -- **`BaseNodeExtension`**: Core extension trait for adding functionality to the node. -- **`BaseRpcExtension`**: Extension for adding custom RPC methods. -- **`FlashblocksCanonExtension`**: Extension for canonical block reconciliation with flashblocks. -- **`TransactionTracingExtension`**: Extension for transaction lifecycle tracing. +Each feature crate provides a single `BaseNodeExtension` that combines all functionality for that feature: + +- **`FlashblocksExtension`** (from `base-flashblocks`): Combines canon ExEx and RPC for flashblocks. +- **`TxPoolExtension`** (from `base-txpool`): Combines transaction tracing ExEx and status RPC. +- **`MeteringExtension`** (from `base-metering`): Provides metering RPC. ## Usage @@ -33,21 +32,17 @@ base-client-runner = { git = "https://github.com/base/node-reth" } Build and run a Base node: ```rust,ignore -use base_client_runner::{BaseNodeBuilder, BaseNodeConfig, FlashblocksConfig}; - -let config = BaseNodeConfig { - flashblocks: FlashblocksConfig { - enabled: true, - url: "wss://flashblocks.base.org".to_string(), - }, - ..Default::default() -}; - -let node = BaseNodeBuilder::new(config) - .with_flashblocks() - .with_transaction_tracing() - .build() - .await?; +use base_client_runner::BaseNodeRunner; +use base_flashblocks::FlashblocksExtension; +use base_metering::MeteringExtension; +use base_txpool::TxPoolExtension; + +let mut runner = BaseNodeRunner::new(args); +runner.install_ext::()?; +runner.install_ext::()?; +runner.install_ext::()?; + +let handle = runner.run(builder); ``` ## License diff --git a/crates/client/client-runner/src/config.rs b/crates/client/client-runner/src/config.rs index c6600ccc..e7a59520 100644 --- a/crates/client/client-runner/src/config.rs +++ b/crates/client/client-runner/src/config.rs @@ -1,9 +1,11 @@ //! Contains the Base node configuration structures. -use base_client_primitives::{FlashblocksConfig, OpProvider, TracingConfig}; -use base_flashblocks::{FlashblocksCanonConfig, FlashblocksCell, FlashblocksState}; -use base_metering::MeteringRpcConfig; -use base_txpool::{TransactionStatusRpcConfig, TransactionTracingConfig}; +use base_client_primitives::OpProvider; +use base_flashblocks::{ + FlashblocksCell, FlashblocksConfig, FlashblocksExtensionConfig, FlashblocksState, +}; +use base_metering::MeteringExtensionConfig; +use base_txpool::{TracingConfig, TxPoolExtensionConfig}; use reth_optimism_node::args::RollupArgs; /// Concrete type alias for the flashblocks cell used in the runner. @@ -34,7 +36,7 @@ impl BaseNodeConfig { // Implement configuration traits for BaseNodeConfig so it can be used // with ConfigurableBaseNodeExtension -impl FlashblocksCanonConfig for BaseNodeConfig { +impl FlashblocksExtensionConfig for BaseNodeConfig { fn flashblocks_cell(&self) -> &FlashblocksCell> { &self.flashblocks_cell } @@ -44,20 +46,18 @@ impl FlashblocksCanonConfig for BaseNodeConfig { } } -impl TransactionTracingConfig for BaseNodeConfig { +impl TxPoolExtensionConfig for BaseNodeConfig { fn tracing(&self) -> &TracingConfig { &self.tracing } -} -impl MeteringRpcConfig for BaseNodeConfig { - fn metering_enabled(&self) -> bool { - self.metering_enabled + fn sequencer_rpc(&self) -> Option<&str> { + self.rollup_args.sequencer.as_deref() } } -impl TransactionStatusRpcConfig for BaseNodeConfig { - fn sequencer_rpc(&self) -> Option<&str> { - self.rollup_args.sequencer.as_deref() +impl MeteringExtensionConfig for BaseNodeConfig { + fn metering_enabled(&self) -> bool { + self.metering_enabled } } diff --git a/crates/client/flashblocks/src/extension.rs b/crates/client/flashblocks/src/extension.rs index 0ba8c3bd..36a493f5 100644 --- a/crates/client/flashblocks/src/extension.rs +++ b/crates/client/flashblocks/src/extension.rs @@ -1,31 +1,45 @@ -//! Contains the [`FlashblocksCanonExtension`] which wires up the `flashblocks-canon` -//! execution extension on the Base node builder. +//! Contains the [`FlashblocksExtension`] which wires up the flashblocks feature +//! (both the canon ExEx and RPC surface) on the Base node builder. use std::sync::Arc; use base_client_primitives::{ - BaseNodeExtension, ConfigurableBaseNodeExtension, FlashblocksConfig, OpBuilder, OpProvider, + BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder, OpProvider, }; use futures_util::TryStreamExt; use once_cell::sync::OnceCell; use reth_exex::ExExEvent; +use tracing::info; +use url::Url; -use crate::FlashblocksState; +use crate::{ + EthApiExt, EthApiOverrideServer, EthPubSub, EthPubSubApiServer, FlashblocksState, + FlashblocksSubscriber, +}; /// The flashblocks cell holds a shared state reference. pub type FlashblocksCell = Arc>>; -/// Helper struct that wires the Flashblocks canon ExEx into the node builder. +/// Flashblocks-specific configuration knobs. +#[derive(Debug, Clone)] +pub struct FlashblocksConfig { + /// The websocket endpoint that streams flashblock updates. + pub websocket_url: String, + /// Maximum number of pending flashblocks to retain in memory. + pub max_pending_blocks_depth: u64, +} + +/// Helper struct that wires the Flashblocks feature (canon ExEx and RPC) into the node builder. #[derive(Debug, Clone)] -pub struct FlashblocksCanonExtension { +pub struct FlashblocksExtension { /// Shared Flashblocks state cache. pub cell: FlashblocksCell>, /// Optional Flashblocks configuration. pub config: Option, } -impl FlashblocksCanonExtension { - /// Create a new Flashblocks canon extension helper. +impl FlashblocksExtension { + /// Create a new Flashblocks extension helper. pub const fn new( cell: FlashblocksCell>, config: Option, @@ -34,23 +48,27 @@ impl FlashblocksCanonExtension { } } -impl BaseNodeExtension for FlashblocksCanonExtension { +impl BaseNodeExtension for FlashblocksExtension { /// Applies the extension to the supplied builder. fn apply(self: Box, builder: OpBuilder) -> OpBuilder { - let flashblocks = self.config; - let flashblocks_enabled = flashblocks.is_some(); + let Some(cfg) = self.config else { + info!(message = "flashblocks integration is disabled"); + return builder; + }; + let flashblocks_cell = self.cell; + let cfg_for_rpc = cfg.clone(); + let flashblocks_cell_for_rpc = flashblocks_cell.clone(); - builder.install_exex_if(flashblocks_enabled, "flashblocks-canon", move |mut ctx| { + // Install the canon ExEx + let builder = builder.install_exex("flashblocks-canon", move |mut ctx| { let flashblocks_cell = flashblocks_cell.clone(); async move { - let fb_config = - flashblocks.as_ref().expect("flashblocks config checked above").clone(); let fb = flashblocks_cell .get_or_init(|| { Arc::new(FlashblocksState::new( ctx.provider().clone(), - fb_config.max_pending_blocks_depth, + cfg.max_pending_blocks_depth, )) }) .clone(); @@ -69,21 +87,55 @@ impl BaseNodeExtension for FlashblocksCanonExtension { Ok(()) }) } + }); + + // Extend with RPC modules + builder.extend_rpc_modules(move |ctx| { + info!(message = "Starting Flashblocks RPC"); + + let ws_url = Url::parse(cfg_for_rpc.websocket_url.as_str())?; + let fb = flashblocks_cell_for_rpc + .get_or_init(|| { + Arc::new(FlashblocksState::new( + ctx.provider().clone(), + cfg_for_rpc.max_pending_blocks_depth, + )) + }) + .clone(); + fb.start(); + + let mut flashblocks_client = FlashblocksSubscriber::new(fb.clone(), ws_url); + flashblocks_client.start(); + + let api_ext = EthApiExt::new( + ctx.registry.eth_api().clone(), + ctx.registry.eth_handlers().filter.clone(), + fb.clone(), + ); + ctx.modules.replace_configured(api_ext.into_rpc())?; + + // Register the eth_subscribe subscription endpoint for flashblocks + // Uses replace_configured since eth_subscribe already exists from reth's standard module + // Pass eth_api to enable proxying standard subscription types to reth's implementation + let eth_pubsub = EthPubSub::new(ctx.registry.eth_api().clone(), fb); + ctx.modules.replace_configured(eth_pubsub.into_rpc())?; + + Ok(()) }) } } -/// Configuration trait for [`FlashblocksCanonExtension`]. +/// Configuration trait for [`FlashblocksExtension`]. /// -/// Types implementing this trait can be used to construct a [`FlashblocksCanonExtension`]. -pub trait FlashblocksCanonConfig { +/// Types implementing this trait can be used to construct a [`FlashblocksExtension`]. +pub trait FlashblocksExtensionConfig { /// Returns the shared flashblocks cell. fn flashblocks_cell(&self) -> &FlashblocksCell>; /// Returns the flashblocks configuration if enabled. fn flashblocks(&self) -> Option<&FlashblocksConfig>; } -impl ConfigurableBaseNodeExtension for FlashblocksCanonExtension { +impl ConfigurableBaseNodeExtension for FlashblocksExtension { fn build(config: &C) -> eyre::Result { Ok(Self::new(config.flashblocks_cell().clone(), config.flashblocks().cloned())) } diff --git a/crates/client/flashblocks/src/lib.rs b/crates/client/flashblocks/src/lib.rs index 1f2f1ced..d1969f72 100644 --- a/crates/client/flashblocks/src/lib.rs +++ b/crates/client/flashblocks/src/lib.rs @@ -45,7 +45,6 @@ pub use rpc::{ }; mod extension; -pub use extension::{FlashblocksCanonConfig, FlashblocksCanonExtension, FlashblocksCell}; - -mod rpc_extension; -pub use rpc_extension::FlashblocksRpcExtension; +pub use extension::{ + FlashblocksCell, FlashblocksConfig, FlashblocksExtension, FlashblocksExtensionConfig, +}; diff --git a/crates/client/flashblocks/src/rpc_extension.rs b/crates/client/flashblocks/src/rpc_extension.rs deleted file mode 100644 index fd5fcde7..00000000 --- a/crates/client/flashblocks/src/rpc_extension.rs +++ /dev/null @@ -1,89 +0,0 @@ -//! Contains the [`FlashblocksRpcExtension`] which wires up the flashblocks RPC surface -//! on the Base node builder. - -use std::sync::Arc; - -use base_client_primitives::{ - BaseNodeExtension, ConfigurableBaseNodeExtension, FlashblocksConfig, OpBuilder, OpProvider, -}; -use tracing::info; -use url::Url; - -use crate::{ - EthApiExt, EthApiOverrideServer, EthPubSub, EthPubSubApiServer, FlashblocksCell, - FlashblocksState, FlashblocksSubscriber, -}; - -/// Helper struct that wires the flashblocks RPC into the node builder. -#[derive(Debug, Clone)] -pub struct FlashblocksRpcExtension { - /// Shared Flashblocks state cache. - pub cell: FlashblocksCell>, - /// Optional Flashblocks configuration. - pub config: Option, -} - -impl FlashblocksRpcExtension { - /// Creates a new flashblocks RPC extension. - pub const fn new( - cell: FlashblocksCell>, - config: Option, - ) -> Self { - Self { cell, config } - } -} - -impl BaseNodeExtension for FlashblocksRpcExtension { - /// Applies the extension to the supplied builder. - fn apply(self: Box, builder: OpBuilder) -> OpBuilder { - let Some(cfg) = self.config else { - info!(message = "flashblocks RPC integration is disabled"); - return builder; - }; - - let flashblocks_cell = self.cell; - - builder.extend_rpc_modules(move |ctx| { - info!(message = "Starting Flashblocks RPC"); - - let ws_url = Url::parse(cfg.websocket_url.as_str())?; - let fb = flashblocks_cell - .get_or_init(|| { - Arc::new(FlashblocksState::new( - ctx.provider().clone(), - cfg.max_pending_blocks_depth, - )) - }) - .clone(); - fb.start(); - - let mut flashblocks_client = FlashblocksSubscriber::new(fb.clone(), ws_url); - flashblocks_client.start(); - - let api_ext = EthApiExt::new( - ctx.registry.eth_api().clone(), - ctx.registry.eth_handlers().filter.clone(), - fb.clone(), - ); - ctx.modules.replace_configured(api_ext.into_rpc())?; - - // Register the eth_subscribe subscription endpoint for flashblocks - // Uses replace_configured since eth_subscribe already exists from reth's standard module - // Pass eth_api to enable proxying standard subscription types to reth's implementation - let eth_pubsub = EthPubSub::new(ctx.registry.eth_api().clone(), fb); - ctx.modules.replace_configured(eth_pubsub.into_rpc())?; - - Ok(()) - }) - } -} - -// Reuse FlashblocksCanonConfig since the methods are identical. -// Any type implementing FlashblocksCanonConfig can construct a FlashblocksRpcExtension. -impl ConfigurableBaseNodeExtension - for FlashblocksRpcExtension -{ - fn build(config: &C) -> eyre::Result { - Ok(Self::new(config.flashblocks_cell().clone(), config.flashblocks().cloned())) - } -} diff --git a/crates/client/metering/src/extension.rs b/crates/client/metering/src/extension.rs index 17771a08..59c1c99e 100644 --- a/crates/client/metering/src/extension.rs +++ b/crates/client/metering/src/extension.rs @@ -1,4 +1,4 @@ -//! Contains the [`MeteringRpcExtension`] which wires up the metering RPC surface +//! Contains the [`MeteringExtension`] which wires up the metering RPC surface //! on the Base node builder. use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; @@ -8,19 +8,19 @@ use crate::{MeteringApiImpl, MeteringApiServer}; /// Helper struct that wires the metering RPC into the node builder. #[derive(Debug, Clone, Copy)] -pub struct MeteringRpcExtension { +pub struct MeteringExtension { /// Whether metering is enabled. pub enabled: bool, } -impl MeteringRpcExtension { - /// Creates a new metering RPC extension. +impl MeteringExtension { + /// Creates a new metering extension. pub const fn new(enabled: bool) -> Self { Self { enabled } } } -impl BaseNodeExtension for MeteringRpcExtension { +impl BaseNodeExtension for MeteringExtension { /// Applies the extension to the supplied builder. fn apply(self: Box, builder: OpBuilder) -> OpBuilder { if !self.enabled { @@ -36,15 +36,15 @@ impl BaseNodeExtension for MeteringRpcExtension { } } -/// Configuration trait for [`MeteringRpcExtension`]. +/// Configuration trait for [`MeteringExtension`]. /// -/// Types implementing this trait can be used to construct a [`MeteringRpcExtension`]. -pub trait MeteringRpcConfig { +/// Types implementing this trait can be used to construct a [`MeteringExtension`]. +pub trait MeteringExtensionConfig { /// Returns whether metering is enabled. fn metering_enabled(&self) -> bool; } -impl ConfigurableBaseNodeExtension for MeteringRpcExtension { +impl ConfigurableBaseNodeExtension for MeteringExtension { fn build(config: &C) -> eyre::Result { Ok(Self::new(config.metering_enabled())) } diff --git a/crates/client/metering/src/lib.rs b/crates/client/metering/src/lib.rs index 16a09587..1c23bd30 100644 --- a/crates/client/metering/src/lib.rs +++ b/crates/client/metering/src/lib.rs @@ -7,7 +7,7 @@ mod block; pub use block::meter_block; mod extension; -pub use extension::{MeteringRpcConfig, MeteringRpcExtension}; +pub use extension::{MeteringExtension, MeteringExtensionConfig}; mod meter; pub use meter::meter_bundle; diff --git a/crates/client/txpool/README.md b/crates/client/txpool/README.md index d115bb60..11ed50f5 100644 --- a/crates/client/txpool/README.md +++ b/crates/client/txpool/README.md @@ -24,13 +24,14 @@ cargo run -p node --release -- \ --enable-transaction-tracing-logs # optional: emit per-tx lifecycle logs ``` -From code, wire the ExEx into the node builder: +From code, wire the extension into the node builder: ```rust,ignore -use base_client_runner::{TracingConfig, extensions::TransactionTracingExtension}; +use base_txpool::{TracingConfig, TxPoolExtension}; let tracing = TracingConfig { enabled: true, logs_enabled: true }; -let builder = TransactionTracingExtension::new(tracing).apply(builder); +let ext = TxPoolExtension::new(tracing, None); +let builder = Box::new(ext).apply(builder); ``` ## Metrics diff --git a/crates/client/txpool/src/extension.rs b/crates/client/txpool/src/extension.rs index e9dab016..21628819 100644 --- a/crates/client/txpool/src/extension.rs +++ b/crates/client/txpool/src/extension.rs @@ -1,46 +1,70 @@ -//! Contains the [`TransactionTracingExtension`] which wires up the `tracex` -//! execution extension on the Base node builder. +//! Contains the [`TxPoolExtension`] which wires up the transaction pool features +//! (tracing ExEx and status RPC) on the Base node builder. -use base_client_primitives::{ - BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder, TracingConfig, -}; +use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; +use tracing::info; -use crate::tracex_exex; +use crate::{TransactionStatusApiImpl, TransactionStatusApiServer, tracex_exex}; -/// Helper struct that wires the transaction tracing ExEx into the node builder. +/// Transaction tracing toggles. #[derive(Debug, Clone, Copy)] -pub struct TransactionTracingExtension { +pub struct TracingConfig { + /// Enables the transaction tracing ExEx. + pub enabled: bool, + /// Emits `info`-level logs for the tracing ExEx when enabled. + pub logs_enabled: bool, +} + +/// Helper struct that wires the transaction pool features into the node builder. +#[derive(Debug, Clone)] +pub struct TxPoolExtension { /// Transaction tracing configuration flags. - pub config: TracingConfig, + pub tracing: TracingConfig, + /// Sequencer RPC endpoint for transaction status proxying. + pub sequencer_rpc: Option, } -impl TransactionTracingExtension { - /// Creates a new transaction tracing extension helper. - pub const fn new(config: TracingConfig) -> Self { - Self { config } +impl TxPoolExtension { + /// Creates a new transaction pool extension helper. + pub const fn new(tracing: TracingConfig, sequencer_rpc: Option) -> Self { + Self { tracing, sequencer_rpc } } } -impl BaseNodeExtension for TransactionTracingExtension { +impl BaseNodeExtension for TxPoolExtension { /// Applies the extension to the supplied builder. fn apply(self: Box, builder: OpBuilder) -> OpBuilder { - let tracing = self.config; - builder.install_exex_if(tracing.enabled, "tracex", move |ctx| async move { + let tracing = self.tracing; + let sequencer_rpc = self.sequencer_rpc; + + // Install the tracing ExEx if enabled + let builder = builder.install_exex_if(tracing.enabled, "tracex", move |ctx| async move { Ok(tracex_exex(ctx, tracing.logs_enabled)) + }); + + // Extend with RPC modules + builder.extend_rpc_modules(move |ctx| { + info!(message = "Starting Transaction Status RPC"); + let proxy_api = TransactionStatusApiImpl::new(sequencer_rpc, ctx.pool().clone()) + .expect("Failed to create transaction status proxy"); + ctx.modules.merge_configured(proxy_api.into_rpc())?; + Ok(()) }) } } -/// Configuration trait for [`TransactionTracingExtension`]. +/// Configuration trait for [`TxPoolExtension`]. /// -/// Types implementing this trait can be used to construct a [`TransactionTracingExtension`]. -pub trait TransactionTracingConfig { +/// Types implementing this trait can be used to construct a [`TxPoolExtension`]. +pub trait TxPoolExtensionConfig { /// Returns the tracing configuration. fn tracing(&self) -> &TracingConfig; + /// Returns the sequencer RPC URL if configured. + fn sequencer_rpc(&self) -> Option<&str>; } -impl ConfigurableBaseNodeExtension for TransactionTracingExtension { +impl ConfigurableBaseNodeExtension for TxPoolExtension { fn build(config: &C) -> eyre::Result { - Ok(Self::new(*config.tracing())) + Ok(Self::new(*config.tracing(), config.sequencer_rpc().map(String::from))) } } diff --git a/crates/client/txpool/src/lib.rs b/crates/client/txpool/src/lib.rs index ab3577d3..66e9ecd1 100644 --- a/crates/client/txpool/src/lib.rs +++ b/crates/client/txpool/src/lib.rs @@ -14,11 +14,8 @@ pub use rpc::{ Status, TransactionStatusApiImpl, TransactionStatusApiServer, TransactionStatusResponse, }; -mod rpc_extension; -pub use rpc_extension::{TransactionStatusRpcConfig, TransactionStatusRpcExtension}; - mod tracker; pub use tracker::Tracker; mod extension; -pub use extension::{TransactionTracingConfig, TransactionTracingExtension}; +pub use extension::{TracingConfig, TxPoolExtension, TxPoolExtensionConfig}; diff --git a/crates/client/txpool/src/rpc_extension.rs b/crates/client/txpool/src/rpc_extension.rs deleted file mode 100644 index 05276225..00000000 --- a/crates/client/txpool/src/rpc_extension.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! Contains the [`TransactionStatusRpcExtension`] which wires up the transaction status -//! RPC surface on the Base node builder. - -use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; - -use crate::{TransactionStatusApiImpl, TransactionStatusApiServer}; - -/// Helper struct that wires the transaction status RPC into the node builder. -#[derive(Debug, Clone)] -pub struct TransactionStatusRpcExtension { - /// Sequencer RPC endpoint for transaction status proxying. - pub sequencer_rpc: Option, -} - -impl TransactionStatusRpcExtension { - /// Creates a new transaction status RPC extension. - pub const fn new(sequencer_rpc: Option) -> Self { - Self { sequencer_rpc } - } -} - -impl BaseNodeExtension for TransactionStatusRpcExtension { - /// Applies the extension to the supplied builder. - fn apply(self: Box, builder: OpBuilder) -> OpBuilder { - let sequencer_rpc = self.sequencer_rpc; - - builder.extend_rpc_modules(move |ctx| { - let proxy_api = TransactionStatusApiImpl::new(sequencer_rpc, ctx.pool().clone()) - .expect("Failed to create transaction status proxy"); - ctx.modules.merge_configured(proxy_api.into_rpc())?; - Ok(()) - }) - } -} - -/// Configuration trait for [`TransactionStatusRpcExtension`]. -/// -/// Types implementing this trait can be used to construct a [`TransactionStatusRpcExtension`]. -pub trait TransactionStatusRpcConfig { - /// Returns the sequencer RPC URL if configured. - fn sequencer_rpc(&self) -> Option<&str>; -} - -impl ConfigurableBaseNodeExtension - for TransactionStatusRpcExtension -{ - fn build(config: &C) -> eyre::Result { - Ok(Self::new(config.sequencer_rpc().map(String::from))) - } -} From 3108574dd0b02d26bac6bb8cf06a10e337526c4e Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 13:31:33 -0600 Subject: [PATCH 03/19] chore: migrate generic node setup to client-primitives, remove client-runner --- Cargo.lock | 16 +---- Cargo.toml | 1 - bin/node/Cargo.toml | 2 +- bin/node/src/cli.rs | 33 ++++------ bin/node/src/main.rs | 26 ++++++-- crates/client/client-primitives/Cargo.toml | 4 ++ .../client/client-primitives/src/extension.rs | 10 +-- .../src/handle.rs | 2 + crates/client/client-primitives/src/lib.rs | 10 ++- .../src/runner.rs | 42 +++++-------- crates/client/client-primitives/src/types.rs | 6 +- crates/client/client-runner/Cargo.toml | 31 --------- crates/client/client-runner/README.md | 50 --------------- crates/client/client-runner/src/config.rs | 63 ------------------- crates/client/client-runner/src/context.rs | 10 --- crates/client/client-runner/src/lib.rs | 16 ----- crates/client/flashblocks/Cargo.toml | 1 - crates/client/flashblocks/src/extension.rs | 20 +----- crates/client/flashblocks/src/lib.rs | 4 +- crates/client/metering/src/extension.rs | 16 +---- crates/client/metering/src/lib.rs | 2 +- crates/client/txpool/src/extension.rs | 18 +----- crates/client/txpool/src/lib.rs | 2 +- 23 files changed, 76 insertions(+), 309 deletions(-) rename crates/client/{client-runner => client-primitives}/src/handle.rs (96%) rename crates/client/{client-runner => client-primitives}/src/runner.rs (61%) delete mode 100644 crates/client/client-runner/Cargo.toml delete mode 100644 crates/client/client-runner/README.md delete mode 100644 crates/client/client-runner/src/config.rs delete mode 100644 crates/client/client-runner/src/context.rs delete mode 100644 crates/client/client-runner/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index da70177d..8e355c27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1558,20 +1558,6 @@ dependencies = [ name = "base-client-primitives" version = "0.2.1" dependencies = [ - "eyre", - "reth", - "reth-db", - "reth-optimism-node", -] - -[[package]] -name = "base-client-runner" -version = "0.2.1" -dependencies = [ - "base-client-primitives", - "base-flashblocks", - "base-metering", - "base-txpool", "derive_more", "eyre", "futures-util", @@ -1702,7 +1688,7 @@ name = "base-reth-node" version = "0.2.1" dependencies = [ "base-cli-utils", - "base-client-runner", + "base-client-primitives", "base-flashblocks", "base-metering", "base-txpool", diff --git a/Cargo.toml b/Cargo.toml index 908fea81..507d310c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,6 @@ base-reth-rpc-types = { path = "crates/shared/reth-rpc-types" } base-client-primitives = { path = "crates/client/client-primitives" } base-metering = { path = "crates/client/metering" } base-txpool = { path = "crates/client/txpool" } -base-client-runner = { path = "crates/client/client-runner" } base-test-utils = { path = "crates/client/test-utils" } base-flashblocks = { path = "crates/client/flashblocks" } diff --git a/bin/node/Cargo.toml b/bin/node/Cargo.toml index afb492c9..0b1be210 100644 --- a/bin/node/Cargo.toml +++ b/bin/node/Cargo.toml @@ -15,9 +15,9 @@ workspace = true [dependencies] # internal base-cli-utils.workspace = true +base-client-primitives.workspace = true base-flashblocks.workspace = true base-metering.workspace = true -base-client-runner.workspace = true base-txpool.workspace = true # reth diff --git a/bin/node/src/cli.rs b/bin/node/src/cli.rs index 7bcd3a34..b4385ca1 100644 --- a/bin/node/src/cli.rs +++ b/bin/node/src/cli.rs @@ -1,11 +1,7 @@ //! Contains the CLI arguments -use std::sync::Arc; - -use base_client_runner::{BaseNodeConfig, RunnerFlashblocksCell}; use base_flashblocks::FlashblocksConfig; use base_txpool::TracingConfig; -use once_cell::sync::OnceCell; use reth_optimism_node::args::RollupArgs; /// CLI Arguments @@ -50,25 +46,20 @@ impl Args { pub const fn flashblocks_enabled(&self) -> bool { self.websocket_url.is_some() } -} -impl From for BaseNodeConfig { - fn from(args: Args) -> Self { - let flashblocks_cell: RunnerFlashblocksCell = Arc::new(OnceCell::new()); - let flashblocks = args.websocket_url.map(|websocket_url| FlashblocksConfig { - websocket_url, - max_pending_blocks_depth: args.max_pending_blocks_depth, - }); + /// Build FlashblocksConfig from CLI args. + pub fn flashblocks_config(&self) -> Option { + self.websocket_url.as_ref().map(|url| FlashblocksConfig { + websocket_url: url.clone(), + max_pending_blocks_depth: self.max_pending_blocks_depth, + }) + } - Self { - rollup_args: args.rollup_args, - flashblocks, - tracing: TracingConfig { - enabled: args.enable_transaction_tracing, - logs_enabled: args.enable_transaction_tracing_logs, - }, - metering_enabled: args.enable_metering, - flashblocks_cell, + /// Build TracingConfig from CLI args. + pub const fn tracing_config(&self) -> TracingConfig { + TracingConfig { + enabled: self.enable_transaction_tracing, + logs_enabled: self.enable_transaction_tracing_logs, } } } diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index c729c98a..e6791368 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -5,10 +5,13 @@ pub mod cli; -use base_client_runner::BaseNodeRunner; -use base_flashblocks::FlashblocksExtension; +use std::sync::Arc; + +use base_client_primitives::{BaseNodeRunner, OpProvider}; +use base_flashblocks::{FlashblocksCell, FlashblocksExtension, FlashblocksState}; use base_metering::MeteringExtension; use base_txpool::TxPoolExtension; +use once_cell::sync::OnceCell; #[global_allocator] static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator(); @@ -24,12 +27,23 @@ fn main() { // Step 3: Hand the parsed CLI to the node runner so it can build and launch the Base node. cli.run(|builder, args| async move { - let mut runner = BaseNodeRunner::new(args); + // Create shared flashblocks cell + let flashblocks_cell: FlashblocksCell> = + Arc::new(OnceCell::new()); + + // Extract values needed for extensions before moving rollup_args + let sequencer_rpc = args.rollup_args.sequencer.clone(); + let tracing_config = args.tracing_config(); + let metering_enabled = args.enable_metering; + let flashblocks_config = args.flashblocks_config(); + + let mut runner = BaseNodeRunner::new(args.rollup_args); // Feature extensions (FlashblocksExtension must be last - uses replace_configured) - runner.install_ext::()?; - runner.install_ext::()?; - runner.install_ext::()?; + runner.install_ext(Box::new(TxPoolExtension::new(tracing_config, sequencer_rpc))); + runner.install_ext(Box::new(MeteringExtension::new(metering_enabled))); + runner + .install_ext(Box::new(FlashblocksExtension::new(flashblocks_cell, flashblocks_config))); let handle = runner.run(builder); handle.await diff --git a/crates/client/client-primitives/Cargo.toml b/crates/client/client-primitives/Cargo.toml index 1fc3f0cb..38130118 100644 --- a/crates/client/client-primitives/Cargo.toml +++ b/crates/client/client-primitives/Cargo.toml @@ -16,6 +16,10 @@ workspace = true reth.workspace = true reth-db.workspace = true reth-optimism-node.workspace = true +reth-optimism-chainspec.workspace = true # misc eyre.workspace = true +futures-util.workspace = true +tracing.workspace = true +derive_more = { workspace = true, features = ["debug"] } diff --git a/crates/client/client-primitives/src/extension.rs b/crates/client/client-primitives/src/extension.rs index 605978bf..5e8e809e 100644 --- a/crates/client/client-primitives/src/extension.rs +++ b/crates/client/client-primitives/src/extension.rs @@ -1,9 +1,7 @@ -//! Traits describing configurable node builder extensions. +//! Traits describing node builder extensions. use std::fmt::Debug; -use eyre::Result; - use crate::OpBuilder; /// A node builder extension that can apply additional wiring to the builder. @@ -11,9 +9,3 @@ pub trait BaseNodeExtension: Send + Sync + Debug { /// Applies the extension to the supplied builder. fn apply(self: Box, builder: OpBuilder) -> OpBuilder; } - -/// An extension that can be constructed from a configuration type. -pub trait ConfigurableBaseNodeExtension: BaseNodeExtension + Sized + 'static { - /// Builds the extension from the node config. - fn build(config: &C) -> Result; -} diff --git a/crates/client/client-runner/src/handle.rs b/crates/client/client-primitives/src/handle.rs similarity index 96% rename from crates/client/client-runner/src/handle.rs rename to crates/client/client-primitives/src/handle.rs index 0332ba2c..9ffc1ca1 100644 --- a/crates/client/client-runner/src/handle.rs +++ b/crates/client/client-primitives/src/handle.rs @@ -1,3 +1,5 @@ +//! Contains the [`BaseNodeHandle`], an awaitable handle to a launched Base node. + use std::{ future::Future, pin::Pin, diff --git a/crates/client/client-primitives/src/lib.rs b/crates/client/client-primitives/src/lib.rs index 8306c677..2dc062de 100644 --- a/crates/client/client-primitives/src/lib.rs +++ b/crates/client/client-primitives/src/lib.rs @@ -4,7 +4,13 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] mod extension; -pub use extension::{BaseNodeExtension, ConfigurableBaseNodeExtension}; +pub use extension::BaseNodeExtension; + +mod handle; +pub use handle::BaseNodeHandle; + +mod runner; +pub use runner::BaseNodeRunner; mod types; -pub use types::{OpBuilder, OpProvider}; +pub use types::{BaseNodeBuilder, OpBuilder, OpProvider}; diff --git a/crates/client/client-runner/src/runner.rs b/crates/client/client-primitives/src/runner.rs similarity index 61% rename from crates/client/client-runner/src/runner.rs rename to crates/client/client-primitives/src/runner.rs index 2500fcd8..7d712d82 100644 --- a/crates/client/client-runner/src/runner.rs +++ b/crates/client/client-primitives/src/runner.rs @@ -1,60 +1,50 @@ //! Contains the [`BaseNodeRunner`], which is responsible for configuring and launching a Base node. -use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension}; use eyre::Result; use reth::{ builder::{EngineNodeLauncher, Node, NodeHandleFor, TreeConfig}, providers::providers::BlockchainProvider, }; -use reth_optimism_node::OpNode; +use reth_optimism_node::{OpNode, args::RollupArgs}; use tracing::info; -use crate::{BaseNodeBuilder, BaseNodeConfig, BaseNodeHandle}; +use crate::{BaseNodeBuilder, BaseNodeExtension, BaseNodeHandle}; /// Wraps the Base node configuration and orchestrates builder wiring. #[derive(Debug)] pub struct BaseNodeRunner { - /// Contains the configuration for the Base node. - config: BaseNodeConfig, + /// Rollup-specific arguments forwarded to the Optimism node implementation. + rollup_args: RollupArgs, /// Registered builder extensions. extensions: Vec>, } impl BaseNodeRunner { - /// Creates a new launcher using the provided configuration. - pub fn new(config: impl Into) -> Self { - Self { config: config.into(), extensions: Vec::new() } + /// Creates a new launcher using the provided rollup arguments. + pub fn new(rollup_args: RollupArgs) -> Self { + Self { rollup_args, extensions: Vec::new() } } - /// Returns the underlying configuration, primarily for testing. - pub const fn config(&self) -> &BaseNodeConfig { - &self.config + /// Registers a new builder extension. + pub fn install_ext(&mut self, extension: Box) { + self.extensions.push(extension); } - /// Registers a new builder extension constructed from the node configuration. - pub fn install_ext(&mut self) -> Result<()> - where - E: ConfigurableBaseNodeExtension, - { - let extension = E::build(&self.config)?; - self.extensions.push(Box::new(extension)); - Ok(()) - } - - /// Applies all Base-specific wiring to the supplied builder, launches the node, and returns a handle that can be awaited. + /// Applies all Base-specific wiring to the supplied builder, launches the node, and returns a + /// handle that can be awaited. pub fn run(self, builder: BaseNodeBuilder) -> BaseNodeHandle { - let Self { config, extensions } = self; - BaseNodeHandle::new(Self::launch_node(config, extensions, builder)) + let Self { rollup_args, extensions } = self; + BaseNodeHandle::new(Self::launch_node(rollup_args, extensions, builder)) } async fn launch_node( - config: BaseNodeConfig, + rollup_args: RollupArgs, extensions: Vec>, builder: BaseNodeBuilder, ) -> Result> { info!(target: "base-runner", "starting custom Base node"); - let op_node = OpNode::new(config.rollup_args.clone()); + let op_node = OpNode::new(rollup_args); let builder = builder .with_types_and_provider::>() diff --git a/crates/client/client-primitives/src/types.rs b/crates/client/client-primitives/src/types.rs index fee2d759..b25f0c46 100644 --- a/crates/client/client-primitives/src/types.rs +++ b/crates/client/client-primitives/src/types.rs @@ -4,10 +4,11 @@ use std::sync::Arc; use reth::{ api::{FullNodeTypesAdapter, NodeTypesWithDBAdapter}, - builder::{Node, NodeBuilderWithComponents, WithLaunchContext}, + builder::{Node, NodeBuilder, NodeBuilderWithComponents, WithLaunchContext}, providers::providers::BlockchainProvider, }; use reth_db::DatabaseEnv; +use reth_optimism_chainspec::OpChainSpec; use reth_optimism_node::OpNode; type OpNodeTypes = FullNodeTypesAdapter, OpProvider>; @@ -20,3 +21,6 @@ pub type OpProvider = BlockchainProvider>; + +/// Convenience alias for the Base node builder type. +pub type BaseNodeBuilder = WithLaunchContext, OpChainSpec>>; diff --git a/crates/client/client-runner/Cargo.toml b/crates/client/client-runner/Cargo.toml deleted file mode 100644 index ced3cfac..00000000 --- a/crates/client/client-runner/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "base-client-runner" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true -description = "Base-specific node launcher wiring" - -[lints] -workspace = true - -[dependencies] -# internal -base-client-primitives.workspace = true -base-flashblocks.workspace = true -base-metering.workspace = true -base-txpool.workspace = true - -# reth -reth.workspace = true -reth-db.workspace = true -reth-optimism-node.workspace = true -reth-optimism-chainspec.workspace = true - -# misc -eyre.workspace = true -futures-util.workspace = true -tracing.workspace = true -derive_more = { workspace = true, features = ["debug"] } diff --git a/crates/client/client-runner/README.md b/crates/client/client-runner/README.md deleted file mode 100644 index 816845c1..00000000 --- a/crates/client/client-runner/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# `base-client-runner` - -CI -MIT License - -Base-specific node launcher that wires together the Optimism node components, execution extensions, and RPC add-ons for the Base node binary. Exposes the types that the CLI uses to build a node and pass them to Optimism's `Cli` runner. - -## Overview - -- **`BaseNodeBuilder`**: Builder for constructing a Base node with custom extensions and configuration. -- **`BaseNodeRunner`**: Runs the Base node with all configured extensions. -- **`BaseNodeHandle`**: Handle to the running node, providing access to providers and state. -- **`BaseNodeConfig`**: Configuration options for the Base node. - -## Extensions - -Each feature crate provides a single `BaseNodeExtension` that combines all functionality for that feature: - -- **`FlashblocksExtension`** (from `base-flashblocks`): Combines canon ExEx and RPC for flashblocks. -- **`TxPoolExtension`** (from `base-txpool`): Combines transaction tracing ExEx and status RPC. -- **`MeteringExtension`** (from `base-metering`): Provides metering RPC. - -## Usage - -Add the dependency to your `Cargo.toml`: - -```toml -[dependencies] -base-client-runner = { git = "https://github.com/base/node-reth" } -``` - -Build and run a Base node: - -```rust,ignore -use base_client_runner::BaseNodeRunner; -use base_flashblocks::FlashblocksExtension; -use base_metering::MeteringExtension; -use base_txpool::TxPoolExtension; - -let mut runner = BaseNodeRunner::new(args); -runner.install_ext::()?; -runner.install_ext::()?; -runner.install_ext::()?; - -let handle = runner.run(builder); -``` - -## License - -Licensed under the [MIT License](https://github.com/base/node-reth/blob/main/LICENSE). diff --git a/crates/client/client-runner/src/config.rs b/crates/client/client-runner/src/config.rs deleted file mode 100644 index e7a59520..00000000 --- a/crates/client/client-runner/src/config.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Contains the Base node configuration structures. - -use base_client_primitives::OpProvider; -use base_flashblocks::{ - FlashblocksCell, FlashblocksConfig, FlashblocksExtensionConfig, FlashblocksState, -}; -use base_metering::MeteringExtensionConfig; -use base_txpool::{TracingConfig, TxPoolExtensionConfig}; -use reth_optimism_node::args::RollupArgs; - -/// Concrete type alias for the flashblocks cell used in the runner. -pub type RunnerFlashblocksCell = FlashblocksCell>; - -/// Captures the pieces of CLI configuration that the node logic cares about. -#[derive(Debug, Clone)] -pub struct BaseNodeConfig { - /// Rollup-specific arguments forwarded to the Optimism node implementation. - pub rollup_args: RollupArgs, - /// Optional flashblocks configuration if the websocket URL was provided. - pub flashblocks: Option, - /// Execution extension tracing toggles. - pub tracing: TracingConfig, - /// Indicates whether the metering RPC surface should be installed. - pub metering_enabled: bool, - /// Shared Flashblocks state cache. - pub flashblocks_cell: RunnerFlashblocksCell, -} - -impl BaseNodeConfig { - /// Returns `true` if flashblocks support should be wired up. - pub const fn flashblocks_enabled(&self) -> bool { - self.flashblocks.is_some() - } -} - -// Implement configuration traits for BaseNodeConfig so it can be used -// with ConfigurableBaseNodeExtension - -impl FlashblocksExtensionConfig for BaseNodeConfig { - fn flashblocks_cell(&self) -> &FlashblocksCell> { - &self.flashblocks_cell - } - - fn flashblocks(&self) -> Option<&FlashblocksConfig> { - self.flashblocks.as_ref() - } -} - -impl TxPoolExtensionConfig for BaseNodeConfig { - fn tracing(&self) -> &TracingConfig { - &self.tracing - } - - fn sequencer_rpc(&self) -> Option<&str> { - self.rollup_args.sequencer.as_deref() - } -} - -impl MeteringExtensionConfig for BaseNodeConfig { - fn metering_enabled(&self) -> bool { - self.metering_enabled - } -} diff --git a/crates/client/client-runner/src/context.rs b/crates/client/client-runner/src/context.rs deleted file mode 100644 index cf28cbb6..00000000 --- a/crates/client/client-runner/src/context.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Contains a type alias for the base node builder used in the runner. - -use std::sync::Arc; - -use reth::builder::{NodeBuilder, WithLaunchContext}; -use reth_db::DatabaseEnv; -use reth_optimism_chainspec::OpChainSpec; - -/// Convenience alias for the Base node builder type. -pub type BaseNodeBuilder = WithLaunchContext, OpChainSpec>>; diff --git a/crates/client/client-runner/src/lib.rs b/crates/client/client-runner/src/lib.rs deleted file mode 100644 index 1e902da1..00000000 --- a/crates/client/client-runner/src/lib.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![doc = include_str!("../README.md")] -#![doc(issue_tracker_base_url = "https://github.com/base/node-reth/issues/")] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -#![cfg_attr(not(test), warn(unused_crate_dependencies))] - -mod context; -pub use context::BaseNodeBuilder; - -mod handle; -pub use handle::BaseNodeHandle; - -mod runner; -pub use runner::BaseNodeRunner; - -mod config; -pub use config::{BaseNodeConfig, RunnerFlashblocksCell}; diff --git a/crates/client/flashblocks/Cargo.toml b/crates/client/flashblocks/Cargo.toml index d95e17ad..6b1ace83 100644 --- a/crates/client/flashblocks/Cargo.toml +++ b/crates/client/flashblocks/Cargo.toml @@ -58,7 +58,6 @@ jsonrpsee-types.workspace = true serde.workspace = true # misc -eyre.workspace = true once_cell.workspace = true url.workspace = true thiserror.workspace = true diff --git a/crates/client/flashblocks/src/extension.rs b/crates/client/flashblocks/src/extension.rs index 36a493f5..ce4a53dd 100644 --- a/crates/client/flashblocks/src/extension.rs +++ b/crates/client/flashblocks/src/extension.rs @@ -3,9 +3,7 @@ use std::sync::Arc; -use base_client_primitives::{ - BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder, OpProvider, -}; +use base_client_primitives::{BaseNodeExtension, OpBuilder, OpProvider}; use futures_util::TryStreamExt; use once_cell::sync::OnceCell; use reth_exex::ExExEvent; @@ -124,19 +122,3 @@ impl BaseNodeExtension for FlashblocksExtension { }) } } - -/// Configuration trait for [`FlashblocksExtension`]. -/// -/// Types implementing this trait can be used to construct a [`FlashblocksExtension`]. -pub trait FlashblocksExtensionConfig { - /// Returns the shared flashblocks cell. - fn flashblocks_cell(&self) -> &FlashblocksCell>; - /// Returns the flashblocks configuration if enabled. - fn flashblocks(&self) -> Option<&FlashblocksConfig>; -} - -impl ConfigurableBaseNodeExtension for FlashblocksExtension { - fn build(config: &C) -> eyre::Result { - Ok(Self::new(config.flashblocks_cell().clone(), config.flashblocks().cloned())) - } -} diff --git a/crates/client/flashblocks/src/lib.rs b/crates/client/flashblocks/src/lib.rs index d1969f72..dbed7230 100644 --- a/crates/client/flashblocks/src/lib.rs +++ b/crates/client/flashblocks/src/lib.rs @@ -45,6 +45,4 @@ pub use rpc::{ }; mod extension; -pub use extension::{ - FlashblocksCell, FlashblocksConfig, FlashblocksExtension, FlashblocksExtensionConfig, -}; +pub use extension::{FlashblocksCell, FlashblocksConfig, FlashblocksExtension}; diff --git a/crates/client/metering/src/extension.rs b/crates/client/metering/src/extension.rs index 59c1c99e..77b0fba2 100644 --- a/crates/client/metering/src/extension.rs +++ b/crates/client/metering/src/extension.rs @@ -1,7 +1,7 @@ //! Contains the [`MeteringExtension`] which wires up the metering RPC surface //! on the Base node builder. -use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; +use base_client_primitives::{BaseNodeExtension, OpBuilder}; use tracing::info; use crate::{MeteringApiImpl, MeteringApiServer}; @@ -35,17 +35,3 @@ impl BaseNodeExtension for MeteringExtension { }) } } - -/// Configuration trait for [`MeteringExtension`]. -/// -/// Types implementing this trait can be used to construct a [`MeteringExtension`]. -pub trait MeteringExtensionConfig { - /// Returns whether metering is enabled. - fn metering_enabled(&self) -> bool; -} - -impl ConfigurableBaseNodeExtension for MeteringExtension { - fn build(config: &C) -> eyre::Result { - Ok(Self::new(config.metering_enabled())) - } -} diff --git a/crates/client/metering/src/lib.rs b/crates/client/metering/src/lib.rs index 1c23bd30..41883a8d 100644 --- a/crates/client/metering/src/lib.rs +++ b/crates/client/metering/src/lib.rs @@ -7,7 +7,7 @@ mod block; pub use block::meter_block; mod extension; -pub use extension::{MeteringExtension, MeteringExtensionConfig}; +pub use extension::MeteringExtension; mod meter; pub use meter::meter_bundle; diff --git a/crates/client/txpool/src/extension.rs b/crates/client/txpool/src/extension.rs index 21628819..e7687ce1 100644 --- a/crates/client/txpool/src/extension.rs +++ b/crates/client/txpool/src/extension.rs @@ -1,7 +1,7 @@ //! Contains the [`TxPoolExtension`] which wires up the transaction pool features //! (tracing ExEx and status RPC) on the Base node builder. -use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; +use base_client_primitives::{BaseNodeExtension, OpBuilder}; use tracing::info; use crate::{TransactionStatusApiImpl, TransactionStatusApiServer, tracex_exex}; @@ -52,19 +52,3 @@ impl BaseNodeExtension for TxPoolExtension { }) } } - -/// Configuration trait for [`TxPoolExtension`]. -/// -/// Types implementing this trait can be used to construct a [`TxPoolExtension`]. -pub trait TxPoolExtensionConfig { - /// Returns the tracing configuration. - fn tracing(&self) -> &TracingConfig; - /// Returns the sequencer RPC URL if configured. - fn sequencer_rpc(&self) -> Option<&str>; -} - -impl ConfigurableBaseNodeExtension for TxPoolExtension { - fn build(config: &C) -> eyre::Result { - Ok(Self::new(*config.tracing(), config.sequencer_rpc().map(String::from))) - } -} diff --git a/crates/client/txpool/src/lib.rs b/crates/client/txpool/src/lib.rs index 66e9ecd1..96f35667 100644 --- a/crates/client/txpool/src/lib.rs +++ b/crates/client/txpool/src/lib.rs @@ -18,4 +18,4 @@ mod tracker; pub use tracker::Tracker; mod extension; -pub use extension::{TracingConfig, TxPoolExtension, TxPoolExtensionConfig}; +pub use extension::{TracingConfig, TxPoolExtension}; From b55a54eac99498e1defbe7282642a875cd53b99b Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 14:04:49 -0600 Subject: [PATCH 04/19] chore: unify test account logic --- .../flashblocks/benches/pending_state.rs | 14 +- .../client/flashblocks/tests/eip7702_tests.rs | 50 ++--- .../flashblocks/tests/eth_call_erc20.rs | 21 +- .../flashblocks/tests/flashblocks_rpc.rs | 41 ++-- crates/client/flashblocks/tests/state.rs | 199 ++++++++---------- crates/client/metering/tests/meter.rs | 84 +++----- crates/client/metering/tests/meter_block.rs | 66 ++---- crates/client/test-utils/src/accounts.rs | 153 +++++--------- crates/client/test-utils/src/harness.rs | 18 +- crates/client/test-utils/src/lib.rs | 2 +- 10 files changed, 244 insertions(+), 404 deletions(-) diff --git a/crates/client/flashblocks/benches/pending_state.rs b/crates/client/flashblocks/benches/pending_state.rs index a38df250..9f2aacfe 100644 --- a/crates/client/flashblocks/benches/pending_state.rs +++ b/crates/client/flashblocks/benches/pending_state.rs @@ -12,7 +12,7 @@ use base_flashblocks::{FlashblocksAPI, FlashblocksReceiver, FlashblocksState}; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{LocalNodeProvider, TestAccounts, TestHarness}; +use base_test_utils::{Account, LocalNodeProvider, TestHarness}; use criterion::{BatchSize, Criterion, Throughput, criterion_group, criterion_main}; use reth::{ chainspec::{ChainSpecProvider, EthChainSpec}, @@ -56,7 +56,7 @@ impl BenchSetup { let flashblocks = tx_counts .iter() .map(|count| { - let txs = sample_transactions(&provider, harness.accounts(), *count); + let txs = sample_transactions(&provider, *count); let blocks = build_flashblocks(&canonical_block, &txs); (format!("pending_state_{}_txs", count), blocks) }) @@ -260,12 +260,8 @@ fn transaction_flashblock( } } -fn sample_transactions( - provider: &LocalNodeProvider, - accounts: &TestAccounts, - count: usize, -) -> Vec { - let signer = B256::from_hex(accounts.alice.private_key).expect("valid private key hex"); +fn sample_transactions(provider: &LocalNodeProvider, count: usize) -> Vec { + let signer = B256::from_hex(Account::Alice.private_key()).expect("valid private key hex"); let chain_id = provider.chain_spec().chain_id(); (0..count as u64) @@ -273,7 +269,7 @@ fn sample_transactions( let txn = TransactionBuilder::default() .signer(signer) .chain_id(chain_id) - .to(accounts.bob.address) + .to(Account::Bob.address()) .nonce(nonce) .value(1_000_000_000u128) .gas_limit(TX_GAS_USED) diff --git a/crates/client/flashblocks/tests/eip7702_tests.rs b/crates/client/flashblocks/tests/eip7702_tests.rs index 1bfc1c55..435cc71b 100644 --- a/crates/client/flashblocks/tests/eip7702_tests.rs +++ b/crates/client/flashblocks/tests/eip7702_tests.rs @@ -31,7 +31,7 @@ struct TestSetup { impl TestSetup { async fn new() -> Result { let harness = FlashblocksHarness::new().await?; - let deployer = &harness.accounts().deployer; + let deployer = Account::Deployer; // Deploy Minimal7702Account contract let deploy_data = Minimal7702Account::BYTECODE.to_vec(); @@ -52,14 +52,6 @@ impl TestSetup { fn chain_id(&self) -> u64 { 84532 // Base Sepolia chain ID (matches test harness) } - - fn alice(&self) -> &Account { - &self.harness.accounts().alice - } - - fn bob(&self) -> &Account { - &self.harness.accounts().bob - } } /// Build an EIP-7702 authorization for delegating to a contract @@ -67,7 +59,7 @@ fn build_authorization( chain_id: u64, contract_address: Address, nonce: u64, - account: &Account, + account: Account, ) -> alloy_eips::eip7702::SignedAuthorization { let auth = Authorization { chain_id: U256::from(chain_id), address: contract_address, nonce }; @@ -83,7 +75,7 @@ fn build_eip7702_tx( value: U256, input: Bytes, authorization_list: Vec, - account: &Account, + account: Account, ) -> Bytes { let tx = TxEip7702 { chain_id, @@ -111,7 +103,7 @@ fn build_eip1559_tx( to: Address, value: U256, input: Bytes, - account: &Account, + account: Account, ) -> Bytes { let tx = TxEip1559 { chain_id, @@ -187,7 +179,7 @@ async fn test_eip7702_delegation_in_pending_flashblock() -> Result<()> { setup.send_flashblock(base_payload).await?; // Create authorization for Alice to delegate to the Minimal7702Account contract - let auth = build_authorization(chain_id, setup.account_contract_address, 0, setup.alice()); + let auth = build_authorization(chain_id, setup.account_contract_address, 0, Account::Alice); // Build EIP-7702 transaction with authorization // This delegates Alice's EOA to execute code from Minimal7702Account @@ -195,11 +187,11 @@ async fn test_eip7702_delegation_in_pending_flashblock() -> Result<()> { let eip7702_tx = build_eip7702_tx( chain_id, 0, - setup.alice().address, + Account::Alice.address(), U256::ZERO, Bytes::from(increment_call.abi_encode()), vec![auth], - setup.alice(), + Account::Alice, ); let tx_hash = alloy_primitives::keccak256(&eip7702_tx); @@ -230,29 +222,29 @@ async fn test_eip7702_multiple_delegations_same_flashblock() -> Result<()> { // Create authorizations for both Alice and Bob let auth_alice = - build_authorization(chain_id, setup.account_contract_address, 0, setup.alice()); - let auth_bob = build_authorization(chain_id, setup.account_contract_address, 0, setup.bob()); + build_authorization(chain_id, setup.account_contract_address, 0, Account::Alice); + let auth_bob = build_authorization(chain_id, setup.account_contract_address, 0, Account::Bob); // Build EIP-7702 transactions let increment_call = Minimal7702Account::incrementCall {}; let tx_alice = build_eip7702_tx( chain_id, 0, - setup.alice().address, + Account::Alice.address(), U256::ZERO, Bytes::from(increment_call.abi_encode()), vec![auth_alice], - setup.alice(), + Account::Alice, ); let tx_bob = build_eip7702_tx( chain_id, 0, - setup.bob().address, + Account::Bob.address(), U256::ZERO, Bytes::from(increment_call.abi_encode()), vec![auth_bob], - setup.bob(), + Account::Bob, ); let tx_hash_alice = alloy_primitives::keccak256(&tx_alice); @@ -303,16 +295,16 @@ async fn test_eip7702_pending_receipt() -> Result<()> { setup.send_flashblock(base_payload).await?; // Create and send EIP-7702 transaction - let auth = build_authorization(chain_id, setup.account_contract_address, 0, setup.alice()); + let auth = build_authorization(chain_id, setup.account_contract_address, 0, Account::Alice); let increment_call = Minimal7702Account::incrementCall {}; let eip7702_tx = build_eip7702_tx( chain_id, 0, - setup.alice().address, + Account::Alice.address(), U256::ZERO, Bytes::from(increment_call.abi_encode()), vec![auth], - setup.alice(), + Account::Alice, ); let tx_hash = alloy_primitives::keccak256(&eip7702_tx); @@ -346,15 +338,15 @@ async fn test_eip7702_delegation_then_execution() -> Result<()> { let delegation_gas = 30000; let delegation_cumulative = BASE_CUMULATIVE_GAS + delegation_gas; - let auth = build_authorization(chain_id, setup.account_contract_address, 0, setup.alice()); + let auth = build_authorization(chain_id, setup.account_contract_address, 0, Account::Alice); let delegation_tx = build_eip7702_tx( chain_id, 0, - setup.alice().address, + Account::Alice.address(), U256::ZERO, Bytes::new(), // Empty input - just setting up delegation vec![auth], - setup.alice(), + Account::Alice, ); let delegation_hash = alloy_primitives::keccak256(&delegation_tx); @@ -372,10 +364,10 @@ async fn test_eip7702_delegation_then_execution() -> Result<()> { let execution_tx = build_eip1559_tx( chain_id, 1, // incremented nonce - setup.alice().address, + Account::Alice.address(), U256::ZERO, Bytes::from(increment_call.abi_encode()), - setup.alice(), + Account::Alice, ); let execution_hash = alloy_primitives::keccak256(&execution_tx); diff --git a/crates/client/flashblocks/tests/eth_call_erc20.rs b/crates/client/flashblocks/tests/eth_call_erc20.rs index 2c62d223..cb833701 100644 --- a/crates/client/flashblocks/tests/eth_call_erc20.rs +++ b/crates/client/flashblocks/tests/eth_call_erc20.rs @@ -20,7 +20,7 @@ use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; use base_test_utils::{ - FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, MockERC20, TransparentUpgradeableProxy, + Account, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, MockERC20, TransparentUpgradeableProxy, }; use eyre::Result; struct Erc20TestSetup { @@ -34,7 +34,7 @@ struct Erc20TestSetup { impl Erc20TestSetup { async fn new(with_proxy: bool) -> Result { let harness = FlashblocksHarness::new().await?; - let deployer = &harness.accounts().deployer; + let deployer = Account::Deployer; // Deploy MockERC20 from solmate with constructor args (name, symbol, decimals) let token_constructor = MockERC20::constructorCall { @@ -52,7 +52,7 @@ impl Erc20TestSetup { // Constructor: (implementation, initialOwner, data) let proxy_constructor = TransparentUpgradeableProxy::constructorCall { _logic: token_address, - initialOwner: deployer.address, + initialOwner: deployer.address(), _data: Bytes::new(), }; let proxy_deploy_data = @@ -212,14 +212,13 @@ async fn test_proxy_erc20_deployment() -> Result<()> { async fn test_erc20_mint() -> Result<()> { let setup = Erc20TestSetup::new(false).await?; let provider = setup.harness.provider(); - let accounts = setup.harness.accounts(); // Deploy contracts first setup.send_base_and_deploy().await?; // Check initial balance is zero let token = MockERC20::MockERC20Instance::new(setup.token_address, provider.clone()); - let balance_call = token.balanceOf(accounts.alice.address).into_transaction_request(); + let balance_call = token.balanceOf(Account::Alice.address()).into_transaction_request(); let result = provider.call(balance_call.clone()).block(BlockNumberOrTag::Pending.into()).await?; let initial_balance = U256::abi_decode(&result)?; @@ -228,8 +227,8 @@ async fn test_erc20_mint() -> Result<()> { // Create mint transaction let mint_amount = U256::from(1000u64); let mint_tx_request = - token.mint(accounts.alice.address, mint_amount).into_transaction_request(); - let (mint_tx, _) = accounts.deployer.sign_txn_request(mint_tx_request.nonce(1))?; + token.mint(Account::Alice.address(), mint_amount).into_transaction_request(); + let (mint_tx, _) = Account::Deployer.sign_txn_request(mint_tx_request.nonce(1))?; // Send mint flashblock let mint_payload = setup.create_mint_payload(mint_tx); @@ -248,7 +247,6 @@ async fn test_erc20_mint() -> Result<()> { async fn test_proxy_erc20_mint() -> Result<()> { let setup = Erc20TestSetup::new(true).await?; let provider = setup.harness.provider(); - let accounts = setup.harness.accounts(); // Deploy contracts first setup.send_base_and_deploy().await?; @@ -256,7 +254,8 @@ async fn test_proxy_erc20_mint() -> Result<()> { // Check initial balance is zero through proxy let proxy_address = setup.proxy_address.unwrap(); let token_via_proxy = MockERC20::MockERC20Instance::new(proxy_address, provider.clone()); - let balance_call = token_via_proxy.balanceOf(accounts.alice.address).into_transaction_request(); + let balance_call = + token_via_proxy.balanceOf(Account::Alice.address()).into_transaction_request(); let result = provider.call(balance_call.clone()).block(BlockNumberOrTag::Pending.into()).await?; let initial_balance = U256::abi_decode(&result)?; @@ -265,8 +264,8 @@ async fn test_proxy_erc20_mint() -> Result<()> { // Create mint transaction through proxy let mint_amount = U256::from(5000u64); let mint_tx_request = - token_via_proxy.mint(accounts.alice.address, mint_amount).into_transaction_request(); - let (mint_tx, _) = accounts.deployer.sign_txn_request(mint_tx_request.nonce(2))?; + token_via_proxy.mint(Account::Alice.address(), mint_amount).into_transaction_request(); + let (mint_tx, _) = Account::Deployer.sign_txn_request(mint_tx_request.nonce(2))?; // Send mint flashblock (note: interaction_address returns proxy) let mint_payload = setup.create_mint_payload(mint_tx); diff --git a/crates/client/flashblocks/tests/flashblocks_rpc.rs b/crates/client/flashblocks/tests/flashblocks_rpc.rs index d589a568..3544af1e 100644 --- a/crates/client/flashblocks/tests/flashblocks_rpc.rs +++ b/crates/client/flashblocks/tests/flashblocks_rpc.rs @@ -14,7 +14,7 @@ use alloy_rpc_types_eth::{TransactionInput, error::EthRpcErrorCode}; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{DoubleCounter, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX}; +use base_test_utils::{Account, DoubleCounter, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX}; use eyre::Result; use futures_util::{SinkExt, StreamExt}; use op_alloy_network::{Optimism, ReceiptResponse, TransactionResponse}; @@ -173,9 +173,9 @@ impl TestSetup { let harness = FlashblocksHarness::new().await?; let provider = harness.provider(); - let deployer = &harness.accounts().deployer; - let alice = &harness.accounts().alice; - let bob = &harness.accounts().bob; + let deployer = Account::Deployer; + let alice = Account::Alice; + let bob = Account::Bob; // DoubleCounter deployment at nonce 0 let (counter_deployment_tx, counter_address, _) = deployer @@ -193,11 +193,11 @@ impl TestSetup { let (eth_transfer_tx, eth_transfer_hash) = alice .sign_txn_request( OpTransactionRequest::default() - .from(alice.address) + .from(alice.address()) .transaction_type(TransactionType::Eip1559.into()) .gas_limit(100_000) .nonce(0) - .to(bob.address) + .to(bob.address()) .value(U256::from_str("999999999000000000000000").unwrap()) .into(), ) @@ -205,14 +205,14 @@ impl TestSetup { // Log-emitting contracts: // Deploy LogEmitterB at deployer nonce 3 - let log_emitter_b_address = deployer.address.create(3); + let log_emitter_b_address = deployer.address().create(3); let log_emitter_b_bytecode = wrap_in_init_code(LOG_EMITTER_B_RUNTIME); let (log_emitter_b_deployment_tx, _, _) = deployer .create_deployment_tx(log_emitter_b_bytecode, 3) .expect("should be able to sign LogEmitterB deployment txn"); // Deploy LogEmitterA at deployer nonce 4 (knows LogEmitterB's address) - let log_emitter_a_address = deployer.address.create(4); + let log_emitter_a_address = deployer.address().create(4); let log_emitter_a_runtime = log_emitter_a_runtime(log_emitter_b_address); let log_emitter_a_bytecode = wrap_in_init_code(&log_emitter_a_runtime); let (log_emitter_a_deployment_tx, _, _) = deployer @@ -223,7 +223,7 @@ impl TestSetup { let (log_trigger_tx, log_trigger_hash) = deployer .sign_txn_request( OpTransactionRequest::default() - .from(deployer.address) + .from(deployer.address()) .transaction_type(TransactionType::Eip1559.into()) .gas_limit(100_000) .nonce(5) @@ -236,7 +236,7 @@ impl TestSetup { let (balance_transfer_tx, _) = alice .sign_txn_request( OpTransactionRequest::default() - .from(alice.address) + .from(alice.address()) .transaction_type(TransactionType::Eip1559.into()) .gas_limit(21_000) .nonce(1) @@ -471,11 +471,8 @@ async fn test_get_transaction_by_hash_pending() -> Result<()> { .await? .expect("tx2 expected"); assert_eq!(tx2.tx_hash(), setup.txn_details.alice_eth_transfer_hash); - assert_eq!(tx2.from(), setup.harness.accounts().alice.address); - assert_eq!( - tx2.inner.inner.as_eip1559().unwrap().to().unwrap(), - setup.harness.accounts().bob.address - ); + assert_eq!(tx2.from(), Account::Alice.address()); + assert_eq!(tx2.inner.inner.as_eip1559().unwrap().to().unwrap(), Account::Bob.address()); Ok(()) } @@ -508,8 +505,8 @@ async fn test_get_transaction_count() -> Result<()> { let setup = TestSetup::new().await?; let provider = setup.harness.provider(); - let deployer_addr = setup.harness.accounts().deployer.address; - let alice_addr = setup.harness.accounts().alice.address; + let deployer_addr = Account::Deployer.address(); + let alice_addr = Account::Alice.address(); assert_eq!(provider.get_transaction_count(DEPOSIT_SENDER).pending().await?, 0); assert_eq!(provider.get_transaction_count(deployer_addr).pending().await?, 0); @@ -532,15 +529,13 @@ async fn test_eth_call() -> Result<()> { let setup = TestSetup::new().await?; let provider = setup.harness.provider(); - let accounts = setup.harness.accounts(); - // Initially, the big spend will succeed because we haven't sent the test payloads yet let big_spend = OpTransactionRequest::default() - .from(accounts.alice.address) + .from(Account::Alice.address()) .transaction_type(0) .gas_limit(200000) .nonce(0) - .to(setup.harness.accounts().bob.address) + .to(Account::Bob.address()) .value(U256::from(9999999999849942300000u128)); let res = provider.call(big_spend.clone()).block(BlockNumberOrTag::Pending.into()).await; @@ -577,11 +572,11 @@ async fn test_eth_estimate_gas() -> Result<()> { // We ensure that eth_estimate_gas will succeed because we are on plain state let send_estimate_gas = OpTransactionRequest::default() - .from(setup.harness.accounts().alice.address) + .from(Account::Alice.address()) .transaction_type(0) .gas_limit(200000) .nonce(0) - .to(setup.harness.accounts().bob.address) + .to(Account::Bob.address()) .value(U256::from(9999999999849942300000u128)) .input(TransactionInput::new(bytes!("0x"))); diff --git a/crates/client/flashblocks/tests/state.rs b/crates/client/flashblocks/tests/state.rs index 6102952c..25068b0e 100644 --- a/crates/client/flashblocks/tests/state.rs +++ b/crates/client/flashblocks/tests/state.rs @@ -4,17 +4,15 @@ use std::{sync::Arc, time::Duration}; use alloy_consensus::{Receipt, Transaction}; use alloy_eips::{BlockHashOrNumber, Encodable2718}; -use alloy_primitives::{ - Address, B256, BlockNumber, Bytes, U256, hex::FromHex, map::foldhash::HashMap, -}; +use alloy_primitives::{Address, B256, BlockNumber, Bytes, U256, hex::FromHex, map::HashMap}; use alloy_rpc_types_engine::PayloadId; use base_flashblocks::{FlashblocksAPI, FlashblocksState, PendingBlocksAPI}; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; use base_test_utils::{ - FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, LocalNodeProvider, - TestAccounts, + Account, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, + LocalNodeProvider, }; use op_alloy_consensus::OpDepositReceipt; use op_alloy_network::BlockResponse; @@ -24,26 +22,17 @@ use reth::{ transaction_pool::test_utils::TransactionBuilder, }; use reth_optimism_primitives::{OpBlock, OpReceipt, OpTransactionSigned}; -use reth_primitives_traits::{Account, Block as BlockT, RecoveredBlock}; +use reth_primitives_traits::{Account as RethAccount, Block as BlockT, RecoveredBlock}; use reth_provider::{ChainSpecProvider, StateProviderFactory}; use tokio::time::sleep; // The amount of time to wait (in milliseconds) after sending a new flashblock or canonical block // so it can be processed by the state processor const SLEEP_TIME: u64 = 10; -#[derive(Eq, PartialEq, Debug, Hash, Clone, Copy)] -enum User { - Alice, - Bob, - Charlie, -} - struct TestHarness { node: FlashblocksHarness, flashblocks: Arc>, provider: LocalNodeProvider, - user_to_address: HashMap, - user_to_private_key: HashMap, } impl TestHarness { @@ -64,67 +53,43 @@ impl TestHarness { .expect("able to recover block"); flashblocks.on_canonical_block_received(genesis_block); - let accounts: TestAccounts = node.accounts().clone(); - - let mut user_to_address = HashMap::default(); - user_to_address.insert(User::Alice, accounts.alice.address); - user_to_address.insert(User::Bob, accounts.bob.address); - user_to_address.insert(User::Charlie, accounts.charlie.address); - - let mut user_to_private_key = HashMap::default(); - user_to_private_key - .insert(User::Alice, Self::decode_private_key(accounts.alice.private_key)); - user_to_private_key.insert(User::Bob, Self::decode_private_key(accounts.bob.private_key)); - user_to_private_key - .insert(User::Charlie, Self::decode_private_key(accounts.charlie.private_key)); - - Self { node, flashblocks, provider, user_to_address, user_to_private_key } - } - - fn decode_private_key(key: &str) -> B256 { - B256::from_hex(key).expect("valid hex-encoded key") - } - - fn address(&self, u: User) -> Address { - assert!(self.user_to_address.contains_key(&u)); - self.user_to_address[&u] + Self { node, flashblocks, provider } } - fn signer(&self, u: User) -> B256 { - assert!(self.user_to_private_key.contains_key(&u)); - self.user_to_private_key[&u] + fn decode_private_key(account: Account) -> B256 { + B256::from_hex(account.private_key()).expect("valid hex-encoded key") } - fn canonical_account(&self, u: User) -> Account { + fn canonical_account(&self, account: Account) -> RethAccount { self.provider - .basic_account(&self.address(u)) + .basic_account(&account.address()) .expect("can lookup account state") .expect("should be existing account state") } - fn canonical_balance(&self, u: User) -> U256 { - self.canonical_account(u).balance + fn canonical_balance(&self, account: Account) -> U256 { + self.canonical_account(account).balance } - fn expected_pending_balance(&self, u: User, delta: u128) -> U256 { - self.canonical_balance(u) + U256::from(delta) + fn expected_pending_balance(&self, account: Account, delta: u128) -> U256 { + self.canonical_balance(account) + U256::from(delta) } - fn account_state(&self, u: User) -> Account { - let basic_account = self.canonical_account(u); + fn account_state(&self, account: Account) -> RethAccount { + let basic_account = self.canonical_account(account); let nonce = self .flashblocks .get_pending_blocks() - .get_transaction_count(self.address(u)) + .get_transaction_count(account.address()) .to::(); let balance = self .flashblocks .get_pending_blocks() - .get_balance(self.address(u)) + .get_balance(account.address()) .unwrap_or(basic_account.balance); - Account { + RethAccount { nonce: nonce + basic_account.nonce, balance, bytecode_hash: basic_account.bytecode_hash, @@ -133,14 +98,14 @@ impl TestHarness { fn build_transaction_to_send_eth( &self, - from: User, - to: User, + from: Account, + to: Account, amount: u128, ) -> OpTransactionSigned { let txn = TransactionBuilder::default() - .signer(self.signer(from)) + .signer(Self::decode_private_key(from)) .chain_id(self.provider.chain_spec().chain_id()) - .to(self.address(to)) + .to(to.address()) .nonce(self.account_state(from).nonce) .value(amount) .gas_limit(21_000) @@ -156,15 +121,15 @@ impl TestHarness { fn build_transaction_to_send_eth_with_nonce( &self, - from: User, - to: User, + from: Account, + to: Account, amount: u128, nonce: u64, ) -> OpTransactionSigned { let txn = TransactionBuilder::default() - .signer(self.signer(from)) + .signer(Self::decode_private_key(from)) .chain_id(self.provider.chain_spec().chain_id()) - .to(self.address(to)) + .to(to.address()) .nonce(nonce) .value(amount) .gas_limit(21_000) @@ -351,14 +316,14 @@ async fn test_state_overrides_persisted_across_flashblocks() { .get_pending_blocks() .get_state_overrides() .unwrap() - .contains_key(&test.address(User::Alice)) + .contains_key(&Account::Alice.address()) ); test.send_flashblock( FlashblockBuilder::new(&test, 1) .with_transactions(vec![test.build_transaction_to_send_eth( - User::Alice, - User::Bob, + Account::Alice, + Account::Bob, 100_000, )]) .build(), @@ -376,14 +341,14 @@ async fn test_state_overrides_persisted_across_flashblocks() { .get_state_overrides() .expect("should be set from txn execution"); - assert!(overrides.get(&test.address(User::Alice)).is_some()); + assert!(overrides.get(&Account::Alice.address()).is_some()); assert_eq!( overrides - .get(&test.address(User::Bob)) + .get(&Account::Bob.address()) .expect("should be set as txn receiver") .balance .expect("should be changed due to receiving funds"), - test.expected_pending_balance(User::Bob, 100_000) + test.expected_pending_balance(Account::Bob, 100_000) ); test.send_flashblock(FlashblockBuilder::new(&test, 2).build()).await; @@ -394,14 +359,14 @@ async fn test_state_overrides_persisted_across_flashblocks() { .get_state_overrides() .expect("should be set from txn execution in flashblock index 1"); - assert!(overrides.get(&test.address(User::Alice)).is_some()); + assert!(overrides.get(&Account::Alice.address()).is_some()); assert_eq!( overrides - .get(&test.address(User::Bob)) + .get(&Account::Bob.address()) .expect("should be set as txn receiver") .balance .expect("should be changed due to receiving funds"), - test.expected_pending_balance(User::Bob, 100_000) + test.expected_pending_balance(Account::Bob, 100_000) ); } @@ -429,14 +394,14 @@ async fn test_state_overrides_persisted_across_blocks() { .get_pending_blocks() .get_state_overrides() .unwrap() - .contains_key(&test.address(User::Alice)) + .contains_key(&Account::Alice.address()) ); test.send_flashblock( FlashblockBuilder::new(&test, 1) .with_transactions(vec![test.build_transaction_to_send_eth( - User::Alice, - User::Bob, + Account::Alice, + Account::Bob, 100_000, )]) .build(), @@ -454,14 +419,14 @@ async fn test_state_overrides_persisted_across_blocks() { .get_state_overrides() .expect("should be set from txn execution"); - assert!(overrides.get(&test.address(User::Alice)).is_some()); + assert!(overrides.get(&Account::Alice.address()).is_some()); assert_eq!( overrides - .get(&test.address(User::Bob)) + .get(&Account::Bob.address()) .expect("should be set as txn receiver") .balance .expect("should be changed due to receiving funds"), - test.expected_pending_balance(User::Bob, 100_000) + test.expected_pending_balance(Account::Bob, 100_000) ); test.send_flashblock( @@ -496,15 +461,15 @@ async fn test_state_overrides_persisted_across_blocks() { .get_pending_blocks() .get_state_overrides() .unwrap() - .contains_key(&test.address(User::Alice)) + .contains_key(&Account::Alice.address()) ); test.send_flashblock( FlashblockBuilder::new(&test, 1) .with_canonical_block_number(initial_block_number) .with_transactions(vec![test.build_transaction_to_send_eth( - User::Alice, - User::Bob, + Account::Alice, + Account::Bob, 100_000, )]) .build(), @@ -517,14 +482,14 @@ async fn test_state_overrides_persisted_across_blocks() { .get_state_overrides() .expect("should be set from txn execution"); - assert!(overrides.get(&test.address(User::Alice)).is_some()); + assert!(overrides.get(&Account::Alice.address()).is_some()); assert_eq!( overrides - .get(&test.address(User::Bob)) + .get(&Account::Bob.address()) .expect("should be set as txn receiver") .balance .expect("should be changed due to receiving funds"), - test.expected_pending_balance(User::Bob, 200_000) + test.expected_pending_balance(Account::Bob, 200_000) ); } @@ -549,14 +514,14 @@ async fn test_only_current_pending_state_cleared_upon_canonical_block_reorg() { .get_pending_blocks() .get_state_overrides() .unwrap() - .contains_key(&test.address(User::Alice)) + .contains_key(&Account::Alice.address()) ); test.send_flashblock( FlashblockBuilder::new(&test, 1) .with_transactions(vec![test.build_transaction_to_send_eth( - User::Alice, - User::Bob, + Account::Alice, + Account::Bob, 100_000, )]) .build(), @@ -573,14 +538,14 @@ async fn test_only_current_pending_state_cleared_upon_canonical_block_reorg() { .get_state_overrides() .expect("should be set from txn execution"); - assert!(overrides.get(&test.address(User::Alice)).is_some()); + assert!(overrides.get(&Account::Alice.address()).is_some()); assert_eq!( overrides - .get(&test.address(User::Bob)) + .get(&Account::Bob.address()) .expect("should be set as txn receiver") .balance .expect("should be changed due to receiving funds"), - test.expected_pending_balance(User::Bob, 100_000) + test.expected_pending_balance(Account::Bob, 100_000) ); test.send_flashblock(FlashblockBuilder::new_base(&test).with_canonical_block_number(1).build()) @@ -589,8 +554,8 @@ async fn test_only_current_pending_state_cleared_upon_canonical_block_reorg() { FlashblockBuilder::new(&test, 1) .with_canonical_block_number(1) .with_transactions(vec![test.build_transaction_to_send_eth( - User::Alice, - User::Bob, + Account::Alice, + Account::Bob, 100_000, )]) .build(), @@ -607,19 +572,19 @@ async fn test_only_current_pending_state_cleared_upon_canonical_block_reorg() { .get_state_overrides() .expect("should be set from txn execution"); - assert!(overrides.get(&test.address(User::Alice)).is_some()); + assert!(overrides.get(&Account::Alice.address()).is_some()); assert_eq!( overrides - .get(&test.address(User::Bob)) + .get(&Account::Bob.address()) .expect("should be set as txn receiver") .balance .expect("should be changed due to receiving funds"), - test.expected_pending_balance(User::Bob, 200_000) + test.expected_pending_balance(Account::Bob, 200_000) ); test.new_canonical_block(vec![test.build_transaction_to_send_eth_with_nonce( - User::Alice, - User::Bob, + Account::Alice, + Account::Bob, 100, 0, )]) @@ -636,14 +601,14 @@ async fn test_only_current_pending_state_cleared_upon_canonical_block_reorg() { .get_state_overrides() .expect("should be set from txn execution"); - assert!(overrides.get(&test.address(User::Alice)).is_some()); + assert!(overrides.get(&Account::Alice.address()).is_some()); assert_eq!( overrides - .get(&test.address(User::Bob)) + .get(&Account::Bob.address()) .expect("should be set as txn receiver") .balance .expect("should be changed due to receiving funds"), - test.expected_pending_balance(User::Bob, 100_000) + test.expected_pending_balance(Account::Bob, 100_000) ); } @@ -660,8 +625,8 @@ async fn test_nonce_uses_pending_canon_block_instead_of_latest() { test.send_flashblock( FlashblockBuilder::new(&test, 1) .with_transactions(vec![test.build_transaction_to_send_eth( - User::Alice, - User::Bob, + Account::Alice, + Account::Bob, 100, )]) .build(), @@ -669,25 +634,25 @@ async fn test_nonce_uses_pending_canon_block_instead_of_latest() { .await; let pending_nonce = - test.provider.basic_account(&test.address(User::Alice)).unwrap().unwrap().nonce + test.provider.basic_account(&Account::Alice.address()).unwrap().unwrap().nonce + test .flashblocks .get_pending_blocks() - .get_transaction_count(test.address(User::Alice)) + .get_transaction_count(Account::Alice.address()) .to::(); assert_eq!(pending_nonce, 1); test.new_canonical_block_without_processing(vec![ - test.build_transaction_to_send_eth_with_nonce(User::Alice, User::Bob, 100, 0), + test.build_transaction_to_send_eth_with_nonce(Account::Alice, Account::Bob, 100, 0), ]) .await; let pending_nonce = - test.provider.basic_account(&test.address(User::Alice)).unwrap().unwrap().nonce + test.provider.basic_account(&Account::Alice.address()).unwrap().unwrap().nonce + test .flashblocks .get_pending_blocks() - .get_transaction_count(test.address(User::Alice)) + .get_transaction_count(Account::Alice.address()) .to::(); // This is 2, because canon block has reached the underlying chain @@ -702,12 +667,12 @@ async fn test_nonce_uses_pending_canon_block_instead_of_latest() { let canon_block = test.flashblocks.get_pending_blocks().get_canonical_block_number(); let canon_state_provider = test.provider.state_by_block_number_or_tag(canon_block).unwrap(); let canon_nonce = - canon_state_provider.account_nonce(&test.address(User::Alice)).unwrap().unwrap(); + canon_state_provider.account_nonce(&Account::Alice.address()).unwrap().unwrap(); let pending_nonce = canon_nonce + test .flashblocks .get_pending_blocks() - .get_transaction_count(test.address(User::Alice)) + .get_transaction_count(Account::Alice.address()) .to::(); assert_eq!(pending_nonce, 1); } @@ -798,8 +763,8 @@ async fn test_non_sequential_payload_clears_pending_state() { test.send_flashblock( FlashblockBuilder::new(&test, 3) .with_transactions(vec![test.build_transaction_to_send_eth( - User::Alice, - User::Bob, + Account::Alice, + Account::Bob, 100, )]) .build(), @@ -817,8 +782,8 @@ async fn test_duplicate_flashblock_ignored() { let fb = FlashblockBuilder::new(&test, 1) .with_transactions(vec![test.build_transaction_to_send_eth( - User::Alice, - User::Bob, + Account::Alice, + Account::Bob, 100_000, )]) .build(); @@ -841,8 +806,12 @@ async fn test_progress_canonical_blocks_without_flashblocks() { assert_eq!(genesis_block.transaction_count(), 0); assert!(test.flashblocks.get_pending_blocks().get_block(true).is_none()); - test.new_canonical_block(vec![test.build_transaction_to_send_eth(User::Alice, User::Bob, 100)]) - .await; + test.new_canonical_block(vec![test.build_transaction_to_send_eth( + Account::Alice, + Account::Bob, + 100, + )]) + .await; let block_one = test.node.latest_block(); assert_eq!(block_one.number, 1); @@ -850,8 +819,8 @@ async fn test_progress_canonical_blocks_without_flashblocks() { assert!(test.flashblocks.get_pending_blocks().get_block(true).is_none()); test.new_canonical_block(vec![ - test.build_transaction_to_send_eth(User::Bob, User::Charlie, 100), - test.build_transaction_to_send_eth(User::Charlie, User::Alice, 1000), + test.build_transaction_to_send_eth(Account::Bob, Account::Charlie, 100), + test.build_transaction_to_send_eth(Account::Charlie, Account::Alice, 1000), ]) .await; diff --git a/crates/client/metering/tests/meter.rs b/crates/client/metering/tests/meter.rs index e014691b..4226b710 100644 --- a/crates/client/metering/tests/meter.rs +++ b/crates/client/metering/tests/meter.rs @@ -2,16 +2,14 @@ use std::sync::Arc; -use alloy_consensus::crypto::secp256k1::public_key_to_address; use alloy_eips::Encodable2718; use alloy_genesis::GenesisAccount; -use alloy_primitives::{Address, B256, Bytes, U256, keccak256}; +use alloy_primitives::{Address, B256, Bytes, U256, hex::FromHex, keccak256}; use base_bundles::{Bundle, ParsedBundle}; use base_metering::meter_bundle; -use base_test_utils::create_provider_factory; +use base_test_utils::{Account, create_provider_factory}; use eyre::Context; use op_alloy_consensus::OpTxEnvelope; -use rand::{SeedableRng, rngs::StdRng}; use reth::{api::NodeTypesWithDBAdapter, chainspec::EthChainSpec}; use reth_db::{DatabaseEnv, test_utils::TempDatabase}; use reth_optimism_chainspec::{BASE_MAINNET, OpChainSpec, OpChainSpecBuilder}; @@ -19,77 +17,45 @@ use reth_optimism_node::OpNode; use reth_optimism_primitives::OpTransactionSigned; use reth_primitives_traits::SealedHeader; use reth_provider::{HeaderProvider, StateProviderFactory, providers::BlockchainProvider}; -use reth_testing_utils::generators::generate_keys; use reth_transaction_pool::test_utils::TransactionBuilder; type NodeTypes = NodeTypesWithDBAdapter>>; -#[derive(Eq, PartialEq, Debug, Hash, Clone, Copy)] -enum User { - Alice, - Bob, -} - #[derive(Debug, Clone)] struct TestHarness { provider: BlockchainProvider, header: SealedHeader, chain_spec: Arc, - user_to_address: std::collections::HashMap, - user_to_private_key: std::collections::HashMap, } impl TestHarness { - fn address(&self, u: User) -> Address { - self.user_to_address[&u] - } - - fn signer(&self, u: User) -> B256 { - self.user_to_private_key[&u] + fn signer(&self, account: Account) -> B256 { + B256::from_hex(account.private_key()).expect("valid private key hex") } } -fn create_chain_spec( - seed: u64, -) -> ( - Arc, - std::collections::HashMap, - std::collections::HashMap, -) { - let keys = generate_keys(&mut StdRng::seed_from_u64(seed), 2); - - let mut addresses = std::collections::HashMap::new(); - let mut private_keys = std::collections::HashMap::new(); - - let alice_key = keys[0]; - let alice_address = public_key_to_address(alice_key.public_key()); - let alice_secret = B256::from(alice_key.secret_bytes()); - addresses.insert(User::Alice, alice_address); - private_keys.insert(User::Alice, alice_secret); - - let bob_key = keys[1]; - let bob_address = public_key_to_address(bob_key.public_key()); - let bob_secret = B256::from(bob_key.secret_bytes()); - addresses.insert(User::Bob, bob_address); - private_keys.insert(User::Bob, bob_secret); - +fn create_chain_spec() -> Arc { let genesis = BASE_MAINNET .genesis .clone() - .extend_accounts(vec![ - (alice_address, GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64))), - (bob_address, GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64))), - ]) + .extend_accounts( + Account::all() + .into_iter() + .map(|a| { + ( + a.address(), + GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64)), + ) + }) + .collect::>(), + ) .with_gas_limit(100_000_000); - let spec = - Arc::new(OpChainSpecBuilder::base_mainnet().genesis(genesis).isthmus_activated().build()); - - (spec, addresses, private_keys) + Arc::new(OpChainSpecBuilder::base_mainnet().genesis(genesis).isthmus_activated().build()) } fn setup_harness() -> eyre::Result { - let (chain_spec, user_to_address, user_to_private_key) = create_chain_spec(1337); + let chain_spec = create_chain_spec(); let factory = create_provider_factory::(chain_spec.clone()); reth_db_common::init::init_genesis(&factory).context("initializing genesis state")?; @@ -100,7 +66,7 @@ fn setup_harness() -> eyre::Result { .context("fetching genesis header")? .expect("genesis header exists"); - Ok(TestHarness { provider, header, chain_spec, user_to_address, user_to_private_key }) + Ok(TestHarness { provider, header, chain_spec }) } fn envelope_from_signed(tx: &OpTransactionSigned) -> eyre::Result { @@ -155,7 +121,7 @@ fn meter_bundle_single_transaction() -> eyre::Result<()> { let to = Address::random(); let signed_tx = TransactionBuilder::default() - .signer(harness.signer(User::Alice)) + .signer(harness.signer(Account::Alice)) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to) @@ -185,7 +151,7 @@ fn meter_bundle_single_transaction() -> eyre::Result<()> { let result = &results[0]; assert!(total_execution_time > 0); - assert_eq!(result.from_address, harness.address(User::Alice)); + assert_eq!(result.from_address, Account::Alice.address()); assert_eq!(result.to_address, Some(to)); assert_eq!(result.tx_hash, tx_hash); assert_eq!(result.gas_price, U256::from(10)); @@ -213,7 +179,7 @@ fn meter_bundle_multiple_transactions() -> eyre::Result<()> { // Create first transaction let signed_tx_1 = TransactionBuilder::default() - .signer(harness.signer(User::Alice)) + .signer(harness.signer(Account::Alice)) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to_1) @@ -229,7 +195,7 @@ fn meter_bundle_multiple_transactions() -> eyre::Result<()> { // Create second transaction let signed_tx_2 = TransactionBuilder::default() - .signer(harness.signer(User::Bob)) + .signer(harness.signer(Account::Bob)) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to_2) @@ -263,7 +229,7 @@ fn meter_bundle_multiple_transactions() -> eyre::Result<()> { // Check first transaction let result_1 = &results[0]; - assert_eq!(result_1.from_address, harness.address(User::Alice)); + assert_eq!(result_1.from_address, Account::Alice.address()); assert_eq!(result_1.to_address, Some(to_1)); assert_eq!(result_1.tx_hash, tx_hash_1); assert_eq!(result_1.gas_price, U256::from(10)); @@ -272,7 +238,7 @@ fn meter_bundle_multiple_transactions() -> eyre::Result<()> { // Check second transaction let result_2 = &results[1]; - assert_eq!(result_2.from_address, harness.address(User::Bob)); + assert_eq!(result_2.from_address, Account::Bob.address()); assert_eq!(result_2.to_address, Some(to_2)); assert_eq!(result_2.tx_hash, tx_hash_2); assert_eq!(result_2.gas_price, U256::from(15)); diff --git a/crates/client/metering/tests/meter_block.rs b/crates/client/metering/tests/meter_block.rs index 0ad45a0d..22bc93a4 100644 --- a/crates/client/metering/tests/meter_block.rs +++ b/crates/client/metering/tests/meter_block.rs @@ -2,13 +2,12 @@ use std::sync::Arc; -use alloy_consensus::{BlockHeader, Header, crypto::secp256k1::public_key_to_address}; +use alloy_consensus::{BlockHeader, Header}; use alloy_genesis::GenesisAccount; -use alloy_primitives::{Address, B256, U256}; +use alloy_primitives::{Address, B256, U256, hex::FromHex}; use base_metering::meter_block; -use base_test_utils::create_provider_factory; +use base_test_utils::{Account, create_provider_factory}; use eyre::Context; -use rand::{SeedableRng, rngs::StdRng}; use reth::{api::NodeTypesWithDBAdapter, chainspec::EthChainSpec}; use reth_db::{DatabaseEnv, test_utils::TempDatabase}; use reth_optimism_chainspec::{BASE_MAINNET, OpChainSpec, OpChainSpecBuilder}; @@ -16,17 +15,10 @@ use reth_optimism_node::OpNode; use reth_optimism_primitives::{OpBlock, OpBlockBody, OpTransactionSigned}; use reth_primitives_traits::Block as BlockT; use reth_provider::{HeaderProvider, providers::BlockchainProvider}; -use reth_testing_utils::generators::generate_keys; use reth_transaction_pool::test_utils::TransactionBuilder; type NodeTypes = NodeTypesWithDBAdapter>>; -#[derive(Eq, PartialEq, Debug, Hash, Clone, Copy)] -enum User { - Alice, - Bob, -} - #[derive(Debug, Clone)] struct TestHarness { provider: BlockchainProvider, @@ -34,47 +26,36 @@ struct TestHarness { genesis_header_number: u64, genesis_header_timestamp: u64, chain_spec: Arc, - user_to_private_key: std::collections::HashMap, } impl TestHarness { - fn signer(&self, u: User) -> B256 { - self.user_to_private_key[&u] + fn signer(&self, account: Account) -> B256 { + B256::from_hex(account.private_key()).expect("valid private key hex") } } -fn create_chain_spec(seed: u64) -> (Arc, std::collections::HashMap) { - let keys = generate_keys(&mut StdRng::seed_from_u64(seed), 2); - - let mut private_keys = std::collections::HashMap::new(); - - let alice_key = keys[0]; - let alice_address = public_key_to_address(alice_key.public_key()); - let alice_secret = B256::from(alice_key.secret_bytes()); - private_keys.insert(User::Alice, alice_secret); - - let bob_key = keys[1]; - let bob_address = public_key_to_address(bob_key.public_key()); - let bob_secret = B256::from(bob_key.secret_bytes()); - private_keys.insert(User::Bob, bob_secret); - +fn create_chain_spec() -> Arc { let genesis = BASE_MAINNET .genesis .clone() - .extend_accounts(vec![ - (alice_address, GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64))), - (bob_address, GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64))), - ]) + .extend_accounts( + Account::all() + .into_iter() + .map(|a| { + ( + a.address(), + GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64)), + ) + }) + .collect::>(), + ) .with_gas_limit(100_000_000); - let spec = - Arc::new(OpChainSpecBuilder::base_mainnet().genesis(genesis).isthmus_activated().build()); - - (spec, private_keys) + Arc::new(OpChainSpecBuilder::base_mainnet().genesis(genesis).isthmus_activated().build()) } fn setup_harness() -> eyre::Result { - let (chain_spec, user_to_private_key) = create_chain_spec(1337); + let chain_spec = create_chain_spec(); let factory = create_provider_factory::(chain_spec.clone()); reth_db_common::init::init_genesis(&factory).context("initializing genesis state")?; @@ -91,7 +72,6 @@ fn setup_harness() -> eyre::Result { genesis_header_number: header.number(), genesis_header_timestamp: header.timestamp(), chain_spec, - user_to_private_key, }) } @@ -144,7 +124,7 @@ fn meter_block_single_transaction() -> eyre::Result<()> { let to = Address::random(); let signed_tx = TransactionBuilder::default() - .signer(harness.signer(User::Alice)) + .signer(harness.signer(Account::Alice)) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to) @@ -191,7 +171,7 @@ fn meter_block_multiple_transactions() -> eyre::Result<()> { // Create first transaction from Alice let signed_tx_1 = TransactionBuilder::default() - .signer(harness.signer(User::Alice)) + .signer(harness.signer(Account::Alice)) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to_1) @@ -208,7 +188,7 @@ fn meter_block_multiple_transactions() -> eyre::Result<()> { // Create second transaction from Bob let signed_tx_2 = TransactionBuilder::default() - .signer(harness.signer(User::Bob)) + .signer(harness.signer(Account::Bob)) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to_2) @@ -268,7 +248,7 @@ fn meter_block_timing_consistency() -> eyre::Result<()> { // Create a block with one transaction let signed_tx = TransactionBuilder::default() - .signer(harness.signer(User::Alice)) + .signer(harness.signer(Account::Alice)) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(Address::random()) diff --git a/crates/client/test-utils/src/accounts.rs b/crates/client/test-utils/src/accounts.rs index 331bbded..08151545 100644 --- a/crates/client/test-utils/src/accounts.rs +++ b/crates/client/test-utils/src/accounts.rs @@ -12,29 +12,66 @@ use reth::{revm::context::TransactionType, rpc::compat::SignTxRequestError}; use crate::BASE_CHAIN_ID; -/// Hardcoded test account with a fixed private key -#[derive(Debug, Clone)] -pub struct Account { - /// Account name for easy identification - pub name: &'static str, - /// Ethereum address - pub address: Address, - /// Private key (hex string without 0x prefix) - pub private_key: &'static str, +/// Hardcoded test accounts using Anvil's deterministic keys. +/// Derived from the test mnemonic: "test test test test test test test test test test test junk" +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Account { + /// Alice (Anvil account #0) + Alice, + /// Bob (Anvil account #1) + Bob, + /// Charlie (Anvil account #2) + Charlie, + /// Deployer (Anvil account #3) + Deployer, } impl Account { + /// Returns the Ethereum address for this account. + pub const fn address(&self) -> Address { + match self { + Self::Alice => address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), + Self::Bob => address!("70997970C51812dc3A010C7d01b50e0d17dc79C8"), + Self::Charlie => address!("3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"), + Self::Deployer => address!("90F79bf6EB2c4f870365E785982E1f101E93b906"), + } + } + + /// Returns the private key (hex string without 0x prefix). + pub const fn private_key(&self) -> &'static str { + match self { + Self::Alice => "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + Self::Bob => "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", + Self::Charlie => "5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a", + Self::Deployer => "7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6", + } + } + + /// Returns all available test accounts. + pub const fn all() -> [Self; 4] { + [Self::Alice, Self::Bob, Self::Charlie, Self::Deployer] + } + + /// Constructs and returns a PrivateKeySigner for this account. + pub fn signer(&self) -> PrivateKeySigner { + let key_bytes = + hex::decode(self.private_key()).expect("should be able to decode private key"); + let key_fixed: FixedBytes<32> = FixedBytes::from_slice(&key_bytes); + PrivateKeySigner::from_bytes(&key_fixed) + .expect("should be able to build the PrivateKeySigner") + } + /// Constructs a signed CREATE transaction with a given nonce and - /// returns the signed bytes, contract address, and transaction hash + /// returns the signed bytes, contract address, and transaction hash. pub fn create_deployment_tx( &self, bytecode: Bytes, nonce: u64, ) -> Result<(Bytes, Address, TxHash)> { let tx_request = OpTransactionRequest::default() - .from(self.address) + .from(self.address()) .transaction_type(TransactionType::Eip1559.into()) - .with_gas_limit(3_000_000) // Increased for larger contracts like ERC-20 + .with_gas_limit(3_000_000) .with_max_fee_per_gas(1_000_000_000) .with_max_priority_fee_per_gas(0) .with_chain_id(BASE_CHAIN_ID) @@ -48,14 +85,14 @@ impl Account { let signed_tx = tx.into_signed(signature); let signed_tx_bytes = signed_tx.encoded_2718().into(); - let contract_address = self.address.create(signed_tx.nonce()); + let contract_address = self.address().create(signed_tx.nonce()); Ok((signed_tx_bytes, contract_address, signed_tx.hash().clone())) } - /// Sign a TransactionRequest and return the signed bytes + /// Sign a TransactionRequest and return the signed bytes. pub fn sign_txn_request(&self, tx_request: OpTransactionRequest) -> Result<(Bytes, TxHash)> { let tx_request = tx_request - .from(self.address) + .from(self.address()) .transaction_type(TransactionType::Eip1559.into()) .with_gas_limit(500_000) .with_chain_id(BASE_CHAIN_ID) @@ -71,90 +108,4 @@ impl Account { let tx_hash = signed_tx.hash(); Ok((signed_tx_bytes, tx_hash.clone())) } - - /// Constructs and returns a PrivateKeySigner for the TestAccount - pub fn signer(&self) -> PrivateKeySigner { - let key_bytes = - hex::decode(self.private_key).expect("should be able to decode private key"); - let key_fixed: FixedBytes<32> = FixedBytes::from_slice(&key_bytes); - PrivateKeySigner::from_bytes(&key_fixed) - .expect("should be able to build the PrivateKeySigner") - .into() - } -} - -/// Handy alias used throughout tests to refer to the deterministic `Account`. -pub type TestAccount = Account; - -/// Collection of all test accounts -#[derive(Debug, Clone)] -pub struct TestAccounts { - /// Alice (Anvil account #0) with a large starting balance. - pub alice: TestAccount, - /// Bob (Anvil account #1) handy for bilateral tests. - pub bob: TestAccount, - /// Charlie (Anvil account #2) used when three participants are required. - pub charlie: TestAccount, - /// Deterministic account intended for contract deployments. - pub deployer: TestAccount, -} - -impl TestAccounts { - /// Create a new instance with all test accounts - pub fn new() -> Self { - Self { alice: ALICE, bob: BOB, charlie: CHARLIE, deployer: DEPLOYER } - } - - /// Get all accounts as a vector - pub fn all(&self) -> Vec<&TestAccount> { - vec![&self.alice, &self.bob, &self.charlie, &self.deployer] - } - - /// Get account by name - pub fn get(&self, name: &str) -> Option<&TestAccount> { - match name { - "alice" => Some(&self.alice), - "bob" => Some(&self.bob), - "charlie" => Some(&self.charlie), - "deployer" => Some(&self.deployer), - _ => None, - } - } -} - -impl Default for TestAccounts { - fn default() -> Self { - Self::new() - } } - -// Hardcoded test accounts using Anvil's deterministic keys -// These are derived from the test mnemonic: "test test test test test test test test test test test junk" - -/// Alice - First test account (Anvil account #0) -pub const ALICE: TestAccount = TestAccount { - name: "Alice", - address: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), - private_key: "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", -}; - -/// Bob - Second test account (Anvil account #1) -pub const BOB: TestAccount = TestAccount { - name: "Bob", - address: address!("70997970C51812dc3A010C7d01b50e0d17dc79C8"), - private_key: "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", -}; - -/// Charlie - Third test account (Anvil account #2) -pub const CHARLIE: TestAccount = TestAccount { - name: "Charlie", - address: address!("3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"), - private_key: "5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a", -}; - -/// Deployer - Account for deploying smart contracts (Anvil account #3) -pub const DEPLOYER: TestAccount = TestAccount { - name: "Deployer", - address: address!("90F79bf6EB2c4f870365E785982E1f101E93b906"), - private_key: "7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6", -}; diff --git a/crates/client/test-utils/src/harness.rs b/crates/client/test-utils/src/harness.rs index 7a1a7e3c..e30c2560 100644 --- a/crates/client/test-utils/src/harness.rs +++ b/crates/client/test-utils/src/harness.rs @@ -23,7 +23,7 @@ use tokio::time::sleep; use crate::{ BLOCK_BUILD_DELAY_MS, BLOCK_TIME_SECONDS, GAS_LIMIT, L1_BLOCK_INFO_DEPOSIT_TX, - NODE_STARTUP_DELAY_MS, TestAccounts, + NODE_STARTUP_DELAY_MS, engine::{EngineApi, IpcEngine}, node::{LocalNode, LocalNodeProvider, OpAddOns, OpBuilder, default_launcher}, tracing::init_silenced_tracing, @@ -34,7 +34,6 @@ use crate::{ pub struct TestHarness { node: LocalNode, engine: EngineApi, - accounts: TestAccounts, } impl TestHarness { @@ -57,11 +56,10 @@ impl TestHarness { /// Build a harness from an already-running [`LocalNode`]. pub(crate) async fn from_node(node: LocalNode) -> Result { let engine = node.engine_api()?; - let accounts = TestAccounts::new(); sleep(Duration::from_millis(NODE_STARTUP_DELAY_MS)).await; - Ok(Self { node, engine, accounts }) + Ok(Self { node, engine }) } /// Return an Optimism JSON-RPC provider connected to the harness node. @@ -69,11 +67,6 @@ impl TestHarness { self.node.provider().expect("provider should always be available after node initialization") } - /// Access the deterministic test accounts backing the harness. - pub fn accounts(&self) -> &TestAccounts { - &self.accounts - } - /// Access the low-level blockchain provider for direct database queries. pub fn blockchain_provider(&self) -> LocalNodeProvider { self.node.blockchain_provider() @@ -195,18 +188,17 @@ mod tests { use alloy_provider::Provider; use super::*; + use crate::Account; + #[tokio::test] async fn test_harness_setup() -> Result<()> { let harness = TestHarness::new().await?; - assert_eq!(harness.accounts().alice.name, "Alice"); - assert_eq!(harness.accounts().bob.name, "Bob"); - let provider = harness.provider(); let chain_id = provider.get_chain_id().await?; assert_eq!(chain_id, crate::BASE_CHAIN_ID); - let alice_balance = provider.get_balance(harness.accounts().alice.address).await?; + let alice_balance = provider.get_balance(Account::Alice.address()).await?; assert!(alice_balance > U256::ZERO); let block_number = provider.get_block_number().await?; diff --git a/crates/client/test-utils/src/lib.rs b/crates/client/test-utils/src/lib.rs index df8fc534..cb81a004 100644 --- a/crates/client/test-utils/src/lib.rs +++ b/crates/client/test-utils/src/lib.rs @@ -4,7 +4,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] mod accounts; -pub use accounts::{ALICE, Account, BOB, CHARLIE, DEPLOYER, TestAccount, TestAccounts}; +pub use accounts::Account; mod constants; pub use constants::{ From 3f5fef50ee4a2e146efa66a6009372f2f1628d60 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 15:46:11 -0600 Subject: [PATCH 05/19] chore: removed dead code --- crates/client/test-utils/src/constants.rs | 39 +++----------- .../test-utils/src/flashblocks_harness.rs | 51 ++----------------- crates/client/test-utils/src/node.rs | 27 +--------- 3 files changed, 15 insertions(+), 102 deletions(-) diff --git a/crates/client/test-utils/src/constants.rs b/crates/client/test-utils/src/constants.rs index c2855e65..673e878b 100644 --- a/crates/client/test-utils/src/constants.rs +++ b/crates/client/test-utils/src/constants.rs @@ -1,63 +1,40 @@ //! Shared constants used across integration tests. -//! -//! This module centralizes configuration values and magic constants to avoid -//! duplication and make them easy to discover. use alloy_primitives::{B256, Bytes, b256, bytes}; -// Re-export NamedChain for convenient access to chain IDs. pub use reth::chainspec::NamedChain; -// ============================================================================= // Chain Configuration -// ============================================================================= -/// Chain ID for the Base Sepolia environment spun up by the harness. -/// -/// This is equivalent to `NamedChain::BaseSepolia as u64`. +/// Chain ID used for test networks (Base Sepolia). pub const BASE_CHAIN_ID: u64 = NamedChain::BaseSepolia as u64; -// ============================================================================= // Block Building -// ============================================================================= -/// Time between blocks in seconds. +/// Block time in seconds for test node configuration. pub const BLOCK_TIME_SECONDS: u64 = 2; - -/// Gas limit for blocks built by the harness. +/// Gas limit for test blocks. pub const GAS_LIMIT: u64 = 200_000_000; -// ============================================================================= // Timing / Delays -// ============================================================================= -/// Delay after node startup before the harness is ready. +/// Delay in milliseconds to wait for node startup. pub const NODE_STARTUP_DELAY_MS: u64 = 500; - -/// Delay between requesting and fetching a payload during block building. +/// Delay in milliseconds to wait for block building. pub const BLOCK_BUILD_DELAY_MS: u64 = 100; -// ============================================================================= // Engine API -// ============================================================================= -/// Default JWT secret for Engine API authentication in tests. -/// -/// This is an all-zeros secret used only for local testing. +/// All-zeros secret for local testing only. pub const DEFAULT_JWT_SECRET: &str = "0x0000000000000000000000000000000000000000000000000000000000000000"; -// ============================================================================= // L1 Block Info (OP Stack) -// ============================================================================= -/// Pre-captured L1 block info deposit transaction required by OP Stack. -/// -/// Every OP Stack block must start with an L1 block info deposit. This is a -/// sample transaction suitable for Base Sepolia tests. +/// Sample L1 block info deposit transaction for Base Sepolia tests. pub const L1_BLOCK_INFO_DEPOSIT_TX: Bytes = bytes!( "0x7ef90104a06c0c775b6b492bab9d7e81abdf27f77cafb698551226455a82f559e0f93fea3794deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8b0098999be000008dd00101c1200000000000000020000000068869d6300000000015f277f000000000000000000000000000000000000000000000000000000000d42ac290000000000000000000000000000000000000000000000000000000000000001abf52777e63959936b1bf633a2a643f0da38d63deffe49452fed1bf8a44975d50000000000000000000000005050f69a9786f081509234f1a7f4684b5e5b76c9000000000000000000000000" ); -/// Hash of the L1 block info deposit transaction. +/// Hash of the sample L1 block info deposit transaction. pub const L1_BLOCK_INFO_DEPOSIT_TX_HASH: B256 = b256!("0xba56c8b0deb460ff070f8fca8e2ee01e51a3db27841cc862fdd94cc1a47662b6"); diff --git a/crates/client/test-utils/src/flashblocks_harness.rs b/crates/client/test-utils/src/flashblocks_harness.rs index a1acaa2c..24467635 100644 --- a/crates/client/test-utils/src/flashblocks_harness.rs +++ b/crates/client/test-utils/src/flashblocks_harness.rs @@ -5,18 +5,11 @@ use std::sync::Arc; use base_flashtypes::Flashblock; use derive_more::Deref; use eyre::Result; -use futures_util::Future; -use reth::builder::NodeHandle; -use reth_e2e_test_utils::Adapter; -use reth_optimism_node::OpNode; use crate::{ harness::TestHarness, init_silenced_tracing, - node::{ - FlashblocksLocalNode, FlashblocksParts, LocalFlashblocksState, OpAddOns, OpBuilder, - default_launcher, - }, + node::{FlashblocksLocalNode, FlashblocksParts, LocalFlashblocksState}, }; /// Helper that exposes [`TestHarness`] conveniences plus Flashblocks helpers. @@ -30,33 +23,15 @@ pub struct FlashblocksHarness { impl FlashblocksHarness { /// Launch a flashblocks-enabled harness with the default launcher. pub async fn new() -> Result { - Self::with_launcher(default_launcher).await - } - - /// Launch the harness configured for manual canonical progression. - pub async fn manual_canonical() -> Result { - Self::manual_canonical_with_launcher(default_launcher).await - } - - /// Launch the harness using a custom node launcher. - pub async fn with_launcher(launcher: L) -> Result - where - L: FnOnce(OpBuilder) -> LRet, - LRet: Future, OpAddOns>>>, - { init_silenced_tracing(); - let flash_node = FlashblocksLocalNode::with_launcher(launcher).await?; + let flash_node = FlashblocksLocalNode::new().await?; Self::from_flashblocks_node(flash_node).await } - /// Launch the harness with a custom launcher while disabling automatic canonical processing. - pub async fn manual_canonical_with_launcher(launcher: L) -> Result - where - L: FnOnce(OpBuilder) -> LRet, - LRet: Future, OpAddOns>>>, - { + /// Launch the harness configured for manual canonical progression. + pub async fn manual_canonical() -> Result { init_silenced_tracing(); - let flash_node = FlashblocksLocalNode::with_manual_canonical_launcher(launcher).await?; + let flash_node = FlashblocksLocalNode::manual_canonical().await?; Self::from_flashblocks_node(flash_node).await } @@ -70,22 +45,6 @@ impl FlashblocksHarness { self.parts.send(flashblock).await } - /// Send a batch of flashblocks sequentially, awaiting each confirmation. - pub async fn send_flashblocks(&self, flashblocks: I) -> Result<()> - where - I: IntoIterator, - { - for flashblock in flashblocks { - self.send_flashblock(flashblock).await?; - } - Ok(()) - } - - /// Consume the flashblocks harness and return the underlying [`TestHarness`]. - pub fn into_inner(self) -> TestHarness { - self.inner - } - async fn from_flashblocks_node(flash_node: FlashblocksLocalNode) -> Result { let (node, parts) = flash_node.into_parts(); let inner = TestHarness::from_node(node).await?; diff --git a/crates/client/test-utils/src/node.rs b/crates/client/test-utils/src/node.rs index d62c1fa8..9a17df5a 100644 --- a/crates/client/test-utils/src/node.rs +++ b/crates/client/test-utils/src/node.rs @@ -405,31 +405,13 @@ impl fmt::Debug for FlashblocksLocalNode { impl FlashblocksLocalNode { /// Launch a flashblocks-enabled node using the default launcher. pub async fn new() -> Result { - Self::with_launcher(default_launcher).await + Self::with_launcher_inner(default_launcher, true).await } /// Builds a flashblocks-enabled node with canonical block streaming disabled so tests can call /// `FlashblocksState::on_canonical_block_received` at precise points. pub async fn manual_canonical() -> Result { - Self::with_manual_canonical_launcher(default_launcher).await - } - - /// Launch a flashblocks-enabled node with a custom launcher and canonical processing enabled. - pub async fn with_launcher(launcher: L) -> Result - where - L: FnOnce(OpBuilder) -> LRet, - LRet: Future, OpAddOns>>>, - { - Self::with_launcher_inner(launcher, true).await - } - - /// Same as [`Self::with_launcher`] but leaves canonical processing to the caller. - pub async fn with_manual_canonical_launcher(launcher: L) -> Result - where - L: FnOnce(OpBuilder) -> LRet, - LRet: Future, OpAddOns>>>, - { - Self::with_launcher_inner(launcher, false).await + Self::with_launcher_inner(default_launcher, false).await } async fn with_launcher_inner(launcher: L, process_canonical: bool) -> Result @@ -459,9 +441,4 @@ impl FlashblocksLocalNode { pub fn into_parts(self) -> (LocalNode, FlashblocksParts) { (self.node, self.parts) } - - /// Borrow the underlying [`LocalNode`]. - pub fn as_node(&self) -> &LocalNode { - &self.node - } } From 01f9d253bbc2f8f416a90aeabca257256d304b0b Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 17:11:06 -0600 Subject: [PATCH 06/19] chore: migrated test harness to support extensions --- Cargo.lock | 5 +- crates/client/flashblocks/Cargo.toml | 1 - crates/client/metering/Cargo.toml | 4 + crates/client/test-utils/Cargo.toml | 7 +- .../test-utils/src/flashblocks_harness.rs | 38 ++-- crates/client/test-utils/src/harness.rs | 69 ++++--- crates/client/test-utils/src/lib.rs | 8 +- crates/client/test-utils/src/node.rs | 190 +++++++++--------- 8 files changed, 178 insertions(+), 144 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e355c27..a91e9337 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1605,7 +1605,6 @@ dependencies = [ "reth", "reth-db", "reth-db-common", - "reth-e2e-test-utils", "reth-evm", "reth-exex", "reth-optimism-chainspec", @@ -1669,6 +1668,7 @@ dependencies = [ "reth", "reth-db", "reth-db-common", + "reth-e2e-test-utils", "reth-evm", "reth-optimism-chainspec", "reth-optimism-evm", @@ -1724,12 +1724,12 @@ dependencies = [ "alloy-signer-local", "alloy-sol-macro", "alloy-sol-types", + "base-client-primitives", "base-flashblocks", "base-flashtypes", "chrono", "derive_more", "eyre", - "futures-util", "jsonrpsee", "once_cell", "op-alloy-network", @@ -1737,7 +1737,6 @@ dependencies = [ "op-alloy-rpc-types-engine", "reth", "reth-db", - "reth-e2e-test-utils", "reth-exex", "reth-ipc", "reth-node-core", diff --git a/crates/client/flashblocks/Cargo.toml b/crates/client/flashblocks/Cargo.toml index 6b1ace83..f1b2376c 100644 --- a/crates/client/flashblocks/Cargo.toml +++ b/crates/client/flashblocks/Cargo.toml @@ -88,7 +88,6 @@ reth-optimism-node.workspace = true reth-transaction-pool = { workspace = true, features = ["test-utils"] } tokio-tungstenite.workspace = true tracing-subscriber.workspace = true -reth-e2e-test-utils.workspace = true base-test-utils.workspace = true serde_json.workspace = true futures-util.workspace = true diff --git a/crates/client/metering/Cargo.toml b/crates/client/metering/Cargo.toml index 8bbbed3a..fd13c915 100644 --- a/crates/client/metering/Cargo.toml +++ b/crates/client/metering/Cargo.toml @@ -11,6 +11,9 @@ description = "Metering RPC for Base node" [lints] workspace = true +[package.metadata.cargo-udeps.ignore] +development = ["reth-e2e-test-utils"] + [dependencies] # workspace base-bundles.workspace = true @@ -40,6 +43,7 @@ serde.workspace = true [dev-dependencies] base-test-utils.workspace = true +reth-e2e-test-utils.workspace = true reth-db = { workspace = true, features = ["test-utils"] } reth-db-common.workspace = true reth-optimism-node.workspace = true diff --git a/crates/client/test-utils/Cargo.toml b/crates/client/test-utils/Cargo.toml index 64f3b529..39ae07f2 100644 --- a/crates/client/test-utils/Cargo.toml +++ b/crates/client/test-utils/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # Project +base-client-primitives.workspace = true base-flashtypes.workspace = true base-flashblocks.workspace = true @@ -24,8 +25,7 @@ reth-optimism-primitives.workspace = true reth-optimism-rpc = { workspace = true, features = ["client"] } reth-provider.workspace = true reth-primitives-traits.workspace = true -reth-db.workspace = true -reth-e2e-test-utils.workspace = true +reth-db = { workspace = true, features = ["test-utils"] } reth-node-core.workspace = true reth-exex.workspace = true reth-tracing.workspace = true @@ -56,9 +56,6 @@ op-alloy-network.workspace = true tokio.workspace = true tokio-stream.workspace = true -# async -futures-util.workspace = true - # rpc jsonrpsee.workspace = true diff --git a/crates/client/test-utils/src/flashblocks_harness.rs b/crates/client/test-utils/src/flashblocks_harness.rs index 24467635..f856ae5f 100644 --- a/crates/client/test-utils/src/flashblocks_harness.rs +++ b/crates/client/test-utils/src/flashblocks_harness.rs @@ -1,15 +1,17 @@ //! Flashblocks-aware wrapper around [`TestHarness`] that wires in the custom RPC modules. -use std::sync::Arc; +use std::{sync::Arc, time::Duration}; use base_flashtypes::Flashblock; use derive_more::Deref; use eyre::Result; +use tokio::time::sleep; use crate::{ + NODE_STARTUP_DELAY_MS, harness::TestHarness, init_silenced_tracing, - node::{FlashblocksLocalNode, FlashblocksParts, LocalFlashblocksState}, + node::{FlashblocksParts, FlashblocksTestExtension, LocalFlashblocksState, LocalNode}, }; /// Helper that exposes [`TestHarness`] conveniences plus Flashblocks helpers. @@ -21,18 +23,14 @@ pub struct FlashblocksHarness { } impl FlashblocksHarness { - /// Launch a flashblocks-enabled harness with the default launcher. + /// Launch a flashblocks-enabled harness with automatic canonical processing. pub async fn new() -> Result { - init_silenced_tracing(); - let flash_node = FlashblocksLocalNode::new().await?; - Self::from_flashblocks_node(flash_node).await + Self::with_options(true).await } /// Launch the harness configured for manual canonical progression. pub async fn manual_canonical() -> Result { - init_silenced_tracing(); - let flash_node = FlashblocksLocalNode::manual_canonical().await?; - Self::from_flashblocks_node(flash_node).await + Self::with_options(false).await } /// Get a handle to the in-memory Flashblocks state backing the harness. @@ -45,9 +43,25 @@ impl FlashblocksHarness { self.parts.send(flashblock).await } - async fn from_flashblocks_node(flash_node: FlashblocksLocalNode) -> Result { - let (node, parts) = flash_node.into_parts(); - let inner = TestHarness::from_node(node).await?; + async fn with_options(process_canonical: bool) -> Result { + init_silenced_tracing(); + + // Create the extension and keep a reference to get parts after launch + let extension = FlashblocksTestExtension::new(process_canonical); + let parts_source = extension.clone(); + + // Launch the node with the flashblocks extension + let node = LocalNode::new(vec![Box::new(extension)]).await?; + let engine = node.engine_api()?; + + sleep(Duration::from_millis(NODE_STARTUP_DELAY_MS)).await; + + // Get the parts from the extension after node launch + let parts = parts_source.parts()?; + + // Create harness by building it directly (avoiding TestHarnessBuilder since we already have node) + let inner = TestHarness::from_parts(node, engine); + Ok(Self { inner, parts }) } } diff --git a/crates/client/test-utils/src/harness.rs b/crates/client/test-utils/src/harness.rs index e30c2560..36187975 100644 --- a/crates/client/test-utils/src/harness.rs +++ b/crates/client/test-utils/src/harness.rs @@ -7,16 +7,11 @@ use alloy_primitives::{B64, B256, Bytes}; use alloy_provider::{Provider, RootProvider}; use alloy_rpc_types::BlockNumberOrTag; use alloy_rpc_types_engine::PayloadAttributes; +use base_client_primitives::BaseNodeExtension; use eyre::{Result, eyre}; -use futures_util::Future; use op_alloy_network::Optimism; use op_alloy_rpc_types_engine::OpPayloadAttributes; -use reth::{ - builder::NodeHandle, - providers::{BlockNumReader, BlockReader, ChainSpecProvider}, -}; -use reth_e2e_test_utils::Adapter; -use reth_optimism_node::OpNode; +use reth::providers::{BlockNumReader, BlockReader, ChainSpecProvider}; use reth_optimism_primitives::OpBlock; use reth_primitives_traits::{Block as BlockT, RecoveredBlock}; use tokio::time::sleep; @@ -25,10 +20,40 @@ use crate::{ BLOCK_BUILD_DELAY_MS, BLOCK_TIME_SECONDS, GAS_LIMIT, L1_BLOCK_INFO_DEPOSIT_TX, NODE_STARTUP_DELAY_MS, engine::{EngineApi, IpcEngine}, - node::{LocalNode, LocalNodeProvider, OpAddOns, OpBuilder, default_launcher}, + node::{LocalNode, LocalNodeProvider}, tracing::init_silenced_tracing, }; +/// Builder for configuring and launching a test harness. +#[derive(Debug, Default)] +pub struct TestHarnessBuilder { + extensions: Vec>, +} + +impl TestHarnessBuilder { + /// Create a new builder with no extensions. + pub fn new() -> Self { + Self::default() + } + + /// Add an extension to be applied during node launch. + pub fn with_extension(mut self, ext: impl BaseNodeExtension + 'static) -> Self { + self.extensions.push(Box::new(ext)); + self + } + + /// Build and launch the test harness. + pub async fn build(self) -> Result { + init_silenced_tracing(); + let node = LocalNode::new(self.extensions).await?; + let engine = node.engine_api()?; + + sleep(Duration::from_millis(NODE_STARTUP_DELAY_MS)).await; + + Ok(TestHarness { node, engine }) + } +} + /// High-level façade that bundles a local node, engine API client, and common helpers. #[derive(Debug)] pub struct TestHarness { @@ -37,29 +62,21 @@ pub struct TestHarness { } impl TestHarness { - /// Launch a new harness using the default launcher configuration. + /// Launch a new harness using the default configuration (no extensions). pub async fn new() -> Result { - Self::with_launcher(default_launcher).await + TestHarnessBuilder::new().build().await } - /// Launch the harness with a custom node launcher (e.g. to tweak components). - pub async fn with_launcher(launcher: L) -> Result - where - L: FnOnce(OpBuilder) -> LRet, - LRet: Future, OpAddOns>>>, - { - init_silenced_tracing(); - let node = LocalNode::new(launcher).await?; - Self::from_node(node).await + /// Create a builder for configuring the test harness with extensions. + pub fn builder() -> TestHarnessBuilder { + TestHarnessBuilder::new() } - /// Build a harness from an already-running [`LocalNode`]. - pub(crate) async fn from_node(node: LocalNode) -> Result { - let engine = node.engine_api()?; - - sleep(Duration::from_millis(NODE_STARTUP_DELAY_MS)).await; - - Ok(Self { node, engine }) + /// Create a harness from pre-built parts. + /// + /// This is useful when you need to capture extension state before building the harness. + pub(crate) fn from_parts(node: LocalNode, engine: EngineApi) -> Self { + Self { node, engine } } /// Return an Optimism JSON-RPC provider connected to the harness node. diff --git a/crates/client/test-utils/src/lib.rs b/crates/client/test-utils/src/lib.rs index cb81a004..dd8a5e09 100644 --- a/crates/client/test-utils/src/lib.rs +++ b/crates/client/test-utils/src/lib.rs @@ -25,12 +25,14 @@ mod flashblocks_harness; pub use flashblocks_harness::FlashblocksHarness; mod harness; -pub use harness::TestHarness; +pub use harness::{TestHarness, TestHarnessBuilder}; mod node; +// Re-export BaseNodeExtension for extension authors +pub use base_client_primitives::BaseNodeExtension; pub use node::{ - FlashblocksLocalNode, FlashblocksParts, LocalFlashblocksState, LocalNode, LocalNodeProvider, - OpAddOns, OpBuilder, OpComponentsBuilder, OpTypes, default_launcher, + FlashblocksLocalNode, FlashblocksParts, FlashblocksTestExtension, LocalFlashblocksState, + LocalNode, LocalNodeProvider, }; mod tracing; diff --git a/crates/client/test-utils/src/node.rs b/crates/client/test-utils/src/node.rs index 9a17df5a..b6a6c38e 100644 --- a/crates/client/test-utils/src/node.rs +++ b/crates/client/test-utils/src/node.rs @@ -4,36 +4,32 @@ use std::{ any::Any, fmt, net::SocketAddr, + path::PathBuf, sync::{Arc, Mutex}, }; use alloy_genesis::Genesis; use alloy_provider::RootProvider; use alloy_rpc_client::RpcClient; +use base_client_primitives::{BaseNodeExtension, OpBuilder, OpProvider}; use base_flashblocks::{ EthApiExt, EthApiOverrideServer, EthPubSub, EthPubSubApiServer, FlashblocksReceiver, FlashblocksState, }; use base_flashtypes::Flashblock; use eyre::Result; -use futures_util::Future; use once_cell::sync::OnceCell; use op_alloy_network::Optimism; use reth::{ - api::{FullNodeTypesAdapter, NodeTypesWithDBAdapter}, args::{DiscoveryArgs, NetworkArgs, RpcServerArgs}, - builder::{ - Node, NodeBuilder, NodeBuilderWithComponents, NodeConfig, NodeHandle, WithLaunchContext, - }, + builder::{EngineNodeLauncher, Node, NodeBuilder, NodeConfig, NodeHandle, TreeConfig}, core::exit::NodeExitFuture, + providers::providers::BlockchainProvider, tasks::TaskManager, }; use reth_db::{ - ClientVersion, DatabaseEnv, init_db, - mdbx::DatabaseArguments, - test_utils::{ERROR_DB_CREATION, TempDatabase, tempdir_path}, + ClientVersion, DatabaseEnv, init_db, mdbx::DatabaseArguments, test_utils::tempdir_path, }; -use reth_e2e_test_utils::{Adapter, TmpDB}; use reth_exex::ExExEvent; use reth_node_core::{ args::DatadirArgs, @@ -41,14 +37,14 @@ use reth_node_core::{ }; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_node::{OpNode, args::RollupArgs}; -use reth_provider::{CanonStateSubscriptions, providers::BlockchainProvider}; +use reth_provider::CanonStateSubscriptions; use tokio::sync::{mpsc, oneshot}; use tokio_stream::StreamExt; use crate::engine::EngineApi; /// Convenience alias for the local blockchain provider type. -pub type LocalNodeProvider = BlockchainProvider>; +pub type LocalNodeProvider = OpProvider; /// Convenience alias for the Flashblocks state backing the local node. pub type LocalFlashblocksState = FlashblocksState; @@ -61,6 +57,14 @@ pub struct LocalNode { _node_exit_future: NodeExitFuture, _node: Box, _task_manager: TaskManager, + _db_path: PathBuf, +} + +impl Drop for LocalNode { + fn drop(&mut self) { + // Clean up the temporary database directory + let _ = std::fs::remove_dir_all(&self._db_path); + } } impl fmt::Debug for LocalNode { @@ -101,12 +105,16 @@ impl FlashblocksParts { } } -#[derive(Clone)] -struct FlashblocksNodeExtensions { - inner: Arc, +/// Test extension for flashblocks functionality. +/// +/// This extension wires up the flashblocks ExEx and RPC modules for testing, +/// with optional control over canonical block processing. +#[derive(Clone, Debug)] +pub struct FlashblocksTestExtension { + inner: Arc, } -struct FlashblocksNodeExtensionsInner { +struct FlashblocksTestExtensionInner { sender: mpsc::Sender<(Flashblock, oneshot::Sender<()>)>, #[allow(clippy::type_complexity)] receiver: Arc)>>>>, @@ -114,10 +122,22 @@ struct FlashblocksNodeExtensionsInner { process_canonical: bool, } -impl FlashblocksNodeExtensions { - fn new(process_canonical: bool) -> Self { +impl fmt::Debug for FlashblocksTestExtensionInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FlashblocksTestExtensionInner") + .field("process_canonical", &self.process_canonical) + .finish_non_exhaustive() + } +} + +impl FlashblocksTestExtension { + /// Create a new flashblocks test extension. + /// + /// If `process_canonical` is true, canonical blocks are automatically processed. + /// Set to false for tests that need manual control over canonical block timing. + pub fn new(process_canonical: bool) -> Self { let (sender, receiver) = mpsc::channel::<(Flashblock, oneshot::Sender<()>)>(100); - let inner = FlashblocksNodeExtensionsInner { + let inner = FlashblocksTestExtensionInner { sender, receiver: Arc::new(Mutex::new(Some(receiver))), fb_cell: Arc::new(OnceCell::new()), @@ -126,7 +146,17 @@ impl FlashblocksNodeExtensions { Self { inner: Arc::new(inner) } } - fn apply(&self, builder: OpBuilder) -> OpBuilder { + /// Get the flashblocks parts after the node has been launched. + pub fn parts(&self) -> Result { + let state = self.inner.fb_cell.get().ok_or_else(|| { + eyre::eyre!("FlashblocksState should be initialized during node launch") + })?; + Ok(FlashblocksParts { sender: self.inner.sender.clone(), state: state.clone() }) + } +} + +impl BaseNodeExtension for FlashblocksTestExtension { + fn apply(self: Box, builder: OpBuilder) -> OpBuilder { let fb_cell = self.inner.fb_cell.clone(); let receiver = self.inner.receiver.clone(); let process_canonical = self.inner.process_canonical; @@ -136,7 +166,6 @@ impl FlashblocksNodeExtensions { builder .install_exex("flashblocks-canon", move |mut ctx| { let fb_cell = fb_cell_for_exex.clone(); - let process_canonical = process_canonical; async move { let provider = ctx.provider().clone(); let fb = init_flashblocks_state(&fb_cell, &provider); @@ -202,53 +231,12 @@ impl FlashblocksNodeExtensions { Ok(()) }) } - - fn wrap_launcher(&self, launcher: L) -> impl FnOnce(OpBuilder) -> LRet - where - L: FnOnce(OpBuilder) -> LRet, - { - let extensions = self.clone(); - move |builder| { - let builder = extensions.apply(builder); - launcher(builder) - } - } - - fn parts(&self) -> Result { - let state = self.inner.fb_cell.get().ok_or_else(|| { - eyre::eyre!("FlashblocksState should be initialized during node launch") - })?; - Ok(FlashblocksParts { sender: self.inner.sender.clone(), state: state.clone() }) - } -} - -/// Optimism node types used for the local harness. -pub type OpTypes = - FullNodeTypesAdapter>>; -/// Builder that wires up the concrete node components. -pub type OpComponentsBuilder = >::ComponentsBuilder; -/// Additional services attached to the node builder. -pub type OpAddOns = >::AddOns; -/// Launcher builder used by the harness to customize node startup. -pub type OpBuilder = - WithLaunchContext>; - -/// Default launcher that is reused across the harness and integration tests. -pub async fn default_launcher( - builder: OpBuilder, -) -> eyre::Result, OpAddOns>> { - let launcher = builder.engine_api_launcher(); - builder.launch_with(launcher).await } impl LocalNode { - /// Launch a new local node using the provided launcher function. - pub async fn new(launcher: L) -> Result - where - L: FnOnce(OpBuilder) -> LRet, - LRet: Future, OpAddOns>>>, - { - build_node(launcher).await + /// Launch a new local node with the provided extensions. + pub async fn new(extensions: Vec>) -> Result { + build_node(extensions).await } /// Creates a test database with a smaller map size to reduce memory usage. @@ -258,7 +246,9 @@ impl LocalNode { /// `ENOMEM` errors when running parallel tests with `cargo test`, as the /// default 8 TB size can cause memory exhaustion when multiple test processes /// run concurrently. - fn create_test_database() -> Result>> { + /// + /// Returns the database wrapped in Arc and the path for cleanup. + fn create_test_database() -> Result<(Arc, PathBuf)> { let default_size = 100 * 1024 * 1024; // 100 MB Self::create_test_database_with_size(default_size) } @@ -268,13 +258,13 @@ impl LocalNode { /// # Arguments /// /// * `max_size` - Maximum map size in bytes. - fn create_test_database_with_size(max_size: usize) -> Result>> { + fn create_test_database_with_size(max_size: usize) -> Result<(Arc, PathBuf)> { let path = tempdir_path(); - let emsg = format!("{ERROR_DB_CREATION}: {path:?}"); + let emsg = format!("Failed to create test database at {path:?}"); let args = DatabaseArguments::new(ClientVersion::default()).with_geometry_max_size(Some(max_size)); let db = init_db(&path, args).expect(&emsg); - Ok(Arc::new(TempDatabase::new(db, path))) + Ok((Arc::new(db), path)) } /// Create an HTTP provider pointed at the node's public RPC endpoint. @@ -300,11 +290,7 @@ impl LocalNode { } } -async fn build_node(launcher: L) -> Result -where - L: FnOnce(OpBuilder) -> LRet, - LRet: Future, OpAddOns>>>, -{ +async fn build_node(extensions: Vec>) -> Result { let tasks = TaskManager::current(); let exec = tasks.executor(); @@ -327,10 +313,9 @@ where RpcServerArgs::default().with_unused_ports().with_http().with_auth_ipc().with_ws(); rpc_args.auth_ipc_path = unique_ipc_path; - let node = OpNode::new(RollupArgs::default()); + let op_node = OpNode::new(RollupArgs::default()); - let temp_db = LocalNode::create_test_database()?; - let db_path = temp_db.path().to_path_buf(); + let (db, db_path) = LocalNode::create_test_database()?; let mut node_config = NodeConfig::new(chain_spec.clone()) .with_network(network_config) @@ -342,14 +327,35 @@ where node_config.with_datadir_args(DatadirArgs { datadir: datadir_path, ..Default::default() }); let builder = NodeBuilder::new(node_config.clone()) - .with_database(temp_db) + .with_database(db) .with_launch_context(exec.clone()) .with_types_and_provider::>() - .with_components(node.components_builder()) - .with_add_ons(node.add_ons()); + .with_components(op_node.components()) + .with_add_ons(op_node.add_ons()) + .on_component_initialized(move |_ctx| Ok(())); + + // Apply all extensions + let builder = + extensions.into_iter().fold(builder, |builder, extension| extension.apply(builder)); + + // Launch with EngineNodeLauncher + let NodeHandle { node: node_handle, node_exit_future } = builder + .launch_with_fn(|builder| { + let engine_tree_config = TreeConfig::default() + .with_persistence_threshold(builder.config().engine.persistence_threshold) + .with_memory_block_buffer_target( + builder.config().engine.memory_block_buffer_target, + ); + + let launcher = EngineNodeLauncher::new( + builder.task_executor().clone(), + builder.config().datadir(), + engine_tree_config, + ); - let NodeHandle { node: node_handle, node_exit_future } = - builder.launch_with_fn(launcher).await?; + builder.launch_with(launcher) + }) + .await?; let http_api_addr = node_handle .rpc_server_handle() @@ -372,6 +378,7 @@ where _node_exit_future: node_exit_future, _node: Box::new(node_handle), _task_manager: tasks, + _db_path: db_path, }) } @@ -403,27 +410,22 @@ impl fmt::Debug for FlashblocksLocalNode { } impl FlashblocksLocalNode { - /// Launch a flashblocks-enabled node using the default launcher. + /// Launch a flashblocks-enabled node using the default configuration. pub async fn new() -> Result { - Self::with_launcher_inner(default_launcher, true).await + Self::with_options(true).await } /// Builds a flashblocks-enabled node with canonical block streaming disabled so tests can call /// `FlashblocksState::on_canonical_block_received` at precise points. pub async fn manual_canonical() -> Result { - Self::with_launcher_inner(default_launcher, false).await + Self::with_options(false).await } - async fn with_launcher_inner(launcher: L, process_canonical: bool) -> Result - where - L: FnOnce(OpBuilder) -> LRet, - LRet: Future, OpAddOns>>>, - { - let extensions = FlashblocksNodeExtensions::new(process_canonical); - let wrapped_launcher = extensions.wrap_launcher(launcher); - let node = LocalNode::new(wrapped_launcher).await?; - - let parts = extensions.parts()?; + async fn with_options(process_canonical: bool) -> Result { + let extension = FlashblocksTestExtension::new(process_canonical); + let parts_source = extension.clone(); + let node = LocalNode::new(vec![Box::new(extension)]).await?; + let parts = parts_source.parts()?; Ok(Self { node, parts }) } From 30fc46f38e0bdf5a49f39113af9015c9c0d2eae6 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 17:23:59 -0600 Subject: [PATCH 07/19] chore: update metering rpc tests to use the node harness --- Cargo.lock | 94 --------------- crates/client/flashblocks/Cargo.toml | 1 - crates/client/metering/Cargo.toml | 5 - crates/client/metering/tests/meter_rpc.rs | 134 +++++----------------- crates/client/test-utils/src/accounts.rs | 9 +- crates/client/test-utils/src/harness.rs | 7 ++ 6 files changed, 42 insertions(+), 208 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a91e9337..5fda0a2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1618,7 +1618,6 @@ dependencies = [ "reth-rpc", "reth-rpc-convert", "reth-rpc-eth-api", - "reth-testing-utils", "reth-tracing", "reth-transaction-pool", "rstest", @@ -1668,7 +1667,6 @@ dependencies = [ "reth", "reth-db", "reth-db-common", - "reth-e2e-test-utils", "reth-evm", "reth-optimism-chainspec", "reth-optimism-evm", @@ -1676,7 +1674,6 @@ dependencies = [ "reth-optimism-primitives", "reth-primitives-traits", "reth-provider", - "reth-testing-utils", "reth-transaction-pool", "serde", "tokio", @@ -7545,16 +7542,12 @@ dependencies = [ "rayon", "reth-config", "reth-consensus", - "reth-ethereum-primitives", "reth-metrics", "reth-network-p2p", "reth-network-peers", "reth-primitives-traits", - "reth-provider", "reth-storage-api", "reth-tasks", - "reth-testing-utils", - "tempfile", "thiserror 2.0.17", "tokio", "tokio-stream", @@ -7562,64 +7555,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "reth-e2e-test-utils" -version = "1.9.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" -dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-network", - "alloy-primitives", - "alloy-provider", - "alloy-rlp", - "alloy-rpc-types-engine", - "alloy-rpc-types-eth", - "alloy-signer", - "alloy-signer-local", - "derive_more", - "eyre", - "futures-util", - "jsonrpsee", - "reth-chainspec", - "reth-cli-commands", - "reth-config", - "reth-consensus", - "reth-db", - "reth-db-common", - "reth-engine-local", - "reth-engine-primitives", - "reth-ethereum-primitives", - "reth-network-api", - "reth-network-p2p", - "reth-network-peers", - "reth-node-api", - "reth-node-builder", - "reth-node-core", - "reth-node-ethereum", - "reth-payload-builder", - "reth-payload-builder-primitives", - "reth-payload-primitives", - "reth-primitives", - "reth-primitives-traits", - "reth-provider", - "reth-rpc-api", - "reth-rpc-builder", - "reth-rpc-eth-api", - "reth-rpc-server-types", - "reth-stages-types", - "reth-tasks", - "reth-tokio-util", - "reth-tracing", - "revm", - "serde_json", - "tempfile", - "tokio", - "tokio-stream", - "tracing", - "url", -] - [[package]] name = "reth-ecies" version = "1.9.3" @@ -7742,7 +7677,6 @@ dependencies = [ "parking_lot", "rayon", "reth-chain-state", - "reth-chainspec", "reth-consensus", "reth-db", "reth-engine-primitives", @@ -7757,13 +7691,9 @@ dependencies = [ "reth-primitives-traits", "reth-provider", "reth-prune", - "reth-prune-types", "reth-revm", - "reth-stages", "reth-stages-api", - "reth-static-file", "reth-tasks", - "reth-tracing", "reth-trie", "reth-trie-parallel", "reth-trie-sparse", @@ -8382,7 +8312,6 @@ dependencies = [ "auto_impl", "derive_more", "futures", - "parking_lot", "reth-consensus", "reth-eth-wire-types", "reth-ethereum-primitives", @@ -9241,7 +9170,6 @@ dependencies = [ "reth-db", "reth-db-api", "reth-errors", - "reth-ethereum-engine-primitives", "reth-ethereum-primitives", "reth-execution-types", "reth-fs-util", @@ -9257,9 +9185,7 @@ dependencies = [ "reth-trie", "reth-trie-db", "revm-database", - "revm-state", "strum 0.27.2", - "tokio", "tracing", ] @@ -9703,7 +9629,6 @@ dependencies = [ "num-traits", "rayon", "reqwest", - "reth-chainspec", "reth-codecs", "reth-config", "reth-consensus", @@ -9712,7 +9637,6 @@ dependencies = [ "reth-era", "reth-era-downloader", "reth-era-utils", - "reth-ethereum-primitives", "reth-etl", "reth-evm", "reth-execution-types", @@ -9727,10 +9651,8 @@ dependencies = [ "reth-stages-api", "reth-static-file-types", "reth-storage-errors", - "reth-testing-utils", "reth-trie", "reth-trie-db", - "tempfile", "thiserror 2.0.17", "tokio", "tracing", @@ -9866,22 +9788,6 @@ dependencies = [ "tracing-futures", ] -[[package]] -name = "reth-testing-utils" -version = "1.9.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" -dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-genesis", - "alloy-primitives", - "rand 0.8.5", - "rand 0.9.2", - "reth-ethereum-primitives", - "reth-primitives-traits", - "secp256k1 0.30.0", -] - [[package]] name = "reth-tokio-util" version = "1.9.3" diff --git a/crates/client/flashblocks/Cargo.toml b/crates/client/flashblocks/Cargo.toml index f1b2376c..41ebacb5 100644 --- a/crates/client/flashblocks/Cargo.toml +++ b/crates/client/flashblocks/Cargo.toml @@ -82,7 +82,6 @@ alloy-sol-macro = { workspace = true, features = ["json"] } alloy-sol-types.workspace = true alloy-contract.workspace = true op-alloy-consensus.workspace = true -reth-testing-utils.workspace = true reth-tracing.workspace = true reth-optimism-node.workspace = true reth-transaction-pool = { workspace = true, features = ["test-utils"] } diff --git a/crates/client/metering/Cargo.toml b/crates/client/metering/Cargo.toml index fd13c915..0a370995 100644 --- a/crates/client/metering/Cargo.toml +++ b/crates/client/metering/Cargo.toml @@ -11,9 +11,6 @@ description = "Metering RPC for Base node" [lints] workspace = true -[package.metadata.cargo-udeps.ignore] -development = ["reth-e2e-test-utils"] - [dependencies] # workspace base-bundles.workspace = true @@ -43,11 +40,9 @@ serde.workspace = true [dev-dependencies] base-test-utils.workspace = true -reth-e2e-test-utils.workspace = true reth-db = { workspace = true, features = ["test-utils"] } reth-db-common.workspace = true reth-optimism-node.workspace = true -reth-testing-utils.workspace = true reth-transaction-pool = { workspace = true, features = ["test-utils"] } alloy-genesis.workspace = true alloy-rpc-client.workspace = true diff --git a/crates/client/metering/tests/meter_rpc.rs b/crates/client/metering/tests/meter_rpc.rs index dd99a9dc..f82d8faf 100644 --- a/crates/client/metering/tests/meter_rpc.rs +++ b/crates/client/metering/tests/meter_rpc.rs @@ -1,34 +1,16 @@ //! Integration tests covering the Metering RPC surface area. -use std::{any::Any, net::SocketAddr, sync::Arc}; - use alloy_eips::Encodable2718; -use alloy_primitives::{Bytes, U256, address, b256, bytes}; +use alloy_primitives::{Bytes, U256, address, bytes}; use alloy_rpc_client::RpcClient; use base_bundles::{Bundle, MeterBundleResponse}; -use base_metering::{MeteringApiImpl, MeteringApiServer}; -use base_test_utils::{init_silenced_tracing, load_genesis}; +use base_metering::MeteringExtension; +use base_test_utils::{Account, TestHarness}; use op_alloy_consensus::OpTxEnvelope; -use reth::{ - args::{DiscoveryArgs, NetworkArgs, RpcServerArgs}, - builder::{Node, NodeBuilder, NodeConfig, NodeHandle}, - chainspec::Chain, - core::exit::NodeExitFuture, - tasks::TaskManager, -}; -use reth_optimism_chainspec::OpChainSpecBuilder; -use reth_optimism_node::{OpNode, args::RollupArgs}; use reth_optimism_primitives::OpTransactionSigned; -use reth_provider::providers::BlockchainProvider; use reth_transaction_pool::test_utils::TransactionBuilder; -struct NodeContext { - http_api_addr: SocketAddr, - _node_exit_future: NodeExitFuture, - _node: Box, -} - -// Helper function to create a Bundle with default fields +/// Helper function to create a Bundle with default fields. fn create_bundle(txs: Vec, block_number: u64, min_timestamp: Option) -> Bundle { Bundle { txs, @@ -43,66 +25,18 @@ fn create_bundle(txs: Vec, block_number: u64, min_timestamp: Option) } } -impl NodeContext { - async fn rpc_client(&self) -> eyre::Result { - let url = format!("http://{}", self.http_api_addr); - let client = RpcClient::new_http(url.parse()?); - Ok(client) - } -} - -async fn setup_node() -> eyre::Result { - init_silenced_tracing(); - let tasks = TaskManager::current(); - let exec = tasks.executor(); - const BASE_SEPOLIA_CHAIN_ID: u64 = 84532; - - let genesis = load_genesis(); - let chain_spec = Arc::new( - OpChainSpecBuilder::base_mainnet() - .genesis(genesis) - .ecotone_activated() - .chain(Chain::from(BASE_SEPOLIA_CHAIN_ID)) - .build(), - ); +/// Set up a test harness with the metering extension and return an RPC client. +async fn setup() -> eyre::Result<(TestHarness, RpcClient)> { + let harness = + TestHarness::builder().with_extension(MeteringExtension::new(true)).build().await?; - let network_config = NetworkArgs { - discovery: DiscoveryArgs { disable_discovery: true, ..DiscoveryArgs::default() }, - ..NetworkArgs::default() - }; - - let node_config = NodeConfig::new(chain_spec.clone()) - .with_network(network_config.clone()) - .with_rpc(RpcServerArgs::default().with_unused_ports().with_http()) - .with_unused_ports(); - - let node = OpNode::new(RollupArgs::default()); - - let NodeHandle { node, node_exit_future } = NodeBuilder::new(node_config.clone()) - .testing_node(exec.clone()) - .with_types_and_provider::>() - .with_components(node.components_builder()) - .with_add_ons(node.add_ons()) - .extend_rpc_modules(move |ctx| { - let metering_api = MeteringApiImpl::new(ctx.provider().clone()); - ctx.modules.merge_configured(metering_api.into_rpc())?; - Ok(()) - }) - .launch() - .await?; - - let http_api_addr = node - .rpc_server_handle() - .http_local_addr() - .ok_or_else(|| eyre::eyre!("Failed to get http api address"))?; - - Ok(NodeContext { http_api_addr, _node_exit_future: node_exit_future, _node: Box::new(node) }) + let client = harness.rpc_client()?; + Ok((harness, client)) } #[tokio::test] async fn test_meter_bundle_empty() -> eyre::Result<()> { - let node = setup_node().await?; - let client = node.rpc_client().await?; + let (_harness, client) = setup().await?; let bundle = create_bundle(vec![], 0, None); @@ -118,14 +52,10 @@ async fn test_meter_bundle_empty() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_single_transaction() -> eyre::Result<()> { - let node = setup_node().await?; - let client = node.rpc_client().await?; + let (_harness, client) = setup().await?; - // Use a funded account from genesis.json - // Account: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 - // Private key from common test accounts (Hardhat account #0) - let sender_address = address!("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"); - let sender_secret = b256!("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); + let sender_address = Account::Alice.address(); + let sender_secret = Account::Alice.signer_b256(); // Build a transaction let tx = TransactionBuilder::default() @@ -166,13 +96,10 @@ async fn test_meter_bundle_single_transaction() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_multiple_transactions() -> eyre::Result<()> { - let node = setup_node().await?; - let client = node.rpc_client().await?; + let (_harness, client) = setup().await?; - // Use funded accounts from genesis.json - // Hardhat account #0 and #1 - let address1 = address!("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"); - let secret1 = b256!("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); + let address1 = Account::Alice.address(); + let secret1 = Account::Alice.signer_b256(); let tx1_inner = TransactionBuilder::default() .signer(secret1) @@ -191,8 +118,8 @@ async fn test_meter_bundle_multiple_transactions() -> eyre::Result<()> { let tx1_bytes = Bytes::from(tx1_envelope.encoded_2718()); // Second transaction from second account - let address2 = address!("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"); - let secret2 = b256!("0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"); + let address2 = Account::Bob.address(); + let secret2 = Account::Bob.signer_b256(); let tx2_inner = TransactionBuilder::default() .signer(secret2) @@ -235,8 +162,7 @@ async fn test_meter_bundle_multiple_transactions() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_invalid_transaction() -> eyre::Result<()> { - let node = setup_node().await?; - let client = node.rpc_client().await?; + let (_harness, client) = setup().await?; let bundle = create_bundle( vec![bytes!("0xdeadbeef")], // Invalid transaction data @@ -254,8 +180,7 @@ async fn test_meter_bundle_invalid_transaction() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_uses_latest_block() -> eyre::Result<()> { - let node = setup_node().await?; - let client = node.rpc_client().await?; + let (_harness, client) = setup().await?; // Metering always uses the latest block state, regardless of bundle.block_number let bundle = create_bundle(vec![], 0, None); @@ -270,8 +195,7 @@ async fn test_meter_bundle_uses_latest_block() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_ignores_bundle_block_number() -> eyre::Result<()> { - let node = setup_node().await?; - let client = node.rpc_client().await?; + let (_harness, client) = setup().await?; // Even if bundle.block_number is different, it should use the latest block // In this test, we specify block_number=0 in the bundle @@ -293,8 +217,7 @@ async fn test_meter_bundle_ignores_bundle_block_number() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_custom_timestamp() -> eyre::Result<()> { - let node = setup_node().await?; - let client = node.rpc_client().await?; + let (_harness, client) = setup().await?; // Test that bundle.min_timestamp is used for simulation. // The timestamp affects block.timestamp in the EVM during simulation but is not @@ -313,8 +236,7 @@ async fn test_meter_bundle_custom_timestamp() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_arbitrary_block_number() -> eyre::Result<()> { - let node = setup_node().await?; - let client = node.rpc_client().await?; + let (_harness, client) = setup().await?; // Since we now ignore bundle.block_number and always use the latest block, // any block_number value should work (it's only used for bundle validity in TIPS) @@ -330,12 +252,10 @@ async fn test_meter_bundle_arbitrary_block_number() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_gas_calculations() -> eyre::Result<()> { - let node = setup_node().await?; - let client = node.rpc_client().await?; + let (_harness, client) = setup().await?; - // Use two funded accounts from genesis.json with different gas prices - let secret1 = b256!("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); - let secret2 = b256!("0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"); + let secret1 = Account::Alice.signer_b256(); + let secret2 = Account::Bob.signer_b256(); // First transaction with 3 gwei gas price let tx1_inner = TransactionBuilder::default() diff --git a/crates/client/test-utils/src/accounts.rs b/crates/client/test-utils/src/accounts.rs index 08151545..651fd77b 100644 --- a/crates/client/test-utils/src/accounts.rs +++ b/crates/client/test-utils/src/accounts.rs @@ -2,7 +2,7 @@ use alloy_consensus::{SignableTransaction, Transaction}; use alloy_eips::eip2718::Encodable2718; -use alloy_primitives::{Address, Bytes, FixedBytes, TxHash, address, hex}; +use alloy_primitives::{Address, B256, Bytes, FixedBytes, TxHash, address, hex}; use alloy_signer::SignerSync; use alloy_signer_local::PrivateKeySigner; use eyre::Result; @@ -61,6 +61,13 @@ impl Account { .expect("should be able to build the PrivateKeySigner") } + /// Returns the private key as a B256 for use with TransactionBuilder. + pub fn signer_b256(&self) -> B256 { + let key_bytes = + hex::decode(self.private_key()).expect("should be able to decode private key"); + B256::from_slice(&key_bytes) + } + /// Constructs a signed CREATE transaction with a given nonce and /// returns the signed bytes, contract address, and transaction hash. pub fn create_deployment_tx( diff --git a/crates/client/test-utils/src/harness.rs b/crates/client/test-utils/src/harness.rs index 36187975..b0196e8d 100644 --- a/crates/client/test-utils/src/harness.rs +++ b/crates/client/test-utils/src/harness.rs @@ -5,6 +5,7 @@ use std::time::Duration; use alloy_eips::{BlockHashOrNumber, eip7685::Requests}; use alloy_primitives::{B64, B256, Bytes}; use alloy_provider::{Provider, RootProvider}; +use alloy_rpc_client::RpcClient; use alloy_rpc_types::BlockNumberOrTag; use alloy_rpc_types_engine::PayloadAttributes; use base_client_primitives::BaseNodeExtension; @@ -99,6 +100,12 @@ impl TestHarness { format!("ws://{}", self.node.ws_api_addr) } + /// Return a JSON-RPC client connected to the harness node. + pub fn rpc_client(&self) -> Result { + let url = self.rpc_url().parse()?; + Ok(RpcClient::new_http(url)) + } + /// Build a block using the provided transactions and push it through the engine. pub async fn build_block_from_transactions(&self, mut transactions: Vec) -> Result<()> { // Ensure the block always starts with the required L1 block info deposit. From 985b5821c60a8a0bab4dfc3854692bb242e0fc4e Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 18:47:21 -0600 Subject: [PATCH 08/19] chore: code cleanup - Remove file structure sections from READMEs - Move build_node function body into LocalNode::new - Replace local TestHarness::signer methods with Account::signer_b256() --- README.md | 18 -- bin/node/src/main.rs | 2 - crates/client/metering/tests/meter.rs | 14 +- crates/client/metering/tests/meter_block.rs | 16 +- crates/client/test-utils/README.md | 21 -- crates/client/test-utils/src/node.rs | 209 +++++++++----------- 6 files changed, 102 insertions(+), 178 deletions(-) diff --git a/README.md b/README.md index b057bd79..73d5c1fa 100644 --- a/README.md +++ b/README.md @@ -44,24 +44,6 @@ Base Reth Node is a Reth-based Ethereum node implementation, specifically tailor > [node-reth image](https://github.com/base/node/pkgs/container/node-reth). This image bundles vanilla Reth and Base Reth and can be toggled with > `NODE_TYPE=base` or `NODE_TYPE=vanilla` -## Repository Structure - -``` -. -├── Cargo.toml # Rust workspace and package definitions -├── Cargo.lock # Dependency lockfile -├── Dockerfile # For building the Docker image -├── LICENSE # MIT License -├── README.md # This file -├── crates/ -│ ├── client/ # Node client crates (cli, rpc, flashblocks, txpool, etc.) -│ └── shared/ # Shared library crates (access-lists, flashtypes) -├── justfile # Command runner for development tasks -└── .github/ - └── workflows/ - └── ci.yml # GitHub Actions CI configuration -``` - ## Prerequisites - **Rust:** Version 1.85 or later (as specified in `Cargo.toml`). You can install Rust using [rustup](https://rustup.rs/). diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index e6791368..9863940d 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -27,11 +27,9 @@ fn main() { // Step 3: Hand the parsed CLI to the node runner so it can build and launch the Base node. cli.run(|builder, args| async move { - // Create shared flashblocks cell let flashblocks_cell: FlashblocksCell> = Arc::new(OnceCell::new()); - // Extract values needed for extensions before moving rollup_args let sequencer_rpc = args.rollup_args.sequencer.clone(); let tracing_config = args.tracing_config(); let metering_enabled = args.enable_metering; diff --git a/crates/client/metering/tests/meter.rs b/crates/client/metering/tests/meter.rs index 4226b710..aa5ce150 100644 --- a/crates/client/metering/tests/meter.rs +++ b/crates/client/metering/tests/meter.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use alloy_eips::Encodable2718; use alloy_genesis::GenesisAccount; -use alloy_primitives::{Address, B256, Bytes, U256, hex::FromHex, keccak256}; +use alloy_primitives::{Address, Bytes, U256, keccak256}; use base_bundles::{Bundle, ParsedBundle}; use base_metering::meter_bundle; use base_test_utils::{Account, create_provider_factory}; @@ -28,12 +28,6 @@ struct TestHarness { chain_spec: Arc, } -impl TestHarness { - fn signer(&self, account: Account) -> B256 { - B256::from_hex(account.private_key()).expect("valid private key hex") - } -} - fn create_chain_spec() -> Arc { let genesis = BASE_MAINNET .genesis @@ -121,7 +115,7 @@ fn meter_bundle_single_transaction() -> eyre::Result<()> { let to = Address::random(); let signed_tx = TransactionBuilder::default() - .signer(harness.signer(Account::Alice)) + .signer(Account::Alice.signer_b256()) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to) @@ -179,7 +173,7 @@ fn meter_bundle_multiple_transactions() -> eyre::Result<()> { // Create first transaction let signed_tx_1 = TransactionBuilder::default() - .signer(harness.signer(Account::Alice)) + .signer(Account::Alice.signer_b256()) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to_1) @@ -195,7 +189,7 @@ fn meter_bundle_multiple_transactions() -> eyre::Result<()> { // Create second transaction let signed_tx_2 = TransactionBuilder::default() - .signer(harness.signer(Account::Bob)) + .signer(Account::Bob.signer_b256()) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to_2) diff --git a/crates/client/metering/tests/meter_block.rs b/crates/client/metering/tests/meter_block.rs index 22bc93a4..258e9e8c 100644 --- a/crates/client/metering/tests/meter_block.rs +++ b/crates/client/metering/tests/meter_block.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use alloy_consensus::{BlockHeader, Header}; use alloy_genesis::GenesisAccount; -use alloy_primitives::{Address, B256, U256, hex::FromHex}; +use alloy_primitives::{Address, B256, U256}; use base_metering::meter_block; use base_test_utils::{Account, create_provider_factory}; use eyre::Context; @@ -28,12 +28,6 @@ struct TestHarness { chain_spec: Arc, } -impl TestHarness { - fn signer(&self, account: Account) -> B256 { - B256::from_hex(account.private_key()).expect("valid private key hex") - } -} - fn create_chain_spec() -> Arc { let genesis = BASE_MAINNET .genesis @@ -124,7 +118,7 @@ fn meter_block_single_transaction() -> eyre::Result<()> { let to = Address::random(); let signed_tx = TransactionBuilder::default() - .signer(harness.signer(Account::Alice)) + .signer(Account::Alice.signer_b256()) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to) @@ -171,7 +165,7 @@ fn meter_block_multiple_transactions() -> eyre::Result<()> { // Create first transaction from Alice let signed_tx_1 = TransactionBuilder::default() - .signer(harness.signer(Account::Alice)) + .signer(Account::Alice.signer_b256()) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to_1) @@ -188,7 +182,7 @@ fn meter_block_multiple_transactions() -> eyre::Result<()> { // Create second transaction from Bob let signed_tx_2 = TransactionBuilder::default() - .signer(harness.signer(Account::Bob)) + .signer(Account::Bob.signer_b256()) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to_2) @@ -248,7 +242,7 @@ fn meter_block_timing_consistency() -> eyre::Result<()> { // Create a block with one transaction let signed_tx = TransactionBuilder::default() - .signer(harness.signer(Account::Alice)) + .signer(Account::Alice.signer_b256()) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(Address::random()) diff --git a/crates/client/test-utils/README.md b/crates/client/test-utils/README.md index 1a425a2a..5bb44b1c 100644 --- a/crates/client/test-utils/README.md +++ b/crates/client/test-utils/README.md @@ -246,27 +246,6 @@ use base_test_utils::{ }; ``` -## File Structure - -```text -test-utils/ -├── src/ -│ ├── lib.rs # Public API and re-exports -│ ├── accounts.rs # Test account definitions -│ ├── constants.rs # Shared constants (chain ID, timing, etc.) -│ ├── contracts.rs # Solidity contract bindings -│ ├── engine.rs # EngineApi (CL wrapper) -│ ├── fixtures.rs # Genesis loading, provider factories -│ ├── flashblocks_harness.rs # FlashblocksHarness + helpers -│ ├── harness.rs # TestHarness (orchestration) -│ ├── node.rs # LocalNode (EL wrapper) -│ └── tracing.rs # Tracing initialization helpers -├── assets/ -│ └── genesis.json # Base Sepolia genesis -├── contracts/ # Solidity sources + compiled artifacts -└── Cargo.toml -``` - ## Usage in Other Crates Add to `dev-dependencies`: diff --git a/crates/client/test-utils/src/node.rs b/crates/client/test-utils/src/node.rs index b6a6c38e..5f95da0f 100644 --- a/crates/client/test-utils/src/node.rs +++ b/crates/client/test-utils/src/node.rs @@ -236,34 +236,103 @@ impl BaseNodeExtension for FlashblocksTestExtension { impl LocalNode { /// Launch a new local node with the provided extensions. pub async fn new(extensions: Vec>) -> Result { - build_node(extensions).await - } + let tasks = TaskManager::current(); + let exec = tasks.executor(); - /// Creates a test database with a smaller map size to reduce memory usage. - /// - /// Unlike `NodeBuilder::testing_node()` which hardcodes an 8 TB map size, - /// this method configures the database with a 100 MB map size. This prevents - /// `ENOMEM` errors when running parallel tests with `cargo test`, as the - /// default 8 TB size can cause memory exhaustion when multiple test processes - /// run concurrently. - /// - /// Returns the database wrapped in Arc and the path for cleanup. - fn create_test_database() -> Result<(Arc, PathBuf)> { - let default_size = 100 * 1024 * 1024; // 100 MB - Self::create_test_database_with_size(default_size) + let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json"))?; + let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); + + let network_config = NetworkArgs { + discovery: DiscoveryArgs { disable_discovery: true, ..DiscoveryArgs::default() }, + ..NetworkArgs::default() + }; + + let unique_ipc_path = format!( + "/tmp/reth_engine_api_{}_{}_{:?}.ipc", + std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos(), + std::process::id(), + std::thread::current().id() + ); + + let mut rpc_args = + RpcServerArgs::default().with_unused_ports().with_http().with_auth_ipc().with_ws(); + rpc_args.auth_ipc_path = unique_ipc_path; + + let op_node = OpNode::new(RollupArgs::default()); + + let (db, db_path) = Self::create_test_database()?; + + let mut node_config = NodeConfig::new(chain_spec.clone()) + .with_network(network_config) + .with_rpc(rpc_args) + .with_unused_ports(); + + let datadir_path = MaybePlatformPath::::from(db_path.clone()); + node_config = node_config + .with_datadir_args(DatadirArgs { datadir: datadir_path, ..Default::default() }); + + let builder = NodeBuilder::new(node_config.clone()) + .with_database(db) + .with_launch_context(exec.clone()) + .with_types_and_provider::>() + .with_components(op_node.components()) + .with_add_ons(op_node.add_ons()) + .on_component_initialized(move |_ctx| Ok(())); + + // Apply all extensions + let builder = + extensions.into_iter().fold(builder, |builder, extension| extension.apply(builder)); + + // Launch with EngineNodeLauncher + let NodeHandle { node: node_handle, node_exit_future } = builder + .launch_with_fn(|builder| { + let engine_tree_config = TreeConfig::default() + .with_persistence_threshold(builder.config().engine.persistence_threshold) + .with_memory_block_buffer_target( + builder.config().engine.memory_block_buffer_target, + ); + + let launcher = EngineNodeLauncher::new( + builder.task_executor().clone(), + builder.config().datadir(), + engine_tree_config, + ); + + builder.launch_with(launcher) + }) + .await?; + + let http_api_addr = node_handle + .rpc_server_handle() + .http_local_addr() + .ok_or_else(|| eyre::eyre!("HTTP RPC server failed to bind to address"))?; + + let ws_api_addr = node_handle + .rpc_server_handle() + .ws_local_addr() + .ok_or_else(|| eyre::eyre!("Failed to get websocket api address"))?; + + let engine_ipc_path = node_config.rpc.auth_ipc_path; + let provider = node_handle.provider().clone(); + + Ok(Self { + http_api_addr, + ws_api_addr, + engine_ipc_path, + provider, + _node_exit_future: node_exit_future, + _node: Box::new(node_handle), + _task_manager: tasks, + _db_path: db_path, + }) } - /// Creates a test database with a configurable map size to reduce memory usage. - /// - /// # Arguments - /// - /// * `max_size` - Maximum map size in bytes. - fn create_test_database_with_size(max_size: usize) -> Result<(Arc, PathBuf)> { + /// Creates a test database with a 100 MB map size (vs reth's default 8 TB). + fn create_test_database() -> Result<(Arc, PathBuf)> { let path = tempdir_path(); - let emsg = format!("Failed to create test database at {path:?}"); - let args = - DatabaseArguments::new(ClientVersion::default()).with_geometry_max_size(Some(max_size)); - let db = init_db(&path, args).expect(&emsg); + let args = DatabaseArguments::new(ClientVersion::default()) + .with_geometry_max_size(Some(100 * 1024 * 1024)); + let db = init_db(&path, args).expect("Failed to create test database"); Ok((Arc::new(db), path)) } @@ -290,98 +359,6 @@ impl LocalNode { } } -async fn build_node(extensions: Vec>) -> Result { - let tasks = TaskManager::current(); - let exec = tasks.executor(); - - let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json"))?; - let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); - - let network_config = NetworkArgs { - discovery: DiscoveryArgs { disable_discovery: true, ..DiscoveryArgs::default() }, - ..NetworkArgs::default() - }; - - let unique_ipc_path = format!( - "/tmp/reth_engine_api_{}_{}_{:?}.ipc", - std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos(), - std::process::id(), - std::thread::current().id() - ); - - let mut rpc_args = - RpcServerArgs::default().with_unused_ports().with_http().with_auth_ipc().with_ws(); - rpc_args.auth_ipc_path = unique_ipc_path; - - let op_node = OpNode::new(RollupArgs::default()); - - let (db, db_path) = LocalNode::create_test_database()?; - - let mut node_config = NodeConfig::new(chain_spec.clone()) - .with_network(network_config) - .with_rpc(rpc_args) - .with_unused_ports(); - - let datadir_path = MaybePlatformPath::::from(db_path.clone()); - node_config = - node_config.with_datadir_args(DatadirArgs { datadir: datadir_path, ..Default::default() }); - - let builder = NodeBuilder::new(node_config.clone()) - .with_database(db) - .with_launch_context(exec.clone()) - .with_types_and_provider::>() - .with_components(op_node.components()) - .with_add_ons(op_node.add_ons()) - .on_component_initialized(move |_ctx| Ok(())); - - // Apply all extensions - let builder = - extensions.into_iter().fold(builder, |builder, extension| extension.apply(builder)); - - // Launch with EngineNodeLauncher - let NodeHandle { node: node_handle, node_exit_future } = builder - .launch_with_fn(|builder| { - let engine_tree_config = TreeConfig::default() - .with_persistence_threshold(builder.config().engine.persistence_threshold) - .with_memory_block_buffer_target( - builder.config().engine.memory_block_buffer_target, - ); - - let launcher = EngineNodeLauncher::new( - builder.task_executor().clone(), - builder.config().datadir(), - engine_tree_config, - ); - - builder.launch_with(launcher) - }) - .await?; - - let http_api_addr = node_handle - .rpc_server_handle() - .http_local_addr() - .ok_or_else(|| eyre::eyre!("HTTP RPC server failed to bind to address"))?; - - let ws_api_addr = node_handle - .rpc_server_handle() - .ws_local_addr() - .ok_or_else(|| eyre::eyre!("Failed to get websocket api address"))?; - - let engine_ipc_path = node_config.rpc.auth_ipc_path; - let provider = node_handle.provider().clone(); - - Ok(LocalNode { - http_api_addr, - ws_api_addr, - engine_ipc_path, - provider, - _node_exit_future: node_exit_future, - _node: Box::new(node_handle), - _task_manager: tasks, - _db_path: db_path, - }) -} - fn init_flashblocks_state( cell: &Arc>>, provider: &LocalNodeProvider, From 1882a183b841e4942ab3991429b21b800c6ccae9 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 19:13:07 -0600 Subject: [PATCH 09/19] chore: migrate metering non-rpc tests --- crates/client/metering/tests/meter.rs | 98 ++++++------- crates/client/metering/tests/meter_block.rs | 136 ++++++++---------- .../test-utils/src/flashblocks_harness.rs | 8 +- crates/client/test-utils/src/harness.rs | 30 +++- crates/client/test-utils/src/node.rs | 16 ++- 5 files changed, 148 insertions(+), 140 deletions(-) diff --git a/crates/client/metering/tests/meter.rs b/crates/client/metering/tests/meter.rs index aa5ce150..56b10f6f 100644 --- a/crates/client/metering/tests/meter.rs +++ b/crates/client/metering/tests/meter.rs @@ -6,28 +6,16 @@ use alloy_eips::Encodable2718; use alloy_genesis::GenesisAccount; use alloy_primitives::{Address, Bytes, U256, keccak256}; use base_bundles::{Bundle, ParsedBundle}; -use base_metering::meter_bundle; -use base_test_utils::{Account, create_provider_factory}; +use base_metering::{MeteringExtension, meter_bundle}; +use base_test_utils::{Account, TestHarness}; use eyre::Context; use op_alloy_consensus::OpTxEnvelope; -use reth::{api::NodeTypesWithDBAdapter, chainspec::EthChainSpec}; -use reth_db::{DatabaseEnv, test_utils::TempDatabase}; +use reth::chainspec::EthChainSpec; use reth_optimism_chainspec::{BASE_MAINNET, OpChainSpec, OpChainSpecBuilder}; -use reth_optimism_node::OpNode; use reth_optimism_primitives::OpTransactionSigned; -use reth_primitives_traits::SealedHeader; -use reth_provider::{HeaderProvider, StateProviderFactory, providers::BlockchainProvider}; +use reth_provider::{HeaderProvider, StateProviderFactory}; use reth_transaction_pool::test_utils::TransactionBuilder; -type NodeTypes = NodeTypesWithDBAdapter>>; - -#[derive(Debug, Clone)] -struct TestHarness { - provider: BlockchainProvider, - header: SealedHeader, - chain_spec: Arc, -} - fn create_chain_spec() -> Arc { let genesis = BASE_MAINNET .genesis @@ -48,19 +36,13 @@ fn create_chain_spec() -> Arc { Arc::new(OpChainSpecBuilder::base_mainnet().genesis(genesis).isthmus_activated().build()) } -fn setup_harness() -> eyre::Result { +async fn setup() -> eyre::Result { let chain_spec = create_chain_spec(); - let factory = create_provider_factory::(chain_spec.clone()); - - reth_db_common::init::init_genesis(&factory).context("initializing genesis state")?; - - let provider = BlockchainProvider::new(factory.clone()).context("creating provider")?; - let header = provider - .sealed_header(0) - .context("fetching genesis header")? - .expect("genesis header exists"); - - Ok(TestHarness { provider, header, chain_spec }) + TestHarness::builder() + .with_chain_spec(chain_spec) + .with_extension(MeteringExtension::new(true)) + .build() + .await } fn envelope_from_signed(tx: &OpTransactionSigned) -> eyre::Result { @@ -85,19 +67,21 @@ fn create_parsed_bundle(envelopes: Vec) -> eyre::Result eyre::Result<()> { - let harness = setup_harness()?; +#[tokio::test] +async fn meter_bundle_empty_transactions() -> eyre::Result<()> { + let harness = setup().await?; + + let provider = harness.blockchain_provider(); + let header = provider.sealed_header(0)?.expect("genesis header exists"); + let chain_spec = harness.chain_spec(); - let state_provider = harness - .provider - .state_by_block_hash(harness.header.hash()) - .context("getting state provider")?; + let state_provider = + provider.state_by_block_hash(header.hash()).context("getting state provider")?; let parsed_bundle = create_parsed_bundle(Vec::new())?; let (results, total_gas_used, total_gas_fees, bundle_hash, total_execution_time) = - meter_bundle(state_provider, harness.chain_spec.clone(), parsed_bundle, &harness.header)?; + meter_bundle(state_provider, chain_spec, parsed_bundle, &header)?; assert!(results.is_empty()); assert_eq!(total_gas_used, 0); @@ -109,14 +93,18 @@ fn meter_bundle_empty_transactions() -> eyre::Result<()> { Ok(()) } -#[test] -fn meter_bundle_single_transaction() -> eyre::Result<()> { - let harness = setup_harness()?; +#[tokio::test] +async fn meter_bundle_single_transaction() -> eyre::Result<()> { + let harness = setup().await?; + + let provider = harness.blockchain_provider(); + let header = provider.sealed_header(0)?.expect("genesis header exists"); + let chain_spec = harness.chain_spec(); let to = Address::random(); let signed_tx = TransactionBuilder::default() .signer(Account::Alice.signer_b256()) - .chain_id(harness.chain_spec.chain_id()) + .chain_id(chain_spec.chain_id()) .nonce(0) .to(to) .value(1_000) @@ -131,15 +119,13 @@ fn meter_bundle_single_transaction() -> eyre::Result<()> { let envelope = envelope_from_signed(&tx)?; let tx_hash = envelope.tx_hash(); - let state_provider = harness - .provider - .state_by_block_hash(harness.header.hash()) - .context("getting state provider")?; + let state_provider = + provider.state_by_block_hash(header.hash()).context("getting state provider")?; let parsed_bundle = create_parsed_bundle(vec![envelope.clone()])?; let (results, total_gas_used, total_gas_fees, bundle_hash, total_execution_time) = - meter_bundle(state_provider, harness.chain_spec.clone(), parsed_bundle, &harness.header)?; + meter_bundle(state_provider, chain_spec, parsed_bundle, &header)?; assert_eq!(results.len(), 1); let result = &results[0]; @@ -164,9 +150,13 @@ fn meter_bundle_single_transaction() -> eyre::Result<()> { Ok(()) } -#[test] -fn meter_bundle_multiple_transactions() -> eyre::Result<()> { - let harness = setup_harness()?; +#[tokio::test] +async fn meter_bundle_multiple_transactions() -> eyre::Result<()> { + let harness = setup().await?; + + let provider = harness.blockchain_provider(); + let header = provider.sealed_header(0)?.expect("genesis header exists"); + let chain_spec = harness.chain_spec(); let to_1 = Address::random(); let to_2 = Address::random(); @@ -174,7 +164,7 @@ fn meter_bundle_multiple_transactions() -> eyre::Result<()> { // Create first transaction let signed_tx_1 = TransactionBuilder::default() .signer(Account::Alice.signer_b256()) - .chain_id(harness.chain_spec.chain_id()) + .chain_id(chain_spec.chain_id()) .nonce(0) .to(to_1) .value(1_000) @@ -190,7 +180,7 @@ fn meter_bundle_multiple_transactions() -> eyre::Result<()> { // Create second transaction let signed_tx_2 = TransactionBuilder::default() .signer(Account::Bob.signer_b256()) - .chain_id(harness.chain_spec.chain_id()) + .chain_id(chain_spec.chain_id()) .nonce(0) .to(to_2) .value(2_000) @@ -208,15 +198,13 @@ fn meter_bundle_multiple_transactions() -> eyre::Result<()> { let tx_hash_1 = envelope_1.tx_hash(); let tx_hash_2 = envelope_2.tx_hash(); - let state_provider = harness - .provider - .state_by_block_hash(harness.header.hash()) - .context("getting state provider")?; + let state_provider = + provider.state_by_block_hash(header.hash()).context("getting state provider")?; let parsed_bundle = create_parsed_bundle(vec![envelope_1.clone(), envelope_2.clone()])?; let (results, total_gas_used, total_gas_fees, bundle_hash, total_execution_time) = - meter_bundle(state_provider, harness.chain_spec.clone(), parsed_bundle, &harness.header)?; + meter_bundle(state_provider, chain_spec, parsed_bundle, &header)?; assert_eq!(results.len(), 2); assert!(total_execution_time > 0); diff --git a/crates/client/metering/tests/meter_block.rs b/crates/client/metering/tests/meter_block.rs index 258e9e8c..2a081289 100644 --- a/crates/client/metering/tests/meter_block.rs +++ b/crates/client/metering/tests/meter_block.rs @@ -5,29 +5,15 @@ use std::sync::Arc; use alloy_consensus::{BlockHeader, Header}; use alloy_genesis::GenesisAccount; use alloy_primitives::{Address, B256, U256}; -use base_metering::meter_block; -use base_test_utils::{Account, create_provider_factory}; -use eyre::Context; -use reth::{api::NodeTypesWithDBAdapter, chainspec::EthChainSpec}; -use reth_db::{DatabaseEnv, test_utils::TempDatabase}; +use base_metering::{MeteringExtension, meter_block}; +use base_test_utils::{Account, TestHarness}; +use reth::chainspec::EthChainSpec; use reth_optimism_chainspec::{BASE_MAINNET, OpChainSpec, OpChainSpecBuilder}; -use reth_optimism_node::OpNode; use reth_optimism_primitives::{OpBlock, OpBlockBody, OpTransactionSigned}; use reth_primitives_traits::Block as BlockT; -use reth_provider::{HeaderProvider, providers::BlockchainProvider}; +use reth_provider::HeaderProvider; use reth_transaction_pool::test_utils::TransactionBuilder; -type NodeTypes = NodeTypesWithDBAdapter>>; - -#[derive(Debug, Clone)] -struct TestHarness { - provider: BlockchainProvider, - genesis_header_hash: B256, - genesis_header_number: u64, - genesis_header_timestamp: u64, - chain_spec: Arc, -} - fn create_chain_spec() -> Arc { let genesis = BASE_MAINNET .genesis @@ -48,35 +34,26 @@ fn create_chain_spec() -> Arc { Arc::new(OpChainSpecBuilder::base_mainnet().genesis(genesis).isthmus_activated().build()) } -fn setup_harness() -> eyre::Result { +async fn setup() -> eyre::Result { let chain_spec = create_chain_spec(); - let factory = create_provider_factory::(chain_spec.clone()); - - reth_db_common::init::init_genesis(&factory).context("initializing genesis state")?; - - let provider = BlockchainProvider::new(factory.clone()).context("creating provider")?; - let header = provider - .sealed_header(0) - .context("fetching genesis header")? - .expect("genesis header exists"); - - Ok(TestHarness { - provider, - genesis_header_hash: header.hash(), - genesis_header_number: header.number(), - genesis_header_timestamp: header.timestamp(), - chain_spec, - }) + TestHarness::builder() + .with_chain_spec(chain_spec) + .with_extension(MeteringExtension::new(true)) + .build() + .await } fn create_block_with_transactions( harness: &TestHarness, transactions: Vec, -) -> OpBlock { +) -> eyre::Result { + let provider = harness.blockchain_provider(); + let genesis = provider.sealed_header(0)?.expect("genesis header exists"); + let header = Header { - parent_hash: harness.genesis_header_hash, - number: harness.genesis_header_number + 1, - timestamp: harness.genesis_header_timestamp + 2, + parent_hash: genesis.hash(), + number: genesis.number() + 1, + timestamp: genesis.timestamp() + 2, gas_limit: 30_000_000, beneficiary: Address::random(), base_fee_per_gas: Some(1), @@ -87,16 +64,16 @@ fn create_block_with_transactions( let body = OpBlockBody { transactions, ommers: vec![], withdrawals: None }; - OpBlock::new(header, body) + Ok(OpBlock::new(header, body)) } -#[test] -fn meter_block_empty_transactions() -> eyre::Result<()> { - let harness = setup_harness()?; +#[tokio::test] +async fn meter_block_empty_transactions() -> eyre::Result<()> { + let harness = setup().await?; - let block = create_block_with_transactions(&harness, vec![]); + let block = create_block_with_transactions(&harness, vec![])?; - let response = meter_block(harness.provider.clone(), harness.chain_spec.clone(), &block)?; + let response = meter_block(harness.blockchain_provider(), harness.chain_spec(), &block)?; assert_eq!(response.block_hash, block.header().hash_slow()); assert_eq!(response.block_number, block.header().number()); @@ -112,14 +89,15 @@ fn meter_block_empty_transactions() -> eyre::Result<()> { Ok(()) } -#[test] -fn meter_block_single_transaction() -> eyre::Result<()> { - let harness = setup_harness()?; +#[tokio::test] +async fn meter_block_single_transaction() -> eyre::Result<()> { + let harness = setup().await?; + let chain_spec = harness.chain_spec(); let to = Address::random(); let signed_tx = TransactionBuilder::default() .signer(Account::Alice.signer_b256()) - .chain_id(harness.chain_spec.chain_id()) + .chain_id(chain_spec.chain_id()) .nonce(0) .to(to) .value(1_000) @@ -132,9 +110,9 @@ fn meter_block_single_transaction() -> eyre::Result<()> { OpTransactionSigned::Eip1559(signed_tx.as_eip1559().expect("eip1559 transaction").clone()); let tx_hash = tx.tx_hash(); - let block = create_block_with_transactions(&harness, vec![tx]); + let block = create_block_with_transactions(&harness, vec![tx])?; - let response = meter_block(harness.provider.clone(), harness.chain_spec.clone(), &block)?; + let response = meter_block(harness.blockchain_provider(), chain_spec, &block)?; assert_eq!(response.block_hash, block.header().hash_slow()); assert_eq!(response.block_number, block.header().number()); @@ -156,9 +134,10 @@ fn meter_block_single_transaction() -> eyre::Result<()> { Ok(()) } -#[test] -fn meter_block_multiple_transactions() -> eyre::Result<()> { - let harness = setup_harness()?; +#[tokio::test] +async fn meter_block_multiple_transactions() -> eyre::Result<()> { + let harness = setup().await?; + let chain_spec = harness.chain_spec(); let to_1 = Address::random(); let to_2 = Address::random(); @@ -166,7 +145,7 @@ fn meter_block_multiple_transactions() -> eyre::Result<()> { // Create first transaction from Alice let signed_tx_1 = TransactionBuilder::default() .signer(Account::Alice.signer_b256()) - .chain_id(harness.chain_spec.chain_id()) + .chain_id(chain_spec.chain_id()) .nonce(0) .to(to_1) .value(1_000) @@ -183,7 +162,7 @@ fn meter_block_multiple_transactions() -> eyre::Result<()> { // Create second transaction from Bob let signed_tx_2 = TransactionBuilder::default() .signer(Account::Bob.signer_b256()) - .chain_id(harness.chain_spec.chain_id()) + .chain_id(chain_spec.chain_id()) .nonce(0) .to(to_2) .value(2_000) @@ -197,9 +176,9 @@ fn meter_block_multiple_transactions() -> eyre::Result<()> { ); let tx_hash_2 = tx_2.tx_hash(); - let block = create_block_with_transactions(&harness, vec![tx_1, tx_2]); + let block = create_block_with_transactions(&harness, vec![tx_1, tx_2])?; - let response = meter_block(harness.provider.clone(), harness.chain_spec.clone(), &block)?; + let response = meter_block(harness.blockchain_provider(), chain_spec, &block)?; assert_eq!(response.block_hash, block.header().hash_slow()); assert_eq!(response.block_number, block.header().number()); @@ -236,14 +215,15 @@ fn meter_block_multiple_transactions() -> eyre::Result<()> { Ok(()) } -#[test] -fn meter_block_timing_consistency() -> eyre::Result<()> { - let harness = setup_harness()?; +#[tokio::test] +async fn meter_block_timing_consistency() -> eyre::Result<()> { + let harness = setup().await?; + let chain_spec = harness.chain_spec(); // Create a block with one transaction let signed_tx = TransactionBuilder::default() .signer(Account::Alice.signer_b256()) - .chain_id(harness.chain_spec.chain_id()) + .chain_id(chain_spec.chain_id()) .nonce(0) .to(Address::random()) .value(1_000) @@ -255,9 +235,9 @@ fn meter_block_timing_consistency() -> eyre::Result<()> { let tx = OpTransactionSigned::Eip1559(signed_tx.as_eip1559().expect("eip1559 transaction").clone()); - let block = create_block_with_transactions(&harness, vec![tx]); + let block = create_block_with_transactions(&harness, vec![tx])?; - let response = meter_block(harness.provider.clone(), harness.chain_spec.clone(), &block)?; + let response = meter_block(harness.blockchain_provider(), chain_spec, &block)?; // Verify timing invariants assert!(response.signer_recovery_time_us > 0, "signer recovery time must be positive"); @@ -276,16 +256,19 @@ fn meter_block_timing_consistency() -> eyre::Result<()> { // Error Path Tests // ============================================================================ -#[test] -fn meter_block_parent_header_not_found() -> eyre::Result<()> { - let harness = setup_harness()?; +#[tokio::test] +async fn meter_block_parent_header_not_found() -> eyre::Result<()> { + let harness = setup().await?; + let chain_spec = harness.chain_spec(); + let provider = harness.blockchain_provider(); + let genesis = provider.sealed_header(0)?.expect("genesis header exists"); // Create a block that references a non-existent parent let fake_parent_hash = B256::random(); let header = Header { parent_hash: fake_parent_hash, // This parent doesn't exist number: 999, - timestamp: harness.genesis_header_timestamp + 2, + timestamp: genesis.timestamp() + 2, gas_limit: 30_000_000, beneficiary: Address::random(), base_fee_per_gas: Some(1), @@ -296,7 +279,7 @@ fn meter_block_parent_header_not_found() -> eyre::Result<()> { let body = OpBlockBody { transactions: vec![], ommers: vec![], withdrawals: None }; let block = OpBlock::new(header, body); - let result = meter_block(harness.provider.clone(), harness.chain_spec.clone(), &block); + let result = meter_block(provider, chain_spec, &block); assert!(result.is_err(), "should fail when parent header is not found"); let err = result.unwrap_err(); @@ -310,16 +293,17 @@ fn meter_block_parent_header_not_found() -> eyre::Result<()> { Ok(()) } -#[test] -fn meter_block_invalid_transaction_signature() -> eyre::Result<()> { +#[tokio::test] +async fn meter_block_invalid_transaction_signature() -> eyre::Result<()> { use alloy_consensus::TxEip1559; use alloy_primitives::Signature; - let harness = setup_harness()?; + let harness = setup().await?; + let chain_spec = harness.chain_spec(); // Create a transaction with an invalid signature let tx = TxEip1559 { - chain_id: harness.chain_spec.chain_id(), + chain_id: chain_spec.chain_id(), nonce: 0, gas_limit: 21_000, max_fee_per_gas: 10, @@ -337,9 +321,9 @@ fn meter_block_invalid_transaction_signature() -> eyre::Result<()> { let signed_tx = alloy_consensus::Signed::new_unchecked(tx, invalid_signature, B256::random()); let op_tx = OpTransactionSigned::Eip1559(signed_tx); - let block = create_block_with_transactions(&harness, vec![op_tx]); + let block = create_block_with_transactions(&harness, vec![op_tx])?; - let result = meter_block(harness.provider.clone(), harness.chain_spec.clone(), &block); + let result = meter_block(harness.blockchain_provider(), chain_spec, &block); assert!(result.is_err(), "should fail when transaction has invalid signature"); let err = result.unwrap_err(); diff --git a/crates/client/test-utils/src/flashblocks_harness.rs b/crates/client/test-utils/src/flashblocks_harness.rs index f856ae5f..18f1219c 100644 --- a/crates/client/test-utils/src/flashblocks_harness.rs +++ b/crates/client/test-utils/src/flashblocks_harness.rs @@ -2,9 +2,11 @@ use std::{sync::Arc, time::Duration}; +use alloy_genesis::Genesis; use base_flashtypes::Flashblock; use derive_more::Deref; use eyre::Result; +use reth_optimism_chainspec::OpChainSpec; use tokio::time::sleep; use crate::{ @@ -46,12 +48,16 @@ impl FlashblocksHarness { async fn with_options(process_canonical: bool) -> Result { init_silenced_tracing(); + // Load default chain spec + let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json"))?; + let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); + // Create the extension and keep a reference to get parts after launch let extension = FlashblocksTestExtension::new(process_canonical); let parts_source = extension.clone(); // Launch the node with the flashblocks extension - let node = LocalNode::new(vec![Box::new(extension)]).await?; + let node = LocalNode::new(vec![Box::new(extension)], chain_spec).await?; let engine = node.engine_api()?; sleep(Duration::from_millis(NODE_STARTUP_DELAY_MS)).await; diff --git a/crates/client/test-utils/src/harness.rs b/crates/client/test-utils/src/harness.rs index b0196e8d..f18684ba 100644 --- a/crates/client/test-utils/src/harness.rs +++ b/crates/client/test-utils/src/harness.rs @@ -1,8 +1,9 @@ //! Unified test harness combining node and engine helpers, plus optional flashblocks adapter. -use std::time::Duration; +use std::{sync::Arc, time::Duration}; use alloy_eips::{BlockHashOrNumber, eip7685::Requests}; +use alloy_genesis::Genesis; use alloy_primitives::{B64, B256, Bytes}; use alloy_provider::{Provider, RootProvider}; use alloy_rpc_client::RpcClient; @@ -13,6 +14,7 @@ use eyre::{Result, eyre}; use op_alloy_network::Optimism; use op_alloy_rpc_types_engine::OpPayloadAttributes; use reth::providers::{BlockNumReader, BlockReader, ChainSpecProvider}; +use reth_optimism_chainspec::OpChainSpec; use reth_optimism_primitives::OpBlock; use reth_primitives_traits::{Block as BlockT, RecoveredBlock}; use tokio::time::sleep; @@ -29,6 +31,7 @@ use crate::{ #[derive(Debug, Default)] pub struct TestHarnessBuilder { extensions: Vec>, + chain_spec: Option>, } impl TestHarnessBuilder { @@ -43,10 +46,28 @@ impl TestHarnessBuilder { self } + /// Set a custom chain spec for the test harness. + /// + /// If not provided, the default genesis from `assets/genesis.json` is used. + pub fn with_chain_spec(mut self, chain_spec: Arc) -> Self { + self.chain_spec = Some(chain_spec); + self + } + /// Build and launch the test harness. pub async fn build(self) -> Result { init_silenced_tracing(); - let node = LocalNode::new(self.extensions).await?; + + let chain_spec = match self.chain_spec { + Some(spec) => spec, + None => { + let genesis: Genesis = + serde_json::from_str(include_str!("../assets/genesis.json"))?; + Arc::new(OpChainSpec::from_genesis(genesis)) + } + }; + + let node = LocalNode::new(self.extensions, chain_spec).await?; let engine = node.engine_api()?; sleep(Duration::from_millis(NODE_STARTUP_DELAY_MS)).await; @@ -204,6 +225,11 @@ impl TestHarness { .expect("canonical block exists"); BlockT::try_into_recovered(block).expect("able to recover canonical block") } + + /// Return the chain specification used by the harness. + pub fn chain_spec(&self) -> Arc { + self.node.blockchain_provider().chain_spec() + } } #[cfg(test)] diff --git a/crates/client/test-utils/src/node.rs b/crates/client/test-utils/src/node.rs index 5f95da0f..ea14563b 100644 --- a/crates/client/test-utils/src/node.rs +++ b/crates/client/test-utils/src/node.rs @@ -234,14 +234,14 @@ impl BaseNodeExtension for FlashblocksTestExtension { } impl LocalNode { - /// Launch a new local node with the provided extensions. - pub async fn new(extensions: Vec>) -> Result { + /// Launch a new local node with the provided extensions and chain spec. + pub async fn new( + extensions: Vec>, + chain_spec: Arc, + ) -> Result { let tasks = TaskManager::current(); let exec = tasks.executor(); - let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json"))?; - let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); - let network_config = NetworkArgs { discovery: DiscoveryArgs { disable_discovery: true, ..DiscoveryArgs::default() }, ..NetworkArgs::default() @@ -399,9 +399,13 @@ impl FlashblocksLocalNode { } async fn with_options(process_canonical: bool) -> Result { + // Load default chain spec + let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json"))?; + let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); + let extension = FlashblocksTestExtension::new(process_canonical); let parts_source = extension.clone(); - let node = LocalNode::new(vec![Box::new(extension)]).await?; + let node = LocalNode::new(vec![Box::new(extension)], chain_spec).await?; let parts = parts_source.parts()?; Ok(Self { node, parts }) } From 96f607467741e2aebf97748efd3b285afecbcdfb Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 21:04:17 -0600 Subject: [PATCH 10/19] chore: chain spec loaders --- Cargo.lock | 3 +- crates/client/metering/tests/meter.rs | 28 ++--------------- crates/client/metering/tests/meter_block.rs | 30 ++----------------- crates/client/test-utils/src/fixtures.rs | 26 +++++++++++++++- crates/client/test-utils/src/lib.rs | 2 +- crates/shared/access-lists/Cargo.toml | 3 +- .../shared/access-lists/tests/builder/main.rs | 9 ++---- 7 files changed, 36 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5fda0a2c..eac1cf5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1515,14 +1515,13 @@ dependencies = [ "alloy-rlp", "alloy-sol-macro", "alloy-sol-types", + "base-test-utils", "eyre", "op-revm", "reth-evm", - "reth-optimism-chainspec", "reth-optimism-evm", "revm", "serde", - "serde_json", "tracing", ] diff --git a/crates/client/metering/tests/meter.rs b/crates/client/metering/tests/meter.rs index 56b10f6f..de6875fd 100644 --- a/crates/client/metering/tests/meter.rs +++ b/crates/client/metering/tests/meter.rs @@ -1,43 +1,19 @@ //! Integration tests covering the Metering logic surface area. -use std::sync::Arc; - use alloy_eips::Encodable2718; -use alloy_genesis::GenesisAccount; use alloy_primitives::{Address, Bytes, U256, keccak256}; use base_bundles::{Bundle, ParsedBundle}; use base_metering::{MeteringExtension, meter_bundle}; -use base_test_utils::{Account, TestHarness}; +use base_test_utils::{Account, TestHarness, load_chain_spec}; use eyre::Context; use op_alloy_consensus::OpTxEnvelope; use reth::chainspec::EthChainSpec; -use reth_optimism_chainspec::{BASE_MAINNET, OpChainSpec, OpChainSpecBuilder}; use reth_optimism_primitives::OpTransactionSigned; use reth_provider::{HeaderProvider, StateProviderFactory}; use reth_transaction_pool::test_utils::TransactionBuilder; -fn create_chain_spec() -> Arc { - let genesis = BASE_MAINNET - .genesis - .clone() - .extend_accounts( - Account::all() - .into_iter() - .map(|a| { - ( - a.address(), - GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64)), - ) - }) - .collect::>(), - ) - .with_gas_limit(100_000_000); - - Arc::new(OpChainSpecBuilder::base_mainnet().genesis(genesis).isthmus_activated().build()) -} - async fn setup() -> eyre::Result { - let chain_spec = create_chain_spec(); + let chain_spec = load_chain_spec(); TestHarness::builder() .with_chain_spec(chain_spec) .with_extension(MeteringExtension::new(true)) diff --git a/crates/client/metering/tests/meter_block.rs b/crates/client/metering/tests/meter_block.rs index 2a081289..1f3bbeb1 100644 --- a/crates/client/metering/tests/meter_block.rs +++ b/crates/client/metering/tests/meter_block.rs @@ -1,41 +1,17 @@ //! Integration tests for block metering functionality. -use std::sync::Arc; - use alloy_consensus::{BlockHeader, Header}; -use alloy_genesis::GenesisAccount; -use alloy_primitives::{Address, B256, U256}; +use alloy_primitives::{Address, B256}; use base_metering::{MeteringExtension, meter_block}; -use base_test_utils::{Account, TestHarness}; +use base_test_utils::{Account, TestHarness, load_chain_spec}; use reth::chainspec::EthChainSpec; -use reth_optimism_chainspec::{BASE_MAINNET, OpChainSpec, OpChainSpecBuilder}; use reth_optimism_primitives::{OpBlock, OpBlockBody, OpTransactionSigned}; use reth_primitives_traits::Block as BlockT; use reth_provider::HeaderProvider; use reth_transaction_pool::test_utils::TransactionBuilder; -fn create_chain_spec() -> Arc { - let genesis = BASE_MAINNET - .genesis - .clone() - .extend_accounts( - Account::all() - .into_iter() - .map(|a| { - ( - a.address(), - GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64)), - ) - }) - .collect::>(), - ) - .with_gas_limit(100_000_000); - - Arc::new(OpChainSpecBuilder::base_mainnet().genesis(genesis).isthmus_activated().build()) -} - async fn setup() -> eyre::Result { - let chain_spec = create_chain_spec(); + let chain_spec = load_chain_spec(); TestHarness::builder() .with_chain_spec(chain_spec) .with_extension(MeteringExtension::new(true)) diff --git a/crates/client/test-utils/src/fixtures.rs b/crates/client/test-utils/src/fixtures.rs index 382a01b5..6e6ab7c1 100644 --- a/crates/client/test-utils/src/fixtures.rs +++ b/crates/client/test-utils/src/fixtures.rs @@ -2,20 +2,44 @@ use std::sync::Arc; -use alloy_genesis::Genesis; +use alloy_genesis::{Genesis, GenesisAccount}; +use alloy_primitives::U256; use reth::api::{NodeTypes, NodeTypesWithDBAdapter}; use reth_db::{ ClientVersion, DatabaseEnv, init_db, mdbx::{DatabaseArguments, KILOBYTE, MEGABYTE, MaxReadTransactionDuration}, test_utils::{ERROR_DB_CREATION, TempDatabase, create_test_static_files_dir, tempdir_path}, }; +use reth_optimism_chainspec::OpChainSpec; use reth_provider::{ProviderFactory, providers::StaticFileProvider}; +use crate::Account; + /// Loads the shared test genesis configuration. pub fn load_genesis() -> Genesis { serde_json::from_str(include_str!("../assets/genesis.json")).unwrap() } +/// Creates a test chain spec with pre-funded test accounts. +/// Uses the shared genesis.json (Base Sepolia) as the base configuration. +pub fn load_chain_spec() -> Arc { + let genesis = load_genesis() + .extend_accounts( + Account::all() + .into_iter() + .map(|a| { + ( + a.address(), + GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64)), + ) + }) + .collect::>(), + ) + .with_gas_limit(100_000_000); + + Arc::new(OpChainSpec::from_genesis(genesis)) +} + /// Creates a provider factory for tests with the given chain spec. pub fn create_provider_factory( chain_spec: Arc, diff --git a/crates/client/test-utils/src/lib.rs b/crates/client/test-utils/src/lib.rs index dd8a5e09..7d361e30 100644 --- a/crates/client/test-utils/src/lib.rs +++ b/crates/client/test-utils/src/lib.rs @@ -19,7 +19,7 @@ mod engine; pub use engine::{EngineAddress, EngineApi, EngineProtocol, HttpEngine, IpcEngine}; mod fixtures; -pub use fixtures::{create_provider_factory, load_genesis}; +pub use fixtures::{create_provider_factory, load_chain_spec, load_genesis}; mod flashblocks_harness; pub use flashblocks_harness::FlashblocksHarness; diff --git a/crates/shared/access-lists/Cargo.toml b/crates/shared/access-lists/Cargo.toml index 2a0828d6..ab4bd3cd 100644 --- a/crates/shared/access-lists/Cargo.toml +++ b/crates/shared/access-lists/Cargo.toml @@ -22,14 +22,13 @@ serde.workspace = true [dev-dependencies] op-revm.workspace = true eyre.workspace = true -reth-optimism-chainspec.workspace = true reth-optimism-evm.workspace = true alloy-consensus.workspace = true alloy-contract.workspace = true alloy-sol-macro = { workspace = true, features = ["json"] } alloy-sol-types.workspace = true reth-evm.workspace = true -serde_json.workspace = true +base-test-utils.workspace = true [[test]] name = "builder" diff --git a/crates/shared/access-lists/tests/builder/main.rs b/crates/shared/access-lists/tests/builder/main.rs index 04d94e6d..405b6048 100644 --- a/crates/shared/access-lists/tests/builder/main.rs +++ b/crates/shared/access-lists/tests/builder/main.rs @@ -1,6 +1,6 @@ //! Tests for ensuring the access list is built properly -use std::{collections::HashMap, sync::Arc}; +use std::collections::HashMap; use alloy_consensus::Header; pub use alloy_primitives::{Address, B256, TxKind, U256}; @@ -8,10 +8,10 @@ use alloy_sol_macro::sol; pub use alloy_sol_types::SolCall; use base_access_lists::FBALBuilderDb; pub use base_access_lists::FlashblockAccessList; +use base_test_utils::load_chain_spec; pub use eyre::Result; pub use op_revm::OpTransaction; use reth_evm::{ConfigureEvm, Evm}; -use reth_optimism_chainspec::OpChainSpec; use reth_optimism_evm::OpEvmConfig; use revm::{DatabaseCommit, context::result::ResultAndState, database::InMemoryDB}; pub use revm::{ @@ -93,10 +93,7 @@ pub fn execute_txns_build_access_list( acc_overrides: Option>, storage_overrides: Option>>, ) -> Result { - let chain_spec = Arc::new(OpChainSpec::from_genesis( - serde_json::from_str(include_str!("../../../../client/test-utils/assets/genesis.json")) - .unwrap(), - )); + let chain_spec = load_chain_spec(); let evm_config = OpEvmConfig::optimism(chain_spec.clone()); let header = Header { base_fee_per_gas: Some(0), ..chain_spec.genesis_header().clone() }; From 07827bfc9a2e720a1182a90cb21b36fbc66b5200 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 21:43:57 -0600 Subject: [PATCH 11/19] chore: remove genesis file + build it in rust --- .../client/flashblocks/tests/eip7702_tests.rs | 12 +- crates/client/metering/tests/meter_rpc.rs | 16 +-- crates/client/test-utils/assets/genesis.json | 107 ------------------ crates/client/test-utils/src/fixtures.rs | 95 ++++++++++++++-- .../test-utils/src/flashblocks_harness.rs | 5 +- crates/client/test-utils/src/harness.rs | 11 +- crates/client/test-utils/src/lib.rs | 2 +- crates/client/test-utils/src/node.rs | 5 +- .../shared/access-lists/tests/builder/main.rs | 6 +- 9 files changed, 114 insertions(+), 145 deletions(-) delete mode 100644 crates/client/test-utils/assets/genesis.json diff --git a/crates/client/flashblocks/tests/eip7702_tests.rs b/crates/client/flashblocks/tests/eip7702_tests.rs index 435cc71b..4ddd8965 100644 --- a/crates/client/flashblocks/tests/eip7702_tests.rs +++ b/crates/client/flashblocks/tests/eip7702_tests.rs @@ -48,10 +48,6 @@ impl TestSetup { fn provider(&self) -> alloy_provider::RootProvider { self.harness.provider() } - - fn chain_id(&self) -> u64 { - 84532 // Base Sepolia chain ID (matches test harness) - } } /// Build an EIP-7702 authorization for delegating to a contract @@ -172,7 +168,7 @@ fn create_eip7702_flashblock(eip7702_tx: Bytes, cumulative_gas: u64) -> Flashblo #[tokio::test] async fn test_eip7702_delegation_in_pending_flashblock() -> Result<()> { let setup = TestSetup::new().await?; - let chain_id = setup.chain_id(); + let chain_id = setup.harness.chain_id(); // Send base flashblock with contract deployment let base_payload = create_base_flashblock(&setup); @@ -214,7 +210,7 @@ async fn test_eip7702_delegation_in_pending_flashblock() -> Result<()> { #[tokio::test] async fn test_eip7702_multiple_delegations_same_flashblock() -> Result<()> { let setup = TestSetup::new().await?; - let chain_id = setup.chain_id(); + let chain_id = setup.harness.chain_id(); // Send base flashblock with contract deployment let base_payload = create_base_flashblock(&setup); @@ -288,7 +284,7 @@ async fn test_eip7702_multiple_delegations_same_flashblock() -> Result<()> { #[tokio::test] async fn test_eip7702_pending_receipt() -> Result<()> { let setup = TestSetup::new().await?; - let chain_id = setup.chain_id(); + let chain_id = setup.harness.chain_id(); // Send base flashblock with contract deployment let base_payload = create_base_flashblock(&setup); @@ -327,7 +323,7 @@ async fn test_eip7702_pending_receipt() -> Result<()> { #[tokio::test] async fn test_eip7702_delegation_then_execution() -> Result<()> { let setup = TestSetup::new().await?; - let chain_id = setup.chain_id(); + let chain_id = setup.harness.chain_id(); // Send base flashblock with contract deployment let base_payload = create_base_flashblock(&setup); diff --git a/crates/client/metering/tests/meter_rpc.rs b/crates/client/metering/tests/meter_rpc.rs index f82d8faf..6739f1a0 100644 --- a/crates/client/metering/tests/meter_rpc.rs +++ b/crates/client/metering/tests/meter_rpc.rs @@ -52,7 +52,7 @@ async fn test_meter_bundle_empty() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_single_transaction() -> eyre::Result<()> { - let (_harness, client) = setup().await?; + let (harness, client) = setup().await?; let sender_address = Account::Alice.address(); let sender_secret = Account::Alice.signer_b256(); @@ -60,7 +60,7 @@ async fn test_meter_bundle_single_transaction() -> eyre::Result<()> { // Build a transaction let tx = TransactionBuilder::default() .signer(sender_secret) - .chain_id(84532) + .chain_id(harness.chain_id()) .nonce(0) .to(address!("0x1111111111111111111111111111111111111111")) .value(1000) @@ -96,14 +96,14 @@ async fn test_meter_bundle_single_transaction() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_multiple_transactions() -> eyre::Result<()> { - let (_harness, client) = setup().await?; + let (harness, client) = setup().await?; let address1 = Account::Alice.address(); let secret1 = Account::Alice.signer_b256(); let tx1_inner = TransactionBuilder::default() .signer(secret1) - .chain_id(84532) + .chain_id(harness.chain_id()) .nonce(0) .to(address!("0x1111111111111111111111111111111111111111")) .value(1000) @@ -123,7 +123,7 @@ async fn test_meter_bundle_multiple_transactions() -> eyre::Result<()> { let tx2_inner = TransactionBuilder::default() .signer(secret2) - .chain_id(84532) + .chain_id(harness.chain_id()) .nonce(0) .to(address!("0x2222222222222222222222222222222222222222")) .value(2000) @@ -252,7 +252,7 @@ async fn test_meter_bundle_arbitrary_block_number() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_gas_calculations() -> eyre::Result<()> { - let (_harness, client) = setup().await?; + let (harness, client) = setup().await?; let secret1 = Account::Alice.signer_b256(); let secret2 = Account::Bob.signer_b256(); @@ -260,7 +260,7 @@ async fn test_meter_bundle_gas_calculations() -> eyre::Result<()> { // First transaction with 3 gwei gas price let tx1_inner = TransactionBuilder::default() .signer(secret1) - .chain_id(84532) + .chain_id(harness.chain_id()) .nonce(0) .to(address!("0x1111111111111111111111111111111111111111")) .value(1000) @@ -277,7 +277,7 @@ async fn test_meter_bundle_gas_calculations() -> eyre::Result<()> { // Second transaction with 7 gwei gas price let tx2_inner = TransactionBuilder::default() .signer(secret2) - .chain_id(84532) + .chain_id(harness.chain_id()) .nonce(0) .to(address!("0x2222222222222222222222222222222222222222")) .value(2000) diff --git a/crates/client/test-utils/assets/genesis.json b/crates/client/test-utils/assets/genesis.json deleted file mode 100644 index dde20c5e..00000000 --- a/crates/client/test-utils/assets/genesis.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "config": { - "chainId": 84532, - "homesteadBlock": 0, - "eip150Block": 0, - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "constantinopleBlock": 0, - "petersburgBlock": 0, - "istanbulBlock": 0, - "muirGlacierBlock": 0, - "berlinBlock": 0, - "londonBlock": 0, - "arrowGlacierBlock": 0, - "grayGlacierBlock": 0, - "mergeNetsplitBlock": 0, - "bedrockBlock": 0, - "regolithTime": 0, - "canyonTime": 0, - "ecotoneTime": 0, - "fjordTime": 0, - "graniteTime": 0, - "isthmusTime": 0, - "jovianTime": 0, - "pragueTime": 0, - "terminalTotalDifficulty": 0, - "terminalTotalDifficultyPassed": true, - "optimism": { - "eip1559Elasticity": 6, - "eip1559Denominator": 50 - } - }, - "nonce": "0x0", - "timestamp": "0x0", - "extraData": "0x00", - "gasLimit": "0x1c9c380", - "difficulty": "0x0", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase": "0x0000000000000000000000000000000000000000", - "alloc": { - "0x14dc79964da2c08b23698b3d3cc7ca32193d9955": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x1cbd3b2770909d4e10f157cabc84c7264073c9ec": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x2546bcd3c84621e976d8185a91a922ae77ecec30": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x71be63f3384f5fb98995898a86b02fb2426c5788": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x8626f6940e2eb28930efb4cef49b2d1f2c9c1199": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x90f79bf6eb2c4f870365e785982e1f101e93b906": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x976ea74026e726554db657fa54763abd0c3a0aa9": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x9c41de96b2088cdc640c6182dfcf5491dc574a57": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xa0ee7a142d267c1f36714e4a8f75612f20a79720": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xbcd4042de499d14e55001ccbb24a551f3b954096": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xbda5747bfd65f08deb54cb465eb87d40e51b197e": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xcd3b766ccdd6ae721141f452c550ca635964ce71": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xdd2fd4581271e230360230f9337d5c0430bf44c0": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xdf3e18d64bc6a983f673ab319ccae4f1a57c7097": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xfabb0ac9d68b0b445fb7357272ff202c5651694a": { - "balance": "0xd3c21bcecceda1000000" - } - }, - "number": "0x0" -} diff --git a/crates/client/test-utils/src/fixtures.rs b/crates/client/test-utils/src/fixtures.rs index 6e6ab7c1..e707b125 100644 --- a/crates/client/test-utils/src/fixtures.rs +++ b/crates/client/test-utils/src/fixtures.rs @@ -1,9 +1,9 @@ //! Shared fixtures and test data reused by integration tests across the Base codebase. -use std::sync::Arc; +use std::{collections::BTreeMap, sync::Arc}; -use alloy_genesis::{Genesis, GenesisAccount}; -use alloy_primitives::U256; +use alloy_genesis::{ChainConfig, Genesis, GenesisAccount}; +use alloy_primitives::{Address, B256, Bytes, U256, utils::parse_ether}; use reth::api::{NodeTypes, NodeTypesWithDBAdapter}; use reth_db::{ ClientVersion, DatabaseEnv, init_db, @@ -15,15 +15,94 @@ use reth_provider::{ProviderFactory, providers::StaticFileProvider}; use crate::Account; -/// Loads the shared test genesis configuration. -pub fn load_genesis() -> Genesis { - serde_json::from_str(include_str!("../assets/genesis.json")).unwrap() +/// Builds a test genesis configuration programmatically. +/// +/// Creates a Base Sepolia-like genesis with: +/// - All EVM and OP hardforks enabled from genesis +/// - Optimism EIP-1559 settings (elasticity=6, denominator=50) +/// - Pre-funded test accounts from the `Account` enum +pub fn build_test_genesis() -> Genesis { + // OP EIP-1559 base fee parameters + const EIP1559_ELASTICITY: u64 = 6; + const EIP1559_DENOMINATOR: u64 = 50; + + // Test account balance: 1 million ETH + let test_account_balance: U256 = parse_ether("1000000").expect("valid ether amount"); + + // Build chain config with all hardforks enabled at genesis + let config = ChainConfig { + chain_id: crate::BASE_CHAIN_ID, + // Block-based EVM hardforks (all at block 0) + homestead_block: Some(0), + eip150_block: Some(0), + eip155_block: Some(0), + eip158_block: Some(0), + byzantium_block: Some(0), + constantinople_block: Some(0), + petersburg_block: Some(0), + istanbul_block: Some(0), + muir_glacier_block: Some(0), + berlin_block: Some(0), + london_block: Some(0), + arrow_glacier_block: Some(0), + gray_glacier_block: Some(0), + merge_netsplit_block: Some(0), + // Time-based hardforks + shanghai_time: Some(0), + cancun_time: Some(0), + prague_time: Some(0), + // Post-merge settings + terminal_total_difficulty: Some(U256::ZERO), + terminal_total_difficulty_passed: true, + // OP-specific hardforks and settings via extra_fields + extra_fields: [ + ("bedrockBlock", serde_json::json!(0)), + ("regolithTime", serde_json::json!(0)), + ("canyonTime", serde_json::json!(0)), + ("ecotoneTime", serde_json::json!(0)), + ("fjordTime", serde_json::json!(0)), + ("graniteTime", serde_json::json!(0)), + ("isthmusTime", serde_json::json!(0)), + ("jovianTime", serde_json::json!(0)), + ( + "optimism", + serde_json::json!({ + "eip1559Elasticity": EIP1559_ELASTICITY, + "eip1559Denominator": EIP1559_DENOMINATOR + }), + ), + ] + .into_iter() + .map(|(k, v)| (k.to_string(), v)) + .collect(), + ..Default::default() + }; + + // Pre-fund all test accounts + let alloc: BTreeMap = Account::all() + .into_iter() + .map(|account| { + (account.address(), GenesisAccount::default().with_balance(test_account_balance)) + }) + .collect(); + + Genesis { + config, + alloc, + gas_limit: 100_000_000, + difficulty: U256::ZERO, + nonce: 0, + timestamp: 0, + extra_data: Bytes::from_static(&[0x00]), + mix_hash: B256::ZERO, + coinbase: Address::ZERO, + ..Default::default() + } } /// Creates a test chain spec with pre-funded test accounts. -/// Uses the shared genesis.json (Base Sepolia) as the base configuration. pub fn load_chain_spec() -> Arc { - let genesis = load_genesis() + let genesis = build_test_genesis() .extend_accounts( Account::all() .into_iter() diff --git a/crates/client/test-utils/src/flashblocks_harness.rs b/crates/client/test-utils/src/flashblocks_harness.rs index 18f1219c..d56df358 100644 --- a/crates/client/test-utils/src/flashblocks_harness.rs +++ b/crates/client/test-utils/src/flashblocks_harness.rs @@ -2,7 +2,6 @@ use std::{sync::Arc, time::Duration}; -use alloy_genesis::Genesis; use base_flashtypes::Flashblock; use derive_more::Deref; use eyre::Result; @@ -48,8 +47,8 @@ impl FlashblocksHarness { async fn with_options(process_canonical: bool) -> Result { init_silenced_tracing(); - // Load default chain spec - let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json"))?; + // Build default chain spec programmatically + let genesis = crate::build_test_genesis(); let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); // Create the extension and keep a reference to get parts after launch diff --git a/crates/client/test-utils/src/harness.rs b/crates/client/test-utils/src/harness.rs index f18684ba..9e5643d9 100644 --- a/crates/client/test-utils/src/harness.rs +++ b/crates/client/test-utils/src/harness.rs @@ -3,7 +3,6 @@ use std::{sync::Arc, time::Duration}; use alloy_eips::{BlockHashOrNumber, eip7685::Requests}; -use alloy_genesis::Genesis; use alloy_primitives::{B64, B256, Bytes}; use alloy_provider::{Provider, RootProvider}; use alloy_rpc_client::RpcClient; @@ -48,7 +47,7 @@ impl TestHarnessBuilder { /// Set a custom chain spec for the test harness. /// - /// If not provided, the default genesis from `assets/genesis.json` is used. + /// If not provided, the default genesis is built programmatically. pub fn with_chain_spec(mut self, chain_spec: Arc) -> Self { self.chain_spec = Some(chain_spec); self @@ -61,8 +60,7 @@ impl TestHarnessBuilder { let chain_spec = match self.chain_spec { Some(spec) => spec, None => { - let genesis: Genesis = - serde_json::from_str(include_str!("../assets/genesis.json"))?; + let genesis = crate::build_test_genesis(); Arc::new(OpChainSpec::from_genesis(genesis)) } }; @@ -230,6 +228,11 @@ impl TestHarness { pub fn chain_spec(&self) -> Arc { self.node.blockchain_provider().chain_spec() } + + /// Return the chain ID used by the harness. + pub fn chain_id(&self) -> u64 { + self.chain_spec().chain().id() + } } #[cfg(test)] diff --git a/crates/client/test-utils/src/lib.rs b/crates/client/test-utils/src/lib.rs index 7d361e30..3a1a1d86 100644 --- a/crates/client/test-utils/src/lib.rs +++ b/crates/client/test-utils/src/lib.rs @@ -19,7 +19,7 @@ mod engine; pub use engine::{EngineAddress, EngineApi, EngineProtocol, HttpEngine, IpcEngine}; mod fixtures; -pub use fixtures::{create_provider_factory, load_chain_spec, load_genesis}; +pub use fixtures::{build_test_genesis, create_provider_factory, load_chain_spec}; mod flashblocks_harness; pub use flashblocks_harness::FlashblocksHarness; diff --git a/crates/client/test-utils/src/node.rs b/crates/client/test-utils/src/node.rs index ea14563b..c06bc86e 100644 --- a/crates/client/test-utils/src/node.rs +++ b/crates/client/test-utils/src/node.rs @@ -8,7 +8,6 @@ use std::{ sync::{Arc, Mutex}, }; -use alloy_genesis::Genesis; use alloy_provider::RootProvider; use alloy_rpc_client::RpcClient; use base_client_primitives::{BaseNodeExtension, OpBuilder, OpProvider}; @@ -399,8 +398,8 @@ impl FlashblocksLocalNode { } async fn with_options(process_canonical: bool) -> Result { - // Load default chain spec - let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json"))?; + // Build default chain spec programmatically + let genesis = crate::build_test_genesis(); let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); let extension = FlashblocksTestExtension::new(process_canonical); diff --git a/crates/shared/access-lists/tests/builder/main.rs b/crates/shared/access-lists/tests/builder/main.rs index 405b6048..5ab64d47 100644 --- a/crates/shared/access-lists/tests/builder/main.rs +++ b/crates/shared/access-lists/tests/builder/main.rs @@ -8,7 +8,7 @@ use alloy_sol_macro::sol; pub use alloy_sol_types::SolCall; use base_access_lists::FBALBuilderDb; pub use base_access_lists::FlashblockAccessList; -use base_test_utils::load_chain_spec; +use base_test_utils::{BASE_CHAIN_ID, load_chain_spec}; pub use eyre::Result; pub use op_revm::OpTransaction; use reth_evm::{ConfigureEvm, Evm}; @@ -80,8 +80,8 @@ sol!( ) ); -/// Chain ID for Base Sepolia -pub const BASE_SEPOLIA_CHAIN_ID: u64 = 84532; +/// Chain ID for Base Sepolia (re-export from test-utils for convenience) +pub const BASE_SEPOLIA_CHAIN_ID: u64 = BASE_CHAIN_ID; /// Executes a list of transactions and builds a FlashblockAccessList tracking all /// account and storage changes across all transactions. From bb07cd06541de37a4959340dad0bbce19a4b8c02 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 22:05:23 -0600 Subject: [PATCH 12/19] chore: setup shared primitives and move accounts/contracts there under test utils --- .gitignore | 1 - .gitmodules | 12 +-- Cargo.lock | 31 ++++-- Cargo.toml | 2 + Justfile | 2 +- crates/client/test-utils/Cargo.toml | 10 +- crates/client/test-utils/src/constants.rs | 5 - crates/client/test-utils/src/fixtures.rs | 94 +----------------- crates/client/test-utils/src/harness.rs | 2 +- crates/client/test-utils/src/lib.rs | 15 +-- crates/shared/access-lists/Cargo.toml | 4 +- .../access-lists/tests/builder/deployment.rs | 8 +- .../shared/access-lists/tests/builder/main.rs | 68 ++----------- .../access-lists/tests/builder/storage.rs | 12 +-- .../access-lists/tests/builder/transfers.rs | 11 +-- crates/shared/primitives/Cargo.toml | 46 +++++++++ .../primitives}/contracts/.gitignore | 0 .../primitives}/contracts/README.md | 0 .../primitives}/contracts/foundry.lock | 0 .../primitives}/contracts/foundry.toml | 0 .../primitives}/contracts/lib/forge-std | 0 .../contracts/lib/openzeppelin-contracts | 0 .../primitives}/contracts/lib/solmate | 0 .../script/DeployDoubleCounter.s.sol | 0 .../contracts/script/DeployERC20.s.sol | 0 .../primitives}/contracts/src/AccessList.sol | 0 .../contracts/src/ContractFactory.sol | 0 .../contracts/src/DoubleCounter.sol | 0 .../contracts/src/Minimal7702Account.sol | 0 .../primitives}/contracts/src/Proxy.sol | 0 crates/shared/primitives/src/lib.rs | 9 ++ .../primitives/src/test_utils}/accounts.rs | 28 +++--- .../primitives/src/test_utils}/contracts.rs | 41 +++++++- .../primitives/src/test_utils/genesis.rs | 96 +++++++++++++++++++ .../shared/primitives/src/test_utils/mod.rs | 13 +++ lychee.toml | 2 +- 36 files changed, 286 insertions(+), 226 deletions(-) create mode 100644 crates/shared/primitives/Cargo.toml rename crates/{client/test-utils => shared/primitives}/contracts/.gitignore (100%) rename crates/{client/test-utils => shared/primitives}/contracts/README.md (100%) rename crates/{client/test-utils => shared/primitives}/contracts/foundry.lock (100%) rename crates/{client/test-utils => shared/primitives}/contracts/foundry.toml (100%) rename crates/{client/test-utils => shared/primitives}/contracts/lib/forge-std (100%) rename crates/{client/test-utils => shared/primitives}/contracts/lib/openzeppelin-contracts (100%) rename crates/{client/test-utils => shared/primitives}/contracts/lib/solmate (100%) rename crates/{client/test-utils => shared/primitives}/contracts/script/DeployDoubleCounter.s.sol (100%) rename crates/{client/test-utils => shared/primitives}/contracts/script/DeployERC20.s.sol (100%) rename crates/{client/test-utils => shared/primitives}/contracts/src/AccessList.sol (100%) rename crates/{client/test-utils => shared/primitives}/contracts/src/ContractFactory.sol (100%) rename crates/{client/test-utils => shared/primitives}/contracts/src/DoubleCounter.sol (100%) rename crates/{client/test-utils => shared/primitives}/contracts/src/Minimal7702Account.sol (100%) rename crates/{client/test-utils => shared/primitives}/contracts/src/Proxy.sol (100%) create mode 100644 crates/shared/primitives/src/lib.rs rename crates/{client/test-utils/src => shared/primitives/src/test_utils}/accounts.rs (85%) rename crates/{client/test-utils/src => shared/primitives/src/test_utils}/contracts.rs (53%) create mode 100644 crates/shared/primitives/src/test_utils/genesis.rs create mode 100644 crates/shared/primitives/src/test_utils/mod.rs diff --git a/.gitignore b/.gitignore index 959253b2..036d404a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ target/ .idea/ -integration_logs/ .DS_Store diff --git a/.gitmodules b/.gitmodules index f9644fe7..4508bf7f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,9 @@ -[submodule "crates/client/test-utils/contracts/lib/forge-std"] - path = crates/client/test-utils/contracts/lib/forge-std +[submodule "crates/shared/primitives/contracts/lib/forge-std"] + path = crates/shared/primitives/contracts/lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "crates/client/test-utils/contracts/lib/solmate"] - path = crates/client/test-utils/contracts/lib/solmate +[submodule "crates/shared/primitives/contracts/lib/solmate"] + path = crates/shared/primitives/contracts/lib/solmate url = https://github.com/transmissions11/solmate -[submodule "crates/client/test-utils/contracts/lib/openzeppelin-contracts"] - path = crates/client/test-utils/contracts/lib/openzeppelin-contracts +[submodule "crates/shared/primitives/contracts/lib/openzeppelin-contracts"] + path = crates/shared/primitives/contracts/lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/Cargo.lock b/Cargo.lock index eac1cf5e..705714ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1513,12 +1513,12 @@ dependencies = [ "alloy-eip7928", "alloy-primitives", "alloy-rlp", - "alloy-sol-macro", "alloy-sol-types", - "base-test-utils", + "base-primitives", "eyre", "op-revm", "reth-evm", + "reth-optimism-chainspec", "reth-optimism-evm", "revm", "serde", @@ -1679,6 +1679,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "base-primitives" +version = "0.2.1" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-signer", + "alloy-signer-local", + "alloy-sol-macro", + "alloy-sol-types", + "eyre", + "op-alloy-network", + "op-alloy-rpc-types", + "serde_json", +] + [[package]] name = "base-reth-node" version = "0.2.1" @@ -1707,8 +1726,6 @@ dependencies = [ name = "base-test-utils" version = "0.2.1" dependencies = [ - "alloy-consensus", - "alloy-contract", "alloy-eips", "alloy-genesis", "alloy-primitives", @@ -1717,19 +1734,16 @@ dependencies = [ "alloy-rpc-types", "alloy-rpc-types-engine", "alloy-signer", - "alloy-signer-local", - "alloy-sol-macro", - "alloy-sol-types", "base-client-primitives", "base-flashblocks", "base-flashtypes", + "base-primitives", "chrono", "derive_more", "eyre", "jsonrpsee", "once_cell", "op-alloy-network", - "op-alloy-rpc-types", "op-alloy-rpc-types-engine", "reth", "reth-db", @@ -1744,7 +1758,6 @@ dependencies = [ "reth-provider", "reth-rpc-layer", "reth-tracing", - "serde_json", "tokio", "tokio-stream", "tower", diff --git a/Cargo.toml b/Cargo.toml index 507d310c..7908db43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ base-access-lists = { path = "crates/shared/access-lists" } base-bundles = { path = "crates/shared/bundles" } base-cli-utils = { path = "crates/shared/cli-utils" } base-flashtypes = { path = "crates/shared/flashtypes" } +base-primitives = { path = "crates/shared/primitives" } base-reth-rpc-types = { path = "crates/shared/reth-rpc-types" } # Client base-client-primitives = { path = "crates/client/client-primitives" } @@ -104,6 +105,7 @@ alloy-eips = "1.0.41" alloy-serde = "1.0.41" alloy-eip7928 = "0.3.0" alloy-genesis = "1.0.41" +alloy-signer = "1.0.41" alloy-signer-local = "1.0.41" alloy-hardforks = "0.4.4" alloy-provider = "1.0.41" diff --git a/Justfile b/Justfile index 78bc8207..fe1ccf12 100644 --- a/Justfile +++ b/Justfile @@ -83,7 +83,7 @@ build-node: # Build the contracts used for tests build-contracts: - cd crates/client/test-utils/contracts && forge build + cd crates/shared/primitives/contracts && forge build # Cleans the workspace clean: diff --git a/crates/client/test-utils/Cargo.toml b/crates/client/test-utils/Cargo.toml index 39ae07f2..39fa52d3 100644 --- a/crates/client/test-utils/Cargo.toml +++ b/crates/client/test-utils/Cargo.toml @@ -16,6 +16,7 @@ workspace = true base-client-primitives.workspace = true base-flashtypes.workspace = true base-flashblocks.workspace = true +base-primitives = { workspace = true, features = ["test-utils"] } # reth reth.workspace = true @@ -35,20 +36,14 @@ reth-ipc.workspace = true # alloy alloy-primitives.workspace = true alloy-genesis.workspace = true -alloy-sol-macro = { workspace = true, features = ["json"] } -alloy-sol-types.workspace = true -alloy-contract.workspace = true alloy-eips.workspace = true alloy-rpc-types.workspace = true alloy-rpc-types-engine.workspace = true -alloy-consensus.workspace = true alloy-provider.workspace = true alloy-rpc-client.workspace = true -alloy-signer = "1.0" -alloy-signer-local = "1.1.0" +alloy-signer.workspace = true # op-alloy -op-alloy-rpc-types.workspace = true op-alloy-rpc-types-engine.workspace = true op-alloy-network.workspace = true @@ -62,7 +57,6 @@ jsonrpsee.workspace = true # misc derive_more = { workspace = true, features = ["deref"] } tracing-subscriber.workspace = true -serde_json.workspace = true eyre.workspace = true once_cell.workspace = true url.workspace = true diff --git a/crates/client/test-utils/src/constants.rs b/crates/client/test-utils/src/constants.rs index 673e878b..5572e4fc 100644 --- a/crates/client/test-utils/src/constants.rs +++ b/crates/client/test-utils/src/constants.rs @@ -3,11 +3,6 @@ use alloy_primitives::{B256, Bytes, b256, bytes}; pub use reth::chainspec::NamedChain; -// Chain Configuration - -/// Chain ID used for test networks (Base Sepolia). -pub const BASE_CHAIN_ID: u64 = NamedChain::BaseSepolia as u64; - // Block Building /// Block time in seconds for test node configuration. diff --git a/crates/client/test-utils/src/fixtures.rs b/crates/client/test-utils/src/fixtures.rs index e707b125..14d67ab3 100644 --- a/crates/client/test-utils/src/fixtures.rs +++ b/crates/client/test-utils/src/fixtures.rs @@ -1,9 +1,10 @@ //! Shared fixtures and test data reused by integration tests across the Base codebase. -use std::{collections::BTreeMap, sync::Arc}; +use std::sync::Arc; -use alloy_genesis::{ChainConfig, Genesis, GenesisAccount}; -use alloy_primitives::{Address, B256, Bytes, U256, utils::parse_ether}; +use alloy_genesis::GenesisAccount; +use alloy_primitives::U256; +use base_primitives::{Account, build_test_genesis}; use reth::api::{NodeTypes, NodeTypesWithDBAdapter}; use reth_db::{ ClientVersion, DatabaseEnv, init_db, @@ -13,93 +14,6 @@ use reth_db::{ use reth_optimism_chainspec::OpChainSpec; use reth_provider::{ProviderFactory, providers::StaticFileProvider}; -use crate::Account; - -/// Builds a test genesis configuration programmatically. -/// -/// Creates a Base Sepolia-like genesis with: -/// - All EVM and OP hardforks enabled from genesis -/// - Optimism EIP-1559 settings (elasticity=6, denominator=50) -/// - Pre-funded test accounts from the `Account` enum -pub fn build_test_genesis() -> Genesis { - // OP EIP-1559 base fee parameters - const EIP1559_ELASTICITY: u64 = 6; - const EIP1559_DENOMINATOR: u64 = 50; - - // Test account balance: 1 million ETH - let test_account_balance: U256 = parse_ether("1000000").expect("valid ether amount"); - - // Build chain config with all hardforks enabled at genesis - let config = ChainConfig { - chain_id: crate::BASE_CHAIN_ID, - // Block-based EVM hardforks (all at block 0) - homestead_block: Some(0), - eip150_block: Some(0), - eip155_block: Some(0), - eip158_block: Some(0), - byzantium_block: Some(0), - constantinople_block: Some(0), - petersburg_block: Some(0), - istanbul_block: Some(0), - muir_glacier_block: Some(0), - berlin_block: Some(0), - london_block: Some(0), - arrow_glacier_block: Some(0), - gray_glacier_block: Some(0), - merge_netsplit_block: Some(0), - // Time-based hardforks - shanghai_time: Some(0), - cancun_time: Some(0), - prague_time: Some(0), - // Post-merge settings - terminal_total_difficulty: Some(U256::ZERO), - terminal_total_difficulty_passed: true, - // OP-specific hardforks and settings via extra_fields - extra_fields: [ - ("bedrockBlock", serde_json::json!(0)), - ("regolithTime", serde_json::json!(0)), - ("canyonTime", serde_json::json!(0)), - ("ecotoneTime", serde_json::json!(0)), - ("fjordTime", serde_json::json!(0)), - ("graniteTime", serde_json::json!(0)), - ("isthmusTime", serde_json::json!(0)), - ("jovianTime", serde_json::json!(0)), - ( - "optimism", - serde_json::json!({ - "eip1559Elasticity": EIP1559_ELASTICITY, - "eip1559Denominator": EIP1559_DENOMINATOR - }), - ), - ] - .into_iter() - .map(|(k, v)| (k.to_string(), v)) - .collect(), - ..Default::default() - }; - - // Pre-fund all test accounts - let alloc: BTreeMap = Account::all() - .into_iter() - .map(|account| { - (account.address(), GenesisAccount::default().with_balance(test_account_balance)) - }) - .collect(); - - Genesis { - config, - alloc, - gas_limit: 100_000_000, - difficulty: U256::ZERO, - nonce: 0, - timestamp: 0, - extra_data: Bytes::from_static(&[0x00]), - mix_hash: B256::ZERO, - coinbase: Address::ZERO, - ..Default::default() - } -} - /// Creates a test chain spec with pre-funded test accounts. pub fn load_chain_spec() -> Arc { let genesis = build_test_genesis() diff --git a/crates/client/test-utils/src/harness.rs b/crates/client/test-utils/src/harness.rs index 9e5643d9..92dcf47d 100644 --- a/crates/client/test-utils/src/harness.rs +++ b/crates/client/test-utils/src/harness.rs @@ -249,7 +249,7 @@ mod tests { let provider = harness.provider(); let chain_id = provider.get_chain_id().await?; - assert_eq!(chain_id, crate::BASE_CHAIN_ID); + assert_eq!(chain_id, crate::DEVNET_CHAIN_ID); let alice_balance = provider.get_balance(Account::Alice.address()).await?; assert!(alice_balance > U256::ZERO); diff --git a/crates/client/test-utils/src/lib.rs b/crates/client/test-utils/src/lib.rs index 3a1a1d86..1ab87bfc 100644 --- a/crates/client/test-utils/src/lib.rs +++ b/crates/client/test-utils/src/lib.rs @@ -3,23 +3,24 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -mod accounts; -pub use accounts::Account; +// Re-export from base-primitives for backwards compatibility +pub use base_primitives::{ + AccessListContract, Account, ContractFactory, DEVNET_CHAIN_ID, DoubleCounter, Logic, Logic2, + Minimal7702Account, MockERC20, Proxy, SimpleStorage, TransparentUpgradeableProxy, + build_test_genesis, +}; mod constants; pub use constants::{ - BASE_CHAIN_ID, BLOCK_BUILD_DELAY_MS, BLOCK_TIME_SECONDS, DEFAULT_JWT_SECRET, GAS_LIMIT, + BLOCK_BUILD_DELAY_MS, BLOCK_TIME_SECONDS, DEFAULT_JWT_SECRET, GAS_LIMIT, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, NODE_STARTUP_DELAY_MS, NamedChain, }; -mod contracts; -pub use contracts::{DoubleCounter, Minimal7702Account, MockERC20, TransparentUpgradeableProxy}; - mod engine; pub use engine::{EngineAddress, EngineApi, EngineProtocol, HttpEngine, IpcEngine}; mod fixtures; -pub use fixtures::{build_test_genesis, create_provider_factory, load_chain_spec}; +pub use fixtures::{create_provider_factory, load_chain_spec}; mod flashblocks_harness; pub use flashblocks_harness::FlashblocksHarness; diff --git a/crates/shared/access-lists/Cargo.toml b/crates/shared/access-lists/Cargo.toml index ab4bd3cd..a392db97 100644 --- a/crates/shared/access-lists/Cargo.toml +++ b/crates/shared/access-lists/Cargo.toml @@ -23,12 +23,12 @@ serde.workspace = true op-revm.workspace = true eyre.workspace = true reth-optimism-evm.workspace = true +reth-optimism-chainspec.workspace = true alloy-consensus.workspace = true alloy-contract.workspace = true -alloy-sol-macro = { workspace = true, features = ["json"] } alloy-sol-types.workspace = true reth-evm.workspace = true -base-test-utils.workspace = true +base-primitives = { workspace = true, features = ["test-utils"] } [[test]] name = "builder" diff --git a/crates/shared/access-lists/tests/builder/deployment.rs b/crates/shared/access-lists/tests/builder/deployment.rs index ea25add6..b4f0b923 100644 --- a/crates/shared/access-lists/tests/builder/deployment.rs +++ b/crates/shared/access-lists/tests/builder/deployment.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use super::{ - AccountInfo, B256, BASE_SEPOLIA_CHAIN_ID, Bytecode, ContractFactory, IntoAddress, ONE_ETHER, + AccountInfo, B256, Bytecode, ContractFactory, DEVNET_CHAIN_ID, IntoAddress, ONE_ETHER, OpTransaction, SimpleStorage, SolCall, TxEnv, TxKind, U256, execute_txns_build_access_list, }; @@ -30,7 +30,7 @@ fn test_create_deployment_tracked() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(factory)) .data( ContractFactory::deployWithCreateCall { @@ -96,7 +96,7 @@ fn test_create2_deployment_tracked() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(factory)) .data( ContractFactory::deployWithCreate2Call { @@ -160,7 +160,7 @@ fn test_create_and_immediate_call() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(factory)) .data( ContractFactory::deployAndCallCall { diff --git a/crates/shared/access-lists/tests/builder/main.rs b/crates/shared/access-lists/tests/builder/main.rs index 5ab64d47..fbcc9b21 100644 --- a/crates/shared/access-lists/tests/builder/main.rs +++ b/crates/shared/access-lists/tests/builder/main.rs @@ -1,17 +1,19 @@ //! Tests for ensuring the access list is built properly -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; use alloy_consensus::Header; pub use alloy_primitives::{Address, B256, TxKind, U256}; -use alloy_sol_macro::sol; pub use alloy_sol_types::SolCall; use base_access_lists::FBALBuilderDb; pub use base_access_lists::FlashblockAccessList; -use base_test_utils::{BASE_CHAIN_ID, load_chain_spec}; +use base_primitives::{ + AccessListContract, ContractFactory, DEVNET_CHAIN_ID, SimpleStorage, build_test_genesis, +}; pub use eyre::Result; pub use op_revm::OpTransaction; use reth_evm::{ConfigureEvm, Evm}; +use reth_optimism_chainspec::OpChainSpec; use reth_optimism_evm::OpEvmConfig; use revm::{DatabaseCommit, context::result::ResultAndState, database::InMemoryDB}; pub use revm::{ @@ -26,62 +28,10 @@ mod deployment; mod storage; mod transfers; -sol!( - #[sol(rpc)] - AccessListContract, - concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../client/test-utils/contracts/out/AccessList.sol/AccessList.json" - ) -); - -sol!( - #[sol(rpc)] - ContractFactory, - concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../client/test-utils/contracts/out/ContractFactory.sol/ContractFactory.json" - ) -); - -sol!( - #[sol(rpc)] - SimpleStorage, - concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../client/test-utils/contracts/out/ContractFactory.sol/SimpleStorage.json" - ) -); - -sol!( - #[sol(rpc)] - Proxy, - concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../client/test-utils/contracts/out/Proxy.sol/Proxy.json" - ) -); - -sol!( - #[sol(rpc)] - Logic, - concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../client/test-utils/contracts/out/Proxy.sol/Logic.json" - ) -); - -sol!( - #[sol(rpc)] - Logic2, - concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../client/test-utils/contracts/out/Proxy.sol/Logic2.json" - ) -); - -/// Chain ID for Base Sepolia (re-export from test-utils for convenience) -pub const BASE_SEPOLIA_CHAIN_ID: u64 = BASE_CHAIN_ID; +/// Loads the test chain spec from the genesis configuration. +fn load_chain_spec() -> Arc { + Arc::new(OpChainSpec::from_genesis(build_test_genesis())) +} /// Executes a list of transactions and builds a FlashblockAccessList tracking all /// account and storage changes across all transactions. diff --git a/crates/shared/access-lists/tests/builder/storage.rs b/crates/shared/access-lists/tests/builder/storage.rs index 98ba221b..8f40bb11 100644 --- a/crates/shared/access-lists/tests/builder/storage.rs +++ b/crates/shared/access-lists/tests/builder/storage.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use super::{ - AccessListContract, AccountInfo, BASE_SEPOLIA_CHAIN_ID, Bytecode, IntoAddress, ONE_ETHER, + AccessListContract, AccountInfo, Bytecode, DEVNET_CHAIN_ID, IntoAddress, ONE_ETHER, OpTransaction, SolCall, TxEnv, TxKind, U256, execute_txns_build_access_list, }; @@ -24,7 +24,7 @@ fn test_sload_zero_value() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(contract)) .data(AccessListContract::valueCall {}.abi_encode().into()) .gas_price(0) @@ -67,7 +67,7 @@ fn test_update_one_value() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(contract)) .data( AccessListContract::updateValueCall { newValue: U256::from(42) } @@ -87,7 +87,7 @@ fn test_update_one_value() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(contract)) .data(AccessListContract::valueCall {}.abi_encode().into()) .nonce(1) @@ -147,7 +147,7 @@ fn test_multi_sload_same_slot() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(contract)) .data(AccessListContract::getABCall {}.abi_encode().into()) .nonce(0) @@ -193,7 +193,7 @@ fn test_multi_sstore() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(contract)) .data( AccessListContract::insertMultipleCall { diff --git a/crates/shared/access-lists/tests/builder/transfers.rs b/crates/shared/access-lists/tests/builder/transfers.rs index 87157f5f..68306b10 100644 --- a/crates/shared/access-lists/tests/builder/transfers.rs +++ b/crates/shared/access-lists/tests/builder/transfers.rs @@ -3,15 +3,14 @@ use std::collections::HashMap; use super::{ - AccountInfo, BASE_SEPOLIA_CHAIN_ID, IntoAddress, ONE_ETHER, OpTransaction, TxEnv, TxKind, U256, + AccountInfo, DEVNET_CHAIN_ID, IntoAddress, ONE_ETHER, OpTransaction, TxEnv, TxKind, U256, execute_txns_build_access_list, }; #[test] /// Tests that the system precompiles get included in the access list fn test_precompiles() { - let base_tx = - TxEnv::builder().chain_id(Some(BASE_SEPOLIA_CHAIN_ID)).gas_limit(50_000).gas_price(0); + let base_tx = TxEnv::builder().chain_id(Some(DEVNET_CHAIN_ID)).gas_limit(50_000).gas_price(0); let tx = OpTransaction::builder().base(base_tx).build_fill(); let access_list = execute_txns_build_access_list(vec![tx], None, None) .expect("access list build should succeed"); @@ -32,7 +31,7 @@ fn test_single_transfer() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(recipient)) .value(U256::from(1_000_000)) .gas_price(0) @@ -73,7 +72,7 @@ fn test_gas_included_in_balance_change() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(recipient)) .value(U256::from(1_000_000)) .gas_price(1000) @@ -118,7 +117,7 @@ fn test_multiple_transfers() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .nonce(i) .kind(TxKind::Call(recipient)) .value(U256::from(1_000_000)) diff --git a/crates/shared/primitives/Cargo.toml b/crates/shared/primitives/Cargo.toml new file mode 100644 index 00000000..2a38b5a7 --- /dev/null +++ b/crates/shared/primitives/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "base-primitives" +description = "Shared primitives and test utilities for node-reth" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[features] +default = [] +test-utils = [ + "dep:alloy-consensus", + "dep:alloy-contract", + "dep:alloy-eips", + "dep:alloy-genesis", + "dep:alloy-signer", + "dep:alloy-signer-local", + "dep:alloy-sol-macro", + "dep:alloy-sol-types", + "dep:eyre", + "dep:op-alloy-network", + "dep:op-alloy-rpc-types", + "dep:serde_json", +] + +[dependencies] +alloy-primitives = { workspace = true, features = ["serde"] } + +# test-utils (optional) +alloy-consensus = { workspace = true, features = ["std"], optional = true } +alloy-contract = { workspace = true, optional = true } +alloy-eips = { workspace = true, optional = true } +alloy-genesis = { workspace = true, optional = true } +alloy-signer = { workspace = true, optional = true } +alloy-signer-local = { workspace = true, optional = true } +alloy-sol-macro = { workspace = true, features = ["json"], optional = true } +alloy-sol-types = { workspace = true, optional = true } +eyre = { workspace = true, optional = true } +op-alloy-network = { workspace = true, optional = true } +op-alloy-rpc-types = { workspace = true, optional = true } +serde_json = { workspace = true, optional = true } diff --git a/crates/client/test-utils/contracts/.gitignore b/crates/shared/primitives/contracts/.gitignore similarity index 100% rename from crates/client/test-utils/contracts/.gitignore rename to crates/shared/primitives/contracts/.gitignore diff --git a/crates/client/test-utils/contracts/README.md b/crates/shared/primitives/contracts/README.md similarity index 100% rename from crates/client/test-utils/contracts/README.md rename to crates/shared/primitives/contracts/README.md diff --git a/crates/client/test-utils/contracts/foundry.lock b/crates/shared/primitives/contracts/foundry.lock similarity index 100% rename from crates/client/test-utils/contracts/foundry.lock rename to crates/shared/primitives/contracts/foundry.lock diff --git a/crates/client/test-utils/contracts/foundry.toml b/crates/shared/primitives/contracts/foundry.toml similarity index 100% rename from crates/client/test-utils/contracts/foundry.toml rename to crates/shared/primitives/contracts/foundry.toml diff --git a/crates/client/test-utils/contracts/lib/forge-std b/crates/shared/primitives/contracts/lib/forge-std similarity index 100% rename from crates/client/test-utils/contracts/lib/forge-std rename to crates/shared/primitives/contracts/lib/forge-std diff --git a/crates/client/test-utils/contracts/lib/openzeppelin-contracts b/crates/shared/primitives/contracts/lib/openzeppelin-contracts similarity index 100% rename from crates/client/test-utils/contracts/lib/openzeppelin-contracts rename to crates/shared/primitives/contracts/lib/openzeppelin-contracts diff --git a/crates/client/test-utils/contracts/lib/solmate b/crates/shared/primitives/contracts/lib/solmate similarity index 100% rename from crates/client/test-utils/contracts/lib/solmate rename to crates/shared/primitives/contracts/lib/solmate diff --git a/crates/client/test-utils/contracts/script/DeployDoubleCounter.s.sol b/crates/shared/primitives/contracts/script/DeployDoubleCounter.s.sol similarity index 100% rename from crates/client/test-utils/contracts/script/DeployDoubleCounter.s.sol rename to crates/shared/primitives/contracts/script/DeployDoubleCounter.s.sol diff --git a/crates/client/test-utils/contracts/script/DeployERC20.s.sol b/crates/shared/primitives/contracts/script/DeployERC20.s.sol similarity index 100% rename from crates/client/test-utils/contracts/script/DeployERC20.s.sol rename to crates/shared/primitives/contracts/script/DeployERC20.s.sol diff --git a/crates/client/test-utils/contracts/src/AccessList.sol b/crates/shared/primitives/contracts/src/AccessList.sol similarity index 100% rename from crates/client/test-utils/contracts/src/AccessList.sol rename to crates/shared/primitives/contracts/src/AccessList.sol diff --git a/crates/client/test-utils/contracts/src/ContractFactory.sol b/crates/shared/primitives/contracts/src/ContractFactory.sol similarity index 100% rename from crates/client/test-utils/contracts/src/ContractFactory.sol rename to crates/shared/primitives/contracts/src/ContractFactory.sol diff --git a/crates/client/test-utils/contracts/src/DoubleCounter.sol b/crates/shared/primitives/contracts/src/DoubleCounter.sol similarity index 100% rename from crates/client/test-utils/contracts/src/DoubleCounter.sol rename to crates/shared/primitives/contracts/src/DoubleCounter.sol diff --git a/crates/client/test-utils/contracts/src/Minimal7702Account.sol b/crates/shared/primitives/contracts/src/Minimal7702Account.sol similarity index 100% rename from crates/client/test-utils/contracts/src/Minimal7702Account.sol rename to crates/shared/primitives/contracts/src/Minimal7702Account.sol diff --git a/crates/client/test-utils/contracts/src/Proxy.sol b/crates/shared/primitives/contracts/src/Proxy.sol similarity index 100% rename from crates/client/test-utils/contracts/src/Proxy.sol rename to crates/shared/primitives/contracts/src/Proxy.sol diff --git a/crates/shared/primitives/src/lib.rs b/crates/shared/primitives/src/lib.rs new file mode 100644 index 00000000..3c53919f --- /dev/null +++ b/crates/shared/primitives/src/lib.rs @@ -0,0 +1,9 @@ +//! Shared primitives and test utilities for node-reth crates. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +#[cfg(any(test, feature = "test-utils"))] +pub mod test_utils; + +#[cfg(any(test, feature = "test-utils"))] +pub use test_utils::*; diff --git a/crates/client/test-utils/src/accounts.rs b/crates/shared/primitives/src/test_utils/accounts.rs similarity index 85% rename from crates/client/test-utils/src/accounts.rs rename to crates/shared/primitives/src/test_utils/accounts.rs index 651fd77b..a67024e7 100644 --- a/crates/client/test-utils/src/accounts.rs +++ b/crates/shared/primitives/src/test_utils/accounts.rs @@ -5,12 +5,14 @@ use alloy_eips::eip2718::Encodable2718; use alloy_primitives::{Address, B256, Bytes, FixedBytes, TxHash, address, hex}; use alloy_signer::SignerSync; use alloy_signer_local::PrivateKeySigner; -use eyre::Result; +use eyre::{Result, eyre}; use op_alloy_network::TransactionBuilder; use op_alloy_rpc_types::OpTransactionRequest; -use reth::{revm::context::TransactionType, rpc::compat::SignTxRequestError}; -use crate::BASE_CHAIN_ID; +use super::DEVNET_CHAIN_ID; + +/// EIP-1559 transaction type constant. +const EIP1559_TX_TYPE: u8 = 2; /// Hardcoded test accounts using Anvil's deterministic keys. /// Derived from the test mnemonic: "test test test test test test test test test test test junk" @@ -77,42 +79,38 @@ impl Account { ) -> Result<(Bytes, Address, TxHash)> { let tx_request = OpTransactionRequest::default() .from(self.address()) - .transaction_type(TransactionType::Eip1559.into()) + .transaction_type(EIP1559_TX_TYPE) .with_gas_limit(3_000_000) .with_max_fee_per_gas(1_000_000_000) .with_max_priority_fee_per_gas(0) - .with_chain_id(BASE_CHAIN_ID) + .with_chain_id(DEVNET_CHAIN_ID) .with_deploy_code(bytecode) .with_nonce(nonce); - let tx = tx_request - .build_typed_tx() - .map_err(|_| SignTxRequestError::InvalidTransactionRequest)?; + let tx = tx_request.build_typed_tx().map_err(|_| eyre!("invalid transaction request"))?; let signature = self.signer().sign_hash_sync(&tx.signature_hash())?; let signed_tx = tx.into_signed(signature); let signed_tx_bytes = signed_tx.encoded_2718().into(); let contract_address = self.address().create(signed_tx.nonce()); - Ok((signed_tx_bytes, contract_address, signed_tx.hash().clone())) + Ok((signed_tx_bytes, contract_address, *signed_tx.hash())) } /// Sign a TransactionRequest and return the signed bytes. pub fn sign_txn_request(&self, tx_request: OpTransactionRequest) -> Result<(Bytes, TxHash)> { let tx_request = tx_request .from(self.address()) - .transaction_type(TransactionType::Eip1559.into()) + .transaction_type(EIP1559_TX_TYPE) .with_gas_limit(500_000) - .with_chain_id(BASE_CHAIN_ID) + .with_chain_id(DEVNET_CHAIN_ID) .with_max_fee_per_gas(1_000_000_000) .with_max_priority_fee_per_gas(0); - let tx = tx_request - .build_typed_tx() - .map_err(|_| SignTxRequestError::InvalidTransactionRequest)?; + let tx = tx_request.build_typed_tx().map_err(|_| eyre!("invalid transaction request"))?; let signature = self.signer().sign_hash_sync(&tx.signature_hash())?; let signed_tx = tx.into_signed(signature); let signed_tx_bytes = signed_tx.encoded_2718().into(); let tx_hash = signed_tx.hash(); - Ok((signed_tx_bytes, tx_hash.clone())) + Ok((signed_tx_bytes, *tx_hash)) } } diff --git a/crates/client/test-utils/src/contracts.rs b/crates/shared/primitives/src/test_utils/contracts.rs similarity index 53% rename from crates/client/test-utils/src/contracts.rs rename to crates/shared/primitives/src/test_utils/contracts.rs index c0bc4332..fe4214e6 100644 --- a/crates/client/test-utils/src/contracts.rs +++ b/crates/shared/primitives/src/test_utils/contracts.rs @@ -2,11 +2,6 @@ //! //! This module provides pre-compiled contract bindings that can be used //! across different test crates without needing relative path references. -//! -//! Contract sources: -//! - `DoubleCounter`: Custom test contract (src/DoubleCounter.sol) -//! - `MockERC20`: Solmate's MockERC20 (lib/solmate) -//! - `TransparentUpgradeableProxy`: OpenZeppelin's TransparentUpgradeableProxy (lib/openzeppelin-contracts) use alloy_sol_macro::sol; @@ -39,3 +34,39 @@ sol!( "/contracts/out/Minimal7702Account.sol/Minimal7702Account.json" ) ); + +sol!( + #[sol(rpc)] + AccessListContract, + concat!(env!("CARGO_MANIFEST_DIR"), "/contracts/out/AccessList.sol/AccessList.json") +); + +sol!( + #[sol(rpc)] + ContractFactory, + concat!(env!("CARGO_MANIFEST_DIR"), "/contracts/out/ContractFactory.sol/ContractFactory.json") +); + +sol!( + #[sol(rpc)] + SimpleStorage, + concat!(env!("CARGO_MANIFEST_DIR"), "/contracts/out/ContractFactory.sol/SimpleStorage.json") +); + +sol!( + #[sol(rpc)] + Proxy, + concat!(env!("CARGO_MANIFEST_DIR"), "/contracts/out/Proxy.sol/Proxy.json") +); + +sol!( + #[sol(rpc)] + Logic, + concat!(env!("CARGO_MANIFEST_DIR"), "/contracts/out/Proxy.sol/Logic.json") +); + +sol!( + #[sol(rpc)] + Logic2, + concat!(env!("CARGO_MANIFEST_DIR"), "/contracts/out/Proxy.sol/Logic2.json") +); diff --git a/crates/shared/primitives/src/test_utils/genesis.rs b/crates/shared/primitives/src/test_utils/genesis.rs new file mode 100644 index 00000000..cd4f5902 --- /dev/null +++ b/crates/shared/primitives/src/test_utils/genesis.rs @@ -0,0 +1,96 @@ +//! Genesis configuration utilities for testing. + +use std::collections::BTreeMap; + +use alloy_genesis::{ChainConfig, Genesis, GenesisAccount}; +use alloy_primitives::{Address, B256, Bytes, U256, utils::parse_ether}; + +use super::Account; + +/// Chain ID for devnet test network. +pub const DEVNET_CHAIN_ID: u64 = 84538453; + +/// Builds a test genesis configuration programmatically. +/// +/// Creates a Base Sepolia-like genesis with: +/// - All EVM and OP hardforks enabled from genesis +/// - Optimism EIP-1559 settings (elasticity=6, denominator=50) +/// - Pre-funded test accounts from the `Account` enum +pub fn build_test_genesis() -> Genesis { + // OP EIP-1559 base fee parameters + const EIP1559_ELASTICITY: u64 = 6; + const EIP1559_DENOMINATOR: u64 = 50; + + // Test account balance: 1 million ETH + let test_account_balance: U256 = parse_ether("1000000").expect("valid ether amount"); + + // Build chain config with all hardforks enabled at genesis + let config = ChainConfig { + chain_id: DEVNET_CHAIN_ID, + // Block-based EVM hardforks (all at block 0) + homestead_block: Some(0), + eip150_block: Some(0), + eip155_block: Some(0), + eip158_block: Some(0), + byzantium_block: Some(0), + constantinople_block: Some(0), + petersburg_block: Some(0), + istanbul_block: Some(0), + muir_glacier_block: Some(0), + berlin_block: Some(0), + london_block: Some(0), + arrow_glacier_block: Some(0), + gray_glacier_block: Some(0), + merge_netsplit_block: Some(0), + // Time-based hardforks + shanghai_time: Some(0), + cancun_time: Some(0), + prague_time: Some(0), + // Post-merge settings + terminal_total_difficulty: Some(U256::ZERO), + terminal_total_difficulty_passed: true, + // OP-specific hardforks and settings via extra_fields + extra_fields: [ + ("bedrockBlock", serde_json::json!(0)), + ("regolithTime", serde_json::json!(0)), + ("canyonTime", serde_json::json!(0)), + ("ecotoneTime", serde_json::json!(0)), + ("fjordTime", serde_json::json!(0)), + ("graniteTime", serde_json::json!(0)), + ("isthmusTime", serde_json::json!(0)), + ("jovianTime", serde_json::json!(0)), + ( + "optimism", + serde_json::json!({ + "eip1559Elasticity": EIP1559_ELASTICITY, + "eip1559Denominator": EIP1559_DENOMINATOR + }), + ), + ] + .into_iter() + .map(|(k, v)| (k.to_string(), v)) + .collect(), + ..Default::default() + }; + + // Pre-fund all test accounts + let alloc: BTreeMap = Account::all() + .into_iter() + .map(|account| { + (account.address(), GenesisAccount::default().with_balance(test_account_balance)) + }) + .collect(); + + Genesis { + config, + alloc, + gas_limit: 100_000_000, + difficulty: U256::ZERO, + nonce: 0, + timestamp: 0, + extra_data: Bytes::from_static(&[0x00]), + mix_hash: B256::ZERO, + coinbase: Address::ZERO, + ..Default::default() + } +} diff --git a/crates/shared/primitives/src/test_utils/mod.rs b/crates/shared/primitives/src/test_utils/mod.rs new file mode 100644 index 00000000..8437cffd --- /dev/null +++ b/crates/shared/primitives/src/test_utils/mod.rs @@ -0,0 +1,13 @@ +//! Test utilities including accounts, genesis configuration, and contract bindings. + +mod accounts; +pub use accounts::Account; + +mod genesis; +pub use genesis::{DEVNET_CHAIN_ID, build_test_genesis}; + +mod contracts; +pub use contracts::{ + AccessListContract, ContractFactory, DoubleCounter, Logic, Logic2, Minimal7702Account, + MockERC20, Proxy, SimpleStorage, TransparentUpgradeableProxy, +}; diff --git a/lychee.toml b/lychee.toml index 885df032..2109d843 100644 --- a/lychee.toml +++ b/lychee.toml @@ -3,7 +3,7 @@ no_progress = false exclude_all_private = false accept = [200, 403] # 403 status code is often returned by private repos instead of 404 exclude_path = [ - "client/test-utils/contracts/lib" + "shared/primitives/contracts/lib" ] exclude = [ 'foo.bar', From 571c1db97c19ae652d5933f1bb1544951c80cd09 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 22:33:14 -0600 Subject: [PATCH 13/19] chore: move FB test harness to flashblocks crate --- Cargo.lock | 8 +- crates/client/flashblocks/Cargo.toml | 10 +- crates/client/flashblocks/src/lib.rs | 3 + crates/client/flashblocks/src/test_harness.rs | 318 ++++++++++++++++++ .../client/flashblocks/tests/eip7702_tests.rs | 5 +- .../flashblocks/tests/eth_call_erc20.rs | 5 +- .../flashblocks/tests/flashblocks_rpc.rs | 3 +- crates/client/flashblocks/tests/state.rs | 7 +- crates/client/test-utils/Cargo.toml | 6 - .../test-utils/src/flashblocks_harness.rs | 72 ---- crates/client/test-utils/src/harness.rs | 2 +- crates/client/test-utils/src/lib.rs | 8 +- crates/client/test-utils/src/node.rs | 245 +------------- 13 files changed, 346 insertions(+), 346 deletions(-) create mode 100644 crates/client/flashblocks/src/test_harness.rs delete mode 100644 crates/client/test-utils/src/flashblocks_harness.rs diff --git a/Cargo.lock b/Cargo.lock index 705714ea..c60d104e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1586,9 +1586,11 @@ dependencies = [ "alloy-sol-types", "arc-swap", "base-client-primitives", + "base-flashblocks", "base-flashtypes", "base-test-utils", "criterion", + "derive_more", "eyre", "futures-util", "jsonrpsee", @@ -1735,19 +1737,14 @@ dependencies = [ "alloy-rpc-types-engine", "alloy-signer", "base-client-primitives", - "base-flashblocks", - "base-flashtypes", "base-primitives", "chrono", - "derive_more", "eyre", "jsonrpsee", - "once_cell", "op-alloy-network", "op-alloy-rpc-types-engine", "reth", "reth-db", - "reth-exex", "reth-ipc", "reth-node-core", "reth-optimism-chainspec", @@ -1759,7 +1756,6 @@ dependencies = [ "reth-rpc-layer", "reth-tracing", "tokio", - "tokio-stream", "tower", "tracing-subscriber 0.3.22", "url", diff --git a/crates/client/flashblocks/Cargo.toml b/crates/client/flashblocks/Cargo.toml index 41ebacb5..01813dc6 100644 --- a/crates/client/flashblocks/Cargo.toml +++ b/crates/client/flashblocks/Cargo.toml @@ -11,6 +11,9 @@ repository.workspace = true [lints] workspace = true +[features] +test-utils = [ "base-test-utils", "derive_more", "eyre" ] + [dependencies] # workspace base-flashtypes.workspace = true @@ -67,7 +70,13 @@ arc-swap.workspace = true metrics-derive.workspace = true rayon.workspace = true +# Optional test-utils dependencies +base-test-utils = { workspace = true, optional = true } +derive_more = { workspace = true, features = ["deref"], optional = true } +eyre = { workspace = true, optional = true } + [dev-dependencies] +base-flashblocks = { path = ".", features = ["test-utils"] } rstest.workspace = true rand.workspace = true eyre.workspace = true @@ -87,7 +96,6 @@ reth-optimism-node.workspace = true reth-transaction-pool = { workspace = true, features = ["test-utils"] } tokio-tungstenite.workspace = true tracing-subscriber.workspace = true -base-test-utils.workspace = true serde_json.workspace = true futures-util.workspace = true criterion = { version = "0.5", features = ["async_tokio"] } diff --git a/crates/client/flashblocks/src/lib.rs b/crates/client/flashblocks/src/lib.rs index dbed7230..06f1f2b0 100644 --- a/crates/client/flashblocks/src/lib.rs +++ b/crates/client/flashblocks/src/lib.rs @@ -46,3 +46,6 @@ pub use rpc::{ mod extension; pub use extension::{FlashblocksCell, FlashblocksConfig, FlashblocksExtension}; + +#[cfg(any(test, feature = "test-utils"))] +pub mod test_harness; diff --git a/crates/client/flashblocks/src/test_harness.rs b/crates/client/flashblocks/src/test_harness.rs new file mode 100644 index 00000000..a5a96704 --- /dev/null +++ b/crates/client/flashblocks/src/test_harness.rs @@ -0,0 +1,318 @@ +//! Flashblocks test harness module. +//! +//! Provides test utilities for flashblocks including: +//! - [`FlashblocksHarness`] - High-level test harness wrapping [`TestHarness`] +//! - [`FlashblocksParts`] - Components for interacting with flashblocks worker tasks +//! - [`FlashblocksTestExtension`] - Node extension for wiring up flashblocks in tests +//! - [`FlashblocksLocalNode`] - Local node wrapper with flashblocks helpers + +use std::{ + fmt, + sync::{Arc, Mutex}, + time::Duration, +}; + +use base_client_primitives::BaseNodeExtension; +use base_flashtypes::Flashblock; +use base_test_utils::{ + LocalNode, LocalNodeProvider, NODE_STARTUP_DELAY_MS, TestHarness, build_test_genesis, + init_silenced_tracing, +}; +use derive_more::Deref; +use eyre::Result; +use once_cell::sync::OnceCell; +use reth::providers::CanonStateSubscriptions; +use reth_optimism_chainspec::OpChainSpec; +use tokio::sync::{mpsc, oneshot}; +use tokio_stream::StreamExt; + +use crate::{ + EthApiExt, EthApiOverrideServer, EthPubSub, EthPubSubApiServer, FlashblocksReceiver, + FlashblocksState, +}; + +/// Convenience alias for the Flashblocks state backing the local node. +pub type LocalFlashblocksState = FlashblocksState; + +/// Components that allow tests to interact with the Flashblocks worker tasks. +#[derive(Clone)] +pub struct FlashblocksParts { + sender: mpsc::Sender<(Flashblock, oneshot::Sender<()>)>, + state: Arc, +} + +impl fmt::Debug for FlashblocksParts { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FlashblocksParts").finish_non_exhaustive() + } +} + +impl FlashblocksParts { + /// Clone the shared [`FlashblocksState`] handle. + pub fn state(&self) -> Arc { + self.state.clone() + } + + /// Send a flashblock to the background processor and wait until it is handled. + pub async fn send(&self, flashblock: Flashblock) -> Result<()> { + let (tx, rx) = oneshot::channel(); + self.sender.send((flashblock, tx)).await.map_err(|err| eyre::eyre!(err))?; + rx.await.map_err(|err| eyre::eyre!(err))?; + Ok(()) + } +} + +/// Test extension for flashblocks functionality. +/// +/// This extension wires up the flashblocks ExEx and RPC modules for testing, +/// with optional control over canonical block processing. +#[derive(Clone, Debug)] +pub struct FlashblocksTestExtension { + inner: Arc, +} + +struct FlashblocksTestExtensionInner { + sender: mpsc::Sender<(Flashblock, oneshot::Sender<()>)>, + #[allow(clippy::type_complexity)] + receiver: Arc)>>>>, + fb_cell: Arc>>, + process_canonical: bool, +} + +impl fmt::Debug for FlashblocksTestExtensionInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FlashblocksTestExtensionInner") + .field("process_canonical", &self.process_canonical) + .finish_non_exhaustive() + } +} + +impl FlashblocksTestExtension { + /// Create a new flashblocks test extension. + /// + /// If `process_canonical` is true, canonical blocks are automatically processed. + /// Set to false for tests that need manual control over canonical block timing. + pub fn new(process_canonical: bool) -> Self { + let (sender, receiver) = mpsc::channel::<(Flashblock, oneshot::Sender<()>)>(100); + let inner = FlashblocksTestExtensionInner { + sender, + receiver: Arc::new(Mutex::new(Some(receiver))), + fb_cell: Arc::new(OnceCell::new()), + process_canonical, + }; + Self { inner: Arc::new(inner) } + } + + /// Get the flashblocks parts after the node has been launched. + pub fn parts(&self) -> Result { + let state = self.inner.fb_cell.get().ok_or_else(|| { + eyre::eyre!("FlashblocksState should be initialized during node launch") + })?; + Ok(FlashblocksParts { sender: self.inner.sender.clone(), state: state.clone() }) + } +} + +impl BaseNodeExtension for FlashblocksTestExtension { + fn apply( + self: Box, + builder: base_client_primitives::OpBuilder, + ) -> base_client_primitives::OpBuilder { + let fb_cell = self.inner.fb_cell.clone(); + let receiver = self.inner.receiver.clone(); + let process_canonical = self.inner.process_canonical; + + let fb_cell_for_exex = fb_cell.clone(); + + builder + .install_exex("flashblocks-canon", move |mut ctx| { + let fb_cell = fb_cell_for_exex.clone(); + async move { + let provider = ctx.provider().clone(); + let fb = init_flashblocks_state(&fb_cell, &provider); + Ok(async move { + use reth_exex::ExExEvent; + while let Some(note) = ctx.notifications.try_next().await? { + if let Some(committed) = note.committed_chain() { + let hash = committed.tip().num_hash(); + if process_canonical { + // Many suites drive canonical updates manually to reproduce race conditions, so + // allowing this to be disabled keeps canonical replay deterministic. + let chain = Arc::unwrap_or_clone(committed); + for (_, block) in chain.into_blocks() { + fb.on_canonical_block_received(block); + } + } + let _ = ctx.events.send(ExExEvent::FinishedHeight(hash)); + } + } + Ok(()) + }) + } + }) + .extend_rpc_modules(move |ctx| { + let fb_cell = fb_cell.clone(); + let provider = ctx.provider().clone(); + let fb = init_flashblocks_state(&fb_cell, &provider); + + let mut canon_stream = tokio_stream::wrappers::BroadcastStream::new( + ctx.provider().subscribe_to_canonical_state(), + ); + tokio::spawn(async move { + use tokio_stream::StreamExt; + while let Some(Ok(notification)) = canon_stream.next().await { + provider.canonical_in_memory_state().notify_canon_state(notification); + } + }); + let api_ext = EthApiExt::new( + ctx.registry.eth_api().clone(), + ctx.registry.eth_handlers().filter.clone(), + fb.clone(), + ); + ctx.modules.replace_configured(api_ext.into_rpc())?; + + // Register eth_subscribe subscription endpoint for flashblocks + // Uses replace_configured since eth_subscribe already exists from reth's standard module + // Pass eth_api to enable proxying standard subscription types to reth's implementation + let eth_pubsub = EthPubSub::new(ctx.registry.eth_api().clone(), fb.clone()); + ctx.modules.replace_configured(eth_pubsub.into_rpc())?; + + let fb_for_task = fb.clone(); + let mut receiver = receiver + .lock() + .expect("flashblock receiver mutex poisoned") + .take() + .expect("flashblock receiver should only be initialized once"); + tokio::spawn(async move { + while let Some((payload, tx)) = receiver.recv().await { + fb_for_task.on_flashblock_received(payload); + let _ = tx.send(()); + } + }); + + Ok(()) + }) + } +} + +fn init_flashblocks_state( + cell: &Arc>>, + provider: &LocalNodeProvider, +) -> Arc { + cell.get_or_init(|| { + let fb = Arc::new(FlashblocksState::new(provider.clone(), 5)); + fb.start(); + fb + }) + .clone() +} + +/// Local node wrapper that exposes helpers specific to Flashblocks tests. +pub struct FlashblocksLocalNode { + node: LocalNode, + parts: FlashblocksParts, +} + +impl fmt::Debug for FlashblocksLocalNode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FlashblocksLocalNode") + .field("node", &self.node) + .field("parts", &self.parts) + .finish() + } +} + +impl FlashblocksLocalNode { + /// Launch a flashblocks-enabled node using the default configuration. + pub async fn new() -> Result { + Self::with_options(true).await + } + + /// Builds a flashblocks-enabled node with canonical block streaming disabled so tests can call + /// `FlashblocksState::on_canonical_block_received` at precise points. + pub async fn manual_canonical() -> Result { + Self::with_options(false).await + } + + async fn with_options(process_canonical: bool) -> Result { + // Build default chain spec programmatically + let genesis = build_test_genesis(); + let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); + + let extension = FlashblocksTestExtension::new(process_canonical); + let parts_source = extension.clone(); + let node = LocalNode::new(vec![Box::new(extension)], chain_spec).await?; + let parts = parts_source.parts()?; + Ok(Self { node, parts }) + } + + /// Access the shared Flashblocks state for assertions or manual driving. + pub fn flashblocks_state(&self) -> Arc { + self.parts.state() + } + + /// Send a flashblock through the background processor and await completion. + pub async fn send_flashblock(&self, flashblock: Flashblock) -> Result<()> { + self.parts.send(flashblock).await + } + + /// Split the wrapper into the underlying node plus flashblocks parts. + pub fn into_parts(self) -> (LocalNode, FlashblocksParts) { + (self.node, self.parts) + } +} + +/// Helper that exposes [`TestHarness`] conveniences plus Flashblocks helpers. +#[derive(Debug, Deref)] +pub struct FlashblocksHarness { + #[deref] + inner: TestHarness, + parts: FlashblocksParts, +} + +impl FlashblocksHarness { + /// Launch a flashblocks-enabled harness with automatic canonical processing. + pub async fn new() -> Result { + Self::with_options(true).await + } + + /// Launch the harness configured for manual canonical progression. + pub async fn manual_canonical() -> Result { + Self::with_options(false).await + } + + /// Get a handle to the in-memory Flashblocks state backing the harness. + pub fn flashblocks_state(&self) -> Arc { + self.parts.state() + } + + /// Send a single flashblock through the harness. + pub async fn send_flashblock(&self, flashblock: Flashblock) -> Result<()> { + self.parts.send(flashblock).await + } + + async fn with_options(process_canonical: bool) -> Result { + init_silenced_tracing(); + + // Build default chain spec programmatically + let genesis = build_test_genesis(); + let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); + + // Create the extension and keep a reference to get parts after launch + let extension = FlashblocksTestExtension::new(process_canonical); + let parts_source = extension.clone(); + + // Launch the node with the flashblocks extension + let node = LocalNode::new(vec![Box::new(extension)], chain_spec).await?; + let engine = node.engine_api()?; + + tokio::time::sleep(Duration::from_millis(NODE_STARTUP_DELAY_MS)).await; + + // Get the parts from the extension after node launch + let parts = parts_source.parts()?; + + // Create harness by building it directly (avoiding TestHarnessBuilder since we already have node) + let inner = TestHarness::from_parts(node, engine); + + Ok(Self { inner, parts }) + } +} diff --git a/crates/client/flashblocks/tests/eip7702_tests.rs b/crates/client/flashblocks/tests/eip7702_tests.rs index 4ddd8965..ab463d1f 100644 --- a/crates/client/flashblocks/tests/eip7702_tests.rs +++ b/crates/client/flashblocks/tests/eip7702_tests.rs @@ -8,12 +8,11 @@ use alloy_eips::{eip2718::Encodable2718, eip7702::Authorization}; use alloy_primitives::{Address, B256, Bytes, U256}; use alloy_provider::Provider; use alloy_sol_types::SolCall; +use base_flashblocks::test_harness::FlashblocksHarness; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{ - Account, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, Minimal7702Account, SignerSync, -}; +use base_test_utils::{Account, L1_BLOCK_INFO_DEPOSIT_TX, Minimal7702Account, SignerSync}; use eyre::Result; use op_alloy_network::ReceiptResponse; diff --git a/crates/client/flashblocks/tests/eth_call_erc20.rs b/crates/client/flashblocks/tests/eth_call_erc20.rs index cb833701..55b4317b 100644 --- a/crates/client/flashblocks/tests/eth_call_erc20.rs +++ b/crates/client/flashblocks/tests/eth_call_erc20.rs @@ -16,12 +16,11 @@ use alloy_primitives::{Address, B256, Bytes, U256}; use alloy_provider::Provider; use alloy_rpc_types_engine::PayloadId; use alloy_sol_types::{SolConstructor, SolValue}; +use base_flashblocks::test_harness::FlashblocksHarness; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{ - Account, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, MockERC20, TransparentUpgradeableProxy, -}; +use base_test_utils::{Account, L1_BLOCK_INFO_DEPOSIT_TX, MockERC20, TransparentUpgradeableProxy}; use eyre::Result; struct Erc20TestSetup { harness: FlashblocksHarness, diff --git a/crates/client/flashblocks/tests/flashblocks_rpc.rs b/crates/client/flashblocks/tests/flashblocks_rpc.rs index 3544af1e..e7d6fc57 100644 --- a/crates/client/flashblocks/tests/flashblocks_rpc.rs +++ b/crates/client/flashblocks/tests/flashblocks_rpc.rs @@ -11,10 +11,11 @@ use alloy_rpc_client::RpcClient; use alloy_rpc_types::simulate::{SimBlock, SimulatePayload}; use alloy_rpc_types_engine::PayloadId; use alloy_rpc_types_eth::{TransactionInput, error::EthRpcErrorCode}; +use base_flashblocks::test_harness::FlashblocksHarness; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{Account, DoubleCounter, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX}; +use base_test_utils::{Account, DoubleCounter, L1_BLOCK_INFO_DEPOSIT_TX}; use eyre::Result; use futures_util::{SinkExt, StreamExt}; use op_alloy_network::{Optimism, ReceiptResponse, TransactionResponse}; diff --git a/crates/client/flashblocks/tests/state.rs b/crates/client/flashblocks/tests/state.rs index 25068b0e..d6321351 100644 --- a/crates/client/flashblocks/tests/state.rs +++ b/crates/client/flashblocks/tests/state.rs @@ -6,13 +6,14 @@ use alloy_consensus::{Receipt, Transaction}; use alloy_eips::{BlockHashOrNumber, Encodable2718}; use alloy_primitives::{Address, B256, BlockNumber, Bytes, U256, hex::FromHex, map::HashMap}; use alloy_rpc_types_engine::PayloadId; -use base_flashblocks::{FlashblocksAPI, FlashblocksState, PendingBlocksAPI}; +use base_flashblocks::{ + FlashblocksAPI, FlashblocksState, PendingBlocksAPI, test_harness::FlashblocksHarness, +}; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; use base_test_utils::{ - Account, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, - LocalNodeProvider, + Account, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, LocalNodeProvider, }; use op_alloy_consensus::OpDepositReceipt; use op_alloy_network::BlockResponse; diff --git a/crates/client/test-utils/Cargo.toml b/crates/client/test-utils/Cargo.toml index 39fa52d3..3bd718e5 100644 --- a/crates/client/test-utils/Cargo.toml +++ b/crates/client/test-utils/Cargo.toml @@ -14,8 +14,6 @@ workspace = true [dependencies] # Project base-client-primitives.workspace = true -base-flashtypes.workspace = true -base-flashblocks.workspace = true base-primitives = { workspace = true, features = ["test-utils"] } # reth @@ -28,7 +26,6 @@ reth-provider.workspace = true reth-primitives-traits.workspace = true reth-db = { workspace = true, features = ["test-utils"] } reth-node-core.workspace = true -reth-exex.workspace = true reth-tracing.workspace = true reth-rpc-layer.workspace = true reth-ipc.workspace = true @@ -49,16 +46,13 @@ op-alloy-network.workspace = true # tokio tokio.workspace = true -tokio-stream.workspace = true # rpc jsonrpsee.workspace = true # misc -derive_more = { workspace = true, features = ["deref"] } tracing-subscriber.workspace = true eyre.workspace = true -once_cell.workspace = true url.workspace = true chrono.workspace = true diff --git a/crates/client/test-utils/src/flashblocks_harness.rs b/crates/client/test-utils/src/flashblocks_harness.rs deleted file mode 100644 index d56df358..00000000 --- a/crates/client/test-utils/src/flashblocks_harness.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! Flashblocks-aware wrapper around [`TestHarness`] that wires in the custom RPC modules. - -use std::{sync::Arc, time::Duration}; - -use base_flashtypes::Flashblock; -use derive_more::Deref; -use eyre::Result; -use reth_optimism_chainspec::OpChainSpec; -use tokio::time::sleep; - -use crate::{ - NODE_STARTUP_DELAY_MS, - harness::TestHarness, - init_silenced_tracing, - node::{FlashblocksParts, FlashblocksTestExtension, LocalFlashblocksState, LocalNode}, -}; - -/// Helper that exposes [`TestHarness`] conveniences plus Flashblocks helpers. -#[derive(Debug, Deref)] -pub struct FlashblocksHarness { - #[deref] - inner: TestHarness, - parts: FlashblocksParts, -} - -impl FlashblocksHarness { - /// Launch a flashblocks-enabled harness with automatic canonical processing. - pub async fn new() -> Result { - Self::with_options(true).await - } - - /// Launch the harness configured for manual canonical progression. - pub async fn manual_canonical() -> Result { - Self::with_options(false).await - } - - /// Get a handle to the in-memory Flashblocks state backing the harness. - pub fn flashblocks_state(&self) -> Arc { - self.parts.state() - } - - /// Send a single flashblock through the harness. - pub async fn send_flashblock(&self, flashblock: Flashblock) -> Result<()> { - self.parts.send(flashblock).await - } - - async fn with_options(process_canonical: bool) -> Result { - init_silenced_tracing(); - - // Build default chain spec programmatically - let genesis = crate::build_test_genesis(); - let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); - - // Create the extension and keep a reference to get parts after launch - let extension = FlashblocksTestExtension::new(process_canonical); - let parts_source = extension.clone(); - - // Launch the node with the flashblocks extension - let node = LocalNode::new(vec![Box::new(extension)], chain_spec).await?; - let engine = node.engine_api()?; - - sleep(Duration::from_millis(NODE_STARTUP_DELAY_MS)).await; - - // Get the parts from the extension after node launch - let parts = parts_source.parts()?; - - // Create harness by building it directly (avoiding TestHarnessBuilder since we already have node) - let inner = TestHarness::from_parts(node, engine); - - Ok(Self { inner, parts }) - } -} diff --git a/crates/client/test-utils/src/harness.rs b/crates/client/test-utils/src/harness.rs index 92dcf47d..3dad8758 100644 --- a/crates/client/test-utils/src/harness.rs +++ b/crates/client/test-utils/src/harness.rs @@ -95,7 +95,7 @@ impl TestHarness { /// Create a harness from pre-built parts. /// /// This is useful when you need to capture extension state before building the harness. - pub(crate) fn from_parts(node: LocalNode, engine: EngineApi) -> Self { + pub fn from_parts(node: LocalNode, engine: EngineApi) -> Self { Self { node, engine } } diff --git a/crates/client/test-utils/src/lib.rs b/crates/client/test-utils/src/lib.rs index 1ab87bfc..18ef3dea 100644 --- a/crates/client/test-utils/src/lib.rs +++ b/crates/client/test-utils/src/lib.rs @@ -22,19 +22,13 @@ pub use engine::{EngineAddress, EngineApi, EngineProtocol, HttpEngine, IpcEngine mod fixtures; pub use fixtures::{create_provider_factory, load_chain_spec}; -mod flashblocks_harness; -pub use flashblocks_harness::FlashblocksHarness; - mod harness; pub use harness::{TestHarness, TestHarnessBuilder}; mod node; // Re-export BaseNodeExtension for extension authors pub use base_client_primitives::BaseNodeExtension; -pub use node::{ - FlashblocksLocalNode, FlashblocksParts, FlashblocksTestExtension, LocalFlashblocksState, - LocalNode, LocalNodeProvider, -}; +pub use node::{LocalNode, LocalNodeProvider}; mod tracing; // Re-export signer traits for use in tests diff --git a/crates/client/test-utils/src/node.rs b/crates/client/test-utils/src/node.rs index c06bc86e..9c3c64f5 100644 --- a/crates/client/test-utils/src/node.rs +++ b/crates/client/test-utils/src/node.rs @@ -1,23 +1,11 @@ //! Local node setup with Base Sepolia chainspec -use std::{ - any::Any, - fmt, - net::SocketAddr, - path::PathBuf, - sync::{Arc, Mutex}, -}; +use std::{any::Any, fmt, net::SocketAddr, path::PathBuf, sync::Arc}; use alloy_provider::RootProvider; use alloy_rpc_client::RpcClient; -use base_client_primitives::{BaseNodeExtension, OpBuilder, OpProvider}; -use base_flashblocks::{ - EthApiExt, EthApiOverrideServer, EthPubSub, EthPubSubApiServer, FlashblocksReceiver, - FlashblocksState, -}; -use base_flashtypes::Flashblock; +use base_client_primitives::{BaseNodeExtension, OpProvider}; use eyre::Result; -use once_cell::sync::OnceCell; use op_alloy_network::Optimism; use reth::{ args::{DiscoveryArgs, NetworkArgs, RpcServerArgs}, @@ -29,23 +17,17 @@ use reth::{ use reth_db::{ ClientVersion, DatabaseEnv, init_db, mdbx::DatabaseArguments, test_utils::tempdir_path, }; -use reth_exex::ExExEvent; use reth_node_core::{ args::DatadirArgs, dirs::{DataDirPath, MaybePlatformPath}, }; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_node::{OpNode, args::RollupArgs}; -use reth_provider::CanonStateSubscriptions; -use tokio::sync::{mpsc, oneshot}; -use tokio_stream::StreamExt; use crate::engine::EngineApi; /// Convenience alias for the local blockchain provider type. pub type LocalNodeProvider = OpProvider; -/// Convenience alias for the Flashblocks state backing the local node. -pub type LocalFlashblocksState = FlashblocksState; /// Handle to a launched local node along with the resources required to keep it alive. pub struct LocalNode { @@ -76,162 +58,6 @@ impl fmt::Debug for LocalNode { } } -/// Components that allow tests to interact with the Flashblocks worker tasks. -#[derive(Clone)] -pub struct FlashblocksParts { - sender: mpsc::Sender<(Flashblock, oneshot::Sender<()>)>, - state: Arc, -} - -impl fmt::Debug for FlashblocksParts { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FlashblocksParts").finish_non_exhaustive() - } -} - -impl FlashblocksParts { - /// Clone the shared [`FlashblocksState`] handle. - pub fn state(&self) -> Arc { - self.state.clone() - } - - /// Send a flashblock to the background processor and wait until it is handled. - pub async fn send(&self, flashblock: Flashblock) -> Result<()> { - let (tx, rx) = oneshot::channel(); - self.sender.send((flashblock, tx)).await.map_err(|err| eyre::eyre!(err))?; - rx.await.map_err(|err| eyre::eyre!(err))?; - Ok(()) - } -} - -/// Test extension for flashblocks functionality. -/// -/// This extension wires up the flashblocks ExEx and RPC modules for testing, -/// with optional control over canonical block processing. -#[derive(Clone, Debug)] -pub struct FlashblocksTestExtension { - inner: Arc, -} - -struct FlashblocksTestExtensionInner { - sender: mpsc::Sender<(Flashblock, oneshot::Sender<()>)>, - #[allow(clippy::type_complexity)] - receiver: Arc)>>>>, - fb_cell: Arc>>, - process_canonical: bool, -} - -impl fmt::Debug for FlashblocksTestExtensionInner { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FlashblocksTestExtensionInner") - .field("process_canonical", &self.process_canonical) - .finish_non_exhaustive() - } -} - -impl FlashblocksTestExtension { - /// Create a new flashblocks test extension. - /// - /// If `process_canonical` is true, canonical blocks are automatically processed. - /// Set to false for tests that need manual control over canonical block timing. - pub fn new(process_canonical: bool) -> Self { - let (sender, receiver) = mpsc::channel::<(Flashblock, oneshot::Sender<()>)>(100); - let inner = FlashblocksTestExtensionInner { - sender, - receiver: Arc::new(Mutex::new(Some(receiver))), - fb_cell: Arc::new(OnceCell::new()), - process_canonical, - }; - Self { inner: Arc::new(inner) } - } - - /// Get the flashblocks parts after the node has been launched. - pub fn parts(&self) -> Result { - let state = self.inner.fb_cell.get().ok_or_else(|| { - eyre::eyre!("FlashblocksState should be initialized during node launch") - })?; - Ok(FlashblocksParts { sender: self.inner.sender.clone(), state: state.clone() }) - } -} - -impl BaseNodeExtension for FlashblocksTestExtension { - fn apply(self: Box, builder: OpBuilder) -> OpBuilder { - let fb_cell = self.inner.fb_cell.clone(); - let receiver = self.inner.receiver.clone(); - let process_canonical = self.inner.process_canonical; - - let fb_cell_for_exex = fb_cell.clone(); - - builder - .install_exex("flashblocks-canon", move |mut ctx| { - let fb_cell = fb_cell_for_exex.clone(); - async move { - let provider = ctx.provider().clone(); - let fb = init_flashblocks_state(&fb_cell, &provider); - Ok(async move { - while let Some(note) = ctx.notifications.try_next().await? { - if let Some(committed) = note.committed_chain() { - let hash = committed.tip().num_hash(); - if process_canonical { - // Many suites drive canonical updates manually to reproduce race conditions, so - // allowing this to be disabled keeps canonical replay deterministic. - let chain = Arc::unwrap_or_clone(committed); - for (_, block) in chain.into_blocks() { - fb.on_canonical_block_received(block); - } - } - let _ = ctx.events.send(ExExEvent::FinishedHeight(hash)); - } - } - Ok(()) - }) - } - }) - .extend_rpc_modules(move |ctx| { - let fb_cell = fb_cell.clone(); - let provider = ctx.provider().clone(); - let fb = init_flashblocks_state(&fb_cell, &provider); - - let mut canon_stream = tokio_stream::wrappers::BroadcastStream::new( - ctx.provider().subscribe_to_canonical_state(), - ); - tokio::spawn(async move { - use tokio_stream::StreamExt; - while let Some(Ok(notification)) = canon_stream.next().await { - provider.canonical_in_memory_state().notify_canon_state(notification); - } - }); - let api_ext = EthApiExt::new( - ctx.registry.eth_api().clone(), - ctx.registry.eth_handlers().filter.clone(), - fb.clone(), - ); - ctx.modules.replace_configured(api_ext.into_rpc())?; - - // Register eth_subscribe subscription endpoint for flashblocks - // Uses replace_configured since eth_subscribe already exists from reth's standard module - // Pass eth_api to enable proxying standard subscription types to reth's implementation - let eth_pubsub = EthPubSub::new(ctx.registry.eth_api().clone(), fb.clone()); - ctx.modules.replace_configured(eth_pubsub.into_rpc())?; - - let fb_for_task = fb.clone(); - let mut receiver = receiver - .lock() - .expect("flashblock receiver mutex poisoned") - .take() - .expect("flashblock receiver should only be initialized once"); - tokio::spawn(async move { - while let Some((payload, tx)) = receiver.recv().await { - fb_for_task.on_flashblock_received(payload); - let _ = tx.send(()); - } - }); - - Ok(()) - }) - } -} - impl LocalNode { /// Launch a new local node with the provided extensions and chain spec. pub async fn new( @@ -357,70 +183,3 @@ impl LocalNode { format!("ws://{}", self.ws_api_addr) } } - -fn init_flashblocks_state( - cell: &Arc>>, - provider: &LocalNodeProvider, -) -> Arc { - cell.get_or_init(|| { - let fb = Arc::new(FlashblocksState::new(provider.clone(), 5)); - fb.start(); - fb - }) - .clone() -} - -/// Local node wrapper that exposes helpers specific to Flashblocks tests. -pub struct FlashblocksLocalNode { - node: LocalNode, - parts: FlashblocksParts, -} - -impl fmt::Debug for FlashblocksLocalNode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FlashblocksLocalNode") - .field("node", &self.node) - .field("parts", &self.parts) - .finish() - } -} - -impl FlashblocksLocalNode { - /// Launch a flashblocks-enabled node using the default configuration. - pub async fn new() -> Result { - Self::with_options(true).await - } - - /// Builds a flashblocks-enabled node with canonical block streaming disabled so tests can call - /// `FlashblocksState::on_canonical_block_received` at precise points. - pub async fn manual_canonical() -> Result { - Self::with_options(false).await - } - - async fn with_options(process_canonical: bool) -> Result { - // Build default chain spec programmatically - let genesis = crate::build_test_genesis(); - let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); - - let extension = FlashblocksTestExtension::new(process_canonical); - let parts_source = extension.clone(); - let node = LocalNode::new(vec![Box::new(extension)], chain_spec).await?; - let parts = parts_source.parts()?; - Ok(Self { node, parts }) - } - - /// Access the shared Flashblocks state for assertions or manual driving. - pub fn flashblocks_state(&self) -> Arc { - self.parts.state() - } - - /// Send a flashblock through the background processor and await completion. - pub async fn send_flashblock(&self, flashblock: Flashblock) -> Result<()> { - self.parts.send(flashblock).await - } - - /// Split the wrapper into the underlying node plus flashblocks parts. - pub fn into_parts(self) -> (LocalNode, FlashblocksParts) { - (self.node, self.parts) - } -} From 3ceb99508aab938a4c6a7ab1e4474ca3fa7d64b6 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 22:50:32 -0600 Subject: [PATCH 14/19] chore: delete test-utils, rename client-primites to node --- Cargo.lock | 75 ++--- Cargo.toml | 3 +- bin/node/Cargo.toml | 2 +- bin/node/src/main.rs | 2 +- crates/client/client-primitives/Cargo.toml | 25 -- crates/client/flashblocks/Cargo.toml | 11 +- .../flashblocks/benches/pending_state.rs | 2 +- crates/client/flashblocks/src/extension.rs | 2 +- crates/client/flashblocks/src/test_harness.rs | 17 +- .../client/flashblocks/tests/eip7702_tests.rs | 4 +- .../flashblocks/tests/eth_call_erc20.rs | 4 +- .../flashblocks/tests/flashblocks_rpc.rs | 2 +- crates/client/flashblocks/tests/state.rs | 6 +- crates/client/metering/Cargo.toml | 4 +- crates/client/metering/src/extension.rs | 2 +- crates/client/metering/tests/meter.rs | 2 +- crates/client/metering/tests/meter_block.rs | 2 +- crates/client/metering/tests/meter_rpc.rs | 2 +- crates/client/node/Cargo.toml | 94 ++++++ .../{client-primitives => node}/README.md | 2 +- .../src/extension.rs | 0 .../{client-primitives => node}/src/handle.rs | 0 .../{client-primitives => node}/src/lib.rs | 6 + .../{client-primitives => node}/src/runner.rs | 0 .../src => node/src/test_utils}/constants.rs | 0 .../src => node/src/test_utils}/engine.rs | 2 +- .../src => node/src/test_utils}/fixtures.rs | 0 .../src => node/src/test_utils}/harness.rs | 20 +- .../src/lib.rs => node/src/test_utils/mod.rs} | 13 +- .../src => node/src/test_utils}/node.rs | 7 +- .../src => node/src/test_utils}/tracing.rs | 0 .../{client-primitives => node}/src/types.rs | 0 crates/client/test-utils/Cargo.toml | 62 ---- crates/client/test-utils/README.md | 307 ------------------ crates/client/txpool/Cargo.toml | 2 +- crates/client/txpool/src/extension.rs | 2 +- 36 files changed, 193 insertions(+), 491 deletions(-) delete mode 100644 crates/client/client-primitives/Cargo.toml create mode 100644 crates/client/node/Cargo.toml rename crates/client/{client-primitives => node}/README.md (94%) rename crates/client/{client-primitives => node}/src/extension.rs (100%) rename crates/client/{client-primitives => node}/src/handle.rs (100%) rename crates/client/{client-primitives => node}/src/lib.rs (72%) rename crates/client/{client-primitives => node}/src/runner.rs (100%) rename crates/client/{test-utils/src => node/src/test_utils}/constants.rs (100%) rename crates/client/{test-utils/src => node/src/test_utils}/engine.rs (99%) rename crates/client/{test-utils/src => node/src/test_utils}/fixtures.rs (100%) rename crates/client/{test-utils/src => node/src/test_utils}/harness.rs (95%) rename crates/client/{test-utils/src/lib.rs => node/src/test_utils/mod.rs} (71%) rename crates/client/{test-utils/src => node/src/test_utils}/node.rs (96%) rename crates/client/{test-utils/src => node/src/test_utils}/tracing.rs (100%) rename crates/client/{client-primitives => node}/src/types.rs (100%) delete mode 100644 crates/client/test-utils/Cargo.toml delete mode 100644 crates/client/test-utils/README.md diff --git a/Cargo.lock b/Cargo.lock index c60d104e..7fbebbda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1554,17 +1554,42 @@ dependencies = [ ] [[package]] -name = "base-client-primitives" +name = "base-client-node" version = "0.2.1" dependencies = [ + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-rpc-types-engine", + "alloy-signer", + "base-primitives", + "chrono", "derive_more", "eyre", "futures-util", + "jsonrpsee", + "op-alloy-network", + "op-alloy-rpc-types-engine", "reth", "reth-db", + "reth-ipc", + "reth-node-core", "reth-optimism-chainspec", "reth-optimism-node", + "reth-optimism-primitives", + "reth-optimism-rpc", + "reth-primitives-traits", + "reth-provider", + "reth-rpc-layer", + "reth-tracing", + "tokio", + "tower", "tracing", + "tracing-subscriber 0.3.22", + "url", ] [[package]] @@ -1585,10 +1610,9 @@ dependencies = [ "alloy-sol-macro", "alloy-sol-types", "arc-swap", - "base-client-primitives", + "base-client-node", "base-flashblocks", "base-flashtypes", - "base-test-utils", "criterion", "derive_more", "eyre", @@ -1659,8 +1683,7 @@ dependencies = [ "alloy-primitives", "alloy-rpc-client", "base-bundles", - "base-client-primitives", - "base-test-utils", + "base-client-node", "eyre", "jsonrpsee", "op-alloy-consensus", @@ -1705,7 +1728,7 @@ name = "base-reth-node" version = "0.2.1" dependencies = [ "base-cli-utils", - "base-client-primitives", + "base-client-node", "base-flashblocks", "base-metering", "base-txpool", @@ -1724,49 +1747,12 @@ dependencies = [ "reth-rpc-eth-types", ] -[[package]] -name = "base-test-utils" -version = "0.2.1" -dependencies = [ - "alloy-eips", - "alloy-genesis", - "alloy-primitives", - "alloy-provider", - "alloy-rpc-client", - "alloy-rpc-types", - "alloy-rpc-types-engine", - "alloy-signer", - "base-client-primitives", - "base-primitives", - "chrono", - "eyre", - "jsonrpsee", - "op-alloy-network", - "op-alloy-rpc-types-engine", - "reth", - "reth-db", - "reth-ipc", - "reth-node-core", - "reth-optimism-chainspec", - "reth-optimism-node", - "reth-optimism-primitives", - "reth-optimism-rpc", - "reth-primitives-traits", - "reth-provider", - "reth-rpc-layer", - "reth-tracing", - "tokio", - "tower", - "tracing-subscriber 0.3.22", - "url", -] - [[package]] name = "base-txpool" version = "0.2.1" dependencies = [ "alloy-primitives", - "base-client-primitives", + "base-client-node", "chrono", "derive_more", "eyre", @@ -7922,6 +7908,7 @@ dependencies = [ "alloy-eip2124", "alloy-hardforks", "alloy-primitives", + "arbitrary", "auto_impl", "once_cell", "rustc-hash", diff --git a/Cargo.toml b/Cargo.toml index 7908db43..ad905eda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,10 +58,9 @@ base-flashtypes = { path = "crates/shared/flashtypes" } base-primitives = { path = "crates/shared/primitives" } base-reth-rpc-types = { path = "crates/shared/reth-rpc-types" } # Client -base-client-primitives = { path = "crates/client/client-primitives" } +base-client-node = { path = "crates/client/node" } base-metering = { path = "crates/client/metering" } base-txpool = { path = "crates/client/txpool" } -base-test-utils = { path = "crates/client/test-utils" } base-flashblocks = { path = "crates/client/flashblocks" } # reth diff --git a/bin/node/Cargo.toml b/bin/node/Cargo.toml index 0b1be210..08e3c1a9 100644 --- a/bin/node/Cargo.toml +++ b/bin/node/Cargo.toml @@ -15,7 +15,7 @@ workspace = true [dependencies] # internal base-cli-utils.workspace = true -base-client-primitives.workspace = true +base-client-node.workspace = true base-flashblocks.workspace = true base-metering.workspace = true base-txpool.workspace = true diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index 9863940d..21ab8b92 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -7,7 +7,7 @@ pub mod cli; use std::sync::Arc; -use base_client_primitives::{BaseNodeRunner, OpProvider}; +use base_client_node::{BaseNodeRunner, OpProvider}; use base_flashblocks::{FlashblocksCell, FlashblocksExtension, FlashblocksState}; use base_metering::MeteringExtension; use base_txpool::TxPoolExtension; diff --git a/crates/client/client-primitives/Cargo.toml b/crates/client/client-primitives/Cargo.toml deleted file mode 100644 index 38130118..00000000 --- a/crates/client/client-primitives/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "base-client-primitives" -description = "Primitive types and traits for Base node runner extensions" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true - -[lints] -workspace = true - -[dependencies] -# reth -reth.workspace = true -reth-db.workspace = true -reth-optimism-node.workspace = true -reth-optimism-chainspec.workspace = true - -# misc -eyre.workspace = true -futures-util.workspace = true -tracing.workspace = true -derive_more = { workspace = true, features = ["debug"] } diff --git a/crates/client/flashblocks/Cargo.toml b/crates/client/flashblocks/Cargo.toml index 01813dc6..a8b15150 100644 --- a/crates/client/flashblocks/Cargo.toml +++ b/crates/client/flashblocks/Cargo.toml @@ -12,12 +12,18 @@ repository.workspace = true workspace = true [features] -test-utils = [ "base-test-utils", "derive_more", "eyre" ] +test-utils = [ + "base-client-node/test-utils", + "derive_more", + "eyre", + "reth-evm/test-utils", + "reth-primitives/test-utils", +] [dependencies] # workspace base-flashtypes.workspace = true -base-client-primitives.workspace = true +base-client-node.workspace = true # reth reth.workspace = true @@ -71,7 +77,6 @@ metrics-derive.workspace = true rayon.workspace = true # Optional test-utils dependencies -base-test-utils = { workspace = true, optional = true } derive_more = { workspace = true, features = ["deref"], optional = true } eyre = { workspace = true, optional = true } diff --git a/crates/client/flashblocks/benches/pending_state.rs b/crates/client/flashblocks/benches/pending_state.rs index 9f2aacfe..182b190a 100644 --- a/crates/client/flashblocks/benches/pending_state.rs +++ b/crates/client/flashblocks/benches/pending_state.rs @@ -8,11 +8,11 @@ use std::{ use alloy_eips::{BlockHashOrNumber, Encodable2718}; use alloy_primitives::{Address, B256, BlockNumber, Bytes, U256, bytes, hex::FromHex}; use alloy_rpc_types_engine::PayloadId; +use base_client_node::test_utils::{Account, LocalNodeProvider, TestHarness}; use base_flashblocks::{FlashblocksAPI, FlashblocksReceiver, FlashblocksState}; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{Account, LocalNodeProvider, TestHarness}; use criterion::{BatchSize, Criterion, Throughput, criterion_group, criterion_main}; use reth::{ chainspec::{ChainSpecProvider, EthChainSpec}, diff --git a/crates/client/flashblocks/src/extension.rs b/crates/client/flashblocks/src/extension.rs index ce4a53dd..ab72aaa7 100644 --- a/crates/client/flashblocks/src/extension.rs +++ b/crates/client/flashblocks/src/extension.rs @@ -3,7 +3,7 @@ use std::sync::Arc; -use base_client_primitives::{BaseNodeExtension, OpBuilder, OpProvider}; +use base_client_node::{BaseNodeExtension, OpBuilder, OpProvider}; use futures_util::TryStreamExt; use once_cell::sync::OnceCell; use reth_exex::ExExEvent; diff --git a/crates/client/flashblocks/src/test_harness.rs b/crates/client/flashblocks/src/test_harness.rs index a5a96704..cb9e1e62 100644 --- a/crates/client/flashblocks/src/test_harness.rs +++ b/crates/client/flashblocks/src/test_harness.rs @@ -12,12 +12,14 @@ use std::{ time::Duration, }; -use base_client_primitives::BaseNodeExtension; -use base_flashtypes::Flashblock; -use base_test_utils::{ - LocalNode, LocalNodeProvider, NODE_STARTUP_DELAY_MS, TestHarness, build_test_genesis, - init_silenced_tracing, +use base_client_node::{ + BaseNodeExtension, + test_utils::{ + LocalNode, LocalNodeProvider, NODE_STARTUP_DELAY_MS, TestHarness, build_test_genesis, + init_silenced_tracing, + }, }; +use base_flashtypes::Flashblock; use derive_more::Deref; use eyre::Result; use once_cell::sync::OnceCell; @@ -113,10 +115,7 @@ impl FlashblocksTestExtension { } impl BaseNodeExtension for FlashblocksTestExtension { - fn apply( - self: Box, - builder: base_client_primitives::OpBuilder, - ) -> base_client_primitives::OpBuilder { + fn apply(self: Box, builder: base_client_node::OpBuilder) -> base_client_node::OpBuilder { let fb_cell = self.inner.fb_cell.clone(); let receiver = self.inner.receiver.clone(); let process_canonical = self.inner.process_canonical; diff --git a/crates/client/flashblocks/tests/eip7702_tests.rs b/crates/client/flashblocks/tests/eip7702_tests.rs index ab463d1f..b1e17f46 100644 --- a/crates/client/flashblocks/tests/eip7702_tests.rs +++ b/crates/client/flashblocks/tests/eip7702_tests.rs @@ -8,11 +8,13 @@ use alloy_eips::{eip2718::Encodable2718, eip7702::Authorization}; use alloy_primitives::{Address, B256, Bytes, U256}; use alloy_provider::Provider; use alloy_sol_types::SolCall; +use base_client_node::test_utils::{ + Account, L1_BLOCK_INFO_DEPOSIT_TX, Minimal7702Account, SignerSync, +}; use base_flashblocks::test_harness::FlashblocksHarness; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{Account, L1_BLOCK_INFO_DEPOSIT_TX, Minimal7702Account, SignerSync}; use eyre::Result; use op_alloy_network::ReceiptResponse; diff --git a/crates/client/flashblocks/tests/eth_call_erc20.rs b/crates/client/flashblocks/tests/eth_call_erc20.rs index 55b4317b..531ddd0b 100644 --- a/crates/client/flashblocks/tests/eth_call_erc20.rs +++ b/crates/client/flashblocks/tests/eth_call_erc20.rs @@ -16,11 +16,13 @@ use alloy_primitives::{Address, B256, Bytes, U256}; use alloy_provider::Provider; use alloy_rpc_types_engine::PayloadId; use alloy_sol_types::{SolConstructor, SolValue}; +use base_client_node::test_utils::{ + Account, L1_BLOCK_INFO_DEPOSIT_TX, MockERC20, TransparentUpgradeableProxy, +}; use base_flashblocks::test_harness::FlashblocksHarness; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{Account, L1_BLOCK_INFO_DEPOSIT_TX, MockERC20, TransparentUpgradeableProxy}; use eyre::Result; struct Erc20TestSetup { harness: FlashblocksHarness, diff --git a/crates/client/flashblocks/tests/flashblocks_rpc.rs b/crates/client/flashblocks/tests/flashblocks_rpc.rs index e7d6fc57..3b46f047 100644 --- a/crates/client/flashblocks/tests/flashblocks_rpc.rs +++ b/crates/client/flashblocks/tests/flashblocks_rpc.rs @@ -11,11 +11,11 @@ use alloy_rpc_client::RpcClient; use alloy_rpc_types::simulate::{SimBlock, SimulatePayload}; use alloy_rpc_types_engine::PayloadId; use alloy_rpc_types_eth::{TransactionInput, error::EthRpcErrorCode}; +use base_client_node::test_utils::{Account, DoubleCounter, L1_BLOCK_INFO_DEPOSIT_TX}; use base_flashblocks::test_harness::FlashblocksHarness; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{Account, DoubleCounter, L1_BLOCK_INFO_DEPOSIT_TX}; use eyre::Result; use futures_util::{SinkExt, StreamExt}; use op_alloy_network::{Optimism, ReceiptResponse, TransactionResponse}; diff --git a/crates/client/flashblocks/tests/state.rs b/crates/client/flashblocks/tests/state.rs index d6321351..a0a85b80 100644 --- a/crates/client/flashblocks/tests/state.rs +++ b/crates/client/flashblocks/tests/state.rs @@ -6,15 +6,15 @@ use alloy_consensus::{Receipt, Transaction}; use alloy_eips::{BlockHashOrNumber, Encodable2718}; use alloy_primitives::{Address, B256, BlockNumber, Bytes, U256, hex::FromHex, map::HashMap}; use alloy_rpc_types_engine::PayloadId; +use base_client_node::test_utils::{ + Account, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, LocalNodeProvider, +}; use base_flashblocks::{ FlashblocksAPI, FlashblocksState, PendingBlocksAPI, test_harness::FlashblocksHarness, }; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{ - Account, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, LocalNodeProvider, -}; use op_alloy_consensus::OpDepositReceipt; use op_alloy_network::BlockResponse; use reth::{ diff --git a/crates/client/metering/Cargo.toml b/crates/client/metering/Cargo.toml index 0a370995..c45ee302 100644 --- a/crates/client/metering/Cargo.toml +++ b/crates/client/metering/Cargo.toml @@ -14,7 +14,7 @@ workspace = true [dependencies] # workspace base-bundles.workspace = true -base-client-primitives.workspace = true +base-client-node.workspace = true # reth reth.workspace = true @@ -39,7 +39,7 @@ eyre.workspace = true serde.workspace = true [dev-dependencies] -base-test-utils.workspace = true +base-client-node = { workspace = true, features = ["test-utils"] } reth-db = { workspace = true, features = ["test-utils"] } reth-db-common.workspace = true reth-optimism-node.workspace = true diff --git a/crates/client/metering/src/extension.rs b/crates/client/metering/src/extension.rs index 77b0fba2..69bdc7cd 100644 --- a/crates/client/metering/src/extension.rs +++ b/crates/client/metering/src/extension.rs @@ -1,7 +1,7 @@ //! Contains the [`MeteringExtension`] which wires up the metering RPC surface //! on the Base node builder. -use base_client_primitives::{BaseNodeExtension, OpBuilder}; +use base_client_node::{BaseNodeExtension, OpBuilder}; use tracing::info; use crate::{MeteringApiImpl, MeteringApiServer}; diff --git a/crates/client/metering/tests/meter.rs b/crates/client/metering/tests/meter.rs index de6875fd..8b541804 100644 --- a/crates/client/metering/tests/meter.rs +++ b/crates/client/metering/tests/meter.rs @@ -3,8 +3,8 @@ use alloy_eips::Encodable2718; use alloy_primitives::{Address, Bytes, U256, keccak256}; use base_bundles::{Bundle, ParsedBundle}; +use base_client_node::test_utils::{Account, TestHarness, load_chain_spec}; use base_metering::{MeteringExtension, meter_bundle}; -use base_test_utils::{Account, TestHarness, load_chain_spec}; use eyre::Context; use op_alloy_consensus::OpTxEnvelope; use reth::chainspec::EthChainSpec; diff --git a/crates/client/metering/tests/meter_block.rs b/crates/client/metering/tests/meter_block.rs index 1f3bbeb1..c8078b60 100644 --- a/crates/client/metering/tests/meter_block.rs +++ b/crates/client/metering/tests/meter_block.rs @@ -2,8 +2,8 @@ use alloy_consensus::{BlockHeader, Header}; use alloy_primitives::{Address, B256}; +use base_client_node::test_utils::{Account, TestHarness, load_chain_spec}; use base_metering::{MeteringExtension, meter_block}; -use base_test_utils::{Account, TestHarness, load_chain_spec}; use reth::chainspec::EthChainSpec; use reth_optimism_primitives::{OpBlock, OpBlockBody, OpTransactionSigned}; use reth_primitives_traits::Block as BlockT; diff --git a/crates/client/metering/tests/meter_rpc.rs b/crates/client/metering/tests/meter_rpc.rs index 6739f1a0..db619d30 100644 --- a/crates/client/metering/tests/meter_rpc.rs +++ b/crates/client/metering/tests/meter_rpc.rs @@ -4,8 +4,8 @@ use alloy_eips::Encodable2718; use alloy_primitives::{Bytes, U256, address, bytes}; use alloy_rpc_client::RpcClient; use base_bundles::{Bundle, MeterBundleResponse}; +use base_client_node::test_utils::{Account, TestHarness}; use base_metering::MeteringExtension; -use base_test_utils::{Account, TestHarness}; use op_alloy_consensus::OpTxEnvelope; use reth_optimism_primitives::OpTransactionSigned; use reth_transaction_pool::test_utils::TransactionBuilder; diff --git a/crates/client/node/Cargo.toml b/crates/client/node/Cargo.toml new file mode 100644 index 00000000..bfe488e6 --- /dev/null +++ b/crates/client/node/Cargo.toml @@ -0,0 +1,94 @@ +[package] +name = "base-client-node" +description = "Primitive types and traits for Base node runner extensions" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[features] +test-utils = [ + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-rpc-types-engine", + "alloy-signer", + "base-primitives/test-utils", + "chrono", + "jsonrpsee", + "op-alloy-network", + "op-alloy-rpc-types-engine", + "reth-db/test-utils", + "reth-ipc", + "reth-node-core", + "reth-optimism-rpc", + "reth-primitives-traits", + "reth-provider", + "reth-rpc-layer", + "reth-tracing", + "tokio", + "tower", + "tracing-subscriber", + "url", + "reth-optimism-node/test-utils", + "reth-primitives-traits?/test-utils", + "reth-provider?/test-utils" +] + +[dependencies] +# Project +base-primitives = { workspace = true, optional = true } + +# reth +reth.workspace = true +reth-db.workspace = true +reth-optimism-node.workspace = true +reth-optimism-chainspec.workspace = true +reth-ipc = { workspace = true, optional = true } +reth-tracing = { workspace = true, optional = true } +reth-rpc-layer = { workspace = true, optional = true } +reth-provider = { workspace = true, optional = true } +reth-node-core = { workspace = true, optional = true } +reth-primitives-traits = { workspace = true, optional = true } +reth-optimism-rpc = { workspace = true, features = ["client"], optional = true } +reth-optimism-primitives.workspace = true + +# alloy +alloy-primitives = { workspace = true, optional = true } +alloy-genesis = { workspace = true, optional = true } +alloy-eips = { workspace = true, optional = true } +alloy-rpc-types = { workspace = true, optional = true } +alloy-rpc-types-engine = { workspace = true, optional = true } +alloy-provider = { workspace = true, optional = true } +alloy-rpc-client = { workspace = true, optional = true } +alloy-signer = { workspace = true, optional = true } + +# op-alloy +op-alloy-rpc-types-engine = { workspace = true, optional = true } +op-alloy-network = { workspace = true, optional = true } + +# tokio +tokio = { workspace = true, optional = true } + +# rpc +jsonrpsee = { workspace = true, optional = true } + +# misc +eyre.workspace = true +futures-util.workspace = true +tracing.workspace = true +derive_more = { workspace = true, features = ["debug"] } +tracing-subscriber = { workspace = true, optional = true } +url = { workspace = true, optional = true } +chrono = { workspace = true, optional = true } + +# tower for middleware +tower = { version = "0.5", optional = true } diff --git a/crates/client/client-primitives/README.md b/crates/client/node/README.md similarity index 94% rename from crates/client/client-primitives/README.md rename to crates/client/node/README.md index 225e06b4..7b10150a 100644 --- a/crates/client/client-primitives/README.md +++ b/crates/client/node/README.md @@ -28,7 +28,7 @@ base-client-primitives = { git = "https://github.com/base/node-reth" } Implement a custom node extension: ```rust,ignore -use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; +use base_client_node::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; use eyre::Result; #[derive(Debug)] diff --git a/crates/client/client-primitives/src/extension.rs b/crates/client/node/src/extension.rs similarity index 100% rename from crates/client/client-primitives/src/extension.rs rename to crates/client/node/src/extension.rs diff --git a/crates/client/client-primitives/src/handle.rs b/crates/client/node/src/handle.rs similarity index 100% rename from crates/client/client-primitives/src/handle.rs rename to crates/client/node/src/handle.rs diff --git a/crates/client/client-primitives/src/lib.rs b/crates/client/node/src/lib.rs similarity index 72% rename from crates/client/client-primitives/src/lib.rs rename to crates/client/node/src/lib.rs index 2dc062de..25f03b3d 100644 --- a/crates/client/client-primitives/src/lib.rs +++ b/crates/client/node/src/lib.rs @@ -3,6 +3,9 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] +// Silence false positive - reth_optimism_primitives is used in test_utils module +use reth_optimism_primitives as _; + mod extension; pub use extension::BaseNodeExtension; @@ -14,3 +17,6 @@ pub use runner::BaseNodeRunner; mod types; pub use types::{BaseNodeBuilder, OpBuilder, OpProvider}; + +#[cfg(feature = "test-utils")] +pub mod test_utils; diff --git a/crates/client/client-primitives/src/runner.rs b/crates/client/node/src/runner.rs similarity index 100% rename from crates/client/client-primitives/src/runner.rs rename to crates/client/node/src/runner.rs diff --git a/crates/client/test-utils/src/constants.rs b/crates/client/node/src/test_utils/constants.rs similarity index 100% rename from crates/client/test-utils/src/constants.rs rename to crates/client/node/src/test_utils/constants.rs diff --git a/crates/client/test-utils/src/engine.rs b/crates/client/node/src/test_utils/engine.rs similarity index 99% rename from crates/client/test-utils/src/engine.rs rename to crates/client/node/src/test_utils/engine.rs index 8e8de2a2..76532653 100644 --- a/crates/client/test-utils/src/engine.rs +++ b/crates/client/node/src/test_utils/engine.rs @@ -21,7 +21,7 @@ use reth_rpc_layer::{AuthClientLayer, JwtSecret}; use reth_tracing::tracing::debug; use url::Url; -use crate::DEFAULT_JWT_SECRET; +use crate::test_utils::DEFAULT_JWT_SECRET; /// Describes how to reach the Engine API endpoint. #[derive(Clone, Debug)] diff --git a/crates/client/test-utils/src/fixtures.rs b/crates/client/node/src/test_utils/fixtures.rs similarity index 100% rename from crates/client/test-utils/src/fixtures.rs rename to crates/client/node/src/test_utils/fixtures.rs diff --git a/crates/client/test-utils/src/harness.rs b/crates/client/node/src/test_utils/harness.rs similarity index 95% rename from crates/client/test-utils/src/harness.rs rename to crates/client/node/src/test_utils/harness.rs index 3dad8758..16aed723 100644 --- a/crates/client/test-utils/src/harness.rs +++ b/crates/client/node/src/test_utils/harness.rs @@ -8,7 +8,6 @@ use alloy_provider::{Provider, RootProvider}; use alloy_rpc_client::RpcClient; use alloy_rpc_types::BlockNumberOrTag; use alloy_rpc_types_engine::PayloadAttributes; -use base_client_primitives::BaseNodeExtension; use eyre::{Result, eyre}; use op_alloy_network::Optimism; use op_alloy_rpc_types_engine::OpPayloadAttributes; @@ -19,11 +18,14 @@ use reth_primitives_traits::{Block as BlockT, RecoveredBlock}; use tokio::time::sleep; use crate::{ - BLOCK_BUILD_DELAY_MS, BLOCK_TIME_SECONDS, GAS_LIMIT, L1_BLOCK_INFO_DEPOSIT_TX, - NODE_STARTUP_DELAY_MS, - engine::{EngineApi, IpcEngine}, - node::{LocalNode, LocalNodeProvider}, - tracing::init_silenced_tracing, + BaseNodeExtension, + test_utils::{ + BLOCK_BUILD_DELAY_MS, BLOCK_TIME_SECONDS, GAS_LIMIT, L1_BLOCK_INFO_DEPOSIT_TX, + NODE_STARTUP_DELAY_MS, + engine::{EngineApi, IpcEngine}, + node::{LocalNode, LocalNodeProvider}, + tracing::init_silenced_tracing, + }, }; /// Builder for configuring and launching a test harness. @@ -60,7 +62,7 @@ impl TestHarnessBuilder { let chain_spec = match self.chain_spec { Some(spec) => spec, None => { - let genesis = crate::build_test_genesis(); + let genesis = crate::test_utils::build_test_genesis(); Arc::new(OpChainSpec::from_genesis(genesis)) } }; @@ -241,7 +243,7 @@ mod tests { use alloy_provider::Provider; use super::*; - use crate::Account; + use crate::test_utils::Account; #[tokio::test] async fn test_harness_setup() -> Result<()> { @@ -249,7 +251,7 @@ mod tests { let provider = harness.provider(); let chain_id = provider.get_chain_id().await?; - assert_eq!(chain_id, crate::DEVNET_CHAIN_ID); + assert_eq!(chain_id, crate::test_utils::DEVNET_CHAIN_ID); let alice_balance = provider.get_balance(Account::Alice.address()).await?; assert!(alice_balance > U256::ZERO); diff --git a/crates/client/test-utils/src/lib.rs b/crates/client/node/src/test_utils/mod.rs similarity index 71% rename from crates/client/test-utils/src/lib.rs rename to crates/client/node/src/test_utils/mod.rs index 18ef3dea..7976c184 100644 --- a/crates/client/test-utils/src/lib.rs +++ b/crates/client/node/src/test_utils/mod.rs @@ -1,7 +1,10 @@ -#![doc = include_str!("../README.md")] -#![doc(issue_tracker_base_url = "https://github.com/base/node-reth/issues/")] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -#![cfg_attr(not(test), warn(unused_crate_dependencies))] +//! Test utilities for integration testing. +//! +//! This module provides testing infrastructure including: +//! - [`TestHarness`] and [`TestHarnessBuilder`] - Unified test harness for node and engine. +//! - [`LocalNode`] and [`LocalNodeProvider`] - Local node setup. +//! - [`EngineApi`] with [`HttpEngine`] and [`IpcEngine`] - Engine API client. +//! - Test constants and fixtures. // Re-export from base-primitives for backwards compatibility pub use base_primitives::{ @@ -26,8 +29,6 @@ mod harness; pub use harness::{TestHarness, TestHarnessBuilder}; mod node; -// Re-export BaseNodeExtension for extension authors -pub use base_client_primitives::BaseNodeExtension; pub use node::{LocalNode, LocalNodeProvider}; mod tracing; diff --git a/crates/client/test-utils/src/node.rs b/crates/client/node/src/test_utils/node.rs similarity index 96% rename from crates/client/test-utils/src/node.rs rename to crates/client/node/src/test_utils/node.rs index 9c3c64f5..d8ce15c9 100644 --- a/crates/client/test-utils/src/node.rs +++ b/crates/client/node/src/test_utils/node.rs @@ -4,7 +4,6 @@ use std::{any::Any, fmt, net::SocketAddr, path::PathBuf, sync::Arc}; use alloy_provider::RootProvider; use alloy_rpc_client::RpcClient; -use base_client_primitives::{BaseNodeExtension, OpProvider}; use eyre::Result; use op_alloy_network::Optimism; use reth::{ @@ -24,7 +23,7 @@ use reth_node_core::{ use reth_optimism_chainspec::OpChainSpec; use reth_optimism_node::{OpNode, args::RollupArgs}; -use crate::engine::EngineApi; +use crate::{BaseNodeExtension, OpProvider, test_utils::engine::EngineApi}; /// Convenience alias for the local blockchain provider type. pub type LocalNodeProvider = OpProvider; @@ -169,8 +168,8 @@ impl LocalNode { } /// Build an Engine API client that talks to the node's IPC endpoint. - pub fn engine_api(&self) -> Result> { - EngineApi::::new(self.engine_ipc_path.clone()) + pub fn engine_api(&self) -> Result> { + EngineApi::::new(self.engine_ipc_path.clone()) } /// Clone the underlying blockchain provider so callers can inspect chain state. diff --git a/crates/client/test-utils/src/tracing.rs b/crates/client/node/src/test_utils/tracing.rs similarity index 100% rename from crates/client/test-utils/src/tracing.rs rename to crates/client/node/src/test_utils/tracing.rs diff --git a/crates/client/client-primitives/src/types.rs b/crates/client/node/src/types.rs similarity index 100% rename from crates/client/client-primitives/src/types.rs rename to crates/client/node/src/types.rs diff --git a/crates/client/test-utils/Cargo.toml b/crates/client/test-utils/Cargo.toml deleted file mode 100644 index 3bd718e5..00000000 --- a/crates/client/test-utils/Cargo.toml +++ /dev/null @@ -1,62 +0,0 @@ -[package] -name = "base-test-utils" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true -description = "Common integration test utilities for node-reth crates" - -[lints] -workspace = true - -[dependencies] -# Project -base-client-primitives.workspace = true -base-primitives = { workspace = true, features = ["test-utils"] } - -# reth -reth.workspace = true -reth-optimism-node.workspace = true -reth-optimism-chainspec.workspace = true -reth-optimism-primitives.workspace = true -reth-optimism-rpc = { workspace = true, features = ["client"] } -reth-provider.workspace = true -reth-primitives-traits.workspace = true -reth-db = { workspace = true, features = ["test-utils"] } -reth-node-core.workspace = true -reth-tracing.workspace = true -reth-rpc-layer.workspace = true -reth-ipc.workspace = true - -# alloy -alloy-primitives.workspace = true -alloy-genesis.workspace = true -alloy-eips.workspace = true -alloy-rpc-types.workspace = true -alloy-rpc-types-engine.workspace = true -alloy-provider.workspace = true -alloy-rpc-client.workspace = true -alloy-signer.workspace = true - -# op-alloy -op-alloy-rpc-types-engine.workspace = true -op-alloy-network.workspace = true - -# tokio -tokio.workspace = true - -# rpc -jsonrpsee.workspace = true - -# misc -tracing-subscriber.workspace = true -eyre.workspace = true -url.workspace = true -chrono.workspace = true - -# tower for middleware -tower = "0.5" - -[dev-dependencies] diff --git a/crates/client/test-utils/README.md b/crates/client/test-utils/README.md deleted file mode 100644 index 5bb44b1c..00000000 --- a/crates/client/test-utils/README.md +++ /dev/null @@ -1,307 +0,0 @@ -# Test Utils - -A comprehensive integration test framework for node-reth crates. - -## Overview - -This crate provides reusable testing utilities for integration tests across the node-reth workspace. It includes: - -- **LocalNode**: Isolated in-process node with Base Sepolia chainspec -- **TestHarness**: Unified orchestration layer combining node, Engine API, and flashblocks -- **EngineApi**: Type-safe Engine API client for CL operations -- **Test Accounts**: Pre-funded hardcoded accounts (Alice, Bob, Charlie, Deployer) -- **Flashblocks Support**: Testing pending state with flashblocks delivery - -## Quick Start - -```rust,ignore -use base_test_utils::TestHarness; - -#[tokio::test] -async fn test_example() -> eyre::Result<()> { - let harness = TestHarness::new().await?; - - // Advance the chain - harness.advance_chain(5).await?; - - // Access accounts - let alice = &harness.accounts().alice; - - // Get balance via provider - let balance = harness.provider().get_balance(alice.address).await?; - - Ok(()) -} -``` - -## Architecture - -The framework follows a three-layer architecture: - -```text -┌─────────────────────────────────────┐ -│ TestHarness │ ← Orchestration layer (tests use this) -│ - Coordinates node + engine │ -│ - Builds blocks from transactions │ -│ - Manages test accounts │ -│ - Manages flashblocks │ -└─────────────────────────────────────┘ - │ │ - ┌──────┘ └──────┐ - ▼ ▼ -┌─────────┐ ┌──────────┐ -│LocalNode│ │EngineApi │ ← Raw API wrappers -│ (EL) │ │ (CL) │ -└─────────┘ └──────────┘ -``` - -### Component Responsibilities - -- **LocalNode** (EL wrapper): In-process Optimism node with HTTP RPC + Engine API IPC -- **EngineApi** (CL wrapper): Raw Engine API calls (forkchoice, payloads) -- **TestHarness**: Orchestrates block building by fetching latest block headers and calling Engine API - -## Components - -### 1. TestHarness - -The main entry point for integration tests that only need canonical chain control. Combines node, engine, and accounts into a single interface. - -```rust,ignore -use base_test_utils::TestHarness; -use alloy_primitives::Bytes; - -#[tokio::test] -async fn test_harness() -> eyre::Result<()> { - let harness = TestHarness::new().await?; - - // Access provider - let provider = harness.provider(); - let chain_id = provider.get_chain_id().await?; - - // Access accounts - let alice = &harness.accounts().alice; - let bob = &harness.accounts().bob; - - // Build empty blocks - harness.advance_chain(10).await?; - - // Build block with transactions - let txs: Vec = vec![/* signed transaction bytes */]; - harness.build_block_from_transactions(txs).await?; - - Ok(()) -} -``` - -> Need pending-state testing? Use `FlashblocksHarness` (see Flashblocks section below) to gain `send_flashblock` helpers. - -**Key Methods:** -- `new()` - Create new harness with node, engine, and accounts -- `provider()` - Get Alloy RootProvider for RPC calls -- `accounts()` - Access test accounts -- `advance_chain(n)` - Build N empty blocks -- `build_block_from_transactions(txs)` - Build block with specific transactions (auto-prepends the L1 block info deposit) - -**Block Building Process:** -1. Fetches latest block header from provider (no local state tracking) -2. Calculates next timestamp (parent + 2 seconds for Base) -3. Calls `engine.update_forkchoice()` with payload attributes -4. Waits for block construction -5. Calls `engine.get_payload()` to retrieve built payload -6. Calls `engine.new_payload()` to validate and submit -7. Calls `engine.update_forkchoice()` again to finalize - -### 2. LocalNode - -In-process Optimism node with Base Sepolia configuration. - -```rust,ignore -use base_test_utils::{LocalNode, default_launcher}; - -#[tokio::test] -async fn test_node() -> eyre::Result<()> { - let node = LocalNode::new(default_launcher).await?; - - let provider = node.provider()?; - let engine = node.engine_api()?; - - Ok(()) -} -``` - -**Features (base):** -- Base Sepolia chain configuration -- Disabled P2P discovery (isolated testing) -- Random unused ports (parallel test safety) -- HTTP RPC server at `node.http_api_addr` -- Engine API IPC at `node.engine_ipc_path` - -For flashblocks-enabled nodes, use `FlashblocksLocalNode`: - -```rust,ignore -use base_test_utils::FlashblocksLocalNode; - -let node = FlashblocksLocalNode::new().await?; -let pending_state = node.flashblocks_state(); -node.send_flashblock(flashblock).await?; -``` - -**Note:** Most tests should use `TestHarness` instead of `LocalNode` directly. - -### 3. EngineApi - -Type-safe Engine API client wrapping raw CL operations. - -```rust,ignore -use base_test_utils::EngineApi; -use alloy_primitives::B256; -use op_alloy_rpc_types_engine::OpPayloadAttributes; - -// Usually accessed via TestHarness, but can be used directly -let engine = node.engine_api()?; - -// Raw Engine API calls -let fcu = engine.update_forkchoice(current_head, new_head, Some(attrs)).await?; -let payload = engine.get_payload(payload_id).await?; -let status = engine.new_payload(payload, vec![], parent_root, requests).await?; -``` - -**Methods:** -- `get_payload(payload_id)` - Retrieve built payload by ID -- `new_payload(payload, hashes, root, requests)` - Submit new payload -- `update_forkchoice(current, new, attrs)` - Update forkchoice state - -**Note:** EngineApi is stateless. Block building logic lives in `TestHarness`. - -### 4. Test Accounts - -Hardcoded test accounts with deterministic addresses (Anvil-compatible). - -```rust,ignore -use base_test_utils::{TestAccounts, TestHarness}; - -let accounts = TestAccounts::new(); - -let alice = &accounts.alice; -let bob = &accounts.bob; -let charlie = &accounts.charlie; -let deployer = &accounts.deployer; - -// Access via harness -let harness = TestHarness::new().await?; -let alice = &harness.accounts().alice; -``` - -**Account Details:** -- **Alice**: `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266` - 10,000 ETH -- **Bob**: `0x70997970C51812dc3A010C7d01b50e0d17dc79C8` - 10,000 ETH -- **Charlie**: `0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC` - 10,000 ETH -- **Deployer**: `0x90F79bf6EB2c4f870365E785982E1f101E93b906` - 10,000 ETH - -Each account includes: -- `name` - Account identifier -- `address` - Ethereum address -- `private_key` - Private key (hex string) -- `initial_balance_eth` - Starting balance in ETH - -### 5. Flashblocks Support - -Use `FlashblocksHarness` when you need `send_flashblock` and access to the in-memory pending state. - -```rust,ignore -use base_test_utils::FlashblocksHarness; - -#[tokio::test] -async fn test_flashblocks() -> eyre::Result<()> { - let harness = FlashblocksHarness::new().await?; - - harness.send_flashblock(flashblock).await?; - - let pending = harness.flashblocks_state(); - // assertions... - - Ok(()) -} -``` - -`FlashblocksHarness` derefs to the base `TestHarness`, so you can keep using methods like `provider()`, `build_block_from_transactions`, etc. - -Test flashblocks delivery without WebSocket connections by constructing payloads and sending them through `FlashblocksHarness` (or the lower-level `FlashblocksLocalNode`). - -## Configuration Constants - -Key constants are exported from the crate root: - -```rust,ignore -use base_test_utils::{ - BASE_CHAIN_ID, // Chain ID for Base Sepolia (84532) - BLOCK_TIME_SECONDS, // Base L2 block time (2 seconds) - GAS_LIMIT, // Default gas limit (200M) - NODE_STARTUP_DELAY_MS, // IPC endpoint initialization (500ms) - BLOCK_BUILD_DELAY_MS, // Payload construction wait (100ms) - L1_BLOCK_INFO_DEPOSIT_TX, // Pre-captured L1 block info deposit - L1_BLOCK_INFO_DEPOSIT_TX_HASH, - DEFAULT_JWT_SECRET, // All-zeros JWT for local testing -}; -``` - -## Usage in Other Crates - -Add to `dev-dependencies`: - -```toml -[dev-dependencies] -base-test-utils.workspace = true -``` - -Import in tests: - -```rust,ignore -use base_test_utils::TestHarness; - -#[tokio::test] -async fn my_test() -> eyre::Result<()> { - let harness = TestHarness::new().await?; - // Your test logic - - Ok(()) -} -``` - -## Design Principles - -1. **Separation of Concerns**: LocalNode (EL), EngineApi (CL), TestHarness (orchestration) -2. **Stateless Components**: No local state tracking; always fetch from provider -3. **Type Safety**: Use reth's `OpEngineApiClient` trait instead of raw RPC strings -4. **Parallel Testing**: Random ports + isolated nodes enable concurrent tests -5. **Anvil Compatibility**: Same mnemonic as Anvil for tooling compatibility - -## Testing - -Run the test suite: - -```bash -cargo test -p base-reth-test-utils -``` - -Run specific test: - -```bash -cargo test -p base-reth-test-utils test_harness_setup -``` - -## Future Enhancements - -- Transaction builders for common operations -- Smart contract deployment helpers (Foundry integration planned) -- Snapshot/restore functionality -- Multi-node network simulation -- Performance benchmarking utilities -- Helper builder for Flashblocks - -## References - -Inspired by: -- [op-rbuilder test framework](https://github.com/flashbots/op-rbuilder/tree/main/crates/op-rbuilder/src/tests/framework) -- [reth e2e-test-utils](https://github.com/paradigmxyz/reth/tree/main/crates/e2e-test-utils) diff --git a/crates/client/txpool/Cargo.toml b/crates/client/txpool/Cargo.toml index 3d7806c6..8a031068 100644 --- a/crates/client/txpool/Cargo.toml +++ b/crates/client/txpool/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] # workspace -base-client-primitives.workspace = true +base-client-node.workspace = true # reth reth.workspace = true diff --git a/crates/client/txpool/src/extension.rs b/crates/client/txpool/src/extension.rs index e7687ce1..92f366c2 100644 --- a/crates/client/txpool/src/extension.rs +++ b/crates/client/txpool/src/extension.rs @@ -1,7 +1,7 @@ //! Contains the [`TxPoolExtension`] which wires up the transaction pool features //! (tracing ExEx and status RPC) on the Base node builder. -use base_client_primitives::{BaseNodeExtension, OpBuilder}; +use base_client_node::{BaseNodeExtension, OpBuilder}; use tracing::info; use crate::{TransactionStatusApiImpl, TransactionStatusApiServer, tracex_exex}; From 20a3495c56906be0be4cbe77f3caac856164dd1d Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 22:59:22 -0600 Subject: [PATCH 15/19] chore: add GHA/script to check that shared crates dont depend on client/ --- .github/workflows/ci.yml | 15 +++++++++++++++ Justfile | 4 ++++ scripts/check-crate-deps.sh | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100755 scripts/check-crate-deps.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0bd83c3..96180292 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -159,3 +159,18 @@ jobs: with: version: stable - run: just check-udeps + + crate-deps: + name: Crate Dependencies + runs-on: ubuntu-latest + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: dtolnay/rust-toolchain@4305c38b25d97ef35a8ad1f985ccf2d2242004f2 # stable + - name: Install just + uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3 + - run: just check-crate-deps diff --git a/Justfile b/Justfile index fe1ccf12..b231c87e 100644 --- a/Justfile +++ b/Justfile @@ -94,6 +94,10 @@ check-udeps: build-contracts @command -v cargo-udeps >/dev/null 2>&1 || cargo install cargo-udeps cargo +nightly udeps --workspace --all-features --all-targets +# Checks that shared crates don't depend on client crates +check-crate-deps: + ./scripts/check-crate-deps.sh + # Watches tests watch-test: build-contracts cargo watch -x test diff --git a/scripts/check-crate-deps.sh b/scripts/check-crate-deps.sh new file mode 100755 index 00000000..f8b478aa --- /dev/null +++ b/scripts/check-crate-deps.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Use cargo metadata to get structured dependency information +# --no-deps: only workspace members, not transitive deps +# --format-version 1: stable JSON format + +echo "Checking that shared crates don't depend on client crates..." + +# Find all violations using jq: +# 1. Select packages in crates/shared/ +# 2. For each, find dependencies with paths pointing to crates/client/ +# 3. Output violations as "shared_crate -> client_crate" +VIOLATIONS=$(cargo metadata --format-version 1 --no-deps | jq -r ' + [.packages[] + | select(.manifest_path | contains("/crates/shared/")) + | . as $pkg + | .dependencies[] + | select(.path) + | select(.path | contains("/crates/client/")) + | "\($pkg.name) -> \(.name)" + ] + | .[] +') + +if [ -n "$VIOLATIONS" ]; then + echo "ERROR: Found dependency boundary violations:" + echo "$VIOLATIONS" | while read -r violation; do + echo " - $violation" + done + echo "" + echo "Shared crates (crates/shared/) must not depend on client crates (crates/client/)" + exit 1 +fi + +echo "All shared crates have valid dependencies (no client crate dependencies)" From 391034c7d1a6def10790dfe371523d3ca4c0053e Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 23:10:54 -0600 Subject: [PATCH 16/19] chore: format features and update lockfile --- Cargo.lock | 95 +++++++++++++++++++++++++++++++++++ crates/client/node/Cargo.toml | 6 +-- 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7fbebbda..ae7bfb46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7536,12 +7536,16 @@ dependencies = [ "rayon", "reth-config", "reth-consensus", + "reth-ethereum-primitives", "reth-metrics", "reth-network-p2p", "reth-network-peers", "reth-primitives-traits", + "reth-provider", "reth-storage-api", "reth-tasks", + "reth-testing-utils", + "tempfile", "thiserror 2.0.17", "tokio", "tokio-stream", @@ -7549,6 +7553,64 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-e2e-test-utils" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rlp", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "alloy-signer", + "alloy-signer-local", + "derive_more", + "eyre", + "futures-util", + "jsonrpsee", + "reth-chainspec", + "reth-cli-commands", + "reth-config", + "reth-consensus", + "reth-db", + "reth-db-common", + "reth-engine-local", + "reth-engine-primitives", + "reth-ethereum-primitives", + "reth-network-api", + "reth-network-p2p", + "reth-network-peers", + "reth-node-api", + "reth-node-builder", + "reth-node-core", + "reth-node-ethereum", + "reth-payload-builder", + "reth-payload-builder-primitives", + "reth-payload-primitives", + "reth-primitives", + "reth-primitives-traits", + "reth-provider", + "reth-rpc-api", + "reth-rpc-builder", + "reth-rpc-eth-api", + "reth-rpc-server-types", + "reth-stages-types", + "reth-tasks", + "reth-tokio-util", + "reth-tracing", + "revm", + "serde_json", + "tempfile", + "tokio", + "tokio-stream", + "tracing", + "url", +] + [[package]] name = "reth-ecies" version = "1.9.3" @@ -7671,6 +7733,7 @@ dependencies = [ "parking_lot", "rayon", "reth-chain-state", + "reth-chainspec", "reth-consensus", "reth-db", "reth-engine-primitives", @@ -7685,9 +7748,13 @@ dependencies = [ "reth-primitives-traits", "reth-provider", "reth-prune", + "reth-prune-types", "reth-revm", + "reth-stages", "reth-stages-api", + "reth-static-file", "reth-tasks", + "reth-tracing", "reth-trie", "reth-trie-parallel", "reth-trie-sparse", @@ -8307,6 +8374,7 @@ dependencies = [ "auto_impl", "derive_more", "futures", + "parking_lot", "reth-consensus", "reth-eth-wire-types", "reth-ethereum-primitives", @@ -8816,6 +8884,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", + "alloy-genesis", "alloy-primitives", "alloy-rpc-types-engine", "alloy-rpc-types-eth", @@ -8826,6 +8895,7 @@ dependencies = [ "op-revm", "reth-chainspec", "reth-consensus", + "reth-e2e-test-utils", "reth-engine-local", "reth-evm", "reth-network", @@ -8847,11 +8917,13 @@ dependencies = [ "reth-rpc-api", "reth-rpc-engine-api", "reth-rpc-server-types", + "reth-tasks", "reth-tracing", "reth-transaction-pool", "reth-trie-common", "revm", "serde", + "serde_json", "tokio", "url", ] @@ -9165,6 +9237,7 @@ dependencies = [ "reth-db", "reth-db-api", "reth-errors", + "reth-ethereum-engine-primitives", "reth-ethereum-primitives", "reth-execution-types", "reth-fs-util", @@ -9180,7 +9253,9 @@ dependencies = [ "reth-trie", "reth-trie-db", "revm-database", + "revm-state", "strum 0.27.2", + "tokio", "tracing", ] @@ -9624,6 +9699,7 @@ dependencies = [ "num-traits", "rayon", "reqwest", + "reth-chainspec", "reth-codecs", "reth-config", "reth-consensus", @@ -9632,6 +9708,7 @@ dependencies = [ "reth-era", "reth-era-downloader", "reth-era-utils", + "reth-ethereum-primitives", "reth-etl", "reth-evm", "reth-execution-types", @@ -9646,8 +9723,10 @@ dependencies = [ "reth-stages-api", "reth-static-file-types", "reth-storage-errors", + "reth-testing-utils", "reth-trie", "reth-trie-db", + "tempfile", "thiserror 2.0.17", "tokio", "tracing", @@ -9783,6 +9862,22 @@ dependencies = [ "tracing-futures", ] +[[package]] +name = "reth-testing-utils" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "rand 0.8.5", + "rand 0.9.2", + "reth-ethereum-primitives", + "reth-primitives-traits", + "secp256k1 0.30.0", +] + [[package]] name = "reth-tokio-util" version = "1.9.3" diff --git a/crates/client/node/Cargo.toml b/crates/client/node/Cargo.toml index bfe488e6..90b60bc6 100644 --- a/crates/client/node/Cargo.toml +++ b/crates/client/node/Cargo.toml @@ -29,18 +29,18 @@ test-utils = [ "reth-db/test-utils", "reth-ipc", "reth-node-core", + "reth-optimism-node/test-utils", "reth-optimism-rpc", "reth-primitives-traits", + "reth-primitives-traits?/test-utils", "reth-provider", + "reth-provider?/test-utils", "reth-rpc-layer", "reth-tracing", "tokio", "tower", "tracing-subscriber", "url", - "reth-optimism-node/test-utils", - "reth-primitives-traits?/test-utils", - "reth-provider?/test-utils" ] [dependencies] From 49f6d1c8da79f9cb90608be5912437394ab24f4c Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sun, 11 Jan 2026 16:11:09 -0600 Subject: [PATCH 17/19] review feedback --- bin/node/src/cli.rs | 25 ++++++---- bin/node/src/main.rs | 7 ++- crates/client/flashblocks/Cargo.toml | 4 +- crates/client/node/Cargo.toml | 50 ++++++++++--------- crates/client/node/README.md | 6 +-- crates/client/node/src/lib.rs | 3 -- .../client/node/src/test_utils/constants.rs | 8 ++- crates/client/node/src/test_utils/engine.rs | 4 +- crates/client/node/src/test_utils/fixtures.rs | 14 +++--- crates/client/node/src/test_utils/mod.rs | 7 +-- crates/client/txpool/README.md | 12 +++-- crates/client/txpool/src/extension.rs | 35 +++++++------ crates/client/txpool/src/lib.rs | 2 +- crates/shared/primitives/README.md | 26 ++++++++++ crates/shared/primitives/src/lib.rs | 5 +- .../primitives/src/test_utils/genesis.rs | 5 +- .../shared/primitives/src/test_utils/mod.rs | 2 +- 17 files changed, 130 insertions(+), 85 deletions(-) create mode 100644 crates/shared/primitives/README.md diff --git a/bin/node/src/cli.rs b/bin/node/src/cli.rs index b4385ca1..36aaf70c 100644 --- a/bin/node/src/cli.rs +++ b/bin/node/src/cli.rs @@ -1,7 +1,7 @@ //! Contains the CLI arguments use base_flashblocks::FlashblocksConfig; -use base_txpool::TracingConfig; +use base_txpool::TxpoolConfig; use reth_optimism_node::args::RollupArgs; /// CLI Arguments @@ -46,20 +46,23 @@ impl Args { pub const fn flashblocks_enabled(&self) -> bool { self.websocket_url.is_some() } +} - /// Build FlashblocksConfig from CLI args. - pub fn flashblocks_config(&self) -> Option { - self.websocket_url.as_ref().map(|url| FlashblocksConfig { - websocket_url: url.clone(), - max_pending_blocks_depth: self.max_pending_blocks_depth, +impl From for Option { + fn from(args: Args) -> Self { + args.websocket_url.map(|url| FlashblocksConfig { + websocket_url: url, + max_pending_blocks_depth: args.max_pending_blocks_depth, }) } +} - /// Build TracingConfig from CLI args. - pub const fn tracing_config(&self) -> TracingConfig { - TracingConfig { - enabled: self.enable_transaction_tracing, - logs_enabled: self.enable_transaction_tracing_logs, +impl From for TxpoolConfig { + fn from(args: Args) -> Self { + Self { + tracing_enabled: args.enable_transaction_tracing, + tracing_logs_enabled: args.enable_transaction_tracing_logs, + sequencer_rpc: args.rollup_args.sequencer, } } } diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index 21ab8b92..d84e25b1 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -30,15 +30,14 @@ fn main() { let flashblocks_cell: FlashblocksCell> = Arc::new(OnceCell::new()); - let sequencer_rpc = args.rollup_args.sequencer.clone(); - let tracing_config = args.tracing_config(); + let txpool_config = args.clone().into(); let metering_enabled = args.enable_metering; - let flashblocks_config = args.flashblocks_config(); + let flashblocks_config = args.clone().into(); let mut runner = BaseNodeRunner::new(args.rollup_args); // Feature extensions (FlashblocksExtension must be last - uses replace_configured) - runner.install_ext(Box::new(TxPoolExtension::new(tracing_config, sequencer_rpc))); + runner.install_ext(Box::new(TxPoolExtension::new(txpool_config))); runner.install_ext(Box::new(MeteringExtension::new(metering_enabled))); runner .install_ext(Box::new(FlashblocksExtension::new(flashblocks_cell, flashblocks_config))); diff --git a/crates/client/flashblocks/Cargo.toml b/crates/client/flashblocks/Cargo.toml index a8b15150..7a0604c4 100644 --- a/crates/client/flashblocks/Cargo.toml +++ b/crates/client/flashblocks/Cargo.toml @@ -14,8 +14,8 @@ workspace = true [features] test-utils = [ "base-client-node/test-utils", - "derive_more", - "eyre", + "dep:derive_more", + "dep:eyre", "reth-evm/test-utils", "reth-primitives/test-utils", ] diff --git a/crates/client/node/Cargo.toml b/crates/client/node/Cargo.toml index 90b60bc6..5ac4c765 100644 --- a/crates/client/node/Cargo.toml +++ b/crates/client/node/Cargo.toml @@ -13,34 +13,36 @@ workspace = true [features] test-utils = [ - "alloy-eips", - "alloy-genesis", - "alloy-primitives", - "alloy-provider", - "alloy-rpc-client", - "alloy-rpc-types", - "alloy-rpc-types-engine", - "alloy-signer", "base-primitives/test-utils", - "chrono", - "jsonrpsee", - "op-alloy-network", - "op-alloy-rpc-types-engine", + "dep:alloy-eips", + "dep:alloy-genesis", + "dep:alloy-primitives", + "dep:alloy-provider", + "dep:alloy-rpc-client", + "dep:alloy-rpc-types", + "dep:alloy-rpc-types-engine", + "dep:alloy-signer", + "dep:base-primitives", + "dep:chrono", + "dep:jsonrpsee", + "dep:op-alloy-network", + "dep:op-alloy-rpc-types-engine", + "dep:reth-ipc", + "dep:reth-node-core", + "dep:reth-optimism-primitives", + "dep:reth-optimism-rpc", + "dep:reth-primitives-traits", + "dep:reth-provider", + "dep:reth-rpc-layer", + "dep:reth-tracing", + "dep:tokio", + "dep:tower", + "dep:tracing-subscriber", + "dep:url", "reth-db/test-utils", - "reth-ipc", - "reth-node-core", "reth-optimism-node/test-utils", - "reth-optimism-rpc", - "reth-primitives-traits", "reth-primitives-traits?/test-utils", - "reth-provider", "reth-provider?/test-utils", - "reth-rpc-layer", - "reth-tracing", - "tokio", - "tower", - "tracing-subscriber", - "url", ] [dependencies] @@ -59,7 +61,7 @@ reth-provider = { workspace = true, optional = true } reth-node-core = { workspace = true, optional = true } reth-primitives-traits = { workspace = true, optional = true } reth-optimism-rpc = { workspace = true, features = ["client"], optional = true } -reth-optimism-primitives.workspace = true +reth-optimism-primitives = { workspace = true, optional = true } # alloy alloy-primitives = { workspace = true, optional = true } diff --git a/crates/client/node/README.md b/crates/client/node/README.md index 7b10150a..b8fa338e 100644 --- a/crates/client/node/README.md +++ b/crates/client/node/README.md @@ -1,4 +1,4 @@ -# `base-client-primitives` +# `base-client-node` CI MIT License @@ -14,7 +14,7 @@ Primitive types and traits for Base node runner extensions. Provides extension t Configuration types are located in their respective feature crates: - **`FlashblocksConfig`**: in `base-flashblocks` crate -- **`TracingConfig`**: in `base-txpool` crate +- **`TxpoolConfig`**: in `base-txpool` crate ## Usage @@ -22,7 +22,7 @@ Add the dependency to your `Cargo.toml`: ```toml [dependencies] -base-client-primitives = { git = "https://github.com/base/node-reth" } +base-client-node = { git = "https://github.com/base/node-reth" } ``` Implement a custom node extension: diff --git a/crates/client/node/src/lib.rs b/crates/client/node/src/lib.rs index 25f03b3d..607989cf 100644 --- a/crates/client/node/src/lib.rs +++ b/crates/client/node/src/lib.rs @@ -3,9 +3,6 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -// Silence false positive - reth_optimism_primitives is used in test_utils module -use reth_optimism_primitives as _; - mod extension; pub use extension::BaseNodeExtension; diff --git a/crates/client/node/src/test_utils/constants.rs b/crates/client/node/src/test_utils/constants.rs index 5572e4fc..7ceb6c7f 100644 --- a/crates/client/node/src/test_utils/constants.rs +++ b/crates/client/node/src/test_utils/constants.rs @@ -10,6 +10,11 @@ pub const BLOCK_TIME_SECONDS: u64 = 2; /// Gas limit for test blocks. pub const GAS_LIMIT: u64 = 200_000_000; +// Test Account Balances + +/// Balance in ETH for test accounts in fixtures. +pub const TEST_ACCOUNT_BALANCE_ETH: u64 = 100; + // Timing / Delays /// Delay in milliseconds to wait for node startup. @@ -20,8 +25,7 @@ pub const BLOCK_BUILD_DELAY_MS: u64 = 100; // Engine API /// All-zeros secret for local testing only. -pub const DEFAULT_JWT_SECRET: &str = - "0x0000000000000000000000000000000000000000000000000000000000000000"; +pub const DEFAULT_JWT_SECRET: B256 = B256::ZERO; // L1 Block Info (OP Stack) diff --git a/crates/client/node/src/test_utils/engine.rs b/crates/client/node/src/test_utils/engine.rs index 76532653..53ba6711 100644 --- a/crates/client/node/src/test_utils/engine.rs +++ b/crates/client/node/src/test_utils/engine.rs @@ -97,7 +97,7 @@ impl EngineApi { /// Build a new HTTP-backed Engine API client from the provided URL. pub fn new(engine_url: String) -> Result { let url: Url = engine_url.parse()?; - let jwt_secret: JwtSecret = DEFAULT_JWT_SECRET.parse()?; + let jwt_secret = JwtSecret::from_hex(DEFAULT_JWT_SECRET.to_string())?; Ok(Self { address: EngineAddress::Http(url), jwt_secret, _phantom: PhantomData }) } @@ -106,7 +106,7 @@ impl EngineApi { impl EngineApi { /// Build a new IPC-backed Engine API client using the IPC socket path. pub fn new(path: String) -> Result { - let jwt_secret: JwtSecret = DEFAULT_JWT_SECRET.parse()?; + let jwt_secret = JwtSecret::from_hex(DEFAULT_JWT_SECRET.to_string())?; Ok(Self { address: EngineAddress::Ipc(path), jwt_secret, _phantom: PhantomData }) } diff --git a/crates/client/node/src/test_utils/fixtures.rs b/crates/client/node/src/test_utils/fixtures.rs index 14d67ab3..eff8a258 100644 --- a/crates/client/node/src/test_utils/fixtures.rs +++ b/crates/client/node/src/test_utils/fixtures.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use alloy_genesis::GenesisAccount; -use alloy_primitives::U256; +use alloy_primitives::{U256, utils::Unit}; use base_primitives::{Account, build_test_genesis}; use reth::api::{NodeTypes, NodeTypesWithDBAdapter}; use reth_db::{ @@ -14,21 +14,23 @@ use reth_db::{ use reth_optimism_chainspec::OpChainSpec; use reth_provider::{ProviderFactory, providers::StaticFileProvider}; +use crate::test_utils::{GENESIS_GAS_LIMIT, TEST_ACCOUNT_BALANCE_ETH}; + /// Creates a test chain spec with pre-funded test accounts. pub fn load_chain_spec() -> Arc { + let test_account_balance: U256 = + Unit::ETHER.wei().saturating_mul(U256::from(TEST_ACCOUNT_BALANCE_ETH)); + let genesis = build_test_genesis() .extend_accounts( Account::all() .into_iter() .map(|a| { - ( - a.address(), - GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64)), - ) + (a.address(), GenesisAccount::default().with_balance(test_account_balance)) }) .collect::>(), ) - .with_gas_limit(100_000_000); + .with_gas_limit(GENESIS_GAS_LIMIT); Arc::new(OpChainSpec::from_genesis(genesis)) } diff --git a/crates/client/node/src/test_utils/mod.rs b/crates/client/node/src/test_utils/mod.rs index 7976c184..5aff3eb1 100644 --- a/crates/client/node/src/test_utils/mod.rs +++ b/crates/client/node/src/test_utils/mod.rs @@ -8,15 +8,16 @@ // Re-export from base-primitives for backwards compatibility pub use base_primitives::{ - AccessListContract, Account, ContractFactory, DEVNET_CHAIN_ID, DoubleCounter, Logic, Logic2, - Minimal7702Account, MockERC20, Proxy, SimpleStorage, TransparentUpgradeableProxy, - build_test_genesis, + AccessListContract, Account, ContractFactory, DEVNET_CHAIN_ID, DoubleCounter, + GENESIS_GAS_LIMIT, Logic, Logic2, Minimal7702Account, MockERC20, Proxy, SimpleStorage, + TransparentUpgradeableProxy, build_test_genesis, }; mod constants; pub use constants::{ BLOCK_BUILD_DELAY_MS, BLOCK_TIME_SECONDS, DEFAULT_JWT_SECRET, GAS_LIMIT, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, NODE_STARTUP_DELAY_MS, NamedChain, + TEST_ACCOUNT_BALANCE_ETH, }; mod engine; diff --git a/crates/client/txpool/README.md b/crates/client/txpool/README.md index 11ed50f5..2ae97087 100644 --- a/crates/client/txpool/README.md +++ b/crates/client/txpool/README.md @@ -27,10 +27,14 @@ cargo run -p node --release -- \ From code, wire the extension into the node builder: ```rust,ignore -use base_txpool::{TracingConfig, TxPoolExtension}; - -let tracing = TracingConfig { enabled: true, logs_enabled: true }; -let ext = TxPoolExtension::new(tracing, None); +use base_txpool::{TxpoolConfig, TxPoolExtension}; + +let config = TxpoolConfig { + tracing_enabled: true, + tracing_logs_enabled: true, + sequencer_rpc: None, +}; +let ext = TxPoolExtension::new(config); let builder = Box::new(ext).apply(builder); ``` diff --git a/crates/client/txpool/src/extension.rs b/crates/client/txpool/src/extension.rs index 92f366c2..d5e9a95b 100644 --- a/crates/client/txpool/src/extension.rs +++ b/crates/client/txpool/src/extension.rs @@ -6,43 +6,46 @@ use tracing::info; use crate::{TransactionStatusApiImpl, TransactionStatusApiServer, tracex_exex}; -/// Transaction tracing toggles. -#[derive(Debug, Clone, Copy)] -pub struct TracingConfig { +/// Transaction pool configuration. +#[derive(Debug, Clone)] +pub struct TxpoolConfig { /// Enables the transaction tracing ExEx. - pub enabled: bool, + pub tracing_enabled: bool, /// Emits `info`-level logs for the tracing ExEx when enabled. - pub logs_enabled: bool, + pub tracing_logs_enabled: bool, + /// Sequencer RPC endpoint for transaction status proxying. + pub sequencer_rpc: Option, } /// Helper struct that wires the transaction pool features into the node builder. #[derive(Debug, Clone)] pub struct TxPoolExtension { - /// Transaction tracing configuration flags. - pub tracing: TracingConfig, - /// Sequencer RPC endpoint for transaction status proxying. - pub sequencer_rpc: Option, + /// Transaction pool configuration. + config: TxpoolConfig, } impl TxPoolExtension { /// Creates a new transaction pool extension helper. - pub const fn new(tracing: TracingConfig, sequencer_rpc: Option) -> Self { - Self { tracing, sequencer_rpc } + #[allow(clippy::missing_const_for_fn)] + pub fn new(config: TxpoolConfig) -> Self { + Self { config } } } impl BaseNodeExtension for TxPoolExtension { /// Applies the extension to the supplied builder. fn apply(self: Box, builder: OpBuilder) -> OpBuilder { - let tracing = self.tracing; - let sequencer_rpc = self.sequencer_rpc; + let config = self.config; // Install the tracing ExEx if enabled - let builder = builder.install_exex_if(tracing.enabled, "tracex", move |ctx| async move { - Ok(tracex_exex(ctx, tracing.logs_enabled)) - }); + let logs_enabled = config.tracing_logs_enabled; + let builder = + builder.install_exex_if(config.tracing_enabled, "tracex", move |ctx| async move { + Ok(tracex_exex(ctx, logs_enabled)) + }); // Extend with RPC modules + let sequencer_rpc = config.sequencer_rpc; builder.extend_rpc_modules(move |ctx| { info!(message = "Starting Transaction Status RPC"); let proxy_api = TransactionStatusApiImpl::new(sequencer_rpc, ctx.pool().clone()) diff --git a/crates/client/txpool/src/lib.rs b/crates/client/txpool/src/lib.rs index 96f35667..3757caa4 100644 --- a/crates/client/txpool/src/lib.rs +++ b/crates/client/txpool/src/lib.rs @@ -18,4 +18,4 @@ mod tracker; pub use tracker::Tracker; mod extension; -pub use extension::{TracingConfig, TxPoolExtension}; +pub use extension::{TxPoolExtension, TxpoolConfig}; diff --git a/crates/shared/primitives/README.md b/crates/shared/primitives/README.md new file mode 100644 index 00000000..d6e37ce3 --- /dev/null +++ b/crates/shared/primitives/README.md @@ -0,0 +1,26 @@ +# `base-primitives` + +CI +MIT License + +Shared primitives and test utilities for node-reth crates. + +## Usage + +Add the dependency to your `Cargo.toml`: + +```toml +[dependencies] +base-primitives = { git = "https://github.com/base/node-reth" } +``` + +For test utilities: + +```toml +[dev-dependencies] +base-primitives = { git = "https://github.com/base/node-reth", features = ["test-utils"] } +``` + +## License + +Licensed under the [MIT License](https://github.com/base/node-reth/blob/main/LICENSE). diff --git a/crates/shared/primitives/src/lib.rs b/crates/shared/primitives/src/lib.rs index 3c53919f..273a68db 100644 --- a/crates/shared/primitives/src/lib.rs +++ b/crates/shared/primitives/src/lib.rs @@ -1,5 +1,6 @@ -//! Shared primitives and test utilities for node-reth crates. - +#![doc = include_str!("../README.md")] +#![doc(issue_tracker_base_url = "https://github.com/base/node-reth/issues/")] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #[cfg(any(test, feature = "test-utils"))] diff --git a/crates/shared/primitives/src/test_utils/genesis.rs b/crates/shared/primitives/src/test_utils/genesis.rs index cd4f5902..df558287 100644 --- a/crates/shared/primitives/src/test_utils/genesis.rs +++ b/crates/shared/primitives/src/test_utils/genesis.rs @@ -10,6 +10,9 @@ use super::Account; /// Chain ID for devnet test network. pub const DEVNET_CHAIN_ID: u64 = 84538453; +/// Gas limit for genesis block configuration. +pub const GENESIS_GAS_LIMIT: u64 = 100_000_000; + /// Builds a test genesis configuration programmatically. /// /// Creates a Base Sepolia-like genesis with: @@ -84,7 +87,7 @@ pub fn build_test_genesis() -> Genesis { Genesis { config, alloc, - gas_limit: 100_000_000, + gas_limit: GENESIS_GAS_LIMIT, difficulty: U256::ZERO, nonce: 0, timestamp: 0, diff --git a/crates/shared/primitives/src/test_utils/mod.rs b/crates/shared/primitives/src/test_utils/mod.rs index 8437cffd..8626c87e 100644 --- a/crates/shared/primitives/src/test_utils/mod.rs +++ b/crates/shared/primitives/src/test_utils/mod.rs @@ -4,7 +4,7 @@ mod accounts; pub use accounts::Account; mod genesis; -pub use genesis::{DEVNET_CHAIN_ID, build_test_genesis}; +pub use genesis::{DEVNET_CHAIN_ID, GENESIS_GAS_LIMIT, build_test_genesis}; mod contracts; pub use contracts::{ From b439aa8683023adbc5db30860319b38d27a4473c Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sun, 11 Jan 2026 16:15:38 -0600 Subject: [PATCH 18/19] chore: inline config extraction in extension constructors Move config extraction directly into extension constructor calls to reduce intermediate variables in main.rs. --- bin/node/src/main.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index d84e25b1..40ddc98f 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -30,17 +30,12 @@ fn main() { let flashblocks_cell: FlashblocksCell> = Arc::new(OnceCell::new()); - let txpool_config = args.clone().into(); - let metering_enabled = args.enable_metering; - let flashblocks_config = args.clone().into(); - - let mut runner = BaseNodeRunner::new(args.rollup_args); + let mut runner = BaseNodeRunner::new(args.rollup_args.clone()); // Feature extensions (FlashblocksExtension must be last - uses replace_configured) - runner.install_ext(Box::new(TxPoolExtension::new(txpool_config))); - runner.install_ext(Box::new(MeteringExtension::new(metering_enabled))); - runner - .install_ext(Box::new(FlashblocksExtension::new(flashblocks_cell, flashblocks_config))); + runner.install_ext(Box::new(TxPoolExtension::new(args.clone().into()))); + runner.install_ext(Box::new(MeteringExtension::new(args.enable_metering))); + runner.install_ext(Box::new(FlashblocksExtension::new(flashblocks_cell, args.into()))); let handle = runner.run(builder); handle.await From 7aa0f89c7aa09d56ebef2debb2848f6b78214c06 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sun, 11 Jan 2026 16:42:06 -0600 Subject: [PATCH 19/19] migrate back to generic install_ext --- Cargo.lock | 1 - bin/node/Cargo.toml | 1 - bin/node/src/main.rs | 16 +++++----------- crates/client/flashblocks/src/extension.rs | 14 +++++++++++--- crates/client/metering/src/extension.rs | 10 +++++++++- crates/client/metering/tests/meter.rs | 2 +- crates/client/metering/tests/meter_block.rs | 2 +- crates/client/metering/tests/meter_rpc.rs | 3 +-- crates/client/node/src/extension.rs | 13 ++++++++++++- crates/client/node/src/lib.rs | 2 +- crates/client/node/src/runner.rs | 6 +++--- crates/client/node/src/test_utils/harness.rs | 12 ++++++++++-- crates/client/txpool/src/extension.rs | 13 ++++++++++--- 13 files changed, 64 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae7bfb46..1b53991d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1733,7 +1733,6 @@ dependencies = [ "base-metering", "base-txpool", "clap", - "once_cell", "reth-cli-util", "reth-optimism-cli", "reth-optimism-node", diff --git a/bin/node/Cargo.toml b/bin/node/Cargo.toml index 08e3c1a9..43f2ce23 100644 --- a/bin/node/Cargo.toml +++ b/bin/node/Cargo.toml @@ -27,7 +27,6 @@ reth-cli-util.workspace = true # misc clap.workspace = true -once_cell.workspace = true [features] default = [] diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index 40ddc98f..10b591ee 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -5,13 +5,10 @@ pub mod cli; -use std::sync::Arc; - -use base_client_node::{BaseNodeRunner, OpProvider}; -use base_flashblocks::{FlashblocksCell, FlashblocksExtension, FlashblocksState}; +use base_client_node::BaseNodeRunner; +use base_flashblocks::FlashblocksExtension; use base_metering::MeteringExtension; use base_txpool::TxPoolExtension; -use once_cell::sync::OnceCell; #[global_allocator] static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator(); @@ -27,15 +24,12 @@ fn main() { // Step 3: Hand the parsed CLI to the node runner so it can build and launch the Base node. cli.run(|builder, args| async move { - let flashblocks_cell: FlashblocksCell> = - Arc::new(OnceCell::new()); - let mut runner = BaseNodeRunner::new(args.rollup_args.clone()); // Feature extensions (FlashblocksExtension must be last - uses replace_configured) - runner.install_ext(Box::new(TxPoolExtension::new(args.clone().into()))); - runner.install_ext(Box::new(MeteringExtension::new(args.enable_metering))); - runner.install_ext(Box::new(FlashblocksExtension::new(flashblocks_cell, args.into()))); + runner.install_ext::(args.clone().into()); + runner.install_ext::(args.enable_metering); + runner.install_ext::(args.into()); let handle = runner.run(builder); handle.await diff --git a/crates/client/flashblocks/src/extension.rs b/crates/client/flashblocks/src/extension.rs index ab72aaa7..a9f68d40 100644 --- a/crates/client/flashblocks/src/extension.rs +++ b/crates/client/flashblocks/src/extension.rs @@ -3,7 +3,7 @@ use std::sync::Arc; -use base_client_node::{BaseNodeExtension, OpBuilder, OpProvider}; +use base_client_node::{BaseNodeExtension, FromExtensionConfig, OpBuilder, OpProvider}; use futures_util::TryStreamExt; use once_cell::sync::OnceCell; use reth_exex::ExExEvent; @@ -31,9 +31,9 @@ pub struct FlashblocksConfig { #[derive(Debug, Clone)] pub struct FlashblocksExtension { /// Shared Flashblocks state cache. - pub cell: FlashblocksCell>, + cell: FlashblocksCell>, /// Optional Flashblocks configuration. - pub config: Option, + config: Option, } impl FlashblocksExtension { @@ -122,3 +122,11 @@ impl BaseNodeExtension for FlashblocksExtension { }) } } + +impl FromExtensionConfig for FlashblocksExtension { + type Config = Option; + + fn from_config(config: Self::Config) -> Self { + Self::new(Arc::new(OnceCell::new()), config) + } +} diff --git a/crates/client/metering/src/extension.rs b/crates/client/metering/src/extension.rs index 69bdc7cd..c089954c 100644 --- a/crates/client/metering/src/extension.rs +++ b/crates/client/metering/src/extension.rs @@ -1,7 +1,7 @@ //! Contains the [`MeteringExtension`] which wires up the metering RPC surface //! on the Base node builder. -use base_client_node::{BaseNodeExtension, OpBuilder}; +use base_client_node::{BaseNodeExtension, FromExtensionConfig, OpBuilder}; use tracing::info; use crate::{MeteringApiImpl, MeteringApiServer}; @@ -35,3 +35,11 @@ impl BaseNodeExtension for MeteringExtension { }) } } + +impl FromExtensionConfig for MeteringExtension { + type Config = bool; + + fn from_config(enabled: Self::Config) -> Self { + Self::new(enabled) + } +} diff --git a/crates/client/metering/tests/meter.rs b/crates/client/metering/tests/meter.rs index 8b541804..3ab38191 100644 --- a/crates/client/metering/tests/meter.rs +++ b/crates/client/metering/tests/meter.rs @@ -16,7 +16,7 @@ async fn setup() -> eyre::Result { let chain_spec = load_chain_spec(); TestHarness::builder() .with_chain_spec(chain_spec) - .with_extension(MeteringExtension::new(true)) + .with_ext::(true) .build() .await } diff --git a/crates/client/metering/tests/meter_block.rs b/crates/client/metering/tests/meter_block.rs index c8078b60..704e3b77 100644 --- a/crates/client/metering/tests/meter_block.rs +++ b/crates/client/metering/tests/meter_block.rs @@ -14,7 +14,7 @@ async fn setup() -> eyre::Result { let chain_spec = load_chain_spec(); TestHarness::builder() .with_chain_spec(chain_spec) - .with_extension(MeteringExtension::new(true)) + .with_ext::(true) .build() .await } diff --git a/crates/client/metering/tests/meter_rpc.rs b/crates/client/metering/tests/meter_rpc.rs index db619d30..3fc528eb 100644 --- a/crates/client/metering/tests/meter_rpc.rs +++ b/crates/client/metering/tests/meter_rpc.rs @@ -27,8 +27,7 @@ fn create_bundle(txs: Vec, block_number: u64, min_timestamp: Option) /// Set up a test harness with the metering extension and return an RPC client. async fn setup() -> eyre::Result<(TestHarness, RpcClient)> { - let harness = - TestHarness::builder().with_extension(MeteringExtension::new(true)).build().await?; + let harness = TestHarness::builder().with_ext::(true).build().await?; let client = harness.rpc_client()?; Ok((harness, client)) diff --git a/crates/client/node/src/extension.rs b/crates/client/node/src/extension.rs index 5e8e809e..beec0801 100644 --- a/crates/client/node/src/extension.rs +++ b/crates/client/node/src/extension.rs @@ -4,8 +4,19 @@ use std::fmt::Debug; use crate::OpBuilder; -/// A node builder extension that can apply additional wiring to the builder. +/// Customizes the node builder before launch. +/// +/// Register extensions via [`BaseNodeRunner::install_ext`]. pub trait BaseNodeExtension: Send + Sync + Debug { /// Applies the extension to the supplied builder. fn apply(self: Box, builder: OpBuilder) -> OpBuilder; } + +/// An extension that can be built from a config. +pub trait FromExtensionConfig: BaseNodeExtension + Sized { + /// Configuration type used to construct this extension. + type Config; + + /// Creates a new extension from the provided configuration. + fn from_config(config: Self::Config) -> Self; +} diff --git a/crates/client/node/src/lib.rs b/crates/client/node/src/lib.rs index 607989cf..eeeb27f4 100644 --- a/crates/client/node/src/lib.rs +++ b/crates/client/node/src/lib.rs @@ -4,7 +4,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] mod extension; -pub use extension::BaseNodeExtension; +pub use extension::{BaseNodeExtension, FromExtensionConfig}; mod handle; pub use handle::BaseNodeHandle; diff --git a/crates/client/node/src/runner.rs b/crates/client/node/src/runner.rs index 7d712d82..dcca0009 100644 --- a/crates/client/node/src/runner.rs +++ b/crates/client/node/src/runner.rs @@ -8,7 +8,7 @@ use reth::{ use reth_optimism_node::{OpNode, args::RollupArgs}; use tracing::info; -use crate::{BaseNodeBuilder, BaseNodeExtension, BaseNodeHandle}; +use crate::{BaseNodeBuilder, BaseNodeExtension, BaseNodeHandle, FromExtensionConfig}; /// Wraps the Base node configuration and orchestrates builder wiring. #[derive(Debug)] @@ -26,8 +26,8 @@ impl BaseNodeRunner { } /// Registers a new builder extension. - pub fn install_ext(&mut self, extension: Box) { - self.extensions.push(extension); + pub fn install_ext(&mut self, config: T::Config) { + self.extensions.push(Box::new(T::from_config(config))); } /// Applies all Base-specific wiring to the supplied builder, launches the node, and returns a diff --git a/crates/client/node/src/test_utils/harness.rs b/crates/client/node/src/test_utils/harness.rs index 16aed723..76ddc3f3 100644 --- a/crates/client/node/src/test_utils/harness.rs +++ b/crates/client/node/src/test_utils/harness.rs @@ -18,7 +18,7 @@ use reth_primitives_traits::{Block as BlockT, RecoveredBlock}; use tokio::time::sleep; use crate::{ - BaseNodeExtension, + BaseNodeExtension, FromExtensionConfig, test_utils::{ BLOCK_BUILD_DELAY_MS, BLOCK_TIME_SECONDS, GAS_LIMIT, L1_BLOCK_INFO_DEPOSIT_TX, NODE_STARTUP_DELAY_MS, @@ -41,7 +41,15 @@ impl TestHarnessBuilder { Self::default() } - /// Add an extension to be applied during node launch. + /// Add an extension to be applied during node launch using its config type. + pub fn with_ext(mut self, config: T::Config) -> Self { + self.extensions.push(Box::new(T::from_config(config))); + self + } + + /// Add a pre-constructed extension to be applied during node launch. + /// + /// Prefer [`with_ext`](Self::with_ext) for simpler configuration. pub fn with_extension(mut self, ext: impl BaseNodeExtension + 'static) -> Self { self.extensions.push(Box::new(ext)); self diff --git a/crates/client/txpool/src/extension.rs b/crates/client/txpool/src/extension.rs index d5e9a95b..3800db71 100644 --- a/crates/client/txpool/src/extension.rs +++ b/crates/client/txpool/src/extension.rs @@ -1,7 +1,7 @@ //! Contains the [`TxPoolExtension`] which wires up the transaction pool features //! (tracing ExEx and status RPC) on the Base node builder. -use base_client_node::{BaseNodeExtension, OpBuilder}; +use base_client_node::{BaseNodeExtension, FromExtensionConfig, OpBuilder}; use tracing::info; use crate::{TransactionStatusApiImpl, TransactionStatusApiServer, tracex_exex}; @@ -26,8 +26,7 @@ pub struct TxPoolExtension { impl TxPoolExtension { /// Creates a new transaction pool extension helper. - #[allow(clippy::missing_const_for_fn)] - pub fn new(config: TxpoolConfig) -> Self { + pub const fn new(config: TxpoolConfig) -> Self { Self { config } } } @@ -55,3 +54,11 @@ impl BaseNodeExtension for TxPoolExtension { }) } } + +impl FromExtensionConfig for TxPoolExtension { + type Config = TxpoolConfig; + + fn from_config(config: Self::Config) -> Self { + Self::new(config) + } +}