From d212842442c94e33d664ae1683768cb41f7b19b8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 11:53:35 -0500 Subject: [PATCH 1/5] chore(release): set version to 1.1.1 (#3624) Co-authored-by: github-actions[bot] --- Cargo.lock | 268 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 2 files changed, 135 insertions(+), 135 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b28502f319..024b0d3281 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2308,7 +2308,7 @@ dependencies = [ [[package]] name = "audit-archiver" -version = "1.1.0" +version = "1.1.1" dependencies = [ "anyhow", "audit-archiver-lib", @@ -2327,7 +2327,7 @@ dependencies = [ [[package]] name = "audit-archiver-lib" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-primitives", @@ -3140,7 +3140,7 @@ dependencies = [ [[package]] name = "base" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-chains", "base-cli-utils", @@ -3161,7 +3161,7 @@ dependencies = [ [[package]] name = "base-access-lists" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-contract 2.0.5", @@ -3179,7 +3179,7 @@ dependencies = [ [[package]] name = "base-action-harness" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -3243,7 +3243,7 @@ dependencies = [ [[package]] name = "base-balance-monitor" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-network 2.0.5", "alloy-node-bindings 2.0.5", @@ -3256,7 +3256,7 @@ dependencies = [ [[package]] name = "base-batcher-admin" -version = "1.1.0" +version = "1.1.1" dependencies = [ "base-batcher-core", "derive_more 2.1.1", @@ -3267,7 +3267,7 @@ dependencies = [ [[package]] name = "base-batcher-bin" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-primitives", "alloy-signer-local 2.0.5", @@ -3286,7 +3286,7 @@ dependencies = [ [[package]] name = "base-batcher-core" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-primitives", @@ -3311,7 +3311,7 @@ dependencies = [ [[package]] name = "base-batcher-encoder" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -3331,7 +3331,7 @@ dependencies = [ [[package]] name = "base-batcher-service" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-eips 2.0.5", "alloy-primitives", @@ -3365,7 +3365,7 @@ dependencies = [ [[package]] name = "base-batcher-source" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-primitives", @@ -3382,7 +3382,7 @@ dependencies = [ [[package]] name = "base-blobs" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-eips 2.0.5", "alloy-primitives", @@ -3393,7 +3393,7 @@ dependencies = [ [[package]] name = "base-builder-bin" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-primitives", "base-builder-core", @@ -3413,7 +3413,7 @@ dependencies = [ [[package]] name = "base-builder-core" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-contract 2.0.5", @@ -3542,7 +3542,7 @@ dependencies = [ [[package]] name = "base-builder-metering" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-primitives", "base-builder-core", @@ -3555,7 +3555,7 @@ dependencies = [ [[package]] name = "base-builder-publish" -version = "1.1.0" +version = "1.1.1" dependencies = [ "base-metrics", "base-ring-buffer", @@ -3575,7 +3575,7 @@ dependencies = [ [[package]] name = "base-bundle-extension" -version = "1.1.0" +version = "1.1.1" dependencies = [ "base-execution-txpool", "base-node-runner", @@ -3588,7 +3588,7 @@ dependencies = [ [[package]] name = "base-bundles" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-primitives", @@ -3605,7 +3605,7 @@ dependencies = [ [[package]] name = "base-challenger" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-primitives", @@ -3648,7 +3648,7 @@ dependencies = [ [[package]] name = "base-challenger-bin" -version = "1.1.0" +version = "1.1.1" dependencies = [ "base-challenger", "base-cli-utils", @@ -3658,7 +3658,7 @@ dependencies = [ [[package]] name = "base-cli-utils" -version = "1.1.0" +version = "1.1.1" dependencies = [ "backtrace", "base-metrics", @@ -3681,7 +3681,7 @@ dependencies = [ [[package]] name = "base-common-chains" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-chains", "alloy-eips 2.0.5", @@ -3697,7 +3697,7 @@ dependencies = [ [[package]] name = "base-common-consensus" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -3728,7 +3728,7 @@ dependencies = [ [[package]] name = "base-common-evm" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -3754,7 +3754,7 @@ dependencies = [ [[package]] name = "base-common-flashblocks" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", @@ -3770,7 +3770,7 @@ dependencies = [ [[package]] name = "base-common-flz" -version = "1.1.0" +version = "1.1.1" dependencies = [ "hex-literal 1.1.0", "rstest", @@ -3778,7 +3778,7 @@ dependencies = [ [[package]] name = "base-common-genesis" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-chains", "alloy-consensus 2.0.5", @@ -3799,7 +3799,7 @@ dependencies = [ [[package]] name = "base-common-network" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-network 2.0.5", @@ -3817,7 +3817,7 @@ dependencies = [ [[package]] name = "base-common-precompiles" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-evm", "alloy-primitives", @@ -3833,7 +3833,7 @@ dependencies = [ [[package]] name = "base-common-rpc-types" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -3858,7 +3858,7 @@ dependencies = [ [[package]] name = "base-common-rpc-types-engine" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -3881,7 +3881,7 @@ dependencies = [ [[package]] name = "base-common-signer" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -3901,7 +3901,7 @@ dependencies = [ [[package]] name = "base-comp" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -3926,7 +3926,7 @@ dependencies = [ [[package]] name = "base-consensus" -version = "1.1.0" +version = "1.1.1" dependencies = [ "base-cli-utils", "base-consensus-cli", @@ -3935,7 +3935,7 @@ dependencies = [ [[package]] name = "base-consensus-cli" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-chains", "alloy-genesis 2.0.5", @@ -3978,7 +3978,7 @@ dependencies = [ [[package]] name = "base-consensus-derive" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -4008,7 +4008,7 @@ dependencies = [ [[package]] name = "base-consensus-disc" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-rlp", "backon", @@ -4031,7 +4031,7 @@ dependencies = [ [[package]] name = "base-consensus-engine" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -4074,7 +4074,7 @@ dependencies = [ [[package]] name = "base-consensus-gossip" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-chains", "alloy-consensus 2.0.5", @@ -4113,7 +4113,7 @@ dependencies = [ [[package]] name = "base-consensus-node" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-chains", "alloy-consensus 2.0.5", @@ -4183,7 +4183,7 @@ dependencies = [ [[package]] name = "base-consensus-peers" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -4209,7 +4209,7 @@ dependencies = [ [[package]] name = "base-consensus-providers" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -4246,7 +4246,7 @@ dependencies = [ [[package]] name = "base-consensus-rpc" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-eips 2.0.5", "alloy-primitives", @@ -4272,7 +4272,7 @@ dependencies = [ [[package]] name = "base-consensus-safedb" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-eips 2.0.5", "alloy-primitives", @@ -4288,7 +4288,7 @@ dependencies = [ [[package]] name = "base-consensus-sources" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-primitives", "alloy-rpc-client 2.0.5", @@ -4310,7 +4310,7 @@ dependencies = [ [[package]] name = "base-consensus-upgrades" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-eips 2.0.5", "alloy-primitives", @@ -4322,7 +4322,7 @@ dependencies = [ [[package]] name = "base-engine-tree" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-chains", "alloy-consensus 2.0.5", @@ -4370,7 +4370,7 @@ dependencies = [ [[package]] name = "base-execution-chainspec" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-chains", "alloy-consensus 2.0.5", @@ -4393,7 +4393,7 @@ dependencies = [ [[package]] name = "base-execution-cli" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-eips 2.0.5", "backon", @@ -4449,7 +4449,7 @@ dependencies = [ [[package]] name = "base-execution-consensus" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-chains", "alloy-consensus 2.0.5", @@ -4479,7 +4479,7 @@ dependencies = [ [[package]] name = "base-execution-evm" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -4507,7 +4507,7 @@ dependencies = [ [[package]] name = "base-execution-exex" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -4533,7 +4533,7 @@ dependencies = [ [[package]] name = "base-execution-payload-builder" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -4572,7 +4572,7 @@ dependencies = [ [[package]] name = "base-execution-rpc" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -4636,7 +4636,7 @@ dependencies = [ [[package]] name = "base-execution-trie" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -4682,7 +4682,7 @@ dependencies = [ [[package]] name = "base-execution-txpool" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -4724,7 +4724,7 @@ dependencies = [ [[package]] name = "base-flashblocks" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -4781,7 +4781,7 @@ dependencies = [ [[package]] name = "base-flashblocks-node" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-contract 2.0.5", @@ -4837,7 +4837,7 @@ dependencies = [ [[package]] name = "base-health" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-transport-http 2.0.5", "async-trait", @@ -4859,7 +4859,7 @@ dependencies = [ [[package]] name = "base-jwt" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-primitives", "alloy-provider 2.0.5", @@ -4875,7 +4875,7 @@ dependencies = [ [[package]] name = "base-load-tester-bin" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-network 2.0.5", "alloy-primitives", @@ -4897,7 +4897,7 @@ dependencies = [ [[package]] name = "base-load-tests" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -4936,7 +4936,7 @@ dependencies = [ [[package]] name = "base-metering" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -4990,7 +4990,7 @@ dependencies = [ [[package]] name = "base-metrics" -version = "1.1.0" +version = "1.1.1" dependencies = [ "ctor", "metrics", @@ -5000,7 +5000,7 @@ dependencies = [ [[package]] name = "base-node-core" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -5069,7 +5069,7 @@ dependencies = [ [[package]] name = "base-node-runner" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-eips 2.0.5", "alloy-genesis 2.0.5", @@ -5120,7 +5120,7 @@ dependencies = [ [[package]] name = "base-precompile-macros" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-primitives", "proc-macro2", @@ -5130,7 +5130,7 @@ dependencies = [ [[package]] name = "base-precompile-storage" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-evm", "alloy-primitives", @@ -5146,7 +5146,7 @@ dependencies = [ [[package]] name = "base-proof" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -5184,7 +5184,7 @@ dependencies = [ [[package]] name = "base-proof-client" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-evm", @@ -5207,7 +5207,7 @@ dependencies = [ [[package]] name = "base-proof-contracts" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-contract 2.0.5", "alloy-primitives", @@ -5224,7 +5224,7 @@ dependencies = [ [[package]] name = "base-proof-driver" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-evm", @@ -5244,7 +5244,7 @@ dependencies = [ [[package]] name = "base-proof-executor" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -5278,7 +5278,7 @@ dependencies = [ [[package]] name = "base-proof-host" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -5319,7 +5319,7 @@ dependencies = [ [[package]] name = "base-proof-mpt" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-primitives", @@ -5340,7 +5340,7 @@ dependencies = [ [[package]] name = "base-proof-preimage" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-primitives", "async-channel", @@ -5354,7 +5354,7 @@ dependencies = [ [[package]] name = "base-proof-primitives" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-chains", "alloy-eips 2.0.5", @@ -5372,7 +5372,7 @@ dependencies = [ [[package]] name = "base-proof-rpc" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-eips 2.0.5", "alloy-network 2.0.5", @@ -5399,7 +5399,7 @@ dependencies = [ [[package]] name = "base-proof-succinct-build-utils" -version = "1.1.0" +version = "1.1.1" dependencies = [ "cargo_metadata 0.18.1", "sp1-build", @@ -5407,7 +5407,7 @@ dependencies = [ [[package]] name = "base-proof-succinct-client-utils" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -5444,7 +5444,7 @@ dependencies = [ [[package]] name = "base-proof-succinct-elfs" -version = "1.1.0" +version = "1.1.1" dependencies = [ "serde", "sha2 0.10.9", @@ -5453,7 +5453,7 @@ dependencies = [ [[package]] name = "base-proof-succinct-ethereum-client-utils" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-genesis 2.0.5", "anyhow", @@ -5470,7 +5470,7 @@ dependencies = [ [[package]] name = "base-proof-succinct-ethereum-host-utils" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-eips 2.0.5", "alloy-primitives", @@ -5489,7 +5489,7 @@ dependencies = [ [[package]] name = "base-proof-succinct-host-utils" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-contract 2.0.5", @@ -5543,7 +5543,7 @@ dependencies = [ [[package]] name = "base-proof-succinct-proof-utils" -version = "1.1.0" +version = "1.1.1" dependencies = [ "anyhow", "base-proof-succinct-elfs", @@ -5563,7 +5563,7 @@ dependencies = [ [[package]] name = "base-proof-succinct-prove" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-contract 2.0.5", "alloy-eips 2.0.5", @@ -5598,7 +5598,7 @@ dependencies = [ [[package]] name = "base-proof-succinct-scripts" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-eips 2.0.5", "alloy-network 2.0.5", @@ -5630,7 +5630,7 @@ dependencies = [ [[package]] name = "base-proof-succinct-signer-utils" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -5651,7 +5651,7 @@ dependencies = [ [[package]] name = "base-proof-succinct-validity" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-eips 2.0.5", "alloy-primitives", @@ -5690,7 +5690,7 @@ dependencies = [ [[package]] name = "base-proof-tee-nitro-attestation-prover" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-primitives", "anyhow", @@ -5709,7 +5709,7 @@ dependencies = [ [[package]] name = "base-proof-tee-nitro-enclave" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-chains", "alloy-eips 2.0.5", @@ -5742,7 +5742,7 @@ dependencies = [ [[package]] name = "base-proof-tee-nitro-host" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-genesis 2.0.5", "alloy-primitives", @@ -5773,7 +5773,7 @@ dependencies = [ [[package]] name = "base-proof-tee-nitro-verifier" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -5790,7 +5790,7 @@ dependencies = [ [[package]] name = "base-proof-tee-registrar" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-primitives", @@ -5828,7 +5828,7 @@ dependencies = [ [[package]] name = "base-proof-tee-registrar-bin" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-primitives", "alloy-provider 2.0.5", @@ -5858,7 +5858,7 @@ dependencies = [ [[package]] name = "base-proofs-extension" -version = "1.1.0" +version = "1.1.1" dependencies = [ "base-common-consensus", "base-execution-exex", @@ -5877,7 +5877,7 @@ dependencies = [ [[package]] name = "base-proposer" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-primitives", @@ -5918,7 +5918,7 @@ dependencies = [ [[package]] name = "base-proposer-bin" -version = "1.1.0" +version = "1.1.1" dependencies = [ "base-cli-utils", "base-proposer", @@ -5929,7 +5929,7 @@ dependencies = [ [[package]] name = "base-protocol" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloc-no-stdlib", "alloy-consensus 2.0.5", @@ -5965,7 +5965,7 @@ dependencies = [ [[package]] name = "base-prover-nitro-enclave" -version = "1.1.0" +version = "1.1.1" dependencies = [ "base-proof-tee-nitro-enclave", "eyre", @@ -5974,7 +5974,7 @@ dependencies = [ [[package]] name = "base-prover-nitro-host" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-primitives", "base-cli-utils", @@ -5994,7 +5994,7 @@ dependencies = [ [[package]] name = "base-prover-service" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-primitives", "alloy-provider 2.0.5", @@ -6037,7 +6037,7 @@ dependencies = [ [[package]] name = "base-prover-service-client" -version = "1.1.0" +version = "1.1.1" dependencies = [ "async-trait", "base-prover-service-protocol", @@ -6052,7 +6052,7 @@ dependencies = [ [[package]] name = "base-prover-service-db" -version = "1.1.0" +version = "1.1.1" dependencies = [ "anyhow", "base-prover-service-protocol", @@ -6069,7 +6069,7 @@ dependencies = [ [[package]] name = "base-prover-service-outbox" -version = "1.1.0" +version = "1.1.1" dependencies = [ "anyhow", "async-trait", @@ -6083,7 +6083,7 @@ dependencies = [ [[package]] name = "base-prover-service-protocol" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-primitives", "base-proof-primitives", @@ -6096,7 +6096,7 @@ dependencies = [ [[package]] name = "base-prover-zk" -version = "1.1.0" +version = "1.1.1" dependencies = [ "base-cli-utils", "base-proof-succinct-host-utils", @@ -6124,14 +6124,14 @@ dependencies = [ [[package]] name = "base-reth-cli" -version = "1.1.0" +version = "1.1.1" dependencies = [ "reth-node-core", ] [[package]] name = "base-reth-node" -version = "1.1.0" +version = "1.1.1" dependencies = [ "base-cli-utils", "base-execution-cli", @@ -6142,11 +6142,11 @@ dependencies = [ [[package]] name = "base-ring-buffer" -version = "1.1.0" +version = "1.1.1" [[package]] name = "base-runtime" -version = "1.1.0" +version = "1.1.1" dependencies = [ "futures", "rand 0.9.4", @@ -6158,7 +6158,7 @@ dependencies = [ [[package]] name = "base-snapshotter" -version = "1.1.0" +version = "1.1.1" dependencies = [ "anyhow", "async-trait", @@ -6183,7 +6183,7 @@ dependencies = [ [[package]] name = "base-snapshotter-bin" -version = "1.1.0" +version = "1.1.1" dependencies = [ "anyhow", "aws-config", @@ -6199,7 +6199,7 @@ dependencies = [ [[package]] name = "base-snark-e2e" -version = "1.1.0" +version = "1.1.1" dependencies = [ "base-zk-service", "tokio", @@ -6209,7 +6209,7 @@ dependencies = [ [[package]] name = "base-system-tests" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -6277,7 +6277,7 @@ dependencies = [ [[package]] name = "base-test-utils" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-contract 2.0.5", @@ -6295,7 +6295,7 @@ dependencies = [ [[package]] name = "base-tx-forwarding" -version = "1.1.0" +version = "1.1.1" dependencies = [ "base-execution-txpool", "base-node-runner", @@ -6305,7 +6305,7 @@ dependencies = [ [[package]] name = "base-tx-manager" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -6339,7 +6339,7 @@ dependencies = [ [[package]] name = "base-txpool-rpc" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-primitives", "base-node-runner", @@ -6356,7 +6356,7 @@ dependencies = [ [[package]] name = "base-txpool-tracing" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -6387,7 +6387,7 @@ dependencies = [ [[package]] name = "base-witness-diff" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-primitives", "alloy-provider 2.0.5", @@ -6413,7 +6413,7 @@ checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" [[package]] name = "base-zk-client" -version = "1.1.0" +version = "1.1.1" dependencies = [ "async-trait", "prost 0.14.4", @@ -6429,7 +6429,7 @@ dependencies = [ [[package]] name = "base-zk-db" -version = "1.1.0" +version = "1.1.1" dependencies = [ "anyhow", "chrono", @@ -6445,7 +6445,7 @@ dependencies = [ [[package]] name = "base-zk-outbox" -version = "1.1.0" +version = "1.1.1" dependencies = [ "anyhow", "async-trait", @@ -6458,7 +6458,7 @@ dependencies = [ [[package]] name = "base-zk-service" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-primitives", "alloy-provider 2.0.5", @@ -6544,7 +6544,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "basectl" -version = "1.1.0" +version = "1.1.1" dependencies = [ "anyhow", "basectl-cli", @@ -6556,7 +6556,7 @@ dependencies = [ [[package]] name = "basectl-cli" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-contract 2.0.5", @@ -6593,7 +6593,7 @@ dependencies = [ [[package]] name = "based" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-primitives", @@ -6609,7 +6609,7 @@ dependencies = [ [[package]] name = "based-bin" -version = "1.1.0" +version = "1.1.1" dependencies = [ "base-cli-utils", "based", @@ -11354,7 +11354,7 @@ dependencies = [ [[package]] name = "ingress-rpc" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-provider 2.0.5", "anyhow", @@ -11373,7 +11373,7 @@ dependencies = [ [[package]] name = "ingress-rpc-lib" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -24779,7 +24779,7 @@ dependencies = [ [[package]] name = "websocket-proxy" -version = "1.1.0" +version = "1.1.1" dependencies = [ "axum 0.8.9", "backoff", @@ -24800,7 +24800,7 @@ dependencies = [ [[package]] name = "websocket-proxy-bin" -version = "1.1.0" +version = "1.1.1" dependencies = [ "axum 0.8.9", "base-cli-utils", diff --git a/Cargo.toml b/Cargo.toml index c93fc604df..3e7aab25ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "1.1.0" +version = "1.1.1" edition = "2024" rust-version = "1.94" license = "MIT" From 4e84ba3d11e5086fb6d59b90eabc55636ec5405f Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Thu, 18 Jun 2026 14:38:45 -0500 Subject: [PATCH 2/5] chore(common): set mainnet activation date (#3627) * chore(common): set mainnet activation date Co-authored-by: Codex * fix ci after beryl mainnet scheduling Co-authored-by: Codex * fix basectl unscheduled hardfork test Co-authored-by: Codex --------- Co-authored-by: Codex --- .github/workflows/base-anvil-package.yml | 9 ++++++++- .github/workflows/base-std-fork-tests.yml | 15 ++++++++++++++- crates/common/chains/src/chain.rs | 11 ++++++++++- crates/common/chains/src/config.rs | 2 +- crates/common/chains/src/upgrade.rs | 5 +++++ crates/execution/chainspec/src/spec.rs | 4 ++-- crates/infra/basectl/src/app/views/upgrades.rs | 2 +- docs/guides/UPGRADES.md | 2 +- 8 files changed, 42 insertions(+), 8 deletions(-) diff --git a/.github/workflows/base-anvil-package.yml b/.github/workflows/base-anvil-package.yml index 7e98961374..28d5a1b323 100644 --- a/.github/workflows/base-anvil-package.yml +++ b/.github/workflows/base-anvil-package.yml @@ -78,6 +78,12 @@ jobs: native-deps: "true" rust-cache-shared-key: "base-anvil-package" + - name: Set up Python 3.13 + if: matrix.settings.smoke_test + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.13" + - name: Clone base-anvil id: base-anvil shell: bash @@ -183,10 +189,11 @@ jobs: set -euo pipefail cd "$BASE_STD_DIR" + make smoke-setup PYTHON=python3 ANVIL_BIN="$BASE_ANVIL_DIR/target/release/anvil" \ FORGE_BIN="$BASE_ANVIL_DIR/target/release/forge" \ ANVIL_LOG="$RUNNER_TEMP/base-std-anvil.log" \ - ./script/run-fork-tests.sh + make fork-tests - name: Upload anvil log if: always() && matrix.settings.smoke_test diff --git a/.github/workflows/base-std-fork-tests.yml b/.github/workflows/base-std-fork-tests.yml index 75738f4dc9..f08e98c44e 100644 --- a/.github/workflows/base-std-fork-tests.yml +++ b/.github/workflows/base-std-fork-tests.yml @@ -45,6 +45,11 @@ jobs: native-deps: "true" rust-cache-shared-key: "base-std-fork-tests" + - name: Set up Python 3.13 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.13" + - name: Clone fork-test repositories shell: bash env: @@ -90,6 +95,14 @@ jobs: "$BASE_ANVIL_DIR/target/release/anvil" --version "$BASE_ANVIL_DIR/target/release/forge" --version + - name: Set up base-std fork-test runner + shell: bash + run: | + set -euo pipefail + + cd "$BASE_STD_DIR" + make smoke-setup PYTHON=python3 + - name: Run base-std fork tests shell: bash run: | @@ -99,7 +112,7 @@ jobs: ANVIL_BIN="$BASE_ANVIL_DIR/target/release/anvil" \ FORGE_BIN="$BASE_ANVIL_DIR/target/release/forge" \ ANVIL_LOG="$RUNNER_TEMP/base-std-anvil.log" \ - ./script/run-fork-tests.sh 2>&1 | tee "$RUNNER_TEMP/fork-test-output.txt" + make fork-tests 2>&1 | tee "$RUNNER_TEMP/fork-test-output.txt" - name: Comment fork test results on PR if: github.event_name == 'pull_request' && always() diff --git a/crates/common/chains/src/chain.rs b/crates/common/chains/src/chain.rs index 31604b3604..d2601a2e6e 100644 --- a/crates/common/chains/src/chain.rs +++ b/crates/common/chains/src/chain.rs @@ -190,7 +190,10 @@ mod tests { base_mainnet_forks[Azul], ForkCondition::Timestamp(ChainConfig::mainnet().azul_timestamp.unwrap()) ); - assert_eq!(base_mainnet_forks[Beryl], ForkCondition::Never); + assert_eq!( + base_mainnet_forks[Beryl], + ForkCondition::Timestamp(ChainConfig::mainnet().beryl_timestamp.unwrap()) + ); assert_eq!(base_mainnet_forks[Cobalt], ForkCondition::Never); } @@ -305,6 +308,12 @@ mod tests { #[test] fn is_beryl_active_at_timestamp() { + let base_mainnet_forks = ChainUpgrades::mainnet(); + assert!(!base_mainnet_forks.is_beryl_active_at_timestamp(0)); + assert!(!base_mainnet_forks.is_beryl_active_at_timestamp(1_782_410_399)); + assert!(base_mainnet_forks.is_beryl_active_at_timestamp(1_782_410_400)); + assert!(base_mainnet_forks.is_beryl_active_at_timestamp(u64::MAX)); + let base_sepolia_forks = ChainUpgrades::sepolia(); assert!(!base_sepolia_forks.is_beryl_active_at_timestamp(0)); assert!(!base_sepolia_forks.is_beryl_active_at_timestamp(1_781_805_599)); diff --git a/crates/common/chains/src/config.rs b/crates/common/chains/src/config.rs index 814bbcc851..bea864690f 100644 --- a/crates/common/chains/src/config.rs +++ b/crates/common/chains/src/config.rs @@ -350,7 +350,7 @@ const MAINNET: ChainConfig = ChainConfig { isthmus_timestamp: 1_746_806_401, jovian_timestamp: 1_764_691_201, azul_timestamp: Some(1_779_991_200), - beryl_timestamp: None, + beryl_timestamp: Some(1_782_410_400), cobalt_timestamp: None, genesis_l1_hash: b256!("5c13d307623a926cd31415036c8b7fa14572f9dac64528e857a470511fc30771"), diff --git a/crates/common/chains/src/upgrade.rs b/crates/common/chains/src/upgrade.rs index fc65c119a6..1073e0313c 100644 --- a/crates/common/chains/src/upgrade.rs +++ b/crates/common/chains/src/upgrade.rs @@ -236,6 +236,11 @@ mod tests { (Chain::base_sepolia(), ChainConfig::sepolia().canyon_timestamp, BaseUpgrade::Canyon), (Chain::base_sepolia(), ChainConfig::sepolia().ecotone_timestamp, BaseUpgrade::Ecotone), (Chain::base_sepolia(), ChainConfig::sepolia().jovian_timestamp, BaseUpgrade::Jovian), + ( + Chain::base_mainnet(), + ChainConfig::mainnet().beryl_timestamp.unwrap(), + BaseUpgrade::Beryl, + ), ( Chain::base_sepolia(), ChainConfig::sepolia().azul_timestamp.unwrap(), diff --git a/crates/execution/chainspec/src/spec.rs b/crates/execution/chainspec/src/spec.rs index 49d7e4ddcc..a66511a04b 100644 --- a/crates/execution/chainspec/src/spec.rs +++ b/crates/execution/chainspec/src/spec.rs @@ -859,7 +859,7 @@ mod tests { fn latest_base_mainnet_fork_id() { let base_mainnet_spec = BaseChainSpec::mainnet(); assert_eq!( - base_mainnet_spec.hardfork_fork_id(BaseUpgrade::Azul).unwrap(), + base_mainnet_spec.hardfork_fork_id(BaseUpgrade::Beryl).unwrap(), base_mainnet_spec.latest_fork_id() ) } @@ -869,7 +869,7 @@ mod tests { let base_mainnet_spec = BaseChainSpec::mainnet(); let base_mainnet = BaseChainSpecBuilder::base_mainnet().build(); assert_eq!( - base_mainnet_spec.hardfork_fork_id(BaseUpgrade::Azul).unwrap(), + base_mainnet_spec.hardfork_fork_id(BaseUpgrade::Beryl).unwrap(), base_mainnet.latest_fork_id() ) } diff --git a/crates/infra/basectl/src/app/views/upgrades.rs b/crates/infra/basectl/src/app/views/upgrades.rs index 55cdb9e1ec..10f210da14 100644 --- a/crates/infra/basectl/src/app/views/upgrades.rs +++ b/crates/infra/basectl/src/app/views/upgrades.rs @@ -1907,7 +1907,7 @@ mod tests { #[test] fn unscheduled_selected_checks_do_not_start() { let mut view = UpgradesView::new(); - view.selected_chain = 3; + view.selected_chain = 0; let resources = Resources::new(MonitoringConfig::mainnet()); assert_eq!(view.selected_check_hardfork(now_unix()), Some("Beryl")); diff --git a/docs/guides/UPGRADES.md b/docs/guides/UPGRADES.md index 2f1956f1a8..d357ff99ce 100644 --- a/docs/guides/UPGRADES.md +++ b/docs/guides/UPGRADES.md @@ -248,7 +248,7 @@ hardforks: HardForkConfig { ### 8. Verify the upgrade consistency tests -**File:** [`crates/common/chains/tests/hardfork_consistency.rs`](https://github.com/base/base/blob/main/crates/common/chains/tests/hardfork_consistency.rs) +**File:** [`crates/common/chains/tests/hardfork_consistency.rs`](../../crates/common/chains/tests/hardfork_consistency.rs) These tests assert that `BaseChainConfig::mainnet().upgrade_activation(fork)` matches `BaseChainUpgrades::mainnet().upgrade_activation(fork)` for every `BaseUpgrade` variant. They should pass without changes as long as both sides consistently return `ForkCondition::Never` for an unscheduled upgrade or the same timestamp once scheduled. From 01e732cdbae0c624d652da9e608d7d3fe0f9c74b Mon Sep 17 00:00:00 2001 From: Haardik Date: Thu, 18 Jun 2026 16:21:24 -0400 Subject: [PATCH 3/5] Backport PR #3603 to releases/v1.1.0 (#3634) * insert parent block hash into pending EVM * typed errors for FlashblockId, use pair of Atomics * fix test failures due to harness adjustment * fmt fix * chore: retrigger CI for release retarget --- .../builder/core/src/flashblocks/payload.rs | 133 ++++++++++++--- crates/common/flashblocks/src/lib.rs | 2 +- crates/common/flashblocks/src/metadata.rs | 139 +++++++++++++++- .../engine-tree/src/cached_execution.rs | 2 +- .../flashblocks-node/benches/pending_state.rs | 4 +- .../flashblocks-node/src/test_harness.rs | 18 +- .../flashblocks-node/tests/eip7702_tests.rs | 8 +- .../flashblocks-node/tests/eth_call_erc20.rs | 6 +- .../flashblocks-node/tests/flashblocks_rpc.rs | 96 ++++++++++- .../execution/flashblocks-node/tests/state.rs | 4 +- .../flashblocks/src/block_assembler.rs | 2 +- crates/execution/flashblocks/src/cache.rs | 2 +- .../flashblocks/src/pending_blocks.rs | 2 +- crates/execution/flashblocks/src/processor.rs | 46 ++++-- .../flashblocks/src/state_builder.rs | 4 +- .../execution/flashblocks/src/validation.rs | 154 +++++++++++------- crates/execution/metering/src/collector.rs | 2 +- crates/execution/metering/src/rpc.rs | 5 +- crates/execution/runner/benches/fcu_unsafe.rs | 4 +- .../runner/src/test_utils/harness.rs | 15 +- .../contracts/src/ParentBlockhashGuard.sol | 18 ++ crates/utilities/test-utils/src/contracts.rs | 9 + crates/utilities/test-utils/src/lib.rs | 2 +- 23 files changed, 541 insertions(+), 136 deletions(-) create mode 100644 crates/utilities/test-utils/contracts/src/ParentBlockhashGuard.sol diff --git a/crates/builder/core/src/flashblocks/payload.rs b/crates/builder/core/src/flashblocks/payload.rs index f56d33a88e..d3de077732 100644 --- a/crates/builder/core/src/flashblocks/payload.rs +++ b/crates/builder/core/src/flashblocks/payload.rs @@ -1,7 +1,10 @@ use core::time::Duration; use std::{ ops::{Div, Rem}, - sync::Arc, + sync::{ + Arc, + atomic::{AtomicU64, Ordering}, + }, time::Instant, }; @@ -18,7 +21,8 @@ use base_bundles::RejectedTransaction; use base_common_chains::Upgrades; use base_common_consensus::{BaseReceipt, BaseTransactionSigned}; use base_common_flashblocks::{ - ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblocksPayloadV1, + ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblockId, FlashblocksPayloadV1, + Metadata, }; use base_execution_consensus::{calculate_receipt_root_no_memo, isthmus}; use base_execution_evm::{BaseEvmConfig, BaseNextBlockEnvAttributes}; @@ -71,6 +75,26 @@ type NextBestFlashblocksTxs = BestFlashblocksTxs< >, >; +#[derive(Debug, Default)] +struct LastEmittedFlashblockId { + block_number: AtomicU64, + index: AtomicU64, +} + +impl LastEmittedFlashblockId { + fn load(&self) -> FlashblockId { + FlashblockId { + block_number: self.block_number.load(Ordering::Relaxed), + index: self.index.load(Ordering::Relaxed), + } + } + + fn store(&self, flashblock_id: FlashblockId) { + self.block_number.store(flashblock_id.block_number, Ordering::Relaxed); + self.index.store(flashblock_id.index, Ordering::Relaxed); + } +} + /// Base payload builder #[derive(Debug, Clone)] pub(super) struct BasePayloadBuilder { @@ -90,11 +114,13 @@ pub(super) struct BasePayloadBuilder { pub config: BuilderConfig, /// Sender for forwarding per-block batches of rejected transactions to the audit-archiver. pub rejected_tx_sender: Option>>, + /// Last flashblock emitted by this builder instance. + last_emitted_flashblock_id: Arc, } impl BasePayloadBuilder { /// `BasePayloadBuilder` constructor. - pub(super) const fn new( + pub(super) fn new( evm_config: BaseEvmConfig, pool: Pool, client: Client, @@ -103,7 +129,24 @@ impl BasePayloadBuilder { ws_pub: Arc, rejected_tx_sender: Option>>, ) -> Self { - Self { evm_config, pool, client, payload_tx, ws_pub, config, rejected_tx_sender } + Self { + evm_config, + pool, + client, + payload_tx, + ws_pub, + config, + rejected_tx_sender, + last_emitted_flashblock_id: Arc::default(), + } + } + + fn previous_flashblock_id(&self) -> FlashblockId { + self.last_emitted_flashblock_id.load() + } + + fn record_emitted_flashblock(&self, block_number: u64, index: u64) { + self.last_emitted_flashblock_id.store(FlashblockId { block_number, index }); } } @@ -257,10 +300,12 @@ where let skip_flashblocks_building = ctx.attributes().no_tx_pool || flashblocks_per_block == 0; + let prev_flashblock_id = self.previous_flashblock_id(); let (payload, fb_payload) = build_block( &mut state, &ctx, &mut info, + prev_flashblock_id, skip_flashblocks_building, // need to calculate state root for CL sync or if not building flashblocks )?; @@ -285,6 +330,7 @@ where .ws_pub .publish(&fb_payload, ctx.block_number(), 0) .map_err(PayloadBuilderError::other)?; + self.record_emitted_flashblock(ctx.block_number(), 0); BuilderMetrics::flashblock_byte_size_histogram().record(flashblock_byte_size as f64); BuilderMetrics::first_flashblock_time_offset() .record(first_flashblock_offset.as_millis() as f64); @@ -643,7 +689,9 @@ where .set(payload_transaction_simulation_time); let total_block_built_duration = Instant::now(); - let build_result = build_block(state, ctx, info, ctx.attributes().no_tx_pool); + let prev_flashblock_id = self.previous_flashblock_id(); + let build_result = + build_block(state, ctx, info, prev_flashblock_id, ctx.attributes().no_tx_pool); let total_block_built_duration = total_block_built_duration.elapsed(); BuilderMetrics::total_block_built_duration().record(total_block_built_duration); BuilderMetrics::total_block_built_gauge().set(total_block_built_duration); @@ -671,6 +719,7 @@ where .ws_pub .publish(&fb_payload, ctx.block_number(), flashblock_index) .wrap_err("failed to publish flashblock via websocket")?; + self.record_emitted_flashblock(ctx.block_number(), flashblock_index); (false, size) } }; @@ -822,7 +871,7 @@ where let start_time = Instant::now(); // Build the final block WITH state root computed - let (final_payload, _) = build_block(state, ctx, info, true)?; + let (final_payload, _) = build_block(state, ctx, info, FlashblockId::default(), true)?; ctx.flush_rejected_txs(info); @@ -904,12 +953,13 @@ where #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize)] struct FlashblocksMetadata { + /// Metadata fields consumed by flashblock clients. + #[serde(flatten)] + metadata: Metadata, /// Receipts for transactions in this flashblock (removed in Base 1.0) receipts: Option>, /// Changed account balances (removed in Base 1.0) new_account_balances: Option>, - /// The block number this flashblock belongs to - block_number: u64, /// The flashblock access list access_list: Option, } @@ -937,6 +987,7 @@ pub(crate) fn build_block( state: &mut State, ctx: &BasePayloadBuilderCtx, info: &mut ExecutionInfo, + prev_flashblock_id: FlashblockId, calculate_state_root: bool, ) -> Result<(BaseBuiltPayload, FlashblocksPayloadV1), PayloadBuilderError> where @@ -1112,14 +1163,14 @@ where let metadata: FlashblocksMetadata = if ctx.chain_spec.is_azul_active_at_timestamp(ctx.attributes().timestamp()) { FlashblocksMetadata { - block_number: ctx.parent().number + 1, + metadata: Metadata { block_number: ctx.parent().number + 1, prev_flashblock_id }, access_list: None, receipts: None, new_account_balances: None, } } else { FlashblocksMetadata { - block_number: ctx.parent().number + 1, + metadata: Metadata { block_number: ctx.parent().number + 1, prev_flashblock_id }, access_list: None, new_account_balances: Some(new_account_balances), receipts: Some(receipts_with_hash), @@ -1186,7 +1237,7 @@ mod tests { use alloy_consensus::{Header, Receipt}; use alloy_primitives::{Address, B256, Log, U256, map::foldhash::HashMap}; use base_common_consensus::BaseReceipt; - use base_common_flashblocks::Metadata; + use base_common_flashblocks::{FlashblockId, Metadata}; use base_execution_chainspec::BaseChainSpec; use reth_chainspec::ChainSpec; use reth_primitives_traits::SealedHeader; @@ -1238,9 +1289,14 @@ mod tests { let mut state = State::builder().with_database(db).with_bundle_update().build(); let mut info = ExecutionInfo::default(); - let (payload, fb_payload) = - build_block::<_, NoopProvider>(&mut state, &ctx, &mut info, false) - .expect("build_block should succeed for an empty block"); + let (payload, fb_payload) = build_block::<_, NoopProvider>( + &mut state, + &ctx, + &mut info, + FlashblockId::default(), + false, + ) + .expect("build_block should succeed for an empty block"); // Block number must be parent + 1. assert_eq!(payload.block().number, parent.number + 1, "block number should be parent + 1"); @@ -1270,9 +1326,14 @@ mod tests { let mut state = State::builder().with_database(db).with_bundle_update().build(); let mut info = ExecutionInfo::default(); - let (payload, _fb_payload) = - build_block::<_, NoopProvider>(&mut state, &ctx, &mut info, true) - .expect("build_block with state root should succeed"); + let (payload, _fb_payload) = build_block::<_, NoopProvider>( + &mut state, + &ctx, + &mut info, + FlashblockId::default(), + true, + ) + .expect("build_block with state root should succeed"); // NoopProvider returns B256::default() for all state root queries, // which equals B256::ZERO. @@ -1306,8 +1367,14 @@ mod tests { let mut state = State::builder().with_database(db).with_bundle_update().build(); let mut info = ExecutionInfo::default(); - let err = build_block::<_, NoopProvider>(&mut state, &ctx, &mut info, false) - .expect_err("build_block should fail on block number mismatch"); + let err = build_block::<_, NoopProvider>( + &mut state, + &ctx, + &mut info, + FlashblockId::default(), + false, + ) + .expect_err("build_block should fail on block number mismatch"); let msg = err.to_string(); assert!( @@ -1335,8 +1402,14 @@ mod tests { let mut state = State::builder().with_database(db).with_bundle_update().build(); let mut info = ExecutionInfo::default(); - let err = build_block::<_, NoopProvider>(&mut state, &ctx, &mut info, false) - .expect_err("build_block should fail without beacon block root"); + let err = build_block::<_, NoopProvider>( + &mut state, + &ctx, + &mut info, + FlashblockId::default(), + false, + ) + .expect_err("build_block should fail without beacon block root"); let msg = err.to_string(); assert!( @@ -1370,7 +1443,10 @@ mod tests { let metadata = FlashblocksMetadata { receipts: Some(receipts), new_account_balances: Some(balances), - block_number: 42, + metadata: Metadata { + block_number: 42, + prev_flashblock_id: FlashblockId { block_number: 41, index: 10 }, + }, access_list: None, }; @@ -1382,12 +1458,13 @@ mod tests { keys.sort(); assert_eq!( keys, - vec!["block_number", "new_account_balances", "receipts"], + vec!["block_number", "new_account_balances", "prev_flashblock_id", "receipts",], "metadata field names changed" ); // block_number is a plain integer, not hex-encoded assert_eq!(obj["block_number"], serde_json::json!(42)); + assert_eq!(obj["prev_flashblock_id"], serde_json::json!("41-10")); // receipts is a map keyed by tx hash let receipts_obj = obj["receipts"].as_object().unwrap(); @@ -1411,7 +1488,10 @@ mod tests { let metadata = FlashblocksMetadata { receipts: Some(HashMap::default()), new_account_balances: Some(HashMap::default()), - block_number: 99, + metadata: Metadata { + block_number: 99, + prev_flashblock_id: FlashblockId { block_number: 98, index: 10 }, + }, access_list: None, }; @@ -1419,6 +1499,10 @@ mod tests { let client_metadata: Metadata = serde_json::from_value(json).expect("client Metadata must parse builder metadata"); assert_eq!(client_metadata.block_number, 99); + assert_eq!( + client_metadata.prev_flashblock_id, + FlashblockId { block_number: 98, index: 10 } + ); } /// The v0.4.1 metadata format (only `block_number`) must still deserialize @@ -1428,5 +1512,6 @@ mod tests { let json = serde_json::json!({"block_number": 123}); let metadata: Metadata = serde_json::from_value(json).expect("v0.4.1 metadata must parse"); assert_eq!(metadata.block_number, 123); + assert_eq!(metadata.prev_flashblock_id, FlashblockId::default()); } } diff --git a/crates/common/flashblocks/src/lib.rs b/crates/common/flashblocks/src/lib.rs index 66aae21fda..f9d6a50132 100644 --- a/crates/common/flashblocks/src/lib.rs +++ b/crates/common/flashblocks/src/lib.rs @@ -14,7 +14,7 @@ mod error; pub use error::FlashblockDecodeError; mod metadata; -pub use metadata::Metadata; +pub use metadata::{FlashblockId, FlashblockIdParseError, Metadata}; mod payload; pub use payload::{ diff --git a/crates/common/flashblocks/src/metadata.rs b/crates/common/flashblocks/src/metadata.rs index cd9e161a06..f55935dddf 100644 --- a/crates/common/flashblocks/src/metadata.rs +++ b/crates/common/flashblocks/src/metadata.rs @@ -1,10 +1,147 @@ //! Contains the [`Metadata`] type used in Flashblocks. -use serde::{Deserialize, Serialize}; +use std::{fmt, num::ParseIntError, str::FromStr}; + +use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; +use thiserror::Error; + +/// Identifies a flashblock within a canonical block build. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct FlashblockId { + /// Canonical block number this flashblock belongs to. + pub block_number: u64, + /// Index of the flashblock within the block. + pub index: u64, +} + +/// Error returned when parsing a [`FlashblockId`] from its compact string form. +#[derive(Debug, Error)] +pub enum FlashblockIdParseError { + /// The input did not use the expected `-` format. + #[error("invalid flashblock id '{value}': expected '-' format")] + InvalidFormat { + /// Original input value. + value: String, + }, + /// The block number component was not a valid integer. + #[error("invalid flashblock id '{value}': block number '{block_number}' must be an integer")] + InvalidBlockNumber { + /// Original input value. + value: String, + /// Block number component. + block_number: String, + /// Parse error for the block number component. + source: ParseIntError, + }, + /// The flashblock index component was not a valid integer. + #[error("invalid flashblock id '{value}': index '{index}' must be an integer")] + InvalidIndex { + /// Original input value. + value: String, + /// Index component. + index: String, + /// Parse error for the index component. + source: ParseIntError, + }, +} + +impl fmt::Display for FlashblockId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}-{}", self.block_number, self.index) + } +} + +impl FromStr for FlashblockId { + type Err = FlashblockIdParseError; + + fn from_str(value: &str) -> Result { + let Some((block_number, index)) = value.split_once('-') else { + return Err(FlashblockIdParseError::InvalidFormat { value: value.to_owned() }); + }; + + Ok(Self { + block_number: block_number.parse().map_err(|source| { + FlashblockIdParseError::InvalidBlockNumber { + value: value.to_owned(), + block_number: block_number.to_owned(), + source, + } + })?, + index: index.parse().map_err(|source| FlashblockIdParseError::InvalidIndex { + value: value.to_owned(), + index: index.to_owned(), + source, + })?, + }) + } +} + +impl From for String { + fn from(value: FlashblockId) -> Self { + value.to_string() + } +} + +impl Serialize for FlashblockId { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for FlashblockId { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + String::deserialize(deserializer)?.parse().map_err(de::Error::custom) + } +} /// Metadata associated with a flashblock. #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Default)] pub struct Metadata { /// Block number this flashblock belongs to. pub block_number: u64, + /// Identifier of the previously emitted flashblock. + #[serde(default)] + pub prev_flashblock_id: FlashblockId, +} + +impl Metadata { + /// Creates metadata for legacy tests and callers that only need a block number. + pub const fn new(block_number: u64) -> Self { + Self { block_number, prev_flashblock_id: FlashblockId { block_number: 0, index: 0 } } + } +} + +#[cfg(test)] +mod tests { + use super::{FlashblockId, Metadata}; + + #[test] + fn flashblock_id_serializes_as_compact_key() { + let json = serde_json::to_value(FlashblockId { block_number: 123, index: 4 }) + .expect("flashblock id should serialize"); + + assert_eq!(json, serde_json::json!("123-4")); + } + + #[test] + fn flashblock_id_deserializes_from_compact_key() { + let id: FlashblockId = serde_json::from_value(serde_json::json!("123-4")) + .expect("flashblock id should deserialize"); + + assert_eq!(id, FlashblockId { block_number: 123, index: 4 }); + } + + #[test] + fn metadata_deserializes_missing_flashblock_id_as_default() { + let metadata: Metadata = serde_json::from_value(serde_json::json!({"block_number": 123})) + .expect("legacy metadata should deserialize"); + + assert_eq!(metadata.prev_flashblock_id, FlashblockId::default()); + } } diff --git a/crates/execution/engine-tree/src/cached_execution.rs b/crates/execution/engine-tree/src/cached_execution.rs index 7a9c7eff85..5b78807245 100644 --- a/crates/execution/engine-tree/src/cached_execution.rs +++ b/crates/execution/engine-tree/src/cached_execution.rs @@ -334,7 +334,7 @@ mod tests { withdrawals_root: B256::ZERO, blob_gas_used: None, }, - metadata: Metadata { block_number }, + metadata: Metadata::new(block_number), } } diff --git a/crates/execution/flashblocks-node/benches/pending_state.rs b/crates/execution/flashblocks-node/benches/pending_state.rs index 47090fdebb..bfd08301c5 100644 --- a/crates/execution/flashblocks-node/benches/pending_state.rs +++ b/crates/execution/flashblocks-node/benches/pending_state.rs @@ -240,7 +240,7 @@ fn base_flashblock( transactions: vec![BLOCK_INFO_TXN], blob_gas_used: Default::default(), }, - metadata: Metadata { block_number }, + metadata: Metadata::new(block_number), } } @@ -272,7 +272,7 @@ fn transaction_flashblock( transactions: tx_bytes, blob_gas_used: Default::default(), }, - metadata: Metadata { block_number }, + metadata: Metadata::new(block_number), } } diff --git a/crates/execution/flashblocks-node/src/test_harness.rs b/crates/execution/flashblocks-node/src/test_harness.rs index 087d806a22..a07ece181b 100644 --- a/crates/execution/flashblocks-node/src/test_harness.rs +++ b/crates/execution/flashblocks-node/src/test_harness.rs @@ -14,7 +14,7 @@ use std::{ time::Duration, }; -use alloy_consensus::{Receipt, Transaction}; +use alloy_consensus::{BlockHeader, Receipt, Transaction}; use alloy_eips::{BlockHashOrNumber, Decodable2718, Encodable2718}; use alloy_primitives::{Address, B256, BlockNumber, Bytes, U256, hex::FromHex, map::HashMap}; use alloy_rpc_types_engine::PayloadId; @@ -288,6 +288,11 @@ impl FlashblocksHarness { Self::with_options(false).await } + /// Get a reference to the inner [`TestHarness`] + pub const fn inner(&self) -> &TestHarness { + &self.inner + } + /// Get a handle to the in-memory Flashblocks state backing the harness. pub fn flashblocks_state(&self) -> Arc { self.parts.state() @@ -358,6 +363,11 @@ impl FlashblocksBuilderTestHarness { Self { node, provider, flashblocks } } + /// Get a reference to the inner [`FlashblocksHarness`] + pub const fn inner(&self) -> &FlashblocksHarness { + &self.node + } + /// Decode a private key from an account. pub fn decode_private_key(account: Account) -> B256 { B256::from_hex(account.private_key()).expect("valid hex-encoded key") @@ -577,7 +587,9 @@ impl<'a> FlashblockBuilder<'a> { let base = if self.index == 0 { Some(ExecutionPayloadBaseV1 { - parent_beacon_block_root: current_block.hash(), + parent_beacon_block_root: current_block + .parent_beacon_block_root() + .unwrap_or_default(), parent_hash: current_block.hash(), fee_recipient: Address::random(), prev_randao: B256::random(), @@ -611,7 +623,7 @@ impl<'a> FlashblockBuilder<'a> { transactions, blob_gas_used: Default::default(), }, - metadata: Metadata { block_number: canonical_block_num }, + metadata: Metadata::new(canonical_block_num), } } } diff --git a/crates/execution/flashblocks-node/tests/eip7702_tests.rs b/crates/execution/flashblocks-node/tests/eip7702_tests.rs index 9210b820d7..f27e4e49bc 100644 --- a/crates/execution/flashblocks-node/tests/eip7702_tests.rs +++ b/crates/execution/flashblocks-node/tests/eip7702_tests.rs @@ -140,7 +140,7 @@ fn create_base_flashblock(setup: &TestSetup) -> Flashblock { transactions: vec![L1_BLOCK_INFO_DEPOSIT_TX, setup.account_deploy_tx.clone()], ..Default::default() }, - metadata: Metadata { block_number: 1 }, + metadata: Metadata::new(1), } } @@ -160,7 +160,7 @@ fn create_eip7702_flashblock(eip7702_tx: Bytes, cumulative_gas: u64) -> Flashblo logs_bloom: Default::default(), withdrawals_root: Default::default(), }, - metadata: Metadata { block_number: 1 }, + metadata: Metadata::new(1), } } @@ -265,7 +265,7 @@ async fn test_eip7702_multiple_delegations_same_flashblock() -> Result<()> { logs_bloom: Default::default(), withdrawals_root: Default::default(), }, - metadata: Metadata { block_number: 1 }, + metadata: Metadata::new(1), }; setup.send_flashblock(flashblock).await?; @@ -383,7 +383,7 @@ async fn test_eip7702_delegation_then_execution() -> Result<()> { logs_bloom: Default::default(), withdrawals_root: Default::default(), }, - metadata: Metadata { block_number: 1 }, + metadata: Metadata::new(1), }; setup.send_flashblock(execution_flashblock).await?; diff --git a/crates/execution/flashblocks-node/tests/eth_call_erc20.rs b/crates/execution/flashblocks-node/tests/eth_call_erc20.rs index 341e52343d..01459bb5cf 100644 --- a/crates/execution/flashblocks-node/tests/eth_call_erc20.rs +++ b/crates/execution/flashblocks-node/tests/eth_call_erc20.rs @@ -90,7 +90,7 @@ impl Erc20TestSetup { transactions: vec![L1_BLOCK_INFO_DEPOSIT_TX], ..Default::default() }, - metadata: Metadata { block_number: 1 }, + metadata: Metadata::new(1), } } @@ -116,7 +116,7 @@ impl Erc20TestSetup { logs_bloom: Default::default(), withdrawals_root: Default::default(), }, - metadata: Metadata { block_number: 1 }, + metadata: Metadata::new(1), } } @@ -137,7 +137,7 @@ impl Erc20TestSetup { logs_bloom: Default::default(), withdrawals_root: Default::default(), }, - metadata: Metadata { block_number: 1 }, + metadata: Metadata::new(1), } } diff --git a/crates/execution/flashblocks-node/tests/flashblocks_rpc.rs b/crates/execution/flashblocks-node/tests/flashblocks_rpc.rs index 017d946664..2ef6b3eec6 100644 --- a/crates/execution/flashblocks-node/tests/flashblocks_rpc.rs +++ b/crates/execution/flashblocks-node/tests/flashblocks_rpc.rs @@ -3,27 +3,37 @@ use std::str::FromStr; use DoubleCounter::DoubleCounterInstance; +use ParentBlockhashGuard::ParentBlockhashGuardInstance; use alloy_consensus::{Transaction, constants::EMPTY_WITHDRAWALS}; -use alloy_eips::{BlockNumberOrTag, eip7685::EMPTY_REQUESTS_HASH}; +use alloy_eips::{ + BlockHashOrNumber, BlockNumberOrTag, Decodable2718, eip7685::EMPTY_REQUESTS_HASH, +}; use alloy_network::{ReceiptResponse, TransactionResponse}; use alloy_primitives::{Address, B256, Bytes, TxHash, U256, address, b256, bytes}; -use alloy_provider::Provider; +use alloy_provider::{MulticallItem, Provider}; 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_common_consensus::{BaseTransactionSigned, BaseTxEnvelope}; use base_common_flashblocks::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; use base_common_network::Base; use base_common_rpc_types::BaseTransactionRequest; -use base_flashblocks_node::test_harness::FlashblocksHarness; +use base_flashblocks::{FlashblocksAPI, PendingBlocksAPI}; +use base_flashblocks_node::test_harness::{ + FlashblockBuilder, FlashblocksBuilderTestHarness, FlashblocksHarness, +}; use base_node_runner::test_utils::L1_BLOCK_INFO_DEPOSIT_TX; -use base_test_utils::{Account, DoubleCounter}; +use base_test_utils::{Account, DoubleCounter, ParentBlockhashGuard}; use eyre::Result; use futures::{SinkExt, StreamExt}; +use reth_primitives_traits::Block; +use reth_provider::BlockReader; use reth_revm::context::TransactionType; use reth_rpc_eth_api::RpcReceipt; +use reth_transaction_pool::test_utils::TransactionBuilder; use serde_json::json; use tokio_tungstenite::{connect_async, tungstenite::Message}; @@ -286,7 +296,7 @@ impl TestSetup { withdrawals_root: EMPTY_WITHDRAWALS, ..Default::default() }, - metadata: Metadata { block_number: 1 }, + metadata: Metadata::new(1), } } @@ -318,7 +328,7 @@ impl TestSetup { logs_bloom: Default::default(), withdrawals_root: EMPTY_WITHDRAWALS, }, - metadata: Metadata { block_number: 1 }, + metadata: Metadata::new(1), } } @@ -505,6 +515,80 @@ async fn test_get_transaction_receipt_pending() -> Result<()> { Ok(()) } +#[tokio::test] +async fn test_blockhash_dependent_transaction_receipt_pending() -> Result<()> { + let mut builder_setup = FlashblocksBuilderTestHarness::new().await; + let mut client_setup = FlashblocksBuilderTestHarness::new().await; + + let builder_provider = builder_setup.node.provider(); + let client_provider = client_setup.node.provider(); + + // Step 1. Deploy the ParentBlockhashGuard contract in canon block 1 + let (deployment_tx, guard_address, _) = Account::Deployer + .create_deployment_tx(ParentBlockhashGuard::BYTECODE.clone(), 0) + .expect("should be able to sign ParentBlockhashGuard deployment txn"); + let deployment_tx = BaseTxEnvelope::decode_2718(&mut deployment_tx.as_ref())?; + builder_setup.new_canonical_block_without_processing(vec![deployment_tx.clone()]).await; + assert_eq!(builder_provider.get_block_number().await?, 1); + client_setup.new_canonical_block_without_processing(vec![deployment_tx]).await; + assert_eq!(client_provider.get_block_number().await?, 1); + + // Step 2. Random flashblocks for canon block 2 + // Created by builder_setup, but inserted into client_setup + let block_two_fb_base = FlashblockBuilder::new_base(&builder_setup).build(); + let block_two_fb_one = FlashblockBuilder::new(&builder_setup, 1).build(); + let block_two_fb_two = FlashblockBuilder::new(&builder_setup, 2).build(); + client_setup.send_flashblock(block_two_fb_base.clone()).await; + client_setup.send_flashblock(block_two_fb_one).await; + client_setup.send_flashblock(block_two_fb_two).await; + builder_setup + .node + .build_block_from_transactions(block_two_fb_base.diff.transactions) + .await + .expect("able to build canon block 2"); + let canon_block_two = builder_setup + .provider + .block(BlockHashOrNumber::Number(2)) + .expect("able to load block 2") + .expect("block 2 should be available") + .try_into_recovered() + .expect("able to recover block 2"); + + // Step 3. Builder builds flashblocks for block 3 + // that contain a call to the contract + // It is processed by client before client has canon block 2 + let guard = ParentBlockhashGuardInstance::new(guard_address, builder_provider.clone()); + let txn = TransactionBuilder::default() + .signer(Account::Deployer.signer_b256()) + .chain_id(builder_provider.get_chain_id().await?) + .to(guard_address) + .nonce(1) + .input(guard.succeedsOnlyWhenParentBlockHashIsCanonical(canon_block_two.hash()).input()) + .gas_limit(200_000) + .max_fee_per_gas(1_000_000_000) + .max_priority_fee_per_gas(1_000_000_000) + .into_eip1559() + .as_eip1559() + .unwrap() + .clone(); + let flashblock = FlashblockBuilder::new_base(&builder_setup).build(); + client_setup.send_flashblock(flashblock).await; + client_setup.flashblocks.on_canonical_block_received(canon_block_two); + let flashblock = FlashblockBuilder::new(&builder_setup, 1) + .with_transactions(vec![BaseTransactionSigned::Eip1559(txn.clone())]) + .build(); + client_setup.send_flashblock(flashblock).await; + + let receipt = client_setup + .flashblocks + .get_pending_blocks() + .get_transaction_receipt(*txn.hash()) + .expect("blockhash-dependent receipt expected"); + assert!(!receipt.status(), "transaction should see the canonical parent block hash and revert"); + + Ok(()) +} + #[tokio::test] async fn test_get_transaction_count() -> Result<()> { let setup = TestSetup::new().await?; diff --git a/crates/execution/flashblocks-node/tests/state.rs b/crates/execution/flashblocks-node/tests/state.rs index 49f48b8f80..459cee3a61 100644 --- a/crates/execution/flashblocks-node/tests/state.rs +++ b/crates/execution/flashblocks-node/tests/state.rs @@ -752,7 +752,7 @@ async fn test_progress_canonical_blocks_without_flashblocks() { let block_one = test.node.latest_block(); assert_eq!(block_one.number, 1); - assert_eq!(block_one.transaction_count(), 2); + assert_eq!(block_one.transaction_count(), 1); assert!(test.flashblocks.get_pending_blocks().get_block(true).is_none()); test.new_canonical_block(vec![ @@ -763,7 +763,7 @@ async fn test_progress_canonical_blocks_without_flashblocks() { let block_two = test.node.latest_block(); assert_eq!(block_two.number, 2); - assert_eq!(block_two.transaction_count(), 3); + assert_eq!(block_two.transaction_count(), 2); assert!(test.flashblocks.get_pending_blocks().get_block(true).is_none()); } diff --git a/crates/execution/flashblocks/src/block_assembler.rs b/crates/execution/flashblocks/src/block_assembler.rs index 0c555bd446..dc75b4aabe 100644 --- a/crates/execution/flashblocks/src/block_assembler.rs +++ b/crates/execution/flashblocks/src/block_assembler.rs @@ -202,7 +202,7 @@ mod tests { withdrawals_root: B256::ZERO, blob_gas_used: None, }, - metadata: Metadata { block_number: 100 }, + metadata: Metadata::new(100), } } diff --git a/crates/execution/flashblocks/src/cache.rs b/crates/execution/flashblocks/src/cache.rs index 56bdab29e5..331a9c6471 100644 --- a/crates/execution/flashblocks/src/cache.rs +++ b/crates/execution/flashblocks/src/cache.rs @@ -109,7 +109,7 @@ mod tests { index, base: None, diff: ExecutionPayloadFlashblockDeltaV1::default(), - metadata: Metadata { block_number }, + metadata: Metadata::new(block_number), } } diff --git a/crates/execution/flashblocks/src/pending_blocks.rs b/crates/execution/flashblocks/src/pending_blocks.rs index a754ec3a7a..13f5e4bf2a 100644 --- a/crates/execution/flashblocks/src/pending_blocks.rs +++ b/crates/execution/flashblocks/src/pending_blocks.rs @@ -893,7 +893,7 @@ mod tests { withdrawals_root: B256::ZERO, blob_gas_used: None, }, - metadata: Metadata { block_number }, + metadata: Metadata::new(block_number), } } diff --git a/crates/execution/flashblocks/src/processor.rs b/crates/execution/flashblocks/src/processor.rs index 3192fa499e..0ecc91f915 100644 --- a/crates/execution/flashblocks/src/processor.rs +++ b/crates/execution/flashblocks/src/processor.rs @@ -350,6 +350,7 @@ where pending_blocks.latest_flashblock_index(), flashblock.metadata.block_number, flashblock.index, + flashblock.metadata.prev_flashblock_id, ); match validation_result { @@ -380,13 +381,29 @@ where self.clear_live_state(); Ok(None) } - SequenceValidationResult::NonSequentialGap { expected: _, actual: _ } => { - // We have received a non-sequential Flashblock for the current block + SequenceValidationResult::NonSequentialGap { expected, actual } => { Metrics::unexpected_block_order().increment(1); error!( - message = "Received non-sequential Flashblock for current block, zeroing Flashblocks until we receive a base Flashblock", curr_block = %pending_blocks.latest_block_number(), + expected_flashblock_index = %expected, + actual_flashblock_index = %actual, + "received non-sequential flashblock index for current block" + ); + self.clear_live_state(); + Ok(None) + } + SequenceValidationResult::NonSequentialPredecessor { expected, actual } => { + Metrics::unexpected_block_order().increment(1); + error!( + curr_block = %pending_blocks.latest_block_number(), + curr_flashblock_index = %pending_blocks.latest_flashblock_index(), new_block = %flashblock.metadata.block_number, + new_flashblock_index = %flashblock.index, + expected_prev_block = %expected.block_number, + expected_prev_index = %expected.index, + actual_prev_block = %actual.block_number, + actual_prev_index = %actual.index, + "received flashblock with non-sequential predecessor link" ); self.clear_live_state(); Ok(None) @@ -412,7 +429,7 @@ where let latest_flashblock_tx_start = prev_pending_blocks.pending_transaction_count(); let mut live_state = self.lock_live_state(); - let Some(LivePendingState { db, state_overrides }) = live_state.take() else { + let Some(LivePendingState { mut db, state_overrides }) = live_state.take() else { warn!( message = "live pending state unavailable, falling back to full rebuild", block_number = flashblock.metadata.block_number, @@ -431,6 +448,8 @@ where let latest_block_header = BlockAssembler::refresh_same_block_header(&latest_header, &latest_block_flashblocks)?; + db.block_hashes.insert(latest_block_base.block_number - 1, latest_block_base.parent_hash); + let evm_config = BaseEvmConfig::base(self.client.chain_spec()); let evm_env = evm_config .evm_env(&latest_header) @@ -440,6 +459,7 @@ where let previous_block_transaction_count = prev_pending_blocks.latest_block_transaction_count(); let pending_block = Block { header: Header { + parent_hash: latest_block_base.parent_hash, number: latest_block_base.block_number, timestamp: latest_block_base.timestamp, gas_limit: latest_block_base.gas_limit, @@ -549,7 +569,7 @@ where }; let mut live_state = self.lock_live_state(); - let Some(LivePendingState { db, state_overrides }) = live_state.take() else { + let Some(LivePendingState { mut db, state_overrides }) = live_state.take() else { warn!( message = "live pending state unavailable, falling back to full rebuild", block_number = flashblock.metadata.block_number, @@ -568,6 +588,7 @@ where let AssembledBlock { block: assembled_block, header: assembled_header, .. } = current_block; let pending_block = Block { header: Header { + parent_hash: base.parent_hash, number: base.block_number, timestamp: base.timestamp, gas_limit: base.gas_limit, @@ -577,6 +598,8 @@ where body: assembled_block.body, }; + db.block_hashes.insert(base.block_number - 1, base.parent_hash); + let evm_config = BaseEvmConfig::base(self.client.chain_spec()); let block_env_attributes = BaseNextBlockEnvAttributes { timestamp: base.timestamp, @@ -616,10 +639,8 @@ where l1_block_info.clone(), state_overrides, ); - pending_state_builder.apply_pre_execution_changes( - previous_header.hash_slow(), - Some(base.parent_beacon_block_root), - )?; + pending_state_builder + .apply_pre_execution_changes(base.parent_hash, Some(base.parent_beacon_block_root))?; for (idx, (transaction, sender)) in txs_with_senders.into_iter().enumerate() { let tx_hash = transaction.tx_hash(); @@ -727,6 +748,9 @@ where extra_data: assembled.base.extra_data.clone(), }; + db.block_hashes + .insert(latest_block_base.block_number - 1, latest_block_base.parent_hash); + let evm_env = evm_config .next_evm_env(&last_block_header, &block_env_attributes) .map_err(|e| ExecutionError::EvmEnv(e.to_string()))?; @@ -757,7 +781,7 @@ where // Clone header before moving block to avoid cloning the entire block let block_header = assembled.block.header.clone(); - let parent_hash = last_block_header.hash_slow(); + let parent_block_hash = assembled.base.parent_hash; let parent_beacon_block_root = Some(assembled.base.parent_beacon_block_root); let mut pending_state_builder = PendingStateBuilder::new( @@ -770,7 +794,7 @@ where ); pending_state_builder - .apply_pre_execution_changes(parent_hash, parent_beacon_block_root)?; + .apply_pre_execution_changes(parent_block_hash, parent_beacon_block_root)?; for (idx, (transaction, sender)) in txs_with_senders.into_iter().enumerate() { let tx_hash = transaction.tx_hash(); diff --git a/crates/execution/flashblocks/src/state_builder.rs b/crates/execution/flashblocks/src/state_builder.rs index 660ad1c756..3d51bd0c41 100644 --- a/crates/execution/flashblocks/src/state_builder.rs +++ b/crates/execution/flashblocks/src/state_builder.rs @@ -636,7 +636,7 @@ mod tests { withdrawals_root: B256::ZERO, blob_gas_used: None, }, - metadata: Metadata { block_number: header.number }, + metadata: Metadata::new(header.number), }]); pending_blocks_builder.with_receipt(tx_hash, first_result.receipt.clone()); pending_blocks_builder.with_transaction_state(tx_hash, first_result.state.clone()); @@ -926,7 +926,7 @@ mod tests { withdrawals_root: B256::ZERO, blob_gas_used: None, }, - metadata: Metadata { block_number: header.number }, + metadata: Metadata::new(header.number), }]); pending_blocks_builder.with_transaction_sender(tx_a_hash, sender); pending_blocks_builder.with_receipt(tx_a_hash, first_result.receipt.clone()); diff --git a/crates/execution/flashblocks/src/validation.rs b/crates/execution/flashblocks/src/validation.rs index 7d5607a9ad..4a9a43ba65 100644 --- a/crates/execution/flashblocks/src/validation.rs +++ b/crates/execution/flashblocks/src/validation.rs @@ -3,6 +3,7 @@ //! Provides stateless validation logic for flashblock sequencing and chain reorg detection. use alloy_primitives::B256; +use base_common_flashblocks::FlashblockId; /// Result of validating a flashblock's position in the sequence. #[derive(Debug, Clone, PartialEq, Eq)] @@ -27,6 +28,13 @@ pub enum SequenceValidationResult { /// The invalid (non-zero) index received. index: u64, }, + /// Incoming flashblock does not link back to the currently tracked latest flashblock. + NonSequentialPredecessor { + /// Expected predecessor flashblock id. + expected: FlashblockId, + /// Actual predecessor flashblock id reported by the incoming flashblock. + actual: FlashblockId, + }, } /// Stateless validator for flashblock sequence ordering. @@ -34,43 +42,53 @@ pub enum SequenceValidationResult { pub struct FlashblockSequenceValidator; impl FlashblockSequenceValidator { - /// Validates whether an incoming flashblock follows the expected sequence. - /// - /// Returns the appropriate [`SequenceValidationResult`] based on: - /// - Same block, index + 1 → `NextInSequence` - /// - Next block, index 0 → `FirstOfNextBlock` - /// - Same block and index → `Duplicate` - /// - Same block, wrong index → `NonSequentialGap` - /// - Different block, non-zero index or block gap → `InvalidNewBlockIndex` - pub const fn validate( + /// Validates whether an incoming flashblock links to the current latest flashblock. + pub fn validate( latest_block_number: u64, latest_flashblock_index: u64, incoming_block_number: u64, incoming_index: u64, + incoming_prev_flashblock_id: FlashblockId, ) -> SequenceValidationResult { - // Next flashblock within the current block - if incoming_block_number == latest_block_number - && incoming_index == latest_flashblock_index + 1 + let latest_flashblock_id = + FlashblockId { block_number: latest_block_number, index: latest_flashblock_index }; + + if incoming_block_number == latest_block_number && incoming_index == latest_flashblock_index { - SequenceValidationResult::NextInSequence - // First flashblock of the next block - } else if incoming_block_number == latest_block_number + 1 && incoming_index == 0 { - SequenceValidationResult::FirstOfNextBlock - // New block with non-zero index or block gap - } else if incoming_block_number != latest_block_number { - SequenceValidationResult::InvalidNewBlockIndex { - block_number: incoming_block_number, - index: incoming_index, - } - } else if incoming_index == latest_flashblock_index { - // Duplicate flashblock - SequenceValidationResult::Duplicate - } else { - // Non-sequential index within the same block - SequenceValidationResult::NonSequentialGap { - expected: latest_flashblock_index + 1, + return SequenceValidationResult::Duplicate; + } + + // We can remove this `incoming_prev_flashblock_id != FlashblockId::default()` check later + // but it is currently necessary as client nodes may be updated before the builder is + // and they need to be able to handle the lack of `prev_flashblock_id` in the stream + if incoming_prev_flashblock_id != FlashblockId::default() + && incoming_prev_flashblock_id != latest_flashblock_id + { + return SequenceValidationResult::NonSequentialPredecessor { + expected: latest_flashblock_id, + actual: incoming_prev_flashblock_id, + }; + } + + let next_flashblock_index = latest_flashblock_index.saturating_add(1); + if incoming_block_number == latest_block_number && incoming_index == next_flashblock_index { + return SequenceValidationResult::NextInSequence; + } + + if incoming_block_number == latest_block_number + 1 && incoming_index == 0 { + return SequenceValidationResult::FirstOfNextBlock; + } + + if incoming_block_number == latest_block_number { + return SequenceValidationResult::NonSequentialGap { + expected: next_flashblock_index, actual: incoming_index, - } + }; + } + + SequenceValidationResult::InvalidNewBlockIndex { + block_number: incoming_block_number, + index: incoming_index, } } } @@ -197,37 +215,60 @@ mod tests { // ==================== FlashblockSequenceValidator Tests ==================== #[rstest] - // NextInSequence: consecutive indices within the same block - #[case(100, 2, 100, 3, SequenceValidationResult::NextInSequence)] - #[case(100, 0, 100, 1, SequenceValidationResult::NextInSequence)] - #[case(100, 999, 100, 1000, SequenceValidationResult::NextInSequence)] - #[case(0, 0, 0, 1, SequenceValidationResult::NextInSequence)] - #[case(100, u64::MAX - 1, 100, u64::MAX, SequenceValidationResult::NextInSequence)] - // FirstOfNextBlock: index 0 of the next block - #[case(0, 0, 1, 0, SequenceValidationResult::FirstOfNextBlock)] - #[case(100, 5, 101, 0, SequenceValidationResult::FirstOfNextBlock)] - #[case(100, 0, 101, 0, SequenceValidationResult::FirstOfNextBlock)] - #[case(999999, 10, 1000000, 0, SequenceValidationResult::FirstOfNextBlock)] - #[case(0, 5, 1, 0, SequenceValidationResult::FirstOfNextBlock)] - #[case(u64::MAX - 1, 0, u64::MAX, 0, SequenceValidationResult::FirstOfNextBlock)] - // Duplicate: same block and index - #[case(100, 5, 100, 5, SequenceValidationResult::Duplicate)] - #[case(100, 0, 100, 0, SequenceValidationResult::Duplicate)] - // NonSequentialGap: non-consecutive indices within the same block - #[case(100, 2, 100, 4, SequenceValidationResult::NonSequentialGap { expected: 3, actual: 4 })] - #[case(100, 0, 100, 10, SequenceValidationResult::NonSequentialGap { expected: 1, actual: 10 })] - #[case(100, 5, 100, 3, SequenceValidationResult::NonSequentialGap { expected: 6, actual: 3 })] - // InvalidNewBlockIndex: new block with non-zero index or block gap - #[case(100, 5, 101, 1, SequenceValidationResult::InvalidNewBlockIndex { block_number: 101, index: 1 })] - #[case(100, 5, 105, 3, SequenceValidationResult::InvalidNewBlockIndex { block_number: 105, index: 3 })] - #[case(100, 5, 102, 0, SequenceValidationResult::InvalidNewBlockIndex { block_number: 102, index: 0 })] - #[case(100, 5, 99, 0, SequenceValidationResult::InvalidNewBlockIndex { block_number: 99, index: 0 })] - #[case(100, 5, 99, 5, SequenceValidationResult::InvalidNewBlockIndex { block_number: 99, index: 5 })] + #[case(100, 5, 100, 6, FlashblockId { block_number: 100, index: 5 }, SequenceValidationResult::NextInSequence)] + #[case(100, 5, 100, 6, FlashblockId::default(), SequenceValidationResult::NextInSequence)] + #[case(100, 5, 101, 0, FlashblockId { block_number: 100, index: 5 }, SequenceValidationResult::FirstOfNextBlock)] + #[case(100, 5, 101, 0, FlashblockId::default(), SequenceValidationResult::FirstOfNextBlock)] + #[case(100, 5, 100, 5, FlashblockId::default(), SequenceValidationResult::Duplicate)] + #[case( + 100, + 5, + 100, + 7, + FlashblockId { block_number: 100, index: 5 }, + SequenceValidationResult::NonSequentialGap { expected: 6, actual: 7 } + )] + #[case( + 100, + 5, + 101, + 3, + FlashblockId { block_number: 100, index: 5 }, + SequenceValidationResult::InvalidNewBlockIndex { block_number: 101, index: 3 } + )] + #[case( + 100, + 5, + 105, + 0, + FlashblockId { block_number: 100, index: 5 }, + SequenceValidationResult::InvalidNewBlockIndex { block_number: 105, index: 0 } + )] + #[case( + 100, + 5, + 101, + 0, + FlashblockId { block_number: 100, index: 4 }, + SequenceValidationResult::NonSequentialPredecessor { + expected: FlashblockId { block_number: 100, index: 5 }, + actual: FlashblockId { block_number: 100, index: 4 }, + } + )] + #[case( + 100, + 5, + 99, + 0, + FlashblockId { block_number: 100, index: 5 }, + SequenceValidationResult::InvalidNewBlockIndex { block_number: 99, index: 0 } + )] fn test_sequence_validator( #[case] latest_block: u64, #[case] latest_idx: u64, #[case] incoming_block: u64, #[case] incoming_idx: u64, + #[case] incoming_prev_flashblock_id: FlashblockId, #[case] expected: SequenceValidationResult, ) { let result = FlashblockSequenceValidator::validate( @@ -235,6 +276,7 @@ mod tests { latest_idx, incoming_block, incoming_idx, + incoming_prev_flashblock_id, ); assert_eq!(result, expected); } diff --git a/crates/execution/metering/src/collector.rs b/crates/execution/metering/src/collector.rs index 3433214747..67c3fd3b05 100644 --- a/crates/execution/metering/src/collector.rs +++ b/crates/execution/metering/src/collector.rs @@ -403,7 +403,7 @@ mod tests { withdrawals_root: B256::ZERO, blob_gas_used: None, }, - metadata: Metadata { block_number: 100 }, + metadata: Metadata::new(100), }; builder.with_flashblocks([flashblock]); diff --git a/crates/execution/metering/src/rpc.rs b/crates/execution/metering/src/rpc.rs index a19f475d00..a72f66f45a 100644 --- a/crates/execution/metering/src/rpc.rs +++ b/crates/execution/metering/src/rpc.rs @@ -625,7 +625,7 @@ mod tests { ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; use base_flashblocks::{FlashblocksConfig, PendingBlocksBuilder}; - use base_node_runner::test_utils::TestHarness; + use base_node_runner::test_utils::{L1_BLOCK_INFO_DEPOSIT_TX, TestHarness}; use base_test_utils::Account; use reth_transaction_pool::test_utils::TransactionBuilder; use url::Url; @@ -658,6 +658,7 @@ mod tests { async fn generate_txs_for_block(chain_id: u64) -> Vec { vec![ + L1_BLOCK_INFO_DEPOSIT_TX, TransactionBuilder::default() .signer(Account::Charlie.signer_b256()) .chain_id(chain_id) @@ -1057,7 +1058,7 @@ mod tests { withdrawals_root: B256::ZERO, blob_gas_used: Some(0), }, - metadata: Metadata { block_number: 2 }, + metadata: Metadata::new(2), }; // Build PendingBlocks with zero-hash header diff --git a/crates/execution/runner/benches/fcu_unsafe.rs b/crates/execution/runner/benches/fcu_unsafe.rs index 4d49cf8291..569480df13 100644 --- a/crates/execution/runner/benches/fcu_unsafe.rs +++ b/crates/execution/runner/benches/fcu_unsafe.rs @@ -26,7 +26,7 @@ use std::{ use alloy_provider::Provider; use alloy_rpc_types::BlockNumberOrTag; -use base_node_runner::test_utils::{PreparedBlock, TestHarness}; +use base_node_runner::test_utils::{L1_BLOCK_INFO_DEPOSIT_TX, PreparedBlock, TestHarness}; use criterion::{Criterion, criterion_group, criterion_main}; use tokio::runtime::Runtime; use tracing_subscriber::{EnvFilter, filter::LevelFilter}; @@ -61,7 +61,7 @@ fn fcu_unsafe_benches(c: &mut Criterion) { let mut total = Duration::ZERO; for _ in 0..iters { let PreparedBlock { new_block_hash, .. } = harness - .prepare_unsafe_block(vec![]) + .prepare_unsafe_block(vec![L1_BLOCK_INFO_DEPOSIT_TX]) .await .expect("prepare_unsafe_block should succeed"); diff --git a/crates/execution/runner/src/test_utils/harness.rs b/crates/execution/runner/src/test_utils/harness.rs index 3763bd3507..2b3dcaf58b 100644 --- a/crates/execution/runner/src/test_utils/harness.rs +++ b/crates/execution/runner/src/test_utils/harness.rs @@ -23,8 +23,7 @@ use tokio::time::sleep; use crate::{ BaseNodeExtension, FromExtensionConfig, test_utils::{ - BLOCK_BUILD_DELAY_MS, BLOCK_TIME_SECONDS, GAS_LIMIT, L1_BLOCK_INFO_DEPOSIT_TX, - NODE_STARTUP_DELAY_MS, + BLOCK_BUILD_DELAY_MS, BLOCK_TIME_SECONDS, GAS_LIMIT, NODE_STARTUP_DELAY_MS, engine::{EngineApi, IpcEngine}, node::{LocalNode, LocalNodeProvider}, tracing::init_silenced_tracing, @@ -158,14 +157,7 @@ impl TestHarness { /// Returns the parent hash and the new block hash so callers can issue the /// final FCU themselves — useful for benchmarks that want to time only the /// canonical FCU step. - pub async fn prepare_unsafe_block( - &self, - mut transactions: Vec, - ) -> Result { - if transactions.first().is_none_or(|tx| tx != &L1_BLOCK_INFO_DEPOSIT_TX) { - transactions.insert(0, L1_BLOCK_INFO_DEPOSIT_TX); - } - + pub async fn prepare_unsafe_block(&self, transactions: Vec) -> Result { let latest_block = self .provider() .get_block_by_number(BlockNumberOrTag::Latest) @@ -259,7 +251,8 @@ impl TestHarness { Ok(()) } - async fn wait_for_header(&self, block_hash: B256, block_number: u64) -> Result<()> { + /// Wait for a given block to become available + pub async fn wait_for_header(&self, block_hash: B256, block_number: u64) -> Result<()> { const HEADER_PERSIST_TIMEOUT: Duration = Duration::from_secs(5); const HEADER_PERSIST_POLL_INTERVAL: Duration = Duration::from_millis(10); diff --git a/crates/utilities/test-utils/contracts/src/ParentBlockhashGuard.sol b/crates/utilities/test-utils/contracts/src/ParentBlockhashGuard.sol new file mode 100644 index 0000000000..cd64c180e1 --- /dev/null +++ b/crates/utilities/test-utils/contracts/src/ParentBlockhashGuard.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +contract ParentBlockhashGuard { + error ParentBlockHashWasCanonical(bytes32 actual); + + function succeedsOnlyWhenParentBlockHashIsCanonical( + bytes32 canonicalParentBlockHash + ) external view returns (bytes32) { + bytes32 parentBlockHash = blockhash(block.number - 1); + + if (parentBlockHash == canonicalParentBlockHash) { + revert ParentBlockHashWasCanonical(parentBlockHash); + } + + return parentBlockHash; + } +} diff --git a/crates/utilities/test-utils/src/contracts.rs b/crates/utilities/test-utils/src/contracts.rs index 5e936f6e13..679722ae5c 100644 --- a/crates/utilities/test-utils/src/contracts.rs +++ b/crates/utilities/test-utils/src/contracts.rs @@ -60,6 +60,15 @@ sol!( concat!(env!("CARGO_MANIFEST_DIR"), "/contracts/out/Proxy.sol/Proxy.json") ); +sol!( + #[sol(rpc)] + ParentBlockhashGuard, + concat!( + env!("CARGO_MANIFEST_DIR"), + "/contracts/out/ParentBlockhashGuard.sol/ParentBlockhashGuard.json" + ) +); + sol!( #[sol(rpc)] Logic, diff --git a/crates/utilities/test-utils/src/lib.rs b/crates/utilities/test-utils/src/lib.rs index bfe920a754..e0c7483bcb 100644 --- a/crates/utilities/test-utils/src/lib.rs +++ b/crates/utilities/test-utils/src/lib.rs @@ -18,5 +18,5 @@ pub use genesis::{ mod contracts; pub use contracts::{ AccessListContract, ContractFactory, DoubleCounter, Logic, Logic2, Minimal7702Account, - MockERC20, Proxy, SimpleStorage, TransparentUpgradeableProxy, + MockERC20, ParentBlockhashGuard, Proxy, SimpleStorage, TransparentUpgradeableProxy, }; From 5c14cf36037567d91177c2bf1db3fbbde8a83874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Duchesneau?= Date: Thu, 18 Jun 2026 22:17:04 -0400 Subject: [PATCH 4/5] Adapt firehose flashblocks to v1.1.1 validator change v1.1.1 (PR #3603) added a prev_flashblock_id predecessor-link arg to FlashblockSequenceValidator::validate and a NonSequentialPredecessor result variant. Pass metadata.prev_flashblock_id from the firehose processor, handle the new variant (reset + wait for base), and update test Metadata literals for the added field. --- Cargo.lock | 6 +++--- crates/firehose-flashblocks/src/processor.rs | 14 ++++++++++++++ crates/firehose-flashblocks/tests/framework/mod.rs | 8 ++++---- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ab949c1f7..631262b2a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4538,7 +4538,7 @@ dependencies = [ [[package]] name = "base-execution-firehose" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-evm", @@ -4746,7 +4746,7 @@ dependencies = [ [[package]] name = "base-firehose-flashblocks" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", @@ -4791,7 +4791,7 @@ dependencies = [ [[package]] name = "base-firehose-tests" -version = "1.1.0" +version = "1.1.1" dependencies = [ "alloy-consensus 2.0.5", "alloy-eips 2.0.5", diff --git a/crates/firehose-flashblocks/src/processor.rs b/crates/firehose-flashblocks/src/processor.rs index 5eb059bcde..4fbd967925 100644 --- a/crates/firehose-flashblocks/src/processor.rs +++ b/crates/firehose-flashblocks/src/processor.rs @@ -656,6 +656,7 @@ where latest_idx, block_number, index, + flashblock.metadata.prev_flashblock_id, ) { SequenceValidationResult::NextInSequence => { // A delta must belong to the same payload as the in-flight base — @@ -855,6 +856,19 @@ where state.reset(); return Ok(()); } + SequenceValidationResult::NonSequentialPredecessor { expected, actual } => { + warn!( + block = block_number, + index, + expected_prev_block = expected.block_number, + expected_prev_index = expected.index, + actual_prev_block = actual.block_number, + actual_prev_index = actual.index, + "flashblock predecessor link mismatch; resetting state and waiting for next base" + ); + state.reset(); + return Ok(()); + } } } } diff --git a/crates/firehose-flashblocks/tests/framework/mod.rs b/crates/firehose-flashblocks/tests/framework/mod.rs index d1d53643ec..1d3e8db4a6 100644 --- a/crates/firehose-flashblocks/tests/framework/mod.rs +++ b/crates/firehose-flashblocks/tests/framework/mod.rs @@ -732,7 +732,7 @@ pub(crate) fn flash_base( metadata: json!({ "block_number": block_number }), }; - let metadata = Metadata { block_number }; + let metadata = Metadata { block_number, ..Default::default() }; TestEvent::flashblock(Flashblock { payload_id: payload.payload_id, @@ -779,7 +779,7 @@ pub(crate) fn flash_delta_with_payload_id( blob_gas_used: Some(0), }; - let metadata = Metadata { block_number }; + let metadata = Metadata { block_number, ..Default::default() }; TestEvent::flashblock(Flashblock { payload_id: PayloadId::new(payload_id.to_be_bytes()), @@ -869,7 +869,7 @@ pub(crate) fn flash_base_with_txs( withdrawals_root: B256::ZERO, blob_gas_used: Some(0), }; - let metadata = Metadata { block_number }; + let metadata = Metadata { block_number, ..Default::default() }; TestEvent::flashblock(Flashblock { payload_id: PayloadId::new([0u8; 8]), index: 0, @@ -898,7 +898,7 @@ pub(crate) fn flash_delta_with_txs( withdrawals_root: B256::ZERO, blob_gas_used: Some(0), }; - let metadata = Metadata { block_number }; + let metadata = Metadata { block_number, ..Default::default() }; TestEvent::flashblock(Flashblock { payload_id: PayloadId::new([0u8; 8]), index, From b77bb68bdb2b0211213b7793d370653dacb5f7f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Duchesneau?= Date: Thu, 18 Jun 2026 22:41:47 -0400 Subject: [PATCH 5/5] Add predecessor-link reset tests and merge analysis docs Tests for the v1.1.1 NonSequentialPredecessor path in the firehose flashblocks processor: mismatched prev_flashblock_id resets state, matching id is accepted, default (unset) id stays backward-compatible. Adds flash_delta_with_prev_id test helper. Docs under docs/firehose/ answer four review questions: the reset tests, Dockerfile.sf build status, Beryl block-model impact, and the pending-EVM parent-hash fix. --- .../tests/flashblock_sequence.rs | 114 +++++++++++++++++- .../tests/framework/mod.rs | 42 ++++++- docs/firehose/merge-1.1.1-q1-reset-tests.md | 64 ++++++++++ docs/firehose/merge-1.1.1-q2-dockerfile-sf.md | 56 +++++++++ .../merge-1.1.1-q3-beryl-block-model.md | 60 +++++++++ docs/firehose/merge-1.1.1-q4-pending-evm.md | 68 +++++++++++ 6 files changed, 399 insertions(+), 5 deletions(-) create mode 100644 docs/firehose/merge-1.1.1-q1-reset-tests.md create mode 100644 docs/firehose/merge-1.1.1-q2-dockerfile-sf.md create mode 100644 docs/firehose/merge-1.1.1-q3-beryl-block-model.md create mode 100644 docs/firehose/merge-1.1.1-q4-pending-evm.md diff --git a/crates/firehose-flashblocks/tests/flashblock_sequence.rs b/crates/firehose-flashblocks/tests/flashblock_sequence.rs index 83461bcd01..aa2528a746 100644 --- a/crates/firehose-flashblocks/tests/flashblock_sequence.rs +++ b/crates/firehose-flashblocks/tests/flashblock_sequence.rs @@ -16,14 +16,16 @@ mod framework; +use base_common_flashblocks::FlashblockId; use base_execution_chainspec::BaseChainSpec; use framework::{ FireEvent, GenesisClient, assembled_block_hash, assert_fire_events_eq, assert_fire_events_metadata_eq, canonical_block, flash_base, flash_delta, - flash_delta_with_payload_id, flash_delta_with_txs, hash, parse_fire_events, - run_flashblock_sequence, run_flashblock_sequence_at, run_flashblock_sequence_at_with_processor, - run_flashblock_sequence_without_peek, signed_legacy_transfer, test_genesis, + flash_delta_with_payload_id, flash_delta_with_prev_id, flash_delta_with_txs, hash, + parse_fire_events, run_flashblock_sequence, run_flashblock_sequence_at, + run_flashblock_sequence_at_with_processor, run_flashblock_sequence_without_peek, + signed_legacy_transfer, test_genesis, }; /// A lone base flashblock (wire idx 0) never emits a standalone FIRE BLOCK: its @@ -1867,3 +1869,109 @@ fn parent_header_missing_buffers_instead_of_resetting() { "both base(2) and delta(2,1) must remain buffered (bug would clear to 0)" ); } + +/// v1.1.1 (PR #3603) added a predecessor-link check to the sequence validator: a delta +/// carries `metadata.prev_flashblock_id` naming the flashblock it claims to follow. When +/// that id is non-default and disagrees with the latest accepted flashblock, the validator +/// returns `NonSequentialPredecessor`. +/// +/// Here base(1,0) establishes latest id `{block:1, index:0}`, then delta(1,1) claims to +/// follow `{block:9, index:9}` — a fork/misorder. The processor must treat it like a gap: +/// drop the delta, reset state, and wait for the next base. The base was squashed (idx 0 +/// never emits standalone), so nothing is emitted and the in-flight sequence is cleared. +#[test] +fn delta_with_mismatched_prev_id_resets() { + let genesis = test_genesis(); + let genesis_hash = BaseChainSpec::from_genesis(genesis.clone()).inner.genesis_hash(); + let client = GenesisClient::new(genesis); + let ts = 0x67d00000u64; + + let result = run_flashblock_sequence_at_with_processor( + client, + vec![ + flash_base(1, hash("1a"), genesis_hash, ts + 2), + // Sequentially next index (1), but the predecessor link points at an + // unrelated flashblock — predecessor mismatch, not an index gap. + flash_delta_with_prev_id( + 1, + hash("1a"), + 1, + FlashblockId { block_number: 9, index: 9 }, + ), + ], + ts + 2, + ); + + let events: Vec = parse_fire_events(&result.raw) + .into_iter() + .filter(|e| matches!(e, FireEvent::Block { .. } | FireEvent::FlashBlock { .. })) + .collect(); + + // Base squashed, predecessor-mismatched delta → reset. Nothing emitted. + assert_fire_events_metadata_eq(&events, &[]); + + // The reset must clear the in-flight sequence entirely. + let (current_block, pending, stored_count) = result.processor.pending_state_for_test(); + assert_eq!(current_block, None, "predecessor mismatch must reset current block to None"); + assert!(!pending, "a reset sequence is not pending"); + assert_eq!(stored_count, 0, "reset must clear all buffered flashblocks"); +} + +/// Counterpart to [`delta_with_mismatched_prev_id_resets`]: a delta whose +/// `prev_flashblock_id` correctly names the latest accepted flashblock (`{block:1, +/// index:0}`) links cleanly and is accepted, emitting the consolidated flashblock at +/// idx=1. Confirms the new check does not reject valid predecessor links. +#[test] +fn delta_with_matching_prev_id_is_accepted() { + let genesis = test_genesis(); + let genesis_hash = BaseChainSpec::from_genesis(genesis.clone()).inner.genesis_hash(); + let client = GenesisClient::new(genesis); + let ts = 0x67d00000u64; + + let raw = run_flashblock_sequence( + client, + vec![ + flash_base(1, hash("1a"), genesis_hash, ts + 2), + flash_delta_with_prev_id( + 1, + hash("1a"), + 1, + FlashblockId { block_number: 1, index: 0 }, + ), + ], + ); + + let events: Vec = parse_fire_events(&raw) + .into_iter() + .filter(|e| matches!(e, FireEvent::Block { .. } | FireEvent::FlashBlock { .. })) + .collect(); + + // Base squashed; delta(1,1) with a matching predecessor link is accepted and emits. + assert_fire_events_metadata_eq(&events, &[FireEvent::flash_block(1, hash("1a"), 1, false)]); +} + +/// Backward compatibility: a delta with a default (all-zero) `prev_flashblock_id` — what a +/// builder that does not yet emit the field produces — skips the predecessor check and is +/// accepted on index ordering alone, exactly as before v1.1.1. +#[test] +fn delta_with_default_prev_id_is_accepted() { + let genesis = test_genesis(); + let genesis_hash = BaseChainSpec::from_genesis(genesis.clone()).inner.genesis_hash(); + let client = GenesisClient::new(genesis); + let ts = 0x67d00000u64; + + let raw = run_flashblock_sequence( + client, + vec![ + flash_base(1, hash("1a"), genesis_hash, ts + 2), + flash_delta_with_prev_id(1, hash("1a"), 1, FlashblockId::default()), + ], + ); + + let events: Vec = parse_fire_events(&raw) + .into_iter() + .filter(|e| matches!(e, FireEvent::Block { .. } | FireEvent::FlashBlock { .. })) + .collect(); + + assert_fire_events_metadata_eq(&events, &[FireEvent::flash_block(1, hash("1a"), 1, false)]); +} diff --git a/crates/firehose-flashblocks/tests/framework/mod.rs b/crates/firehose-flashblocks/tests/framework/mod.rs index 1d3e8db4a6..c563f9c06e 100644 --- a/crates/firehose-flashblocks/tests/framework/mod.rs +++ b/crates/firehose-flashblocks/tests/framework/mod.rs @@ -27,8 +27,8 @@ use alloy_rpc_types_engine::PayloadId; use alloy_signer_local::PrivateKeySigner; use base_common_consensus::{BaseBlock, BasePrimitives, BaseReceipt, BaseTxEnvelope}; use base_common_flashblocks::{ - ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, FlashblocksPayloadV1, - Metadata, + ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, FlashblockId, + FlashblocksPayloadV1, Metadata, }; use base_execution_chainspec::BaseChainSpec; use base_firehose_flashblocks::{ @@ -790,6 +790,44 @@ pub(crate) fn flash_delta_with_payload_id( }) } +/// Like [`flash_delta`] but stamps the delta's metadata with an explicit +/// `prev_flashblock_id` (the link to the flashblock this delta claims to follow). +/// +/// Used to exercise the predecessor-link validation added in v1.1.1: the sequence +/// validator rejects a delta whose `prev_flashblock_id` is non-default and does not +/// match the latest accepted flashblock id, returning `NonSequentialPredecessor` and +/// causing the processor to reset and wait for the next base. A default (all-zero) +/// `prev_flashblock_id` is treated as "unset" and skips the check for backward +/// compatibility with builders that do not emit the field yet. +pub(crate) fn flash_delta_with_prev_id( + block_number: u64, + block_hash: B256, + index: u64, + prev_flashblock_id: FlashblockId, +) -> TestEvent { + let diff = ExecutionPayloadFlashblockDeltaV1 { + state_root: B256::ZERO, + receipts_root: B256::ZERO, + logs_bloom: Bloom::default(), + gas_used: 0, + block_hash, + transactions: Vec::new(), + withdrawals: Vec::new(), + withdrawals_root: B256::ZERO, + blob_gas_used: Some(0), + }; + + let metadata = Metadata { block_number, prev_flashblock_id }; + + TestEvent::flashblock(Flashblock { + payload_id: PayloadId::new([0u8; 8]), + index, + base: None, + diff, + metadata, + }) +} + /// Signals that canonical block `block_number` (with the given `block_hash`) is now /// available from the provider, and triggers a canonical FIRE BLOCK emission with that /// hash. The parent hash is implicit — the canonical tracer seals from a synthesised diff --git a/docs/firehose/merge-1.1.1-q1-reset-tests.md b/docs/firehose/merge-1.1.1-q1-reset-tests.md new file mode 100644 index 0000000000..eb6a9d2148 --- /dev/null +++ b/docs/firehose/merge-1.1.1-q1-reset-tests.md @@ -0,0 +1,64 @@ +# Q1 — Tests for the flashblock reset behavior + +## What changed and why a test was needed + +v1.1.1 (backport of PR #3603) changed `FlashblockSequenceValidator::validate` to take a +fifth argument, `prev_flashblock_id: FlashblockId`, and added a new result variant, +`NonSequentialPredecessor { expected, actual }`. + +Each delta flashblock now carries `metadata.prev_flashblock_id` — an explicit link naming +the flashblock it claims to follow. The validator checks it: + +```rust +// crates/execution/flashblocks/src/validation.rs +if incoming_prev_flashblock_id != FlashblockId::default() + && incoming_prev_flashblock_id != latest_flashblock_id +{ + return SequenceValidationResult::NonSequentialPredecessor { expected, actual }; +} +``` + +The firehose processor consumes this validator, so it was adapted to pass +`flashblock.metadata.prev_flashblock_id` and to handle the new variant by **resetting** +its in-flight state and waiting for the next base flashblock (same recovery path as the +existing `NonSequentialGap`). + +This reset path had no test coverage. Three were added. + +## Tests added + +File: `crates/firehose-flashblocks/tests/flashblock_sequence.rs` + +1. **`delta_with_mismatched_prev_id_resets`** — the reset case. + `base(1,0)` establishes the latest id `{block:1, index:0}`; then a `delta(1,1)` whose + `prev_flashblock_id` is `{block:9, index:9}` (non-default, non-matching) arrives. The + index is sequentially valid, so this isolates the *predecessor* check from the *gap* + check. Asserts: no FIRE events emitted, and — reading processor state directly via + `pending_state_for_test()` — `current_block == None`, `pending == false`, + `stored_count == 0`, i.e. the sequence was fully reset. + +2. **`delta_with_matching_prev_id_is_accepted`** — the happy path. + Same setup but `prev_flashblock_id == {block:1, index:0}` (correctly links to the base). + Asserts the delta is accepted and emits the consolidated flashblock at `flash_idx=1`. + Guards against the new check rejecting valid links. + +3. **`delta_with_default_prev_id_is_accepted`** — backward compatibility. + `prev_flashblock_id == FlashblockId::default()` (all-zero), which is what a builder that + does not yet emit the field produces. Asserts the check is skipped and the delta is + accepted on index ordering alone, exactly as pre-v1.1.1. + +## Supporting test helper + +Added `flash_delta_with_prev_id(block_number, block_hash, index, prev_flashblock_id)` to +`crates/firehose-flashblocks/tests/framework/mod.rs` — builds a delta with an explicit +`metadata.prev_flashblock_id`. The pre-existing helpers default that field, so they could +not exercise the new check. + +## Result + +``` +cargo test -p base-firehose-flashblocks --test flashblock_sequence +test result: ok. 38 passed; 0 failed +``` + +(35 pre-existing + 3 new.) diff --git a/docs/firehose/merge-1.1.1-q2-dockerfile-sf.md b/docs/firehose/merge-1.1.1-q2-dockerfile-sf.md new file mode 100644 index 0000000000..486d2e1681 --- /dev/null +++ b/docs/firehose/merge-1.1.1-q2-dockerfile-sf.md @@ -0,0 +1,56 @@ +# Q2 — Does `Dockerfile.sf` still build after the merge? + +**Yes.** The exact build invocation from `Dockerfile.sf` was reproduced locally and +succeeds in release mode. + +## What `Dockerfile.sf` builds + +```dockerfile +# Dockerfile.sf, build stage +RUN cargo build --release \ + --package base-reth-node --bin base-reth-node \ + --package base-consensus --bin base-consensus +``` + +It then copies the two binaries onto a `ghcr.io/streamingfast/firehose-ethereum` base +image. It does **not** build the workspace, tests, or the `base-test-utils` crate. + +## Why this matters for the merge + +The merge initially broke `cargo check --workspace` for **two** reasons. Only the second +mattered for the firehose code; **neither** affects `Dockerfile.sf`: + +1. **`ParentBlockhashGuard.sol` artifact missing.** v1.1.1 added a new Solidity test + contract under `crates/utilities/test-utils/contracts/`. `base-test-utils` embeds the + compiled `out/.../ParentBlockhashGuard.json` via `concat!`/`include`, and `out/` is + gitignored (generated by `forge build`). Until `forge build` runs, `base-test-utils` + fails to compile. + + **Not in the Docker image path.** `base-test-utils` is a dev/test-only crate and is not + a (non-dev) dependency of `base-reth-node` or `base-consensus`. The release build of + both binaries completed without ever running `forge` — proving the contract artifact is + not on their dependency graph. + +2. **Firehose validator signature change** (the real one) — fixed in this branch. Covered + in Q1. + +## Verification + +``` +cargo build --release \ + --package base-reth-node --bin base-reth-node \ + --package base-consensus --bin base-consensus + Finished `release` profile [optimized] target(s) in 5m 06s # exit 0 +``` + +This is byte-for-byte the Dockerfile's compile step, so the in-container build will +compile the same way. The Rust toolchain pinned in the Dockerfile (`RUST_VERSION=1.94`) +is unchanged by the merge. + +## Caveat (not a blocker) + +A full `docker build -f Dockerfile.sf .` was **not** run end-to-end (it pulls the +`firehose-ethereum` base image and recompiles inside the container — long, network-bound). +The compile step — the only part the merge could have broken — is verified above. The +final-stage `COPY`/`ln -s` steps reference only the two produced binaries and are +unaffected by the merge. diff --git a/docs/firehose/merge-1.1.1-q3-beryl-block-model.md b/docs/firehose/merge-1.1.1-q3-beryl-block-model.md new file mode 100644 index 0000000000..1134cded08 --- /dev/null +++ b/docs/firehose/merge-1.1.1-q3-beryl-block-model.md @@ -0,0 +1,60 @@ +# Q3 — Does the Beryl activation affect the block model / firehose blocks? + +**Short answer: no.** Beryl does not change the block model (header fields, transaction +types, receipt format) that firehose serializes. v1.1.1 only *schedules* Beryl on mainnet; +the Beryl code already shipped in v1.1.0-fh. + +## What the merge actually did + +The only Beryl change in v1.1.1 is a timestamp flip in `crates/common/chains/src/config.rs`: + +```rust +// base mainnet +beryl_timestamp: Some(1_782_410_400), // was None ("Never") +``` + +Plus the matching test assertions. No Beryl *logic* changed — that all predates the merge. +So the question reduces to: "what does activating Beryl do, and does any of it reach the +firehose block?" + +## Beryl introduces no new Ethereum EVM spec + +`BaseUpgrade::Beryl` maps to `SpecId::OSAKA` — **the same Ethereum spec as the preceding +`Azul` upgrade** (`crates/common/chains/src/upgrade.rs`, `crates/common/evm/src/spec.rs`): + +``` +Azul => SpecId::OSAKA +Beryl => SpecId::OSAKA +Cobalt => SpecId::OSAKA +``` + +Therefore Beryl adds **no new transaction types, no new header fields, and no new receipt +fields** at the EL-spec level. Anything OSAKA-related (and everything from Prague/Cancun +below it) was already reachable under Azul. The firehose Ethereum block model already +handles this spec. + +## What Beryl actually adds: Base-native precompiles + +Beryl installs a dynamic precompile lookup (`BerylLookup`, in +`crates/common/precompiles/`): the activation registry, the B20 factory / stablecoin / +asset precompiles, and policy precompiles. These are contracts at fixed addresses, invoked +by ordinary `CALL`s from transactions. + +Firehose impact: **none structural.** A precompile call appears to the firehose tracer as +a normal internal call frame to the precompile's address, with its gas, input, output, and +any resulting account/storage/balance changes — all of which firehose already captures the +same way it captures any other call. There is no new block-, transaction-, or +receipt-level field to serialize. + +The one operational note (not a block-model issue): Beryl's activation-registry precompile +requires an admin configured at genesis (`config.rs` comment). That is a chain-config / +genesis concern, not something that alters the firehose block. + +## Bottom line for firehose + +- No firehose protobuf / block-model change is required for Beryl. +- When mainnet crosses `1782410400`, firehose blocks will start containing transactions + that call the Beryl precompiles; these serialize as ordinary call traces. +- Worth a one-time sanity check on a Beryl-active block (sepolia activates earlier, at + `1781805600`) to confirm precompile call frames trace as expected — but this exercises + pre-existing tracer code, not anything introduced by this merge. diff --git a/docs/firehose/merge-1.1.1-q4-pending-evm.md b/docs/firehose/merge-1.1.1-q4-pending-evm.md new file mode 100644 index 0000000000..2d2fbd2261 --- /dev/null +++ b/docs/firehose/merge-1.1.1-q4-pending-evm.md @@ -0,0 +1,68 @@ +# Q4 — What is the "pending EVM" thing (PR #3603)? + +## The feature: a pending block built from flashblocks + +Base's sequencer streams **flashblocks** — partial block updates (a base + a series of +deltas) emitted ~200 ms apart, before the full block is sealed and committed. To let RPC +clients query the not-yet-canonical tip (`eth_call`, `eth_getBalance`, `eth_call` against +the `pending` block tag, etc.), the node maintains a **pending EVM state**: an in-memory +EVM/`State` built by replaying the in-flight flashblocks on top of the latest canonical +state. + +In `crates/execution/flashblocks/src/processor.rs` this is the `LivePendingState` +(`db` = a revm `State`/`CacheDB`, plus `state_overrides`) that the processor keeps swapping +as new flashblocks arrive. "Pending EVM" = that in-memory EVM state used to answer queries +about the block currently being built. + +## The bug PR #3603 fixed + +The pending block is built on top of state that is **not yet committed to the database**. +revm's `BLOCKHASH` opcode (and anything reading the parent block hash) resolves the +immediate parent's hash via the in-memory `block_hashes` map. For the pending block, that +map had **no entry for `block_number - 1`**, so `blockhash(block.number - 1)` returned the +wrong value (zero) inside the pending EVM — diverging from how the same call would behave +once the block is canonical. + +The fix (the part the new `ParentBlockhashGuard.sol` test pins down): + +```rust +// crates/execution/flashblocks/src/processor.rs +db.block_hashes.insert(base.block_number - 1, base.parent_hash); +// and the assembled pending header now carries: +header.parent_hash = base.parent_hash; +``` + +The new test contract `ParentBlockhashGuard.succeedsOnlyWhenParentBlockHashIsCanonical` +calls `blockhash(block.number - 1)` and asserts it equals the canonical parent hash — +exactly the value that was previously missing in the pending EVM. + +The rest of PR #3603 (typed `FlashblockId` errors, the `prev_flashblock_id` predecessor +link, atomic counters) is unrelated plumbing bundled in the same backport. + +## Relevance to firehose + +**The fix lives in the RPC pending-block pipeline, which firehose does not run.** Firehose +has its **own** processor (`crates/firehose-flashblocks/src/processor.rs`) that re-executes +flashblocks to emit FIRE BLOCK trace lines. The upstream `LivePendingState` change does +**not** propagate to it, and it was **not** ported — this is the pre-existing divergence +flagged in the merge summary. + +Whether the firehose execute path needs an equivalent parent-hash insertion is a +**pre-existing** question, not something v1.1.1 changes: + +- Firehose only executes a block once its **parent is available from the provider**. The + execute path looks up `header_by_number(block_number - 1)` and, on a miss, buffers the + sequence as *pending* instead of executing (see the test + `parent_header_missing_buffers_instead_of_resetting`). So for the immediate parent, + `provider.block_hash(n-1)` has a resolution path that the upstream pending pipeline + lacked. +- Firehose runs `apply_pre_execution_changes()` per block, which performs the EIP-2935 + history-storage system call (writing the parent hash into the history contract). Beryl is + well past Isthmus/Prague where EIP-2935 is active, so `blockhash` reads of recent blocks + resolve through contract state that firehose populates. + +**Recommended follow-up (not a merge blocker):** add a targeted trace-correctness check +that traces a transaction calling `blockhash(n-1)` (e.g. a `ParentBlockhashGuard`-style +contract) through the firehose processor and confirms the traced result matches canonical. +That validates the firehose path has the same parent-hash guarantee the upstream RPC path +just gained — but it exercises pre-existing firehose code, independent of this merge.