diff --git a/CHANGELOG.sf.md b/CHANGELOG.sf.md index 39b7019429..969f06275d 100644 --- a/CHANGELOG.sf.md +++ b/CHANGELOG.sf.md @@ -5,7 +5,28 @@ * Added Firehose tracing-regression coverage for Base transactions: a prestate-driven test (block invariants + JSON projection golden) plus an end-to-end `base-system-tests` integration test tracing a B-20 precompile transfer. The chain-agnostic capture / invariants / projection / golden - framework lives in the shared `firehose-tracer-test` crate (`evm-firehose-tracer-rs` `5.3.0`). + framework lives in the shared `firehose-tracer-test` crate (`evm-firehose-tracer-rs` `5.4.1`). + +* Added `base-firehose-prestate` (`etc/tools/firehose-prestate`), a thin Base wrapper around + `firehose-tracer-prestate` `5.4.1`, the chain-agnostic Rust port of + `streamingfast/go-ethereum`'s `generate-prestate`. `generate` turns any mined Base mainnet or + Sepolia transaction into a self-contained `prestate.json` that `run_prestate` replays through the + tracer with no node and no Docker. Base supplies the chain-specific seam: the `BaseTxEnvelope` + type, the genesis config re-projected from `ChainConfig` (the chain spec's own genesis drops + every timestamped fork), the `L1Block` predeploy slots the OP L1-cost function reads outside the + EVM journal, and the chain id. Without those L1 slots a replay computes a zero L1 fee. + +* Added a `base_mainnet_replay` prestate case replaying Base mainnet transaction + `0x32ecdb4e72df6ec331edb81256b58a768ba49d1e3e89a1a071b980a85d6b72c0` (block `49663794`, index + `159`). Its golden was seeded from StreamingFast's production Firehose by the tool's `reference` + subcommand (`sf.firehose.v2.Fetch/Block`), so the first run of the test compared the replay + directly against production — and matched. That validation is a one-off proving the generator and + the tracer agree with production; the golden is an ordinary `GOLDEN_UPDATE=1` one from then on, + and further cases only need `generate`. Both sides share the + `ProductionReplay` projection (`firehose-tracer-test`, re-exported from `base-firehose-tests`), + which excludes only block-wide positional fields (ordinals, transaction index, log block index, + cumulative gas used) and keeps gas accounting, the call tree, logs and absolute balance, nonce + and storage values verbatim. ### Changed diff --git a/Cargo.lock b/Cargo.lock index 11caab5802..709bd70a9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4903,6 +4903,24 @@ dependencies = [ "url", ] +[[package]] +name = "base-firehose-prestate" +version = "1.2.0" +dependencies = [ + "alloy-genesis 2.0.5", + "base-common-chains", + "base-common-consensus", + "base-common-evm", + "base-common-rpc-types", + "base-execution-chainspec", + "clap", + "eyre", + "firehose-tracer-prestate", + "serde", + "serde_json", + "tokio", +] + [[package]] name = "base-firehose-tests" version = "1.2.0" @@ -6946,7 +6964,7 @@ dependencies = [ "bitflags 2.13.0", "cexpr", "clang-sys", - "itertools 0.13.0", + "itertools 0.12.1", "log", "prettyplease", "proc-macro2", @@ -6966,7 +6984,7 @@ dependencies = [ "bitflags 2.13.0", "cexpr", "clang-sys", - "itertools 0.13.0", + "itertools 0.12.1", "proc-macro2", "quote", "regex", @@ -9817,9 +9835,9 @@ checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" [[package]] name = "firehose-tracer" -version = "5.3.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f116192c9a01744168c80024324eb8d25fa94917f644a84236da685041eb8b" +checksum = "68617cc0fb3d0dfb49cbe29f3e05969fcabf93fcecaa1e0fda577c7c6c95494c" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -9838,11 +9856,36 @@ dependencies = [ "tracing", ] +[[package]] +name = "firehose-tracer-prestate" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16514d9d18486c39ec541467d6c2d792d57d74a363b44442dc5ed850632c8fd1" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-genesis 2.0.5", + "alloy-primitives", + "alloy-rpc-client 2.0.5", + "alloy-transport-http 2.0.5", + "clap", + "eyre", + "firehose-tracer", + "firehose-tracer-test", + "prost 0.14.4", + "reqwest 0.13.4", + "serde", + "serde_json", + "tokio", + "tonic 0.14.6", + "tonic-prost", +] + [[package]] name = "firehose-tracer-test" -version = "5.3.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23840e080c438da1ff6feb80b7bfcc6474f330c2e49579651c8d5bb7de0be325" +checksum = "875f72c331b8b8bb3211030a9a44850c72439a7037a07e65f56c02d50f64ca69" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -11224,7 +11267,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core 0.61.2", ] [[package]] @@ -15608,7 +15651,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ "heck", - "itertools 0.14.0", + "itertools 0.12.1", "log", "multimap", "once_cell", @@ -15628,7 +15671,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" dependencies = [ "heck", - "itertools 0.14.0", + "itertools 0.12.1", "log", "multimap", "petgraph 0.8.3", @@ -15662,7 +15705,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.117", @@ -15675,7 +15718,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.117", @@ -23651,9 +23694,11 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", + "rustls-native-certs", "socket2 0.6.4", "sync_wrapper 1.0.2", "tokio", + "tokio-rustls 0.26.4", "tokio-stream", "tower 0.5.3", "tower-layer", diff --git a/Cargo.toml b/Cargo.toml index 847df8ee7f..5962f4fd13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -650,10 +650,13 @@ ark-bls12-381 = { version = "0.5.0", default-features = false } # Firehose tracer (defined by the streamingfast reth fork, firehose/2.x branch). reth-firehose = { git = "https://github.com/streamingfast/reth.git", tag = "v2.3.0-fh-7" } reth-firehose-tests = { git = "https://github.com/streamingfast/reth.git", tag = "v2.3.0-fh-7" } -firehose-tracer = "5.3.0" +firehose-tracer = "5.4.1" # Shared tracing-regression framework (capture / invariants / projection / golden). -firehose-tracer-test = "5.3.0" +firehose-tracer-test = "5.4.1" + +# Chain-agnostic generator for replayable Firehose prestate fixtures. +firehose-tracer-prestate = "5.4.1" [patch.crates-io] # SF fork of alloy-evm that routes system calls (EIP-4788, EIP-2935, etc.) through the diff --git a/clippy.toml b/clippy.toml index 0e14e821bd..037929e87d 100644 --- a/clippy.toml +++ b/clippy.toml @@ -24,4 +24,5 @@ doc-valid-idents = [ "MEV", "OP", "Optimism", + "StreamingFast", ] diff --git a/crates/execution/firehose-tests/README.md b/crates/execution/firehose-tests/README.md index dd8cfa4959..b9c4246f41 100644 --- a/crates/execution/firehose-tests/README.md +++ b/crates/execution/firehose-tests/README.md @@ -18,3 +18,21 @@ Assert on the captured `Block` with: `VolatilePolicy` (`none()` for reproducible prestate replays, `live_node()` for system tests). Regenerate every golden with `GOLDEN_UPDATE=1`. + +## Cases replaying a real transaction + +`tests/cases/base_mainnet_replay/` replays a real Base mainnet transaction. Its `prestate.json` +comes from `base-firehose-prestate generate` (`etc/tools/firehose-prestate`) against an archive +node, and its golden was *seeded* by `base-firehose-prestate reference` from StreamingFast's +production Firehose. The first run of the test was therefore a direct comparison against +production — which is the whole point of seeding it that way, and it matched. + +That validation is deliberately a one-off. It established that the generator and the tracer agree +with production; from then on the golden is an ordinary one, regenerated with `GOLDEN_UPDATE=1` +like any other, and no test needs credentials or a production endpoint. Further cases only need +`generate`. + +`ProductionReplay` is the projection both sides share; it lives in `firehose-tracer-test` and is +re-exported here. The replay runs the transaction alone in a synthetic single-transaction block, so +it excludes exactly the block-wide positional fields — ordinals, the transaction's index, each +log's block index, and the cumulative gas used — and keeps everything else verbatim. diff --git a/crates/execution/firehose-tests/src/lib.rs b/crates/execution/firehose-tests/src/lib.rs index 7def4a0a6e..6614099bf7 100644 --- a/crates/execution/firehose-tests/src/lib.rs +++ b/crates/execution/firehose-tests/src/lib.rs @@ -1,13 +1,14 @@ #![doc = include_str!("../README.md")] -mod prestate; -pub use prestate::run_prestate; - -mod capture; -pub use capture::BaseFirehoseCapture; // The tracing-regression framework is chain-agnostic and lives in `firehose-tracer-test`. pub use firehose_tracer_test::{ BlockDiff, BlockInvariants, BlockProjection, FirehoseCapture, Golden, InvariantConfig, - SymbolTable, Violation, VolatilePolicy, + ProductionReplay, SymbolTable, Violation, VolatilePolicy, }; pub use reth_firehose_tests::RunOutcome; + +mod prestate; +pub use prestate::run_prestate; + +mod capture; +pub use capture::BaseFirehoseCapture; diff --git a/crates/execution/firehose-tests/tests/cases/base_mainnet_replay/block.49663794.json b/crates/execution/firehose-tests/tests/cases/base_mainnet_replay/block.49663794.json new file mode 100644 index 0000000000..0e64219e9f --- /dev/null +++ b/crates/execution/firehose-tests/tests/cases/base_mainnet_replay/block.49663794.json @@ -0,0 +1,74 @@ +{ + "calls": [ + { + "address": "0xec0e36a6060339694c618ffffcc9ec7da21cb0cc", + "balance_changes": [ + { + "address": "0xabbac9becc5b171842ae47703dfa6640b23c9710", + "new_value": "0x054f62c0f2f516dc", + "old_value": "0x054f634d28dc754b", + "reason": "REASON_GAS_BUY" + }, + { + "address": "0xabbac9becc5b171842ae47703dfa6640b23c9710", + "new_value": "0x054f632dc29791dc", + "old_value": "0x054f62c0f2f516dc", + "reason": "REASON_GAS_REFUND" + }, + { + "address": "0x4200000000000000000000000000000000000019", + "new_value": "0x01e52f91908dcdaac0", + "old_value": "0x01e52f9171aaa6b5c0", + "reason": "REASON_REWARD_TRANSACTION_FEE" + }, + { + "address": "0x420000000000000000000000000000000000001a", + "new_value": "0x16d824463b189260", + "old_value": "0x16d82445b7faa3f1", + "reason": "REASON_REWARD_TRANSACTION_FEE" + } + ], + "call_type": "CALL", + "caller": "0xabbac9becc5b171842ae47703dfa6640b23c9710", + "executed_code": true, + "gas_consumed": 5280, + "gas_limit": 98748, + "index": 1, + "input": "0xd67704ad000000000000000000000000000000000000000000000000000000036948f05a", + "keccak_preimages": { + "String(\"647538f573622c2dc5ad534d3157795075362ce71b9944e2a4e56b07eac973fc\")": "000000000000000000000000abbac9becc5b171842ae47703dfa6640b23c97100000000000000000000000000000000000000000000000000000000000000000" + }, + "nonce_changes": [ + { + "address": "0xabbac9becc5b171842ae47703dfa6640b23c9710", + "new_value": 6989526, + "old_value": 6989525 + } + ], + "storage_changes": [ + { + "address": "0xec0e36a6060339694c618ffffcc9ec7da21cb0cc", + "key": "0x647538f573622c2dc5ad534d3157795075362ce71b9944e2a4e56b07eac973fc", + "new_value": "0x000000000000000000000000000000000000000000000000000000036948f05a", + "old_value": "0x0000000000000000000000000000000000000000000000000000000367c0ba09" + } + ] + } + ], + "from": "0xabbac9becc5b171842ae47703dfa6640b23c9710", + "gas_limit": 120000, + "gas_price": "0x4c4b40", + "gas_used": 26532, + "hash": "0x32ecdb4e72df6ec331edb81256b58a768ba49d1e3e89a1a071b980a85d6b72c0", + "input": "0xd67704ad000000000000000000000000000000000000000000000000000000036948f05a", + "max_fee_per_gas": "0x1dcf2781", + "nonce": 6989525, + "r": "0x02f41d76d4d32a48ed9dc1cf0ea08e40ac40fabb2e5ec3be3d6906a296a7fc66", + "receipt": { + "logs_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + "s": "0x42f0f6ce0005056cea2f280e60d032efa10afa2dfb9928215a971d5269771812", + "status": "SUCCEEDED", + "to": "0xec0e36a6060339694c618ffffcc9ec7da21cb0cc", + "type": "TRX_TYPE_DYNAMIC_FEE" +} diff --git a/crates/execution/firehose-tests/tests/cases/base_mainnet_replay/prestate.json b/crates/execution/firehose-tests/tests/cases/base_mainnet_replay/prestate.json new file mode 100644 index 0000000000..f4f69acc3c --- /dev/null +++ b/crates/execution/firehose-tests/tests/cases/base_mainnet_replay/prestate.json @@ -0,0 +1,108 @@ +{ + "context": { + "baseFeePerGas": "5000000", + "difficulty": "0", + "gasLimit": "400000000", + "miner": "0x4200000000000000000000000000000000000011", + "number": "49663794", + "timestamp": "1786116935" + }, + "genesis": { + "alloc": { + "0x4200000000000000000000000000000000000011": { + "balance": "0x6b3b59fab8c03baae", + "code": "0x60806040526004361061005e5760003560e01c80635c60da1b116100435780635c60da1b146100be5780638f283970146100f8578063f851a440146101185761006d565b80633659cfe6146100755780634f1ef286146100955761006d565b3661006d5761006b61012d565b005b61006b61012d565b34801561008157600080fd5b5061006b6100903660046106d9565b610224565b6100a86100a33660046106f4565b610296565b6040516100b59190610777565b60405180910390f35b3480156100ca57600080fd5b506100d3610419565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100b5565b34801561010457600080fd5b5061006b6101133660046106d9565b6104b0565b34801561012457600080fd5b506100d3610517565b60006101577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b905073ffffffffffffffffffffffffffffffffffffffff8116610201576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f78793a20696d706c656d656e746174696f6e206e6f7420696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b3660008037600080366000845af43d6000803e8061021e573d6000fd5b503d6000f35b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061027d575033155b1561028e5761028b816105a3565b50565b61028b61012d565b60606102c07fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102f7575033155b1561040a57610305846105a3565b6000808573ffffffffffffffffffffffffffffffffffffffff16858560405161032f9291906107ea565b600060405180830381855af49150503d806000811461036a576040519150601f19603f3d011682016040523d82523d6000602084013e61036f565b606091505b509150915081610401576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f50726f78793a2064656c656761746563616c6c20746f206e657720696d706c6560448201527f6d656e746174696f6e20636f6e7472616374206661696c65640000000000000060648201526084016101f8565b91506104129050565b61041261012d565b9392505050565b60006104437fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061047a575033155b156104a557507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b6104ad61012d565b90565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610509575033155b1561028e5761028b8161060b565b60006105417fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610578575033155b156104a557507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc81905560405173ffffffffffffffffffffffffffffffffffffffff8216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b60006106357fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61038390556040805173ffffffffffffffffffffffffffffffffffffffff8084168252851660208201529192507f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f910160405180910390a15050565b803573ffffffffffffffffffffffffffffffffffffffff811681146106d457600080fd5b919050565b6000602082840312156106eb57600080fd5b610412826106b0565b60008060006040848603121561070957600080fd5b610712846106b0565b9250602084013567ffffffffffffffff8082111561072f57600080fd5b818601915086601f83011261074357600080fd5b81358181111561075257600080fd5b87602082850101111561076457600080fd5b6020830194508093505050509250925092565b600060208083528351808285015260005b818110156107a457858101830151858201604001528201610788565b818111156107b6576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b818382376000910190815291905056fea164736f6c634300080f000a" + }, + "0x4200000000000000000000000000000000000015": { + "balance": "0x0", + "code": "0x60806040526004361061005e5760003560e01c80635c60da1b116100435780635c60da1b146100be5780638f283970146100f8578063f851a440146101185761006d565b80633659cfe6146100755780634f1ef286146100955761006d565b3661006d5761006b61012d565b005b61006b61012d565b34801561008157600080fd5b5061006b6100903660046106d9565b610224565b6100a86100a33660046106f4565b610296565b6040516100b59190610777565b60405180910390f35b3480156100ca57600080fd5b506100d3610419565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100b5565b34801561010457600080fd5b5061006b6101133660046106d9565b6104b0565b34801561012457600080fd5b506100d3610517565b60006101577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b905073ffffffffffffffffffffffffffffffffffffffff8116610201576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f78793a20696d706c656d656e746174696f6e206e6f7420696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b3660008037600080366000845af43d6000803e8061021e573d6000fd5b503d6000f35b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061027d575033155b1561028e5761028b816105a3565b50565b61028b61012d565b60606102c07fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102f7575033155b1561040a57610305846105a3565b6000808573ffffffffffffffffffffffffffffffffffffffff16858560405161032f9291906107ea565b600060405180830381855af49150503d806000811461036a576040519150601f19603f3d011682016040523d82523d6000602084013e61036f565b606091505b509150915081610401576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f50726f78793a2064656c656761746563616c6c20746f206e657720696d706c6560448201527f6d656e746174696f6e20636f6e7472616374206661696c65640000000000000060648201526084016101f8565b91506104129050565b61041261012d565b9392505050565b60006104437fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061047a575033155b156104a557507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b6104ad61012d565b90565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610509575033155b1561028e5761028b8161060b565b60006105417fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610578575033155b156104a557507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc81905560405173ffffffffffffffffffffffffffffffffffffffff8216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b60006106357fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61038390556040805173ffffffffffffffffffffffffffffffffffffffff8084168252851660208201529192507f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f910160405180910390a15050565b803573ffffffffffffffffffffffffffffffffffffffff811681146106d457600080fd5b919050565b6000602082840312156106eb57600080fd5b610412826106b0565b60008060006040848603121561070957600080fd5b610712846106b0565b9250602084013567ffffffffffffffff8082111561072f57600080fd5b818601915086601f83011261074357600080fd5b81358181111561075257600080fd5b87602082850101111561076457600080fd5b6020830194508093505050509250925092565b600060208083528351808285015260005b818110156107a457858101830151858201604001528201610788565b818111156107b6576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b818382376000910190815291905056fea164736f6c634300080f000a", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x000000000000000000000000000000000000000000000000000000000d0edac4", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x00000000000000000000000000000000000008dd00101c120000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000000000000000000000000000000000000000000bc", + "0x0000000000000000000000000000000000000000000000000000000000000006": "0x00000000000000000000000000000000000000000000000000000000000a6fe0", + "0x0000000000000000000000000000000000000000000000000000000000000007": "0x0000000000000000000000000000000000000000000000000000000000cafb4e", + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x0000000000000000000000000000000000000094000000000000000000000000" + } + }, + "0x4200000000000000000000000000000000000019": { + "balance": "0x1e52f9171aaa6b5c0", + "code": "0x60806040526004361061005e5760003560e01c80635c60da1b116100435780635c60da1b146100be5780638f283970146100f8578063f851a440146101185761006d565b80633659cfe6146100755780634f1ef286146100955761006d565b3661006d5761006b61012d565b005b61006b61012d565b34801561008157600080fd5b5061006b6100903660046106d9565b610224565b6100a86100a33660046106f4565b610296565b6040516100b59190610777565b60405180910390f35b3480156100ca57600080fd5b506100d3610419565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100b5565b34801561010457600080fd5b5061006b6101133660046106d9565b6104b0565b34801561012457600080fd5b506100d3610517565b60006101577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b905073ffffffffffffffffffffffffffffffffffffffff8116610201576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f78793a20696d706c656d656e746174696f6e206e6f7420696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b3660008037600080366000845af43d6000803e8061021e573d6000fd5b503d6000f35b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061027d575033155b1561028e5761028b816105a3565b50565b61028b61012d565b60606102c07fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102f7575033155b1561040a57610305846105a3565b6000808573ffffffffffffffffffffffffffffffffffffffff16858560405161032f9291906107ea565b600060405180830381855af49150503d806000811461036a576040519150601f19603f3d011682016040523d82523d6000602084013e61036f565b606091505b509150915081610401576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f50726f78793a2064656c656761746563616c6c20746f206e657720696d706c6560448201527f6d656e746174696f6e20636f6e7472616374206661696c65640000000000000060648201526084016101f8565b91506104129050565b61041261012d565b9392505050565b60006104437fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061047a575033155b156104a557507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b6104ad61012d565b90565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610509575033155b1561028e5761028b8161060b565b60006105417fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610578575033155b156104a557507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc81905560405173ffffffffffffffffffffffffffffffffffffffff8216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b60006106357fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61038390556040805173ffffffffffffffffffffffffffffffffffffffff8084168252851660208201529192507f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f910160405180910390a15050565b803573ffffffffffffffffffffffffffffffffffffffff811681146106d457600080fd5b919050565b6000602082840312156106eb57600080fd5b610412826106b0565b60008060006040848603121561070957600080fd5b610712846106b0565b9250602084013567ffffffffffffffff8082111561072f57600080fd5b818601915086601f83011261074357600080fd5b81358181111561075257600080fd5b87602082850101111561076457600080fd5b6020830194508093505050509250925092565b600060208083528351808285015260005b818110156107a457858101830151858201604001528201610788565b818111156107b6576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b818382376000910190815291905056fea164736f6c634300080f000a" + }, + "0x420000000000000000000000000000000000001a": { + "balance": "0x16d82445b7faa3f1", + "code": "0x60806040526004361061005e5760003560e01c80635c60da1b116100435780635c60da1b146100be5780638f283970146100f8578063f851a440146101185761006d565b80633659cfe6146100755780634f1ef286146100955761006d565b3661006d5761006b61012d565b005b61006b61012d565b34801561008157600080fd5b5061006b6100903660046106d9565b610224565b6100a86100a33660046106f4565b610296565b6040516100b59190610777565b60405180910390f35b3480156100ca57600080fd5b506100d3610419565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100b5565b34801561010457600080fd5b5061006b6101133660046106d9565b6104b0565b34801561012457600080fd5b506100d3610517565b60006101577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b905073ffffffffffffffffffffffffffffffffffffffff8116610201576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f78793a20696d706c656d656e746174696f6e206e6f7420696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b3660008037600080366000845af43d6000803e8061021e573d6000fd5b503d6000f35b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061027d575033155b1561028e5761028b816105a3565b50565b61028b61012d565b60606102c07fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102f7575033155b1561040a57610305846105a3565b6000808573ffffffffffffffffffffffffffffffffffffffff16858560405161032f9291906107ea565b600060405180830381855af49150503d806000811461036a576040519150601f19603f3d011682016040523d82523d6000602084013e61036f565b606091505b509150915081610401576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f50726f78793a2064656c656761746563616c6c20746f206e657720696d706c6560448201527f6d656e746174696f6e20636f6e7472616374206661696c65640000000000000060648201526084016101f8565b91506104129050565b61041261012d565b9392505050565b60006104437fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061047a575033155b156104a557507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b6104ad61012d565b90565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610509575033155b1561028e5761028b8161060b565b60006105417fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610578575033155b156104a557507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc81905560405173ffffffffffffffffffffffffffffffffffffffff8216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b60006106357fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61038390556040805173ffffffffffffffffffffffffffffffffffffffff8084168252851660208201529192507f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f910160405180910390a15050565b803573ffffffffffffffffffffffffffffffffffffffff811681146106d457600080fd5b919050565b6000602082840312156106eb57600080fd5b610412826106b0565b60008060006040848603121561070957600080fd5b610712846106b0565b9250602084013567ffffffffffffffff8082111561072f57600080fd5b818601915086601f83011261074357600080fd5b81358181111561075257600080fd5b87602082850101111561076457600080fd5b6020830194508093505050509250925092565b600060208083528351808285015260005b818110156107a457858101830151858201604001528201610788565b818111156107b6576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b818382376000910190815291905056fea164736f6c634300080f000a" + }, + "0x420000000000000000000000000000000000001b": { + "balance": "0x0", + "code": "0x60806040526004361061005e5760003560e01c80635c60da1b116100435780635c60da1b146100be5780638f283970146100f8578063f851a440146101185761006d565b80633659cfe6146100755780634f1ef286146100955761006d565b3661006d5761006b61012d565b005b61006b61012d565b34801561008157600080fd5b5061006b6100903660046106d9565b610224565b6100a86100a33660046106f4565b610296565b6040516100b59190610777565b60405180910390f35b3480156100ca57600080fd5b506100d3610419565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100b5565b34801561010457600080fd5b5061006b6101133660046106d9565b6104b0565b34801561012457600080fd5b506100d3610517565b60006101577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b905073ffffffffffffffffffffffffffffffffffffffff8116610201576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f78793a20696d706c656d656e746174696f6e206e6f7420696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b3660008037600080366000845af43d6000803e8061021e573d6000fd5b503d6000f35b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061027d575033155b1561028e5761028b816105a3565b50565b61028b61012d565b60606102c07fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102f7575033155b1561040a57610305846105a3565b6000808573ffffffffffffffffffffffffffffffffffffffff16858560405161032f9291906107ea565b600060405180830381855af49150503d806000811461036a576040519150601f19603f3d011682016040523d82523d6000602084013e61036f565b606091505b509150915081610401576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f50726f78793a2064656c656761746563616c6c20746f206e657720696d706c6560448201527f6d656e746174696f6e20636f6e7472616374206661696c65640000000000000060648201526084016101f8565b91506104129050565b61041261012d565b9392505050565b60006104437fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061047a575033155b156104a557507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b6104ad61012d565b90565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610509575033155b1561028e5761028b8161060b565b60006105417fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610578575033155b156104a557507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc81905560405173ffffffffffffffffffffffffffffffffffffffff8216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b60006106357fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61038390556040805173ffffffffffffffffffffffffffffffffffffffff8084168252851660208201529192507f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f910160405180910390a15050565b803573ffffffffffffffffffffffffffffffffffffffff811681146106d457600080fd5b919050565b6000602082840312156106eb57600080fd5b610412826106b0565b60008060006040848603121561070957600080fd5b610712846106b0565b9250602084013567ffffffffffffffff8082111561072f57600080fd5b818601915086601f83011261074357600080fd5b81358181111561075257600080fd5b87602082850101111561076457600080fd5b6020830194508093505050509250925092565b600060208083528351808285015260005b818110156107a457858101830151858201604001528201610788565b818111156107b6576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b818382376000910190815291905056fea164736f6c634300080f000a" + }, + "0xabbac9becc5b171842ae47703dfa6640b23c9710": { + "balance": "0x54f634d28dc754b", + "nonce": "0x6aa6d5" + }, + "0xec0e36a6060339694c618ffffcc9ec7da21cb0cc": { + "balance": "0x0", + "code": "0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c8063054c1a7514610043578063d67704ad1461005d578063f037e4e31461007e575b5f5ffd5b61004b61009d565b60405190815260200160405180910390f35b61007c61006b3660046102f1565b335f90815260208190526040902055565b005b61004b61008c366004610308565b5f6020819052908152604090205481565b5f7fac7832e2a6e3dcea422bb986d8068206f4fcdecafd6a376a9614b8fc3f23202a7f7ea2008aaa88a92bac50badc40e963d2ec8b2155cdfe23b734117cc9275aa9397f42277aa406c44a6f20c76f91d2c074fcdb5e5fd5e90245e6d059ec7846f490ae7f6d2480575f7bda3cb468dad3389aad90c27503cbeef0488ae18862cfc4aba29d7faef086b4ce5c25cdead388716368ec1aa03c9cea45dbec5429c3b70f831d22147ffb01da0628c5e8e28acbaa3612fcb55f8297662f647090ceda38f3722a2cb5f87f1df667e7bf1263313667641b4b266fa0af2a6525593c093414c63ec75f1fd3287f7c5319a6648712d1203d648bec508db8ee424910184195ee68695afec176143f7fabc2a0987d746752912c43aa45a3f03393d78d494e0497317d846bb97dbf238e7fc52131e207f1d3db2143b8cb6e4f3d9cf8a66e8b32d46277ada996b3cb3363d27f2594cf491d88e16e2475e18e20a9a2bf98c2ecccda9d254bd5377f260eab578a7fe179a37d55c1b8df593e8dea26cbd2df0c74e90ecdecd7708740e2b73532d75b6102747fa8b7090f7abd106cf42a40eabea86062ddbe17866f7845b878a2f593c4f91ab07f8ede0f5a61255a2ae6cde2d0a160e2c0d1e888e1c814e7af45940c0cc6c376b3610335565b61027e9190610335565b6102889190610335565b6102929190610335565b61029c9190610335565b6102a69190610335565b6102b09190610335565b6102ba9190610335565b6102c49190610335565b6102ce9190610335565b6102d89190610335565b6102e29190610335565b6102ec9190610335565b905090565b5f60208284031215610301575f5ffd5b5035919050565b5f60208284031215610318575f5ffd5b81356001600160a01b038116811461032e575f5ffd5b9392505050565b8082018082111561035457634e487b7160e01b5f52601160045260245ffd5b9291505056fea2646970667358221220b19e1d4dbc58b2b25af6b8d4f4141a10b3955f53d545efbdb67facc7d4e26fd764736f6c634300081c0033", + "nonce": "0x1", + "storage": { + "0x647538f573622c2dc5ad534d3157795075362ce71b9944e2a4e56b07eac973fc": "0x0000000000000000000000000000000000000000000000000000000367c0ba09" + } + } + }, + "baseFeePerGas": "0x4c4b40", + "blobGasUsed": "0x70863c", + "coinbase": "0x4200000000000000000000000000000000000011", + "config": { + "activationAdminAddress": "0xce3a3bee7e72e2a24079f3c0cb3b97740ed425a9", + "arrowGlacierBlock": 0, + "base": { + "azul": 1779991200, + "beryl": 1782410400, + "cobalt": null + }, + "bedrockBlock": 0, + "berlinBlock": 0, + "byzantiumBlock": 0, + "canyonTime": 1704992401, + "chainId": 8453, + "constantinopleBlock": 0, + "daoForkSupport": false, + "ecotoneTime": 1710374401, + "eip150Block": 0, + "eip1559Denominator": 50, + "eip1559DenominatorCanyon": 250, + "eip1559Elasticity": 6, + "eip155Block": 0, + "eip158Block": 0, + "fjordTime": 1720627201, + "graniteTime": 1726070401, + "grayGlacierBlock": 0, + "holoceneTime": 1736445601, + "homesteadBlock": 0, + "istanbulBlock": 0, + "isthmusTime": 1746806401, + "jovianTime": 1764691201, + "londonBlock": 0, + "mergeNetsplitBlock": 0, + "muirGlacierBlock": 0, + "optimism": { + "eip1559Denominator": 50, + "eip1559Elasticity": 6 + }, + "petersburgBlock": 0, + "regolithTime": 0, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true + }, + "difficulty": "0x0", + "excessBlobGas": "0x0", + "extraData": "0x01000000640000000500000000004c4b40", + "gasLimit": "0x17d78400", + "mixHash": "0x5c6509951e2eabead99d5e3abbd20cc053c5b5b1a82b0cdfef7c9134a24fc490", + "nonce": "0x0", + "number": "0x2f5cf31", + "timestamp": "0x6a75fb45" + }, + "input": "0x02f890822105836aa6d580841dcf27818301d4c094ec0e36a6060339694c618ffffcc9ec7da21cb0cc80a4d67704ad000000000000000000000000000000000000000000000000000000036948f05ac080a002f41d76d4d32a48ed9dc1cf0ea08e40ac40fabb2e5ec3be3d6906a296a7fc66a042f0f6ce0005056cea2f280e60d032efa10afa2dfb9928215a971d5269771812" +} diff --git a/crates/execution/firehose-tests/tests/prestate.rs b/crates/execution/firehose-tests/tests/prestate.rs index 645e493b98..18e8dcc389 100644 --- a/crates/execution/firehose-tests/tests/prestate.rs +++ b/crates/execution/firehose-tests/tests/prestate.rs @@ -1,14 +1,20 @@ //! Docker-free prestate coverage of the invariant and projection layers. //! -//! Replays the `nop_transfer` prestate through the tracer and asserts the property invariants plus -//! a projection golden over its single transaction — the same two layers `base-system-tests`' -//! `firehose_b20` uses, without a node or Docker, so a regression in the harness itself surfaces in -//! the default workspace run. +//! Two cases, both replayed with no node and no Docker so a regression in the harness itself +//! surfaces in the default workspace run: +//! +//! * `nop_transfer` — a hand-written fixture asserted against the property invariants and a +//! projection golden, the same two layers `base-system-tests`' `firehose_b20` uses. +//! * `base_mainnet_replay` — a real Base mainnet transaction, generated by +//! `base-firehose-prestate`, whose golden was seeded from production Firehose so that its first +//! run validated the generator and the tracer against production. Both are ordinary goldens now. use std::path::PathBuf; +use alloy_primitives::{B256, b256}; use base_firehose_tests::{ - BlockInvariants, BlockProjection, Golden, SymbolTable, VolatilePolicy, run_prestate, + BlockInvariants, BlockProjection, Golden, ProductionReplay, SymbolTable, VolatilePolicy, + run_prestate, }; #[test] @@ -42,6 +48,43 @@ fn nop_transfer() { .assert_equal(); } +/// Replays a real Base mainnet transaction generated by `base-firehose-prestate`. +/// +/// `prestate.json` came from `base-firehose-prestate generate` against an archive node. The golden +/// was *seeded* by `base-firehose-prestate reference`, which writes the transaction exactly as +/// StreamingFast's production Firehose recorded it — so the first run of this test was a direct +/// comparison against production, and it matched. That validation is a one-off: it proved the +/// generator and the tracer agree with production, and from here the golden is an ordinary one, +/// regenerated with `GOLDEN_UPDATE=1` like every other. No test needs credentials or a production +/// endpoint, and further cases only need `generate`. +/// +/// [`ProductionReplay`] is what made that first comparison possible: the replay runs the +/// transaction alone in a synthetic single-transaction block, so it excludes the block-wide +/// positional fields (ordinals, the transaction's index, each log's block index, the cumulative gas +/// used) and keeps everything else — gas accounting, the call tree, logs and absolute balance, +/// nonce and storage values. +#[test] +fn base_mainnet_replay() { + const TX: B256 = b256!("0x32ecdb4e72df6ec331edb81256b58a768ba49d1e3e89a1a071b980a85d6b72c0"); + + let folder = cases_dir().join("base_mainnet_replay"); + let outcome = run_prestate(&folder).expect("base_mainnet_replay prestate must succeed"); + + BlockInvariants::assert(&outcome.block).expect("captured block must satisfy the invariants"); + + let projection = ProductionReplay::projection() + .transaction(&outcome.block, TX.as_slice()) + .expect("projecting the replayed transaction must succeed"); + + Golden::is_json_equal(&projection, &folder.join("block.49663794.json")) + .expect("comparing against golden must not error") + .assert_equal(); +} + fn case_dir() -> PathBuf { - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests").join("cases").join("nop_transfer") + cases_dir().join("nop_transfer") +} + +fn cases_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests").join("cases") } diff --git a/etc/tools/firehose-prestate/Cargo.toml b/etc/tools/firehose-prestate/Cargo.toml new file mode 100644 index 0000000000..1c5dcd63af --- /dev/null +++ b/etc/tools/firehose-prestate/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "base-firehose-prestate" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +publish = false + +[lints] +workspace = true + +[dependencies] +# base +base-common-evm.workspace = true +base-common-chains.workspace = true +base-common-rpc-types.workspace = true +base-execution-chainspec.workspace = true +base-common-consensus = { workspace = true, features = ["serde"] } + +# alloy +alloy-genesis.workspace = true + +# firehose +firehose-tracer-prestate = { workspace = true, features = ["cli"] } + +# misc +eyre.workspace = true +serde.workspace = true +serde_json.workspace = true +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } +clap = { workspace = true, features = ["derive", "env"] } diff --git a/etc/tools/firehose-prestate/README.md b/etc/tools/firehose-prestate/README.md new file mode 100644 index 0000000000..4996563a07 --- /dev/null +++ b/etc/tools/firehose-prestate/README.md @@ -0,0 +1,44 @@ +Generate replayable Firehose prestate fixtures for real Base transactions. + +This is the Base wrapper around +[`firehose-tracer-prestate`](https://crates.io/crates/firehose-tracer-prestate), +the chain-agnostic Rust port of `streamingfast/go-ethereum`'s +[`generate-prestate`](https://github.com/streamingfast/go-ethereum/blob/release/geth-v1.17.x-fh3.0/eth/tracers/internal/tracetest/firehose/generate-prestate/main.go). +Point it at a transaction hash and an archive node and it writes a `prestate.json` that +`base_firehose_tests::run_prestate` replays through the Firehose tracer with no node, no Docker and +no network. + +Base supplies the four things the shared crate cannot know: the `BaseTxEnvelope` type, the genesis +`config` (re-projected from `ChainConfig`, because the chain spec's own genesis drops every +timestamped fork), the `L1Block` predeploy slots the L1-cost function reads outside the EVM journal, +and the chain id. + +## Generating a fixture + +```console +$ export ARCHIVE_ENDPOINT=https:// +$ cargo run -p base-firehose-prestate -- generate \ + --network base \ + --tx 0x32ecdb4e72df6ec331edb81256b58a768ba49d1e3e89a1a071b980a85d6b72c0 \ + --out crates/execution/firehose-tests/tests/cases/base_mainnet_replay +``` + +The endpoint must expose `debug_traceTransaction` with the `prestateTracer` over archive state. +Most public Base RPCs do not; `https://base.drpc.org` does. + +The fixture is `{ genesis, context, input }`: + +- `genesis` — the parent block's header fields, the `prestateTracer` alloc, and the network's real + fork schedule taken straight from `BaseChainSpec` rather than a hand-copied table. +- `context` — the traced block's number, timestamp, gas limit, coinbase, difficulty and base fee. +- `input` — the transaction, EIP-2718 encoded. + +### Why the `L1Block` predeploy is seeded explicitly + +The OP L1-cost function reads the `L1Block` predeploy (`0x4200…0015`) storage straight from the +state database, outside the EVM journal, so `debug_traceTransaction` never reports those slots. A +fixture without them replays with a zero L1 fee and every fee balance change comes out wrong. The +generator therefore seeds the slots `L1BlockInfo::try_fetch` reads, listed locally from the +already-public `L1BlockInfo` slot constants. They are read at the traced block, since those slots +are written by the L1-info deposit at index 0 of that same block. Anything the tracer did report +wins. diff --git a/etc/tools/firehose-prestate/src/lib.rs b/etc/tools/firehose-prestate/src/lib.rs new file mode 100644 index 0000000000..74bc18842d --- /dev/null +++ b/etc/tools/firehose-prestate/src/lib.rs @@ -0,0 +1,4 @@ +#![doc = include_str!("../README.md")] + +mod network; +pub use network::PrestateNetwork; diff --git a/etc/tools/firehose-prestate/src/main.rs b/etc/tools/firehose-prestate/src/main.rs new file mode 100644 index 0000000000..aefbfca5a1 --- /dev/null +++ b/etc/tools/firehose-prestate/src/main.rs @@ -0,0 +1,36 @@ +#![doc = include_str!("../README.md")] + +use base_firehose_prestate::PrestateNetwork; +use clap::{Parser, Subcommand}; +use eyre::Result; +use firehose_tracer_prestate::GenerateArgs; + +/// Generate replayable Firehose prestate fixtures for Base transactions. +#[derive(Parser, Debug)] +#[command(name = "base-firehose-prestate")] +struct Cli { + /// Which Base network the transaction belongs to. + #[arg(long, value_enum, default_value = "base", global = true)] + network: PrestateNetwork, + + #[command(subcommand)] + command: Command, +} + +#[derive(Subcommand, Debug)] +enum Command { + /// Write `prestate.json` for a mined transaction, read from an archive node. + Generate(GenerateArgs), +} + +#[tokio::main] +async fn main() -> Result<()> { + let cli = Cli::parse(); + match cli.command { + Command::Generate(args) => { + args.run(cli.network).await?; + } + } + + Ok(()) +} diff --git a/etc/tools/firehose-prestate/src/network.rs b/etc/tools/firehose-prestate/src/network.rs new file mode 100644 index 0000000000..e25998a9ed --- /dev/null +++ b/etc/tools/firehose-prestate/src/network.rs @@ -0,0 +1,181 @@ +//! The Base networks a prestate fixture can be generated for. + +use std::sync::LazyLock; + +use alloy_genesis::ChainConfig as GenesisChainConfig; +use base_common_chains::ChainConfig; +use base_common_consensus::{BaseTxEnvelope, Predeploys}; +use base_common_evm::L1BlockInfo; +use base_common_rpc_types::{FeeInfo, GenesisInfo, UpgradeInfo}; +use base_execution_chainspec::BaseChainSpec; +use eyre::{Context, eyre}; +use firehose_tracer_prestate::{PrestateChain, SeededAccount}; + +/// A Base network the generator knows how to build a fixture for. +/// +/// The Go generator this ports carries a hand-copied `params.ChainConfig` per network. Here the +/// fork schedule comes out of [`ChainConfig`], the same table the node configures itself from, so a +/// fixture can never drift from the schedule the node executes with. +#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)] +pub enum PrestateNetwork { + /// Base Mainnet, chain id 8453. + #[value(name = "base")] + Mainnet, + /// Base Sepolia, chain id 84532. + #[value(name = "base-sepolia")] + Sepolia, +} + +impl PrestateNetwork { + /// The node-side configuration for this network. + pub const fn chain_config(self) -> &'static ChainConfig { + match self { + Self::Mainnet => ChainConfig::mainnet(), + Self::Sepolia => ChainConfig::sepolia(), + } + } + + /// The chain spec this network's genesis is derived from. + pub fn chain_spec(self) -> BaseChainSpec { + match self { + Self::Mainnet => BaseChainSpec::mainnet(), + Self::Sepolia => BaseChainSpec::sepolia(), + } + } + + /// The L2 chain id. + pub const fn chain_id(self) -> u64 { + self.chain_config().chain_id + } + + /// The genesis config a generated fixture embeds. + /// + /// [`BaseChainSpec`]'s own genesis config only carries the block-numbered forks: every + /// timestamped OP and Base upgrade reaches the spec through [`ChainConfig`] instead, and so + /// would be silently missing from a fixture built from the genesis alone — the replay would run + /// a post-Isthmus transaction under pre-Canyon rules. This re-projects the timestamps, the + /// Base-specific upgrades and the EIP-1559 parameters back into the genesis `config` extras that + /// [`BaseChainSpec::from_genesis`] reads. + pub fn genesis_config(self) -> eyre::Result { + let chain = self.chain_config(); + let mut config = self.chain_spec().genesis.config.clone(); + + let genesis_info = GenesisInfo { + bedrock_block: Some(chain.bedrock_block), + regolith_time: Some(chain.regolith_timestamp), + canyon_time: Some(chain.canyon_timestamp), + ecotone_time: Some(chain.ecotone_timestamp), + fjord_time: Some(chain.fjord_timestamp), + granite_time: Some(chain.granite_timestamp), + holocene_time: Some(chain.holocene_timestamp), + isthmus_time: Some(chain.isthmus_timestamp), + jovian_time: Some(chain.jovian_timestamp), + base: UpgradeInfo { + azul: chain.azul_timestamp, + beryl: chain.beryl_timestamp, + cobalt: chain.cobalt_timestamp, + }, + activation_admin_address: ChainConfig::beryl_activation_admin_address_by_chain_id( + chain.chain_id, + ), + }; + + let fee_info = FeeInfo { + eip1559_elasticity: Some(chain.eip1559_elasticity), + eip1559_denominator: Some(chain.eip1559_denominator), + eip1559_denominator_canyon: Some(chain.eip1559_denominator_canyon), + }; + + Self::merge(&mut config, &genesis_info).context("merging the Base genesis info")?; + Self::merge(&mut config, &fee_info).context("merging the EIP-1559 parameters")?; + + Ok(config) + } + + /// Flattens `extras` into a genesis config's extra fields. + fn merge(config: &mut GenesisChainConfig, extras: &impl serde::Serialize) -> eyre::Result<()> { + let serde_json::Value::Object(fields) = serde_json::to_value(extras)? else { + return Err(eyre!("genesis config extras must serialise to an object")); + }; + + config.extra_fields.extend(fields); + + Ok(()) + } +} + +impl PrestateChain for PrestateNetwork { + type Transaction = BaseTxEnvelope; + + fn chain_id(&self) -> u64 { + self.chain_config().chain_id + } + + fn genesis_config(&self) -> eyre::Result { + (*self).genesis_config() + } + + fn seeded_accounts(&self) -> &[SeededAccount] { + static L1_BLOCK: LazyLock<[SeededAccount; 1]> = LazyLock::new(|| [l1_block_account()]); + L1_BLOCK.as_slice() + } +} + +/// The `L1Block` predeploy plus the slots [`L1BlockInfo::try_fetch`] reads. +/// +/// Kept here so the generator does not grow the `base-common-*` public API. Isthmus's +/// [`L1BlockInfo::OPERATOR_FEE_SCALARS_SLOT`] and Jovian's +/// [`L1BlockInfo::DA_FOOTPRINT_GAS_SCALAR_SLOT`] share slot 8, so the latter is omitted. A new +/// fork that adds a distinct slot needs an extra entry. +fn l1_block_account() -> SeededAccount { + SeededAccount { + address: Predeploys::L1_BLOCK_INFO, + slots: vec![ + L1BlockInfo::L1_BASE_FEE_SLOT, + L1BlockInfo::ECOTONE_L1_FEE_SCALARS_SLOT, + L1BlockInfo::L1_OVERHEAD_SLOT, + L1BlockInfo::L1_SCALAR_SLOT, + L1BlockInfo::ECOTONE_L1_BLOB_BASE_FEE_SLOT, + L1BlockInfo::OPERATOR_FEE_SCALARS_SLOT, + ], + } +} + +#[cfg(test)] +mod tests { + use base_common_consensus::Predeploys; + use base_execution_chainspec::BaseChainSpec; + use firehose_tracer_prestate::PrestateChain; + + use super::PrestateNetwork; + + /// The whole point of [`PrestateNetwork::genesis_config`]: a genesis carrying it must rebuild + /// into the same fork schedule the node runs with. A missing timestamp here means a fixture + /// replays a transaction under the wrong rules and quietly disagrees with production. + #[test] + fn genesis_config_round_trips_into_the_same_hardforks() { + for (network, expected) in [ + (PrestateNetwork::Mainnet, BaseChainSpec::mainnet()), + (PrestateNetwork::Sepolia, BaseChainSpec::sepolia()), + ] { + let mut genesis = expected.genesis.clone(); + genesis.config = network.genesis_config().expect("building the genesis config"); + + let rebuilt = BaseChainSpec::from_genesis(genesis); + + assert_eq!( + rebuilt.hardforks.forks_iter().collect::>(), + expected.hardforks.forks_iter().collect::>(), + "{network:?} genesis config must round trip into the same hardforks" + ); + } + } + + #[test] + fn seeds_the_l1_block_predeploy_slots_the_l1_cost_function_reads() { + let accounts = PrestateNetwork::Mainnet.seeded_accounts(); + assert_eq!(accounts.len(), 1); + assert_eq!(accounts[0].address, Predeploys::L1_BLOCK_INFO); + assert_eq!(accounts[0], super::l1_block_account()); + } +}