Compare commits

..

143 Commits

Author SHA1 Message Date
yongkangc
f568349be9 perf(engine-tree): reuse provider builder in validation 2025-12-22 07:48:35 +00:00
YK
62abfdaeb5 feat(cli): add tracing-samply to profiling (#20546) 2025-12-21 11:52:26 +00:00
emmmm
256a9fdb79 docs: add missing trace methods to pruning tables (#20547) 2025-12-21 12:40:58 +01:00
github-actions[bot]
4d9aff99bf chore(deps): weekly cargo update (#20545)
Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com>
2025-12-21 12:40:14 +01:00
Vitalyr
28bb2891bb refactor(consensus): simplify verify_receipts return (#20517) 2025-12-20 19:05:50 +01:00
kurahin
1d8f265744 chore(net): remove stale ECIES rand TODO (#20531) 2025-12-20 19:05:37 +01:00
Matthias Seitz
c754caf8c7 fix: remove stale blobs (#20528) 2025-12-20 15:35:22 +00:00
cui
e1b0046329 chore: remove todo after jovian fork (#20535)
Co-authored-by: weixie.cui <weixie.cui@okg.com>
2025-12-20 15:31:08 +00:00
cui
ddfe177578 chore: remove todo (#20533)
Co-authored-by: weixie.cui <weixie.cui@okg.com>
2025-12-20 15:19:53 +00:00
Gigi
178558c6d7 fix(tree): correct block buffer eviction policy comment (#20512) 2025-12-20 09:44:51 +00:00
Emilia Hane
f4d3a9701f chore(trie): Rm redundant clone of propagated error (#20466)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
2025-12-20 08:42:20 +00:00
Gigi
42e41a9370 docs: add reth JSON-RPC namespace documentation (#20522) 2025-12-20 08:03:06 +00:00
pepes
a66dcce834 chore(evm): remove deprecated state_change compatibility alias (#20518) 2025-12-20 07:50:12 +00:00
Arsenii Kulikov
21d835cf2b perf: use LRU eviction policy for precompile cache (#20527) 2025-12-20 02:12:42 +00:00
Alexey Shekhirin
29438631be fix: propagate keccak-cache-global feature to reth-node-core (#20524) 2025-12-19 17:11:41 +00:00
Brian Picciano
0eb4e0ce29 fix(stages): Fix two bugs related to stage checkpoints and pipeline syncs (#20521) 2025-12-19 16:09:57 +00:00
gustavo
9147f9aafe perf(trie): remove more unnecessary channels (#20489) 2025-12-19 15:34:42 +00:00
Snezhkko
13b111e058 refactor: remove dead storage multiproof path (#20485) 2025-12-19 15:11:31 +00:00
leniram159
25c247b14c refactor(engine): simplify fork detection in insert_block (#20441) 2025-12-19 14:49:33 +00:00
Matthias Seitz
72bea44d8c chore: remove redundant num hash (#20501) 2025-12-19 14:48:42 +00:00
alex017
63b9d5fe57 refactor(db-api): remove redundant clone and unused import in unwind (#20499)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
2025-12-19 14:47:11 +00:00
Arsenii Kulikov
30162c535e perf: properly share precompile cache + use moka (#20502) 2025-12-18 22:42:44 +00:00
Federico Gimenez
cd8fec3273 feat(stages): use EitherWriter for TransactionLookupStage RocksDB writes (#20428) 2025-12-18 21:34:17 +00:00
Tomass
1e38c7fea8 chore(hardforks): drop unnecessary field reassignment in TTD branch (#20457)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
2025-12-18 21:02:56 +00:00
Block Wizard
4dfaf238c9 chore(net): fix misleading comment about uncompressed message size check (#19510) 2025-12-18 20:34:50 +00:00
forkfury
4cf36dda54 docs: correct FinishedStateUpdates message name (#20471) 2025-12-18 20:16:15 +00:00
phrwlk
41ce3d3bbf docs: fix Docker db-access troubleshooting example (#20483) 2025-12-18 20:13:01 +00:00
sashass1315
429d13772e chore(cli): correct p2p body error message (#20498) 2025-12-18 20:01:59 +00:00
Gigi
0cbf89193d docs: correct intra-doc link references (#20467) 2025-12-18 19:56:57 +00:00
radik878
0c3c42bffe chore(primitives-traits): correct SealedBlock::senders return description (#20465) 2025-12-18 19:56:22 +00:00
cui
cdbbd08677 fix: session config should be read from config file (#20484)
Co-authored-by: weixie.cui <weixie.cui@okg.com>
2025-12-18 19:53:18 +00:00
Alexey Shekhirin
4adb1fa5ac fix(cli): default to 0 genesis block number (#20494) 2025-12-18 15:07:59 +00:00
Brian Picciano
b3a792ad1e fix(engine): Use OverlayStateProviderFactory for state root fallback (#20462) 2025-12-18 14:30:11 +00:00
Arsenii Kulikov
98a7095c7a fix: properly determine first stage during pipeline consistency check (#20460) 2025-12-18 10:43:08 +00:00
Matthias Seitz
701e5ec455 chore: add engine terminate (#20420)
Co-authored-by: joshieDo <93316087+joshieDo@users.noreply.github.com>
2025-12-18 09:01:36 +00:00
Lorsmirq Benton
8e00e81af4 docs: remove orphaned debug.mdx (#20474) 2025-12-18 04:14:23 +00:00
YK
453514c48f perf(engine): share Arc<ExecutionOutcome> to avoid cloning BundleState (#20448) 2025-12-18 01:07:18 +00:00
James Niken
432ac7afa1 chore: fix blob count in validation benchmark (#20456) 2025-12-18 00:51:45 +00:00
Emilia Hane
c7fca9f2b4 chore(node): Report actual gas price to ethstats (#20461)
Co-authored-by: Rifvck Zieger <rifvckzieger@gmail.com>
2025-12-18 00:50:16 +00:00
DaniPopes
715ca5b980 chore: simplify prewarm state providers (#20469) 2025-12-17 22:11:11 +00:00
Federico Gimenez
9ae62aad26 feat(storage): add method to check invariants on RocksDB tables (#20340) 2025-12-17 20:26:51 +00:00
YK
c65df40526 perf: remove redundant contains_key check in ProofSequencer::add_proof (#20459) 2025-12-17 13:58:59 +00:00
Vui-Chee
d8acc1e4cf feat: support non-zero genesis block numbers (#19877)
Co-authored-by: JimmyShi22 <417711026@qq.com>
2025-12-17 11:03:12 +00:00
sashass1315
852aad8126 docs(exex): document ChainRevert flow in how-it-works (#20455) 2025-12-17 10:28:49 +00:00
Karl Yu
61c072ad20 feat: add engine_getBlobsV3 method (#20451) 2025-12-17 10:15:49 +00:00
Lorsmirq Benton
6a5b985113 docs: remove orphaned recover CLI documentation (#20447) 2025-12-17 10:13:55 +00:00
joshieDo
1adc6aec00 chore(engine): extract on_persistence_complete (#20443) 2025-12-17 09:07:54 +00:00
Matthias Seitz
5edc16ad85 perf: only populate cache during prewarm (#20445) 2025-12-17 08:46:16 +00:00
phrwlk
f54a8a1ef5 fix(payload): clarify PayloadTransactions mark_invalid semantics (#20452) 2025-12-17 08:44:17 +00:00
leniram159
c681851ec8 chore: make docs correct (#20440)
Co-authored-by: YK <chiayongkang@hotmail.com>
2025-12-17 04:32:18 +00:00
DaniPopes
d964fcbcde chore: simplify execution state providers (#20444) 2025-12-16 22:52:57 +00:00
Alexey Shekhirin
e79691aae7 feat: turn on asm-keccak by default, use maxperf profile in Dockerfiles (#20422) 2025-12-16 22:43:20 +00:00
bigbear
4231f4b688 docs: fix incorrect API example in node-components.mdx (#20297) 2025-12-16 15:09:29 +00:00
Léa Narzis
0b607113dc refactor(era): make era count in era file name optional (#20292) 2025-12-16 15:08:43 +00:00
emmmm
be4dc53b92 docs: fix --color auto option description (#20352) 2025-12-16 15:06:04 +00:00
emmmm
4afb555d06 docs(opstack): document all rollup CLI arguments (#20374) 2025-12-16 15:04:34 +00:00
Matthias Seitz
ab2ef99458 chore: add keccak-global (#20418) 2025-12-16 14:59:09 +00:00
Sophia Raye
bfd4b79245 docs(trace): remove duplicate comment (#20360) 2025-12-16 14:56:01 +00:00
Federico Gimenez
49057b1c0c feat(storage): add with_default_tables() to register RocksDB column families at initialization (#20416) 2025-12-16 12:59:58 +00:00
Gigi
b6772370d7 docs: fix incorrect method reference in try_recover_sealed_with_senders (#20410) 2025-12-16 12:27:53 +00:00
Karl Yu
d72935628a feat: add support for eth/70 eip-7975 (#20255)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
2025-12-16 12:05:11 +00:00
YK
ad63b135d6 feat(storage): implement EitherWriter/EitherReader methods for RocksDB (#20408) 2025-12-16 11:26:31 +00:00
Brian Picciano
90651ae8e8 feat(engine): Use BAL in state root validation (#20383) 2025-12-16 11:05:51 +00:00
Matthias Seitz
bbd51862d4 chore: rm flaky bench (#20413) 2025-12-16 09:35:38 +00:00
Arsenii Kulikov
08a16a5bde perf: recover transactions in parallel during network import (#20385) 2025-12-16 09:33:24 +00:00
Snezhkko
f2c39db7a2 chore(rpc): fix misleading link and comment (#20367) 2025-12-16 09:32:25 +00:00
oooLowNeoNooo
ae9e84d6e3 fix(discv4): correct ping_interval default value in docs (#20396) 2025-12-16 09:29:45 +00:00
theo
c51da593d1 feat(net/p2p): support fixed external addresses with DNS resolution (#20411) 2025-12-16 09:28:31 +00:00
Matthias Seitz
0e08f9f56c perf: remove unnecessary channels from parallel trie operations (#20406) 2025-12-16 09:15:27 +00:00
sashass1315
7eef092110 docs(exex): sync hello-world notifications loop with code (#20403) 2025-12-16 08:39:45 +00:00
YK
40e8241bf5 feat(storage): use RocksDBBatch in EitherWriter and related modules (#20377) 2025-12-16 03:57:41 +00:00
dependabot[bot]
dd9ff731e4 chore(deps): bump peter-evans/create-pull-request from 7 to 8 (#20402)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-16 00:11:22 +00:00
dependabot[bot]
83f9d1837f chore(deps): bump actions/download-artifact from 4 to 7 (#20401)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-16 00:11:00 +00:00
dependabot[bot]
68911e617b chore(deps): bump actions/upload-artifact from 5 to 6 (#20400)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-16 00:10:40 +00:00
0xcharry
36ba6db029 chore: remove redundant .as_str() calls after to_string() (#20404) 2025-12-16 00:10:03 +00:00
Matthias Seitz
fec4432d82 perf: defer transaction pool notifications until after lock release (#20405) 2025-12-15 23:06:34 +00:00
Matthias Seitz
179da26305 perf: use RwLock for transaction pool listeners (#20398)
Co-authored-by: Arsenii Kulikov <klkvrr@gmail.com>
2025-12-15 21:47:59 +00:00
Matthias Seitz
b5e7a694d2 chore: update metric once (#20371) 2025-12-15 20:38:24 +00:00
Maxim Evtush
9489667814 fix: post-state generator to include deletions in proptest (#20276) 2025-12-15 16:43:02 +00:00
gustavo
004877ba59 refactor(cli): cleanup repair-trie metrics (#20226) 2025-12-15 16:41:48 +00:00
Brian Picciano
a9e36923e1 feat(trie): Proof Rewrite: Use cached branch nodes (#20075)
Co-authored-by: YK <chiayongkang@hotmail.com>
Co-authored-by: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com>
2025-12-15 15:27:04 +00:00
DaniPopes
74a3816611 ci: reduce feature powerset depth (#20379) 2025-12-15 14:42:14 +00:00
Alexey Shekhirin
5576d4547f revert: feat(engine): run sync state root if not enough parallelism (#20127) (#20378) 2025-12-15 14:05:54 +00:00
DaniPopes
21216e2f24 perf: use indexed parallel iterators for tx recovery (#20342) 2025-12-15 13:40:03 +00:00
YK
42c1e1afe1 feat(storage): add account history constructors to EitherWriter/EitherReader (#20366) 2025-12-15 12:45:07 +00:00
MoNyAvA
5f7e87fa2a docs: add blob sub-pool to tx pool docs (#20375) 2025-12-15 12:27:54 +00:00
Matthias Seitz
1b417dacc4 chore: sanity check for u64::Max (#20373) 2025-12-15 11:33:50 +00:00
Niven
bb952be5b5 feat(flashblocks): support eth_getBlockTransactionCount for flashblocks (#20291)
Co-authored-by: lucas <66681646+limyeechern@users.noreply.github.com>
Co-authored-by: lucas.lim <lucas.lim@okg.com>
2025-12-15 11:29:23 +00:00
Federico Magnani
f927eec880 chore: export FlashBlockDecoder (#20370) 2025-12-15 11:00:46 +00:00
Tomass
9c61f5568c fix(rpc-testing-util): use buffer_unordered in trace_block_opcode_gas_unordered (#20369) 2025-12-15 10:38:40 +00:00
ligt
662c0486a1 feat(storage): add rocksdb provider into database provider (#20253) 2025-12-15 10:15:57 +00:00
Matthias Seitz
997848c2a1 fix(txpool): remove stale senderinfo (#20368) 2025-12-15 10:00:25 +00:00
Olexandr88
155bdecf3b docs(repo): add Ethereum-specific crates section (#20363) 2025-12-15 09:56:40 +00:00
github-actions[bot]
679234f105 chore(deps): weekly cargo update (#20359)
Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com>
2025-12-14 20:54:42 +00:00
phrwlk
419c7b489b fix(rpc): remove dead flashbots module config (#20364) 2025-12-14 20:54:15 +00:00
Rej Ect
06dac07b5f ci(hive): bump actions/cache to v5 (#20349) 2025-12-13 09:04:07 +00:00
YK
5621132b8b feat: add RocksDB variant to EitherReader and EitherWriter (#20288) 2025-12-13 04:06:44 +00:00
Matthias Seitz
3380eb69c8 fix: only collect already tracked accounts (#20341) 2025-12-12 22:09:21 +00:00
Arsenii Kulikov
0366497ada perf: skip redundant recovery (#20343) 2025-12-12 22:01:05 +00:00
Alexey Shekhirin
cd71f3d5a4 feat(engine): record total latencies on instrumented state provider drop (#20337) 2025-12-12 21:14:44 +00:00
Alexey Shekhirin
64909d33e6 feat(engine): cli argument to disable state cache (#20143) 2025-12-12 17:51:22 +00:00
Alexey Shekhirin
3c9ad31344 chore(engine): make InstrumentedStateProvider public (#20335) 2025-12-12 16:41:42 +00:00
gustavo
f3e14fd061 feat(rpc): handle dedicated eth_simulate errors (#20099) 2025-12-12 16:40:13 +00:00
Alexey Shekhirin
daf6b88dc6 feat(node): engine args defaults (#20203) 2025-12-12 15:54:05 +00:00
emmmm
d2d58f9a0e docs: add missing RPC namespaces to JSON-RPC intro (#20321) 2025-12-12 15:40:38 +00:00
Matthias Seitz
ace4e515b5 chore: bump inspectors 0.33.2 (#20334) 2025-12-12 15:39:04 +00:00
Hesham Shabanah
134164954b feat: add --max-peers CLI flag (#20139)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
2025-12-12 13:26:44 +00:00
Lorsmirq Benton
2775dd1f23 docs: correct comments in custom-inspector (#20304) 2025-12-12 13:21:03 +00:00
Alexey Shekhirin
ac0f9687bd chore(engine): move noisy multiproof debug logs to trace level (#20331) 2025-12-12 13:01:01 +00:00
Arsenii Kulikov
a9c21a395d perf: spawn rpc handlers as blocking (#20330) 2025-12-12 12:15:02 +00:00
Federico Magnani
df7ad9ae45 chore(ethapi): increase visibility tx_batch_sender (#20315)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
2025-12-12 12:14:43 +00:00
sashass1315
5903e42a98 docs: refresh repo layout crate lists (#20319) 2025-12-12 10:59:57 +00:00
Matthias Seitz
3c41b99599 chore: lower block buffer size (#20324) 2025-12-12 08:15:54 +00:00
pepes
d70d80fff1 fix(docs): document discv5 discovery port 9200 (#20322) 2025-12-12 08:12:08 +00:00
gustavo
ed3a8a03d5 feat(node-core): make rpc server args customizable (#20312) 2025-12-11 23:24:31 +00:00
YK
bfcd46d01d feat: add account_history_in_rocksdb field to StorageSettings (#20282) 2025-12-11 19:37:36 +00:00
Brian Picciano
194d545fae feat(engine): Add BAL stub methods to ExecutionPayload and BlockOrPayload (#20311)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
2025-12-11 19:07:43 +00:00
sashass1315
97243ec1f4 docs: fix misleading links (#20300) 2025-12-11 18:49:18 +00:00
DaniPopes
93c1b0f52f ci: add more sccache (#20316) 2025-12-11 18:46:11 +00:00
Arsenii Kulikov
474c09095f feat: bump alloy-evm (#20314) 2025-12-11 19:46:34 +01:00
Matthias Seitz
24c298133f feat: allow larger ws frames on client side (#20307) 2025-12-11 16:43:10 +00:00
Block Wizard
da27336a1e docs: add architecture diagrams to ExEx documentation (#20193)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
2025-12-11 11:41:15 +00:00
Matthias Seitz
2e567d6658 feat: add semaphore for blocking IO requests (#20289) 2025-12-11 11:35:50 +00:00
Alexey Shekhirin
28e7c8a7cb ci: scale down depot runners (#20295) 2025-12-11 11:33:49 +00:00
Matthias Seitz
a2a5e03cb8 perf: fetch header directly (#20294) 2025-12-11 11:18:51 +00:00
Sophia Raye
6073aa5b4a docs(exex): fix DebugApi comment (#20296) 2025-12-11 10:06:31 +00:00
Karl Yu
e90cfedf3d feat: add support for testing_ rpc namespace and testing_buildBlockV1 (#20094)
Co-authored-by: Arsenii Kulikov <klkvrr@gmail.com>
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
2025-12-11 08:56:46 +00:00
Matthias Seitz
8b27ca6fa2 chore: update engine_getBlobs metric (#20290) 2025-12-11 08:11:54 +00:00
Tomass
1752d6fb99 chore(optimism): move predeploy constant to op-alloy (#20181)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
2025-12-11 07:04:01 +00:00
emmmm
ac891a780b docs: fix stages order and add missing EraStage (#20283) 2025-12-11 06:26:27 +00:00
Adrian
036626b8a7 docs: improve map_add_ons method documentation (#20248) 2025-12-11 06:03:34 +00:00
josé v
68f0c9812f feat: add transaction_hash_numbers_in_rocksdb field to StorageSettings (#20209) 2025-12-11 01:07:12 +00:00
sashass1315
c9920c9690 docs: clarify network mode, tx gossip and NAT (#20247) 2025-12-10 21:52:04 +00:00
Karl Yu
af82606ff4 feat: add support for debug_getBadBlock (#20177)
Co-authored-by: Arsenii Kulikov <klkvrr@gmail.com>
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
2025-12-10 21:03:53 +00:00
radik878
38331a362e fix(rpc): avoid signing Optimism deposit transactions (#20254) 2025-12-10 20:46:43 +00:00
Tomass
e8dae2ae7d chore(deps): bump op-alloy to 0.23.0 (#20256)
Co-authored-by: Arsenii Kulikov <klkvrr@gmail.com>
2025-12-10 20:44:54 +00:00
Sophia Raye
ce5f90175b docs(jsonrpc): add missing debug namespace RPC methods (#20267) 2025-12-10 17:24:29 +00:00
gustavo
8c361c87c2 feat(txpool): handle more simulated scenarios in test_utils/pool.rs (#20138) 2025-12-10 17:13:59 +00:00
Block Wizard
4fbbb1fe54 feat: add recover_transactions_unchecked_ref to BlockBody (#20266) 2025-12-10 17:13:08 +00:00
Brian Picciano
b7d8815104 perf(prune): use delete_current_duplicates for MerkleChangeSets tables (#20230) 2025-12-10 13:33:11 +00:00
Alexey Shekhirin
b91cd8f451 ci: sccache (#20265) 2025-12-10 13:05:25 +00:00
Alexey Shekhirin
09aee4e35a ci: use 16 cores for Hive workflow (#20264) 2025-12-10 13:02:14 +00:00
Alexey Shekhirin
505a384b10 ci: increase partitions for crate-checks to 3 (#20261) 2025-12-10 13:02:11 +00:00
371 changed files with 11107 additions and 4295 deletions

View File

@@ -12,7 +12,7 @@ workflows:
# Check that `A` activates the features of `B`.
"propagate-feature",
# These are the features to check:
"--features=std,op,dev,asm-keccak,jemalloc,jemalloc-prof,tracy-allocator,serde-bincode-compat,serde,test-utils,arbitrary,bench,alloy-compat,min-error-logs,min-warn-logs,min-info-logs,min-debug-logs,min-trace-logs,otlp,js-tracer,portable",
"--features=std,op,dev,asm-keccak,jemalloc,jemalloc-prof,tracy-allocator,serde-bincode-compat,serde,test-utils,arbitrary,bench,alloy-compat,min-error-logs,min-warn-logs,min-info-logs,min-debug-logs,min-trace-logs,otlp,js-tracer,portable,keccak-cache-global",
# Do not try to add a new section to `[features]` of `A` only because `B` exposes that feature. There are edge-cases where this is still needed, but we can add them manually.
"--left-side-feature-missing=ignore",
# Ignore the case that `A` it outside of the workspace. Otherwise it will report errors in external dependencies that we have no influence on.

View File

@@ -11,17 +11,14 @@ go build .
# Run each hive command in the background for each simulator and wait
echo "Building images"
./hive -client reth --sim "ethereum/eels" \
--sim.buildarg fixtures=https://github.com/ethereum/execution-spec-tests/releases/download/bal@v1.8.0/fixtures_bal.tar.gz \
--sim.buildarg branch=eips/amsterdam/eip-7928 \
--sim.timelimit 1s || true &
# TODO: test code has been moved from https://github.com/ethereum/execution-spec-tests to https://github.com/ethereum/execution-specs we need to pin eels branch with `--sim.buildarg branch=<release-branch-name>` once we have the fusaka release tagged on the new repo
./hive -client reth --sim "ethereum/eels" --sim.buildarg fixtures=https://github.com/ethereum/execution-spec-tests/releases/download/v5.3.0/fixtures_develop.tar.gz -sim.timelimit 1s || true &
./hive -client reth --sim "ethereum/engine" -sim.timelimit 1s || true &
./hive -client reth --sim "devp2p" -sim.timelimit 1s || true &
./hive -client reth --sim "ethereum/rpc-compat" -sim.timelimit 1s || true &
./hive -client reth --sim "smoke/genesis" -sim.timelimit 1s || true &
./hive -client reth --sim "smoke/network" -sim.timelimit 1s || true &
./hive -client reth --sim "ethereum/sync" -sim.timelimit 1s || true &
wait
# Run docker save in parallel, wait and exit on error
@@ -40,7 +37,7 @@ for pid in "${saving_pids[@]}"; do
wait "$pid" || exit
done
# Make sure we don't rebuild images on the CI jobs
# Make sure we don't rebuild images on the CI jobs
git apply ../.github/assets/hive/no_sim_build.diff
go build .
mv ./hive ../hive_assets/

View File

@@ -24,4 +24,4 @@ done
wait
docker image ls -a
docker image ls -a

View File

@@ -7,7 +7,7 @@ sim="${1}"
limit="${2}"
run_hive() {
hive --sim "${sim}" --sim.limit "${limit}" --sim.parallelism 8 --client reth 2>&1 | tee /tmp/log || true
hive --sim "${sim}" --sim.limit "${limit}" --sim.parallelism 16 --client reth 2>&1 | tee /tmp/log || true
}
check_log() {

View File

@@ -3,14 +3,15 @@
on:
pull_request:
# TODO: Disabled temporarily for https://github.com/CodSpeedHQ/runner/issues/55
# merge_group :
# merge_group:
push:
branches: ["**"]
branches: [main]
env:
CARGO_TERM_COLOR: always
BASELINE: base
SEED: reth
RUSTC_WRAPPER: "sccache"
name: bench
jobs:
@@ -22,6 +23,7 @@ jobs:
submodules: true
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true

View File

@@ -10,9 +10,12 @@ on:
types: [opened, reopened, synchronize, closed]
merge_group:
env:
RUSTC_WRAPPER: "sccache"
jobs:
build:
runs-on: depot-ubuntu-latest-16
runs-on: depot-ubuntu-latest-8
timeout-minutes: 90
steps:
- name: Checkout
@@ -33,6 +36,8 @@ jobs:
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@nightly
- uses: mozilla-actions/sccache-action@v0.0.9
- name: Build docs
run: cd docs/vocs && bash scripts/build-cargo-docs.sh
@@ -69,4 +74,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v4

View File

@@ -9,15 +9,16 @@ on:
pull_request:
merge_group:
push:
branches: ["**"]
branches: [main]
env:
CARGO_TERM_COLOR: always
RUSTC_WRAPPER: "sccache"
name: compact-codec
jobs:
compact-codec:
runs-on: depot-ubuntu-latest-16
runs-on: depot-ubuntu-latest
strategy:
matrix:
bin:
@@ -26,6 +27,7 @@ jobs:
steps:
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true

View File

@@ -6,11 +6,12 @@ on:
pull_request:
merge_group:
push:
branches: ["**"]
branches: [main]
env:
CARGO_TERM_COLOR: always
SEED: rustethereumethereumrust
RUSTC_WRAPPER: "sccache"
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@@ -19,13 +20,14 @@ concurrency:
jobs:
test:
name: e2e-testsuite
runs-on: depot-ubuntu-latest-16
runs-on: depot-ubuntu-latest-4
env:
RUST_BACKTRACE: 1
timeout-minutes: 90
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: taiki-e/install-action@nextest
- uses: Swatinem/rust-cache@v2
with:

View File

@@ -6,9 +6,7 @@ on:
workflow_dispatch:
schedule:
- cron: "0 */6 * * *"
pull_request:
branches:
- main
env:
CARGO_TERM_COLOR: always
@@ -26,14 +24,13 @@ jobs:
prepare-hive:
if: github.repository == 'paradigmxyz/reth'
timeout-minutes: 45
runs-on: depot-ubuntu-latest
runs-on: depot-ubuntu-latest-16
steps:
- uses: actions/checkout@v6
- name: Checkout hive tests
uses: actions/checkout@v6
with:
repository: ethereum/hive
ref: master
path: hivetests
- name: Get hive commit hash
@@ -47,7 +44,7 @@ jobs:
- name: Restore hive assets cache
id: cache-hive
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ./hive_assets
key: hive-assets-${{ steps.hive-commit.outputs.hash }}-${{ hashFiles('.github/assets/hive/build_simulators.sh') }}
@@ -70,7 +67,7 @@ jobs:
chmod +x hive
- name: Upload hive assets
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: hive_assets
path: ./hive_assets
@@ -118,29 +115,27 @@ jobs:
# eth_ rpc methods
- sim: ethereum/rpc-compat
include:
# - eth_blockNumber
- eth_blockNumber
- eth_call
# - eth_chainId
# - eth_createAccessList
# - eth_estimateGas
# - eth_feeHistory
# - eth_getBalance
# - eth_getBlockBy
# - eth_getBlockTransactionCountBy
# - eth_getCode
# - eth_getProof
# - eth_getStorage
# - eth_getTransactionBy
# - eth_getTransactionCount
# - eth_getTransactionReceipt
# - eth_sendRawTransaction
# - eth_syncing
# # debug_ rpc methods
# - debug_
- eth_chainId
- eth_createAccessList
- eth_estimateGas
- eth_feeHistory
- eth_getBalance
- eth_getBlockBy
- eth_getBlockTransactionCountBy
- eth_getCode
- eth_getProof
- eth_getStorage
- eth_getTransactionBy
- eth_getTransactionCount
- eth_getTransactionReceipt
- eth_sendRawTransaction
- eth_syncing
# debug_ rpc methods
- debug_
# consume-engine
- sim: ethereum/eels/consume-engine
limit: .*tests/amsterdam.*
- sim: ethereum/eels/consume-engine
limit: .*tests/osaka.*
- sim: ethereum/eels/consume-engine
@@ -157,10 +152,10 @@ jobs:
limit: .*tests/homestead.*
- sim: ethereum/eels/consume-engine
limit: .*tests/frontier.*
- sim: ethereum/eels/consume-engine
limit: .*tests/paris.*
# consume-rlp
- sim: ethereum/eels/consume-rlp
limit: .*tests/amsterdam.*
- sim: ethereum/eels/consume-rlp
limit: .*tests/osaka.*
- sim: ethereum/eels/consume-rlp
@@ -177,11 +172,13 @@ jobs:
limit: .*tests/homestead.*
- sim: ethereum/eels/consume-rlp
limit: .*tests/frontier.*
- sim: ethereum/eels/consume-rlp
limit: .*tests/paris.*
needs:
- prepare-reth
- prepare-hive
name: run ${{ matrix.scenario.sim }}${{ matrix.scenario.limit && format(' - {0}', matrix.scenario.limit) }}
runs-on: depot-ubuntu-latest
runs-on: depot-ubuntu-latest-16
permissions:
issues: write
steps:
@@ -190,13 +187,13 @@ jobs:
fetch-depth: 0
- name: Download hive assets
uses: actions/download-artifact@v5
uses: actions/download-artifact@v7
with:
name: hive_assets
path: /tmp
- name: Download reth image
uses: actions/download-artifact@v5
uses: actions/download-artifact@v7
with:
name: artifacts
path: /tmp
@@ -248,7 +245,7 @@ jobs:
notify-on-error:
needs: test
if: failure()
runs-on: depot-ubuntu-latest
runs-on: ubuntu-latest
steps:
- name: Slack Webhook Action
uses: rtCamp/action-slack-notify@v2

View File

@@ -6,7 +6,7 @@ on:
pull_request:
merge_group:
push:
branches: ["**"]
branches: [main]
schedule:
# Run once a day at 3:00 UTC
- cron: "0 3 * * *"
@@ -14,6 +14,7 @@ on:
env:
CARGO_TERM_COLOR: always
SEED: rustethereumethereumrust
RUSTC_WRAPPER: "sccache"
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@@ -23,7 +24,7 @@ jobs:
test:
name: test / ${{ matrix.network }}
if: github.event_name != 'schedule'
runs-on: depot-ubuntu-latest-16
runs-on: depot-ubuntu-latest-4
env:
RUST_BACKTRACE: 1
strategy:
@@ -37,6 +38,7 @@ jobs:
- name: Install Geth
run: .github/assets/install_geth.sh
- uses: taiki-e/install-action@nextest
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -74,6 +76,7 @@ jobs:
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@nextest
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true

View File

@@ -41,7 +41,7 @@ jobs:
fetch-depth: 0
- name: Download reth image
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
with:
name: artifacts
path: /tmp
@@ -85,7 +85,7 @@ jobs:
notify-on-error:
needs: test
if: failure()
runs-on: depot-ubuntu-latest
runs-on: ubuntu-latest
steps:
- name: Slack Webhook Action
uses: rtCamp/action-slack-notify@v2

View File

@@ -39,7 +39,7 @@ jobs:
fetch-depth: 0
- name: Download reth image
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
with:
name: artifacts
path: /tmp
@@ -58,7 +58,7 @@ jobs:
notify-on-error:
needs: test
if: failure()
runs-on: depot-ubuntu-latest
runs-on: ubuntu-latest
steps:
- name: Slack Webhook Action
uses: rtCamp/action-slack-notify@v2

View File

@@ -4,10 +4,11 @@ on:
pull_request:
merge_group:
push:
branches: ["**"]
branches: [main]
env:
CARGO_TERM_COLOR: always
RUSTC_WRAPPER: "sccache"
jobs:
clippy-binaries:
@@ -26,6 +27,7 @@ jobs:
- uses: dtolnay/rust-toolchain@clippy
with:
components: clippy
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -40,7 +42,7 @@ jobs:
clippy:
name: clippy
runs-on: depot-ubuntu-latest-16
runs-on: depot-ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
@@ -48,6 +50,7 @@ jobs:
- uses: dtolnay/rust-toolchain@nightly
with:
components: clippy
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -65,6 +68,7 @@ jobs:
with:
target: wasm32-wasip1
- uses: taiki-e/install-action@cargo-hack
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -84,6 +88,7 @@ jobs:
with:
target: riscv32imac-unknown-none-elf
- uses: taiki-e/install-action@cargo-hack
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -93,17 +98,18 @@ jobs:
crate-checks:
name: crate-checks (${{ matrix.partition }}/${{ matrix.total_partitions }})
runs-on: depot-ubuntu-latest-16
runs-on: depot-ubuntu-latest-4
strategy:
matrix:
partition: [1, 2]
total_partitions: [2]
partition: [1, 2, 3]
total_partitions: [3]
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@cargo-hack
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -124,6 +130,7 @@ jobs:
- uses: dtolnay/rust-toolchain@master
with:
toolchain: "1.88" # MSRV
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -133,12 +140,13 @@ jobs:
docs:
name: docs
runs-on: depot-ubuntu-latest-16
runs-on: depot-ubuntu-latest-4
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@nightly
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -158,17 +166,19 @@ jobs:
- uses: dtolnay/rust-toolchain@nightly
with:
components: rustfmt
- uses: mozilla-actions/sccache-action@v0.0.9
- name: Run fmt
run: cargo fmt --all --check
udeps:
name: udeps
runs-on: depot-ubuntu-latest-16
runs-on: depot-ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@nightly
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -177,12 +187,13 @@ jobs:
book:
name: book
runs-on: depot-ubuntu-latest-16
runs-on: depot-ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@nightly
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -234,17 +245,14 @@ jobs:
# Checks that selected crates can compile with power set of features
features:
name: features (${{ matrix.partition }}/${{ matrix.total_partitions }})
runs-on: depot-ubuntu-latest-16
strategy:
matrix:
partition: [1, 2]
total_partitions: [2]
name: features
runs-on: depot-ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@clippy
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -256,7 +264,7 @@ jobs:
--package reth-primitives-traits \
--package reth-primitives \
--feature-powerset \
--partition ${{ matrix.partition }}/${{ matrix.total_partitions }}
--depth 2
env:
RUSTFLAGS: -D warnings
@@ -267,6 +275,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: rui314/setup-mold@v1
- uses: taiki-e/cache-cargo-install-action@v2
with:

View File

@@ -50,7 +50,7 @@ jobs:
- name: Upload reth image
id: upload
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: artifacts
path: ./artifacts

View File

@@ -22,6 +22,7 @@ env:
CARGO_TERM_COLOR: always
DOCKER_IMAGE_NAME_URL: https://ghcr.io/${{ github.repository_owner }}/reth
DOCKER_OP_IMAGE_NAME_URL: https://ghcr.io/${{ github.repository_owner }}/op-reth
RUSTC_WRAPPER: "sccache"
jobs:
dry-run:
@@ -51,6 +52,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: mozilla-actions/sccache-action@v0.0.9
- name: Verify crate version matches tag
# Check that the Cargo version starts with the tag,
# so that Cargo version 1.4.8 can be matched against both v1.4.8 and v1.4.8-rc.1
@@ -104,6 +106,7 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
with:
target: ${{ matrix.configs.target }}
- uses: mozilla-actions/sccache-action@v0.0.9
- name: Install cross main
id: cross_main
run: |
@@ -141,14 +144,14 @@ jobs:
- name: Upload artifact
if: ${{ github.event.inputs.dry_run != 'true' }}
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz
path: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz
- name: Upload signature
if: ${{ github.event.inputs.dry_run != 'true' }}
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz.asc
path: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz.asc
@@ -170,7 +173,7 @@ jobs:
with:
fetch-depth: 0
- name: Download artifacts
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
- name: Generate full changelog
id: changelog
run: |

View File

@@ -42,7 +42,7 @@ jobs:
echo "Binaries SHA256 on ${{ matrix.machine }}: $(cat checksum.sha256)"
- name: Upload the hash
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: checksum-${{ matrix.machine }}
path: |
@@ -55,12 +55,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Download artifacts from machine-1
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: checksum-machine-1
path: machine-1/
- name: Download artifacts from machine-2
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: checksum-machine-2
path: machine-2/

View File

@@ -6,12 +6,13 @@ on:
pull_request:
merge_group:
push:
branches: ["**"]
branches: [main]
env:
CARGO_TERM_COLOR: always
FROM_BLOCK: 0
TO_BLOCK: 50000
RUSTC_WRAPPER: "sccache"
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@@ -31,6 +32,7 @@ jobs:
- uses: actions/checkout@v6
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true

View File

@@ -9,6 +9,7 @@ on:
env:
CARGO_TERM_COLOR: always
RUSTC_WRAPPER: "sccache"
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@@ -41,6 +42,7 @@ jobs:
- uses: actions/checkout@v6
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true

View File

@@ -9,6 +9,7 @@ on:
env:
CARGO_TERM_COLOR: always
RUSTC_WRAPPER: "sccache"
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@@ -41,6 +42,7 @@ jobs:
- uses: actions/checkout@v6
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true

View File

@@ -4,14 +4,14 @@ name: unit
on:
pull_request:
branches: ["**"]
merge_group:
push:
branches: ["**"]
branches: [main]
env:
CARGO_TERM_COLOR: always
SEED: rustethereumethereumrust
RUSTC_WRAPPER: "sccache"
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@@ -20,7 +20,7 @@ concurrency:
jobs:
test:
name: test / ${{ matrix.type }} (${{ matrix.partition }}/${{ matrix.total_partitions }})
runs-on: depot-ubuntu-latest-16
runs-on: depot-ubuntu-latest-4
env:
RUST_BACKTRACE: 1
strategy:
@@ -47,6 +47,7 @@ jobs:
- uses: actions/checkout@v6
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -92,6 +93,7 @@ jobs:
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@nextest
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -99,7 +101,7 @@ jobs:
doc:
name: doc tests
runs-on: depot-ubuntu-latest-16
runs-on: depot-ubuntu-latest
env:
RUST_BACKTRACE: 1
timeout-minutes: 30
@@ -107,6 +109,7 @@ jobs:
- uses: actions/checkout@v6
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true

View File

@@ -27,7 +27,7 @@ jobs:
./fetch_superchain_config.sh
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
uses: peter-evans/create-pull-request@v8
with:
commit-message: "chore: update superchain config"
title: "chore: update superchain config"

View File

@@ -4,14 +4,17 @@ name: windows
on:
push:
branches: ["**"]
branches: [main]
pull_request:
branches: ["**"]
branches: [main]
merge_group:
env:
RUSTC_WRAPPER: "sccache"
jobs:
check-reth:
runs-on: depot-ubuntu-latest-16
runs-on: depot-ubuntu-latest
timeout-minutes: 60
steps:
@@ -21,6 +24,7 @@ jobs:
with:
target: x86_64-pc-windows-gnu
- uses: taiki-e/install-action@cross
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -30,7 +34,7 @@ jobs:
run: cargo check --target x86_64-pc-windows-gnu
check-op-reth:
runs-on: depot-ubuntu-latest-16
runs-on: depot-ubuntu-latest
timeout-minutes: 60
steps:
@@ -40,6 +44,7 @@ jobs:
with:
target: x86_64-pc-windows-gnu
- uses: taiki-e/install-action@cross
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true

494
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -376,11 +376,11 @@ reth-era-utils = { path = "crates/era-utils" }
reth-errors = { path = "crates/errors" }
reth-eth-wire = { path = "crates/net/eth-wire" }
reth-eth-wire-types = { path = "crates/net/eth-wire-types" }
reth-ethereum-payload-builder = { path = "crates/ethereum/payload" }
reth-ethereum-cli = { path = "crates/ethereum/cli", default-features = false }
reth-ethereum-consensus = { path = "crates/ethereum/consensus", default-features = false }
reth-ethereum-engine-primitives = { path = "crates/ethereum/engine-primitives", default-features = false }
reth-ethereum-forks = { path = "crates/ethereum/hardforks", default-features = false }
reth-ethereum-payload-builder = { path = "crates/ethereum/payload" }
reth-ethereum-primitives = { path = "crates/ethereum/primitives", default-features = false }
reth-ethereum = { path = "crates/ethereum/reth" }
reth-etl = { path = "crates/etl" }
@@ -475,24 +475,24 @@ reth-ress-provider = { path = "crates/ress/provider" }
# revm
revm = { version = "33.1.0", default-features = false }
revm-bytecode = { version = "7.1.1", default-features = false }
revm-database = { version = "9.0.6", default-features = false }
revm-database = { version = "9.0.5", default-features = false }
revm-state = { version = "8.1.1", default-features = false }
revm-primitives = { version = "21.0.2", default-features = false }
revm-interpreter = { version = "31.1.0", default-features = false }
revm-database-interface = { version = "8.0.5", default-features = false }
op-revm = { version = "14.1.0", default-features = false }
revm-inspectors = "0.33.1"
revm-inspectors = "0.33.2"
# eth
alloy-primitives = { version = "1.4.1", default-features = false, features = ["map-foldhash"] }
alloy-chains = { version = "0.2.5", default-features = false }
alloy-evm = { version = "0.24.2", default-features = false }
alloy-dyn-abi = "1.4.1"
alloy-eip2124 = { version = "0.2.0", default-features = false }
alloy-eip7928 = { version = "0.1.0" }
alloy-evm = { version = "0.25.1", default-features = false }
alloy-primitives = { version = "1.5.0", default-features = false, features = ["map-foldhash"] }
alloy-rlp = { version = "0.3.10", default-features = false, features = ["core-net"] }
alloy-sol-macro = "1.4.1"
alloy-sol-types = { version = "1.4.1", default-features = false }
alloy-sol-macro = "1.5.0"
alloy-sol-types = { version = "1.5.0", default-features = false }
alloy-trie = { version = "0.9.1", default-features = false }
alloy-hardforks = "0.4.5"
@@ -526,13 +526,13 @@ alloy-transport-ipc = { version = "1.1.3", default-features = false }
alloy-transport-ws = { version = "1.1.3", default-features = false }
# op
alloy-op-evm = { version = "0.24.2", default-features = false }
alloy-op-hardforks = "0.4.3"
op-alloy-rpc-types = { version = "0.22.0", default-features = false }
op-alloy-rpc-types-engine = { version = "0.22.0", default-features = false }
op-alloy-network = { version = "0.22.0", default-features = false }
op-alloy-consensus = { version = "0.22.0", default-features = false }
op-alloy-rpc-jsonrpsee = { version = "0.22.0", default-features = false }
alloy-op-evm = { version = "0.25.0", default-features = false }
alloy-op-hardforks = "0.4.4"
op-alloy-rpc-types = { version = "0.23.1", default-features = false }
op-alloy-rpc-types-engine = { version = "0.23.1", default-features = false }
op-alloy-network = { version = "0.23.1", default-features = false }
op-alloy-consensus = { version = "0.23.1", default-features = false }
op-alloy-rpc-jsonrpsee = { version = "0.23.1", default-features = false }
op-alloy-flz = { version = "0.13.1", default-features = false }
# misc
@@ -587,6 +587,7 @@ url = { version = "2.3", default-features = false }
zstd = "0.13"
byteorder = "1"
mini-moka = "0.10"
moka = "0.12"
tar-no-std = { version = "0.3.2", default-features = false }
miniz_oxide = { version = "0.8.4", default-features = false }
chrono = "0.4.41"
@@ -729,6 +730,7 @@ socket2 = { version = "0.5", default-features = false }
sysinfo = { version = "0.33", default-features = false }
tracing-journald = "0.3"
tracing-logfmt = "0.3.3"
tracing-samply = "0.1"
tracing-subscriber = { version = "0.3", default-features = false }
triehash = "0.8"
typenum = "1.15.0"
@@ -736,40 +738,39 @@ vergen = "9.0.4"
visibility = "0.1.1"
walkdir = "2.3.3"
vergen-git2 = "1.0.5"
# networking
ipnet = "2.11"
[patch.crates-io]
alloy-consensus = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-contract = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-eips = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-genesis = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-json-rpc = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-network = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-network-primitives = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-provider = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-pubsub = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-rpc-client = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-rpc-types = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-rpc-types-admin = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-rpc-types-anvil = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-rpc-types-beacon = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-rpc-types-debug = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-rpc-types-engine = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-rpc-types-eth = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-rpc-types-mev = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-rpc-types-trace = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-rpc-types-txpool = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-serde = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-signer = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-signer-local = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-transport = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-transport-http = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-transport-ipc = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
alloy-transport-ws = { git = "https://github.com/Soubhik-10/alloy", branch = "bal" }
# alloy-hardforks = { git = "https://github.com/Rimeeeeee/hardforks", branch = "amsterdam" }
# [patch.crates-io]
# alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types-debug = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types-mev = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-op-hardforks = { git = "https://github.com/Rimeeeeee/hardforks", branch = "amsterdam" }
# op-alloy-consensus = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" }
# op-alloy-network = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" }
# op-alloy-rpc-types = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" }
@@ -777,20 +778,6 @@ alloy-transport-ws = { git = "https://github.com/Soubhik-10/alloy", branch = "ba
# op-alloy-rpc-jsonrpsee = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" }
#
# revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "1207e33" }
# need to change to bluealloy rakita/bal
revm = { git = "https://github.com/Rimeeeeee/revm", branch = "rakita/bal" }
alloy-evm = { git = "https://github.com/Rimeeeeee/evm", branch = "new-approach4" }
alloy-op-evm = { git = "https://github.com/Rimeeeeee/evm", branch = "new-approach4" }
revm-bytecode = { git = "https://github.com/Rimeeeeee/revm", branch = "rakita/bal" }
revm-database = { git = "https://github.com/Rimeeeeee/revm", branch = "rakita/bal" }
revm-state = { git = "https://github.com/Rimeeeeee/revm", branch = "rakita/bal" }
revm-primitives = { git = "https://github.com/Rimeeeeee/revm", branch = "rakita/bal" }
revm-interpreter = { git = "https://github.com/Rimeeeeee/revm", branch = "rakita/bal" }
revm-inspector = { git = "https://github.com/Rimeeeeee/revm", branch = "rakita/bal" }
revm-context = { git = "https://github.com/Rimeeeeee/revm", branch = "rakita/bal" }
revm-context-interface = { git = "https://github.com/Rimeeeeee/revm", branch = "rakita/bal" }
revm-database-interface = { git = "https://github.com/Rimeeeeee/revm", branch = "rakita/bal" }
op-revm = { git = "https://github.com/Rimeeeeee/revm", branch = "rakita/bal" }
#
# jsonrpsee = { git = "https://github.com/paradigmxyz/jsonrpsee", branch = "matt/make-rpc-service-pub" }
# jsonrpsee-core = { git = "https://github.com/paradigmxyz/jsonrpsee", branch = "matt/make-rpc-service-pub" }

View File

@@ -18,7 +18,7 @@ FROM chef AS builder
COPY --from=planner /app/recipe.json recipe.json
# Build profile, release by default
ARG BUILD_PROFILE=release
ARG BUILD_PROFILE=maxperf
ENV BUILD_PROFILE=$BUILD_PROFILE
# Extra Cargo flags

View File

@@ -14,7 +14,7 @@ RUN cargo chef prepare --recipe-path recipe.json
FROM chef AS builder
COPY --from=planner /app/recipe.json recipe.json
ARG BUILD_PROFILE=release
ARG BUILD_PROFILE=maxperf
ENV BUILD_PROFILE=$BUILD_PROFILE
ARG RUSTFLAGS=""

View File

@@ -521,5 +521,3 @@ pr:
make update-book-cli && \
cargo docs --document-private-items && \
make test
check-features:

View File

@@ -329,6 +329,7 @@ pub(crate) async fn run_comparison(args: Args, _ctx: CliContext) -> Result<()> {
output_dir.clone(),
git_manager.clone(),
args.features.clone(),
args.profile,
)?;
// Initialize node manager
let mut node_manager = NodeManager::new(&args);

View File

@@ -14,6 +14,7 @@ pub(crate) struct CompilationManager {
output_dir: PathBuf,
git_manager: GitManager,
features: String,
enable_profiling: bool,
}
impl CompilationManager {
@@ -23,8 +24,9 @@ impl CompilationManager {
output_dir: PathBuf,
git_manager: GitManager,
features: String,
enable_profiling: bool,
) -> Result<Self> {
Ok(Self { repo_root, output_dir, git_manager, features })
Ok(Self { repo_root, output_dir, git_manager, features, enable_profiling })
}
/// Detect if the RPC endpoint is an Optimism chain
@@ -100,9 +102,18 @@ impl CompilationManager {
let mut cmd = Command::new("cargo");
cmd.arg("build").arg("--profile").arg("profiling");
// Add features
cmd.arg("--features").arg(&self.features);
info!("Using features: {}", self.features);
// Append samply feature when profiling to enable tracing span markers.
// NOTE: The `samply` feature must exist in the branch being compiled. If comparing
// against an older branch that predates the samply integration, compilation will fail
// or markers won't appear. In that case, omit --profile or ensure both branches
// include the samply feature support.
let features = if self.enable_profiling && !self.features.contains("samply") {
format!("{},samply", self.features)
} else {
self.features.clone()
};
cmd.arg("--features").arg(&features);
info!("Using features: {}", features);
// Add bin-specific arguments for optimism
if is_optimism {

View File

@@ -150,45 +150,6 @@ pub(crate) fn block_to_new_payload(
let (payload, sidecar) = ExecutionPayload::from_block_slow(&block);
let (version, params) = match payload {
ExecutionPayload::V4(payload) => {
let cancun = sidecar.cancun().unwrap();
if let Some(prague) = sidecar.prague() {
if is_optimism {
(
EngineApiMessageVersion::V4,
serde_json::to_value((
OpExecutionPayloadV4 {
payload_inner: payload.payload_inner,
withdrawals_root: block.withdrawals_root.unwrap(),
},
cancun.versioned_hashes.clone(),
cancun.parent_beacon_block_root,
Requests::default(),
))?,
)
} else {
(
EngineApiMessageVersion::V4,
serde_json::to_value((
payload,
cancun.versioned_hashes.clone(),
cancun.parent_beacon_block_root,
prague.requests.requests_hash(),
))?,
)
}
} else {
(
EngineApiMessageVersion::V3,
serde_json::to_value((
payload,
cancun.versioned_hashes.clone(),
cancun.parent_beacon_block_root,
))?,
)
}
}
ExecutionPayload::V3(payload) => {
let cancun = sidecar.cancun().unwrap();
@@ -283,10 +244,7 @@ pub(crate) async fn call_forkchoice_updated<N, P: EngineApiValidWaitExt<N>>(
payload_attributes: Option<PayloadAttributes>,
) -> TransportResult<ForkchoiceUpdated> {
match message_version {
EngineApiMessageVersion::V3 |
EngineApiMessageVersion::V4 |
EngineApiMessageVersion::V5 |
EngineApiMessageVersion::V6 => {
EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 | EngineApiMessageVersion::V5 => {
provider.fork_choice_updated_v3_wait(forkchoice_state, payload_attributes).await
}
EngineApiMessageVersion::V2 => {

View File

@@ -81,12 +81,16 @@ backon.workspace = true
tempfile.workspace = true
[features]
default = ["jemalloc", "otlp", "reth-revm/portable", "js-tracer"]
default = ["jemalloc", "otlp", "reth-revm/portable", "js-tracer", "keccak-cache-global", "asm-keccak"]
otlp = [
"reth-ethereum-cli/otlp",
"reth-node-core/otlp",
]
samply = [
"reth-ethereum-cli/samply",
"reth-node-core/samply",
]
js-tracer = [
"reth-node-builder/js-tracer",
"reth-node-ethereum/js-tracer",
@@ -102,7 +106,10 @@ asm-keccak = [
"reth-ethereum-cli/asm-keccak",
"reth-node-ethereum/asm-keccak",
]
keccak-cache-global = [
"reth-node-core/keccak-cache-global",
"reth-node-ethereum/keccak-cache-global",
]
jemalloc = [
"reth-cli-util/jemalloc",
"reth-node-core/jemalloc",

View File

@@ -173,7 +173,6 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
transactions: transactions.into_iter().map(|tx| tx.into_inner()).collect(),
ommers: Vec::new(),
withdrawals: Some(vec![].into()),
block_access_list: None,
},
)
}

View File

@@ -45,10 +45,6 @@ use reth_network_peers::{
};
use reth_primitives_traits::{sync::LazyLock, BlockHeader, SealedHeader};
/// The hash of an empty block access list.
const EMPTY_BLOCK_ACCESS_LIST_HASH: B256 =
b256!("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347");
/// Helper method building a [`Header`] given [`Genesis`] and [`ChainHardforks`].
pub fn make_genesis_header(genesis: &Genesis, hardforks: &ChainHardforks) -> Header {
// If London is activated at genesis, we set the initial base fee as per EIP-1559.
@@ -83,13 +79,9 @@ pub fn make_genesis_header(genesis: &Genesis, hardforks: &ChainHardforks) -> Hea
.active_at_timestamp(genesis.timestamp)
.then_some(EMPTY_REQUESTS_HASH);
// If Amsterdam is activated at genesis we set block access list hash empty hash.
let block_access_list_hash = hardforks
.fork(EthereumHardfork::Amsterdam)
.active_at_timestamp(genesis.timestamp)
.then_some(EMPTY_BLOCK_ACCESS_LIST_HASH);
Header {
number: genesis.number.unwrap_or_default(),
parent_hash: genesis.parent_hash.unwrap_or_default(),
gas_limit: genesis.gas_limit,
difficulty: genesis.difficulty,
nonce: genesis.nonce.into(),
@@ -104,7 +96,6 @@ pub fn make_genesis_header(genesis: &Genesis, hardforks: &ChainHardforks) -> Hea
blob_gas_used,
excess_blob_gas,
requests_hash,
block_access_list_hash,
..Default::default()
}
}
@@ -309,7 +300,6 @@ pub fn create_chain_config(
cancun_time: timestamp(EthereumHardfork::Cancun),
prague_time: timestamp(EthereumHardfork::Prague),
osaka_time: timestamp(EthereumHardfork::Osaka),
amsterdam_time: timestamp(EthereumHardfork::Amsterdam),
bpo1_time: timestamp(EthereumHardfork::Bpo1),
bpo2_time: timestamp(EthereumHardfork::Bpo2),
bpo3_time: timestamp(EthereumHardfork::Bpo3),
@@ -899,7 +889,6 @@ impl From<Genesis> for ChainSpec {
(EthereumHardfork::Bpo3.boxed(), genesis.config.bpo3_time),
(EthereumHardfork::Bpo4.boxed(), genesis.config.bpo4_time),
(EthereumHardfork::Bpo5.boxed(), genesis.config.bpo5_time),
(EthereumHardfork::Amsterdam.boxed(), genesis.config.amsterdam_time),
];
let mut time_hardforks = time_hardfork_opts
@@ -1206,19 +1195,6 @@ impl ChainSpecBuilder {
self
}
/// Enable Amsterdam at genesis.
pub fn amsterdam_activated(mut self) -> Self {
self = self.osaka_activated();
self.hardforks.insert(EthereumHardfork::Amsterdam, ForkCondition::Timestamp(0));
self
}
/// Enable Amsterdam at the given timestamp.
pub fn with_amsterdam_at(mut self, timestamp: u64) -> Self {
self.hardforks.insert(EthereumHardfork::Amsterdam, ForkCondition::Timestamp(timestamp));
self
}
/// Build the resulting [`ChainSpec`].
///
/// # Panics

View File

@@ -23,7 +23,10 @@ use reth_node_core::{
dirs::{ChainPath, DataDirPath},
};
use reth_provider::{
providers::{BlockchainProvider, NodeTypesForProvider, StaticFileProvider},
providers::{
BlockchainProvider, NodeTypesForProvider, RocksDBProvider, StaticFileProvider,
StaticFileProviderBuilder,
},
ProviderFactory, StaticFileProviderFactory,
};
use reth_stages::{sets::DefaultStages, Pipeline, PipelineTarget};
@@ -75,10 +78,12 @@ impl<C: ChainSpecParser> EnvironmentArgs<C> {
let data_dir = self.datadir.clone().resolve_datadir(self.chain.chain());
let db_path = data_dir.db();
let sf_path = data_dir.static_files();
let rocksdb_path = data_dir.rocksdb();
if access.is_read_write() {
reth_fs_util::create_dir_all(&db_path)?;
reth_fs_util::create_dir_all(&sf_path)?;
reth_fs_util::create_dir_all(&rocksdb_path)?;
}
let config_path = self.config.clone().unwrap_or_else(|| data_dir.config());
@@ -98,18 +103,32 @@ impl<C: ChainSpecParser> EnvironmentArgs<C> {
}
info!(target: "reth::cli", ?db_path, ?sf_path, "Opening storage");
let genesis_block_number = self.chain.genesis().number.unwrap_or_default();
let (db, sfp) = match access {
AccessRights::RW => (
Arc::new(init_db(db_path, self.db.database_args())?),
StaticFileProvider::read_write(sf_path)?,
),
AccessRights::RO | AccessRights::RoInconsistent => (
Arc::new(open_db_read_only(&db_path, self.db.database_args())?),
StaticFileProvider::read_only(sf_path, false)?,
StaticFileProviderBuilder::read_write(sf_path)?
.with_genesis_block_number(genesis_block_number)
.build()?,
),
AccessRights::RO | AccessRights::RoInconsistent => {
(Arc::new(open_db_read_only(&db_path, self.db.database_args())?), {
let provider = StaticFileProviderBuilder::read_only(sf_path)?
.with_genesis_block_number(genesis_block_number)
.build()?;
provider.watch_directory();
provider
})
}
};
// TransactionDB only support read-write mode
let rocksdb_provider = RocksDBProvider::builder(data_dir.rocksdb())
.with_default_tables()
.with_database_log_level(self.db.log_level)
.build()?;
let provider_factory = self.create_provider_factory(&config, db, sfp, access)?;
let provider_factory =
self.create_provider_factory(&config, db, sfp, rocksdb_provider, access)?;
if access.is_read_write() {
debug!(target: "reth::cli", chain=%self.chain.chain(), genesis=?self.chain.genesis_hash(), "Initializing genesis");
init_genesis_with_settings(&provider_factory, self.static_files.to_settings())?;
@@ -128,6 +147,7 @@ impl<C: ChainSpecParser> EnvironmentArgs<C> {
config: &Config,
db: Arc<DatabaseEnv>,
static_file_provider: StaticFileProvider<N::Primitives>,
rocksdb_provider: RocksDBProvider,
access: AccessRights,
) -> eyre::Result<ProviderFactory<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>>
where
@@ -138,6 +158,7 @@ impl<C: ChainSpecParser> EnvironmentArgs<C> {
db,
self.chain.clone(),
static_file_provider,
rocksdb_provider,
)?
.with_prune_modes(prune_modes.clone());

View File

@@ -2,6 +2,7 @@ use crate::common::{AccessRights, CliNodeTypes, Environment, EnvironmentArgs};
use clap::{Parser, Subcommand};
use reth_chainspec::{EthChainSpec, EthereumHardforks};
use reth_cli::chainspec::ChainSpecParser;
use reth_cli_runner::CliContext;
use reth_db::version::{get_db_version, DatabaseVersionError, DB_VERSION};
use reth_db_common::DbTool;
use std::{
@@ -79,7 +80,10 @@ macro_rules! db_exec {
impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C> {
/// Execute `db` command
pub async fn execute<N: CliNodeTypes<ChainSpec = C::ChainSpec>>(self) -> eyre::Result<()> {
pub async fn execute<N: CliNodeTypes<ChainSpec = C::ChainSpec>>(
self,
ctx: CliContext,
) -> eyre::Result<()> {
let data_dir = self.env.datadir.clone().resolve_datadir(self.env.chain.chain());
let db_path = data_dir.db();
let static_files_path = data_dir.static_files();
@@ -158,7 +162,7 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C>
let access_rights =
if command.dry_run { AccessRights::RO } else { AccessRights::RW };
db_exec!(self.env, tool, N, access_rights, {
command.execute(&tool)?;
command.execute(&tool, ctx.task_executor.clone())?;
});
}
Subcommands::StaticFileHeader(command) => {

View File

@@ -18,6 +18,7 @@ use reth_node_metrics::{
};
use reth_provider::{providers::ProviderNodeTypes, ChainSpecProvider, StageCheckpointReader};
use reth_stages::StageId;
use reth_tasks::TaskExecutor;
use reth_trie::{
verify::{Output, Verifier},
Nibbles,
@@ -48,52 +49,37 @@ pub struct Command {
impl Command {
/// Execute `db repair-trie` command
pub fn execute<N: ProviderNodeTypes>(self, tool: &DbTool<N>) -> eyre::Result<()> {
pub fn execute<N: ProviderNodeTypes>(
self,
tool: &DbTool<N>,
task_executor: TaskExecutor,
) -> eyre::Result<()> {
// Set up metrics server if requested
let _metrics_handle = if let Some(listen_addr) = self.metrics {
// Spawn an OS thread with a single-threaded tokio runtime for the metrics server
let chain_name = tool.provider_factory.chain_spec().chain().to_string();
let executor = task_executor.clone();
let handle = std::thread::Builder::new().name("metrics-server".to_string()).spawn(
move || {
// Create a single-threaded tokio runtime
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to create tokio runtime for metrics server");
let handle = task_executor.spawn_critical("metrics server", async move {
let config = MetricServerConfig::new(
listen_addr,
VersionInfo {
version: version_metadata().cargo_pkg_version.as_ref(),
build_timestamp: version_metadata().vergen_build_timestamp.as_ref(),
cargo_features: version_metadata().vergen_cargo_features.as_ref(),
git_sha: version_metadata().vergen_git_sha.as_ref(),
target_triple: version_metadata().vergen_cargo_target_triple.as_ref(),
build_profile: version_metadata().build_profile_name.as_ref(),
},
ChainSpecInfo { name: chain_name },
executor,
Hooks::builder().build(),
);
let handle = runtime.handle().clone();
runtime.block_on(async move {
let task_manager = reth_tasks::TaskManager::new(handle.clone());
let task_executor = task_manager.executor();
let config = MetricServerConfig::new(
listen_addr,
VersionInfo {
version: version_metadata().cargo_pkg_version.as_ref(),
build_timestamp: version_metadata().vergen_build_timestamp.as_ref(),
cargo_features: version_metadata().vergen_cargo_features.as_ref(),
git_sha: version_metadata().vergen_git_sha.as_ref(),
target_triple: version_metadata()
.vergen_cargo_target_triple
.as_ref(),
build_profile: version_metadata().build_profile_name.as_ref(),
},
ChainSpecInfo { name: chain_name },
task_executor,
Hooks::builder().build(),
);
// Spawn the metrics server
if let Err(e) = MetricServer::new(config).serve().await {
tracing::error!("Metrics server error: {}", e);
}
// Block forever to keep the runtime alive
std::future::pending::<()>().await
});
},
)?;
// Spawn the metrics server
if let Err(e) = MetricServer::new(config).serve().await {
tracing::error!("Metrics server error: {}", e);
}
});
Some(handle)
} else {

View File

@@ -92,6 +92,8 @@ impl Command {
receipts_in_static_files: _,
transaction_senders_in_static_files: _,
storages_history_in_rocksdb: _,
transaction_hash_numbers_in_rocksdb: _,
account_history_in_rocksdb: _,
} = settings.unwrap_or_else(StorageSettings::legacy);
// Update the setting based on the key

View File

@@ -1,8 +1,9 @@
//! Command that initializes the node from a genesis file.
use crate::common::{AccessRights, CliNodeTypes, Environment, EnvironmentArgs};
use alloy_consensus::BlockHeader;
use clap::Parser;
use reth_chainspec::{EthChainSpec, EthereumHardforks};
use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks};
use reth_cli::chainspec::ChainSpecParser;
use reth_provider::BlockHashReader;
use std::sync::Arc;
@@ -22,8 +23,9 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> InitComman
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
let genesis_block_number = provider_factory.chain_spec().genesis_header().number();
let hash = provider_factory
.block_hash(0)?
.block_hash(genesis_block_number)?
.ok_or_else(|| eyre::eyre!("Genesis hash not found."))?;
info!(target: "reth::cli", hash = ?hash, "Genesis block written");

View File

@@ -72,7 +72,7 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>
.split();
if result.len() != 1 {
eyre::bail!(
"Invalid number of headers received. Expected: 1. Received: {}",
"Invalid number of bodies received. Expected: 1. Received: {}",
result.len()
)
}
@@ -189,7 +189,7 @@ impl<C: ChainSpecParser> DownloadArgs<C> {
let net = NetworkConfigBuilder::<N::NetworkPrimitives>::new(p2p_secret_key)
.peer_config(config.peers_config_with_basic_nodes_from_file(None))
.external_ip_resolver(self.network.nat)
.external_ip_resolver(self.network.nat.clone())
.network_id(self.network.network_id)
.boot_nodes(boot_nodes.clone())
.apply(|builder| {

View File

@@ -110,7 +110,6 @@ impl<C: ChainSpecParser> Command<C> {
tx.clear::<tables::TransactionBlocks>()?;
tx.clear::<tables::BlockOmmers<HeaderTy<N>>>()?;
tx.clear::<tables::BlockWithdrawals>()?;
tx.clear::<tables::BlockAccessLists>()?;
reset_stage_checkpoint(tx, StageId::Bodies)?;
insert_genesis_header(&provider_rw, &self.env.chain)?;

View File

@@ -9,7 +9,7 @@ use reth_evm::ConfigureEvm;
use reth_node_builder::NodeTypesWithDB;
use reth_node_core::dirs::{ChainPath, DataDirPath};
use reth_provider::{
providers::{ProviderNodeTypes, StaticFileProvider},
providers::{ProviderNodeTypes, RocksDBProvider, StaticFileProvider},
DatabaseProviderFactory, ProviderFactory,
};
use reth_stages::{stages::ExecutionStage, Stage, StageCheckpoint, UnwindInput};
@@ -42,6 +42,7 @@ where
Arc::new(output_db),
db_tool.chain(),
StaticFileProvider::read_write(output_datadir.static_files())?,
RocksDBProvider::builder(output_datadir.rocksdb()).build()?,
)?,
to,
from,

View File

@@ -6,7 +6,7 @@ use reth_db_api::{database::Database, table::TableImporter, tables};
use reth_db_common::DbTool;
use reth_node_core::dirs::{ChainPath, DataDirPath};
use reth_provider::{
providers::{ProviderNodeTypes, StaticFileProvider},
providers::{ProviderNodeTypes, RocksDBProvider, StaticFileProvider},
DatabaseProviderFactory, ProviderFactory,
};
use reth_stages::{stages::AccountHashingStage, Stage, StageCheckpoint, UnwindInput};
@@ -39,6 +39,7 @@ pub(crate) async fn dump_hashing_account_stage<N: ProviderNodeTypes<DB = Arc<Dat
Arc::new(output_db),
db_tool.chain(),
StaticFileProvider::read_write(output_datadir.static_files())?,
RocksDBProvider::builder(output_datadir.rocksdb()).build()?,
)?,
to,
from,

View File

@@ -5,7 +5,7 @@ use reth_db_api::{database::Database, table::TableImporter, tables};
use reth_db_common::DbTool;
use reth_node_core::dirs::{ChainPath, DataDirPath};
use reth_provider::{
providers::{ProviderNodeTypes, StaticFileProvider},
providers::{ProviderNodeTypes, RocksDBProvider, StaticFileProvider},
DatabaseProviderFactory, ProviderFactory,
};
use reth_stages::{stages::StorageHashingStage, Stage, StageCheckpoint, UnwindInput};
@@ -29,6 +29,7 @@ pub(crate) async fn dump_hashing_storage_stage<N: ProviderNodeTypes<DB = Arc<Dat
Arc::new(output_db),
db_tool.chain(),
StaticFileProvider::read_write(output_datadir.static_files())?,
RocksDBProvider::builder(output_datadir.rocksdb()).build()?,
)?,
to,
from,

View File

@@ -12,7 +12,7 @@ use reth_evm::ConfigureEvm;
use reth_exex::ExExManagerHandle;
use reth_node_core::dirs::{ChainPath, DataDirPath};
use reth_provider::{
providers::{ProviderNodeTypes, StaticFileProvider},
providers::{ProviderNodeTypes, RocksDBProvider, StaticFileProvider},
DatabaseProviderFactory, ProviderFactory,
};
use reth_stages::{
@@ -62,6 +62,7 @@ where
Arc::new(output_db),
db_tool.chain(),
StaticFileProvider::read_write(output_datadir.static_files())?,
RocksDBProvider::builder(output_datadir.rocksdb()).build()?,
)?,
to,
from,

View File

@@ -97,6 +97,57 @@ impl CliRunner {
command_res
}
/// Executes a command in a blocking context with access to `CliContext`.
///
/// See [`Runtime::spawn_blocking`](tokio::runtime::Runtime::spawn_blocking).
pub fn run_blocking_command_until_exit<F, E>(
self,
command: impl FnOnce(CliContext) -> F + Send + 'static,
) -> Result<(), E>
where
F: Future<Output = Result<(), E>> + Send + 'static,
E: Send + Sync + From<std::io::Error> + From<reth_tasks::PanickedTaskError> + 'static,
{
let AsyncCliRunner { context, mut task_manager, tokio_runtime } =
AsyncCliRunner::new(self.tokio_runtime);
// Spawn the command on the blocking thread pool
let handle = tokio_runtime.handle().clone();
let command_handle =
tokio_runtime.handle().spawn_blocking(move || handle.block_on(command(context)));
// Wait for the command to complete or ctrl-c
let command_res = tokio_runtime.block_on(run_to_completion_or_panic(
&mut task_manager,
run_until_ctrl_c(
async move { command_handle.await.expect("Failed to join blocking task") },
),
));
if command_res.is_err() {
error!(target: "reth::cli", "shutting down due to error");
} else {
debug!(target: "reth::cli", "shutting down gracefully");
task_manager.graceful_shutdown_with_timeout(Duration::from_secs(5));
}
// Shutdown the runtime on a separate thread
let (tx, rx) = mpsc::channel();
std::thread::Builder::new()
.name("tokio-runtime-shutdown".to_string())
.spawn(move || {
drop(tokio_runtime);
let _ = tx.send(());
})
.unwrap();
let _ = rx.recv_timeout(Duration::from_secs(5)).inspect_err(|err| {
debug!(target: "reth::cli", %err, "tokio runtime shutdown timed out");
});
command_res
}
/// Executes a regular future until completion or until external signal received.
pub fn run_until_ctrl_c<F, E>(self, fut: F) -> Result<(), E>
where

View File

@@ -14,14 +14,11 @@ workspace = true
# reth
reth-chainspec.workspace = true
reth-consensus.workspace = true
tracing.workspace = true
# ethereum
reth-primitives-traits.workspace = true
alloy-consensus.workspace = true
alloy-primitives.workspace = true
alloy-eips.workspace = true
alloy-rlp.workspace = true
[dev-dependencies]
alloy-primitives = { workspace = true, features = ["rand"] }
@@ -38,6 +35,4 @@ std = [
"reth-primitives-traits/std",
"reth-ethereum-primitives/std",
"alloy-primitives/std",
"alloy-rlp/std",
"tracing/std",
]

View File

@@ -69,29 +69,6 @@ pub fn validate_shanghai_withdrawals<B: Block>(
Ok(())
}
/// Validate that block access lists are present in Amsterdam
///
/// [EIP-7928]: https://eips.ethereum.org/EIPS/eip-7928
#[inline]
pub fn validate_amsterdam_block_access_lists<B: Block>(
block: &SealedBlock<B>,
) -> Result<(), ConsensusError> {
let bal = block.body().block_access_list().ok_or(ConsensusError::BlockAccessListMissing)?;
let bal_hash = alloy_primitives::keccak256(alloy_rlp::encode(bal));
let header_bal_hash =
block.block_access_list_hash().ok_or(ConsensusError::BlockAccessListHashMissing)?;
if bal_hash != header_bal_hash {
tracing::error!(
target: "consensus",
?header_bal_hash,
?bal,
"Block access list hash mismatch in validation.rs in L81"
);
return Err(ConsensusError::InvalidBalHash);
}
Ok(())
}
/// Validate that blob gas is present in the block if Cancun is active.
///
/// See [EIP-4844]: Shard Blob Transactions
@@ -154,21 +131,6 @@ where
}
_ => return Err(ConsensusError::WithdrawalsRootUnexpected),
}
if let (Some(expected_hash), Some(body_bal)) =
(header.block_access_list_hash(), body.block_access_list())
{
let got_hash = alloy_primitives::keccak256(alloy_rlp::encode(body_bal));
if got_hash != expected_hash {
tracing::error!(
target: "consensus",
?expected_hash,
?body_bal,
"Block access list hash mismatch in validation.rs in L164"
);
return Err(ConsensusError::InvalidBalHash);
}
}
Ok(())
}
@@ -255,10 +217,6 @@ where
})
}
if chain_spec.is_amsterdam_active_at_timestamp(block.header().timestamp()) {
validate_amsterdam_block_access_lists(block)?;
}
Ok(())
}
@@ -321,20 +279,28 @@ pub fn validate_against_parent_hash_number<H: BlockHeader>(
header: &H,
parent: &SealedHeader<H>,
) -> Result<(), ConsensusError> {
// Parent number is consistent.
if parent.number() + 1 != header.number() {
return Err(ConsensusError::ParentBlockNumberMismatch {
parent_block_number: parent.number(),
block_number: header.number(),
})
}
if parent.hash() != header.parent_hash() {
return Err(ConsensusError::ParentHashMismatch(
GotExpected { got: header.parent_hash(), expected: parent.hash() }.into(),
))
}
let Some(parent_number) = parent.number().checked_add(1) else {
// parent block already reached the maximum
return Err(ConsensusError::ParentBlockNumberMismatch {
parent_block_number: parent.number(),
block_number: u64::MAX,
})
};
// Parent number is consistent.
if parent_number != header.number() {
return Err(ConsensusError::ParentBlockNumberMismatch {
parent_block_number: parent.number(),
block_number: header.number(),
})
}
Ok(())
}
@@ -526,7 +492,6 @@ mod tests {
transactions: vec![transaction],
ommers: vec![],
withdrawals: Some(Withdrawals::default()),
block_access_list: None,
};
let block = SealedBlock::seal_slow(alloy_consensus::Block { header, body });

View File

@@ -404,41 +404,9 @@ pub enum ConsensusError {
/// The maximum allowed RLP length.
max_rlp_length: usize,
},
/// Error when the hash of block access list is different from the expected hash.
#[error("Block header's BAL hash does not match the computed BAL hash.")]
InvalidBalHash,
/// Error when the block access list hash is missing.
#[error("block access list hash missing")]
BlockAccessListHashMissing,
/// Error when the block access list is different from the expected access list.
#[error("Block's access list is invalid.")]
InvalidBlockAccessList,
/// Error when the block access list is missing.
#[error("block access list missing")]
BlockAccessListMissing,
/// Error when the block access list hash is unexpected.
#[error("block access list hash unexpected")]
BlockAccessListHashUnexpected,
/// Error when the block access list contains an account change that is not present in the
/// computed access list.
#[error("Block BAL contains an account change that is not present in the computed BAL.")]
InvalidBalExtraAccount,
/// Error when the block access list is missing an account change that is present in the
/// computed access list.
#[error("Block BAL is missing an account change that is present in the computed BAL.")]
InvalidBalMissingAccount,
/// EIP-7825: Transaction gas limit exceeds maximum allowed
#[error(transparent)]
TransactionGasLimitTooHigh(Box<TxGasLimitTooHighErr>),
/// Other, likely an injected L2 error.
#[error("{0}")]
Other(String),

View File

@@ -1,5 +1,5 @@
use crate::BlockProvider;
use alloy_provider::{ConnectionConfig, Network, Provider, ProviderBuilder};
use alloy_provider::{ConnectionConfig, Network, Provider, ProviderBuilder, WebSocketConfig};
use alloy_transport::TransportResult;
use futures::{Stream, StreamExt};
use reth_node_api::Block;
@@ -29,7 +29,12 @@ impl<N: Network, PrimitiveBlock> RpcBlockProvider<N, PrimitiveBlock> {
ProviderBuilder::default()
.connect_with_config(
rpc_url,
ConnectionConfig::default().with_max_retries(u32::MAX),
ConnectionConfig::default().with_max_retries(u32::MAX).with_ws_config(
WebSocketConfig::default()
// allow larger messages/frames for big blocks
.max_frame_size(Some(128 * 1024 * 1024))
.max_message_size(Some(128 * 1024 * 1024)),
),
)
.await?,
),

View File

@@ -11,6 +11,7 @@ use reth_node_builder::{
PayloadTypes,
};
use reth_node_core::args::{DiscoveryArgs, NetworkArgs, RpcServerArgs};
use reth_primitives_traits::AlloyBlockHeader;
use reth_provider::providers::BlockchainProvider;
use reth_rpc_server_types::RpcModuleSelection;
use reth_tasks::TaskManager;
@@ -157,8 +158,8 @@ where
.await?;
let node = NodeTestContext::new(node, self.attributes_generator).await?;
let genesis = node.block_hash(0);
let genesis_number = self.chain_spec.genesis_header().number();
let genesis = node.block_hash(genesis_number);
node.update_forkchoice(genesis, genesis).await?;
eyre::Ok(node)

View File

@@ -110,6 +110,7 @@ pub async fn setup_engine_with_chain_import(
// Create database path and static files path
let db_path = datadir.join("db");
let static_files_path = datadir.join("static_files");
let rocksdb_dir_path = datadir.join("rocksdb");
// Initialize the database using init_db (same as CLI import command)
// Use the same database arguments as the node will use
@@ -125,6 +126,7 @@ pub async fn setup_engine_with_chain_import(
db.clone(),
chain_spec.clone(),
reth_provider::providers::StaticFileProvider::read_write(static_files_path.clone())?,
reth_provider::providers::RocksDBProvider::builder(rocksdb_dir_path).build().unwrap(),
)?;
// Initialize genesis if needed
@@ -311,6 +313,7 @@ mod tests {
std::fs::create_dir_all(&datadir).unwrap();
let db_path = datadir.join("db");
let static_files_path = datadir.join("static_files");
let rocksdb_dir_path = datadir.join("rocksdb");
// Import the chain
{
@@ -324,6 +327,9 @@ mod tests {
chain_spec.clone(),
reth_provider::providers::StaticFileProvider::read_write(static_files_path.clone())
.unwrap(),
reth_provider::providers::RocksDBProvider::builder(rocksdb_dir_path.clone())
.build()
.unwrap(),
)
.expect("failed to create provider factory");
@@ -385,6 +391,9 @@ mod tests {
chain_spec.clone(),
reth_provider::providers::StaticFileProvider::read_only(static_files_path, false)
.unwrap(),
reth_provider::providers::RocksDBProvider::builder(rocksdb_dir_path)
.build()
.unwrap(),
)
.expect("failed to create provider factory");
@@ -472,11 +481,15 @@ mod tests {
// Create static files path
let static_files_path = datadir.join("static_files");
// Create rocksdb path
let rocksdb_dir_path = datadir.join("rocksdb");
// Create a provider factory
let provider_factory: ProviderFactory<MockNodeTypesWithDB> = ProviderFactory::new(
db.clone(),
chain_spec.clone(),
reth_provider::providers::StaticFileProvider::read_write(static_files_path).unwrap(),
reth_provider::providers::RocksDBProvider::builder(rocksdb_dir_path).build().unwrap(),
)
.expect("failed to create provider factory");

View File

@@ -56,7 +56,6 @@ pub fn generate_test_blocks(chain_spec: &ChainSpec, count: u64) -> Vec<SealedBlo
excess_blob_gas: None,
parent_beacon_block_root: None,
requests_hash: None,
block_access_list_hash: None,
};
// Set required fields based on chain spec
@@ -107,7 +106,6 @@ pub fn generate_test_blocks(chain_spec: &ChainSpec, count: u64) -> Vec<SealedBlo
transactions: vec![],
ommers: vec![],
withdrawals: header.withdrawals_root.is_some().then(Withdrawals::default),
block_access_list: None,
};
// Create the block

View File

@@ -448,14 +448,12 @@ mod tests {
nonce: account.nonce,
code_hash: account.bytecode_hash.unwrap_or_default(),
code: None,
storage_id: None,
}),
original_info: (i == 0).then(|| AccountInfo {
balance: account.balance.checked_div(U256::from(2)).unwrap_or(U256::ZERO),
nonce: 0,
code_hash: account.bytecode_hash.unwrap_or_default(),
code: None,
storage_id: None,
}),
storage,
status: AccountStatus::default(),
@@ -841,7 +839,6 @@ mod tests {
receipts: vec![],
requests: Requests::default(),
gas_used: 0,
block_access_list: Default::default(),
blob_gas_used: 0,
},
};

View File

@@ -204,7 +204,7 @@ where
EngineApiMessageVersion::default(),
)
.await?;
tracing::debug!(target: "engine::local", "FCU result: {res:?}");
if !res.is_valid() {
eyre::bail!("Invalid payload status")
}

View File

@@ -1,5 +1,7 @@
//! Engine tree configuration.
use alloy_eips::merge::EPOCH_SLOTS;
/// Triggers persistence when the number of canonical blocks in memory exceeds this threshold.
pub const DEFAULT_PERSISTENCE_THRESHOLD: u64 = 2;
@@ -40,7 +42,7 @@ pub const DEFAULT_RESERVED_CPU_CORES: usize = 1;
/// Default maximum concurrency for prewarm task.
pub const DEFAULT_PREWARM_MAX_CONCURRENCY: usize = 16;
const DEFAULT_BLOCK_BUFFER_LIMIT: u32 = 256;
const DEFAULT_BLOCK_BUFFER_LIMIT: u32 = EPOCH_SLOTS as u32 * 2;
const DEFAULT_MAX_INVALID_HEADER_CACHE_LENGTH: u32 = 256;
const DEFAULT_MAX_EXECUTE_BLOCK_BATCH_SIZE: usize = 4;
const DEFAULT_CROSS_BLOCK_CACHE_SIZE: u64 = 4 * 1024 * 1024 * 1024;
@@ -89,6 +91,8 @@ pub struct TreeConfig {
/// Whether to always compare trie updates from the state root task to the trie updates from
/// the regular state root calculation.
always_compare_trie_updates: bool,
/// Whether to disable state cache.
disable_state_cache: bool,
/// Whether to disable parallel prewarming.
disable_prewarming: bool,
/// Whether to disable the parallel sparse trie state root algorithm.
@@ -97,7 +101,7 @@ pub struct TreeConfig {
state_provider_metrics: bool,
/// Cross-block cache size in bytes.
cross_block_cache_size: u64,
/// Whether the host has enough parallelism to run state root in parallel.
/// Whether the host has enough parallelism to run state root task.
has_enough_parallelism: bool,
/// Whether multiproof task should chunk proof targets.
multiproof_chunking_enabled: bool,
@@ -143,6 +147,7 @@ impl Default for TreeConfig {
max_execute_block_batch_size: DEFAULT_MAX_EXECUTE_BLOCK_BATCH_SIZE,
legacy_state_root: false,
always_compare_trie_updates: false,
disable_state_cache: false,
disable_prewarming: false,
disable_parallel_sparse_trie: false,
state_provider_metrics: false,
@@ -173,6 +178,7 @@ impl TreeConfig {
max_execute_block_batch_size: usize,
legacy_state_root: bool,
always_compare_trie_updates: bool,
disable_state_cache: bool,
disable_prewarming: bool,
disable_parallel_sparse_trie: bool,
state_provider_metrics: bool,
@@ -197,6 +203,7 @@ impl TreeConfig {
max_execute_block_batch_size,
legacy_state_root,
always_compare_trie_updates,
disable_state_cache,
disable_prewarming,
disable_parallel_sparse_trie,
state_provider_metrics,
@@ -271,7 +278,12 @@ impl TreeConfig {
self.disable_parallel_sparse_trie
}
/// Returns whether or not parallel prewarming should be used.
/// Returns whether or not state cache is disabled.
pub const fn disable_state_cache(&self) -> bool {
self.disable_state_cache
}
/// Returns whether or not parallel prewarming is disabled.
pub const fn disable_prewarming(&self) -> bool {
self.disable_prewarming
}
@@ -363,6 +375,12 @@ impl TreeConfig {
self
}
/// Setter for whether to disable state cache.
pub const fn without_state_cache(mut self, disable_state_cache: bool) -> Self {
self.disable_state_cache = disable_state_cache;
self
}
/// Setter for whether to disable parallel prewarming.
pub const fn without_prewarming(mut self, disable_prewarming: bool) -> Self {
self.disable_prewarming = disable_prewarming;
@@ -385,17 +403,12 @@ impl TreeConfig {
self
}
/// Setter for whether or not the host has enough parallelism to run state root in parallel.
/// Setter for has enough parallelism.
pub const fn with_has_enough_parallelism(mut self, has_enough_parallelism: bool) -> Self {
self.has_enough_parallelism = has_enough_parallelism;
self
}
/// Whether or not the host has enough parallelism to run state root in parallel.
pub const fn has_enough_parallelism(&self) -> bool {
self.has_enough_parallelism
}
/// Setter for state provider metrics.
pub const fn with_state_provider_metrics(mut self, state_provider_metrics: bool) -> Self {
self.state_provider_metrics = state_provider_metrics;

View File

@@ -22,7 +22,8 @@ use reth_trie_common::HashedPostState;
use serde::{de::DeserializeOwned, Serialize};
// Re-export [`ExecutionPayload`] moved to `reth_payload_primitives`
pub use reth_evm::{ConfigureEngineEvm, ExecutableTxIterator};
#[cfg(feature = "std")]
pub use reth_evm::{ConfigureEngineEvm, ExecutableTxIterator, ExecutableTxTuple};
pub use reth_payload_primitives::ExecutionPayload;
mod error;
@@ -61,8 +62,7 @@ pub trait EngineTypes:
+ TryInto<Self::ExecutionPayloadEnvelopeV2>
+ TryInto<Self::ExecutionPayloadEnvelopeV3>
+ TryInto<Self::ExecutionPayloadEnvelopeV4>
+ TryInto<Self::ExecutionPayloadEnvelopeV5>
+ TryInto<Self::ExecutionPayloadEnvelopeV6>,
+ TryInto<Self::ExecutionPayloadEnvelopeV5>,
> + DeserializeOwned
+ Serialize
{
@@ -106,14 +106,6 @@ pub trait EngineTypes:
+ Send
+ Sync
+ 'static;
/// Execution Payload V6 envelope type.
type ExecutionPayloadEnvelopeV6: DeserializeOwned
+ Serialize
+ Clone
+ Unpin
+ Send
+ Sync
+ 'static;
}
/// Type that validates the payloads processed by the engine API.

View File

@@ -16,7 +16,7 @@ reth-chain-state.workspace = true
reth-chainspec = { workspace = true, optional = true }
reth-consensus.workspace = true
reth-db.workspace = true
reth-engine-primitives.workspace = true
reth-engine-primitives = { workspace = true, features = ["std"] }
reth-errors.workspace = true
reth-execution-types.workspace = true
reth-evm = { workspace = true, features = ["metrics"] }
@@ -29,6 +29,7 @@ reth-provider.workspace = true
reth-prune.workspace = true
reth-revm.workspace = true
reth-stages-api.workspace = true
reth-storage-errors.workspace = true
reth-tasks.workspace = true
reth-trie-parallel.workspace = true
reth-trie-sparse = { workspace = true, features = ["std", "metrics"] }
@@ -39,6 +40,7 @@ reth-trie.workspace = true
alloy-evm.workspace = true
alloy-consensus.workspace = true
alloy-eips.workspace = true
alloy-eip7928.workspace = true
alloy-primitives.workspace = true
alloy-rlp.workspace = true
alloy-rpc-types-engine.workspace = true
@@ -51,6 +53,7 @@ futures.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["rt", "rt-multi-thread", "sync", "macros"] }
mini-moka = { workspace = true, features = ["sync"] }
moka = { workspace = true, features = ["sync"] }
smallvec.workspace = true
# metrics
@@ -112,6 +115,10 @@ harness = false
name = "state_root_task"
harness = false
[[bench]]
name = "state_provider_builder"
harness = false
[features]
test-utils = [
"reth-chain-state/test-utils",

View File

@@ -26,12 +26,10 @@ fn create_bench_state(num_accounts: usize) -> EvmState {
nonce: 10,
code_hash: B256::from_slice(&rng.random::<[u8; 32]>()),
code: Default::default(),
storage_id: None,
},
storage,
status: AccountStatus::empty(),
transaction_id: 0,
..Default::default()
};
let address = Address::with_last_byte(i as u8);

View File

@@ -0,0 +1,49 @@
//! Benchmark for state provider builder reuse.
#![allow(missing_docs)]
use alloy_consensus::Header;
use alloy_primitives::B256;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use reth_engine_tree::tree::StateProviderBuilder;
use reth_ethereum_primitives::EthPrimitives;
use reth_provider::{test_utils::MockEthProvider, HeaderProvider};
use std::hint::black_box;
fn build_builder(
provider: &MockEthProvider<EthPrimitives>,
hash: B256,
) -> StateProviderBuilder<EthPrimitives, MockEthProvider<EthPrimitives>> {
let header = provider.header(hash).expect("header lookup failed");
assert!(header.is_some(), "missing header for hash");
StateProviderBuilder::new(provider.clone(), hash, None)
}
fn bench_state_provider_builder(c: &mut Criterion) {
let provider = MockEthProvider::<EthPrimitives>::new();
let hash = B256::from([0x11u8; 32]);
let header = Header { number: 1, ..Header::default() };
provider.add_header(hash, header);
let mut group = c.benchmark_group("state_provider_builder_reuse");
group.bench_with_input(BenchmarkId::new("single_lookup", 1), &hash, |b, hash| {
b.iter(|| {
let builder = build_builder(&provider, *hash);
black_box(builder);
});
});
group.bench_with_input(BenchmarkId::new("double_lookup", 2), &hash, |b, hash| {
b.iter(|| {
let first = build_builder(&provider, *hash);
let second = build_builder(&provider, *hash);
black_box((first, second));
});
});
group.finish();
}
criterion_group!(benches, bench_state_provider_builder);
criterion_main!(benches);

View File

@@ -62,7 +62,6 @@ fn create_bench_state_updates(params: &BenchParams) -> Vec<EvmState> {
storage: HashMap::default(),
status: AccountStatus::SelfDestructed,
transaction_id: 0,
..Default::default()
}
} else {
RevmAccount {
@@ -71,7 +70,6 @@ fn create_bench_state_updates(params: &BenchParams) -> Vec<EvmState> {
nonce: rng.random::<u64>(),
code_hash: KECCAK_EMPTY,
code: Some(Default::default()),
storage_id: None,
},
storage: (0..rng.random_range(0..=params.storage_slots_per_account))
.map(|_| {
@@ -87,7 +85,6 @@ fn create_bench_state_updates(params: &BenchParams) -> Vec<EvmState> {
.collect(),
status: AccountStatus::Touched,
transaction_id: 0,
..Default::default()
}
};
@@ -233,17 +230,18 @@ fn bench_state_root(c: &mut Criterion) {
let mut handle = payload_processor.spawn(
Default::default(),
(
core::iter::empty::<
Vec::<
Result<
Recovered<TransactionSigned>,
core::convert::Infallible,
>,
>(),
>::new(),
std::convert::identity,
),
StateProviderBuilder::new(provider.clone(), genesis_hash, None),
OverlayStateProviderFactory::new(provider),
&TreeConfig::default(),
None,
);
let mut state_hook = handle.state_hook();

View File

@@ -128,12 +128,12 @@ we send them along with the state updates to the [Sparse Trie Task](#sparse-trie
### Finishing the calculation
Once all transactions are executed, the [Engine](#engine) sends a `StateRootMessage::FinishStateUpdates` message
Once all transactions are executed, the [Engine](#engine) sends a `StateRootMessage::FinishedStateUpdates` message
to the State Root Task, marking the end of receiving state updates.
Every time we receive a new proof from the [MultiProof Manager](#multiproof-manager), we also check
the following conditions:
1. Are all updates received? (`StateRootMessage::FinishStateUpdates` was sent)
1. Are all updates received? (`StateRootMessage::FinishedStateUpdates` was sent)
2. Is `ProofSequencer` empty? (no proofs are pending for sequencing)
3. Are all proofs that were sent to the [`MultiProofManager::spawn_or_queue`](#multiproof-manager) finished
calculating and were sent to the [Sparse Trie Task](#sparse-trie-task)?

View File

@@ -219,10 +219,19 @@ pub enum HandlerEvent<T> {
}
/// Internal events issued by the [`ChainOrchestrator`].
#[derive(Clone, Debug)]
#[derive(Debug)]
pub enum FromOrchestrator {
/// Invoked when backfill sync finished
BackfillSyncFinished(ControlFlow),
/// Invoked when backfill sync started
BackfillSyncStarted,
/// Gracefully terminate the engine service.
///
/// When this variant is received, the engine will persist all remaining in-memory blocks
/// to disk before shutting down. Once persistence is complete, a signal is sent through
/// the oneshot channel to notify the caller.
Terminate {
/// Channel to signal termination completion.
tx: tokio::sync::oneshot::Sender<()>,
},
}

View File

@@ -1,5 +1,4 @@
use crate::metrics::PersistenceMetrics;
use alloy_consensus::BlockHeader;
use alloy_eips::BlockNumHash;
use reth_chain_state::ExecutedBlock;
use reth_errors::ProviderError;
@@ -142,27 +141,23 @@ where
&self,
blocks: Vec<ExecutedBlock<N::Primitives>>,
) -> Result<Option<BlockNumHash>, PersistenceError> {
let first_block_hash = blocks.first().map(|b| b.recovered_block.num_hash());
let last_block_hash = blocks.last().map(|b| b.recovered_block.num_hash());
debug!(target: "engine::persistence", first=?first_block_hash, last=?last_block_hash, "Saving range of blocks");
let first_block = blocks.first().map(|b| b.recovered_block.num_hash());
let last_block = blocks.last().map(|b| b.recovered_block.num_hash());
debug!(target: "engine::persistence", first=?first_block, last=?last_block, "Saving range of blocks");
let start_time = Instant::now();
let last_block_hash_num = blocks.last().map(|block| BlockNumHash {
hash: block.recovered_block().hash(),
number: block.recovered_block().header().number(),
});
if last_block_hash_num.is_some() {
if last_block.is_some() {
let provider_rw = self.provider.database_provider_rw()?;
provider_rw.save_blocks(blocks)?;
provider_rw.commit()?;
}
debug!(target: "engine::persistence", first=?first_block_hash, last=?last_block_hash, "Saved range of blocks");
debug!(target: "engine::persistence", first=?first_block, last=?last_block, "Saved range of blocks");
self.metrics.save_blocks_duration_seconds.record(start_time.elapsed());
Ok(last_block_hash_num)
Ok(last_block)
}
}

View File

@@ -14,7 +14,7 @@ use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
/// * [`BlockBuffer::remove_old_blocks`] to remove old blocks that precede the finalized number.
///
/// Note: Buffer is limited by number of blocks that it can contain and eviction of the block
/// is done by last recently used block.
/// is done in FIFO order (oldest inserted block is evicted first).
#[derive(Debug)]
pub struct BlockBuffer<B: Block> {
/// All blocks in the buffer stored by their block hash.

View File

@@ -31,6 +31,9 @@ pub(crate) struct CachedStateProvider<S> {
/// Metrics for the cached state provider
metrics: CachedStateMetrics,
/// If prewarm enabled we populate every cache miss
prewarm: bool,
}
impl<S> CachedStateProvider<S>
@@ -39,12 +42,32 @@ where
{
/// Creates a new [`CachedStateProvider`] from an [`ExecutionCache`], state provider, and
/// [`CachedStateMetrics`].
pub(crate) const fn new_with_caches(
pub(crate) const fn new(
state_provider: S,
caches: ExecutionCache,
metrics: CachedStateMetrics,
) -> Self {
Self { state_provider, caches, metrics }
Self { state_provider, caches, metrics, prewarm: false }
}
}
impl<S> CachedStateProvider<S> {
/// Enables pre-warm mode so that every cache miss is populated.
///
/// This is only relevant for pre-warm transaction execution with the intention to pre-populate
/// the cache with data for regular block execution. During regular block execution the
/// cache doesn't need to be populated because the actual EVM database
/// [`State`](revm::database::State) also caches internally during block execution and the cache
/// is then updated after the block with the entire [`BundleState`] output of that block which
/// contains all accessed accounts,code,storage. See also [`ExecutionCache::insert_state`].
pub(crate) const fn prewarm(mut self) -> Self {
self.prewarm = true;
self
}
/// Returns whether this provider should pre-warm cache misses.
const fn is_prewarm(&self) -> bool {
self.prewarm
}
}
@@ -123,7 +146,10 @@ impl<S: AccountReader> AccountReader for CachedStateProvider<S> {
self.metrics.account_cache_misses.increment(1);
let res = self.state_provider.basic_account(address)?;
self.caches.account_cache.insert(*address, res);
if self.is_prewarm() {
self.caches.account_cache.insert(*address, res);
}
Ok(res)
}
}
@@ -148,15 +174,19 @@ impl<S: StateProvider> StateProvider for CachedStateProvider<S> {
match self.caches.get_storage(&account, &storage_key) {
(SlotStatus::NotCached, maybe_cache) => {
let final_res = self.state_provider.storage(account, storage_key)?;
let account_cache = maybe_cache.unwrap_or_default();
account_cache.insert_storage(storage_key, final_res);
// we always need to insert the value to update the weights.
// Note: there exists a race when the storage cache did not exist yet and two
// consumers looking up the a storage value for this account for the first time,
// however we can assume that this will only happen for the very first (mostlikely
// the same) value, and don't expect that this will accidentally
// replace an account storage cache with additional values.
self.caches.insert_storage_cache(account, account_cache);
if self.is_prewarm() {
let account_cache = maybe_cache.unwrap_or_default();
account_cache.insert_storage(storage_key, final_res);
// we always need to insert the value to update the weights.
// Note: there exists a race when the storage cache did not exist yet and two
// consumers looking up the a storage value for this account for the first time,
// however we can assume that this will only happen for the very first
// (mostlikely the same) value, and don't expect that this
// will accidentally replace an account storage cache with
// additional values.
self.caches.insert_storage_cache(account, account_cache);
}
self.metrics.storage_cache_misses.increment(1);
Ok(final_res)
@@ -183,7 +213,11 @@ impl<S: BytecodeReader> BytecodeReader for CachedStateProvider<S> {
self.metrics.code_cache_misses.increment(1);
let final_res = self.state_provider.bytecode_by_hash(code_hash)?;
self.caches.code_cache.insert(*code_hash, final_res.clone());
if self.is_prewarm() {
self.caches.code_cache.insert(*code_hash, final_res.clone());
}
Ok(final_res)
}
}
@@ -785,7 +819,7 @@ mod tests {
let caches = ExecutionCacheBuilder::default().build_caches(1000);
let state_provider =
CachedStateProvider::new_with_caches(provider, caches, CachedStateMetrics::zeroed());
CachedStateProvider::new(provider, caches, CachedStateMetrics::zeroed());
// check that the storage is empty
let res = state_provider.storage(address, storage_key);
@@ -808,7 +842,7 @@ mod tests {
let caches = ExecutionCacheBuilder::default().build_caches(1000);
let state_provider =
CachedStateProvider::new_with_caches(provider, caches, CachedStateMetrics::zeroed());
CachedStateProvider::new(provider, caches, CachedStateMetrics::zeroed());
// check that the storage returns the expected value
let res = state_provider.storage(address, storage_key);

View File

@@ -22,7 +22,7 @@ const NANOS_PER_SEC: u32 = 1_000_000_000;
/// An atomic version of [`Duration`], using an [`AtomicU64`] to store the total nanoseconds in the
/// duration.
#[derive(Default)]
#[derive(Debug, Default)]
pub(crate) struct AtomicDuration {
/// The nanoseconds part of the duration
///
@@ -59,7 +59,8 @@ impl AtomicDuration {
}
/// A wrapper of a state provider and latency metrics.
pub(crate) struct InstrumentedStateProvider<S> {
#[derive(Debug)]
pub struct InstrumentedStateProvider<S> {
/// The state provider
state_provider: S,
@@ -80,11 +81,12 @@ impl<S> InstrumentedStateProvider<S>
where
S: StateProvider,
{
/// Creates a new [`InstrumentedStateProvider`] from a state provider
pub(crate) fn from_state_provider(state_provider: S) -> Self {
/// Creates a new [`InstrumentedStateProvider`] from a state provider with the provided label
/// for metrics.
pub fn new(state_provider: S, source: &'static str) -> Self {
Self {
state_provider,
metrics: StateProviderMetrics::default(),
metrics: StateProviderMetrics::new_with_labels(&[("source", source)]),
total_storage_fetch_latency: AtomicDuration::zero(),
total_code_fetch_latency: AtomicDuration::zero(),
total_account_fetch_latency: AtomicDuration::zero(),
@@ -134,6 +136,12 @@ impl<S> InstrumentedStateProvider<S> {
}
}
impl<S> Drop for InstrumentedStateProvider<S> {
fn drop(&mut self) {
self.record_total_latency();
}
}
/// Metrics for the instrumented state provider
#[derive(Metrics, Clone)]
#[metrics(scope = "sync.state_provider")]

View File

@@ -477,7 +477,6 @@ mod tests {
receipts: vec![],
requests: Requests::default(),
gas_used: 1000,
block_access_list: None,
blob_gas_used: 0,
},
))
@@ -571,12 +570,10 @@ mod tests {
nonce: 10,
code_hash: B256::random(),
code: Default::default(),
storage_id: None,
},
storage,
status: AccountStatus::default(),
transaction_id: 0,
..Default::default()
},
);
state

View File

@@ -39,6 +39,7 @@ use revm::state::EvmState;
use state::TreeState;
use std::{
fmt::Debug,
ops,
sync::{
mpsc::{Receiver, RecvError, RecvTimeoutError, Sender},
Arc,
@@ -54,7 +55,7 @@ use tracing::*;
mod block_buffer;
mod cached_state;
pub mod error;
mod instrumented_state;
pub mod instrumented_state;
mod invalid_headers;
mod metrics;
mod payload_processor;
@@ -320,7 +321,7 @@ where
BlockReader<Block = N::Block, Header = N::BlockHeader>,
C: ConfigureEvm<Primitives = N> + 'static,
T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
V: EngineValidator<T>,
V: EngineValidator<T, N>,
{
/// Creates a new [`EngineApiTreeHandler`].
#[expect(clippy::too_many_arguments)]
@@ -426,9 +427,13 @@ where
match self.try_recv_engine_message() {
Ok(Some(msg)) => {
debug!(target: "engine::tree", %msg, "received new engine message");
if let Err(fatal) = self.on_engine_message(msg) {
error!(target: "engine::tree", %fatal, "insert block fatal error");
return
match self.on_engine_message(msg) {
Ok(ops::ControlFlow::Break(())) => return,
Ok(ops::ControlFlow::Continue(())) => {}
Err(fatal) => {
error!(target: "engine::tree", %fatal, "insert block fatal error");
return
}
}
}
Ok(None) => {
@@ -536,7 +541,7 @@ where
// null}` if the expected and the actual arrays don't match.
//
// This validation **MUST** be instantly run in all cases even during active sync process.
tracing::debug!("Payload received {:?}", payload);
let num_hash = payload.num_hash();
let engine_event = ConsensusEngineEvent::BlockReceived(num_hash);
self.emit_event(EngineApiEvent::BeaconConsensus(engine_event));
@@ -545,7 +550,6 @@ where
// Check for invalid ancestors
if let Some(invalid) = self.find_invalid_ancestor(&payload) {
tracing::debug!(target: "engine::tree", ?invalid, "found invalid ancestor for payload");
let status = self.handle_invalid_ancestor_payload(payload, invalid)?;
return Ok(TreeOutcome::new(status));
}
@@ -554,7 +558,6 @@ where
self.metrics.block_validation.record_payload_validation(start.elapsed().as_secs_f64());
let status = if self.backfill_sync_state.is_idle() {
tracing::debug!(target: "engine::tree", "inserting payload directly");
self.try_insert_payload(payload)?
} else {
self.try_buffer_payload(payload)?
@@ -593,7 +596,7 @@ where
let parent_hash = payload.parent_hash();
let mut latest_valid_hash = None;
match self.insert_payload(payload.clone()) {
match self.insert_payload(payload) {
Ok(status) => {
let status = match status {
InsertPayloadOk::Inserted(BlockStatus::Valid) => {
@@ -615,10 +618,7 @@ where
Ok(PayloadStatus::new(status, latest_valid_hash))
}
Err(error) => match error {
InsertPayloadError::Block(error) => {
tracing::debug!("payload in new payload l 617 {:?}", payload);
Ok(self.on_insert_block_error(error)?)
}
InsertPayloadError::Block(error) => Ok(self.on_insert_block_error(error)?),
InsertPayloadError::Payload(error) => {
Ok(self.on_new_payload_error(error, num_hash, parent_hash)?)
}
@@ -931,48 +931,6 @@ where
Ok(())
}
/// Determines if the given block is part of a fork by checking that these
/// conditions are true:
/// * walking back from the target hash to verify that the target hash is not part of an
/// extension of the canonical chain.
/// * walking back from the current head to verify that the target hash is not already part of
/// the canonical chain.
///
/// The header is required as an arg, because we might be checking that the header is a fork
/// block before it's in the tree state and before it's in the database.
fn is_fork(&self, target: BlockWithParent) -> ProviderResult<bool> {
let target_hash = target.block.hash;
// verify that the given hash is not part of an extension of the canon chain.
let canonical_head = self.state.tree_state.canonical_head();
let mut current_hash;
let mut current_block = target;
loop {
if current_block.block.hash == canonical_head.hash {
return Ok(false)
}
// We already passed the canonical head
if current_block.block.number <= canonical_head.number {
break
}
current_hash = current_block.parent;
let Some(next_block) = self.sealed_header_by_hash(current_hash)? else { break };
current_block = next_block.block_with_parent();
}
// verify that the given hash is not already part of canonical chain stored in memory
if self.canonical_in_memory_state.header_by_hash(target_hash).is_some() {
return Ok(false)
}
// verify that the given hash is not already part of persisted canonical chain
if self.provider.block_number(target_hash)?.is_some() {
return Ok(false)
}
Ok(true)
}
/// Invoked when we receive a new forkchoice update message. Calls into the blockchain tree
/// to resolve chain forks and ensure that the Execution Layer is working with the latest valid
/// chain.
@@ -1307,22 +1265,7 @@ where
// Check if persistence has complete
match rx.try_recv() {
Ok(last_persisted_hash_num) => {
self.metrics.engine.persistence_duration.record(start_time.elapsed());
let Some(BlockNumHash {
hash: last_persisted_block_hash,
number: last_persisted_block_number,
}) = last_persisted_hash_num
else {
// if this happened, then we persisted no blocks because we sent an
// empty vec of blocks
warn!(target: "engine::tree", "Persistence task completed but did not persist any blocks");
return Ok(())
};
debug!(target: "engine::tree", ?last_persisted_block_hash, ?last_persisted_block_number, elapsed=?start_time.elapsed(), "Finished persisting, calling finish");
self.persistence_state
.finish(last_persisted_block_hash, last_persisted_block_number);
self.on_new_persisted_block()?;
self.on_persistence_complete(last_persisted_hash_num, start_time)?;
}
Err(TryRecvError::Closed) => return Err(TryRecvError::Closed.into()),
Err(TryRecvError::Empty) => {
@@ -1335,7 +1278,8 @@ where
if let Some(new_tip_num) = self.find_disk_reorg()? {
self.remove_blocks(new_tip_num)
} else if self.should_persist() {
let blocks_to_persist = self.get_canonical_blocks_to_persist()?;
let blocks_to_persist =
self.get_canonical_blocks_to_persist(PersistTarget::Threshold)?;
self.persist_blocks(blocks_to_persist);
}
}
@@ -1343,11 +1287,72 @@ where
Ok(())
}
/// Finishes termination by persisting all remaining blocks and signaling completion.
///
/// This blocks until all persistence is complete. Always signals completion,
/// even if an error occurs.
fn finish_termination(
&mut self,
pending_termination: oneshot::Sender<()>,
) -> Result<(), AdvancePersistenceError> {
trace!(target: "engine::tree", "finishing termination, persisting remaining blocks");
let result = self.persist_until_complete();
let _ = pending_termination.send(());
result
}
/// Persists all remaining blocks until none are left.
fn persist_until_complete(&mut self) -> Result<(), AdvancePersistenceError> {
loop {
// Wait for any in-progress persistence to complete (blocking)
if let Some((rx, start_time, _action)) = self.persistence_state.rx.take() {
let result = rx.blocking_recv().map_err(|_| TryRecvError::Closed)?;
self.on_persistence_complete(result, start_time)?;
}
let blocks_to_persist = self.get_canonical_blocks_to_persist(PersistTarget::Head)?;
if blocks_to_persist.is_empty() {
debug!(target: "engine::tree", "persistence complete, signaling termination");
return Ok(())
}
debug!(target: "engine::tree", count = blocks_to_persist.len(), "persisting remaining blocks before shutdown");
self.persist_blocks(blocks_to_persist);
}
}
/// Handles a completed persistence task.
fn on_persistence_complete(
&mut self,
last_persisted_hash_num: Option<BlockNumHash>,
start_time: Instant,
) -> Result<(), AdvancePersistenceError> {
self.metrics.engine.persistence_duration.record(start_time.elapsed());
let Some(BlockNumHash {
hash: last_persisted_block_hash,
number: last_persisted_block_number,
}) = last_persisted_hash_num
else {
// if this happened, then we persisted no blocks because we sent an empty vec of blocks
warn!(target: "engine::tree", "Persistence task completed but did not persist any blocks");
return Ok(())
};
debug!(target: "engine::tree", ?last_persisted_block_hash, ?last_persisted_block_number, elapsed=?start_time.elapsed(), "Finished persisting, calling finish");
self.persistence_state.finish(last_persisted_block_hash, last_persisted_block_number);
self.on_new_persisted_block()?;
Ok(())
}
/// Handles a message from the engine.
///
/// Returns `ControlFlow::Break(())` if the engine should terminate.
fn on_engine_message(
&mut self,
msg: FromEngine<EngineApiRequest<T, N>, N::Block>,
) -> Result<(), InsertBlockFatalError> {
) -> Result<ops::ControlFlow<()>, InsertBlockFatalError> {
match msg {
FromEngine::Event(event) => match event {
FromOrchestrator::BackfillSyncStarted => {
@@ -1357,6 +1362,13 @@ where
FromOrchestrator::BackfillSyncFinished(ctrl) => {
self.on_backfill_sync_finished(ctrl)?;
}
FromOrchestrator::Terminate { tx } => {
debug!(target: "engine::tree", "received terminate request");
if let Err(err) = self.finish_termination(tx) {
error!(target: "engine::tree", %err, "Termination failed");
}
return Ok(ops::ControlFlow::Break(()))
}
},
FromEngine::Request(request) => {
match request {
@@ -1364,7 +1376,7 @@ where
let block_num_hash = block.recovered_block().num_hash();
if block_num_hash.number <= self.state.tree_state.canonical_block_number() {
// outdated block that can be skipped
return Ok(())
return Ok(ops::ControlFlow::Continue(()))
}
debug!(target: "engine::tree", block=?block_num_hash, "inserting already executed block");
@@ -1472,7 +1484,7 @@ where
}
}
}
Ok(())
Ok(ops::ControlFlow::Continue(()))
}
/// Invoked if the backfill sync has finished to target.
@@ -1706,10 +1718,10 @@ where
}
/// Returns a batch of consecutive canonical blocks to persist in the range
/// `(last_persisted_number .. canonical_head - threshold]`. The expected
/// order is oldest -> newest.
/// `(last_persisted_number .. target]`. The expected order is oldest -> newest.
fn get_canonical_blocks_to_persist(
&self,
target: PersistTarget,
) -> Result<Vec<ExecutedBlock<N>>, AdvancePersistenceError> {
// We will calculate the state root using the database, so we need to be sure there are no
// changes
@@ -1720,9 +1732,12 @@ where
let last_persisted_number = self.persistence_state.last_persisted_block.number;
let canonical_head_number = self.state.tree_state.canonical_block_number();
// Persist only up to block buffer target
let target_number =
canonical_head_number.saturating_sub(self.config.memory_block_buffer_target());
let target_number = match target {
PersistTarget::Head => canonical_head_number,
PersistTarget::Threshold => {
canonical_head_number.saturating_sub(self.config.memory_block_buffer_target())
}
};
debug!(
target: "engine::tree",
@@ -2422,7 +2437,9 @@ where
self.insert_block_or_payload(
payload.block_with_parent(),
payload,
|validator, payload, ctx| validator.validate_payload(payload, ctx),
|validator, payload, ctx, provider_builder| {
validator.validate_payload(payload, ctx, provider_builder)
},
|this, payload| Ok(this.payload_validator.convert_payload_to_block(payload)?),
)
}
@@ -2434,7 +2451,9 @@ where
self.insert_block_or_payload(
block.block_with_parent(),
block,
|validator, block, ctx| validator.validate_block(block, ctx),
|validator, block, ctx, provider_builder| {
validator.validate_block(block, ctx, provider_builder)
},
|_, block| Ok(block),
)
}
@@ -2460,7 +2479,12 @@ where
&mut self,
block_id: BlockWithParent,
input: Input,
execute: impl FnOnce(&mut V, Input, TreeCtx<'_, N>) -> Result<ExecutedBlock<N>, Err>,
execute: impl FnOnce(
&mut V,
Input,
TreeCtx<'_, N>,
V::ProviderBuilder,
) -> Result<ExecutedBlock<N>, Err>,
convert_to_block: impl FnOnce(&mut Self, Input) -> Result<SealedBlock<N::Block>, Err>,
) -> Result<InsertPayloadOk, Err>
where
@@ -2485,7 +2509,7 @@ where
};
// Ensure that the parent state is available.
match self.state_provider_builder(block_id.parent) {
let provider_builder = match self.payload_validator.provider_builder(block_id.parent, &self.state) {
Err(err) => {
let block = convert_to_block(self, input)?;
return Err(InsertBlockError::new(block, err.into()).into());
@@ -2509,23 +2533,20 @@ where
missing_ancestor,
}))
}
Ok(Some(_)) => {}
}
// determine whether we are on a fork chain
let is_fork = match self.is_fork(block_id) {
Err(err) => {
let block = convert_to_block(self, input)?;
return Err(InsertBlockError::new(block, err.into()).into());
}
Ok(is_fork) => is_fork,
Ok(Some(provider_builder)) => provider_builder,
};
// determine whether we are on a fork chain by comparing the block number with the
// canonical head. This is a simple check that is sufficient for the event emission below.
// A block is considered a fork if its number is less than or equal to the canonical head,
// as this indicates there's already a canonical block at that height.
let is_fork = block_id.block.number <= self.state.tree_state.current_canonical_head.number;
let ctx = TreeCtx::new(&mut self.state, &self.canonical_in_memory_state);
let start = Instant::now();
let executed = execute(&mut self.payload_validator, input, ctx)?;
let executed = execute(&mut self.payload_validator, input, ctx, provider_builder)?;
// if the parent is the canonical head, we can insert the block as the pending block
if self.state.tree_state.canonical_block_hash() == executed.recovered_block().parent_hash()
@@ -2865,3 +2886,12 @@ pub enum InsertPayloadOk {
/// The payload was valid and inserted into the tree.
Inserted(BlockStatus),
}
/// Target for block persistence.
#[derive(Debug, Clone, Copy)]
enum PersistTarget {
/// Persist up to `canonical_head - memory_block_buffer_target`.
Threshold,
/// Persist all blocks up to and including the canonical head.
Head,
}

View File

@@ -0,0 +1,318 @@
//! BAL (Block Access List, EIP-7928) related functionality.
use alloy_consensus::constants::KECCAK_EMPTY;
use alloy_eip7928::BlockAccessList;
use alloy_primitives::{keccak256, U256};
use reth_primitives_traits::Account;
use reth_provider::{AccountReader, ProviderError};
use reth_trie::{HashedPostState, HashedStorage};
/// Converts a Block Access List into a [`HashedPostState`] by extracting the final state
/// of modified accounts and storage slots.
pub fn bal_to_hashed_post_state<P>(
bal: &BlockAccessList,
provider: &P,
) -> Result<HashedPostState, ProviderError>
where
P: AccountReader,
{
let mut hashed_state = HashedPostState::with_capacity(bal.len());
for account_changes in bal {
let address = account_changes.address;
let hashed_address = keccak256(address);
// Get the latest balance (last balance change if any)
let balance = account_changes.balance_changes.last().map(|change| change.post_balance);
// Get the latest nonce (last nonce change if any)
let nonce = account_changes.nonce_changes.last().map(|change| change.new_nonce);
// Get the latest code (last code change if any)
let code_hash = if let Some(code_change) = account_changes.code_changes.last() {
if code_change.new_code.is_empty() {
Some(Some(KECCAK_EMPTY))
} else {
Some(Some(keccak256(&code_change.new_code)))
}
} else {
None
};
// Only fetch account from provider if we're missing any field
let existing_account = if balance.is_none() || nonce.is_none() || code_hash.is_none() {
provider.basic_account(&address)?
} else {
None
};
// Build the final account state
let account = Account {
balance: balance.unwrap_or_else(|| {
existing_account.as_ref().map(|acc| acc.balance).unwrap_or(U256::ZERO)
}),
nonce: nonce
.unwrap_or_else(|| existing_account.as_ref().map(|acc| acc.nonce).unwrap_or(0)),
bytecode_hash: code_hash.unwrap_or_else(|| {
existing_account.as_ref().and_then(|acc| acc.bytecode_hash).or(Some(KECCAK_EMPTY))
}),
};
hashed_state.accounts.insert(hashed_address, Some(account));
// Process storage changes
if !account_changes.storage_changes.is_empty() {
let mut storage_map = HashedStorage::new(false);
for slot_changes in &account_changes.storage_changes {
let hashed_slot = keccak256(slot_changes.slot);
// Get the last change for this slot
if let Some(last_change) = slot_changes.changes.last() {
storage_map
.storage
.insert(hashed_slot, U256::from_be_bytes(last_change.new_value.0));
}
}
if !storage_map.storage.is_empty() {
hashed_state.storages.insert(hashed_address, storage_map);
}
}
}
Ok(hashed_state)
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_eip7928::{
AccountChanges, BalanceChange, CodeChange, NonceChange, SlotChanges, StorageChange,
};
use alloy_primitives::{Address, Bytes, StorageKey, B256};
use reth_revm::test_utils::StateProviderTest;
#[test]
fn test_bal_to_hashed_post_state_basic() {
let provider = StateProviderTest::default();
let address = Address::random();
let account_changes = AccountChanges {
address,
storage_changes: vec![],
storage_reads: vec![],
balance_changes: vec![BalanceChange::new(0, U256::from(100))],
nonce_changes: vec![NonceChange::new(0, 1)],
code_changes: vec![],
};
let bal = vec![account_changes];
let result = bal_to_hashed_post_state(&bal, &provider).unwrap();
assert_eq!(result.accounts.len(), 1);
let hashed_address = keccak256(address);
let account_opt = result.accounts.get(&hashed_address).unwrap();
assert!(account_opt.is_some());
let account = account_opt.as_ref().unwrap();
assert_eq!(account.balance, U256::from(100));
assert_eq!(account.nonce, 1);
assert_eq!(account.bytecode_hash, Some(KECCAK_EMPTY));
}
#[test]
fn test_bal_with_storage_changes() {
let provider = StateProviderTest::default();
let address = Address::random();
let slot = StorageKey::random();
let value = B256::random();
let slot_changes = SlotChanges { slot, changes: vec![StorageChange::new(0, value)] };
let account_changes = AccountChanges {
address,
storage_changes: vec![slot_changes],
storage_reads: vec![],
balance_changes: vec![BalanceChange::new(0, U256::from(500))],
nonce_changes: vec![NonceChange::new(0, 2)],
code_changes: vec![],
};
let bal = vec![account_changes];
let result = bal_to_hashed_post_state(&bal, &provider).unwrap();
let hashed_address = keccak256(address);
assert!(result.storages.contains_key(&hashed_address));
let storage = result.storages.get(&hashed_address).unwrap();
let hashed_slot = keccak256(slot);
let stored_value = storage.storage.get(&hashed_slot).unwrap();
assert_eq!(*stored_value, U256::from_be_bytes(value.0));
}
#[test]
fn test_bal_with_code_change() {
let provider = StateProviderTest::default();
let address = Address::random();
let code = Bytes::from(vec![0x60, 0x80, 0x60, 0x40]); // Some bytecode
let account_changes = AccountChanges {
address,
storage_changes: vec![],
storage_reads: vec![],
balance_changes: vec![BalanceChange::new(0, U256::from(1000))],
nonce_changes: vec![NonceChange::new(0, 1)],
code_changes: vec![CodeChange::new(0, code.clone())],
};
let bal = vec![account_changes];
let result = bal_to_hashed_post_state(&bal, &provider).unwrap();
let hashed_address = keccak256(address);
let account_opt = result.accounts.get(&hashed_address).unwrap();
let account = account_opt.as_ref().unwrap();
let expected_code_hash = keccak256(&code);
assert_eq!(account.bytecode_hash, Some(expected_code_hash));
}
#[test]
fn test_bal_with_empty_code() {
let provider = StateProviderTest::default();
let address = Address::random();
let empty_code = Bytes::default();
let account_changes = AccountChanges {
address,
storage_changes: vec![],
storage_reads: vec![],
balance_changes: vec![BalanceChange::new(0, U256::from(1000))],
nonce_changes: vec![NonceChange::new(0, 1)],
code_changes: vec![CodeChange::new(0, empty_code)],
};
let bal = vec![account_changes];
let result = bal_to_hashed_post_state(&bal, &provider).unwrap();
let hashed_address = keccak256(address);
let account_opt = result.accounts.get(&hashed_address).unwrap();
let account = account_opt.as_ref().unwrap();
assert_eq!(account.bytecode_hash, Some(KECCAK_EMPTY));
}
#[test]
fn test_bal_multiple_changes_takes_last() {
let provider = StateProviderTest::default();
let address = Address::random();
// Multiple balance changes - should take the last one
let account_changes = AccountChanges {
address,
storage_changes: vec![],
storage_reads: vec![],
balance_changes: vec![
BalanceChange::new(0, U256::from(100)),
BalanceChange::new(1, U256::from(200)),
BalanceChange::new(2, U256::from(300)),
],
nonce_changes: vec![
NonceChange::new(0, 1),
NonceChange::new(1, 2),
NonceChange::new(2, 3),
],
code_changes: vec![],
};
let bal = vec![account_changes];
let result = bal_to_hashed_post_state(&bal, &provider).unwrap();
let hashed_address = keccak256(address);
let account_opt = result.accounts.get(&hashed_address).unwrap();
let account = account_opt.as_ref().unwrap();
// Should have the last values
assert_eq!(account.balance, U256::from(300));
assert_eq!(account.nonce, 3);
}
#[test]
fn test_bal_uses_provider_for_missing_fields() {
let mut provider = StateProviderTest::default();
let address = Address::random();
let code_hash = B256::random();
let existing_account =
Account { balance: U256::from(999), nonce: 42, bytecode_hash: Some(code_hash) };
provider.insert_account(address, existing_account, None, Default::default());
// Only change balance, nonce and code should come from provider
let account_changes = AccountChanges {
address,
storage_changes: vec![],
storage_reads: vec![],
balance_changes: vec![BalanceChange::new(0, U256::from(1500))],
nonce_changes: vec![],
code_changes: vec![],
};
let bal = vec![account_changes];
let result = bal_to_hashed_post_state(&bal, &provider).unwrap();
let hashed_address = keccak256(address);
let account_opt = result.accounts.get(&hashed_address).unwrap();
let account = account_opt.as_ref().unwrap();
// Balance should be updated
assert_eq!(account.balance, U256::from(1500));
// Nonce and bytecode_hash should come from provider
assert_eq!(account.nonce, 42);
assert_eq!(account.bytecode_hash, Some(code_hash));
}
#[test]
fn test_bal_multiple_storage_changes_per_slot() {
let provider = StateProviderTest::default();
let address = Address::random();
let slot = StorageKey::random();
// Multiple changes to the same slot - should take the last one
let slot_changes = SlotChanges {
slot,
changes: vec![
StorageChange::new(0, B256::from(U256::from(100).to_be_bytes::<32>())),
StorageChange::new(1, B256::from(U256::from(200).to_be_bytes::<32>())),
StorageChange::new(2, B256::from(U256::from(300).to_be_bytes::<32>())),
],
};
let account_changes = AccountChanges {
address,
storage_changes: vec![slot_changes],
storage_reads: vec![],
balance_changes: vec![BalanceChange::new(0, U256::from(100))],
nonce_changes: vec![NonceChange::new(0, 1)],
code_changes: vec![],
};
let bal = vec![account_changes];
let result = bal_to_hashed_post_state(&bal, &provider).unwrap();
let hashed_address = keccak256(address);
let storage = result.storages.get(&hashed_address).unwrap();
let hashed_slot = keccak256(slot);
let stored_value = storage.storage.get(&hashed_slot).unwrap();
// Should have the last value
assert_eq!(*stored_value, U256::from(300));
}
}

View File

@@ -13,6 +13,7 @@ use crate::tree::{
sparse_trie::SparseTrieTask,
StateProviderBuilder, TreeConfig,
};
use alloy_eip7928::BlockAccessList;
use alloy_eips::eip1898::BlockWithParent;
use alloy_evm::{block::StateChangeSource, ToTxEnv};
use alloy_primitives::B256;
@@ -21,12 +22,13 @@ use executor::WorkloadExecutor;
use multiproof::{SparseTrieUpdate, *};
use parking_lot::RwLock;
use prewarm::PrewarmMetrics;
use rayon::iter::{ParallelBridge, ParallelIterator};
use reth_engine_primitives::ExecutableTxIterator;
use rayon::prelude::*;
use reth_evm::{
execute::{ExecutableTxFor, WithTxEnv},
ConfigureEvm, EvmEnvFor, OnStateHook, SpecFor, TxEnvFor,
ConfigureEvm, EvmEnvFor, ExecutableTxIterator, ExecutableTxTuple, OnStateHook, SpecFor,
TxEnvFor,
};
use reth_execution_types::ExecutionOutcome;
use reth_primitives_traits::NodePrimitives;
use reth_provider::{BlockReader, DatabaseProviderROFactory, StateProviderFactory, StateReader};
use reth_revm::{db::BundleState, state::EvmState};
@@ -49,8 +51,9 @@ use std::{
},
time::Instant,
};
use tracing::{debug, debug_span, instrument, warn, Span};
use tracing::{debug, debug_span, error, instrument, warn, Span};
pub mod bal;
mod configured_sparse_trie;
pub mod executor;
pub mod multiproof;
@@ -90,6 +93,13 @@ pub const SPARSE_TRIE_MAX_NODES_SHRINK_CAPACITY: usize = 1_000_000;
/// 144MB.
pub const SPARSE_TRIE_MAX_VALUES_SHRINK_CAPACITY: usize = 1_000_000;
/// Type alias for [`PayloadHandle`] returned by payload processor spawn methods.
type IteratorPayloadHandle<Evm, I, N> = PayloadHandle<
WithTxEnv<TxEnvFor<Evm>, <I as ExecutableTxTuple>::Tx>,
<I as ExecutableTxTuple>::Error,
<N as NodePrimitives>::Receipt,
>;
/// Entrypoint for executing the payload.
#[derive(Debug)]
pub struct PayloadProcessor<Evm>
@@ -106,6 +116,8 @@ where
cross_block_cache_size: u64,
/// Whether transactions should not be executed on prewarming task.
disable_transaction_prewarming: bool,
/// Whether state cache should be disable
disable_state_cache: bool,
/// Determines how to configure the evm for execution.
evm_config: Evm,
/// Whether precompile cache should be disabled.
@@ -149,6 +161,7 @@ where
cross_block_cache_size: config.cross_block_cache_size(),
disable_transaction_prewarming: config.disable_prewarming(),
evm_config,
disable_state_cache: config.disable_state_cache(),
precompile_cache_disabled: config.precompile_cache_disabled(),
precompile_cache_map,
sparse_state_trie: Arc::default(),
@@ -195,7 +208,6 @@ where
///
/// This returns a handle to await the final state root and to interact with the tasks (e.g.
/// canceling)
#[allow(clippy::type_complexity)]
#[instrument(
level = "debug",
target = "engine::tree::payload_processor",
@@ -209,7 +221,8 @@ where
provider_builder: StateProviderBuilder<N, P>,
multiproof_provider_factory: F,
config: &TreeConfig,
) -> PayloadHandle<WithTxEnv<TxEnvFor<Evm>, I::Tx>, I::Error>
bal: Option<Arc<BlockAccessList>>,
) -> IteratorPayloadHandle<Evm, I, N>
where
P: BlockReader + StateProviderFactory + StateReader + Clone + 'static,
F: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>
@@ -249,19 +262,45 @@ where
// wire the multiproof task to the prewarm task
let to_multi_proof = Some(multi_proof_task.state_root_message_sender());
let prewarm_handle = self.spawn_caching_with(
env,
prewarm_rx,
transaction_count_hint,
provider_builder,
to_multi_proof.clone(),
);
// Handle BAL-based optimization if available
let prewarm_handle = if let Some(bal) = bal {
// When BAL is present, skip spawning prewarm tasks entirely and send BAL to multiproof
debug!(target: "engine::tree::payload_processor", "BAL present, skipping prewarm tasks");
// Send BAL message immediately to MultiProofTask
if let Some(ref sender) = to_multi_proof &&
let Err(err) = sender.send(MultiProofMessage::BlockAccessList(bal))
{
// In this case state root validation will simply fail
error!(target: "engine::tree::payload_processor", ?err, "Failed to send BAL to MultiProofTask");
}
// Spawn minimal cache-only task without prewarming
self.spawn_caching_with(
env,
prewarm_rx,
transaction_count_hint,
provider_builder.clone(),
None, // Don't send proof targets when BAL is present
)
} else {
// Normal path: spawn with full prewarming
self.spawn_caching_with(
env,
prewarm_rx,
transaction_count_hint,
provider_builder.clone(),
to_multi_proof.clone(),
)
};
// spawn multi-proof task
let parent_span = span.clone();
self.executor.spawn_blocking(move || {
let _enter = parent_span.entered();
multi_proof_task.run();
// Build a state provider for the multiproof task
let provider = provider_builder.build().expect("failed to build provider");
multi_proof_task.run(provider);
});
// wire the sparse trie to the state root response receiver
@@ -288,7 +327,7 @@ where
env: ExecutionEnv<Evm>,
transactions: I,
provider_builder: StateProviderBuilder<N, P>,
) -> PayloadHandle<WithTxEnv<TxEnvFor<Evm>, I::Tx>, I::Error>
) -> IteratorPayloadHandle<Evm, I, N>
where
P: BlockReader + StateProviderFactory + StateReader + Clone + 'static,
{
@@ -315,36 +354,32 @@ where
usize,
) {
let (transactions, convert) = transactions.into();
let transactions = transactions.into_iter();
// Get the transaction count for prewarming task
// Use upper bound if available (more accurate), otherwise use lower bound
let (lower, upper) = transactions.size_hint();
let transaction_count_hint = upper.unwrap_or(lower);
let transactions = transactions.into_par_iter();
let transaction_count_hint = transactions.len();
// Spawn a task that iterates through all transactions in parallel and sends them to the
// main task.
let (tx, rx) = mpsc::channel();
let (ooo_tx, ooo_rx) = mpsc::channel();
let (prewarm_tx, prewarm_rx) = mpsc::channel();
let (execute_tx, execute_rx) = mpsc::channel();
// Spawn a task that `convert`s all transactions in parallel and sends them out-of-order.
self.executor.spawn_blocking(move || {
transactions.enumerate().par_bridge().for_each_with(tx, |sender, (idx, tx)| {
transactions.enumerate().for_each_with(ooo_tx, |ooo_tx, (idx, tx)| {
let tx = convert(tx);
let tx = tx.map(|tx| WithTxEnv { tx_env: tx.to_tx_env(), tx: Arc::new(tx) });
let _ = sender.send((idx, tx));
// Only send Ok(_) variants to prewarming task.
if let Ok(tx) = &tx {
let _ = prewarm_tx.send(tx.clone());
}
let _ = ooo_tx.send((idx, tx));
});
});
// Spawn a task that processes out-of-order transactions from the task above and sends them
// to prewarming and execution tasks.
let (prewarm_tx, prewarm_rx) = mpsc::channel();
let (execute_tx, execute_rx) = mpsc::channel();
// to the execution task in order.
self.executor.spawn_blocking(move || {
let mut next_for_execution = 0;
let mut queue = BTreeMap::new();
while let Ok((idx, tx)) = rx.recv() {
// only send Ok(_) variants to prewarming task
if let Ok(tx) = &tx {
let _ = prewarm_tx.send(tx.clone());
}
while let Ok((idx, tx)) = ooo_rx.recv() {
if next_for_execution == idx {
let _ = execute_tx.send(tx);
next_for_execution += 1;
@@ -372,7 +407,7 @@ where
transaction_count_hint: usize,
provider_builder: StateProviderBuilder<N, P>,
to_multi_proof: Option<CrossbeamSender<MultiProofMessage>>,
) -> CacheTaskHandle
) -> CacheTaskHandle<N::Receipt>
where
P: BlockReader + StateProviderFactory + StateReader + Clone + 'static,
{
@@ -382,9 +417,15 @@ where
transactions = mpsc::channel().1;
}
let saved_cache = self.cache_for(env.parent_hash);
let cache = saved_cache.cache().clone();
let cache_metrics = saved_cache.metrics().clone();
let (saved_cache, cache, cache_metrics) = if self.disable_state_cache {
(None, None, None)
} else {
let saved_cache = self.cache_for(env.parent_hash);
let cache = saved_cache.cache().clone();
let cache_metrics = saved_cache.metrics().clone();
(Some(saved_cache), Some(cache), Some(cache_metrics))
};
// configure prewarming
let prewarm_ctx = PrewarmContext {
env,
@@ -547,12 +588,15 @@ where
}
/// Handle to all the spawned tasks.
///
/// Generic over `R` (receipt type) to allow sharing `Arc<ExecutionOutcome<R>>` with the
/// caching task without cloning the expensive `BundleState`.
#[derive(Debug)]
pub struct PayloadHandle<Tx, Err> {
pub struct PayloadHandle<Tx, Err, R> {
/// Channel for evm state updates
to_multi_proof: Option<CrossbeamSender<MultiProofMessage>>,
// must include the receiver of the state root wired to the sparse trie
prewarm_handle: CacheTaskHandle,
prewarm_handle: CacheTaskHandle<R>,
/// Stream of block transactions
transactions: mpsc::Receiver<Result<Tx, Err>>,
/// Receiver for the state root
@@ -561,7 +605,7 @@ pub struct PayloadHandle<Tx, Err> {
_span: Span,
}
impl<Tx, Err> PayloadHandle<Tx, Err> {
impl<Tx, Err, R: Send + Sync + 'static> PayloadHandle<Tx, Err, R> {
/// Awaits the state root
///
/// # Panics
@@ -590,18 +634,18 @@ impl<Tx, Err> PayloadHandle<Tx, Err> {
move |source: StateChangeSource, state: &EvmState| {
if let Some(sender) = &to_multi_proof {
let _ = sender.send(MultiProofMessage::StateUpdate(source, state.clone()));
let _ = sender.send(MultiProofMessage::StateUpdate(source.into(), state.clone()));
}
}
}
/// Returns a clone of the caches used by prewarming
pub(super) fn caches(&self) -> StateExecutionCache {
pub(super) fn caches(&self) -> Option<StateExecutionCache> {
self.prewarm_handle.cache.clone()
}
/// Returns a clone of the cache metrics used by prewarming
pub(super) fn cache_metrics(&self) -> CachedStateMetrics {
pub(super) fn cache_metrics(&self) -> Option<CachedStateMetrics> {
self.prewarm_handle.cache_metrics.clone()
}
@@ -614,9 +658,14 @@ impl<Tx, Err> PayloadHandle<Tx, Err> {
/// Terminates the entire caching task.
///
/// If the [`BundleState`] is provided it will update the shared cache.
pub(super) fn terminate_caching(&mut self, block_output: Option<&BundleState>) {
self.prewarm_handle.terminate_caching(block_output)
/// If the [`ExecutionOutcome`] is provided it will update the shared cache using its
/// bundle state. Using `Arc<ExecutionOutcome>` allows sharing with the main execution
/// path without cloning the expensive `BundleState`.
pub(super) fn terminate_caching(
&mut self,
execution_outcome: Option<Arc<ExecutionOutcome<R>>>,
) {
self.prewarm_handle.terminate_caching(execution_outcome)
}
/// Returns iterator yielding transactions from the stream.
@@ -628,17 +677,20 @@ impl<Tx, Err> PayloadHandle<Tx, Err> {
}
/// Access to the spawned [`PrewarmCacheTask`].
///
/// Generic over `R` (receipt type) to allow sharing `Arc<ExecutionOutcome<R>>` with the
/// prewarm task without cloning the expensive `BundleState`.
#[derive(Debug)]
pub(crate) struct CacheTaskHandle {
pub(crate) struct CacheTaskHandle<R> {
/// The shared cache the task operates with.
cache: StateExecutionCache,
cache: Option<StateExecutionCache>,
/// Metrics for the caches
cache_metrics: CachedStateMetrics,
cache_metrics: Option<CachedStateMetrics>,
/// Channel to the spawned prewarm task if any
to_prewarm_task: Option<std::sync::mpsc::Sender<PrewarmTaskEvent>>,
to_prewarm_task: Option<std::sync::mpsc::Sender<PrewarmTaskEvent<R>>>,
}
impl CacheTaskHandle {
impl<R: Send + Sync + 'static> CacheTaskHandle<R> {
/// Terminates the pre-warming transaction processing.
///
/// Note: This does not terminate the task yet.
@@ -650,20 +702,25 @@ impl CacheTaskHandle {
/// Terminates the entire pre-warming task.
///
/// If the [`BundleState`] is provided it will update the shared cache.
pub(super) fn terminate_caching(&mut self, block_output: Option<&BundleState>) {
/// If the [`ExecutionOutcome`] is provided it will update the shared cache using its
/// bundle state. Using `Arc<ExecutionOutcome>` avoids cloning the expensive `BundleState`.
pub(super) fn terminate_caching(
&mut self,
execution_outcome: Option<Arc<ExecutionOutcome<R>>>,
) {
if let Some(tx) = self.to_prewarm_task.take() {
// Only clone when we have an active task and a state to send
let event = PrewarmTaskEvent::Terminate { block_output: block_output.cloned() };
let event = PrewarmTaskEvent::Terminate { execution_outcome };
let _ = tx.send(event);
}
}
}
impl Drop for CacheTaskHandle {
impl<R> Drop for CacheTaskHandle<R> {
fn drop(&mut self) {
// Ensure we always terminate on drop
self.terminate_caching(None);
// Ensure we always terminate on drop - send None without needing Send + Sync bounds
if let Some(tx) = self.to_prewarm_task.take() {
let _ = tx.send(PrewarmTaskEvent::Terminate { execution_outcome: None });
}
}
}
@@ -716,6 +773,8 @@ impl ExecutionCache {
cache
.as_ref()
// Check `is_available()` to ensure no other tasks (e.g., prewarming) currently hold
// a reference to this cache. We can only reuse it when we have exclusive access.
.filter(|c| c.executed_block_hash() == parent_hash && c.is_available())
.cloned()
}
@@ -972,12 +1031,10 @@ mod tests {
nonce: rng.random::<u64>(),
code_hash: KECCAK_EMPTY,
code: Some(Default::default()),
storage_id: None,
},
storage,
status: AccountStatus::Touched,
transaction_id: 0,
..Default::default()
};
state_update.insert(address, account);
@@ -1050,19 +1107,17 @@ mod tests {
let provider_factory = BlockchainProvider::new(factory).unwrap();
let mut handle =
payload_processor.spawn(
Default::default(),
(
core::iter::empty::<
Result<Recovered<TransactionSigned>, core::convert::Infallible>,
>(),
std::convert::identity,
),
StateProviderBuilder::new(provider_factory.clone(), genesis_hash, None),
OverlayStateProviderFactory::new(provider_factory),
&TreeConfig::default(),
);
let mut handle = payload_processor.spawn(
Default::default(),
(
Vec::<Result<Recovered<TransactionSigned>, core::convert::Infallible>>::new(),
std::convert::identity,
),
StateProviderBuilder::new(provider_factory.clone(), genesis_hash, None),
OverlayStateProviderFactory::new(provider_factory),
&TreeConfig::default(),
None, // No BAL for test
);
let mut state_hook = handle.state_hook();

View File

@@ -1,16 +1,15 @@
//! Multiproof task related functionality.
use crate::tree::payload_processor::bal::bal_to_hashed_post_state;
use alloy_eip7928::BlockAccessList;
use alloy_evm::block::StateChangeSource;
use alloy_primitives::{
keccak256,
map::{B256Set, HashSet},
B256,
};
use alloy_primitives::{keccak256, map::HashSet, B256};
use crossbeam_channel::{unbounded, Receiver as CrossbeamReceiver, Sender as CrossbeamSender};
use dashmap::DashMap;
use derive_more::derive::Deref;
use metrics::{Gauge, Histogram};
use reth_metrics::Metrics;
use reth_provider::AccountReader;
use reth_revm::state::EvmState;
use reth_trie::{
added_removed_keys::MultiAddedRemovedKeys, DecodedMultiProof, HashedPostState, HashedStorage,
@@ -20,12 +19,35 @@ use reth_trie_parallel::{
proof::ParallelProof,
proof_task::{
AccountMultiproofInput, ProofResultContext, ProofResultMessage, ProofWorkerHandle,
StorageProofInput,
},
};
use std::{collections::BTreeMap, mem, ops::DerefMut, sync::Arc, time::Instant};
use tracing::{debug, error, instrument, trace};
/// Source of state changes, either from EVM execution or from a Block Access List.
#[derive(Clone, Copy)]
pub enum Source {
/// State changes from EVM execution.
Evm(StateChangeSource),
/// State changes from Block Access List (EIP-7928).
BlockAccessList,
}
impl std::fmt::Debug for Source {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Evm(source) => source.fmt(f),
Self::BlockAccessList => f.write_str("BlockAccessList"),
}
}
}
impl From<StateChangeSource> for Source {
fn from(source: StateChangeSource) -> Self {
Self::Evm(source)
}
}
/// Maximum number of targets to batch together for prefetch batching.
/// Prefetches are just proof requests (no state merging), so we allow a higher cap than state
/// updates
@@ -82,7 +104,7 @@ pub(super) enum MultiProofMessage {
/// Prefetch proof targets
PrefetchProofs(MultiProofTargets),
/// New state update from transaction execution with its source
StateUpdate(StateChangeSource, EvmState),
StateUpdate(Source, EvmState),
/// State update that can be applied to the sparse trie without any new proofs.
///
/// It can be the case when all accounts and storage slots from the state update were already
@@ -93,6 +115,11 @@ pub(super) enum MultiProofMessage {
/// The state update that was used to calculate the proof
state: HashedPostState,
},
/// Block Access List (EIP-7928; BAL) containing complete state changes for the block.
///
/// When received, the task generates a single state update from the BAL and processes it.
/// No further messages are expected after receiving this variant.
BlockAccessList(Arc<BlockAccessList>),
/// Signals state update stream end.
///
/// This is triggered by block execution, indicating that no additional state updates are
@@ -138,11 +165,6 @@ impl ProofSequencer {
while let Some(pending) = self.pending_proofs.remove(&current_sequence) {
consecutive_proofs.push(pending);
current_sequence += 1;
// if we don't have the next number, stop collecting
if !self.pending_proofs.contains_key(&current_sequence) {
break;
}
}
self.next_to_deliver += consecutive_proofs.len() as u64;
@@ -209,78 +231,10 @@ pub(crate) fn evm_state_to_hashed_post_state(update: EvmState) -> HashedPostStat
hashed_state
}
/// A pending multiproof task, either [`StorageMultiproofInput`] or [`MultiproofInput`].
#[derive(Debug)]
enum PendingMultiproofTask {
/// A storage multiproof task input.
Storage(StorageMultiproofInput),
/// A regular multiproof task input.
Regular(MultiproofInput),
}
impl PendingMultiproofTask {
/// Returns the proof sequence number of the task.
const fn proof_sequence_number(&self) -> u64 {
match self {
Self::Storage(input) => input.proof_sequence_number,
Self::Regular(input) => input.proof_sequence_number,
}
}
/// Returns whether or not the proof targets are empty.
fn proof_targets_is_empty(&self) -> bool {
match self {
Self::Storage(input) => input.proof_targets.is_empty(),
Self::Regular(input) => input.proof_targets.is_empty(),
}
}
/// Destroys the input and sends a [`MultiProofMessage::EmptyProof`] message to the sender.
fn send_empty_proof(self) {
match self {
Self::Storage(input) => input.send_empty_proof(),
Self::Regular(input) => input.send_empty_proof(),
}
}
}
impl From<StorageMultiproofInput> for PendingMultiproofTask {
fn from(input: StorageMultiproofInput) -> Self {
Self::Storage(input)
}
}
impl From<MultiproofInput> for PendingMultiproofTask {
fn from(input: MultiproofInput) -> Self {
Self::Regular(input)
}
}
/// Input parameters for dispatching a dedicated storage multiproof calculation.
#[derive(Debug)]
struct StorageMultiproofInput {
hashed_state_update: HashedPostState,
hashed_address: B256,
proof_targets: B256Set,
proof_sequence_number: u64,
state_root_message_sender: CrossbeamSender<MultiProofMessage>,
multi_added_removed_keys: Arc<MultiAddedRemovedKeys>,
}
impl StorageMultiproofInput {
/// Destroys the input and sends a [`MultiProofMessage::EmptyProof`] message to the sender.
fn send_empty_proof(self) {
let _ = self.state_root_message_sender.send(MultiProofMessage::EmptyProof {
sequence_number: self.proof_sequence_number,
state: self.hashed_state_update,
});
}
}
/// Input parameters for dispatching a multiproof calculation.
#[derive(Debug)]
struct MultiproofInput {
source: Option<StateChangeSource>,
source: Option<Source>,
hashed_state_update: HashedPostState,
proof_targets: MultiProofTargets,
proof_sequence_number: u64,
@@ -351,91 +305,18 @@ impl MultiproofManager {
}
/// Dispatches a new multiproof calculation to worker pools.
fn dispatch(&self, input: PendingMultiproofTask) {
fn dispatch(&self, input: MultiproofInput) {
// If there are no proof targets, we can just send an empty multiproof back immediately
if input.proof_targets_is_empty() {
debug!(
sequence_number = input.proof_sequence_number(),
if input.proof_targets.is_empty() {
trace!(
sequence_number = input.proof_sequence_number,
"No proof targets, sending empty multiproof back immediately"
);
input.send_empty_proof();
return;
}
match input {
PendingMultiproofTask::Storage(storage_input) => {
self.dispatch_storage_proof(storage_input);
}
PendingMultiproofTask::Regular(multiproof_input) => {
self.dispatch_multiproof(multiproof_input);
}
}
}
/// Dispatches a single storage proof calculation to worker pool.
fn dispatch_storage_proof(&self, storage_multiproof_input: StorageMultiproofInput) {
let StorageMultiproofInput {
hashed_state_update,
hashed_address,
proof_targets,
proof_sequence_number,
multi_added_removed_keys,
state_root_message_sender: _,
} = storage_multiproof_input;
let storage_targets = proof_targets.len();
trace!(
target: "engine::tree::payload_processor::multiproof",
proof_sequence_number,
?proof_targets,
storage_targets,
"Dispatching storage proof to workers"
);
let start = Instant::now();
// Create prefix set from targets
let prefix_set = reth_trie::prefix_set::PrefixSetMut::from(
proof_targets.iter().map(reth_trie::Nibbles::unpack),
);
let prefix_set = prefix_set.freeze();
// Build computation input (data only)
let input = StorageProofInput::new(
hashed_address,
prefix_set,
proof_targets,
true, // with_branch_node_masks
Some(multi_added_removed_keys),
);
// Dispatch to storage worker
if let Err(e) = self.proof_worker_handle.dispatch_storage_proof(
input,
ProofResultContext::new(
self.proof_result_tx.clone(),
proof_sequence_number,
hashed_state_update,
start,
),
) {
error!(target: "engine::tree::payload_processor::multiproof", ?e, "Failed to dispatch storage proof");
return;
}
self.metrics
.active_storage_workers_histogram
.record(self.proof_worker_handle.active_storage_workers() as f64);
self.metrics
.active_account_workers_histogram
.record(self.proof_worker_handle.active_account_workers() as f64);
self.metrics
.pending_storage_multiproofs_histogram
.record(self.proof_worker_handle.pending_storage_tasks() as f64);
self.metrics
.pending_account_multiproofs_histogram
.record(self.proof_worker_handle.pending_account_tasks() as f64);
self.dispatch_multiproof(input);
}
/// Signals that a multiproof calculation has finished.
@@ -782,17 +663,14 @@ impl MultiProofTask {
available_storage_workers,
MultiProofTargets::chunks,
|proof_targets| {
self.multiproof_manager.dispatch(
MultiproofInput {
source: None,
hashed_state_update: Default::default(),
proof_targets,
proof_sequence_number: self.proof_sequencer.next_sequence(),
state_root_message_sender: self.tx.clone(),
multi_added_removed_keys: Some(multi_added_removed_keys.clone()),
}
.into(),
);
self.multiproof_manager.dispatch(MultiproofInput {
source: None,
hashed_state_update: Default::default(),
proof_targets,
proof_sequence_number: self.proof_sequencer.next_sequence(),
state_root_message_sender: self.tx.clone(),
multi_added_removed_keys: Some(multi_added_removed_keys.clone()),
});
},
);
self.metrics.prefetch_proof_chunks_histogram.record(num_chunks as f64);
@@ -883,9 +761,19 @@ impl MultiProofTask {
skip(self, update),
fields(accounts = update.len(), chunks = 0)
)]
fn on_state_update(&mut self, source: StateChangeSource, update: EvmState) -> u64 {
fn on_state_update(&mut self, source: Source, update: EvmState) -> u64 {
let hashed_state_update = evm_state_to_hashed_post_state(update);
self.on_hashed_state_update(source, hashed_state_update)
}
/// Processes a hashed state update and dispatches multiproofs as needed.
///
/// Returns the number of state updates dispatched (both `EmptyProof` and regular multiproofs).
fn on_hashed_state_update(
&mut self,
source: Source,
hashed_state_update: HashedPostState,
) -> u64 {
// Update removed keys based on the state update.
self.multi_added_removed_keys.update_with_state(&hashed_state_update);
@@ -930,17 +818,14 @@ impl MultiProofTask {
);
spawned_proof_targets.extend_ref(&proof_targets);
self.multiproof_manager.dispatch(
MultiproofInput {
source: Some(source),
hashed_state_update,
proof_targets,
proof_sequence_number: self.proof_sequencer.next_sequence(),
state_root_message_sender: self.tx.clone(),
multi_added_removed_keys: Some(multi_added_removed_keys.clone()),
}
.into(),
);
self.multiproof_manager.dispatch(MultiproofInput {
source: Some(source),
hashed_state_update,
proof_targets,
proof_sequence_number: self.proof_sequencer.next_sequence(),
state_root_message_sender: self.tx.clone(),
multi_added_removed_keys: Some(multi_added_removed_keys.clone()),
});
},
);
self.metrics
@@ -982,12 +867,16 @@ impl MultiProofTask {
/// This preserves ordering without requeuing onto the channel.
///
/// Returns `true` if done, `false` to continue.
fn process_multiproof_message(
fn process_multiproof_message<P>(
&mut self,
msg: MultiProofMessage,
ctx: &mut MultiproofBatchCtx,
batch_metrics: &mut MultiproofBatchMetrics,
) -> bool {
provider: &P,
) -> bool
where
P: AccountReader,
{
match msg {
// Prefetch proofs: batch consecutive prefetch requests up to target/message limits
MultiProofMessage::PrefetchProofs(targets) => {
@@ -1045,7 +934,7 @@ impl MultiProofTask {
let storage_targets =
merged_targets.values().map(|slots| slots.len()).sum::<usize>();
batch_metrics.prefetch_proofs_requested += self.on_prefetch_proof(merged_targets);
debug!(
trace!(
target: "engine::tree::payload_processor::multiproof",
account_targets,
storage_targets,
@@ -1135,7 +1024,7 @@ impl MultiProofTask {
let batch_len = merged_update.len();
batch_metrics.state_update_proofs_requested +=
self.on_state_update(batch_source, merged_update);
debug!(
trace!(
target: "engine::tree::payload_processor::multiproof",
?batch_source,
len = batch_len,
@@ -1146,6 +1035,56 @@ impl MultiProofTask {
false
}
// Process Block Access List (BAL) - complete state changes provided upfront
MultiProofMessage::BlockAccessList(bal) => {
trace!(target: "engine::tree::payload_processor::multiproof", "processing MultiProofMessage::BAL");
if ctx.first_update_time.is_none() {
self.metrics
.first_update_wait_time_histogram
.record(ctx.start.elapsed().as_secs_f64());
ctx.first_update_time = Some(Instant::now());
debug!(target: "engine::tree::payload_processor::multiproof", "Started state root calculation from BAL");
}
// Convert BAL to HashedPostState and process it
match bal_to_hashed_post_state(&bal, &provider) {
Ok(hashed_state) => {
debug!(
target: "engine::tree::payload_processor::multiproof",
accounts = hashed_state.accounts.len(),
storages = hashed_state.storages.len(),
"Processing BAL state update"
);
// Use BlockAccessList as source for BAL-derived state updates
batch_metrics.state_update_proofs_requested +=
self.on_hashed_state_update(Source::BlockAccessList, hashed_state);
}
Err(err) => {
error!(target: "engine::tree::payload_processor::multiproof", ?err, "Failed to convert BAL to hashed state");
return true;
}
}
// Mark updates as finished since BAL provides complete state
ctx.updates_finished_time = Some(Instant::now());
// Check if we're done (might need to wait for proofs to complete)
if self.is_done(
batch_metrics.proofs_processed,
batch_metrics.state_update_proofs_requested,
batch_metrics.prefetch_proofs_requested,
ctx.updates_finished(),
) {
debug!(
target: "engine::tree::payload_processor::multiproof",
"BAL processed and all proofs complete, ending calculation"
);
return true;
}
false
}
// Signal that no more state updates will arrive
MultiProofMessage::FinishedStateUpdates => {
trace!(target: "engine::tree::payload_processor::multiproof", "processing MultiProofMessage::FinishedStateUpdates");
@@ -1238,7 +1177,10 @@ impl MultiProofTask {
target = "engine::tree::payload_processor::multiproof",
skip_all
)]
pub(crate) fn run(mut self) {
pub(crate) fn run<P>(mut self, provider: P)
where
P: AccountReader,
{
let mut ctx = MultiproofBatchCtx::new(Instant::now());
let mut batch_metrics = MultiproofBatchMetrics::default();
@@ -1248,7 +1190,7 @@ impl MultiProofTask {
trace!(target: "engine::tree::payload_processor::multiproof", "entering main channel receiving loop");
if let Some(msg) = ctx.pending_msg.take() {
if self.process_multiproof_message(msg, &mut ctx, &mut batch_metrics) {
if self.process_multiproof_message(msg, &mut ctx, &mut batch_metrics, &provider) {
break 'main;
}
continue;
@@ -1271,7 +1213,7 @@ impl MultiProofTask {
// Convert ProofResultMessage to SparseTrieUpdate
match proof_result.result {
Ok(proof_result_data) => {
debug!(
trace!(
target: "engine::tree::payload_processor::multiproof",
sequence = proof_result.sequence_number,
total_proofs = batch_metrics.proofs_processed,
@@ -1323,7 +1265,7 @@ impl MultiProofTask {
}
};
if self.process_multiproof_message(msg, &mut ctx, &mut batch_metrics) {
if self.process_multiproof_message(msg, &mut ctx, &mut batch_metrics, &provider) {
break 'main;
}
}
@@ -1359,6 +1301,9 @@ impl MultiProofTask {
/// Context for multiproof message batching loop.
///
/// Contains processing state that persists across loop iterations.
///
/// Used by `process_multiproof_message` to batch consecutive same-type messages received via
/// `try_recv` for efficient processing.
struct MultiproofBatchCtx {
/// Buffers a non-matching message type encountered during batching.
/// Processed first in next iteration to preserve ordering while allowing same-type
@@ -1374,7 +1319,7 @@ struct MultiproofBatchCtx {
/// Reusable buffer for accumulating prefetch targets during batching.
accumulated_prefetch_targets: Vec<MultiProofTargets>,
/// Reusable buffer for accumulating state updates during batching.
accumulated_state_updates: Vec<(StateChangeSource, EvmState)>,
accumulated_state_updates: Vec<(Source, EvmState)>,
}
impl MultiproofBatchCtx {
@@ -1492,34 +1437,44 @@ where
/// are safe to merge because they originate from the same logical execution and can be
/// coalesced to amortize proof work.
fn can_batch_state_update(
batch_source: StateChangeSource,
batch_source: Source,
batch_update: &EvmState,
next_source: StateChangeSource,
next_source: Source,
next_update: &EvmState,
) -> bool {
if !same_state_change_source(batch_source, next_source) {
if !same_source(batch_source, next_source) {
return false;
}
match (batch_source, next_source) {
(StateChangeSource::PreBlock(_), StateChangeSource::PreBlock(_)) |
(StateChangeSource::PostBlock(_), StateChangeSource::PostBlock(_)) => {
batch_update == next_update
}
(
Source::Evm(StateChangeSource::PreBlock(_)),
Source::Evm(StateChangeSource::PreBlock(_)),
) |
(
Source::Evm(StateChangeSource::PostBlock(_)),
Source::Evm(StateChangeSource::PostBlock(_)),
) => batch_update == next_update,
_ => true,
}
}
/// Checks whether two state change sources refer to the same origin.
fn same_state_change_source(lhs: StateChangeSource, rhs: StateChangeSource) -> bool {
/// Checks whether two sources refer to the same origin.
fn same_source(lhs: Source, rhs: Source) -> bool {
match (lhs, rhs) {
(StateChangeSource::Transaction(a), StateChangeSource::Transaction(b)) => a == b,
(StateChangeSource::PreBlock(a), StateChangeSource::PreBlock(b)) => {
mem::discriminant(&a) == mem::discriminant(&b)
}
(StateChangeSource::PostBlock(a), StateChangeSource::PostBlock(b)) => {
mem::discriminant(&a) == mem::discriminant(&b)
}
(
Source::Evm(StateChangeSource::Transaction(a)),
Source::Evm(StateChangeSource::Transaction(b)),
) => a == b,
(
Source::Evm(StateChangeSource::PreBlock(a)),
Source::Evm(StateChangeSource::PreBlock(b)),
) => mem::discriminant(&a) == mem::discriminant(&b),
(
Source::Evm(StateChangeSource::PostBlock(a)),
Source::Evm(StateChangeSource::PostBlock(b)),
) => mem::discriminant(&a) == mem::discriminant(&b),
(Source::BlockAccessList, Source::BlockAccessList) => true,
_ => false,
}
}
@@ -1539,7 +1494,8 @@ fn estimate_evm_state_targets(state: &EvmState) -> usize {
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::map::B256Set;
use alloy_eip7928::{AccountChanges, BalanceChange};
use alloy_primitives::{map::B256Set, Address};
use reth_provider::{
providers::OverlayStateProviderFactory, test_utils::create_test_provider_factory,
BlockReader, DatabaseProviderFactory, PruneCheckpointReader, StageCheckpointReader,
@@ -1548,7 +1504,7 @@ mod tests {
use reth_trie::MultiProof;
use reth_trie_parallel::proof_task::{ProofTaskCtx, ProofWorkerHandle};
use revm_primitives::{B256, U256};
use std::sync::OnceLock;
use std::sync::{Arc, OnceLock};
use tokio::runtime::{Handle, Runtime};
/// Get a handle to the test runtime, creating it if necessary
@@ -2083,9 +2039,7 @@ mod tests {
nonce: 1,
code_hash: Default::default(),
code: Default::default(),
storage_id: Some(0),
},
original_info: Default::default(),
transaction_id: Default::default(),
storage: Default::default(),
status: revm_state::AccountStatus::Touched,
@@ -2101,9 +2055,7 @@ mod tests {
nonce: 2,
code_hash: Default::default(),
code: Default::default(),
storage_id: Some(0),
},
original_info: Default::default(),
transaction_id: Default::default(),
storage: Default::default(),
status: revm_state::AccountStatus::Touched,
@@ -2113,8 +2065,8 @@ mod tests {
let source = StateChangeSource::Transaction(0);
let tx = task.state_root_message_sender();
tx.send(MultiProofMessage::StateUpdate(source, update1.clone())).unwrap();
tx.send(MultiProofMessage::StateUpdate(source, update2.clone())).unwrap();
tx.send(MultiProofMessage::StateUpdate(source.into(), update1.clone())).unwrap();
tx.send(MultiProofMessage::StateUpdate(source.into(), update2.clone())).unwrap();
let proofs_requested =
if let Ok(MultiProofMessage::StateUpdate(_src, update)) = task.rx.recv() {
@@ -2133,7 +2085,7 @@ mod tests {
assert!(merged_update.contains_key(&addr1));
assert!(merged_update.contains_key(&addr2));
task.on_state_update(source, merged_update)
task.on_state_update(source.into(), merged_update)
} else {
panic!("Expected StateUpdate message");
};
@@ -2163,9 +2115,7 @@ mod tests {
nonce: 1,
code_hash: Default::default(),
code: Default::default(),
storage_id: Some(0),
},
original_info: Default::default(),
transaction_id: Default::default(),
storage: Default::default(),
status: revm_state::AccountStatus::Touched,
@@ -2179,20 +2129,20 @@ mod tests {
// Queue: A1 (immediate dispatch), B1 (batched), A2 (should become pending)
let tx = task.state_root_message_sender();
tx.send(MultiProofMessage::StateUpdate(source_a, create_state_update(addr_a1, 100)))
tx.send(MultiProofMessage::StateUpdate(source_a.into(), create_state_update(addr_a1, 100)))
.unwrap();
tx.send(MultiProofMessage::StateUpdate(source_b, create_state_update(addr_b1, 200)))
tx.send(MultiProofMessage::StateUpdate(source_b.into(), create_state_update(addr_b1, 200)))
.unwrap();
tx.send(MultiProofMessage::StateUpdate(source_a, create_state_update(addr_a2, 300)))
tx.send(MultiProofMessage::StateUpdate(source_a.into(), create_state_update(addr_a2, 300)))
.unwrap();
let mut pending_msg: Option<MultiProofMessage> = None;
if let Ok(MultiProofMessage::StateUpdate(first_source, _)) = task.rx.recv() {
assert!(same_state_change_source(first_source, source_a));
assert!(same_source(first_source, source_a.into()));
// Simulate batching loop for remaining messages
let mut accumulated_updates: Vec<(StateChangeSource, EvmState)> = Vec::new();
let mut accumulated_updates: Vec<(Source, EvmState)> = Vec::new();
let mut accumulated_targets = 0usize;
loop {
@@ -2240,7 +2190,7 @@ mod tests {
assert_eq!(accumulated_updates.len(), 1, "Should only batch matching sources");
let batch_source = accumulated_updates[0].0;
assert!(same_state_change_source(batch_source, source_b));
assert!(same_source(batch_source, source_b.into()));
let batch_source = accumulated_updates[0].0;
let mut merged_update = accumulated_updates.remove(0).1;
@@ -2248,10 +2198,7 @@ mod tests {
merged_update.extend(next_update);
}
assert!(
same_state_change_source(batch_source, source_b),
"Batch should use matching source"
);
assert!(same_source(batch_source, source_b.into()), "Batch should use matching source");
assert!(merged_update.contains_key(&addr_b1));
assert!(!merged_update.contains_key(&addr_a1));
assert!(!merged_update.contains_key(&addr_a2));
@@ -2261,7 +2208,7 @@ mod tests {
match pending_msg {
Some(MultiProofMessage::StateUpdate(pending_source, pending_update)) => {
assert!(same_state_change_source(pending_source, source_a));
assert!(same_source(pending_source, source_a.into()));
assert!(pending_update.contains_key(&addr_a2));
}
other => panic!("Expected pending StateUpdate with source_a, got {:?}", other),
@@ -2291,9 +2238,7 @@ mod tests {
nonce: 1,
code_hash: Default::default(),
code: Default::default(),
storage_id: Some(0),
},
original_info: Default::default(),
transaction_id: Default::default(),
storage: Default::default(),
status: revm_state::AccountStatus::Touched,
@@ -2306,17 +2251,20 @@ mod tests {
// Queue: first update dispatched immediately, next two should not merge
let tx = task.state_root_message_sender();
tx.send(MultiProofMessage::StateUpdate(source, create_state_update(addr1, 100))).unwrap();
tx.send(MultiProofMessage::StateUpdate(source, create_state_update(addr2, 200))).unwrap();
tx.send(MultiProofMessage::StateUpdate(source, create_state_update(addr3, 300))).unwrap();
tx.send(MultiProofMessage::StateUpdate(source.into(), create_state_update(addr1, 100)))
.unwrap();
tx.send(MultiProofMessage::StateUpdate(source.into(), create_state_update(addr2, 200)))
.unwrap();
tx.send(MultiProofMessage::StateUpdate(source.into(), create_state_update(addr3, 300)))
.unwrap();
let mut pending_msg: Option<MultiProofMessage> = None;
if let Ok(MultiProofMessage::StateUpdate(first_source, first_update)) = task.rx.recv() {
assert!(same_state_change_source(first_source, source));
assert!(same_source(first_source, source.into()));
assert!(first_update.contains_key(&addr1));
let mut accumulated_updates: Vec<(StateChangeSource, EvmState)> = Vec::new();
let mut accumulated_updates: Vec<(Source, EvmState)> = Vec::new();
let mut accumulated_targets = 0usize;
loop {
@@ -2368,7 +2316,7 @@ mod tests {
"Second pre-block update should not merge with a different payload"
);
let (batched_source, batched_update) = accumulated_updates.remove(0);
assert!(same_state_change_source(batched_source, source));
assert!(same_source(batched_source, source.into()));
assert!(batched_update.contains_key(&addr2));
assert!(!batched_update.contains_key(&addr3));
@@ -2418,9 +2366,7 @@ mod tests {
nonce: 1,
code_hash: Default::default(),
code: Default::default(),
storage_id: Some(0),
},
original_info: Default::default(),
transaction_id: Default::default(),
storage: Default::default(),
status: revm_state::AccountStatus::Touched,
@@ -2437,9 +2383,7 @@ mod tests {
nonce: 2,
code_hash: Default::default(),
code: Default::default(),
storage_id: Some(0),
},
original_info: Default::default(),
transaction_id: Default::default(),
storage: Default::default(),
status: revm_state::AccountStatus::Touched,
@@ -2452,8 +2396,8 @@ mod tests {
let tx = task.state_root_message_sender();
tx.send(MultiProofMessage::PrefetchProofs(targets1)).unwrap();
tx.send(MultiProofMessage::PrefetchProofs(targets2)).unwrap();
tx.send(MultiProofMessage::StateUpdate(source, state_update1)).unwrap();
tx.send(MultiProofMessage::StateUpdate(source, state_update2)).unwrap();
tx.send(MultiProofMessage::StateUpdate(source.into(), state_update1)).unwrap();
tx.send(MultiProofMessage::StateUpdate(source.into(), state_update2)).unwrap();
tx.send(MultiProofMessage::PrefetchProofs(targets3.clone())).unwrap();
// Step 1: Receive and batch PrefetchProofs (should get targets1 + targets2)
@@ -2520,6 +2464,7 @@ mod tests {
use revm_state::Account;
let test_provider_factory = create_test_provider_factory();
let test_provider = test_provider_factory.latest().unwrap();
let mut task = create_test_state_root_task(test_provider_factory);
// Queue: Prefetch1, StateUpdate, Prefetch2
@@ -2540,9 +2485,7 @@ mod tests {
nonce: 1,
code_hash: Default::default(),
code: Default::default(),
storage_id: Some(0),
},
original_info: Default::default(),
transaction_id: Default::default(),
storage: Default::default(),
status: revm_state::AccountStatus::Touched,
@@ -2553,7 +2496,7 @@ mod tests {
let tx = task.state_root_message_sender();
tx.send(MultiProofMessage::PrefetchProofs(prefetch1)).unwrap();
tx.send(MultiProofMessage::StateUpdate(source, state_update)).unwrap();
tx.send(MultiProofMessage::StateUpdate(source.into(), state_update)).unwrap();
tx.send(MultiProofMessage::PrefetchProofs(prefetch2.clone())).unwrap();
let mut ctx = MultiproofBatchCtx::new(Instant::now());
@@ -2562,12 +2505,22 @@ mod tests {
// First message: Prefetch1 batches; StateUpdate becomes pending.
let first = task.rx.recv().unwrap();
assert!(matches!(first, MultiProofMessage::PrefetchProofs(_)));
assert!(!task.process_multiproof_message(first, &mut ctx, &mut batch_metrics));
assert!(!task.process_multiproof_message(
first,
&mut ctx,
&mut batch_metrics,
&test_provider
));
let pending = ctx.pending_msg.take().expect("pending message captured");
assert!(matches!(pending, MultiProofMessage::StateUpdate(_, _)));
// Pending message should be handled before the next select loop.
assert!(!task.process_multiproof_message(pending, &mut ctx, &mut batch_metrics));
assert!(!task.process_multiproof_message(
pending,
&mut ctx,
&mut batch_metrics,
&test_provider
));
// Prefetch2 should now be in pending_msg (captured by StateUpdate's batching loop).
match ctx.pending_msg.take() {
@@ -2625,9 +2578,7 @@ mod tests {
nonce: 1,
code_hash: Default::default(),
code: Default::default(),
storage_id: Some(0),
},
original_info: Default::default(),
transaction_id: Default::default(),
storage: Default::default(),
status: revm_state::AccountStatus::Touched,
@@ -2641,12 +2592,21 @@ mod tests {
// Queue: [Prefetch1, State1, State2, State3, Prefetch2]
let tx = task.state_root_message_sender();
tx.send(MultiProofMessage::PrefetchProofs(prefetch1.clone())).unwrap();
tx.send(MultiProofMessage::StateUpdate(source, create_state_update(state_addr1, 100)))
.unwrap();
tx.send(MultiProofMessage::StateUpdate(source, create_state_update(state_addr2, 200)))
.unwrap();
tx.send(MultiProofMessage::StateUpdate(source, create_state_update(state_addr3, 300)))
.unwrap();
tx.send(MultiProofMessage::StateUpdate(
source.into(),
create_state_update(state_addr1, 100),
))
.unwrap();
tx.send(MultiProofMessage::StateUpdate(
source.into(),
create_state_update(state_addr2, 200),
))
.unwrap();
tx.send(MultiProofMessage::StateUpdate(
source.into(),
create_state_update(state_addr3, 300),
))
.unwrap();
tx.send(MultiProofMessage::PrefetchProofs(prefetch2.clone())).unwrap();
// Simulate the state-machine loop behavior
@@ -2719,4 +2679,44 @@ mod tests {
_ => panic!("Prefetch2 was lost!"),
}
}
/// Verifies that BAL messages are processed correctly and generate state updates.
#[test]
fn test_bal_message_processing() {
let test_provider_factory = create_test_provider_factory();
let test_provider = test_provider_factory.latest().unwrap();
let mut task = create_test_state_root_task(test_provider_factory);
// Create a simple BAL with one account change
let account_address = Address::random();
let account_changes = AccountChanges {
address: account_address,
balance_changes: vec![BalanceChange::new(0, U256::from(1000))],
nonce_changes: vec![],
code_changes: vec![],
storage_changes: vec![],
storage_reads: vec![],
};
let bal = Arc::new(vec![account_changes]);
let mut ctx = MultiproofBatchCtx::new(Instant::now());
let mut batch_metrics = MultiproofBatchMetrics::default();
let should_finish = task.process_multiproof_message(
MultiProofMessage::BlockAccessList(bal),
&mut ctx,
&mut batch_metrics,
&test_provider,
);
// BAL should mark updates as finished
assert!(ctx.updates_finished_time.is_some());
// Should have dispatched state update proofs
assert!(batch_metrics.state_update_proofs_requested > 0);
// Should need to wait for the results of those proofs to arrive
assert!(!should_finish, "Should continue waiting for proofs");
}
}

View File

@@ -27,10 +27,11 @@ use alloy_primitives::{keccak256, map::B256Set, B256};
use crossbeam_channel::Sender as CrossbeamSender;
use metrics::{Counter, Gauge, Histogram};
use reth_evm::{execute::ExecutableTxFor, ConfigureEvm, Evm, EvmFor, SpecFor};
use reth_execution_types::ExecutionOutcome;
use reth_metrics::Metrics;
use reth_primitives_traits::NodePrimitives;
use reth_provider::{BlockReader, StateProviderFactory, StateReader};
use reth_revm::{database::StateProviderDatabase, db::BundleState, state::EvmState};
use reth_revm::{database::StateProviderDatabase, state::EvmState};
use reth_trie::MultiProofTargets;
use std::{
sync::{
@@ -86,7 +87,7 @@ where
/// Sender to emit evm state outcome messages, if any.
to_multi_proof: Option<CrossbeamSender<MultiProofMessage>>,
/// Receiver for events produced by tx execution
actions_rx: Receiver<PrewarmTaskEvent>,
actions_rx: Receiver<PrewarmTaskEvent<N::Receipt>>,
/// Parent span for tracing
parent_span: Span,
}
@@ -105,7 +106,7 @@ where
to_multi_proof: Option<CrossbeamSender<MultiProofMessage>>,
transaction_count_hint: usize,
max_concurrency: usize,
) -> (Self, Sender<PrewarmTaskEvent>) {
) -> (Self, Sender<PrewarmTaskEvent<N::Receipt>>) {
let (actions_tx, actions_rx) = channel();
trace!(
@@ -135,8 +136,11 @@ where
/// For Optimism chains, special handling is applied to the first transaction if it's a
/// deposit transaction (type 0x7E/126) which sets critical metadata that affects all
/// subsequent transactions in the block.
fn spawn_all<Tx>(&self, pending: mpsc::Receiver<Tx>, actions_tx: Sender<PrewarmTaskEvent>)
where
fn spawn_all<Tx>(
&self,
pending: mpsc::Receiver<Tx>,
actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>,
) where
Tx: ExecutableTxFor<Evm> + Clone + Send + 'static,
{
let executor = self.executor.clone();
@@ -248,38 +252,43 @@ where
///
/// This method is called from `run()` only after all execution tasks are complete.
#[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
fn save_cache(self, state: BundleState) {
fn save_cache(self, execution_outcome: Arc<ExecutionOutcome<N::Receipt>>) {
let start = Instant::now();
let Self { execution_cache, ctx: PrewarmContext { env, metrics, saved_cache, .. }, .. } =
self;
let hash = env.hash;
debug!(target: "engine::caching", parent_hash=?hash, "Updating execution cache");
// Perform all cache operations atomically under the lock
execution_cache.update_with_guard(|cached| {
// consumes the `SavedCache` held by the prewarming task, which releases its usage guard
let (caches, cache_metrics) = saved_cache.split();
let new_cache = SavedCache::new(hash, caches, cache_metrics);
if let Some(saved_cache) = saved_cache {
debug!(target: "engine::caching", parent_hash=?hash, "Updating execution cache");
// Perform all cache operations atomically under the lock
execution_cache.update_with_guard(|cached| {
// consumes the `SavedCache` held by the prewarming task, which releases its usage
// guard
let (caches, cache_metrics) = saved_cache.split();
let new_cache = SavedCache::new(hash, caches, cache_metrics);
// Insert state into cache while holding the lock
if new_cache.cache().insert_state(&state).is_err() {
// Clear the cache on error to prevent having a polluted cache
*cached = None;
debug!(target: "engine::caching", "cleared execution cache on update error");
return;
}
// Insert state into cache while holding the lock
// Access the BundleState through the shared ExecutionOutcome
if new_cache.cache().insert_state(execution_outcome.state()).is_err() {
// Clear the cache on error to prevent having a polluted cache
*cached = None;
debug!(target: "engine::caching", "cleared execution cache on update error");
return;
}
new_cache.update_metrics();
new_cache.update_metrics();
// Replace the shared cache with the new one; the previous cache (if any) is dropped.
*cached = Some(new_cache);
});
// Replace the shared cache with the new one; the previous cache (if any) is
// dropped.
*cached = Some(new_cache);
});
let elapsed = start.elapsed();
debug!(target: "engine::caching", parent_hash=?hash, elapsed=?elapsed, "Updated execution cache");
let elapsed = start.elapsed();
debug!(target: "engine::caching", parent_hash=?hash, elapsed=?elapsed, "Updated execution cache");
metrics.cache_saving_duration.set(elapsed.as_secs_f64());
metrics.cache_saving_duration.set(elapsed.as_secs_f64());
}
}
/// Executes the task.
@@ -296,12 +305,12 @@ where
pub(super) fn run(
self,
pending: mpsc::Receiver<impl ExecutableTxFor<Evm> + Clone + Send + 'static>,
actions_tx: Sender<PrewarmTaskEvent>,
actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>,
) {
// spawn execution tasks.
self.spawn_all(pending, actions_tx);
let mut final_block_output = None;
let mut final_execution_outcome = None;
let mut finished_execution = false;
while let Ok(event) = self.actions_rx.recv() {
match event {
@@ -314,9 +323,9 @@ where
// completed executing a set of transactions
self.send_multi_proof_targets(proof_targets);
}
PrewarmTaskEvent::Terminate { block_output } => {
PrewarmTaskEvent::Terminate { execution_outcome } => {
trace!(target: "engine::tree::payload_processor::prewarm", "Received termination signal");
final_block_output = Some(block_output);
final_execution_outcome = Some(execution_outcome);
if finished_execution {
// all tasks are done, we can exit, which will save caches and exit
@@ -330,7 +339,7 @@ where
finished_execution = true;
if final_block_output.is_some() {
if final_execution_outcome.is_some() {
// all tasks are done, we can exit, which will save caches and exit
break
}
@@ -340,9 +349,9 @@ where
debug!(target: "engine::tree::payload_processor::prewarm", "Completed prewarm execution");
// save caches and finish
if let Some(Some(state)) = final_block_output {
self.save_cache(state);
// save caches and finish using the shared ExecutionOutcome
if let Some(Some(execution_outcome)) = final_execution_outcome {
self.save_cache(execution_outcome);
}
}
}
@@ -356,7 +365,7 @@ where
{
pub(super) env: ExecutionEnv<Evm>,
pub(super) evm_config: Evm,
pub(super) saved_cache: SavedCache,
pub(super) saved_cache: Option<SavedCache>,
/// Provider to obtain the state
pub(super) provider: StateProviderBuilder<N, P>,
pub(super) metrics: PrewarmMetrics,
@@ -384,10 +393,10 @@ where
metrics,
terminate_execution,
precompile_cache_disabled,
mut precompile_cache_map,
precompile_cache_map,
} = self;
let state_provider = match provider.build() {
let mut state_provider = match provider.build() {
Ok(provider) => provider,
Err(err) => {
trace!(
@@ -400,10 +409,15 @@ where
};
// Use the caches to create a new provider with caching
let caches = saved_cache.cache().clone();
let cache_metrics = saved_cache.metrics().clone();
let state_provider =
CachedStateProvider::new_with_caches(state_provider, caches, cache_metrics);
if let Some(saved_cache) = saved_cache {
let caches = saved_cache.cache().clone();
let cache_metrics = saved_cache.metrics().clone();
state_provider = Box::new(
CachedStateProvider::new(state_provider, caches, cache_metrics)
// ensure we pre-warm the cache
.prewarm(),
);
}
let state_provider = StateProviderDatabase::new(state_provider);
@@ -445,7 +459,7 @@ where
fn transact_batch<Tx>(
self,
txs: mpsc::Receiver<IndexedTransaction<Tx>>,
sender: Sender<PrewarmTaskEvent>,
sender: Sender<PrewarmTaskEvent<N::Receipt>>,
done_tx: Sender<()>,
) where
Tx: ExecutableTxFor<Evm>,
@@ -526,7 +540,7 @@ where
&self,
idx: usize,
executor: &WorkloadExecutor,
actions_tx: Sender<PrewarmTaskEvent>,
actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>,
done_tx: Sender<()>,
) -> mpsc::Sender<IndexedTransaction<Tx>>
where
@@ -582,14 +596,18 @@ fn multiproof_targets_from_state(state: EvmState) -> (MultiProofTargets, usize)
}
/// The events the pre-warm task can handle.
pub(super) enum PrewarmTaskEvent {
///
/// Generic over `R` (receipt type) to allow sharing `Arc<ExecutionOutcome<R>>` with the main
/// execution path without cloning the expensive `BundleState`.
pub(super) enum PrewarmTaskEvent<R> {
/// Forcefully terminate all remaining transaction execution.
TerminateTransactionExecution,
/// Forcefully terminate the task on demand and update the shared cache with the given output
/// before exiting.
Terminate {
/// The final block state output.
block_output: Option<BundleState>,
/// The final execution outcome. Using `Arc` allows sharing with the main execution
/// path without cloning the expensive `BundleState`.
execution_outcome: Option<Arc<ExecutionOutcome<R>>>,
},
/// The outcome of a pre-warm task
Outcome {

View File

@@ -166,8 +166,7 @@ where
// Update storage slots with new values and calculate storage roots.
let span = tracing::Span::current();
let (tx, rx) = mpsc::channel();
state
let results: Vec<_> = state
.storages
.into_iter()
.map(|(address, storage)| (address, storage, trie.take_storage_trie(&address)))
@@ -217,13 +216,7 @@ where
SparseStateTrieResult::Ok((address, storage_trie))
})
.for_each_init(
|| tx.clone(),
|tx, result| {
let _ = tx.send(result);
},
);
drop(tx);
.collect();
// Defer leaf removals until after updates/additions, so that we don't delete an intermediate
// branch node during a removal and then re-add that branch back during a later leaf addition.
@@ -235,7 +228,7 @@ where
let _enter =
tracing::debug_span!(target: "engine::tree::payload_processor::sparse_trie", "account trie")
.entered();
for result in rx {
for result in results {
let (address, storage_trie) = result?;
trie.insert_storage_trie(address, storage_trie);

View File

@@ -11,9 +11,11 @@ use crate::tree::{
StateProviderDatabase, TreeConfig,
};
use alloy_consensus::transaction::Either;
use alloy_eip7928::BlockAccessList;
use alloy_eips::{eip1898::BlockWithParent, NumHash};
use alloy_evm::Evm;
use alloy_primitives::B256;
use rayon::prelude::*;
use reth_chain_state::{CanonicalInMemoryState, DeferredTrieData, ExecutedBlock};
use reth_consensus::{ConsensusError, FullConsensus};
use reth_engine_primitives::{
@@ -33,12 +35,13 @@ use reth_primitives_traits::{
};
use reth_provider::{
providers::OverlayStateProviderFactory, BlockExecutionOutput, BlockReader,
DatabaseProviderFactory, ExecutionOutcome, HashedPostStateProvider, ProviderError,
PruneCheckpointReader, StageCheckpointReader, StateProvider, StateProviderFactory, StateReader,
StateRootProvider, TrieReader,
DatabaseProviderFactory, DatabaseProviderROFactory, ExecutionOutcome, HashedPostStateProvider,
ProviderError, PruneCheckpointReader, StageCheckpointReader, StateProvider,
StateProviderFactory, StateReader, TrieReader,
};
use reth_revm::db::State;
use reth_trie::{updates::TrieUpdates, HashedPostState, TrieInputSorted};
use reth_storage_errors::db::DatabaseError;
use reth_trie::{updates::TrieUpdates, HashedPostState, StateRoot, TrieInputSorted};
use reth_trie_parallel::root::{ParallelStateRoot, ParallelStateRootError};
use revm_primitives::Address;
use std::{
@@ -220,7 +223,7 @@ where
.map_err(NewPayloadError::other)?
.into();
let iter = Either::Left(iter.into_iter().map(Either::Left));
let iter = Either::Left(iter.into_par_iter().map(Either::Left));
let convert = move |tx| {
let Either::Left(tx) = tx else { unreachable!() };
convert(tx).map(Either::Left).map_err(Either::Left)
@@ -230,8 +233,9 @@ where
Ok((iter, Box::new(convert) as Box<dyn Fn(_) -> _ + Send + Sync + 'static>))
}
BlockOrPayload::Block(block) => {
let iter =
Either::Right(block.body().clone_transactions().into_iter().map(Either::Right));
let iter = Either::Right(
block.body().clone_transactions().into_par_iter().map(Either::Right),
);
let convert = move |tx: Either<_, N::SignedTx>| {
let Either::Right(tx) = tx else { unreachable!() };
tx.try_into_recovered().map(Either::Right).map_err(Either::Right)
@@ -319,6 +323,7 @@ where
&mut self,
input: BlockOrPayload<T>,
mut ctx: TreeCtx<'_, N>,
provider_builder: StateProviderBuilder<N, P>,
) -> ValidationOutcome<N, InsertPayloadError<N::Block>>
where
V: PayloadValidator<T, Block = N::Block>,
@@ -358,20 +363,11 @@ where
trace!(target: "engine::tree::payload_validator", "Fetching block state provider");
let _enter =
debug_span!(target: "engine::tree::payload_validator", "state provider").entered();
let Some(provider_builder) =
ensure_ok!(self.state_provider_builder(parent_hash, ctx.state()))
else {
// this is pre-validated in the tree
return Err(InsertBlockError::new(
self.convert_to_block(input)?,
ProviderError::HeaderNotFound(parent_hash.into()).into(),
)
.into())
};
let state_provider = ensure_ok!(provider_builder.build());
let mut state_provider = ensure_ok!(provider_builder.build());
drop(_enter);
// fetch parent block
// Fetch parent block. This goes to memory most of the time unless the parent block is
// beyond the in-memory buffer.
let Some(parent_block) = ensure_ok!(self.sealed_header_by_hash(parent_hash, ctx.state()))
else {
return Err(InsertBlockError::new(
@@ -396,9 +392,17 @@ where
"Decided which state root algorithm to run"
);
// use prewarming background task
// Get an iterator over the transactions in the payload
let txs = self.tx_iterator_for(&input)?;
// Extract the BAL, if valid and available
let block_access_list = ensure_ok!(input
.block_access_list()
.transpose()
// Eventually gets converted to a `InsertBlockErrorKind::Other`
.map_err(Box::<dyn std::error::Error + Send + Sync>::from))
.map(Arc::new);
// Spawn the appropriate processor based on strategy
let mut handle = ensure_ok!(self.spawn_payload_processor(
env.clone(),
@@ -407,25 +411,23 @@ where
parent_hash,
ctx.state(),
strategy,
block_access_list,
));
// Use cached state provider before executing, used in execution after prewarming threads
// complete
let state_provider = CachedStateProvider::new_with_caches(
state_provider,
handle.caches(),
handle.cache_metrics(),
);
if let Some((caches, cache_metrics)) = handle.caches().zip(handle.cache_metrics()) {
state_provider =
Box::new(CachedStateProvider::new(state_provider, caches, cache_metrics));
};
if self.config.state_provider_metrics() {
state_provider = Box::new(InstrumentedStateProvider::new(state_provider, "engine"));
}
// Execute the block and handle any execution errors
let (output, senders) = match if self.config.state_provider_metrics() {
let state_provider = InstrumentedStateProvider::from_state_provider(&state_provider);
let result = self.execute_block(&state_provider, env, &input, &mut handle);
state_provider.record_total_latency();
result
} else {
self.execute_block(&state_provider, env, &input, &mut handle)
} {
let (output, senders) = match self.execute_block(&state_provider, env, &input, &mut handle)
{
Ok(output) => output,
Err(err) => return self.handle_execution_error(input, err, &parent_block),
};
@@ -509,7 +511,7 @@ where
}
let (root, updates) = ensure_ok_post_block!(
state_provider.state_root_with_updates(hashed_state.clone()),
self.compute_state_root_serial(block.parent_hash(), &hashed_state, ctx.state()),
block
);
(root, updates, root_time.elapsed())
@@ -539,17 +541,14 @@ where
.into())
}
// terminate prewarming task with good state output
handle.terminate_caching(Some(&output.state));
// Create ExecutionOutcome and wrap in Arc for sharing with both the caching task
// and the deferred trie task. This avoids cloning the expensive BundleState.
let execution_outcome = Arc::new(ExecutionOutcome::from((output, block_num_hash.number)));
Ok(self.spawn_deferred_trie_task(
block,
output,
block_num_hash.number,
&ctx,
hashed_state,
trie_output,
))
// Terminate prewarming task with the shared execution outcome
handle.terminate_caching(Some(Arc::clone(&execution_outcome)));
Ok(self.spawn_deferred_trie_task(block, execution_outcome, &ctx, hashed_state, trie_output))
}
/// Return sealed block header from database or in-memory state by hash.
@@ -592,7 +591,7 @@ where
state_provider: S,
env: ExecutionEnv<Evm>,
input: &BlockOrPayload<T>,
handle: &mut PayloadHandle<impl ExecutableTxFor<Evm>, Err>,
handle: &mut PayloadHandle<impl ExecutableTxFor<Evm>, Err, N::Receipt>,
) -> Result<(BlockExecutionOutput<N::Receipt>, Vec<Address>), InsertBlockErrorKind>
where
S: StateProvider,
@@ -602,14 +601,12 @@ where
Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
{
debug!(target: "engine::tree::payload_validator", "Executing block");
let mut db = State::builder()
.with_database(StateProviderDatabase::new(&state_provider))
.with_database(StateProviderDatabase::new(state_provider))
.with_bundle_update()
.with_bal_builder() //TODO
.without_state_clear()
.build();
db.bal_state.bal_index = 0;
db.bal_state.bal_builder = Some(revm::state::bal::Bal::new());
let evm = self.evm_config.evm_with_env(&mut db, env.evm_env.clone());
let ctx =
@@ -652,8 +649,6 @@ where
///
/// Returns `Ok(_)` if computed successfully.
/// Returns `Err(_)` if error was encountered during computation.
/// `Err(ProviderError::ConsistentView(_))` can be safely ignored and fallback computation
/// should be used instead.
#[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
fn compute_state_root_parallel(
&self,
@@ -683,6 +678,36 @@ where
ParallelStateRoot::new(factory, prefix_sets).incremental_root_with_updates()
}
/// Compute state root for the given hashed post state in serial.
fn compute_state_root_serial(
&self,
parent_hash: B256,
hashed_state: &HashedPostState,
state: &EngineApiTreeState<N>,
) -> ProviderResult<(B256, TrieUpdates)> {
let (mut input, block_hash) = self.compute_trie_input(parent_hash, state)?;
// Extend state overlay with current block's sorted state.
input.prefix_sets.extend(hashed_state.construct_prefix_sets());
let sorted_hashed_state = hashed_state.clone_into_sorted();
Arc::make_mut(&mut input.state).extend_ref(&sorted_hashed_state);
let TrieInputSorted { nodes, state, .. } = input;
let prefix_sets = hashed_state.construct_prefix_sets();
let factory = OverlayStateProviderFactory::new(self.provider.clone())
.with_block_hash(Some(block_hash))
.with_trie_overlay(Some(nodes))
.with_hashed_state_overlay(Some(state));
let provider = factory.database_provider_ro()?;
Ok(StateRoot::new(&provider, &provider)
.with_prefix_sets(prefix_sets.freeze())
.root_with_updates()
.map_err(Into::<DatabaseError>::into)?)
}
/// Validates the block after execution.
///
/// This performs:
@@ -777,10 +802,12 @@ where
parent_hash: B256,
state: &EngineApiTreeState<N>,
strategy: StateRootStrategy,
block_access_list: Option<Arc<BlockAccessList>>,
) -> Result<
PayloadHandle<
impl ExecutableTxFor<Evm> + use<N, P, Evm, V, T>,
impl core::error::Error + Send + Sync + 'static + use<N, P, Evm, V, T>,
N::Receipt,
>,
InsertBlockErrorKind,
> {
@@ -806,12 +833,14 @@ where
.record(trie_input_start.elapsed().as_secs_f64());
let spawn_start = Instant::now();
let handle = self.payload_processor.spawn(
env,
txs,
provider_builder,
multiproof_provider_factory,
&self.config,
block_access_list,
);
// record prewarming initialization duration
@@ -838,43 +867,12 @@ where
}
}
/// Creates a `StateProviderBuilder` for the given parent hash.
///
/// This method checks if the parent is in the tree state (in-memory) or persisted to disk,
/// and creates the appropriate provider builder.
fn state_provider_builder(
&self,
hash: B256,
state: &EngineApiTreeState<N>,
) -> ProviderResult<Option<StateProviderBuilder<N, P>>> {
if let Some((historical, blocks)) = state.tree_state.blocks_by_hash(hash) {
debug!(target: "engine::tree::payload_validator", %hash, %historical, "found canonical state for block in memory, creating provider builder");
// the block leads back to the canonical chain
return Ok(Some(StateProviderBuilder::new(
self.provider.clone(),
historical,
Some(blocks),
)))
}
// Check if the block is persisted
if let Some(header) = self.provider.header(hash)? {
debug!(target: "engine::tree::payload_validator", %hash, number = %header.number(), "found canonical state for block in database, creating provider builder");
// For persisted blocks, we create a builder that will fetch state directly from the
// database
return Ok(Some(StateProviderBuilder::new(self.provider.clone(), hash, None)))
}
debug!(target: "engine::tree::payload_validator", %hash, "no canonical state found for block");
Ok(None)
}
/// Determines the state root computation strategy based on configuration.
///
/// Note: Use state root task only if prefix sets are empty, otherwise proof generation is
/// too expensive because it requires walking all paths in every proof.
const fn plan_state_root_computation(&self) -> StateRootStrategy {
if self.config.state_root_fallback() || !self.config.has_enough_parallelism() {
if self.config.state_root_fallback() {
StateRootStrategy::Synchronous
} else if self.config.use_state_root_task() {
StateRootStrategy::StateRootTask
@@ -1013,8 +1011,7 @@ where
fn spawn_deferred_trie_task(
&self,
block: RecoveredBlock<N::Block>,
output: BlockExecutionOutput<N::Receipt>,
block_number: u64,
execution_outcome: Arc<ExecutionOutcome<N::Receipt>>,
ctx: &TreeCtx<'_, N>,
hashed_state: HashedPostState,
trie_output: TrieUpdates,
@@ -1064,7 +1061,7 @@ where
ExecutedBlock::with_deferred_trie_data(
Arc::new(block),
Arc::new(ExecutionOutcome::from((output, block_number))),
execution_outcome,
deferred_trie_data,
)
}
@@ -1092,6 +1089,19 @@ pub trait EngineValidator<
N: NodePrimitives = <<Types as PayloadTypes>::BuiltPayload as BuiltPayload>::Primitives,
>: Send + Sync + 'static
{
/// State provider builder type used by the validator.
type ProviderBuilder;
/// Creates a `StateProviderBuilder` for the given parent hash.
///
/// This method checks if the parent is in the tree state (in-memory) or persisted to disk,
/// and creates the appropriate provider builder.
fn provider_builder(
&self,
hash: B256,
state: &EngineApiTreeState<N>,
) -> ProviderResult<Option<Self::ProviderBuilder>>;
/// Validates the payload attributes with respect to the header.
///
/// By default, this enforces that the payload attributes timestamp is greater than the
@@ -1125,6 +1135,7 @@ pub trait EngineValidator<
&mut self,
payload: Types::ExecutionData,
ctx: TreeCtx<'_, N>,
provider_builder: Self::ProviderBuilder,
) -> ValidationOutcome<N>;
/// Validates a block downloaded from the network.
@@ -1132,6 +1143,7 @@ pub trait EngineValidator<
&mut self,
block: SealedBlock<N::Block>,
ctx: TreeCtx<'_, N>,
provider_builder: Self::ProviderBuilder,
) -> ValidationOutcome<N>;
/// Hook called after an executed block is inserted directly into the tree.
@@ -1156,6 +1168,35 @@ where
Evm: ConfigureEngineEvm<Types::ExecutionData, Primitives = N> + 'static,
Types: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
{
type ProviderBuilder = StateProviderBuilder<N, P>;
fn provider_builder(
&self,
hash: B256,
state: &EngineApiTreeState<N>,
) -> ProviderResult<Option<Self::ProviderBuilder>> {
if let Some((historical, blocks)) = state.tree_state.blocks_by_hash(hash) {
debug!(target: "engine::tree::payload_validator", %hash, %historical, "found canonical state for block in memory, creating provider builder");
// the block leads back to the canonical chain
return Ok(Some(StateProviderBuilder::new(
self.provider.clone(),
historical,
Some(blocks),
)))
}
// Check if the block is persisted
if let Some(header) = self.provider.header(hash)? {
debug!(target: "engine::tree::payload_validator", %hash, number = %header.number(), "found canonical state for block in database, creating provider builder");
// For persisted blocks, we create a builder that will fetch state directly from the
// database
return Ok(Some(StateProviderBuilder::new(self.provider.clone(), hash, None)))
}
debug!(target: "engine::tree::payload_validator", %hash, "no canonical state found for block");
Ok(None)
}
fn validate_payload_attributes_against_header(
&self,
attr: &Types::PayloadAttributes,
@@ -1176,16 +1217,18 @@ where
&mut self,
payload: Types::ExecutionData,
ctx: TreeCtx<'_, N>,
provider_builder: Self::ProviderBuilder,
) -> ValidationOutcome<N> {
self.validate_block_with_state(BlockOrPayload::Payload(payload), ctx)
self.validate_block_with_state(BlockOrPayload::Payload(payload), ctx, provider_builder)
}
fn validate_block(
&mut self,
block: SealedBlock<N::Block>,
ctx: TreeCtx<'_, N>,
provider_builder: Self::ProviderBuilder,
) -> ValidationOutcome<N> {
self.validate_block_with_state(BlockOrPayload::Block(block), ctx)
self.validate_block_with_state(BlockOrPayload::Block(block), ctx, provider_builder)
}
fn on_inserted_executed_block(&self, block: ExecutedBlock<N>) {
@@ -1245,4 +1288,10 @@ impl<T: PayloadTypes> BlockOrPayload<T> {
Self::Block(_) => "block",
}
}
/// Returns the block access list if available.
pub const fn block_access_list(&self) -> Option<Result<BlockAccessList, alloy_rlp::Error>> {
// TODO decode and return `BlockAccessList`
None
}
}

View File

@@ -1,50 +1,58 @@
//! Contains a precompile cache backed by `schnellru::LruMap` (LRU by length).
use alloy_primitives::Bytes;
use parking_lot::Mutex;
use dashmap::DashMap;
use moka::policy::EvictionPolicy;
use reth_evm::precompiles::{DynPrecompile, Precompile, PrecompileInput};
use revm::precompile::{PrecompileId, PrecompileOutput, PrecompileResult};
use revm_primitives::Address;
use schnellru::LruMap;
use std::{
collections::HashMap,
hash::{Hash, Hasher},
sync::Arc,
};
use std::{hash::Hash, sync::Arc};
/// Default max cache size for [`PrecompileCache`]
const MAX_CACHE_SIZE: u32 = 10_000;
/// Stores caches for each precompile.
#[derive(Debug, Clone, Default)]
pub struct PrecompileCacheMap<S>(HashMap<Address, PrecompileCache<S>>)
pub struct PrecompileCacheMap<S>(Arc<DashMap<Address, PrecompileCache<S>>>)
where
S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone;
S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone + 'static;
impl<S> PrecompileCacheMap<S>
where
S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone + 'static,
{
pub(crate) fn cache_for_address(&mut self, address: Address) -> PrecompileCache<S> {
pub(crate) fn cache_for_address(&self, address: Address) -> PrecompileCache<S> {
// Try just using `.get` first to avoid acquiring a write lock.
if let Some(cache) = self.0.get(&address) {
return cache.clone();
}
// Otherwise, fallback to `.entry` and initialize the cache.
//
// This should be very rare as caches for all precompiles will be initialized as soon as
// first EVM is created.
self.0.entry(address).or_default().clone()
}
}
/// Cache for precompiles, for each input stores the result.
///
/// [`LruMap`] requires a mutable reference on `get` since it updates the LRU order,
/// so we use a [`Mutex`] instead of an `RwLock`.
#[derive(Debug, Clone)]
pub struct PrecompileCache<S>(Arc<Mutex<LruMap<CacheKey<S>, CacheEntry>>>)
pub struct PrecompileCache<S>(
moka::sync::Cache<Bytes, CacheEntry<S>, alloy_primitives::map::DefaultHashBuilder>,
)
where
S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone;
S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone + 'static;
impl<S> Default for PrecompileCache<S>
where
S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone + 'static,
{
fn default() -> Self {
Self(Arc::new(Mutex::new(LruMap::new(schnellru::ByLength::new(MAX_CACHE_SIZE)))))
Self(
moka::sync::CacheBuilder::new(MAX_CACHE_SIZE as u64)
.initial_capacity(MAX_CACHE_SIZE as usize)
.eviction_policy(EvictionPolicy::lru())
.build_with_hasher(Default::default()),
)
}
}
@@ -52,63 +60,31 @@ impl<S> PrecompileCache<S>
where
S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone + 'static,
{
fn get(&self, key: &CacheKeyRef<'_, S>) -> Option<CacheEntry> {
self.0.lock().get(key).cloned()
fn get(&self, input: &[u8], spec: S) -> Option<CacheEntry<S>> {
self.0.get(input).filter(|e| e.spec == spec)
}
/// Inserts the given key and value into the cache, returning the new cache size.
fn insert(&self, key: CacheKey<S>, value: CacheEntry) -> usize {
let mut cache = self.0.lock();
cache.insert(key, value);
cache.len()
}
}
/// Cache key, spec id and precompile call input. spec id is included in the key to account for
/// precompile repricing across fork activations.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CacheKey<S>((S, Bytes));
impl<S> CacheKey<S> {
const fn new(spec_id: S, input: Bytes) -> Self {
Self((spec_id, input))
}
}
/// Cache key reference, used to avoid cloning the input bytes when looking up using a [`CacheKey`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CacheKeyRef<'a, S>((S, &'a [u8]));
impl<'a, S> CacheKeyRef<'a, S> {
const fn new(spec_id: S, input: &'a [u8]) -> Self {
Self((spec_id, input))
}
}
impl<S: PartialEq> PartialEq<CacheKey<S>> for CacheKeyRef<'_, S> {
fn eq(&self, other: &CacheKey<S>) -> bool {
self.0 .0 == other.0 .0 && self.0 .1 == other.0 .1.as_ref()
}
}
impl<'a, S: Hash> Hash for CacheKeyRef<'a, S> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0 .0.hash(state);
self.0 .1.hash(state);
fn insert(&self, input: Bytes, value: CacheEntry<S>) -> usize {
self.0.insert(input, value);
self.0.entry_count() as usize
}
}
/// Cache entry, precompile successful output.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CacheEntry(PrecompileOutput);
pub struct CacheEntry<S> {
output: PrecompileOutput,
spec: S,
}
impl CacheEntry {
impl<S> CacheEntry<S> {
const fn gas_used(&self) -> u64 {
self.0.gas_used
self.output.gas_used
}
fn to_precompile_result(&self) -> PrecompileResult {
Ok(self.0.clone())
Ok(self.output.clone())
}
}
@@ -190,9 +166,7 @@ where
}
fn call(&self, input: PrecompileInput<'_>) -> PrecompileResult {
let key = CacheKeyRef::new(self.spec_id.clone(), input.data);
if let Some(entry) = &self.cache.get(&key) {
if let Some(entry) = &self.cache.get(input.data, self.spec_id.clone()) {
self.increment_by_one_precompile_cache_hits();
if input.gas >= entry.gas_used() {
return entry.to_precompile_result()
@@ -204,8 +178,10 @@ where
match &result {
Ok(output) => {
let key = CacheKey::new(self.spec_id.clone(), Bytes::copy_from_slice(calldata));
let size = self.cache.insert(key, CacheEntry(output.clone()));
let size = self.cache.insert(
Bytes::copy_from_slice(calldata),
CacheEntry { output: output.clone(), spec: self.spec_id.clone() },
);
self.set_precompile_cache_size_metric(size as f64);
self.increment_by_one_precompile_cache_misses();
}
@@ -246,39 +222,20 @@ impl CachedPrecompileMetrics {
#[cfg(test)]
mod tests {
use std::hash::DefaultHasher;
use super::*;
use reth_evm::{EthEvmFactory, Evm, EvmEnv, EvmFactory};
use reth_revm::db::EmptyDB;
use revm::{context::TxEnv, precompile::PrecompileOutput};
use revm_primitives::hardfork::SpecId;
#[test]
fn test_cache_key_ref_hash() {
let key1 = CacheKey::new(SpecId::PRAGUE, b"test_input".into());
let key2 = CacheKeyRef::new(SpecId::PRAGUE, b"test_input");
assert!(PartialEq::eq(&key2, &key1));
let mut hasher = DefaultHasher::new();
key1.hash(&mut hasher);
let hash1 = hasher.finish();
let mut hasher = DefaultHasher::new();
key2.hash(&mut hasher);
let hash2 = hasher.finish();
assert_eq!(hash1, hash2);
}
#[test]
fn test_precompile_cache_basic() {
let dyn_precompile: DynPrecompile = (|_input: PrecompileInput<'_>| -> PrecompileResult {
Ok(PrecompileOutput {
gas_used: 0,
gas_refunded: 0,
bytes: Bytes::default(),
reverted: false,
gas_refunded: 0,
})
})
.into();
@@ -288,17 +245,16 @@ mod tests {
let output = PrecompileOutput {
gas_used: 50,
gas_refunded: 0,
bytes: alloy_primitives::Bytes::copy_from_slice(b"cached_result"),
reverted: false,
gas_refunded: 0,
};
let key = CacheKey::new(SpecId::PRAGUE, b"test_input".into());
let expected = CacheEntry(output);
cache.cache.insert(key, expected.clone());
let input = b"test_input";
let expected = CacheEntry { output, spec: SpecId::PRAGUE };
cache.cache.insert(input.into(), expected.clone());
let key = CacheKeyRef::new(SpecId::PRAGUE, b"test_input");
let actual = cache.cache.get(&key).unwrap();
let actual = cache.cache.get(input, SpecId::PRAGUE).unwrap();
assert_eq!(actual, expected);
}
@@ -312,7 +268,7 @@ mod tests {
let address1 = Address::repeat_byte(1);
let address2 = Address::repeat_byte(2);
let mut cache_map = PrecompileCacheMap::default();
let cache_map = PrecompileCacheMap::default();
// create the first precompile with a specific output
let precompile1: DynPrecompile = (PrecompileId::custom("custom"), {
@@ -321,9 +277,9 @@ mod tests {
Ok(PrecompileOutput {
gas_used: 5000,
gas_refunded: 0,
bytes: alloy_primitives::Bytes::copy_from_slice(b"output_from_precompile_1"),
reverted: false,
gas_refunded: 0,
})
}
})
@@ -336,9 +292,9 @@ mod tests {
Ok(PrecompileOutput {
gas_used: 7000,
gas_refunded: 0,
bytes: alloy_primitives::Bytes::copy_from_slice(b"output_from_precompile_2"),
reverted: false,
gas_refunded: 0,
})
}
})

View File

@@ -4,7 +4,7 @@ use crate::{
tree::{
payload_validator::{BasicEngineValidator, TreeCtx, ValidationOutcome},
persistence_state::CurrentPersistenceAction,
TreeConfig,
PersistTarget, TreeConfig,
},
};
@@ -285,7 +285,8 @@ impl TestHarness {
let fcu_state = self.fcu_state(block_hash);
let (tx, rx) = oneshot::channel();
self.tree
let _ = self
.tree
.on_engine_message(FromEngine::Request(
BeaconEngineMessage::ForkchoiceUpdated {
state: fcu_state,
@@ -427,7 +428,13 @@ impl ValidatorTestHarness {
&mut self.harness.tree.state,
&self.harness.tree.canonical_in_memory_state,
);
let result = self.validator.validate_block(block, ctx);
let provider_builder = self
.harness
.tree
.state_provider_builder(block.parent_hash())
.expect("state provider builder lookup failed")
.expect("missing parent state provider builder");
let result = self.validator.validate_block(block, ctx, provider_builder);
self.metrics.record_validation(result.is_ok());
result
}
@@ -498,7 +505,7 @@ fn test_tree_persist_block_batch() {
// process the message
let msg = test_harness.tree.try_recv_engine_message().unwrap().unwrap();
test_harness.tree.on_engine_message(msg).unwrap();
let _ = test_harness.tree.on_engine_message(msg).unwrap();
// we now should receive the other batch
let msg = test_harness.tree.try_recv_engine_message().unwrap().unwrap();
@@ -577,7 +584,7 @@ async fn test_engine_request_during_backfill() {
.with_backfill_state(BackfillSyncState::Active);
let (tx, rx) = oneshot::channel();
test_harness
let _ = test_harness
.tree
.on_engine_message(FromEngine::Request(
BeaconEngineMessage::ForkchoiceUpdated {
@@ -658,7 +665,7 @@ async fn test_holesky_payload() {
TestHarness::new(HOLESKY.clone()).with_backfill_state(BackfillSyncState::Active);
let (tx, rx) = oneshot::channel();
test_harness
let _ = test_harness
.tree
.on_engine_message(FromEngine::Request(
BeaconEngineMessage::NewPayload {
@@ -883,7 +890,8 @@ async fn test_get_canonical_blocks_to_persist() {
.with_persistence_threshold(persistence_threshold)
.with_memory_block_buffer_target(memory_block_buffer_target);
let blocks_to_persist = test_harness.tree.get_canonical_blocks_to_persist().unwrap();
let blocks_to_persist =
test_harness.tree.get_canonical_blocks_to_persist(PersistTarget::Threshold).unwrap();
let expected_blocks_to_persist_length: usize =
(canonical_head_number - memory_block_buffer_target - last_persisted_block_number)
@@ -902,7 +910,8 @@ async fn test_get_canonical_blocks_to_persist() {
assert!(test_harness.tree.state.tree_state.sealed_header_by_hash(&fork_block_hash).is_some());
let blocks_to_persist = test_harness.tree.get_canonical_blocks_to_persist().unwrap();
let blocks_to_persist =
test_harness.tree.get_canonical_blocks_to_persist(PersistTarget::Threshold).unwrap();
assert_eq!(blocks_to_persist.len(), expected_blocks_to_persist_length);
// check that the fork block is not included in the blocks to persist
@@ -981,7 +990,7 @@ async fn test_engine_tree_live_sync_transition_required_blocks_requested() {
let backfill_tip_block = main_chain[(backfill_finished_block_number - 1) as usize].clone();
// add block to mock provider to enable persistence clean up.
test_harness.provider.add_block(backfill_tip_block.hash(), backfill_tip_block.into_block());
test_harness.tree.on_engine_message(FromEngine::Event(backfill_finished)).unwrap();
let _ = test_harness.tree.on_engine_message(FromEngine::Event(backfill_finished)).unwrap();
let event = test_harness.from_tree_rx.recv().await.unwrap();
match event {
@@ -991,7 +1000,7 @@ async fn test_engine_tree_live_sync_transition_required_blocks_requested() {
_ => panic!("Unexpected event: {event:#?}"),
}
test_harness
let _ = test_harness
.tree
.on_engine_message(FromEngine::DownloadedBlocks(vec![main_chain
.last()
@@ -1047,7 +1056,7 @@ async fn test_fcu_with_canonical_ancestor_updates_latest_block() {
// Send FCU to the canonical ancestor
let (tx, rx) = oneshot::channel();
test_harness
let _ = test_harness
.tree
.on_engine_message(FromEngine::Request(
BeaconEngineMessage::ForkchoiceUpdated {
@@ -1943,4 +1952,53 @@ mod forkchoice_updated_tests {
.unwrap();
assert!(result.is_some(), "OpStack should handle canonical head");
}
/// Test that engine termination persists all blocks and signals completion.
#[test]
fn test_engine_termination_with_everything_persisted() {
let chain_spec = MAINNET.clone();
let mut test_block_builder = TestBlockBuilder::eth().with_chain_spec((*chain_spec).clone());
// Create 10 blocks to persist
let blocks: Vec<_> = test_block_builder.get_executed_blocks(1..11).collect();
let canonical_tip = blocks.last().unwrap().recovered_block().number;
let test_harness = TestHarness::new(chain_spec).with_blocks(blocks);
// Create termination channel
let (terminate_tx, mut terminate_rx) = oneshot::channel();
let to_tree_tx = test_harness.to_tree_tx.clone();
let action_rx = test_harness.action_rx;
// Spawn tree in background thread
std::thread::Builder::new()
.name("Engine Task".to_string())
.spawn(|| test_harness.tree.run())
.unwrap();
// Send terminate request
to_tree_tx
.send(FromEngine::Event(FromOrchestrator::Terminate { tx: terminate_tx }))
.unwrap();
// Handle persistence actions until termination completes
let mut last_persisted_number = 0;
loop {
if terminate_rx.try_recv().is_ok() {
break;
}
if let Ok(PersistenceAction::SaveBlocks(saved_blocks, sender)) =
action_rx.recv_timeout(std::time::Duration::from_millis(100))
{
if let Some(last) = saved_blocks.last() {
last_persisted_number = last.recovered_block().number;
}
sender.send(saved_blocks.last().map(|b| b.recovered_block().num_hash())).unwrap();
}
}
// Ensure we persisted right to the tip
assert_eq!(last_persisted_number, canonical_tip);
}
}

View File

@@ -16,7 +16,7 @@ reth-primitives-traits.workspace = true
reth-errors.workspace = true
reth-chainspec.workspace = true
reth-fs-util.workspace = true
reth-engine-primitives.workspace = true
reth-engine-primitives = { workspace = true, features = ["std"] }
reth-engine-tree.workspace = true
reth-evm.workspace = true
reth-revm.workspace = true
@@ -27,9 +27,6 @@ reth-payload-primitives.workspace = true
alloy-rpc-types-engine.workspace = true
alloy-consensus.workspace = true
# revm
revm.workspace = true
# async
tokio = { workspace = true, default-features = false }
tokio-util.workspace = true

View File

@@ -283,12 +283,8 @@ where
let mut state = State::builder()
.with_database_ref(StateProviderDatabase::new(&state_provider))
.with_bundle_update()
.with_bal_builder()
.build();
state.bal_state.bal_index = 0;
state.bal_state.bal_builder = Some(revm::state::bal::Bal::new());
let ctx = evm_config.context_for_block(&reorg_target).map_err(RethError::other)?;
let evm = evm_config.evm_for_block(&mut state, &reorg_target).map_err(RethError::other)?;
let mut builder = evm_config.create_block_builder(evm, &reorg_target_parent, ctx);

View File

@@ -150,6 +150,12 @@ where
let era1_id = Era1Id::new(&config.network, start_block, block_count as u32)
.with_hash(historical_root);
let era1_id = if config.max_blocks_per_file == MAX_BLOCKS_PER_ERA1 as u64 {
era1_id
} else {
era1_id.with_era_count()
};
debug!("Final file name {}", era1_id.to_file_name());
let file_path = config.dir.join(era1_id.to_file_name());
let file = std::fs::File::create(&file_path)?;

View File

@@ -24,7 +24,7 @@ fn test_export_with_genesis_only() {
assert!(file_path.exists(), "Exported file should exist on disk");
let file_name = file_path.file_name().unwrap().to_str().unwrap();
assert!(
file_name.starts_with("mainnet-00000-00001-"),
file_name.starts_with("mainnet-00000-"),
"File should have correct prefix with era format"
);
assert!(file_name.ends_with(".era1"), "File should have correct extension");

View File

@@ -30,8 +30,11 @@ pub trait EraFileFormat: Sized {
/// Era file identifiers
pub trait EraFileId: Clone {
/// Convert to standardized file name
fn to_file_name(&self) -> String;
/// File type for this identifier
const FILE_TYPE: EraFileType;
/// Number of items, slots for `era`, blocks for `era1`, per era
const ITEMS_PER_ERA: u64;
/// Get the network name
fn network_name(&self) -> &str;
@@ -41,6 +44,43 @@ pub trait EraFileId: Clone {
/// Get the count of items
fn count(&self) -> u32;
/// Get the optional hash identifier
fn hash(&self) -> Option<[u8; 4]>;
/// Whether to include era count in filename
fn include_era_count(&self) -> bool;
/// Calculate era number
fn era_number(&self) -> u64 {
self.start_number() / Self::ITEMS_PER_ERA
}
/// Calculate the number of eras spanned per file.
///
/// If the user can decide how many slots/blocks per era file there are, we need to calculate
/// it. Most of the time it should be 1, but it can never be more than 2 eras per file
/// as there is a maximum of 8192 slots/blocks per era file.
fn era_count(&self) -> u64 {
if self.count() == 0 {
return 0;
}
let first_era = self.era_number();
let last_number = self.start_number() + self.count() as u64 - 1;
let last_era = last_number / Self::ITEMS_PER_ERA;
last_era - first_era + 1
}
/// Convert to standardized file name.
fn to_file_name(&self) -> String {
Self::FILE_TYPE.format_filename(
self.network_name(),
self.era_number(),
self.hash(),
self.include_era_count(),
self.era_count(),
)
}
}
/// [`StreamReader`] for reading era-format files
@@ -154,6 +194,37 @@ impl EraFileType {
}
}
/// Generate era file name.
///
/// Standard format: `<config-name>-<era-number>-<short-historical-root>.<ext>`
/// See also <https://github.com/eth-clients/e2store-format-specs/blob/main/formats/era.md#file-name>
///
/// With era count (for custom exports):
/// `<config-name>-<era-number>-<era-count>-<short-historical-root>.<ext>`
pub fn format_filename(
&self,
network_name: &str,
era_number: u64,
hash: Option<[u8; 4]>,
include_era_count: bool,
era_count: u64,
) -> String {
let hash = format_hash(hash);
if include_era_count {
format!(
"{}-{:05}-{:05}-{}{}",
network_name,
era_number,
era_count,
hash,
self.extension()
)
} else {
format!("{}-{:05}-{}{}", network_name, era_number, hash, self.extension())
}
}
/// Detect file type from URL
/// By default, it assumes `Era` type
pub fn from_url(url: &str) -> Self {
@@ -164,3 +235,11 @@ impl EraFileType {
}
}
}
/// Format hash as hex string, or placeholder if none
pub fn format_hash(hash: Option<[u8; 4]>) -> String {
match hash {
Some(h) => format!("{:02x}{:02x}{:02x}{:02x}", h[0], h[1], h[2], h[3]),
None => "00000000".to_string(),
}
}

View File

@@ -3,7 +3,7 @@
//! See also <https://github.com/eth-clients/e2store-format-specs/blob/main/formats/era.md>
use crate::{
common::file_ops::EraFileId,
common::file_ops::{EraFileId, EraFileType},
e2s::types::{Entry, IndexEntry, SLOT_INDEX},
era::types::consensus::{CompressedBeaconState, CompressedSignedBeaconBlock},
};
@@ -163,12 +163,22 @@ pub struct EraId {
/// Optional hash identifier for this file
/// First 4 bytes of the last historical root in the last state in the era file
pub hash: Option<[u8; 4]>,
/// Whether to include era count in filename
/// It is used for custom exports when we don't use the max number of items per file
include_era_count: bool,
}
impl EraId {
/// Create a new [`EraId`]
pub fn new(network_name: impl Into<String>, start_slot: u64, slot_count: u32) -> Self {
Self { network_name: network_name.into(), start_slot, slot_count, hash: None }
Self {
network_name: network_name.into(),
start_slot,
slot_count,
hash: None,
include_era_count: false,
}
}
/// Add a hash identifier to [`EraId`]
@@ -177,32 +187,18 @@ impl EraId {
self
}
/// Calculate which era number the file starts at
pub const fn era_number(&self) -> u64 {
self.start_slot / SLOTS_PER_HISTORICAL_ROOT
}
// Helper function to calculate the number of eras per era1 file,
// If the user can decide how many blocks per era1 file there are, we need to calculate it.
// Most of the time it should be 1, but it can never be more than 2 eras per file
// as there is a maximum of 8192 blocks per era1 file.
const fn calculate_era_count(&self) -> u64 {
if self.slot_count == 0 {
return 0;
}
let first_era = self.era_number();
// Calculate the actual last slot number in the range
let last_slot = self.start_slot + self.slot_count as u64 - 1;
// Find which era the last block belongs to
let last_era = last_slot / SLOTS_PER_HISTORICAL_ROOT;
// Count how many eras we span
last_era - first_era + 1
/// Include era count in filename, for custom slot-per-file exports
pub const fn with_era_count(mut self) -> Self {
self.include_era_count = true;
self
}
}
impl EraFileId for EraId {
const FILE_TYPE: EraFileType = EraFileType::Era;
const ITEMS_PER_ERA: u64 = SLOTS_PER_HISTORICAL_ROOT;
fn network_name(&self) -> &str {
&self.network_name
}
@@ -214,24 +210,13 @@ impl EraFileId for EraId {
fn count(&self) -> u32 {
self.slot_count
}
/// Convert to file name following the era file naming:
/// `<config-name>-<era-number>-<era-count>-<short-historical-root>.era`
/// <https://github.com/eth-clients/e2store-format-specs/blob/main/formats/era.md#file-name>
/// See also <https://github.com/eth-clients/e2store-format-specs/blob/main/formats/era.md>
fn to_file_name(&self) -> String {
let era_number = self.era_number();
let era_count = self.calculate_era_count();
if let Some(hash) = self.hash {
format!(
"{}-{:05}-{:05}-{:02x}{:02x}{:02x}{:02x}.era",
self.network_name, era_number, era_count, hash[0], hash[1], hash[2], hash[3]
)
} else {
// era spec format with placeholder hash when no hash available
// Format: `<config-name>-<era-number>-<era-count>-00000000.era`
format!("{}-{:05}-{:05}-00000000.era", self.network_name, era_number, era_count)
}
fn hash(&self) -> Option<[u8; 4]> {
self.hash
}
fn include_era_count(&self) -> bool {
self.include_era_count
}
}
@@ -399,4 +384,40 @@ mod tests {
let parsed_offset = index.offsets[0];
assert_eq!(parsed_offset, -1024);
}
#[test_case::test_case(
EraId::new("mainnet", 0, 8192).with_hash([0x4b, 0x36, 0x3d, 0xb9]),
"mainnet-00000-4b363db9.era";
"Mainnet era 0"
)]
#[test_case::test_case(
EraId::new("mainnet", 8192, 8192).with_hash([0x40, 0xcf, 0x2f, 0x3c]),
"mainnet-00001-40cf2f3c.era";
"Mainnet era 1"
)]
#[test_case::test_case(
EraId::new("mainnet", 0, 8192),
"mainnet-00000-00000000.era";
"Without hash"
)]
fn test_era_id_file_naming(id: EraId, expected_file_name: &str) {
let actual_file_name = id.to_file_name();
assert_eq!(actual_file_name, expected_file_name);
}
// File naming with era-count, for custom exports
#[test_case::test_case(
EraId::new("mainnet", 0, 8192).with_hash([0x4b, 0x36, 0x3d, 0xb9]).with_era_count(),
"mainnet-00000-00001-4b363db9.era";
"Mainnet era 0 with count"
)]
#[test_case::test_case(
EraId::new("mainnet", 8000, 500).with_hash([0xab, 0xcd, 0xef, 0x12]).with_era_count(),
"mainnet-00000-00002-abcdef12.era";
"Spanning two eras with count"
)]
fn test_era_id_file_naming_with_era_count(id: EraId, expected_file_name: &str) {
let actual_file_name = id.to_file_name();
assert_eq!(actual_file_name, expected_file_name);
}
}

View File

@@ -39,7 +39,6 @@
//! transactions: vec![Bytes::from(vec![1, 2, 3])],
//! ommers: vec![],
//! withdrawals: None,
//! block_access_list: None,
//! };
//! // Compress the body: rlp encoding and snappy compression
//! let compressed_body = CompressedBody::from_body(&body)?;
@@ -582,12 +581,8 @@ mod tests {
#[test]
fn test_block_body_conversion() {
let block_body: BlockBody<Bytes> = BlockBody {
transactions: vec![],
ommers: vec![],
withdrawals: None,
block_access_list: None,
};
let block_body: BlockBody<Bytes> =
BlockBody { transactions: vec![], ommers: vec![], withdrawals: None };
let compressed_body = CompressedBody::from_body(&block_body).unwrap();
@@ -642,8 +637,7 @@ mod tests {
let withdrawals = Some(Withdrawals(vec![]));
let block_body =
BlockBody { transactions, ommers: vec![], withdrawals, block_access_list: None };
let block_body = BlockBody { transactions, ommers: vec![], withdrawals };
let block = Block::new(header, block_body);

View File

@@ -3,7 +3,7 @@
//! See also <https://github.com/eth-clients/e2store-format-specs/blob/main/formats/era1.md>
use crate::{
common::file_ops::EraFileId,
common::file_ops::{EraFileId, EraFileType},
e2s::types::{Entry, IndexEntry},
era1::types::execution::{Accumulator, BlockTuple, MAX_BLOCKS_PER_ERA1},
};
@@ -105,6 +105,10 @@ pub struct Era1Id {
/// Optional hash identifier for this file
/// First 4 bytes of the last historical root in the last state in the era file
pub hash: Option<[u8; 4]>,
/// Whether to include era count in filename
/// It is used for custom exports when we don't use the max number of items per file
pub include_era_count: bool,
}
impl Era1Id {
@@ -114,7 +118,13 @@ impl Era1Id {
start_block: BlockNumber,
block_count: u32,
) -> Self {
Self { network_name: network_name.into(), start_block, block_count, hash: None }
Self {
network_name: network_name.into(),
start_block,
block_count,
hash: None,
include_era_count: false,
}
}
/// Add a hash identifier to [`Era1Id`]
@@ -123,21 +133,17 @@ impl Era1Id {
self
}
// Helper function to calculate the number of eras per era1 file,
// If the user can decide how many blocks per era1 file there are, we need to calculate it.
// Most of the time it should be 1, but it can never be more than 2 eras per file
// as there is a maximum of 8192 blocks per era1 file.
const fn calculate_era_count(&self, first_era: u64) -> u64 {
// Calculate the actual last block number in the range
let last_block = self.start_block + self.block_count as u64 - 1;
// Find which era the last block belongs to
let last_era = last_block / MAX_BLOCKS_PER_ERA1 as u64;
// Count how many eras we span
last_era - first_era + 1
/// Include era count in filename, for custom block-per-file exports
pub const fn with_era_count(mut self) -> Self {
self.include_era_count = true;
self
}
}
impl EraFileId for Era1Id {
const FILE_TYPE: EraFileType = EraFileType::Era1;
const ITEMS_PER_ERA: u64 = MAX_BLOCKS_PER_ERA1 as u64;
fn network_name(&self) -> &str {
&self.network_name
}
@@ -149,24 +155,13 @@ impl EraFileId for Era1Id {
fn count(&self) -> u32 {
self.block_count
}
/// Convert to file name following the era file naming:
/// `<config-name>-<era-number>-<era-count>-<short-historical-root>.era(1)`
/// <https://github.com/eth-clients/e2store-format-specs/blob/main/formats/era.md#file-name>
/// See also <https://github.com/eth-clients/e2store-format-specs/blob/main/formats/era1.md>
fn to_file_name(&self) -> String {
// Find which era the first block belongs to
let era_number = self.start_block / MAX_BLOCKS_PER_ERA1 as u64;
let era_count = self.calculate_era_count(era_number);
if let Some(hash) = self.hash {
format!(
"{}-{:05}-{:05}-{:02x}{:02x}{:02x}{:02x}.era1",
self.network_name, era_number, era_count, hash[0], hash[1], hash[2], hash[3]
)
} else {
// era spec format with placeholder hash when no hash available
// Format: `<config-name>-<era-number>-<era-count>-00000000.era1`
format!("{}-{:05}-{:05}-00000000.era1", self.network_name, era_number, era_count)
}
fn hash(&self) -> Option<[u8; 4]> {
self.hash
}
fn include_era_count(&self) -> bool {
self.include_era_count
}
}
@@ -314,35 +309,51 @@ mod tests {
#[test_case::test_case(
Era1Id::new("mainnet", 0, 8192).with_hash([0x5e, 0xc1, 0xff, 0xb8]),
"mainnet-00000-00001-5ec1ffb8.era1";
"mainnet-00000-5ec1ffb8.era1";
"Mainnet era 0"
)]
#[test_case::test_case(
Era1Id::new("mainnet", 8192, 8192).with_hash([0x5e, 0xcb, 0x9b, 0xf9]),
"mainnet-00001-00001-5ecb9bf9.era1";
"mainnet-00001-5ecb9bf9.era1";
"Mainnet era 1"
)]
#[test_case::test_case(
Era1Id::new("sepolia", 0, 8192).with_hash([0x90, 0x91, 0x84, 0x72]),
"sepolia-00000-00001-90918472.era1";
"sepolia-00000-90918472.era1";
"Sepolia era 0"
)]
#[test_case::test_case(
Era1Id::new("sepolia", 155648, 8192).with_hash([0xfa, 0x77, 0x00, 0x19]),
"sepolia-00019-00001-fa770019.era1";
"sepolia-00019-fa770019.era1";
"Sepolia era 19"
)]
#[test_case::test_case(
Era1Id::new("mainnet", 1000, 100),
"mainnet-00000-00001-00000000.era1";
"mainnet-00000-00000000.era1";
"ID without hash"
)]
#[test_case::test_case(
Era1Id::new("sepolia", 101130240, 8192).with_hash([0xab, 0xcd, 0xef, 0x12]),
"sepolia-12345-00001-abcdef12.era1";
"sepolia-12345-abcdef12.era1";
"Large block number era 12345"
)]
fn test_era1id_file_naming(id: Era1Id, expected_file_name: &str) {
fn test_era1_id_file_naming(id: Era1Id, expected_file_name: &str) {
let actual_file_name = id.to_file_name();
assert_eq!(actual_file_name, expected_file_name);
}
// File naming with era-count, for custom exports
#[test_case::test_case(
Era1Id::new("mainnet", 0, 8192).with_hash([0x5e, 0xc1, 0xff, 0xb8]).with_era_count(),
"mainnet-00000-00001-5ec1ffb8.era1";
"Mainnet era 0 with count"
)]
#[test_case::test_case(
Era1Id::new("mainnet", 8000, 500).with_hash([0xab, 0xcd, 0xef, 0x12]).with_era_count(),
"mainnet-00000-00002-abcdef12.era1";
"Spanning two eras with count"
)]
fn test_era1_id_file_naming_with_era_count(id: Era1Id, expected_file_name: &str) {
let actual_file_name = id.to_file_name();
assert_eq!(actual_file_name, expected_file_name);
}

View File

@@ -34,7 +34,6 @@ pub(crate) fn create_header() -> Header {
excess_blob_gas: None,
parent_beacon_block_root: None,
requests_hash: None,
block_access_list_hash: None,
}
}
@@ -139,7 +138,6 @@ pub(crate) fn create_test_block_with_compressed_data(number: BlockNumber) -> Blo
excess_blob_gas: None,
parent_beacon_block_root: None,
requests_hash: None,
block_access_list_hash: None,
};
// Create test body
@@ -147,7 +145,6 @@ pub(crate) fn create_test_block_with_compressed_data(number: BlockNumber) -> Blo
transactions: vec![Bytes::from(vec![(number % 256) as u8; 10])],
ommers: vec![],
withdrawals: Some(Withdrawals(vec![])),
block_access_list: None,
};
// Create test receipt list with bloom

View File

@@ -38,6 +38,7 @@ tempfile.workspace = true
default = []
otlp = ["reth-tracing/otlp", "reth-node-core/otlp"]
samply = ["reth-tracing/samply", "reth-node-core/samply"]
dev = ["reth-cli-commands/arbitrary"]

View File

@@ -154,7 +154,9 @@ where
Commands::ImportEra(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
Commands::ExportEra(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
Commands::DumpGenesis(command) => runner.run_blocking_until_ctrl_c(command.execute()),
Commands::Db(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
Commands::Db(command) => {
runner.run_blocking_command_until_exit(|ctx| command.execute::<N>(ctx))
}
Commands::Download(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
Commands::Stage(command) => {
runner.run_command_until_exit(|ctx| command.execute::<N, _>(ctx, components))

View File

@@ -22,7 +22,6 @@ reth-consensus.workspace = true
alloy-eips.workspace = true
alloy-primitives.workspace = true
alloy-consensus.workspace = true
alloy-rlp.workspace = true
tracing.workspace = true
@@ -39,7 +38,6 @@ std = [
"reth-execution-types/std",
"reth-primitives-traits/std",
"tracing/std",
"alloy-rlp/std",
]
[dev-dependencies]

View File

@@ -75,13 +75,7 @@ where
block: &RecoveredBlock<N::Block>,
result: &BlockExecutionResult<N::Receipt>,
) -> Result<(), ConsensusError> {
validate_block_post_execution(
block,
&self.chain_spec,
&result.receipts,
&result.requests,
&result.block_access_list,
)
validate_block_post_execution(block, &self.chain_spec, &result.receipts, &result.requests)
}
}
@@ -182,15 +176,6 @@ where
} else if header.requests_hash().is_some() {
return Err(ConsensusError::RequestsHashUnexpected)
}
// if self.chain_spec.is_amsterdam_active_at_timestamp(header.timestamp()) &&
// header.block_access_list_hash().is_none()
// {
// return Err(ConsensusError::BlockAccessListHashMissing)
// } else if !self.chain_spec.is_amsterdam_active_at_timestamp(header.timestamp()) &&
// header.block_access_list_hash().is_some()
// {
// return Err(ConsensusError::BlockAccessListHashUnexpected)
// }
Ok(())
}

View File

@@ -1,11 +1,11 @@
use alloc::vec::Vec;
use alloy_consensus::{proofs::calculate_receipt_root, BlockHeader, TxReceipt};
use alloy_eips::{eip7685::Requests, eip7928::BlockAccessList, Encodable2718};
use alloy_eips::{eip7685::Requests, Encodable2718};
use alloy_primitives::{Bloom, Bytes, B256};
use reth_chainspec::EthereumHardforks;
use reth_consensus::ConsensusError;
use reth_primitives_traits::{
receipt::gas_spent_by_transactions, Block, BlockBody, GotExpected, Receipt, RecoveredBlock,
receipt::gas_spent_by_transactions, Block, GotExpected, Receipt, RecoveredBlock,
};
/// Validate a block with regard to execution results:
@@ -17,7 +17,6 @@ pub fn validate_block_post_execution<B, R, ChainSpec>(
chain_spec: &ChainSpec,
receipts: &[R],
requests: &Requests,
block_access_list: &Option<BlockAccessList>,
) -> Result<(), ConsensusError>
where
B: Block,
@@ -66,33 +65,6 @@ where
}
}
// Validate bal hash matches the calculated hash
if chain_spec.is_amsterdam_active_at_timestamp(block.header().timestamp()) {
let Some(header_block_access_list_hash) = block.header().block_access_list_hash() else {
return Err(ConsensusError::BlockAccessListHashMissing)
};
if let Some(bal) = block_access_list {
let bal_hash = alloy_primitives::keccak256(alloy_rlp::encode(bal));
let block_bal = block.body().block_access_list();
tracing::debug!("Block Bal :{:?}", block_bal);
if let Some(body_bal) = block_bal {
if body_bal.len() == 0 {
tracing::debug!("Hit Empty BAL : Block is {:?}", block);
}
verify_bal(body_bal, bal)?;
}
if bal_hash != header_block_access_list_hash {
tracing::debug!(
?bal_hash,
?header_block_access_list_hash,
"block access list hash mismatch"
);
return Err(ConsensusError::InvalidBalHash);
}
}
}
Ok(())
}
@@ -115,9 +87,7 @@ fn verify_receipts<R: Receipt>(
logs_bloom,
expected_receipts_root,
expected_logs_bloom,
)?;
Ok(())
)
}
/// Compare the calculated receipts root with the expected receipts root, also compare
@@ -143,47 +113,6 @@ fn compare_receipts_root_and_logs_bloom(
Ok(())
}
/// Validates that the block access list in the body matches the expected block access list.
fn verify_bal(
body_bal: &BlockAccessList,
expected_bal: &BlockAccessList,
) -> Result<(), ConsensusError> {
if body_bal == expected_bal {
return Ok(());
}
// Extract addresses
let body_addrs: Vec<_> = body_bal.iter().map(|a| a.address).collect();
let expected_addrs: Vec<_> = expected_bal.iter().map(|a| a.address).collect();
// Missing accounts (expected but not found in body)
for addr in &expected_addrs {
if !body_addrs.contains(addr) {
tracing::debug!("Missing acc : computed bal {:?},body bal{:?}", expected_bal, body_bal);
tracing::debug!("Missing Address: {:?}", addr);
return Err(ConsensusError::InvalidBalMissingAccount);
}
}
// Extra accounts (body has accounts not in expected)
for addr in &body_addrs {
if !expected_addrs.contains(addr) {
tracing::debug!("Extra acc : computed bal {:?},body bal{:?}", expected_bal, body_bal);
tracing::debug!("Extra Address: {:?}", addr);
return Err(ConsensusError::InvalidBalExtraAccount);
}
}
tracing::debug!(
?expected_bal,
?body_bal,
"block access list in body does not match the provided block access list"
);
// Fallback: mismatched access lists
Err(ConsensusError::InvalidBlockAccessList)
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -17,9 +17,7 @@ pub use payload::{payload_id, BlobSidecars, EthBuiltPayload, EthPayloadBuilderAt
mod error;
pub use error::*;
use alloy_rpc_types_engine::{
ExecutionData, ExecutionPayload, ExecutionPayloadEnvelopeV5, ExecutionPayloadEnvelopeV6,
};
use alloy_rpc_types_engine::{ExecutionData, ExecutionPayload, ExecutionPayloadEnvelopeV5};
pub use alloy_rpc_types_engine::{
ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4,
ExecutionPayloadV1, PayloadAttributes as EthPayloadAttributes,
@@ -68,15 +66,13 @@ where
+ TryInto<ExecutionPayloadEnvelopeV2>
+ TryInto<ExecutionPayloadEnvelopeV3>
+ TryInto<ExecutionPayloadEnvelopeV4>
+ TryInto<ExecutionPayloadEnvelopeV5>
+ TryInto<ExecutionPayloadEnvelopeV6>,
+ TryInto<ExecutionPayloadEnvelopeV5>,
{
type ExecutionPayloadEnvelopeV1 = ExecutionPayloadV1;
type ExecutionPayloadEnvelopeV2 = ExecutionPayloadEnvelopeV2;
type ExecutionPayloadEnvelopeV3 = ExecutionPayloadEnvelopeV3;
type ExecutionPayloadEnvelopeV4 = ExecutionPayloadEnvelopeV4;
type ExecutionPayloadEnvelopeV5 = ExecutionPayloadEnvelopeV5;
type ExecutionPayloadEnvelopeV6 = ExecutionPayloadEnvelopeV6;
}
/// A default payload type for [`EthEngineTypes`]

View File

@@ -11,9 +11,8 @@ use alloy_primitives::{Address, B256, U256};
use alloy_rlp::Encodable;
use alloy_rpc_types_engine::{
BlobsBundleV1, BlobsBundleV2, ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3,
ExecutionPayloadEnvelopeV4, ExecutionPayloadEnvelopeV5, ExecutionPayloadEnvelopeV6,
ExecutionPayloadFieldV2, ExecutionPayloadV1, ExecutionPayloadV3, ExecutionPayloadV4,
PayloadAttributes, PayloadId,
ExecutionPayloadEnvelopeV4, ExecutionPayloadEnvelopeV5, ExecutionPayloadFieldV2,
ExecutionPayloadV1, ExecutionPayloadV3, PayloadAttributes, PayloadId,
};
use core::convert::Infallible;
use reth_ethereum_primitives::EthPrimitives;
@@ -159,38 +158,6 @@ impl EthBuiltPayload {
execution_requests: requests.unwrap_or_default(),
})
}
/// Try converting built payload into [`ExecutionPayloadEnvelopeV6`].
pub fn try_into_v6(self) -> Result<ExecutionPayloadEnvelopeV6, BuiltPayloadConversionError> {
let Self { block, fees, sidecars, requests, .. } = self;
let blobs_bundle = match sidecars {
BlobSidecars::Empty => BlobsBundleV2::empty(),
BlobSidecars::Eip7594(sidecars) => BlobsBundleV2::from(sidecars),
BlobSidecars::Eip4844(_) => {
return Err(BuiltPayloadConversionError::UnexpectedEip4844Sidecars)
}
};
Ok(ExecutionPayloadEnvelopeV6 {
execution_payload: ExecutionPayloadV4::from_block_unchecked(
block.hash(),
&Arc::unwrap_or_clone(block).into_block(),
),
block_value: fees,
// From the engine API spec:
//
// > Client software **MAY** use any heuristics to decide whether to set
// `shouldOverrideBuilder` flag or not. If client software does not implement any
// heuristic this flag **SHOULD** be set to `false`.
//
// Spec:
// <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification-2>
should_override_builder: false,
blobs_bundle,
execution_requests: requests.unwrap_or_default(),
})
}
}
impl<N: NodePrimitives> BuiltPayload for EthBuiltPayload<N> {
@@ -258,14 +225,6 @@ impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV5 {
}
}
impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV6 {
type Error = BuiltPayloadConversionError;
fn try_from(value: EthBuiltPayload) -> Result<Self, Self::Error> {
value.try_into_v6()
}
}
/// An enum representing blob transaction sidecars belonging to [`EthBuiltPayload`].
#[derive(Clone, Default, Debug)]
pub enum BlobSidecars {

View File

@@ -27,7 +27,6 @@ alloy-eips.workspace = true
alloy-evm.workspace = true
alloy-consensus.workspace = true
alloy-rpc-types-engine.workspace = true
alloy-rlp.workspace = true
# Misc
parking_lot = { workspace = true, optional = true }
@@ -58,7 +57,6 @@ std = [
"derive_more?/std",
"alloy-rpc-types-engine/std",
"reth-storage-errors/std",
"alloy-rlp/std",
]
test-utils = [
"dep:parking_lot",

View File

@@ -5,7 +5,6 @@ use alloy_consensus::{
};
use alloy_eips::merge::BEACON_NONCE;
use alloy_evm::{block::BlockExecutorFactory, eth::EthBlockExecutionCtx};
use alloy_primitives::Bytes;
use reth_chainspec::{EthChainSpec, EthereumHardforks};
use reth_evm::execute::{BlockAssembler, BlockAssemblerInput, BlockExecutionError};
use reth_execution_types::BlockExecutionResult;
@@ -17,14 +16,12 @@ use revm::context::Block as _;
pub struct EthBlockAssembler<ChainSpec = reth_chainspec::ChainSpec> {
/// The chainspec.
pub chain_spec: Arc<ChainSpec>,
/// Extra data to use for the blocks.
pub extra_data: Bytes,
}
impl<ChainSpec> EthBlockAssembler<ChainSpec> {
/// Creates a new [`EthBlockAssembler`].
pub fn new(chain_spec: Arc<ChainSpec>) -> Self {
Self { chain_spec, extra_data: Default::default() }
pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
Self { chain_spec }
}
}
@@ -48,8 +45,7 @@ where
execution_ctx: ctx,
parent,
transactions,
output:
BlockExecutionResult { receipts, requests, gas_used, blob_gas_used, block_access_list },
output: BlockExecutionResult { receipts, requests, gas_used, blob_gas_used },
state_root,
..
} = input;
@@ -94,18 +90,6 @@ where
};
}
let (built_block_access_list, block_access_list_hash) =
if self.chain_spec.is_amsterdam_active_at_timestamp(timestamp) {
if let Some(bal) = block_access_list {
let hash = alloy_primitives::keccak256(alloy_rlp::encode(bal));
(Some(bal), Some(hash))
} else {
(None, None)
}
} else {
(None, None)
};
let header = Header {
parent_hash: ctx.parent_hash,
ommers_hash: EMPTY_OMMER_ROOT_HASH,
@@ -123,22 +107,16 @@ where
gas_limit: evm_env.block_env.gas_limit(),
difficulty: evm_env.block_env.difficulty(),
gas_used: *gas_used,
extra_data: self.extra_data.clone(),
extra_data: ctx.extra_data,
parent_beacon_block_root: ctx.parent_beacon_block_root,
blob_gas_used: block_blob_gas_used,
excess_blob_gas,
requests_hash,
block_access_list_hash,
};
Ok(Block {
header,
body: BlockBody {
transactions,
ommers: Default::default(),
withdrawals,
block_access_list: built_block_access_list.cloned(),
},
body: BlockBody { transactions, ommers: Default::default(), withdrawals },
})
}
}

View File

@@ -19,32 +19,37 @@ extern crate alloc;
use alloc::{borrow::Cow, sync::Arc};
use alloy_consensus::Header;
use alloy_eips::Decodable2718;
pub use alloy_evm::EthEvm;
use alloy_evm::{
eth::{EthBlockExecutionCtx, EthBlockExecutorFactory},
EthEvmFactory, FromRecoveredTx, FromTxWithEncoded,
};
use alloy_primitives::{Bytes, U256};
use alloy_rpc_types_engine::ExecutionData;
use core::{convert::Infallible, fmt::Debug};
use reth_chainspec::{ChainSpec, EthChainSpec, EthereumHardforks, MAINNET};
use reth_chainspec::{ChainSpec, EthChainSpec, MAINNET};
use reth_ethereum_primitives::{Block, EthPrimitives, TransactionSigned};
use reth_evm::{
eth::NextEvmEnvAttributes, precompiles::PrecompilesMap, ConfigureEngineEvm, ConfigureEvm,
EvmEnv, EvmEnvFor, EvmFactory, ExecutableTxIterator, ExecutionCtxFor, NextBlockEnvAttributes,
TransactionEnv,
eth::NextEvmEnvAttributes, precompiles::PrecompilesMap, ConfigureEvm, EvmEnv, EvmFactory,
NextBlockEnvAttributes, TransactionEnv,
};
use reth_primitives_traits::{
constants::MAX_TX_GAS_LIMIT_OSAKA, SealedBlock, SealedHeader, SignedTransaction, TxTy,
};
use reth_storage_errors::any::AnyError;
use revm::{
context::{BlockEnv, CfgEnv},
context_interface::block::BlobExcessGasAndPrice,
primitives::hardfork::SpecId,
use reth_primitives_traits::{SealedBlock, SealedHeader};
use revm::{context::BlockEnv, primitives::hardfork::SpecId};
#[cfg(feature = "std")]
use reth_evm::{ConfigureEngineEvm, ExecutableTxIterator};
#[allow(unused_imports)]
use {
alloy_eips::Decodable2718,
alloy_primitives::{Bytes, U256},
alloy_rpc_types_engine::ExecutionData,
reth_chainspec::EthereumHardforks,
reth_evm::{EvmEnvFor, ExecutionCtxFor},
reth_primitives_traits::{constants::MAX_TX_GAS_LIMIT_OSAKA, SignedTransaction, TxTy},
reth_storage_errors::any::AnyError,
revm::context::CfgEnv,
revm::context_interface::block::BlobExcessGasAndPrice,
};
pub use alloy_evm::EthEvm;
mod config;
use alloy_evm::eth::spec::EthExecutorSpec;
pub use config::{revm_spec, revm_spec_by_timestamp_and_block_number};
@@ -116,12 +121,6 @@ impl<ChainSpec, EvmFactory> EthEvmConfig<ChainSpec, EvmFactory> {
pub const fn chain_spec(&self) -> &Arc<ChainSpec> {
self.executor_factory.spec()
}
/// Sets the extra data for the block assembler.
pub fn with_extra_data(mut self, extra_data: Bytes) -> Self {
self.block_assembler.extra_data = extra_data;
self
}
}
impl<ChainSpec, EvmF> ConfigureEvm for EthEvmConfig<ChainSpec, EvmF>
@@ -193,6 +192,7 @@ where
parent_beacon_block_root: block.header().parent_beacon_block_root,
ommers: &block.body().ommers,
withdrawals: block.body().withdrawals.as_ref().map(Cow::Borrowed),
extra_data: block.header().extra_data.clone(),
})
}
@@ -206,10 +206,12 @@ where
parent_beacon_block_root: attributes.parent_beacon_block_root,
ommers: &[],
withdrawals: attributes.withdrawals.map(Cow::Owned),
extra_data: attributes.extra_data,
})
}
}
#[cfg(feature = "std")]
impl<ChainSpec, EvmF> ConfigureEngineEvm<ExecutionData> for EthEvmConfig<ChainSpec, EvmF>
where
ChainSpec: EthExecutorSpec + EthChainSpec<Header = Header> + Hardforks + 'static,
@@ -282,6 +284,7 @@ where
parent_beacon_block_root: payload.sidecar.parent_beacon_block_root(),
ommers: &[],
withdrawals: payload.payload.withdrawals().map(|w| Cow::Owned(w.clone().into())),
extra_data: payload.payload.as_v1().extra_data.clone(),
})
}
@@ -289,7 +292,7 @@ where
&self,
payload: &ExecutionData,
) -> Result<impl ExecutableTxIterator<Self>, Self::Error> {
let txs = payload.payload.transactions().clone().into_iter();
let txs = payload.payload.transactions().clone();
let convert = |tx: Bytes| {
let tx =
TxTy::<Self::Primitives>::decode_2718_exact(tx.as_ref()).map_err(AnyError::new)?;

View File

@@ -2,7 +2,7 @@ use crate::EthEvmConfig;
use alloc::{boxed::Box, sync::Arc, vec, vec::Vec};
use alloy_consensus::Header;
use alloy_eips::eip7685::Requests;
use alloy_evm::{block::StateDB, precompiles::PrecompilesMap};
use alloy_evm::precompiles::PrecompilesMap;
use alloy_primitives::Bytes;
use alloy_rpc_types_engine::ExecutionData;
use parking_lot::Mutex;
@@ -19,6 +19,7 @@ use reth_execution_types::{BlockExecutionResult, ExecutionOutcome};
use reth_primitives_traits::{BlockTy, SealedBlock, SealedHeader};
use revm::{
context::result::{ExecutionResult, Output, ResultAndState, SuccessReason},
database::State,
Inspector,
};
@@ -57,12 +58,12 @@ impl BlockExecutorFactory for MockEvmConfig {
fn create_executor<'a, DB, I>(
&'a self,
evm: EthEvm<DB, I, PrecompilesMap>,
evm: EthEvm<&'a mut State<DB>, I, PrecompilesMap>,
_ctx: Self::ExecutionCtx<'a>,
) -> impl BlockExecutorFor<'a, Self, DB, I>
where
DB: StateDB + Database + 'a,
I: Inspector<<Self::EvmFactory as EvmFactory>::Context<DB>> + 'a,
DB: Database + 'a,
I: Inspector<<Self::EvmFactory as EvmFactory>::Context<&'a mut State<DB>>> + 'a,
{
MockExecutor { result: self.exec_results.lock().pop().unwrap(), evm, hook: None }
}
@@ -70,17 +71,17 @@ impl BlockExecutorFactory for MockEvmConfig {
/// Mock executor that returns a fixed execution result.
#[derive(derive_more::Debug)]
pub struct MockExecutor<DB: Database, I> {
pub struct MockExecutor<'a, DB: Database, I> {
result: ExecutionOutcome,
evm: EthEvm<DB, I, PrecompilesMap>,
evm: EthEvm<&'a mut State<DB>, I, PrecompilesMap>,
#[debug(skip)]
hook: Option<Box<dyn reth_evm::OnStateHook>>,
}
impl<DB: StateDB + Database, I: Inspector<EthEvmContext<DB>>> BlockExecutor
for MockExecutor<DB, I>
impl<'a, DB: Database, I: Inspector<EthEvmContext<&'a mut State<DB>>>> BlockExecutor
for MockExecutor<'a, DB, I>
{
type Evm = EthEvm<DB, I, PrecompilesMap>;
type Evm = EthEvm<&'a mut State<DB>, I, PrecompilesMap>;
type Transaction = TransactionSigned;
type Receipt = Receipt;
@@ -124,11 +125,10 @@ impl<DB: StateDB + Database, I: Inspector<EthEvmContext<DB>>> BlockExecutor
reqs
}),
gas_used: 0,
block_access_list: None,
blob_gas_used: 0,
};
*evm.db_mut().bundle_state_mut() = bundle;
evm.db_mut().bundle_state = bundle;
Ok((evm, result))
}

View File

@@ -38,7 +38,6 @@ fn create_database_with_beacon_root_contract() -> CacheDB<EmptyDB> {
code_hash: keccak256(BEACON_ROOTS_CODE.clone()),
nonce: 1,
code: Some(Bytecode::new_raw(BEACON_ROOTS_CODE.clone())),
storage_id: None,
};
db.insert_account_info(BEACON_ROOTS_ADDRESS, beacon_root_contract_account);
@@ -54,7 +53,6 @@ fn create_database_with_withdrawal_requests_contract() -> CacheDB<EmptyDB> {
balance: U256::ZERO,
code_hash: keccak256(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone()),
code: Some(Bytecode::new_raw(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone())),
storage_id: None,
};
db.insert_account_info(
@@ -88,12 +86,7 @@ fn eip_4788_non_genesis_call() {
.execute_one(&RecoveredBlock::new_unhashed(
Block {
header: header.clone(),
body: BlockBody {
transactions: vec![],
ommers: vec![],
withdrawals: None,
block_access_list: None,
},
body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None },
},
vec![],
))
@@ -112,12 +105,7 @@ fn eip_4788_non_genesis_call() {
.execute_one(&RecoveredBlock::new_unhashed(
Block {
header: header.clone(),
body: BlockBody {
transactions: vec![],
ommers: vec![],
withdrawals: None,
block_access_list: None,
},
body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None },
},
vec![],
))
@@ -177,12 +165,7 @@ fn eip_4788_no_code_cancun() {
.execute_one(&RecoveredBlock::new_unhashed(
Block {
header,
body: BlockBody {
transactions: vec![],
ommers: vec![],
withdrawals: None,
block_access_list: None,
},
body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None },
},
vec![],
))
@@ -224,12 +207,7 @@ fn eip_4788_empty_account_call() {
.execute_one(&RecoveredBlock::new_unhashed(
Block {
header,
body: BlockBody {
transactions: vec![],
ommers: vec![],
withdrawals: None,
block_access_list: None,
},
body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None },
},
vec![],
))
@@ -361,7 +339,6 @@ fn create_database_with_block_hashes(latest_block: u64) -> CacheDB<EmptyDB> {
code_hash: keccak256(HISTORY_STORAGE_CODE.clone()),
code: Some(Bytecode::new_raw(HISTORY_STORAGE_CODE.clone())),
nonce: 1,
storage_id: None,
};
db.insert_account_info(HISTORY_STORAGE_ADDRESS, blockhashes_contract_account);
@@ -819,7 +796,6 @@ fn test_balance_increment_not_duplicated() {
transactions: vec![],
ommers: vec![],
withdrawals: Some(vec![withdrawal].into()),
block_access_list: None,
},
},
vec![],

View File

@@ -170,7 +170,7 @@ impl DisplayHardforks {
let mut post_merge = Vec::new();
for (fork, condition, metadata) in hardforks {
let mut display_fork = DisplayFork {
let display_fork = DisplayFork {
name: fork.name().to_string(),
activated_at: condition,
eip: None,
@@ -181,12 +181,7 @@ impl DisplayHardforks {
ForkCondition::Block(_) => {
pre_merge.push(display_fork);
}
ForkCondition::TTD { activation_block_number, total_difficulty, fork_block } => {
display_fork.activated_at = ForkCondition::TTD {
activation_block_number,
fork_block,
total_difficulty,
};
ForkCondition::TTD { .. } => {
with_merge.push(display_fork);
}
ForkCondition::Timestamp(_) => {

View File

@@ -24,7 +24,7 @@ reth-provider.workspace = true
reth-transaction-pool.workspace = true
reth-network.workspace = true
reth-evm.workspace = true
reth-evm-ethereum.workspace = true
reth-evm-ethereum = { workspace = true, features = ["std"] }
reth-rpc.workspace = true
reth-rpc-api.workspace = true
reth-rpc-eth-api.workspace = true
@@ -35,7 +35,7 @@ reth-chainspec.workspace = true
reth-revm = { workspace = true, features = ["std"] }
reth-rpc-eth-types.workspace = true
reth-engine-local.workspace = true
reth-engine-primitives.workspace = true
reth-engine-primitives = { workspace = true, features = ["std"] }
reth-payload-primitives.workspace = true
# ethereum
@@ -61,6 +61,9 @@ reth-node-core.workspace = true
reth-e2e-test-utils.workspace = true
reth-tasks.workspace = true
reth-testing-utils.workspace = true
reth-stages-types.workspace = true
tempfile.workspace = true
jsonrpsee-core.workspace = true
alloy-primitives.workspace = true
alloy-provider.workspace = true
@@ -86,6 +89,10 @@ asm-keccak = [
"reth-node-core/asm-keccak",
"revm/asm-keccak",
]
keccak-cache-global = [
"alloy-primitives/keccak-cache-global",
"reth-node-core/keccak-cache-global",
]
js-tracer = [
"reth-node-builder/js-tracer",
"reth-rpc/js-tracer",
@@ -104,4 +111,5 @@ test-utils = [
"reth-evm/test-utils",
"reth-primitives-traits/test-utils",
"reth-evm-ethereum/test-utils",
"reth-stages-types/test-utils",
]

View File

@@ -32,15 +32,15 @@ use reth_node_builder::{
EngineValidatorBuilder, EthApiBuilder, EthApiCtx, Identity, PayloadValidatorBuilder,
RethRpcAddOns, RpcAddOns, RpcHandle,
},
BuilderContext, DebugNode, Node, NodeAdapter, PayloadBuilderConfig,
BuilderContext, DebugNode, Node, NodeAdapter,
};
use reth_payload_primitives::PayloadTypes;
use reth_provider::{providers::ProviderFactoryBuilder, EthStorage};
use reth_rpc::{
eth::core::{EthApiFor, EthRpcConverterFor},
ValidationApi,
TestingApi, ValidationApi,
};
use reth_rpc_api::servers::BlockSubmissionValidationApiServer;
use reth_rpc_api::servers::{BlockSubmissionValidationApiServer, TestingApiServer};
use reth_rpc_builder::{config::RethRpcServerConfig, middleware::RethRpcMiddleware};
use reth_rpc_eth_api::{
helpers::{
@@ -118,13 +118,14 @@ impl EthereumNode {
/// use reth_chainspec::ChainSpecBuilder;
/// use reth_db::open_db_read_only;
/// use reth_node_ethereum::EthereumNode;
/// use reth_provider::providers::StaticFileProvider;
/// use reth_provider::providers::{RocksDBProvider, StaticFileProvider};
/// use std::sync::Arc;
///
/// let factory = EthereumNode::provider_factory_builder()
/// .db(Arc::new(open_db_read_only("db", Default::default()).unwrap()))
/// .chainspec(ChainSpecBuilder::mainnet().build().into())
/// .static_file(StaticFileProvider::read_only("db/static_files", false).unwrap())
/// .rocksdb_provider(RocksDBProvider::builder("db/rocksdb").build().unwrap())
/// .build_provider_factory();
/// ```
pub fn provider_factory_builder() -> ProviderFactoryBuilder<Self> {
@@ -313,6 +314,17 @@ where
.modules
.merge_if_module_configured(RethRpcModule::Eth, eth_config.into_rpc())?;
// testing_buildBlockV1: only wire when the hidden testing module is explicitly
// requested on any transport. Default stays disabled to honor security guidance.
let testing_api = TestingApi::new(
container.registry.eth_api().clone(),
container.registry.evm_config().clone(),
)
.into_rpc();
container
.modules
.merge_if_module_configured(RethRpcModule::Testing, testing_api)?;
Ok(())
})
.await
@@ -426,9 +438,7 @@ where
type EVM = EthEvmConfig<Types::ChainSpec>;
async fn build_evm(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::EVM> {
let evm_config = EthEvmConfig::new(ctx.chain_spec())
.with_extra_data(ctx.payload_builder_config().extra_data_bytes());
Ok(evm_config)
Ok(EthEvmConfig::new(ctx.chain_spec()))
}
}

Some files were not shown because too many files have changed in this diff Show More