From 2bbf4127043e0ac31838f0c99e0676ce9d7c9547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20=C3=93=20Mhuiris?= Date: Wed, 6 May 2026 14:16:49 +0100 Subject: [PATCH 1/3] Precompile spike --- Cargo.lock | 956 ++++++++++-------- Cargo.toml | 47 +- README.md | 5 +- crates/arkiv-node/Cargo.toml | 16 + crates/arkiv-node/src/cli.rs | 30 + crates/arkiv-node/src/genesis.rs | 29 + crates/arkiv-node/src/install.rs | 150 +++ crates/arkiv-node/src/lib.rs | 29 + crates/arkiv-node/src/main.rs | 117 +-- crates/arkiv-node/src/precompile.rs | 517 ++++++++++ crates/arkiv-node/src/rpc.rs | 41 +- crates/arkiv-node/src/storage/jsonrpc.rs | 12 +- docs/architecture.md | 35 +- docs/custom-precompile.md | 1025 ++++++++++++++++++++ justfile | 16 +- precompiles/.gitignore | 3 + precompiles/README.md | 43 + precompiles/contracts/PrecompileCaller.sol | 34 + precompiles/foundry.toml | 9 + precompiles/run.sh | 64 ++ scripts/mock-entitydb.js | 11 +- 21 files changed, 2605 insertions(+), 584 deletions(-) create mode 100644 crates/arkiv-node/src/cli.rs create mode 100644 crates/arkiv-node/src/genesis.rs create mode 100644 crates/arkiv-node/src/install.rs create mode 100644 crates/arkiv-node/src/lib.rs create mode 100644 crates/arkiv-node/src/precompile.rs create mode 100644 docs/custom-precompile.md create mode 100644 precompiles/.gitignore create mode 100644 precompiles/README.md create mode 100644 precompiles/contracts/PrecompileCaller.sol create mode 100644 precompiles/foundry.toml create mode 100755 precompiles/run.sh diff --git a/Cargo.lock b/Cargo.lock index 6c5722b..429c58a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,14 +101,14 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8c24c95e90c1608c2d91cff1b451d796474168d3310ccc8b7cd12502ca8169" +checksum = "e3d64da86c616b5092ea64eea648f311bbd58630a0b384c42d699175d6f9122b" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-trie", "alloy-tx-macros", "auto_impl", @@ -128,23 +128,23 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d211ad0ef468a70a7a829e49683ff59ad25f02b4ab3764344c4c2663329a52c" +checksum = "8fd98696ca3617d3a9ba1a6f2011880cbfd5618228dab6400c9f8bca457859a8" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-contract" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59d55233ac14aa7fa6bcdcad45ba305e90c556065e0947cd9f243c4469e7c2d" +checksum = "de3df0aadc569a8b277808a7d0ad0e421180654ea36a3c59e9ed2bb968c9a1cd" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -222,9 +222,9 @@ dependencies = [ [[package]] name = "alloy-eip7928" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "407510740da514b694fecb44d8b3cebdc60d448f70cc5d24743e8ba273448a6e" +checksum = "ec6ae911a2fc304a7cb80a79fb7bed6d1474aed4e7c203df1f8ff538f64fc78d" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -258,9 +258,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae69eaa5096b47ffe97e6a5d6bde7e7fa2dec106af22a9315621d11039c3de3c" +checksum = "64c0456f5f7a4497e9342d20f528e30f5288ddfa0d6a012bd5044afee46cd8a0" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -268,7 +268,7 @@ dependencies = [ "alloy-eip7928", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "auto_impl", "borsh", "c-kzg", @@ -283,12 +283,12 @@ dependencies = [ [[package]] name = "alloy-evm" -version = "0.31.0" +version = "0.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcd754dd6733c3f2d44dd99ddecb7d844428a9b3cdbcafbe65570886bf24b20" +checksum = "7f092eb6af80456e4ab41c9722e777d791335bc9c00b11bc3db7bf29a432dfd0" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-hardforks", "alloy-primitives", "alloy-rpc-types-engine", @@ -303,13 +303,13 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39789db0b3f3bbef0e6549c87bc6842b73886ebabee1405b6941685b1cc34083" +checksum = "a71ff8b55d2b8aa05259f474cae7dea0e4991724dc18936b81cb23ec492a0c2a" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-trie", "borsh", "serde", @@ -344,9 +344,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662b525af73e86b2167dae923261c8edf440ba7e1426b30a8b993177bc214c02" +checksum = "19e352478b756bad5d7203148e4b461861282ea2ded3da406ba24868b52cd098" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -359,19 +359,19 @@ dependencies = [ [[package]] name = "alloy-network" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c657c2d9751d3c7d94990554b231e5372c3c2e4bad842806280b6151a0d6a05d" +checksum = "ed08ae169869e08370ed121612e0d3dadac33d1a256e9f2465926b23f0bd7d95" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-json-rpc", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-any", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer", "alloy-sol-types", "async-trait", @@ -385,24 +385,24 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e7c4bb0ebbd6d7406d2808968f43c0d5186c69c5e58cedcbee7380f4cd1fcf" +checksum = "02e6c7ad28afe348a9a9c5624b67ee5b3607b8de98d5816b3056ecdfa6fa2697" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-op-evm" -version = "0.30.0" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +version = "0.31.0" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-op-hardforks", "alloy-primitives", @@ -416,7 +416,7 @@ dependencies = [ [[package]] name = "alloy-op-hardforks" version = "0.4.7" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-chains", "alloy-hardforks", @@ -454,13 +454,13 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4fea0fc2628cdbc851aaa333124f9d8ab9f567ab8d4c20202819db13aa1a534" +checksum = "93a7c17472b55482d4734154c2f5ed13f72e03f6752cebb927f6a2d8b52e646c" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-json-rpc", "alloy-network", "alloy-network-primitives", @@ -486,7 +486,7 @@ dependencies = [ "lru", "parking_lot", "pin-project", - "reqwest 0.13.2", + "reqwest 0.13.3", "serde", "serde_json", "thiserror 2.0.18", @@ -498,9 +498,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc7b42e514613c717887dc77bb58d35e845557ebd63a18c3f92a77094e4891f" +checksum = "a8d86958b02bca85103d64fa60d7b364a8b017c6e40f2b02c3f50ca22964a738" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -542,9 +542,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5ee7b51752c68fb95f21705e402700750e692b1d21ccc294ac48fadc8655d53" +checksum = "5beb5c2fe6b960c8e8b038e69fd502a90a2e930afa4770efb748b163b0767729" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -555,7 +555,7 @@ dependencies = [ "alloy-transport-ws", "futures", "pin-project", - "reqwest 0.13.2", + "reqwest 0.13.3", "serde", "serde_json", "tokio", @@ -568,22 +568,22 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fa76988f54105ad4398828e8aaf1a39b3f07f91fb79091529056689514ee8c2" +checksum = "4ee1257a278f6d293e05c5162c5940a1561b1aa85ded0028b464c81de37ebfa5" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-rpc-types-admin" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670b3a8e0d1b32e9886b7a419b8efe6754ea00b9fdd4c0ea3c7411b6c30431f4" +checksum = "e2144d5b2866e651796eac0a997d3b5a056449c12e0d91be3184129e0c722885" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -593,38 +593,38 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d276bea4e92e4991269d31b9abd3e722eed2565b82036478a4416adb8dd4992" +checksum = "df32156f085e74eac942b6103744be49b817c302341aaa8cb0c1c88dc29228d9" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-rpc-types-any" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f1a9a3bda9be7f6515316eb792710532411878bbfc88934973f4b371376b00d" +checksum = "6a234bfbdf7a76c3d13808f729af5321852de3dedcaa6fc6d5f54787aaf54c6a" dependencies = [ "alloy-consensus-any", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", "serde_json", ] [[package]] name = "alloy-rpc-types-beacon" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf5d68ddca890854fb78291cbde06115473ded00b2337d0f815e92c0c1f8003" +checksum = "296450f5e76bece0116c939b9437b0421a5da9c5d40031bf4cf9b38d3d94e475" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-engine", "derive_more", @@ -640,9 +640,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea21739e232c221779741eba7e7b9bc19ad8ff777b72736647ae519f5c9f6f33" +checksum = "0ab075ac1c25bcf697f133b7cd92e2fb26afe213e872ef79fdf77f0d7bcb3793" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -653,15 +653,15 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f05338cfb4ee5508ff76f01c88142cab8a4579db74b7d9432936c26e4f11374" +checksum = "73b12366c96f4013e1aeebc96c6b56e5f33f07853c42ea2f485045c0c157a4a1" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "derive_more", "ethereum_ssz", "ethereum_ssz_derive", @@ -673,17 +673,17 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda4ece0050154ab278241aeffade58916b04f38254832e8cb6e4671c6e72ed2" +checksum = "56a282daf869eeb7383d3d5c2deb35b0b3fb45ecb329513af4090fc61245ee18" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network-primitives", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-sol-types", "itertools 0.14.0", "serde", @@ -694,28 +694,28 @@ dependencies = [ [[package]] name = "alloy-rpc-types-mev" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df223478aec91d8fb0f8643234764042fa432e6111aca126774802b2618a3a5" +checksum = "7adc1243d55744a66b3a6cbbbba96436e8df5d248f2ee8186bef4238ef704ec7" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", "serde_json", ] [[package]] name = "alloy-rpc-types-trace" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5905ac3663b0859d67b82d912acce20887d20682a0cadde79c8a763b133a515" +checksum = "6184b5d14152b68b0bb8beb621339d94f0b761a37958bb365fbf7c00922125c2" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", "serde_json", "thiserror 2.0.18", @@ -723,13 +723,13 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fbf71892d4df9cae8d35dc96f15d522384bb93806205465e2c8c012b7f0a34" +checksum = "f00b631c361e7c7baaf4f1f5a9877730f3507fed2acb9d4b34841b8184b2ec28" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] @@ -746,9 +746,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beaa5c581a67e2743d95b4849eb9cfeb90866429cdaa6d8f6b75eb988b2d0cd9" +checksum = "a0eada2558e921b39dfcead33c487364df9b31374f5733c1c9d2c891c4529933" dependencies = [ "alloy-primitives", "serde", @@ -757,9 +757,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5da9ae50f9b48d7b4e2e5cde87175257be7e5e56909a7794720597c1d9806f6" +checksum = "41eb29f7a8adcd8941fbb8e134022a133e6f8dfd345f2e3b7109599f8a7dca08" dependencies = [ "alloy-primitives", "async-trait", @@ -772,9 +772,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b794002d57fd2f71b4c87298a41ca24dfc0f2cf6630d95106a477e451747ba" +checksum = "bef839e7ce9b59aa60fa9a175e97986c6145c888d643b0f1fb0a3e7b8e56a2e2" dependencies = [ "alloy-consensus", "alloy-network", @@ -861,9 +861,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19dec9bfb59647254afdecbb5ddcddd7ba02edcd48ffa40510bddfbed0be1634" +checksum = "3ac7a80c0bac3e44559d53d002e34c461dc2f23262b42cafec019bc70551abbe" dependencies = [ "alloy-json-rpc", "auto_impl", @@ -884,14 +884,14 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2035f3c4d6bee20624da2dcf765d469b292398e48d766ffade61b0fcf8b4d45d" +checksum = "eed3ed3300a998f88639ed619fdbbd88bd82865e00c6a8ecb796c99eb12358f6" dependencies = [ "alloy-json-rpc", "alloy-transport", "itertools 0.14.0", - "reqwest 0.13.2", + "reqwest 0.13.3", "serde_json", "tower", "tracing", @@ -900,9 +900,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfad7aa9206fcb831ae401b6a1c893a402b8eed74f9c8ffbb7a7323afb0d9a4c" +checksum = "1075d9d30fd4d71e50000fd4afb19ed2664ceab20c2a29f3889a6e988329e02d" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -920,9 +920,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5aa8ff49386df3e008b73c7fb0a5479410e8493fdb86a8b916877a16e8aead9" +checksum = "0e3bff84b2b2a46eb34cc522dc3f889a2867c70be90a377421429b662b3ec4ce" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -955,9 +955,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3520337f3d3d063a7fe20f47aaa62d695e3dc0372b34f601560dee24e76988b9" +checksum = "99fce0350197dcd4ba4e9a7dd43915d908c0eb0e7352755791709a705e1c76b6" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -1337,7 +1337,7 @@ dependencies = [ [[package]] name = "arkiv-bindings" version = "0.1.0" -source = "git+https://github.com/Arkiv-Network/arkiv-contracts.git?rev=ff30cab#ff30cab364f789bf40aa1d393114c45de8430a5d" +source = "git+https://github.com/Arkiv-Network/arkiv-contracts.git?rev=d4bf0d59#d4bf0d59c069aab38ae491f20e420dc834909c88" dependencies = [ "alloy-contract", "alloy-primitives", @@ -1390,7 +1390,13 @@ name = "arkiv-node" version = "0.1.0" dependencies = [ "alloy-consensus", + "alloy-eips 2.0.4", + "alloy-evm", + "alloy-hardforks", + "alloy-op-evm", "alloy-primitives", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", "alloy-sol-types", "arkiv-bindings", "arkiv-genesis", @@ -1398,15 +1404,21 @@ dependencies = [ "eyre", "futures-util", "jsonrpsee", + "op-alloy-consensus", + "op-revm", "reqwest 0.12.28", + "reth-evm", "reth-execution-types", "reth-exex", "reth-node-api", + "reth-node-builder", "reth-optimism-chainspec", "reth-optimism-cli", "reth-optimism-node", "reth-optimism-primitives", + "reth-primitives-traits", "reth-storage-api", + "revm-precompile", "serde", "serde_json", "tokio", @@ -1436,9 +1448,9 @@ checksum = "4858a9d740c5007a9069007c3b4e91152d0506f13c1b31dd49051fd537656156" [[package]] name = "async-compression" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" +checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" dependencies = [ "compression-codecs", "compression-core", @@ -1716,9 +1728,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.8.4" +version = "1.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" dependencies = [ "arrayref", "arrayvec", @@ -1924,9 +1936,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.60" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "jobserver", @@ -2139,9 +2151,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" dependencies = [ "brotli", "compression-core", @@ -2153,9 +2165,9 @@ dependencies = [ [[package]] name = "compression-core" -version = "0.4.31" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" [[package]] name = "concat-kdf" @@ -2271,9 +2283,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "crc32fast" @@ -2512,15 +2524,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "data-encoding-macro" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" +checksum = "3259c913752a86488b501ed8680446a5ed2d5aeac6e596cb23ba3800768ea32c" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -2528,9 +2540,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" +checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" dependencies = [ "data-encoding", "syn 2.0.117", @@ -2913,7 +2925,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2967,6 +2979,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "evmap" +version = "11.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8874945f036109c72242964c1174cf99434e30cfa45bf45fedc983f50046f8" +dependencies = [ + "hashbag", + "left-right", + "smallvec", +] + [[package]] name = "eyre" version = "0.6.12" @@ -3254,6 +3277,21 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" +[[package]] +name = "generator" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link 0.2.1", + "windows-result 0.4.1", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -3410,6 +3448,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "hashbag" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7040a10f52cba493ddb09926e15d10a9d8a28043708a405931fe4c6f19fac064" + [[package]] name = "hashbrown" version = "0.12.3" @@ -3675,6 +3719,7 @@ dependencies = [ "hyper-util", "log", "rustls", + "rustls-native-certs", "tokio", "tokio-rustls", "tower-service", @@ -3729,7 +3774,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core 0.62.2", ] [[package]] @@ -3743,12 +3788,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -3762,7 +3808,6 @@ checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", - "serde", "tinystr", "writeable", "zerovec", @@ -3770,11 +3815,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b24a59706036ba941c9476a55cd57b82b77f38a3c667d637ee7cabbc85eaedc" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -3785,31 +3829,29 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.0.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5a97b8ac6235e69506e8dacfb2adf38461d2ce6d3e9bd9c94c4cbc3cd4400a4" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" @@ -3819,8 +3861,6 @@ checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", - "serde", - "stable_deref_trait", "writeable", "yoke", "zerofrom", @@ -3853,9 +3893,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -4090,6 +4130,36 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "simd_cesu8", + "syn 2.0.117", +] + [[package]] name = "jni-sys" version = "0.3.1" @@ -4130,9 +4200,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ "cfg-if", "futures-util", @@ -4421,11 +4491,22 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "left-right" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0c21e4c8ff95f487fb34e6f9182875f42c84cef966d29216bf115d9bba835a" +dependencies = [ + "crossbeam-utils", + "loom", + "slab", +] + [[package]] name = "libc" -version = "0.2.185" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libgit2-sys" @@ -4583,6 +4664,19 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber 0.3.23", +] + [[package]] name = "lru" version = "0.16.4" @@ -4686,12 +4780,12 @@ dependencies = [ [[package]] name = "metrics" -version = "0.24.3" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" +checksum = "ff56c2e7dce6bd462e3b8919986a617027481b1dcc703175b58cf9dd98a2f071" dependencies = [ - "ahash", "portable-atomic", + "rapidhash", ] [[package]] @@ -4707,11 +4801,12 @@ dependencies = [ [[package]] name = "metrics-exporter-prometheus" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3589659543c04c7dc5526ec858591015b87cd8746583b51b48ef4353f99dbcda" +checksum = "5c0ca2990f7f78a72c4000ddce186db7d1b700477426563ee851c95ea3c0d0c4" dependencies = [ "base64 0.22.1", + "evmap", "indexmap 2.14.0", "metrics", "metrics-util", @@ -4737,9 +4832,9 @@ dependencies = [ [[package]] name = "metrics-util" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdfb1365fea27e6dd9dc1dbc19f570198bc86914533ad639dae939635f096be4" +checksum = "55ff5c12b797ebf094dc7c1d87e905efc0329cba332f96d51db03875441012b5" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -4748,6 +4843,7 @@ dependencies = [ "quanta", "rand 0.9.4", "rand_xoshiro", + "rapidhash", "sketches-ddsketch", ] @@ -4882,23 +4978,13 @@ dependencies = [ [[package]] name = "multihash" -version = "0.19.4" +version = "0.19.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ace881e3f514092ce9efbcb8f413d0ad9763860b828981c2de51ddc666936c" +checksum = "577c63b00ad74d57e8c9aa870b5fccebf2fd64a308a5aee9f1bb88e4aea19447" dependencies = [ - "no_std_io2", "unsigned-varint", ] -[[package]] -name = "no_std_io2" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a3564ce7035b1e4778d8cb6cacebb5d766b5e8fe5a75b9e441e33fb61a872c6" -dependencies = [ - "memchr", -] - [[package]] name = "nom" version = "7.1.3" @@ -4951,7 +5037,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5127,7 +5213,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "op-alloy" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "op-alloy-consensus", "op-alloy-network", @@ -5139,15 +5225,15 @@ dependencies = [ [[package]] name = "op-alloy-consensus" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "bytes", "derive_more", "modular-bitfield", @@ -5168,7 +5254,7 @@ checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc" [[package]] name = "op-alloy-network" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-consensus", "alloy-network", @@ -5181,7 +5267,7 @@ dependencies = [ [[package]] name = "op-alloy-provider" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-network", "alloy-primitives", @@ -5195,7 +5281,7 @@ dependencies = [ [[package]] name = "op-alloy-rpc-jsonrpsee" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-primitives", "jsonrpsee", @@ -5204,15 +5290,15 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer", "derive_more", "op-alloy-consensus", @@ -5225,15 +5311,14 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types-engine" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", - "alloy-serde 2.0.1", - "derive_more", + "alloy-serde 2.0.4", "ethereum_ssz", "ethereum_ssz_derive", "op-alloy-consensus", @@ -5245,8 +5330,8 @@ dependencies = [ [[package]] name = "op-revm" -version = "17.0.0" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +version = "19.0.0" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "auto_impl", "revm", @@ -5839,7 +5924,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -6158,6 +6243,7 @@ dependencies = [ "pin-project-lite", "quinn", "rustls", + "rustls-native-certs", "rustls-pki-types", "serde", "serde_json", @@ -6177,9 +6263,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" dependencies = [ "base64 0.22.1", "bytes", @@ -6199,7 +6285,7 @@ dependencies = [ "quinn", "rustls", "rustls-pki-types", - "rustls-platform-verifier 0.6.2", + "rustls-platform-verifier 0.7.0", "serde", "serde_json", "serde_urlencoded", @@ -6226,10 +6312,10 @@ checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" [[package]] name = "reth-basic-payload-builder" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "futures-core", "futures-util", @@ -6253,10 +6339,10 @@ dependencies = [ [[package]] name = "reth-chain-state" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "derive_more", "metrics", @@ -6283,11 +6369,11 @@ dependencies = [ [[package]] name = "reth-chainspec" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-genesis", "alloy-primitives", @@ -6303,7 +6389,7 @@ dependencies = [ [[package]] name = "reth-cli" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-genesis", "clap", @@ -6316,11 +6402,11 @@ dependencies = [ [[package]] name = "reth-cli-commands" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "backon", @@ -6339,7 +6425,7 @@ dependencies = [ "parking_lot", "ratatui", "rayon", - "reqwest 0.13.2", + "reqwest 0.13.3", "reth-chainspec", "reth-cli", "reth-cli-runner", @@ -6399,7 +6485,7 @@ dependencies = [ [[package]] name = "reth-cli-runner" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "reth-tasks", "tokio", @@ -6409,9 +6495,9 @@ dependencies = [ [[package]] name = "reth-cli-util" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "cfg-if", "eyre", @@ -6425,12 +6511,12 @@ dependencies = [ [[package]] name = "reth-codecs" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a29541038ab108b2e9d527c66b565e717e252e4eef6675377fc21f9ba587f792" +checksum = "fce542a96bf888f31854803e80b3340bc233927743aa580838014e8a88fe0d66" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-primitives", "alloy-trie", @@ -6444,9 +6530,9 @@ dependencies = [ [[package]] name = "reth-codecs-derive" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5646d4aff98bd51050fc920bc3ffdff209f2343def9ed31b56eea13c4245c4da" +checksum = "634c90f1cc0f9887680ca785b0b21aa961070b9465917bf65afaec56a6d005bb" dependencies = [ "proc-macro2", "quote", @@ -6456,7 +6542,7 @@ dependencies = [ [[package]] name = "reth-config" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "eyre", "humantime-serde", @@ -6472,7 +6558,7 @@ dependencies = [ [[package]] name = "reth-consensus" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -6485,10 +6571,10 @@ dependencies = [ [[package]] name = "reth-consensus-common" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "reth-chainspec", "reth-consensus", @@ -6498,10 +6584,10 @@ dependencies = [ [[package]] name = "reth-consensus-debug-client" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-json-rpc", "alloy-primitives", "alloy-provider", @@ -6511,7 +6597,7 @@ dependencies = [ "derive_more", "eyre", "futures", - "reqwest 0.13.2", + "reqwest 0.13.3", "reth-node-api", "reth-primitives-traits", "reth-tracing", @@ -6524,7 +6610,7 @@ dependencies = [ [[package]] name = "reth-db" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-primitives", "derive_more", @@ -6550,7 +6636,7 @@ dependencies = [ [[package]] name = "reth-db-api" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -6574,7 +6660,7 @@ dependencies = [ [[package]] name = "reth-db-common" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -6604,9 +6690,9 @@ dependencies = [ [[package]] name = "reth-db-models" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "bytes", "modular-bitfield", @@ -6618,7 +6704,7 @@ dependencies = [ [[package]] name = "reth-discv4" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -6643,7 +6729,7 @@ dependencies = [ [[package]] name = "reth-discv5" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -6667,7 +6753,7 @@ dependencies = [ [[package]] name = "reth-dns-discovery" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-primitives", "dashmap", @@ -6691,10 +6777,10 @@ dependencies = [ [[package]] name = "reth-downloaders" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "async-compression", @@ -6722,7 +6808,7 @@ dependencies = [ [[package]] name = "reth-ecies" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "aes", "alloy-primitives", @@ -6750,7 +6836,7 @@ dependencies = [ [[package]] name = "reth-engine-local" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -6773,10 +6859,10 @@ dependencies = [ [[package]] name = "reth-engine-primitives" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-engine", "auto_impl", @@ -6798,11 +6884,11 @@ dependencies = [ [[package]] name = "reth-engine-tree" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", "alloy-eip7928", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", "alloy-rlp", @@ -6810,6 +6896,7 @@ dependencies = [ "crossbeam-channel", "derive_more", "futures", + "indexmap 2.14.0", "metrics", "moka", "parking_lot", @@ -6849,7 +6936,7 @@ dependencies = [ [[package]] name = "reth-engine-util" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", "alloy-rpc-types-engine", @@ -6877,10 +6964,10 @@ dependencies = [ [[package]] name = "reth-era" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "ethereum_ssz", @@ -6892,13 +6979,13 @@ dependencies = [ [[package]] name = "reth-era-downloader" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-primitives", "bytes", "eyre", "futures-util", - "reqwest 0.13.2", + "reqwest 0.13.3", "reth-era", "reth-fs-util", "sha2", @@ -6908,7 +6995,7 @@ dependencies = [ [[package]] name = "reth-era-utils" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -6930,7 +7017,7 @@ dependencies = [ [[package]] name = "reth-errors" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "reth-consensus", "reth-execution-errors", @@ -6941,7 +7028,7 @@ dependencies = [ [[package]] name = "reth-eth-wire" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-chains", "alloy-primitives", @@ -6969,11 +7056,12 @@ dependencies = [ [[package]] name = "reth-eth-wire-types" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eip7928", + "alloy-eips 2.0.4", "alloy-hardforks", "alloy-primitives", "alloy-rlp", @@ -6990,9 +7078,9 @@ dependencies = [ [[package]] name = "reth-ethereum-engine-primitives" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-engine", "reth-engine-primitives", @@ -7006,7 +7094,7 @@ dependencies = [ [[package]] name = "reth-ethereum-forks" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-eip2124", "alloy-hardforks", @@ -7019,10 +7107,10 @@ dependencies = [ [[package]] name = "reth-ethereum-primitives" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-eth", "reth-codecs", @@ -7033,7 +7121,7 @@ dependencies = [ [[package]] name = "reth-etl" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "rayon", "reth-db-api", @@ -7043,10 +7131,10 @@ dependencies = [ [[package]] name = "reth-evm" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", "auto_impl", @@ -7067,10 +7155,10 @@ dependencies = [ [[package]] name = "reth-evm-ethereum" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", "alloy-rpc-types-engine", @@ -7087,7 +7175,7 @@ dependencies = [ [[package]] name = "reth-execution-cache" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-primitives", "fixed-cache", @@ -7105,7 +7193,7 @@ dependencies = [ [[package]] name = "reth-execution-errors" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-evm", "alloy-primitives", @@ -7118,10 +7206,10 @@ dependencies = [ [[package]] name = "reth-execution-types" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", "alloy-rlp", @@ -7137,10 +7225,10 @@ dependencies = [ [[package]] name = "reth-exex" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "eyre", "futures", @@ -7175,9 +7263,9 @@ dependencies = [ [[package]] name = "reth-exex-types" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "reth-chain-state", "reth-execution-types", @@ -7189,7 +7277,7 @@ dependencies = [ [[package]] name = "reth-fs-util" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "serde", "serde_json", @@ -7199,7 +7287,7 @@ dependencies = [ [[package]] name = "reth-invalid-block-hooks" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7227,7 +7315,7 @@ dependencies = [ [[package]] name = "reth-ipc" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "bytes", "futures", @@ -7247,7 +7335,7 @@ dependencies = [ [[package]] name = "reth-libmdbx" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "bitflags 2.11.1", "byteorder", @@ -7264,7 +7352,7 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "bindgen", "cc", @@ -7273,7 +7361,7 @@ dependencies = [ [[package]] name = "reth-metrics" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "futures", "metrics", @@ -7285,7 +7373,7 @@ dependencies = [ [[package]] name = "reth-net-banlist" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-primitives", "ipnet", @@ -7294,11 +7382,11 @@ dependencies = [ [[package]] name = "reth-net-nat" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "futures-util", "if-addrs", - "reqwest 0.13.2", + "reqwest 0.13.3", "serde_with", "thiserror 2.0.18", "tokio", @@ -7308,10 +7396,10 @@ dependencies = [ [[package]] name = "reth-network" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "aquamarine", @@ -7364,7 +7452,7 @@ dependencies = [ [[package]] name = "reth-network-api" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7389,10 +7477,10 @@ dependencies = [ [[package]] name = "reth-network-p2p" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "auto_impl", "derive_more", @@ -7411,7 +7499,7 @@ dependencies = [ [[package]] name = "reth-network-peers" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -7426,7 +7514,7 @@ dependencies = [ [[package]] name = "reth-network-types" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-eip2124", "humantime-serde", @@ -7440,7 +7528,7 @@ dependencies = [ [[package]] name = "reth-nippy-jar" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "anyhow", "bincode 1.3.3", @@ -7457,7 +7545,7 @@ dependencies = [ [[package]] name = "reth-node-api" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-rpc-types-engine", "eyre", @@ -7481,10 +7569,10 @@ dependencies = [ [[package]] name = "reth-node-builder" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-provider", "alloy-rpc-types", @@ -7549,10 +7637,10 @@ dependencies = [ [[package]] name = "reth-node-core" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-engine", "clap", @@ -7604,7 +7692,7 @@ dependencies = [ [[package]] name = "reth-node-ethstats" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7628,10 +7716,10 @@ dependencies = [ [[package]] name = "reth-node-events" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-engine", "derive_more", @@ -7652,7 +7740,7 @@ dependencies = [ [[package]] name = "reth-node-metrics" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "bytes", "eyre", @@ -7664,7 +7752,7 @@ dependencies = [ "metrics-process", "metrics-util", "procfs", - "reqwest 0.13.2", + "reqwest 0.13.3", "reth-metrics", "reth-tasks", "tokio", @@ -7675,7 +7763,7 @@ dependencies = [ [[package]] name = "reth-node-types" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "reth-chainspec", "reth-db-api", @@ -7687,11 +7775,11 @@ dependencies = [ [[package]] name = "reth-optimism-chainspec" version = "1.11.3" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-hardforks", "alloy-primitives", @@ -7715,10 +7803,10 @@ dependencies = [ [[package]] name = "reth-optimism-cli" version = "1.11.3" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "clap", @@ -7765,10 +7853,10 @@ dependencies = [ [[package]] name = "reth-optimism-consensus" version = "1.11.3" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-trie", "reth-chainspec", @@ -7790,10 +7878,10 @@ dependencies = [ [[package]] name = "reth-optimism-evm" version = "1.11.3" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-op-evm", "alloy-primitives", @@ -7819,10 +7907,10 @@ dependencies = [ [[package]] name = "reth-optimism-exex" version = "1.11.3" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "eyre", "futures-util", "reth-execution-types", @@ -7838,10 +7926,10 @@ dependencies = [ [[package]] name = "reth-optimism-flashblocks" version = "1.11.3" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types", "alloy-rpc-types-engine", @@ -7876,7 +7964,7 @@ dependencies = [ [[package]] name = "reth-optimism-forks" version = "1.11.3" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-op-hardforks", "alloy-primitives", @@ -7887,7 +7975,7 @@ dependencies = [ [[package]] name = "reth-optimism-node" version = "1.11.3" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7940,10 +8028,10 @@ dependencies = [ [[package]] name = "reth-optimism-payload-builder" version = "1.11.3" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", "alloy-rlp", @@ -7980,10 +8068,10 @@ dependencies = [ [[package]] name = "reth-optimism-primitives" version = "1.11.3" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "op-alloy-consensus", @@ -7995,10 +8083,10 @@ dependencies = [ [[package]] name = "reth-optimism-rpc" version = "1.11.3" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-json-rpc", "alloy-op-evm", "alloy-primitives", @@ -8007,7 +8095,7 @@ dependencies = [ "alloy-rpc-types-debug", "alloy-rpc-types-engine", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-transport", "alloy-transport-http", "async-trait", @@ -8024,7 +8112,7 @@ dependencies = [ "op-alloy-rpc-types", "op-alloy-rpc-types-engine", "op-revm", - "reqwest 0.13.2", + "reqwest 0.13.3", "reth-basic-payload-builder", "reth-chain-state", "reth-chainspec", @@ -8066,7 +8154,7 @@ dependencies = [ [[package]] name = "reth-optimism-storage" version = "1.11.3" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-consensus", "reth-optimism-primitives", @@ -8076,9 +8164,9 @@ dependencies = [ [[package]] name = "reth-optimism-trie" version = "1.11.3" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "auto_impl", "bincode 2.0.1", @@ -8109,15 +8197,15 @@ dependencies = [ [[package]] name = "reth-optimism-txpool" version = "1.11.3" -source = "git+https://github.com/ethereum-optimism/optimism.git?branch=develop#11b9948f67f2d53189f132ec48105096867f67ef" +source = "git+https://github.com/ethereum-optimism/optimism.git?tag=op-reth%2Fv2.2.0#484be19f69054f74bc736b5df229e896e338d9a3" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-json-rpc", "alloy-primitives", "alloy-rpc-client", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "c-kzg", "derive_more", "futures-util", @@ -8148,7 +8236,7 @@ dependencies = [ [[package]] name = "reth-payload-builder" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8172,7 +8260,7 @@ dependencies = [ [[package]] name = "reth-payload-builder-primitives" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "pin-project", "reth-payload-primitives", @@ -8184,10 +8272,10 @@ dependencies = [ [[package]] name = "reth-payload-primitives" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", @@ -8208,7 +8296,7 @@ dependencies = [ [[package]] name = "reth-payload-util" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8218,7 +8306,7 @@ dependencies = [ [[package]] name = "reth-payload-validator" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", "alloy-rpc-types-engine", @@ -8227,12 +8315,12 @@ dependencies = [ [[package]] name = "reth-primitives-traits" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96ffdb2ce0cdcd814d39428dc1e9b660c85d85e0b75eb465a1ed0943a48c7bd" +checksum = "8ee12e304adbacbb32248c9806ebafbe1e2811fbfefe53c5e5b710a8438b7ec0" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-primitives", "alloy-rlp", @@ -8258,10 +8346,10 @@ dependencies = [ [[package]] name = "reth-provider" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-primitives", "alloy-rpc-types-engine", @@ -8301,10 +8389,10 @@ dependencies = [ [[package]] name = "reth-prune" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "itertools 0.14.0", "metrics", @@ -8330,7 +8418,7 @@ dependencies = [ [[package]] name = "reth-prune-types" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-primitives", "derive_more", @@ -8345,7 +8433,7 @@ dependencies = [ [[package]] name = "reth-revm" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -8360,12 +8448,11 @@ dependencies = [ [[package]] name = "reth-rpc" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", "alloy-dyn-abi", - "alloy-eip7928", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-genesis", "alloy-network", @@ -8381,7 +8468,7 @@ dependencies = [ "alloy-rpc-types-mev", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer", "alloy-signer-local", "async-trait", @@ -8438,10 +8525,9 @@ dependencies = [ [[package]] name = "reth-rpc-api" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ - "alloy-eip7928", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-json-rpc", "alloy-primitives", @@ -8455,7 +8541,7 @@ dependencies = [ "alloy-rpc-types-mev", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "jsonrpsee", "reth-chain-state", "reth-engine-primitives", @@ -8469,7 +8555,7 @@ dependencies = [ [[package]] name = "reth-rpc-builder" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-network", "alloy-provider", @@ -8512,7 +8598,7 @@ dependencies = [ [[package]] name = "reth-rpc-convert" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", "alloy-evm", @@ -8532,9 +8618,9 @@ dependencies = [ [[package]] name = "reth-rpc-engine-api" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", @@ -8563,12 +8649,12 @@ dependencies = [ [[package]] name = "reth-rpc-eth-api" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", "alloy-dyn-abi", "alloy-eip7928", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-json-rpc", "alloy-network", @@ -8576,7 +8662,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types-eth", "alloy-rpc-types-mev", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "async-trait", "auto_impl", "dyn-clone", @@ -8609,10 +8695,10 @@ dependencies = [ [[package]] name = "reth-rpc-eth-types" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-network", "alloy-primitives", @@ -8627,7 +8713,7 @@ dependencies = [ "jsonrpsee-types", "metrics", "rand 0.9.4", - "reqwest 0.13.2", + "reqwest 0.13.3", "reth-chain-state", "reth-chainspec", "reth-errors", @@ -8657,7 +8743,7 @@ dependencies = [ [[package]] name = "reth-rpc-layer" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-rpc-types-engine", "http", @@ -8671,9 +8757,9 @@ dependencies = [ [[package]] name = "reth-rpc-server-types" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-engine", "jsonrpsee-core", @@ -8686,9 +8772,9 @@ dependencies = [ [[package]] name = "reth-rpc-traits" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c870f120b2e179e44906b7b288b0dea6577573010272adda8fff536e86fedd05" +checksum = "860fe223501a76ff14aa3bf164f739f31008c2a2905ac85708bfd88f042e6151" dependencies = [ "alloy-consensus", "alloy-network", @@ -8702,10 +8788,10 @@ dependencies = [ [[package]] name = "reth-stages" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "eyre", @@ -8714,7 +8800,7 @@ dependencies = [ "num-traits", "page_size", "rayon", - "reqwest 0.13.2", + "reqwest 0.13.3", "reth-chainspec", "reth-codecs", "reth-config", @@ -8751,9 +8837,9 @@ dependencies = [ [[package]] name = "reth-stages-api" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "aquamarine", "auto_impl", @@ -8779,7 +8865,7 @@ dependencies = [ [[package]] name = "reth-stages-types" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-primitives", "bytes", @@ -8792,7 +8878,7 @@ dependencies = [ [[package]] name = "reth-static-file" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-primitives", "parking_lot", @@ -8812,7 +8898,7 @@ dependencies = [ [[package]] name = "reth-static-file-types" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-primitives", "clap", @@ -8827,10 +8913,10 @@ dependencies = [ [[package]] name = "reth-storage-api" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-engine", "auto_impl", @@ -8851,9 +8937,9 @@ dependencies = [ [[package]] name = "reth-storage-errors" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "derive_more", @@ -8869,7 +8955,7 @@ dependencies = [ [[package]] name = "reth-tasks" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "crossbeam-utils", "dashmap", @@ -8890,7 +8976,7 @@ dependencies = [ [[package]] name = "reth-tokio-util" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "tokio", "tokio-stream", @@ -8900,7 +8986,7 @@ dependencies = [ [[package]] name = "reth-tracing" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "clap", "eyre", @@ -8916,7 +9002,7 @@ dependencies = [ [[package]] name = "reth-tracing-otlp" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "clap", "eyre", @@ -8933,10 +9019,10 @@ dependencies = [ [[package]] name = "reth-transaction-pool" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "aquamarine", @@ -8976,10 +9062,10 @@ dependencies = [ [[package]] name = "reth-trie" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "alloy-trie", @@ -9001,13 +9087,13 @@ dependencies = [ [[package]] name = "reth-trie-common" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-consensus", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-trie", "arrayvec", "bytes", @@ -9025,7 +9111,7 @@ dependencies = [ [[package]] name = "reth-trie-db" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-primitives", "metrics", @@ -9045,7 +9131,7 @@ dependencies = [ [[package]] name = "reth-trie-parallel" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-eip7928", "alloy-evm", @@ -9073,7 +9159,7 @@ dependencies = [ [[package]] name = "reth-trie-sparse" version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=552d896f9c4b75201def55969d3c23bcc990dd80#552d896f9c4b75201def55969d3c23bcc990dd80" +source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -9094,18 +9180,18 @@ dependencies = [ [[package]] name = "reth-zstd-compressors" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3882441cf1d51fe24dfc6df9919e6f17edfdb6b121050bd34348e925e61c7af1" +checksum = "c12fafa33d2f420a9d39249a3e0357b1928d09429f30758b85280409092873b2" dependencies = [ "zstd", ] [[package]] name = "revm" -version = "36.0.0" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0abc15d09cd211e9e73410ada10134069c794d4bcdb787dfc16a1bf0939849c" +checksum = "91202d39dbe8e8d10e9e8f2b76c30da68ecd1d25be69ba6d853ad0d03a3a398a" dependencies = [ "revm-bytecode", "revm-context", @@ -9122,9 +9208,9 @@ dependencies = [ [[package]] name = "revm-bytecode" -version = "9.0.0" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86e468df3cf5cf59fa7ef71a3e9ccabb76bb336401ea2c0674f563104cf3c5e" +checksum = "bdbb3a3d735efa94c91f2ef6bf20a35f99a77bc78f3e25bd758336901bdf9661" dependencies = [ "bitvec", "phf", @@ -9134,9 +9220,9 @@ dependencies = [ [[package]] name = "revm-context" -version = "15.0.0" +version = "16.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eb1f0a76b14d684a444fc52f7bf6b7564bf882599d91ee62e76d602e7a187c7" +checksum = "c5f68d928d8b228e0faeb1c6ed75c4fde7d124f1ddf9119b67e7a0ad4041237d" dependencies = [ "bitvec", "cfg-if", @@ -9151,9 +9237,9 @@ dependencies = [ [[package]] name = "revm-context-interface" -version = "16.0.0" +version = "17.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc256b27743e2912ca16899568e6652a372eb5d1d573e6edb16c7836b16cf487" +checksum = "1f3758e6167c4ba7a59a689c519a047edaefcd4c37d74f279b93ed87bc8aece4" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -9167,9 +9253,9 @@ dependencies = [ [[package]] name = "revm-database" -version = "12.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c0a7d6da41061f2c50f99a2632571026b23684b5449ff319914151f4449b6c8" +checksum = "c281a1f11d3bcb8c0bba1199ed6bcb001d1aeb3d4fb366819e14f88723989a4e" dependencies = [ "alloy-eips 1.8.3", "revm-bytecode", @@ -9181,9 +9267,9 @@ dependencies = [ [[package]] name = "revm-database-interface" -version = "10.0.0" +version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd497a38a79057b94a049552cb1f925ad15078bc1a479c132aeeebd1d2ccc768" +checksum = "d89efb9832a4e3742bb4ded5f7fe5bf905e8860e69427d4dfec153484fc6d304" dependencies = [ "auto_impl", "either", @@ -9195,9 +9281,9 @@ dependencies = [ [[package]] name = "revm-handler" -version = "17.0.0" +version = "18.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1eed729ca9b228ae98688f352235871e9b8be3d568d488e4070f64c56e9d3d" +checksum = "783e903d6922b7f5f9a940d1bb229530502d2924b1aed9d5ca5a94ebf065d460" dependencies = [ "auto_impl", "derive-where", @@ -9214,9 +9300,9 @@ dependencies = [ [[package]] name = "revm-inspector" -version = "17.0.0" +version = "19.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf5102391706513689f91cb3cb3d97b5f13a02e8647e6e9cb7620877ef84847" +checksum = "8216ad58422090d0daa9eb430e0a081f7ad07e7fd30681dee71f8420c99624e0" dependencies = [ "auto_impl", "either", @@ -9232,9 +9318,9 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.37.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac49e53897c4cc59dbd7a7bb097386755a4dfa2bc7088b298f341b5dfcda6f2c" +checksum = "731b682530a732ef9c189ef831589128e2ce34d4a306c956322ae2dffe009715" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -9250,9 +9336,9 @@ dependencies = [ [[package]] name = "revm-interpreter" -version = "34.0.0" +version = "35.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf22f80612bb8f58fd1f578750281f2afadb6c93835b14ae6a4d6b75ca26f445" +checksum = "1ece9f41b69658c15d748288a4dbdfc06a63f3ce93d983af440de3f1631dce6a" dependencies = [ "revm-bytecode", "revm-context-interface", @@ -9263,9 +9349,9 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "32.1.0" +version = "34.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ec11f45deec71e4945e1809736bb20d454285f9167ab53c5159dae1deb603f" +checksum = "a346a8cc6c8c39bd65306641c692191299c0a7b63d38810e39e8fe9b92378660" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -9279,6 +9365,7 @@ dependencies = [ "cfg-if", "k256", "p256", + "revm-context-interface", "revm-primitives", "ripemd", "secp256k1 0.31.1", @@ -9287,9 +9374,9 @@ dependencies = [ [[package]] name = "revm-primitives" -version = "22.1.0" +version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfb5ce6cf18b118932bcdb7da05cd9c250f2cb9f64131396b55f3fe3537c35" +checksum = "0c99bda77d9661521ba0b4bc04558c6692074f01e65dd420fa3b893033d9b8a2" dependencies = [ "alloy-primitives", "num_enum", @@ -9299,9 +9386,9 @@ dependencies = [ [[package]] name = "revm-state" -version = "10.0.0" +version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29404707763da607e5d6e4771cb203998c28159279c2f64cc32de08d2814651" +checksum = "c32490ed687dba31c3c882beb8c20408bdd30ef96690d8f145b0ee9a87040bfe" dependencies = [ "alloy-eip7928", "bitflags 2.11.1", @@ -9389,9 +9476,9 @@ dependencies = [ [[package]] name = "roaring" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba9ce64a8f45d7fc86358410bb1a82e8c987504c0d4900e9141d69a9f26c885" +checksum = "1dedc5658c6ecb3bdb5ef5f3295bb9253f42dcf3fd1402c03f6b1f7659c3c4a9" dependencies = [ "bytemuck", "byteorder", @@ -9499,14 +9586,14 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.38" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "aws-lc-rs", "log", @@ -9532,9 +9619,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "web-time", "zeroize", @@ -9548,7 +9635,7 @@ checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" dependencies = [ "core-foundation", "core-foundation-sys", - "jni", + "jni 0.21.1", "log", "once_cell", "rustls", @@ -9563,13 +9650,13 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ "core-foundation", "core-foundation-sys", - "jni", + "jni 0.22.4", "log", "once_cell", "rustls", @@ -9579,7 +9666,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs 1.0.7", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -9677,6 +9764,12 @@ dependencies = [ "hashbrown 0.13.2", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -10012,6 +10105,22 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version 0.4.1", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "simple_asn1" version = "0.6.4" @@ -10265,7 +10374,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -10380,7 +10489,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", - "serde_core", "zerovec", ] @@ -10795,9 +10903,11 @@ dependencies = [ "serde", "serde_json", "sharded-slab", + "smallvec", "thread_local", "tracing", "tracing-core", + "tracing-log", "tracing-serde", ] @@ -11135,9 +11245,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ "cfg-if", "once_cell", @@ -11148,9 +11258,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.68" +version = "0.4.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" dependencies = [ "js-sys", "wasm-bindgen", @@ -11158,9 +11268,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -11168,9 +11278,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ "bumpalo", "proc-macro2", @@ -11181,9 +11291,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ "unicode-ident", ] @@ -11251,9 +11361,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.95" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" dependencies = [ "js-sys", "wasm-bindgen", @@ -11333,7 +11443,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -12038,7 +12148,6 @@ dependencies = [ "displaydoc", "yoke", "zerofrom", - "zerovec", ] [[package]] @@ -12047,7 +12156,6 @@ version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ - "serde", "yoke", "zerofrom", "zerovec-derive", diff --git a/Cargo.toml b/Cargo.toml index 24519bb..0df2c94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,20 +14,23 @@ readme = "README.md" keywords = ["ethereum", "reth", "optimism", "arkiv"] [workspace.dependencies] -# Op-Reth (from ethereum-optimism monorepo) -reth-optimism-node = { git = "https://github.com/ethereum-optimism/optimism.git", branch = "develop" } -reth-optimism-cli = { git = "https://github.com/ethereum-optimism/optimism.git", branch = "develop" } -reth-optimism-chainspec = { git = "https://github.com/ethereum-optimism/optimism.git", branch = "develop" } -reth-optimism-primitives = { git = "https://github.com/ethereum-optimism/optimism.git", branch = "develop" } +# Op-Reth (from ethereum-optimism monorepo, pinned to op-reth/v2.2.0) +reth-optimism-node = { git = "https://github.com/ethereum-optimism/optimism.git", tag = "op-reth/v2.2.0" } +reth-optimism-cli = { git = "https://github.com/ethereum-optimism/optimism.git", tag = "op-reth/v2.2.0" } +reth-optimism-chainspec = { git = "https://github.com/ethereum-optimism/optimism.git", tag = "op-reth/v2.2.0" } +reth-optimism-primitives = { git = "https://github.com/ethereum-optimism/optimism.git", tag = "op-reth/v2.2.0" } -# Reth (pinned to same rev as op-reth) -reth-exex = { git = "https://github.com/paradigmxyz/reth", rev = "552d896f9c4b75201def55969d3c23bcc990dd80" } -reth-node-api = { git = "https://github.com/paradigmxyz/reth", rev = "552d896f9c4b75201def55969d3c23bcc990dd80" } -reth-execution-types = { git = "https://github.com/paradigmxyz/reth", rev = "552d896f9c4b75201def55969d3c23bcc990dd80" } -reth-storage-api = { git = "https://github.com/paradigmxyz/reth", rev = "552d896f9c4b75201def55969d3c23bcc990dd80" } +# Reth (pinned to same rev as op-reth/v2.2.0) +reth-exex = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } +reth-evm = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } +reth-node-api = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } +reth-node-builder = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } +reth-execution-types = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } +reth-primitives-traits = "0.3" +reth-storage-api = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } # Arkiv bindings (from arkiv-contracts repo) -arkiv-bindings = { git = "https://github.com/Arkiv-Network/arkiv-contracts.git", rev = "ff30cab" } +arkiv-bindings = { git = "https://github.com/Arkiv-Network/arkiv-contracts.git", rev = "d4bf0d59" } # Arkiv genesis primitives (in-tree) arkiv-genesis = { path = "crates/arkiv-genesis" } @@ -42,9 +45,27 @@ alloy-signer-local = { version = "2.0", features = ["mnemonic"] } alloy-network = "2.0" alloy-contract = "2.0" alloy-rpc-types = "2.0" +alloy-rpc-types-engine = "2.0" +alloy-rpc-types-eth = "2.0" +alloy-eips = "2.0" +alloy-hardforks = "0.4" -# EVM -revm = { version = "36.0.0", default-features = false, features = ["std"] } +# EVM. Pinned to `38.0.0` to match what `alloy-op-evm` / `op-revm` / +# `revm-precompile 34` pull in transitively. Earlier this workspace had +# `revm = "36.0.0"` (used only by `arkiv-genesis`), which silently +# produced parallel `revm 36` and `revm 38` graphs and meant types from +# the two were incompatible. Keeping a single version simplifies the +# precompile wiring — see `docs/custom-precompile.md` §9.E. +revm = { version = "38.0.0", default-features = false, features = ["std"] } + +# Custom-precompile wiring (POC). +# alloy-op-evm must come from the optimism git tag, not crates.io, so its +# `revm 38` line aligns with op-revm/op-reth. +alloy-evm = { version = "0.33", default-features = false, features = ["std"] } +alloy-op-evm = { git = "https://github.com/ethereum-optimism/optimism.git", tag = "op-reth/v2.2.0", default-features = false, features = ["std"] } +op-alloy-consensus = { git = "https://github.com/ethereum-optimism/optimism.git", tag = "op-reth/v2.2.0", default-features = false, features = ["std"] } +op-revm = { git = "https://github.com/ethereum-optimism/optimism.git", tag = "op-reth/v2.2.0", default-features = false, features = ["std"] } +revm-precompile = { version = "34", default-features = false, features = ["std"] } # Util clap = { version = "4", features = ["derive"] } diff --git a/README.md b/README.md index b0f5be9..a683e1d 100644 --- a/README.md +++ b/README.md @@ -192,8 +192,9 @@ Working today: - ExEx detects the predeploy by chainspec content and activates on explicit operator opt-in (`--arkiv.db-url` or `--arkiv.debug`). - ExEx → EntityDB JSON-RPC v2 wire format is complete and documented. -- `arkiv_query` JSON-RPC proxy method (registered when `--arkiv.db-url` - is set; transparent passthrough to EntityDB). +- `arkiv_*` JSON-RPC namespace (registered when `--arkiv.db-url` is set; + transparent passthrough to EntityDB). Currently: `arkiv_query`, + `arkiv_getEntityCount`, `arkiv_getBlockTiming`. - Operator CLI covers all six entity-operation types plus batched submission with cross-references between ops. - Storage backends: `LoggingStore` (tracing) and `JsonRpcStore` diff --git a/crates/arkiv-node/Cargo.toml b/crates/arkiv-node/Cargo.toml index 911f6dc..f720ddf 100644 --- a/crates/arkiv-node/Cargo.toml +++ b/crates/arkiv-node/Cargo.toml @@ -5,6 +5,9 @@ edition.workspace = true rust-version.workspace = true license.workspace = true +[lib] +path = "src/lib.rs" + [[bin]] name = "arkiv-node" path = "src/main.rs" @@ -16,16 +19,29 @@ reth-optimism-chainspec.workspace = true reth-optimism-primitives.workspace = true reth-exex.workspace = true +reth-evm.workspace = true reth-node-api.workspace = true +reth-node-builder.workspace = true reth-execution-types.workspace = true +reth-primitives-traits.workspace = true reth-storage-api.workspace = true arkiv-bindings.workspace = true arkiv-genesis.workspace = true alloy-primitives.workspace = true alloy-consensus.workspace = true +alloy-eips.workspace = true +alloy-hardforks.workspace = true +alloy-rpc-types-engine.workspace = true +alloy-rpc-types-eth.workspace = true alloy-sol-types.workspace = true +alloy-evm.workspace = true +alloy-op-evm.workspace = true +op-alloy-consensus.workspace = true +op-revm.workspace = true +revm-precompile.workspace = true + clap.workspace = true eyre.workspace = true reqwest.workspace = true diff --git a/crates/arkiv-node/src/cli.rs b/crates/arkiv-node/src/cli.rs new file mode 100644 index 0000000..5e0b092 --- /dev/null +++ b/crates/arkiv-node/src/cli.rs @@ -0,0 +1,30 @@ +//! Arkiv-specific clap args. Designed to be `#[command(flatten)]`-ed into +//! a host CLI so downstream binaries can compose Arkiv onto their own +//! argument surface. + +use reth_optimism_node::args::RollupArgs; + +/// CLI extension over [`RollupArgs`]. Adds Arkiv-specific flags. +#[derive(Debug, clap::Args)] +pub struct ArkivExt { + /// EntityDB JSON-RPC URL. On an Arkiv chainspec, enables the ExEx + /// (forwarding to EntityDB) and the `arkiv_query` JSON-RPC method. + #[arg(long = "arkiv.db-url", env = "ARKIV_ENTITYDB_URL")] + pub arkiv_db_url: Option, + + /// Debug mode: run the ExEx with the in-process `LoggingStore` backend + /// (decoded ops are emitted as tracing events). Useful for local dev + /// without a running EntityDB. The `arkiv_*` RPC namespace is not + /// installed in this mode. + #[arg(long = "arkiv.debug", conflicts_with = "arkiv_db_url")] + pub arkiv_debug: bool, + + /// Install the Arkiv EntityDB-write precompile (POC). Requires + /// `--arkiv.db-url`; the precompile reuses the same EntityDB client. + /// Off by default; the precompile is independent of the ExEx. + #[arg(long = "arkiv.precompile", requires = "arkiv_db_url")] + pub arkiv_precompile: bool, + + #[command(flatten)] + pub rollup: RollupArgs, +} diff --git a/crates/arkiv-node/src/genesis.rs b/crates/arkiv-node/src/genesis.rs new file mode 100644 index 0000000..37eb0f9 --- /dev/null +++ b/crates/arkiv-node/src/genesis.rs @@ -0,0 +1,29 @@ +//! Predeploy detection for Arkiv chainspecs. + +use alloy_primitives::keccak256; +use reth_optimism_chainspec::OpChainSpec; + +/// Returns `true` iff the chainspec's genesis alloc contains the Arkiv +/// EntityRegistry predeploy at the canonical address with bytecode that +/// matches the runtime form for this chain's chain_id. +/// +/// The bytecode hash check (rather than mere address presence) guards +/// against squatting at `0x44…0044` with unrelated code. +pub fn has_arkiv_predeploy(chain: &OpChainSpec) -> bool { + let chain_id = chain.inner.chain.id(); + let Some(account) = chain + .inner + .genesis + .alloc + .get(&arkiv_genesis::ENTITY_REGISTRY_ADDRESS) + else { + return false; + }; + let Some(code) = &account.code else { + return false; + }; + let Ok(expected) = arkiv_genesis::deploy_creation_code(chain_id) else { + return false; + }; + keccak256(code) == keccak256(&expected) +} diff --git a/crates/arkiv-node/src/install.rs b/crates/arkiv-node/src/install.rs new file mode 100644 index 0000000..00152d0 --- /dev/null +++ b/crates/arkiv-node/src/install.rs @@ -0,0 +1,150 @@ +//! Mode resolution + installation of Arkiv onto an op-stack node builder. +//! +//! The split is deliberate: +//! +//! - [`resolve_mode`] is pure validation + a network health check. No +//! reth/builder generics. Embedders can call it directly, or skip it +//! entirely and construct an [`ArkivMode`] themselves. +//! - [`install`] is the only function that touches `reth-node-builder` +//! generics. It mirrors op-reth's `launch_node_with_proof_history` +//! pattern: take the post-`.node()` builder, call `install_exex` and +//! (conditionally) `extend_rpc_modules`, return the builder. + +use std::sync::Arc; + +use eyre::{Result, WrapErr, bail}; +use reth_node_builder::{ + FullNodeTypes, NodeAdapter, NodeBuilderWithComponents, NodeComponentsBuilder, NodeTypes, + WithLaunchContext, rpc::RethRpcAddOns, +}; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_primitives::OpPrimitives; + +use crate::exex; +use crate::genesis::has_arkiv_predeploy; +use crate::rpc::{ArkivApiServer, ArkivRpc}; +use crate::storage::{EntityDbClient, JsonRpcStore, Storage, logging::LoggingStore}; + +/// Resolved Arkiv configuration. Decouples "what was decided" from how it +/// was decided (CLI flags, programmatic config, …). +#[derive(Clone)] +pub enum ArkivMode { + /// No Arkiv extensions; behave as plain op-reth. + Disabled, + /// In-process [`LoggingStore`] backend; no RPC namespace. + Debug, + /// Forward to EntityDB; install `arkiv_*` RPC. + EntityDb { client: Arc }, +} + +/// Extract the EntityDB client to bind to the custom precompile, if and +/// only if the `--arkiv.precompile` flag is set. Mirrors the clap +/// `requires = "arkiv_db_url"` rule: if the flag is set we expect to be +/// in [`ArkivMode::EntityDb`]. +pub fn precompile_client( + mode: &ArkivMode, + enabled: bool, +) -> Result>> { + if !enabled { + tracing::info!("Arkiv precompile: disabled (pass --arkiv.precompile to enable)"); + return Ok(None); + } + match mode { + ArkivMode::EntityDb { client } => { + tracing::info!( + address = %crate::precompile::ARKIV_PRECOMPILE_ADDRESS, + "Arkiv precompile: enabled; calls will forward to EntityDB" + ); + Ok(Some(client.clone())) + } + _ => bail!("--arkiv.precompile requires --arkiv.db-url"), + } +} + +/// Validate the given Arkiv flags against the loaded chainspec and, in +/// the EntityDB case, run a health check. Mirrors the original `match` in +/// `main` 1:1; only the shape is different. +pub async fn resolve_mode( + arkiv_db_url: Option, + arkiv_debug: bool, + chain: &OpChainSpec, +) -> Result { + let predeploy = has_arkiv_predeploy(chain); + + match (predeploy, arkiv_db_url, arkiv_debug) { + (false, None, false) => { + tracing::info!("EntityRegistry predeploy not detected; running as plain op-reth"); + Ok(ArkivMode::Disabled) + } + (false, _, _) => { + bail!( + "Arkiv flags set but the loaded chainspec does not contain the \ + EntityRegistry predeploy at {}", + arkiv_genesis::ENTITY_REGISTRY_ADDRESS, + ); + } + (true, None, false) => { + bail!( + "EntityRegistry predeploy detected; either --arkiv.db-url (or \ + ARKIV_ENTITYDB_URL) or --arkiv.debug is required", + ); + } + (true, None, true) => { + tracing::info!("Arkiv: predeploy detected; installing ExEx with LoggingStore (debug)"); + Ok(ArkivMode::Debug) + } + (true, Some(url), false) => { + let client = Arc::new(EntityDbClient::new(url.clone())); + client + .health_check() + .await + .wrap_err_with(|| format!("EntityDB unreachable at {url}"))?; + tracing::info!(%url, "Arkiv: predeploy + EntityDB OK; installing ExEx + arkiv_* RPC"); + Ok(ArkivMode::EntityDb { client }) + } + (true, Some(_), true) => { + // Mirrors `clap::conflicts_with` for callers that bypass clap. + bail!("--arkiv.db-url and --arkiv.debug are mutually exclusive"); + } + } +} + +/// Install the Arkiv ExEx (and, in [`ArkivMode::EntityDb`], the `arkiv_*` +/// RPC namespace) on an op-stack node builder. No-op for +/// [`ArkivMode::Disabled`]. +/// +/// The bounds match what the underlying `install_exex` / +/// `extend_rpc_modules` calls require, plus `Primitives = OpPrimitives` +/// (the ExEx assumes op-stack primitives). +pub fn install( + node: WithLaunchContext>, + mode: ArkivMode, +) -> WithLaunchContext> +where + T: FullNodeTypes, + T::Types: NodeTypes, + CB: NodeComponentsBuilder, + AO: RethRpcAddOns>, +{ + match mode { + ArkivMode::Disabled => node, + ArkivMode::Debug => { + let store: Arc = Arc::new(LoggingStore::new()); + node.install_exex("arkiv", move |ctx| async move { + Ok(exex::arkiv_exex(ctx, store)) + }) + } + ArkivMode::EntityDb { client } => { + let store: Arc = Arc::new(JsonRpcStore::from_client(client.clone())); + let rpc_client = client; + node.install_exex("arkiv", move |ctx| async move { + Ok(exex::arkiv_exex(ctx, store)) + }) + .extend_rpc_modules(move |ctx| { + ctx.modules + .merge_configured(ArkivRpc::new(rpc_client).into_rpc())?; + Ok(()) + }) + } + } +} diff --git a/crates/arkiv-node/src/lib.rs b/crates/arkiv-node/src/lib.rs new file mode 100644 index 0000000..6aa4d2a --- /dev/null +++ b/crates/arkiv-node/src/lib.rs @@ -0,0 +1,29 @@ +//! Arkiv node library. +//! +//! This crate exposes the building blocks the `arkiv-node` binary uses to +//! turn an op-stack node builder into an Arkiv node: +//! +//! - [`ArkivExt`] — clap args (`--arkiv.db-url`, `--arkiv.debug`). +//! - [`ArkivMode`] — resolved configuration (off / debug / EntityDB). +//! - [`resolve_mode`] — validates flags against the loaded chainspec and +//! performs the EntityDB health check. Returns an [`ArkivMode`]. +//! - [`install`] — wires the ExEx (and the `arkiv_*` RPC namespace, when +//! applicable) onto an op-stack [`NodeBuilderWithComponents`]. +//! - [`has_arkiv_predeploy`] — bytecode-equality check for the +//! EntityRegistry predeploy in a chainspec's genesis alloc. +//! +//! Consumers compose these the same way op-reth's +//! `launch_node_with_proof_history` composes its ExEx onto `OpNode`. + +pub mod exex; +pub mod precompile; +pub mod rpc; +pub mod storage; + +mod cli; +mod genesis; +mod install; + +pub use cli::ArkivExt; +pub use genesis::has_arkiv_predeploy; +pub use install::{ArkivMode, install, precompile_client, resolve_mode}; diff --git a/crates/arkiv-node/src/main.rs b/crates/arkiv-node/src/main.rs index abd194c..f08042d 100644 --- a/crates/arkiv-node/src/main.rs +++ b/crates/arkiv-node/src/main.rs @@ -1,117 +1,18 @@ -mod exex; -mod rpc; -mod storage; - -use alloy_primitives::keccak256; +use arkiv_node::{ + ArkivExt, install, precompile::ArkivOpNode, precompile_client, resolve_mode, +}; use clap::Parser; -use eyre::{Result, WrapErr, bail}; -use reth_optimism_chainspec::OpChainSpec; +use eyre::Result; use reth_optimism_cli::{Cli, chainspec::OpChainSpecParser}; -use reth_optimism_node::{OpNode, args::RollupArgs}; -use std::sync::Arc; - -use crate::rpc::{ArkivApiServer, ArkivRpc}; -use crate::storage::{EntityDbClient, JsonRpcStore, Storage, logging::LoggingStore}; - -/// CLI extension over [`RollupArgs`]. Adds Arkiv-specific flags. -#[derive(Debug, clap::Args)] -struct ArkivExt { - /// EntityDB JSON-RPC URL. On an Arkiv chainspec, enables the ExEx - /// (forwarding to EntityDB) and the `arkiv_query` JSON-RPC method. - #[arg(long = "arkiv.db-url", env = "ARKIV_ENTITYDB_URL")] - arkiv_db_url: Option, - - /// Debug mode: run the ExEx with the in-process `LoggingStore` backend - /// (decoded ops are emitted as tracing events). Useful for local dev - /// without a running EntityDB. The `arkiv_*` RPC namespace is not - /// installed in this mode. - #[arg(long = "arkiv.debug", conflicts_with = "arkiv_db_url")] - arkiv_debug: bool, - - #[command(flatten)] - rollup: RollupArgs, -} +use reth_optimism_node::OpNode; fn main() -> Result<()> { Cli::::parse().run(|builder, ext| async move { - let predeploy = has_arkiv_predeploy(&builder.config().chain); - let mut node = builder.node(OpNode::new(ext.rollup)); - - // clap's `conflicts_with` rejects --arkiv.db-url + --arkiv.debug at parse time. - match (predeploy, ext.arkiv_db_url, ext.arkiv_debug) { - (false, None, false) => { - tracing::info!("EntityRegistry predeploy not detected; running as plain op-reth"); - } - (false, _, _) => { - bail!( - "Arkiv flags set but the loaded chainspec does not contain the \ - EntityRegistry predeploy at {}", - arkiv_genesis::ENTITY_REGISTRY_ADDRESS, - ); - } - (true, None, false) => { - bail!( - "EntityRegistry predeploy detected; either --arkiv.db-url (or \ - ARKIV_ENTITYDB_URL) or --arkiv.debug is required", - ); - } - (true, None, true) => { - tracing::info!("Arkiv: predeploy detected; installing ExEx with LoggingStore (debug)"); - let store: Arc = Arc::new(LoggingStore::new()); - node = node.install_exex("arkiv", move |ctx| async move { - Ok(exex::arkiv_exex(ctx, store)) - }); - } - (true, Some(url), false) => { - let client = Arc::new(EntityDbClient::new(url.clone())); - client - .health_check() - .await - .wrap_err_with(|| format!("EntityDB unreachable at {url}"))?; - tracing::info!(%url, "Arkiv: predeploy + EntityDB OK; installing ExEx + arkiv_* RPC"); - - let store: Arc = Arc::new(JsonRpcStore::from_client(client.clone())); - let rpc_client = client.clone(); - - node = node - .install_exex("arkiv", move |ctx| async move { - Ok(exex::arkiv_exex(ctx, store)) - }) - .extend_rpc_modules(move |ctx| { - ctx.modules - .merge_configured(ArkivRpc::new(rpc_client).into_rpc())?; - Ok(()) - }); - } - (true, Some(_), true) => unreachable!("clap conflicts_with rejects this combination"), - } - + let mode = resolve_mode(ext.arkiv_db_url, ext.arkiv_debug, &builder.config().chain).await?; + let pc_client = precompile_client(&mode, ext.arkiv_precompile)?; + let arkiv_node = ArkivOpNode::new(OpNode::new(ext.rollup), pc_client); + let node = install(builder.node(arkiv_node), mode); let handle = node.launch_with_debug_capabilities().await?; handle.wait_for_node_exit().await }) } - -/// Returns `true` iff the chainspec's genesis alloc contains the Arkiv -/// EntityRegistry predeploy at the canonical address with bytecode that -/// matches the runtime form for this chain's chain_id. -/// -/// The bytecode hash check (rather than mere address presence) guards -/// against squatting at `0x44…0044` with unrelated code. -fn has_arkiv_predeploy(chain: &OpChainSpec) -> bool { - let chain_id = chain.inner.chain.id(); - let Some(account) = chain - .inner - .genesis - .alloc - .get(&arkiv_genesis::ENTITY_REGISTRY_ADDRESS) - else { - return false; - }; - let Some(code) = &account.code else { - return false; - }; - let Ok(expected) = arkiv_genesis::deploy_creation_code(chain_id) else { - return false; - }; - keccak256(code) == keccak256(&expected) -} diff --git a/crates/arkiv-node/src/precompile.rs b/crates/arkiv-node/src/precompile.rs new file mode 100644 index 0000000..9ffd881 --- /dev/null +++ b/crates/arkiv-node/src/precompile.rs @@ -0,0 +1,517 @@ +//! Arkiv EntityDB-write custom precompile (POC). +//! +//! Installs a precompile at [`ARKIV_PRECOMPILE_ADDRESS`] that, on every +//! call, synchronously POSTs the calldata to EntityDB via JSON-RPC +//! (`arkiv_precompileWrite`) and returns the response's `stateRoot` +//! (32 bytes) as the precompile's return data. +//! +//! # Consensus assumption +//! +//! A precompile is part of the state-transition function. This module +//! only makes sense if EntityDB is itself deterministic — every node +//! executing the same transaction must hit an EntityDB that returns the +//! same `stateRoot`, byte-for-byte. The repo's `mock-entitydb.js` +//! always returns zero, which is fine for a single dev node. Multi-node +//! determinism is out of scope here. +//! +//! # Activation +//! +//! Active iff both `--arkiv.db-url` and `--arkiv.precompile` are set +//! (see `crate::cli` and `main.rs`). When inactive, the address has no +//! precompile installed and behaves like a regular EOA. +//! +//! # Wiring +//! +//! - [`entitydb_write_precompile`] — the [`DynPrecompile`] itself. +//! - [`ArkivOpEvmFactory`] — wraps the default OP `EvmFactory` and +//! installs the precompile in `create_evm` / +//! `create_evm_with_inspector` (both code paths must agree, otherwise +//! tracing diverges from execution). +//! - [`ArkivOpEvmConfig`] — a local newtype around `OpEvmConfig` so we +//! can satisfy the orphan rule when implementing +//! `ConfigureEngineEvm` (upstream only impls it for the +//! default `OpEvmFactory` variant of `OpEvmConfig`, so our +//! custom-factory variant gets no impl from the open OP crates). +//! `ConfigureEvm` is a thin passthrough to the inner `OpEvmConfig`. +//! - [`ArkivOpExecutorBuilder`] — replaces `OpExecutorBuilder` in the +//! node's component bundle so the factory is the one used at runtime. + +use std::sync::Arc; + +// Note: `revm` types are imported via `alloy_evm::revm` (which re-exports +// the matching `revm 38` line). The workspace also has `revm 36.0.0` in +// scope (used by `arkiv-genesis`); going through the alloy_evm re-export +// guarantees we get the version that matches `alloy-op-evm` and avoids +// silent type mismatches between the two `revm` versions. +use alloy_evm::{ + Database, Evm, EvmEnv, EvmFactory, + precompiles::{DynPrecompile, PrecompileInput, PrecompilesMap}, + revm::{Inspector, context::BlockEnv, context_interface::result::EVMError, inspector::NoOpInspector}, +}; +use alloy_op_evm::{OpBlockExecutorFactory, OpEvm, OpEvmContext, OpEvmFactory, OpTxError}; +use alloy_primitives::{Address, B256, Bytes, address, hex}; +use op_revm::{OpHaltReason, OpSpecId}; +use alloy_consensus::Header; +use alloy_eips::eip1559::BaseFeeParams; +use alloy_hardforks::EthereumHardforks; +use op_alloy_consensus::EIP1559ParamError; +use reth_evm::{EvmEnvFor, ExecutionCtxFor}; +use reth_node_api::PayloadAttributesBuilder; +use reth_node_builder::{ + BuilderContext, ConfigureEngineEvm, ConfigureEvm, DebugNode, FullNodeComponents, + FullNodeTypes, Node, NodeAdapter, NodeComponentsBuilder, NodeTypes, + components::{BasicPayloadServiceBuilder, ComponentsBuilder, ExecutorBuilder}, + rpc::BasicEngineValidatorBuilder, +}; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_primitives::OpBlock; +use reth_primitives_traits::{SealedBlock, SealedHeader}; +use reth_optimism_node::{ + OpAddOns, OpBlockAssembler, OpEngineApiBuilder, OpEngineTypes, OpEvmConfig, + OpNextBlockEnvAttributes, OpNode, OpRethReceiptBuilder, OpStorage, OpTx, + // The node-local `OpPayloadBuilder` (vs the 3-generic one re-exported at the + // crate root from `reth_optimism_payload_builder`). + node::{ + OpConsensusBuilder, OpEngineValidatorBuilder, OpNetworkBuilder, OpPayloadBuilder, + OpPoolBuilder, + }, + payload::{OpExecData, OpPayloadAttributes, OpPayloadAttrs}, + rpc::OpEthApiBuilder, +}; +use reth_optimism_primitives::OpPrimitives; +use revm_precompile::{ + PrecompileError, PrecompileHalt, PrecompileId, PrecompileOutput, PrecompileResult, +}; +use serde::Deserialize; +use serde_json::json; + +use crate::storage::EntityDbClient; + +/// Fixed address. Outside the reserved precompile range (`0x01..=0x11`) +/// and distinct from the `EntityRegistry` predeploy at `0x44…0044`. +pub const ARKIV_PRECOMPILE_ADDRESS: Address = + address!("0x000000000000000000000000000000000000aa01"); + +/// Flat gas cost. Order-of-magnitude DoS defence; not yet calibrated. +const GAS_COST: u64 = 5_000; + +const PRECOMPILE_NAME: &str = "ARKIV_ENTITYDB_WRITE"; +const ENTITYDB_METHOD: &str = "arkiv_precompileWrite"; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct WriteResponse { + state_root: B256, +} + +/// Build the [`DynPrecompile`] bound to the given EntityDB client. +pub fn entitydb_write_precompile(client: Arc) -> DynPrecompile { + let id = PrecompileId::custom(PRECOMPILE_NAME); + let call = move |input: PrecompileInput<'_>| -> PrecompileResult { + if input.gas < GAS_COST { + // Soft halt — preserves return-data semantics, doesn't abort the tx. + return Ok(PrecompileOutput::halt( + PrecompileHalt::OutOfGas, + input.reservoir, + )); + } + let params = json!({ + "data": format!("0x{}", hex::encode(input.data)), + "caller": input.caller, + "value": input.value, + }); + match client.rpc_call::(ENTITYDB_METHOD, params) { + Ok(resp) => Ok(PrecompileOutput::new( + GAS_COST, + Bytes::copy_from_slice(resp.state_root.as_slice()), + input.reservoir, + )), + Err(e) => { + tracing::warn!(error = %e, "arkiv precompile: EntityDB call failed"); + // Fatal: aborts the tx. EntityDB unreachable is not a + // recoverable EVM-level condition for this POC. + Err(PrecompileError::Fatal(e.to_string())) + } + } + }; + (id, call).into() +} + +// --------------------------------------------------------------------------- +// EvmFactory wrapper +// --------------------------------------------------------------------------- + +/// Wraps the default [`OpEvmFactory`] and (when a client is configured) +/// installs the EntityDB-write precompile on every fresh EVM instance. +#[derive(Debug, Clone, Default)] +pub struct ArkivOpEvmFactory { + inner: OpEvmFactory, + client: Option>, +} + +impl ArkivOpEvmFactory { + pub fn new(client: Option>) -> Self { + Self { inner: OpEvmFactory::default(), client } + } + + fn install(&self, evm: &mut E) + where + E: Evm, + { + let Some(client) = self.client.as_ref() else { return }; + let precompile = entitydb_write_precompile(client.clone()); + evm.precompiles_mut() + .apply_precompile(&ARKIV_PRECOMPILE_ADDRESS, |_existing| Some(precompile)); + } +} + +impl EvmFactory for ArkivOpEvmFactory { + // Mirror `OpEvmFactory`'s associated types concretely. Forwarding + // through ` as EvmFactory>::X` projections compiles + // here but leaves bounds like `Self::Tx: FromRecoveredTx<_>` unresolved + // in downstream `ConfigureEvm` / `OpAddOns` impls, because the compiler + // does not always normalise nested projections through trait bounds. + type Evm>> = OpEvm; + type Context = OpEvmContext; + type Tx = OpTx; + type Error = + EVMError; + type HaltReason = OpHaltReason; + type Spec = OpSpecId; + type BlockEnv = BlockEnv; + type Precompiles = PrecompilesMap; + + fn create_evm( + &self, + db: DB, + input: EvmEnv, + ) -> Self::Evm { + let mut evm = self.inner.create_evm(db, input); + self.install(&mut evm); + evm + } + + fn create_evm_with_inspector>>( + &self, + db: DB, + input: EvmEnv, + inspector: I, + ) -> Self::Evm { + let mut evm = self.inner.create_evm_with_inspector(db, input, inspector); + self.install(&mut evm); + evm + } +} + +// --------------------------------------------------------------------------- +// ArkivOpEvmConfig — local newtype wrapper around OpEvmConfig +// --------------------------------------------------------------------------- +// +// Required because the orphan rule does not let us impl the foreign +// trait `ConfigureEngineEvm` directly on the foreign type +// `OpEvmConfig<_, _, _, ArkivOpEvmFactory>` — the local type only +// appears as a generic argument inside a foreign type, which is not +// enough to satisfy E0117. Wrapping in a local newtype lifts the local +// type to the head position and unblocks the impls. +// +// `ConfigureEvm` is a pure passthrough; `ConfigureEngineEvm` +// delegates to a temporary default-factory `OpEvmConfig` built from the +// same chain spec, since the upstream impl body does not actually +// depend on the EVM factory — it only reads `chain_spec()` and +// constructs values from the payload data. + +type InnerEvmConfig = + OpEvmConfig; +type DefaultEvmConfig = OpEvmConfig; + +#[derive(Debug, Clone)] +pub struct ArkivOpEvmConfig { + inner: InnerEvmConfig, + /// Default-factory `OpEvmConfig` sharing the same chain spec. Used by + /// the `ConfigureEngineEvm` shim (whose upstream impl body + /// does not depend on the EVM factory). Stored as a field rather than + /// constructed on the fly so the iterator returned by + /// `tx_iterator_for_payload` can outlive the call. + inner_default: DefaultEvmConfig, +} + +impl ArkivOpEvmConfig { + pub fn new(chain_spec: Arc, client: Option>) -> Self { + let executor_factory = OpBlockExecutorFactory::new( + OpRethReceiptBuilder::default(), + chain_spec.clone(), + ArkivOpEvmFactory::new(client), + ); + let inner = OpEvmConfig { + block_assembler: OpBlockAssembler::new(chain_spec.clone()), + executor_factory, + _pd: core::marker::PhantomData, + }; + let inner_default = OpEvmConfig::new(chain_spec, OpRethReceiptBuilder::default()); + Self { inner, inner_default } + } +} + +impl ConfigureEvm for ArkivOpEvmConfig { + // Concrete types (rather than `::X` + // projections) so downstream bounds like + // `::NextBlockEnvCtx: BuildNextEnv<...>` in + // `OpAddOns: NodeAddOns` can be checked without normalising through + // `OpEvmConfig`'s blanket `ConfigureEvm` impl. Rust's trait solver is + // sometimes unable to do that normalisation under nested bounds. + type Primitives = OpPrimitives; + type Error = EIP1559ParamError; + type NextBlockEnvCtx = OpNextBlockEnvAttributes; + type BlockExecutorFactory = + OpBlockExecutorFactory, ArkivOpEvmFactory>; + type BlockAssembler = OpBlockAssembler; + + fn block_executor_factory(&self) -> &Self::BlockExecutorFactory { + self.inner.block_executor_factory() + } + + fn block_assembler(&self) -> &Self::BlockAssembler { + self.inner.block_assembler() + } + + fn evm_env(&self, header: &Header) -> Result, Self::Error> { + self.inner.evm_env(header) + } + + fn next_evm_env( + &self, + parent: &Header, + attributes: &Self::NextBlockEnvCtx, + ) -> Result, Self::Error> { + self.inner.next_evm_env(parent, attributes) + } + + fn context_for_block<'a>( + &self, + block: &'a SealedBlock, + ) -> Result, Self::Error> { + self.inner.context_for_block(block) + } + + fn context_for_next_block( + &self, + parent: &SealedHeader
, + attributes: Self::NextBlockEnvCtx, + ) -> Result, Self::Error> { + self.inner.context_for_next_block(parent, attributes) + } +} + +impl ConfigureEngineEvm for ArkivOpEvmConfig { + fn evm_env_for_payload( + &self, + payload: &OpExecData, + ) -> Result, ::Error> { + self.inner_default.evm_env_for_payload(payload) + } + + fn context_for_payload<'a>( + &self, + payload: &'a OpExecData, + ) -> Result, ::Error> { + self.inner_default.context_for_payload(payload) + } + + fn tx_iterator_for_payload( + &self, + payload: &OpExecData, + ) -> Result, ::Error> { + self.inner_default.tx_iterator_for_payload(payload) + } +} + +// --------------------------------------------------------------------------- +// ExecutorBuilder +// --------------------------------------------------------------------------- + +/// Drop-in replacement for `OpExecutorBuilder` that produces an +/// [`ArkivOpEvmConfig`] (which uses [`ArkivOpEvmFactory`] internally). +#[derive(Debug, Clone, Default)] +pub struct ArkivOpExecutorBuilder { + client: Option>, +} + +impl ArkivOpExecutorBuilder { + pub fn new(client: Option>) -> Self { + Self { client } + } +} + +impl ExecutorBuilder for ArkivOpExecutorBuilder +where + N: FullNodeTypes>, +{ + type EVM = ArkivOpEvmConfig; + + async fn build_evm(self, ctx: &BuilderContext) -> eyre::Result { + Ok(ArkivOpEvmConfig::new(ctx.chain_spec(), self.client)) + } +} + +// --------------------------------------------------------------------------- +// ArkivOpNode — thin wrapper around `OpNode` that swaps the executor slot +// --------------------------------------------------------------------------- +// +// Required because `OpNode::add_ons()` returns `OpAddOns, ...>` — the AddOns is hardcoded to the default +// component bundle (with `OpEvmConfig<.., OpEvmFactory>`). When we +// swap in `ArkivOpExecutorBuilder` the `Components` type changes, so +// `op_node.add_ons()` no longer matches the components we built and +// `with_add_ons` rejects it. +// +// The fix mirrors the upstream `examples/custom-node` pattern: define a +// local `Node` impl whose `ComponentsBuilder` and `AddOns` are typed +// consistently against `ArkivOpExecutorBuilder`, and forward to the +// inner `OpNode` for everything else. + +#[derive(Debug, Clone)] +pub struct ArkivOpNode { + inner: OpNode, + precompile_client: Option>, +} + +impl ArkivOpNode { + pub fn new(inner: OpNode, precompile_client: Option>) -> Self { + Self { inner, precompile_client } + } +} + +impl NodeTypes for ArkivOpNode { + type Primitives = OpPrimitives; + type ChainSpec = OpChainSpec; + type Storage = OpStorage; + type Payload = OpEngineTypes; +} + +impl Node for ArkivOpNode +where + N: FullNodeTypes, +{ + type ComponentsBuilder = ComponentsBuilder< + N, + OpPoolBuilder, + BasicPayloadServiceBuilder, + OpNetworkBuilder, + ArkivOpExecutorBuilder, + OpConsensusBuilder, + >; + + type AddOns = OpAddOns< + NodeAdapter>::Components>, + OpEthApiBuilder, + OpEngineValidatorBuilder, + OpEngineApiBuilder, + BasicEngineValidatorBuilder, + >; + + fn components_builder(&self) -> Self::ComponentsBuilder { + self.inner + .components() + .executor(ArkivOpExecutorBuilder::new(self.precompile_client.clone())) + } + + fn add_ons(&self) -> Self::AddOns { + self.inner.add_ons_builder().build() + } +} + +// Required for `launch_with_debug_capabilities()`. Body is a faithful +// copy of upstream `OpNode`'s impl (op-reth/crates/node/src/node.rs:337 +// and the private `OpLocalPayloadAttributesBuilder` it constructs at +// :74-137); the type lives in the op-reth `node.rs` module privately +// so we cannot reuse it directly. +impl DebugNode for ArkivOpNode +where + N: FullNodeComponents, +{ + type RpcBlock = alloy_rpc_types_eth::Block; + + fn rpc_to_primitive_block(rpc_block: Self::RpcBlock) -> reth_node_api::BlockTy { + rpc_block.into_consensus() + } + + fn local_payload_attributes_builder( + chain_spec: &Self::ChainSpec, + ) -> impl PayloadAttributesBuilder<::PayloadAttributes> + { + ArkivLocalPayloadAttributesBuilder { chain_spec: Arc::new(chain_spec.clone()) } + } +} + +/// Local-mining payload attributes builder. Verbatim copy of +/// `OpLocalPayloadAttributesBuilder` from op-reth's private module; kept +/// in sync with `op-reth/v2.2.0`. +struct ArkivLocalPayloadAttributesBuilder { + chain_spec: Arc, +} + +impl PayloadAttributesBuilder for ArkivLocalPayloadAttributesBuilder { + fn build( + &self, + parent: &reth_primitives_traits::SealedHeader, + ) -> OpPayloadAttrs { + use alloy_consensus::BlockHeader; + use alloy_primitives::{Address, B64}; + + let timestamp = std::cmp::max( + parent.timestamp().saturating_add(1), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(), + ); + + let eth_attrs = alloy_rpc_types_engine::PayloadAttributes { + timestamp, + prev_randao: alloy_primitives::B256::random(), + suggested_fee_recipient: Address::random(), + withdrawals: self + .chain_spec + .is_shanghai_active_at_timestamp(timestamp) + .then(Default::default), + parent_beacon_block_root: self + .chain_spec + .is_cancun_active_at_timestamp(timestamp) + .then(alloy_primitives::B256::random), + slot_number: None, + }; + + // OP Mainnet `setL1BlockValuesEcotone` system tx at index 0 of + // block 124665056. Hard-coded for dev mode so blocks pass the + // OP "first tx must be a deposit" rule. + const TX_SET_L1_BLOCK: [u8; 251] = alloy_primitives::hex!( + "7ef8f8a0683079df94aa5b9cf86687d739a60a9b4f0835e520ec4d664e2e415dca17a6df94deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8a4440a5e200000146b000f79c500000000000000040000000066d052e700000000013ad8a3000000000000000000000000000000000000000000000000000000003ef1278700000000000000000000000000000000000000000000000000000000000000012fdf87b89884a61e74b322bbcf60386f543bfae7827725efaaf0ab1de2294a590000000000000000000000006887246668a3b87f54deb3b94ba47a6f63f32985" + ); + + let default_params = BaseFeeParams::optimism(); + let denominator = std::env::var("OP_DEV_EIP1559_DENOMINATOR") + .ok() + .and_then(|v| v.parse::().ok()) + .unwrap_or(default_params.max_change_denominator as u32); + let elasticity = std::env::var("OP_DEV_EIP1559_ELASTICITY") + .ok() + .and_then(|v| v.parse::().ok()) + .unwrap_or(default_params.elasticity_multiplier as u32); + let gas_limit = std::env::var("OP_DEV_GAS_LIMIT") + .ok() + .and_then(|v| v.parse::().ok()); + + let mut eip1559_bytes = [0u8; 8]; + eip1559_bytes[0..4].copy_from_slice(&denominator.to_be_bytes()); + eip1559_bytes[4..8].copy_from_slice(&elasticity.to_be_bytes()); + + OpPayloadAttrs(OpPayloadAttributes { + payload_attributes: eth_attrs, + transactions: Some(vec![TX_SET_L1_BLOCK.into()]), + no_tx_pool: None, + gas_limit, + eip_1559_params: Some(B64::from(eip1559_bytes)), + min_base_fee: Some(0), + }) + } +} diff --git a/crates/arkiv-node/src/rpc.rs b/crates/arkiv-node/src/rpc.rs index 8d45f8e..433b58f 100644 --- a/crates/arkiv-node/src/rpc.rs +++ b/crates/arkiv-node/src/rpc.rs @@ -1,9 +1,9 @@ //! `arkiv_*` JSON-RPC namespace. //! -//! Single endpoint `arkiv_query` that forwards its `params` verbatim to the -//! configured EntityDB and returns the raw `result`. The handler shares the -//! same [`EntityDbClient`] (and therefore the same connection pool) as the -//! ExEx's write-side `JsonRpcStore`. +//! Transparent proxy: each method forwards its positional args verbatim to +//! the configured EntityDB and returns the raw `result`. The handler shares +//! the same [`EntityDbClient`] (and connection pool) as the ExEx's write-side +//! `JsonRpcStore`. use crate::storage::EntityDbClient; use jsonrpsee::core::{RpcResult, async_trait}; @@ -14,10 +14,18 @@ use std::sync::Arc; #[rpc(server, namespace = "arkiv")] pub trait ArkivApi { - /// Forward an arbitrary query to the configured EntityDB. The `params` - /// envelope is passed through unmodified. + /// Query entities. `expr` is the EntityDB query expression; `options` + /// is an optional object (paging / projection / atBlock). #[method(name = "query")] - async fn query(&self, query: Value) -> RpcResult; + async fn query(&self, expr: String, options: Option) -> RpcResult; + + /// Total entity count currently stored in EntityDB. + #[method(name = "getEntityCount")] + async fn get_entity_count(&self) -> RpcResult; + + /// Timing for the current head block. + #[method(name = "getBlockTiming")] + async fn get_block_timing(&self) -> RpcResult; } pub struct ArkivRpc { @@ -32,9 +40,24 @@ impl ArkivRpc { #[async_trait] impl ArkivApiServer for ArkivRpc { - async fn query(&self, query: Value) -> RpcResult { + async fn query(&self, expr: String, options: Option) -> RpcResult { + let opts = options.unwrap_or(Value::Null); + self.client + .proxy("arkiv_query", vec![Value::String(expr), opts]) + .await + .map_err(to_rpc_err) + } + + async fn get_entity_count(&self) -> RpcResult { + self.client + .proxy("arkiv_getEntityCount", vec![]) + .await + .map_err(to_rpc_err) + } + + async fn get_block_timing(&self) -> RpcResult { self.client - .proxy("arkiv_query", query) + .proxy("arkiv_getBlockTiming", vec![]) .await .map_err(to_rpc_err) } diff --git a/crates/arkiv-node/src/storage/jsonrpc.rs b/crates/arkiv-node/src/storage/jsonrpc.rs index 1a3925a..bdbdb90 100644 --- a/crates/arkiv-node/src/storage/jsonrpc.rs +++ b/crates/arkiv-node/src/storage/jsonrpc.rs @@ -42,6 +42,7 @@ struct CommitResponse { /// Thin JSON-RPC client over a single EntityDB endpoint. Shared between the /// write-side ExEx ([`JsonRpcStore`]) and the read-side `arkiv_query` RPC /// proxy so they reuse one connection pool and one URL. +#[derive(Debug)] pub struct EntityDbClient { http: reqwest::Client, url: String, @@ -106,14 +107,17 @@ impl EntityDbClient { unwrap_response(rpc_resp) } - /// Async RPC proxy — used by the `arkiv_query` JSON-RPC handler. Forwards - /// `params` as-is and returns the raw `result` payload. - pub async fn proxy(&self, method: &str, params: Value) -> Result { + /// Async RPC proxy — used by the `arkiv_*` JSON-RPC handlers. The caller + /// supplies the full positional-args array; it is forwarded verbatim and + /// the raw `result` payload is returned. Note this differs from + /// [`Self::rpc_call`], which wraps a single `Value` in a one-element + /// array for the ExEx write-path methods. + pub async fn proxy(&self, method: &str, params: Vec) -> Result { let body = serde_json::json!({ "jsonrpc": "2.0", "id": self.next_id(), "method": method, - "params": [params] + "params": params, }); let resp = self diff --git a/docs/architecture.md b/docs/architecture.md index a52a5e2..c56098d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -214,24 +214,28 @@ runtime. #### 3.2.3 Read-side RPC proxy When `--arkiv.db-url` is set, the node also registers an `arkiv` JSON-RPC -namespace via reth's `extend_rpc_modules` hook. Today it exposes a -single method: +namespace via reth's `extend_rpc_modules` hook. Today it exposes: | Method | Params | Returns | |---|---|---| -| `arkiv_query` | arbitrary JSON value, forwarded as-is | EntityDB's `result` payload, returned as-is | +| `arkiv_query` | `expr: string`, `options?: object` | `{ data, blockNumber, cursor? }` (per EntityDB) | +| `arkiv_getEntityCount` | none | total entity count (number) | +| `arkiv_getBlockTiming` | none | `{ current_block, current_block_time, duration }` | -The handler is a transparent proxy: the same `EntityDbClient` -(connection pool, timeouts) backs both the write-side `JsonRpcStore` and -the read-side proxy. Errors from EntityDB are surfaced as JSON-RPC error -`-32000` with the underlying message. +Each handler is a transparent proxy: positional args are forwarded +verbatim to the configured EntityDB and the raw `result` is returned to +the caller. The same `EntityDbClient` (connection pool, timeouts) backs +both the write-side `JsonRpcStore` and these read-side handlers. Errors +from EntityDB are surfaced as JSON-RPC error `-32000` with the +underlying message. Not implementing wildcard forwarding (e.g. anything matching `arkiv_*`) is intentional: jsonrpsee dispatches by exact method name, and a wildcard would either need EntityDB-side method introspection at startup -or a custom middleware layer. A single typed `arkiv_query` endpoint -covers the use case without either. New methods can be added one rpc -trait method at a time as needs surface. +or a custom middleware layer. The typed-trait approach — one rpc trait +method per supported EntityDB endpoint — keeps the wire surface +explicit at the cost of mirroring new EntityDB methods here as they're +added. The RPC namespace is registered on every transport that the operator has enabled (`--http`, `--ws`, `--ipc`); operators who want to keep the @@ -687,11 +691,12 @@ Honest scope notes: is the L2 execution client only. - **Pre-Bedrock state import.** Standard op-reth concern; the canonical Optimism docs cover it. -- **More than one Arkiv RPC method.** Today the namespace exposes only - `arkiv_query` (transparent proxy to EntityDB). Other methods such as - `arkiv_changeSetHash` could be added — either as additional typed - proxy methods or as RPCs that read directly from contract storage. - Not pursued yet. +- **Full coverage of EntityDB's RPC surface.** The namespace currently + proxies `arkiv_query`, `arkiv_getEntityCount`, and `arkiv_getBlockTiming`. + Other EntityDB methods (e.g. `arkiv_getNumberOfUsedSlots`) and any + on-node-only RPCs (e.g. an `arkiv_changeSetHash` reading directly from + contract storage) can be added as additional trait methods; not pursued + yet. --- diff --git a/docs/custom-precompile.md b/docs/custom-precompile.md new file mode 100644 index 0000000..f6c6eac --- /dev/null +++ b/docs/custom-precompile.md @@ -0,0 +1,1025 @@ +# Custom precompiles in op-reth + +How custom precompiles are wired into the Arkiv node, and the constraints +that come with them. Reflects the working POC in +`crates/arkiv-node/src/precompile.rs`, which installs an EntityDB-write +precompile at `0x0000000000000000000000000000000000aa01` when both +`--arkiv.db-url` and `--arkiv.precompile` are passed. + +## Workspace pins + +The wiring below references concrete types. Versions used: + +| Crate | Version | Source | +|---|---|---| +| `paradigmxyz/reth` | rev `27bfddeada3953edc22759080a3659ccea62ca1f` | git | +| `ethereum-optimism/optimism` | tag `op-reth/v2.2.0` (rev `484be19`) | git | +| `revm` | 38.0.0 | crates.io | +| `revm-precompile` | 34.0.0 | crates.io (transitive) | +| `alloy-evm` | 0.33.x | crates.io | +| `alloy-op-evm` | 0.31.0 | git, op-reth tag | +| `op-revm` | 19.0.0 | git, op-reth tag | + +Local cache paths (substitute these in the file references below): + +``` +~/.cargo/git/checkouts/reth-e231042ee7db3fb7/27bfdde/ +~/.cargo/git/checkouts/optimism-852bcbde357560e3/484be19/rust/ +~/.cargo/registry/src/index.crates.io-*/{alloy-evm,revm-precompile,…}/ +``` + +Several APIs below have churned over recent versions (especially +`revm-precompile` 32 → 34, which reworked the error/halt model). On +every reth/op-reth bump, re-check **§3** (precompile return shape) and +the line numbers cited in **§5** before assuming the doc still applies. + +--- + +## 1. Mental model + +A precompile is a fixed-address handler the EVM consults instead of +running EVM bytecode. It receives the call's calldata, gas, caller, and +value, and returns either output bytes (with gas accounting) or a halt +status. It is part of the state-transition function — every node must +agree on its presence and behaviour or the chain forks. + +Adding one to op-reth means inserting an entry into the +`PrecompilesMap` of every fresh `OpEvm` instance the node creates. +That requires reaching three layers up the stack: + +1. **Write the precompile** — a closure (or `Precompile` impl) producing + `PrecompileResult` from `PrecompileInput`. +2. **Install it in every EVM** — wrap `OpEvmFactory` so that + `create_evm` and `create_evm_with_inspector` both call + `evm.precompiles_mut().apply_precompile(...)` after the default + set has been loaded. +3. **Plumb the wrapped factory through the node builder** — replace + `OpExecutorBuilder` (the component slot that constructs `OpEvmConfig`) + with a custom one that uses our factory, then put the result behind + a wrapper `Node` impl so the AddOns are typed against the customised + components. + +The third step is where most of the friction lives; **§5** documents the +specific patterns that make it compile. + +## 2. Layering + +Four crates, low to high: + +1. **`revm-precompile`** — wire types: `PrecompileResult`, + `PrecompileOutput`, `PrecompileError`, `PrecompileHalt`, + `PrecompileId`, `PrecompileFn`. +2. **`alloy-evm`** — higher-level `Precompile` trait, + `PrecompileInput`, `PrecompilesMap`, `DynPrecompile`, and the + `EvmFactory` trait. This is the registration layer. +3. **`alloy-op-evm` + `reth-optimism-evm`** — OP-specific glue: + `OpEvm`, `OpEvmFactory`, `OpEvmConfig`, `OpBlockExecutorFactory`, + `OpBlockAssembler`, `OpRethReceiptBuilder`, `OpTx`. The node passes + `OpEvmConfig` around as `ConfigureEvm`. +4. **`reth-node-builder` + `reth-optimism-node`** — `Node` / + `ExecutorBuilder` traits and `OpNode` itself. This is where the + custom executor builder gets installed. + +The reference for the entire pattern is upstream +`reth/examples/precompile-cache/src/main.rs` (a wrap-every-precompile +cache rather than a new precompile, but it touches every relevant API). +For the specific OP-stack interactions covered in §5, +`op-reth/examples/custom-node/src/evm/` is the closest analogue. + +--- + +## 3. The precompile API (`alloy-evm 0.33` / `revm-precompile 34.0.0`) + +### 3.1 Trait + +```rust +pub trait Precompile { + fn precompile_id(&self) -> &PrecompileId; + fn call(&self, input: PrecompileInput<'_>) -> PrecompileResult; + fn supports_caching(&self) -> bool { true } +} +``` + +`supports_caching = false` (`DynPrecompile::new_stateful`) tells the +engine that results are not a pure function of `(data, gas)` and must +not be memoised. Use this for any precompile whose return depends on +state outside `PrecompileInput`. + +### 3.2 Input + +```rust +pub struct PrecompileInput<'a> { + pub data: &'a [u8], // calldata + pub gas: u64, // gas limit available + pub reservoir: u64, // EIP-8037 state-gas reservoir; 0 on mainnet today + pub caller: Address, // msg.sender + pub value: U256, // call value + pub target_address: Address, // address being called + pub is_static: bool, // STATICCALL? + pub bytecode_address: Address, + pub internals: EvmInternals<'a>, // hooks back into journaled EVM state +} +``` + +`PrecompileInput::is_direct_call()` returns `true` when not invoked via +`DELEGATECALL` / `CALLCODE`; useful for refusing indirect calls. + +### 3.3 Return + +```rust +pub type PrecompileResult = Result; + +pub struct PrecompileOutput { + pub status: PrecompileStatus, // Success | Revert | Halt(PrecompileHalt) + pub gas_used: u64, + pub gas_refunded: i64, + pub state_gas_used: u64, // EIP-8037 + pub reservoir: u64, // EIP-8037; passthrough of input.reservoir + pub bytes: Bytes, +} + +pub enum PrecompileError { Fatal(String), FatalAny(AnyError) } + +pub enum PrecompileHalt { + OutOfGas, + // … many crypto-precompile-specific halts … + Other(Cow<'static, str>), +} +``` + +Three concrete return paths: + +- **Success.** `Ok(PrecompileOutput::new(gas_used, bytes, input.reservoir))`. +- **Out-of-gas / soft halt.** + `Ok(PrecompileOutput::halt(PrecompileHalt::OutOfGas, input.reservoir))` + — the EVM consumes all available gas for the call frame and reverts + the call's state changes, but the surrounding transaction continues. + This is the pattern for "couldn't afford the work" — `PrecompileError` + has no `OutOfGas` variant any more. +- **Revert with return data.** `Ok(PrecompileOutput::revert(gas_used, bytes, input.reservoir))`. +- **Fatal.** `Err(PrecompileError::Fatal(msg))` aborts the entire + transaction. Reserved for things the EVM cannot recover from + (precompile panicked; unrecoverable I/O on a path that is ostensibly + consensus-deterministic; etc.). + +**Gas accounting is the precompile's responsibility.** Nothing checks +that `gas_used ≤ input.gas` for you in the trait contract; check it +explicitly and return an OOG halt if you can't afford the work. + +### 3.4 `PrecompileId` + +Enum, one variant per stdlib precompile plus +`Custom(Cow<'static, str>)`. For new precompiles use +`PrecompileId::custom("ARKIV_")`. The string is informational +(tracing, EIP-7910 introspection); it is not the precompile's address. +Pick stable, namespaced strings — they end up in client-facing tooling. + +### 3.5 `DynPrecompile` + +`PrecompilesMap` stores `DynPrecompile`, which is +`Arc`. Three convenient constructors: + +```rust +// 1. From a closure. +let p: DynPrecompile = ( + PrecompileId::custom("ARKIV_X"), + |input: PrecompileInput<'_>| -> PrecompileResult { /* ... */ }, +).into(); + +// 2. From a struct that implements Precompile. +let p = DynPrecompile::new(id, my_struct); +let p = DynPrecompile::new_stateful(id, my_struct); // disables caching +``` + +The closure form is what the POC uses — it captures the +`Arc` and is otherwise stateless from the trait's +perspective. + +--- + +## 4. `PrecompilesMap` — registration mechanics + +`PrecompilesMap` is the mutable container of precompiles attached to a +specific EVM instance. Source: +`alloy-evm-0.33.x/src/precompiles.rs`. Methods that matter: + +| Method | Purpose | +|---|---| +| `from_static(&'static Precompiles)` | Initial population from a hardfork's static set. | +| `apply_precompile(&Address, FnOnce(Option) -> Option)` | Insert / replace / remove at one address. Returning `None` removes; `Some(p)` installs. | +| `with_applied_precompile(...)` | Builder-style version (consuming). | +| `map_precompile(&Address, F)` / `map_precompiles(F)` | Transform existing precompile(s) in place; this is what the cache example uses. | +| `extend_precompiles(I)` | Bulk insert; replaces on collision. | +| `move_precompiles(I)` | Relocate by `(src, dst)` pairs; errors if `src` isn't a precompile. | +| `set_precompile_lookup(L)` | Install a fallback resolver. Cold-access penalty applies; static entries take priority. | + +Behavioural notes: + +- `set_precompile_lookup` is invoked on **every** precompile check for + unregistered addresses. It must be cheap, and addresses it returns + are always treated as cold. For a fixed-address precompile, prefer + `apply_precompile` — the address gets warmed by the standard rules. +- `extend_precompiles` *replaces* on collision; to wrap or extend an + existing precompile use `map_precompile` / `apply_precompile`. + +The POC's call site is a one-liner inside the wrapped EvmFactory: + +```rust +evm.precompiles_mut().apply_precompile(&ARKIV_PRECOMPILE_ADDRESS, |_existing| { + Some(precompile) +}); +``` + +Choice of address: + +- Outside the reserved precompile range (`0x01..=0x11`). +- Distinct from any predeploy in `chain.inner.genesis.alloc`. Arkiv + reserves `0x4400000000000000000000000000000000000044` for + `EntityRegistry`; the POC uses `0x00…00aa01` per high-address + convention. + +--- + +## 5. op-reth wiring + +The OP stack mirrors the Ethereum reference example with its own +types: + +| Concept | OP type | +|---|---| +| Spec id | `OpSpecId` | +| Default precompiles | `OpPrecompiles` | +| EVM | `OpEvm` | +| EVM factory | `OpEvmFactory` | +| EVM config | `OpEvmConfig` | +| Default executor builder | `OpExecutorBuilder` | +| Tx env | `OpTransaction` (wrapped as `OpTx`) | +| Halt reason | `OpHaltReason` | +| Tx error | `EVMError` | + +Wiring breaks into four pieces, each with a non-obvious wrinkle: + +1. The custom `EvmFactory` (§5.1). +2. A local newtype around `OpEvmConfig` to dodge the orphan rule on + `ConfigureEngineEvm` (§5.2). +3. A local `Node` impl so the AddOns are typed against the customised + components (§5.3). +4. A `DebugNode` impl so `--dev` mining still works (§5.4). + +All concrete code below is in `crates/arkiv-node/src/precompile.rs`. + +### 5.1 Custom `EvmFactory` + +Wrap `OpEvmFactory` and install the precompile after the default +set is loaded. Both code paths must apply the same mutation — forgetting +`create_evm_with_inspector` silently breaks `debug_traceTransaction` and +similar simulation/tracing endpoints. + +```rust +pub struct ArkivOpEvmFactory { + inner: OpEvmFactory, + client: Option>, +} + +impl EvmFactory for ArkivOpEvmFactory { + // Spell associated types concretely; do NOT forward through + // ` as EvmFactory>::X` projections — see §6.4. + type Evm>> = + OpEvm; + type Context = OpEvmContext; + type Tx = OpTx; + type Error = EVMError; + type HaltReason = OpHaltReason; + type Spec = OpSpecId; + type BlockEnv = BlockEnv; + type Precompiles = PrecompilesMap; + + fn create_evm(&self, db: DB, input: EvmEnv<...>) + -> Self::Evm + { + let mut evm = self.inner.create_evm(db, input); + self.install(&mut evm); + evm + } + + fn create_evm_with_inspector(&self, db: DB, input: EvmEnv<...>, inspector: I) + -> Self::Evm + { + let mut evm = self.inner.create_evm_with_inspector(db, input, inspector); + self.install(&mut evm); + evm + } +} +``` + +`install` calls `apply_precompile` only when a client is configured; +when `client = None` the wrapper is a transparent passthrough over the +default OP factory. + +### 5.2 Why we need a local newtype for `OpEvmConfig` + +`OpEvmConfig` is generic over the factory: + +```rust +pub struct OpEvmConfig< + ChainSpec = OpChainSpec, + N = OpPrimitives, + R = OpRethReceiptBuilder, + EvmFactory = OpEvmFactory, +> { … } +``` + +The blanket `ConfigureEvm` impl +(`op-reth/crates/evm/src/lib.rs:129`) is fully generic over all four, +so a custom-factory `OpEvmConfig` is a valid `ConfigureEvm`. **But two +other impls op-reth needs in the launcher path are gated to the +3-generic form** (i.e. defaulted factory): + +```rust +// op-reth/crates/evm/src/lib.rs:215 +impl ConfigureEngineEvm + for OpEvmConfig { … } + +// op-reth/crates/payload/src/lib.rs:35 +impl ConfigureEngineEvm + for OpEvmConfig +where Self: ConfigureEngineEvm, … { … } +``` + +`ConfigureEngineEvm` is required transitively by +`BasicEngineValidatorBuilder: EngineValidatorBuilder` (the default +`EVB` slot in `OpAddOns`), which is itself required by +`OpAddOns: NodeAddOns` and `RethRpcAddOns`. Without it, +`with_add_ons(…)` and `launch*()` reject the builder. The diagnostic is +a deeply nested trait-bound error whose only useful hint is +`expected `OpEvmConfig<_>`, found `…`` — misleading; the *real* missing +piece is `ConfigureEngineEvm`, not `OpEvmConfig` itself. + +Adding `impl ConfigureEngineEvm for OpEvmConfig<…, ArkivOpEvmFactory>` +directly hits the orphan rule (E0117): both the trait and the type are +foreign; the only local type (`ArkivOpEvmFactory`) is buried as a +generic argument inside `OpEvmConfig`, which Rust does not count as +"local at the head". + +The fix is a local newtype that wraps `OpEvmConfig` and also stores a +default-factory `OpEvmConfig` to delegate the engine-API methods to: + +```rust +pub struct ArkivOpEvmConfig { + inner: OpEvmConfig, + /// Default-factory `OpEvmConfig` sharing the same chain spec. + /// Used by the `ConfigureEngineEvm` shim, whose upstream impl body + /// does not actually depend on the EVM factory. + inner_default: OpEvmConfig, +} +``` + +- `ConfigureEvm` is a passthrough to `inner` (concrete associated types + — see §6.4 — so downstream bound checks normalise). +- `ConfigureEngineEvm` delegates to `inner_default`. +- `inner_default` is **stored as a field**, not constructed on the fly, + because `tx_iterator_for_payload` returns + `impl ExecutableTxIterator` and the compiler treats the iterator + as `'static`-bounded; a temporary `OpEvmConfig` would be dropped at + end-of-statement. + +### 5.3 Why we need a local `Node` impl + +`OpNode::add_ons() -> Self::AddOns` returns +`OpAddOns>::Components>, …>`. +The `Components` here are derived from `OpNode::ComponentsBuilder`, +which hardcodes `OpExecutorBuilder` and therefore the default +`OpEvmConfig`. If you go + +```rust +let nb = builder + .with_types::() + .with_components(op_node.components().executor(ArkivOpExecutorBuilder::new(client))) + .with_add_ons(op_node.add_ons()); +``` + +the `N` in the AddOns no longer matches the `N` the customised +components produce, and `with_add_ons` rejects the builder. This is the +second cause of the same `expected OpEvmConfig<_>, found …` error. + +The fix mirrors `op-reth/examples/custom-node`: define a local `Node` +impl that wraps `OpNode` and overrides `components_builder()` to swap +the executor and `add_ons()` to call `inner.add_ons_builder().build()` +(the latter is generic over `N`, so it'll be parameterised over the +actual customised components). + +```rust +pub struct ArkivOpNode { + inner: OpNode, + precompile_client: Option>, +} + +impl NodeTypes for ArkivOpNode { + type Primitives = OpPrimitives; + type ChainSpec = OpChainSpec; + type Storage = OpStorage; + type Payload = OpEngineTypes; +} + +impl> Node for ArkivOpNode { + type ComponentsBuilder = ComponentsBuilder< + N, OpPoolBuilder, BasicPayloadServiceBuilder, + OpNetworkBuilder, ArkivOpExecutorBuilder, OpConsensusBuilder, + >; + type AddOns = OpAddOns< + NodeAdapter>::Components>, + OpEthApiBuilder, OpEngineValidatorBuilder, + OpEngineApiBuilder, + BasicEngineValidatorBuilder, + >; + + fn components_builder(&self) -> Self::ComponentsBuilder { + self.inner + .components() + .executor(ArkivOpExecutorBuilder::new(self.precompile_client.clone())) + } + + fn add_ons(&self) -> Self::AddOns { self.inner.add_ons_builder().build() } +} +``` + +`OpFullNodeTypes`, `OpNodeTypes`, and `NodeTypesForProvider` are all +blanket-impl'd, so as long as our `NodeTypes` associated types match +`OpNode`'s (they do — same `Primitives`, `ChainSpec`, `Storage`, +`Payload`) we get them for free. + +The entrypoint in `main.rs` becomes: + +```rust +let arkiv_node = ArkivOpNode::new(OpNode::new(ext.rollup), pc_client); +let node = install(builder.node(arkiv_node), mode); +let handle = node.launch_with_debug_capabilities().await?; +``` + +i.e. the original shorthand survives, just with `ArkivOpNode` in place +of `OpNode`. + +### 5.4 `DebugNode` for `--dev` mining + +`launch_with_debug_capabilities()` requires +`: DebugNode>`, which provides +`local_payload_attributes_builder()` for the `--dev` LocalMiner. For +`ArkivOpNode` this is two methods: + +- `rpc_to_primitive_block` — one-liner (`rpc_block.into_consensus()`). +- `local_payload_attributes_builder` — must return + `impl PayloadAttributesBuilder`. + +`OpNode`'s impl returns `OpLocalPayloadAttributesBuilder`, which is +**private to `op-reth/crates/node/src/node.rs`** and we cannot reuse. +The POC copies the body verbatim into a local +`ArkivLocalPayloadAttributesBuilder`. It pulls in deps for the types +it constructs: `alloy-eips` (for `BaseFeeParams::optimism()`), +`alloy-hardforks` (for `EthereumHardforks` — see §6.5), +`alloy-rpc-types-engine`, `op-alloy-consensus`. + +When bumping `op-reth/v2.2.0`, diff the upstream +`OpLocalPayloadAttributesBuilder` against ours. Things to look for: + +- The hard-coded `TX_SET_L1_BLOCK` deposit transaction (synthetic + `setL1BlockValuesEcotone` call, required so dev blocks pass OP's + "first tx must be a deposit" rule). If the deposit ABI ever + changes, this constant must change with it. +- The `OP_DEV_EIP1559_DENOMINATOR` / `OP_DEV_EIP1559_ELASTICITY` / + `OP_DEV_GAS_LIMIT` env-var hooks. +- Hardfork checks. + +If you don't need `--dev` mining, `launch()` (using `EngineNodeLauncher` +directly) drops the `DebugNode` requirement entirely — but it silently +swallows the `--dev` flag, which is worse than the copy-paste cost. + +### 5.5 The custom `ExecutorBuilder` + +The piece that actually constructs `ArkivOpEvmConfig` from a +`BuilderContext`: + +```rust +pub struct ArkivOpExecutorBuilder { client: Option> } + +impl ExecutorBuilder for ArkivOpExecutorBuilder +where + N: FullNodeTypes>, +{ + type EVM = ArkivOpEvmConfig; + + async fn build_evm(self, ctx: &BuilderContext) -> eyre::Result { + Ok(ArkivOpEvmConfig::new(ctx.chain_spec(), self.client)) + } +} +``` + +Bounded on a concrete `OpChainSpec` rather than `OpHardforks` because +`ArkivOpEvmConfig` itself is concretely typed against `OpChainSpec`; +generality here would only complicate the wrapper without buying +anything. + +--- + +## 6. Constraints and gotchas + +### 6.1 Consensus and determinism + +A precompile is part of the state-transition function. Every node must +agree on its presence and behaviour, or the chain forks. There is no +"opt-in per node" mode — if it's compiled into the canonical binary, it +must be on for everyone, or activation must be gated by chain id / +timestamp inside `create_evm`. + +The stricter requirement is on the call itself: `Precompile::call` must +be a **pure function of `PrecompileInput` and any journaled EVM state** +visible via `EvmInternals`. The EVM relies on this to compute a single +deterministic state root after each block. If two honest nodes given +the same input compute different precompile outputs, they produce +different receipts, different state roots, and the chain forks at that +block. The usual violators — wall-clock time, RNG, non-deterministic +iteration over hash maps, **and any I/O to anything outside the EVM** — +are unsafe for exactly this reason. + +#### What the POC does, and why it is not a normal precompile + +The POC's precompile makes a synchronous HTTP/JSON-RPC call to an +EntityDB instance via the `EntityDbClient` already used by the ExEx +(§6.6 has the runtime mechanics). That call leaves the EVM sandbox. +Mechanically, this is the canonical example of "do not do this in a +precompile" — there is nothing in the precompile contract that says the +remote endpoint will exist, respond on time, return the same bytes on +every node, or even be the same physical server twice in a row. + +The POC ships anyway because the project's working assumption is that +**EntityDB is treated as part of the protocol, not an external service +the node happens to talk to**. The mental model is: every Arkiv node +runs its own EntityDB co-located with the execution client; EntityDB +itself is a deterministic state machine whose state is a pure function +of the consensus-determined sequence of writes (precompile calls today, +plus the existing ExEx-driven event stream); and the call from the +precompile is an in-process commit, not a network query against +arbitrary infrastructure. Under that model the precompile output is +still a deterministic function of consensus inputs — it just happens to +be computed by a sibling component instead of inline EVM code. + +That assumption is the load-bearing one. If it breaks, the chain forks. + +#### Empirical: a single tx fires the precompile multiple times + +Running `precompiles/run.sh` against the local mock surfaces this +immediately. One `cast send` invocation of `callPrecompile(0xdeadbeef)` +produces a sequence of byte-identical `arkiv_precompileWrite` requests +on the mock — same caller, same calldata, same value, typically six +in a row, with only the JSON-RPC envelope `id` advancing: + +```json +{ "id": 77, "jsonrpc": "2.0", "method": "arkiv_precompileWrite", + "params": [{ "caller": "0x9fe4…6e0", "data": "0xdeadbeef", "value": "0x0" }] } +{ "id": 78, …same params… } +{ "id": 79, …same params… } +… +{ "id": 82, …same params… } +``` + +The transaction is one user-level send, but the EVM executes it +repeatedly across the node's lifecycle. The paths, in roughly the +order reth invokes them: + +| # | Path | Where | +|---|---|---| +| 1 | `eth_estimateGas` | Cast probes for a gas limit before signing. | +| 2 | Mempool admission | `OpTransactionValidator` partial-executes the tx to validate. | +| 3 | Pending-block / state computation | `OpEvmConfig` builds pending state for queries that follow. | +| 4 | Block payload building | `OpPayloadBuilder` runs the tx while assembling the next block. | +| 5 | Canonical block execution | The block executor produces the canonical state diff. | +| 6 | Engine API validation | `BasicEngineValidatorBuilder`'s validator re-executes during the forkchoice update. | + +All six paths go through `EvmFactory::create_evm` (or +`create_evm_with_inspector`); `ArkivOpEvmFactory` installs the +precompile in every `OpEvm` it produces, so each path independently +fires the JSON-RPC call. + +This is the textbook "side-effecting precompile" hazard, made +concrete: **external side effects multiply by execution count, not +by transaction count.** With the current mock-entitydb response +(constant `0x000…0`) the duplication is harmless. The moment +EntityDB's response or stored state depends on prior writes the +duplication becomes consensus-affecting on a single node — before +two nodes even have a chance to disagree — and the chain forks. + +##### This is non-negotiable from the precompile side + +The instinct is to read this as a bug to fix in the precompile +layer — track "have we executed this tx already?", short-circuit on +re-entry, etc. **It is not.** Repeated execution of the same +transaction (and the same block) is a load-bearing property of how +reth — and every EVM client — operates: + +- gas estimation is by definition speculative execution against + pending state; +- mempool admission cannot trust user-supplied gas limits without + partially executing the tx; +- payload building has to run the tx to learn its receipt and the + resulting state root; +- the engine API re-executes the canonical block to validate the + payload it was just asked to import; +- historical replay (`debug_traceBlock`, snapshot regeneration, + full resync from genesis) re-runs every block, often many times + in a node's lifetime. + +A precompile sits inside `EvmFactory::create_evm` and cannot tell +these callers apart — and even if it could, suppressing the side +effect on any of them would break the path that needs it. Gas +estimation that skips the precompile reports the wrong gas; +validation that skips the precompile accepts blocks that canonical +execution rejects. The EVM has to be free to invoke the precompile +as many times as it wants, with no awareness on the precompile's +part of which invocation this one is. + +The corollary is that **a side-effecting precompile cannot be made +once-only from inside the precompile**. Whatever mitigation exists +has to live downstream of the precompile, in the component that +actually accepts the side effect — i.e. EntityDB. + +##### Whether the DB can absorb this is an open question + +In principle EntityDB could be made idempotent under repeated +identical writes: compute a deterministic key from the request and +treat duplicate keys as a no-op rather than as an append. In +practice this hits two structural problems with no obviously clean +answer: + +1. **Distinguishing replays from legitimate identical calls.** Two + transactions, in two different blocks, that both call + `precompileWrite(0xdeadbeef)` with the same caller and value + are logically distinct user events. A payload-hash key collapses + them into one. To dedupe re-executions while preserving distinct + user calls the key has to include something the EVM ties to a + specific call-site within a specific transaction within a + specific block — tx hash + call-frame index + intra-call + counter, say. None of those are exposed on `PrecompileInput`, + and the engine API's pre-canonicalisation re-execution operates + on a tx hash that is not yet final. + +2. **Reorg-aware retraction.** Re-execution along the canonical + path is one thing; what happens when the chain reorgs and the + tx gets unwound is another. Anything the DB committed + speculatively during pending-block / payload-building now + refers to a transaction that no longer exists, and the DB has + to roll it back atomically with the EVM state. The ExEx already + carries `arkiv_revert` / `arkiv_reorg` for exactly this reason, + and they are non-trivial. + +Whether a workable design exists at the intersection of these two +constraints is genuinely open. It may turn out that the DB cannot +plausibly disambiguate a re-execution from a legitimate identical +user call without changes to the EVM-side API surface that are out +of scope for this project — in which case side-effecting precompile +writes are not a viable model and the project has to fall back to +something else (the ExEx-driven event stream, which runs exactly +once per canonical block by construction, is the obvious candidate). + +##### Could the precompile flag canonical execution itself? + +Natural follow-up: can the precompile distinguish "this is the +canonical block executor" from "this is gas estimation / payload +building / engine-API validation", and only write in the canonical +case? Mechanisms exist; none are clean. + +- **Static factory swap.** One `OpEvmConfig` for block execution, + another for everything else. Reth's node builder exposes a single + `ConfigureEvm` slot; routing to two requires upstream cooperation + or a wrapper that internally dispatches on context it doesn't + have. +- **DB type discrimination.** Sniff the `DB` handed to + `create_evm` — the canonical executor passes a particular + `State<…>`, gas estimation a `CacheDB<…>`, etc. Requires `Any` + downcasts against reth-internal types that change across + versions, and "canonical execution" isn't a single DB type — it + covers the executor, the engine-API validator, and reorg replay. + Pins consensus correctness to reth's internal type churn. +- **`BlockEnv` discrimination.** The factory sees the block + header. Doesn't help: gas estimation, payload building, and + canonical execution all target the same block number. +- **Thread-local / atomic flag** set at the executor's entry. + Wrong on every axis — re-execution paths set and clear it + differently, concurrent payload-build vs engine-validate breaks + the invariant, and the precompile becomes a function of + out-of-band state by construction. + +The deeper objection is the one already named: any of these makes +the precompile **context-sensitive** — its behaviour varies with a +hidden input the EVM doesn't model. The EVM's contract with a +precompile is "deterministic function of input"; a precompile that +sometimes writes and sometimes doesn't is in violation of that +contract even when the EVM-visible return value is identical across +paths. + +The one escape hatch: if the EVM-visible return is fully determined +by `(data, caller, value, journaled state)` and the DB write is +purely ornamental — nothing the EVM observes ever depends on +whether the write happened — then varying the side effect across +paths is consistent with the contract. But once the write is +logically detached from the return value, the right place to do it +is the existing ExEx, which already fires once per canonical +commit, is reorg-aware, and sits outside the consensus surface. +Reproducing that pattern from inside a precompile via +canonical-execution discrimination buys nothing and adds coupling +to reth-internal execution-path identity, a fresh consensus +surface, and two side-effect channels for one logical event. + +The shape that survives this analysis is a precompile that is +**pure** (or at most a read against committed state via +`EvmInternals`), with the write emitted as an event the ExEx +consumes — the model the existing `arkiv` ExEx already implements +for `EntityRegistry`. + +The POC ships with the duplicates visible in the mock and makes no +attempt to suppress them; its job is to prove the wiring, not to +commit to a design. + +#### Concrete divergence vectors to keep in mind + +Things that would silently break the assumption and cause a fork, in +rough order of how easy it is to trip them by accident: + +1. **Different nodes pointing at different EntityDBs.** A misconfigured + `--arkiv.db-url` aimed at a shared service, or a stale snapshot on + one node, means two nodes compute against different state. The + precompile returns different `stateRoot` values; receipts diverge + on the next block. +2. **Asynchrony between the EVM and EntityDB commits.** If the call + is fire-and-forget on one side and synchronous on another, or if + EntityDB lags by a block, the same precompile invocation sees + different state at different sites. Today the call is synchronous + on the EVM side; the determinism budget assumes EntityDB commits + the write before responding and that the response is + byte-deterministic. +3. **Non-determinism inside EntityDB.** Floating-point arithmetic, + hash-map iteration order, OS-time-based logic, RNG, parallel + reduce of mutable state, anything that lets two identical inputs + produce different outputs. Out-of-tree concern, but every change + inside EntityDB is now a potential consensus change. +4. **Transport availability skew.** Network failure on one node and + not another — the precompile returns `Err(Fatal)` on the failing + node and `Ok(…)` elsewhere. This is a fork at the receipt level + even if EntityDB itself is perfectly deterministic. +5. **Re-execution / replay paths.** Block replay, `debug_traceBlock`, + trie re-derivation — anything that re-runs a historical block must + reach the same EntityDB state it had at the original execution. + If EntityDB's history isn't pinned to the chain history (rolling + forward only, with no reorg-aware revert), historical replay + produces different bytes than canonical execution. + +#### What the POC does not yet do, but production needs + +- **In-process embedding.** Today EntityDB is a separate process + reached over local HTTP. The transport-skew vector goes away if + EntityDB is linked into the node binary and committed atomically + with the EVM state. +- **Anchor commitments in EVM state.** Have the precompile read an + EVM-side commitment (e.g. an `EntityRegistry` storage slot) and + refuse calls whose response doesn't match. Divergence becomes a + deterministic revert rather than a silent fork. +- **Reorg-aware EntityDB writes.** EntityDB needs to revert / replay + in lockstep with the EVM, not just append; the existing ExEx already + has `arkiv_revert` / `arkiv_reorg` for this, and the precompile path + needs to participate in the same model. +- **Hardfork gate plus shadow-mode canary.** Activate the precompile + behind a timestamp, run nodes with it disabled-but-log-only first, + and only flip the gate once divergence has been observed to be + zero across a non-trivial period. + +None of these are in scope for the POC. The module docs in +`crates/arkiv-node/src/precompile.rs` carry the short-form caveat in +code. + +### 6.2 Gas pricing + +Gas cost has to be high enough that an adversary can't DoS the chain +with calls. There is no mempool-side filter for "this transaction calls +precompile X N times"; the only knob is the per-call gas charge. + +The POC charges a flat 5,000 gas, which is an order-of-magnitude guess, +not a calibrated number. Anything that lands in production needs a +proper analysis against the work the precompile actually performs +(network round-trip, EntityDB-side state mutation, etc.). + +### 6.3 Tracing parity + +`create_evm` and `create_evm_with_inspector` must apply the same +precompile mutations. Forgetting one means simulation/tracing endpoints +(`debug_traceTransaction`, `eth_call` under inspectors, etc.) see a +different precompile set than block execution, which silently +desynchronises trace output from canonical state. + +### 6.4 Concrete associated types beat projection forwarding + +Forwarding associated types through projections compiles but breaks +downstream bound checks: + +```rust +// Compiles, but downstream `Self::Tx: FromRecoveredTx<…>` bounds fail +// because Rust does not always normalise nested projections through +// trait bounds. +type Tx = as EvmFactory>::Tx; +``` + +Always spell concrete types in `EvmFactory` and `ConfigureEvm` impls +(`type Tx = OpTx;`, `type Spec = OpSpecId;`, etc.). The POC does this +in both `ArkivOpEvmFactory` and `ArkivOpEvmConfig`. + +### 6.5 `EthereumHardforks` supertrait import + +`OpHardforks: EthereumHardforks`, and `OpChainSpec: OpHardforks`. So +`OpChainSpec` does have `is_shanghai_active_at_timestamp` etc. — but +Rust requires the trait that *defines* a method to be in scope to call +it, not just a subtrait. The dev-payload-attributes builder needs: + +```rust +use alloy_hardforks::EthereumHardforks; // even though OpHardforks would seem to cover it +``` + +### 6.6 Sync HTTP from inside a precompile (POC choice) + +The POC's precompile reuses `EntityDbClient::rpc_call`, which uses +`tokio::task::block_in_place(|| Handle::current().block_on(…))` to make +a sync call inside an async runtime. This works only when: + +- the current task runs on a multi-threaded tokio runtime worker, **and** +- the call is *not* nested inside `spawn_blocking` (which is already + off-runtime; `block_in_place` panics there). + +Reth's block executor satisfies both today; the same trick is used by +the ExEx's `JsonRpcStore::handle_commit`. If a future reth refactor +moves block execution off the multi-threaded runtime (or into +`spawn_blocking`), the precompile will start panicking under load with +little warning. Worth keeping on the radar; an integration test that +actually exercises the precompile would catch a regression early. + +### 6.7 `OpPayloadBuilder` name collision + +`reth_optimism_node` has two unrelated `OpPayloadBuilder` types: + +- `reth_optimism_node::OpPayloadBuilder` — re-exported at the crate + root from `reth_optimism_payload_builder`; takes 3+ generics + (`OpPayloadBuilder`). +- `reth_optimism_node::node::OpPayloadBuilder` — the 1-generic + `OpPayloadBuilder` defined in `node.rs`, used by + `OpNode::ComponentsBuilder`. + +The crate-root re-export shadows the `pub use node::*`. For +component-builder use, always import via +`reth_optimism_node::node::OpPayloadBuilder`. The error if you get this +wrong is "missing generics". + +### 6.8 `EntityDbClient: Debug` + +`OpEvmConfig`'s `ConfigureEvm` impl bound includes `EvmF: Debug`; that +propagates to `Option>: Debug` and hence +`EntityDbClient: Debug`. The POC adds `#[derive(Debug)]` to +`EntityDbClient` for this reason. Worth flagging because the diagnostic +chain is indirect. + +### 6.9 Genesis collisions + +A precompile address must not appear in `chain.inner.genesis.alloc` for +any Arkiv chainspec. Arkiv reserves `0x44…0044` for `EntityRegistry`; +the POC uses `0x00…00aa01`. If you add a new chainspec, audit its alloc +against any precompile addresses you've registered. + +### 6.10 EIP-7910 introspection + +If precompiles are ever surfaced via an introspection RPC, the +`PrecompileId::custom("ARKIV_")` string is what's exposed. Pick +stable, namespaced strings — they end up in client tooling. + +### 6.11 Hardfork gating + +The POC is always-on whenever both flags are set. To gate by hardfork +instead, branch on `input.cfg_env.spec` (`OpSpecId`) inside +`create_evm` before the `apply_precompile` call. `OpHardforks` provides +fork activation predicates if the gate needs to be timestamp-based. + +### 6.12 Read-only state access via `EvmInternals` + +`PrecompileInput::internals` exposes hooks back into the EVM journal: +account balances, code, storage. If a precompile needs to read chain +state, this is how. Writes are technically possible but strongly +discouraged — they make gas accounting and reverts subtle. If the +precompile would write state, consider whether the right answer is +actually a normal contract. + +### 6.13 Precompile vs ExEx + +Use a precompile when EVM bytecode needs to call into native logic +during execution and observe the return value within the same +transaction. For after-the-fact side effects — what the existing +`arkiv` ExEx does for `EntityRegistry` events — an ExEx is the right +tool: it doesn't touch consensus and can't fork the chain. + +--- + +## 7. Diagnostic guide + +When a precompile-related change fails to compile, two error patterns +account for most of the noise. + +### "expected `OpEvmConfig<_>`, found `…`" + +The hint is the *closest existing impl*, not literally what's required. +The actual missing constraint is several layers up the bound chain. +Walk it like this: + +1. `OpAddOns: NodeAddOns` — bounds at + `op-reth/crates/node/src/node.rs:594`. The `EVB` slot's + `EngineValidatorBuilder` requirement is the most common culprit. +2. `BasicEngineValidatorBuilder: EngineValidatorBuilder` — + bounds at `reth/crates/node/builder/src/rpc.rs`. Requires + `Node::Evm: ConfigureEngineEvm<::ExecutionData>`. +3. For `OpEngineTypes`, `ExecutionData = OpExecData`. Verify your EVM + config impls `ConfigureEngineEvm` directly. + +If your EVM config is `OpEvmConfig<…, CustomFactory>` and you've not +added a wrapper newtype, you're hitting §5.2. + +### "trait bound … is not implemented for `OpAddOns<…>`" + +…where the components in the type contain a custom EVM type but the +AddOns shows defaults. That's §5.3 — `op_node.add_ons()` is bound to +`OpNode::ComponentsBuilder`'s default components. + +### Two `revm` versions in errors + +If you ever see `expected revm::context::X, found revm::context::X` +(same path, different versions), Cargo has resolved two `revm` majors +in parallel. The workspace currently pins a single `revm = "38.0.0"` +to avoid this. To localise the damage if it recurs, import revm types +via re-exporting crates in the precompile path: + +- `alloy_evm::revm::…` for general revm types, +- `op_revm::…` for OP-specific (`OpSpecId`, `OpHaltReason`), +- `alloy_op_evm::…` for `OpEvm`, `OpEvmContext`, `OpTxError`, `OpTx`. + +`arkiv-genesis` continues to use `revm` directly; that's fine because +its API surface is stable across recent revm majors. + +--- + +## 8. Reading order + +When picking this up cold, read in this order: + +1. **The POC itself.** `crates/arkiv-node/src/precompile.rs` — wires + together everything described above; all the patterns are visible + in <500 lines. +2. **Upstream Eth reference.** + `~/.cargo/git/checkouts/reth-e231042ee7db3fb7/27bfdde/examples/precompile-cache/src/main.rs` + — smallest end-to-end working example; non-OP, but demonstrates the + `EvmFactory` + `ExecutorBuilder` swap. +3. **API surface.** + `~/.cargo/registry/src/index.crates.io-*/alloy-evm-0.33.x/src/precompiles.rs` — + `Precompile`, `PrecompileInput`, `PrecompilesMap`, `DynPrecompile`. +4. **Wire types.** + `~/.cargo/registry/src/index.crates.io-*/revm-precompile-34.0.0/src/interface.rs` — + `PrecompileResult`, `PrecompileOutput`, `PrecompileError`, + `PrecompileHalt`. +5. **OP `EvmFactory`.** + `~/.cargo/git/checkouts/optimism-852bcbde357560e3/484be19/rust/alloy-op-evm/src/lib.rs` + ~lines 200–280 — `OpEvmFactory::create_evm`, the body our wrapper + delegates to. +6. **OP custom-EVM template.** + `~/.cargo/git/checkouts/optimism-852bcbde357560e3/484be19/rust/op-reth/examples/custom-node/` + — wrapper-`Node` and wrapper-`EvmConfig` patterns. Doesn't customise + precompiles, but is the structural model for §5.2 / §5.3. +7. **OP `ExecutorBuilder` and AddOns.** + `~/.cargo/git/checkouts/optimism-852bcbde357560e3/484be19/rust/op-reth/crates/node/src/node.rs` + — `OpExecutorBuilder` (~line 976), `OpAddOns` and its `NodeAddOns` + impl (~line 594), `OpNode::add_ons()` and the private + `OpLocalPayloadAttributesBuilder` we copy in §5.4 (~lines 74–137). +8. **OP `OpEvmConfig` and the 3-generic engine impls.** + `~/.cargo/git/checkouts/optimism-852bcbde357560e3/484be19/rust/op-reth/crates/evm/src/lib.rs` + lines 60–230, plus + `op-reth/crates/payload/src/lib.rs:35` — the impls described in + §5.2. + +--- + +## 9. Out of scope (today) + +- **State-mutating precompiles** via `EvmInternals`. The API exists; we + haven't investigated reverts/gas semantics. +- **Hardfork-gated activation** via `OpHardforks` predicates. POC is + always-on when its flag is set; gating is straightforward (§6.11) but + unimplemented. +- **System-call path.** + `Evm::transact_system_call` interactions if a precompile is ever + invoked from a system tx. +- **Automated tests.** Verification today is by sending real + transactions to a `--dev`-mining node and watching `mock-entitydb`'s + logs. For production, unit tests at the `Precompile::call` level plus + integration tests through the full executor are needed. +- **Performance.** Caching, warm/cold address lists, and the + `supports_caching` flag — only briefly noted in §3.1. + +These are the obvious next things to investigate. diff --git a/justfile b/justfile index 1e037b2..e5a8917 100644 --- a/justfile +++ b/justfile @@ -104,12 +104,16 @@ commitment key: {{ arkiv_cli }} query --key {{ key }} # Send an arkiv_query JSON-RPC request to the running node (proxied to EntityDB). -# Pass the params payload as a JSON string; default is `null`. -# Example: just query '{"key":"0x..."}' -query payload='null': +# `expr` is the query expression (must be a JSON string literal); `opts` is an +# optional options object. Both default to selecting all entities with no opts. +# Examples: +# just query # all entities, no opts +# just query '"type = \"nft\""' # filter by attribute +# just query '"$all"' '{"resultsPerPage":"0xa"}' # with options +query expr='"$all"' opts='null': curl -s -X POST {{ rpc }} \ -H 'Content-Type: application/json' \ - -d '{"jsonrpc":"2.0","id":1,"method":"arkiv_query","params":[{{ payload }}]}' + -d '{"jsonrpc":"2.0","id":1,"method":"arkiv_query","params":[{{ expr }}, {{ opts }}]}' # Read the current changeset hash hash: @@ -142,7 +146,8 @@ mock-entitydb port='9545': node scripts/mock-entitydb.js {{ port }} # Run arkiv-node in dev mode with JsonRpcStore pointing at mock EntityDB. -# Same setup as `node-dev` plus the ExEx forwarding to a local EntityDB. +# Same setup as `node-dev` plus the ExEx forwarding to a local EntityDB, +# and the EntityDB-write precompile at 0x00…0aa01 enabled (POC). node-dev-jsonrpc *args='': #!/usr/bin/env bash set -e @@ -163,6 +168,7 @@ node-dev-jsonrpc *args='': --datadir "$TMPDIR" \ --http \ --arkiv.db-url http://localhost:9545 \ + --arkiv.precompile \ --log.file.directory "$TMPDIR/logs" \ {{ args }} rm -rf "$TMPDIR" diff --git a/precompiles/.gitignore b/precompiles/.gitignore new file mode 100644 index 0000000..ccb0983 --- /dev/null +++ b/precompiles/.gitignore @@ -0,0 +1,3 @@ +cache/ +out/ +broadcast/ diff --git a/precompiles/README.md b/precompiles/README.md new file mode 100644 index 0000000..adfaa0a --- /dev/null +++ b/precompiles/README.md @@ -0,0 +1,43 @@ +# Precompile experiments + +End-to-end harness for the EntityDB-write precompile shipped in +`crates/arkiv-node/src/precompile.rs`. See `docs/custom-precompile.md` +for the design background and consensus caveats. + +## Layout + +``` +contracts/PrecompileCaller.sol # tiny harness: forwards calldata to 0x00…aa01 +foundry.toml # standalone foundry config (src=contracts/, out=out/) +run.sh # build → deploy → call → print round-trip +``` + +## Running an experiment + +Three terminals, in the workspace root: + +1. `just mock-entitydb` +2. `just node-dev-jsonrpc` (the recipe enables `--arkiv.precompile` by default) +3. `precompiles/run.sh` + +`run.sh` builds the contract, deploys it from the standard dev account, +calls `callPrecompile(0xdeadbeef)`, prints the resulting log, and reads +`lastStateRoot()` back. Cross-check terminal 1 — every successful +invocation should print an `arkiv_precompileWrite` request whose `data` +field matches what you passed in. + +To send different calldata: `CALLDATA=0xcafebabe precompiles/run.sh`. +Other knobs: `RPC_URL` (default `http://localhost:8545`), +`PRIVATE_KEY` (default = the standard hardhat key 0). + +## What this exercises + +- The wiring in `crates/arkiv-node/src/precompile.rs` (custom EvmFactory → + `ArkivOpEvmConfig` → `ArkivOpNode` → executor swap). +- The sync HTTP-from-precompile path + (`tokio::block_in_place` inside `EntityDbClient::rpc_call`). +- That `mock-entitydb.js` returns `{ stateRoot: ZERO_ROOT }` for the + unrecognised `arkiv_precompileWrite` method, which the precompile + parses and returns to the EVM as 32 bytes — so the + `PrecompileResult` event's `stateRoot` should be all zeroes when + pointed at the mock. diff --git a/precompiles/contracts/PrecompileCaller.sol b/precompiles/contracts/PrecompileCaller.sol new file mode 100644 index 0000000..2ef4314 --- /dev/null +++ b/precompiles/contracts/PrecompileCaller.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.28; + +/// @title PrecompileCaller — POC harness for the Arkiv EntityDB-write precompile. +/// @notice Calls the precompile at 0x00…aa01 with arbitrary calldata, expects a +/// 32-byte response (the EntityDB stateRoot), emits an event, and +/// returns the value so it can also be fetched via `cast call`. +contract PrecompileCaller { + /// @notice Address of the Arkiv EntityDB-write precompile. + /// @dev Must match `ARKIV_PRECOMPILE_ADDRESS` in + /// `crates/arkiv-node/src/precompile.rs`. + address public constant ARKIV_PRECOMPILE = 0x000000000000000000000000000000000000Aa01; + + /// @notice Last response returned by the precompile, for `cast call` retrieval. + bytes32 public lastStateRoot; + + /// @notice Emitted on every successful precompile call. + /// @param caller msg.sender of `callPrecompile` + /// @param data Calldata forwarded to the precompile + /// @param stateRoot 32-byte response from the precompile (EntityDB's reply) + event PrecompileResult(address indexed caller, bytes data, bytes32 stateRoot); + + /// @notice Forward `data` to the Arkiv precompile, store and emit the response. + /// @param data Arbitrary bytes; opaque to this contract, interpreted by EntityDB. + /// @return stateRoot The 32-byte EntityDB stateRoot returned by the precompile. + function callPrecompile(bytes calldata data) external returns (bytes32 stateRoot) { + (bool ok, bytes memory ret) = ARKIV_PRECOMPILE.call(data); + require(ok, "precompile call reverted"); + require(ret.length == 32, "precompile returned unexpected length"); + stateRoot = abi.decode(ret, (bytes32)); + lastStateRoot = stateRoot; + emit PrecompileResult(msg.sender, data, stateRoot); + } +} diff --git a/precompiles/foundry.toml b/precompiles/foundry.toml new file mode 100644 index 0000000..0935953 --- /dev/null +++ b/precompiles/foundry.toml @@ -0,0 +1,9 @@ +[profile.default] +src = "contracts" +out = "out" +libs = [] +solc_version = "0.8.28" +evm_version = "cancun" +optimizer = true +optimizer_runs = 200 +via_ir = false diff --git a/precompiles/run.sh b/precompiles/run.sh new file mode 100755 index 0000000..19817dd --- /dev/null +++ b/precompiles/run.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# +# End-to-end exercise of the Arkiv EntityDB-write precompile. +# +# Prereqs (each in its own terminal, in the workspace root): +# 1. just mock-entitydb +# 2. just node-dev-jsonrpc # has --arkiv.precompile baked in +# +# Then run this script. It will: +# - forge build the harness contract +# - deploy it from the standard dev account +# - invoke callPrecompile() with some calldata +# - print the transaction receipt's logs (the PrecompileResult event) +# - read back lastStateRoot via `cast call` for sanity +# +# Watch the mock-entitydb terminal: every successful invocation should log +# an `arkiv_precompileWrite` request with the calldata you passed in. + +set -euo pipefail + +cd "$(dirname "$0")" + +# Defaults; override via env if your setup differs. +RPC_URL="${RPC_URL:-http://localhost:8545}" +PRIVATE_KEY="${PRIVATE_KEY:-0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80}" +CALLDATA="${CALLDATA:-0xdeadbeef}" + +# 1. Build. +echo "[1/4] forge build" +forge build --root . --silent + +BYTECODE=$(jq -r '.bytecode.object' out/PrecompileCaller.sol/PrecompileCaller.json) + +# 2. Deploy. +echo "[2/4] deploy PrecompileCaller" +DEPLOY_TX=$(cast send \ + --rpc-url "$RPC_URL" \ + --private-key "$PRIVATE_KEY" \ + --create "$BYTECODE" \ + --json) +ADDRESS=$(echo "$DEPLOY_TX" | jq -r '.contractAddress') +echo " deployed at: $ADDRESS" + +# 3. Invoke. +echo "[3/4] callPrecompile($CALLDATA)" +CALL_TX=$(cast send \ + --rpc-url "$RPC_URL" \ + --private-key "$PRIVATE_KEY" \ + "$ADDRESS" \ + 'callPrecompile(bytes)' "$CALLDATA" \ + --json) +TX_HASH=$(echo "$CALL_TX" | jq -r '.transactionHash') +echo " tx: $TX_HASH" +echo " logs:" +echo "$CALL_TX" | jq -r '.logs[] | " address=\(.address) topic0=\(.topics[0]) data=\(.data)"' + +# 4. Read back. +echo "[4/4] lastStateRoot()" +ROOT=$(cast call --rpc-url "$RPC_URL" "$ADDRESS" 'lastStateRoot()(bytes32)') +echo " lastStateRoot = $ROOT" + +echo +echo "Done. Cross-check the mock-entitydb terminal: expect a request like" +echo " { method: 'arkiv_precompileWrite', params: [{ data: '$CALLDATA', caller: '...', value: '0x0' }] }" diff --git a/scripts/mock-entitydb.js b/scripts/mock-entitydb.js index 0e64d95..72ee564 100755 --- a/scripts/mock-entitydb.js +++ b/scripts/mock-entitydb.js @@ -36,13 +36,16 @@ const server = http.createServer((req, res) => { }); // Dummy response shape per method. Write-side methods get a state-root envelope -// (matching what the ExEx parses); arkiv_query echoes the inbound payload so the -// proxy round-trip is observable. +// (matching what the ExEx parses); read-side methods get noop-shaped payloads +// matching the documented arkiv-op-reth API so the proxy round-trip is observable. function dummyResultFor(req) { - const payload = Array.isArray(req.params) ? req.params[0] : req.params; switch (req.method) { case "arkiv_query": - return { ok: true, echo: payload }; + return { data: [], blockNumber: "0x0" }; + case "arkiv_getEntityCount": + return 0; + case "arkiv_getBlockTiming": + return { current_block: 0, current_block_time: 0, duration: 0 }; case "arkiv_ping": return "pong"; default: From ef56d42ed56db20adc18d94231d691e265004c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20=C3=93=20Mhuiris?= Date: Thu, 7 May 2026 12:29:06 +0100 Subject: [PATCH 2/3] more precompile docs --- docs/precompile-out-of-band-coupling.md | 1197 +++++++++++++++++++++++ 1 file changed, 1197 insertions(+) create mode 100644 docs/precompile-out-of-band-coupling.md diff --git a/docs/precompile-out-of-band-coupling.md b/docs/precompile-out-of-band-coupling.md new file mode 100644 index 0000000..c994f94 --- /dev/null +++ b/docs/precompile-out-of-band-coupling.md @@ -0,0 +1,1197 @@ +# Out-of-Band DB Coupling in Custom Precompiles + +A challenge analysis for the Arkiv L3 node design, with attention to the +multi-execution model that every EVM client implements and what it implies +for any precompile that talks to a sibling process. + +> **Status.** Analysis document. Frames the problem space; does not +> prescribe a single resolution. Sibling reading: the v2 design spec +> ([`reth-exex-entity-db-design-v2.md`](reth-exex-entity-db-design-v2.md)) +> for the architecture this analyses, and the existing +> [`custom-precompile.md`](custom-precompile.md) §6.1 for the working POC's +> treatment of the same problem at lower depth. + +--- + +## Contents + +- [0. Purpose and scope](#0-purpose-and-scope) +- [1. Precompiles: foundations](#1-precompiles-foundations) + - [1.1 What a precompile is](#11-what-a-precompile-is) + - [1.2 What a precompile sees](#12-what-a-precompile-sees) + - [1.3 What a precompile does *not* see](#13-what-a-precompile-does-not-see) + - [1.4 The precompile contract: consensus-pure return](#14-the-precompile-contract-consensus-pure-return) + - [1.5 The standard set is all pure of input bytes](#15-the-standard-set-is-all-pure-of-input-bytes) + - [1.6 Stateful precompiles via `EvmInternals`](#16-stateful-precompiles-via-evminternals) + - [1.7 An aside on registration: `PrecompilesMap`, `EvmFactory`, `create_evm`](#17-an-aside-on-registration-precompilesmap-evmfactory-create_evm) +- [2. The Arkiv L3 architecture in brief](#2-the-arkiv-l3-architecture-in-brief) + - [2.1 Components](#21-components) + - [2.2 The Arkiv precompile's role](#22-the-arkiv-precompiles-role) + - [2.3 The two-level staging discipline](#23-the-two-level-staging-discipline) + - [2.4 In-block state-root commitment](#24-in-block-state-root-commitment) +- [3. The core challenge: out-of-band coupling](#3-the-core-challenge-out-of-band-coupling) + - [3.1 What "out-of-band" means in this context](#31-what-out-of-band-means-in-this-context) + - [3.2 Reads vs writes — the load-bearing distinction](#32-reads-vs-writes--the-load-bearing-distinction) + - [3.3 The EVM has no rollback authority over external state](#33-the-evm-has-no-rollback-authority-over-external-state) + - [3.4 The implicit assumption that has to hold](#34-the-implicit-assumption-that-has-to-hold) +- [4. The multi-execution problem](#4-the-multi-execution-problem) + - [4.1 Why the EVM re-executes the same transaction](#41-why-the-evm-re-executes-the-same-transaction) + - [4.2 The execution paths in detail](#42-the-execution-paths-in-detail) + - [4.3 Why the precompile fires on every path](#43-why-the-precompile-fires-on-every-path) + - [4.4 Empirical evidence](#44-empirical-evidence) +- [5. How multi-execution interacts with the v2 staging discipline](#5-how-multi-execution-interacts-with-the-v2-staging-discipline) + - [5.1 The hierarchy is sound for one linear execution stream](#51-the-hierarchy-is-sound-for-one-linear-execution-stream) + - [5.2 It does not compose across the seven paths](#52-it-does-not-compose-across-the-seven-paths) + - [5.3 Concrete failure modes](#53-concrete-failure-modes) + - [5.4 The atomicity claim is path-restricted](#54-the-atomicity-claim-is-path-restricted) +- [6. Why this is hard to fix from inside the precompile](#6-why-this-is-hard-to-fix-from-inside-the-precompile) + - [6.1 What the precompile can and cannot know](#61-what-the-precompile-can-and-cannot-know) + - [6.2 The discrimination problem (briefly)](#62-the-discrimination-problem-briefly) + - [6.3 Context-sensitivity violates the precompile contract](#63-context-sensitivity-violates-the-precompile-contract) +- [7. Resolution shapes and their trade-offs](#7-resolution-shapes-and-their-trade-offs) + - [7.1 Session-id-aware DB](#71-session-id-aware-db) + - [7.2 Idempotent staging keyed by consensus-stable identifiers](#72-idempotent-staging-keyed-by-consensus-stable-identifiers) + - [7.3 `EvmFactory` routing per call site](#73-evmfactory-routing-per-call-site) + - [7.4 Full DB idempotency with semantic restrictions](#74-full-db-idempotency-with-semantic-restrictions) + - [7.5 Read-only precompile + ExEx-side writes](#75-read-only-precompile--exex-side-writes) + - [7.6 Comparison](#76-comparison) +- [8. The deeper question: can a sibling DB be in the consensus envelope?](#8-the-deeper-question-can-a-sibling-db-be-in-the-consensus-envelope) +- [9. Open questions](#9-open-questions) +- [10. Glossary](#10-glossary) + +--- + +## 0. Purpose and scope + +This document explains, in depth, why a custom precompile that communicates +with a sibling process — Arkiv's `EntityDB` is the concrete case, but the +analysis applies to any external state machine — runs into structural +problems that are not present for any precompile in the canonical Ethereum +set, and that are not resolved by the per-tx / per-block staging discipline +proposed in the v2 design. + +The intended reader is someone who knows broadly what an L2/L3 execution +client does but has not had to internalise the precompile contract or the +multi-execution model the EVM relies on. The document therefore starts at +foundations (§1, what a precompile actually is and what it sees), builds +the Arkiv architecture on top (§2), and only then turns to the challenge +itself (§3-§6) and the solution space (§7-§8). + +What this document does **not** do: + +- Pick a single resolution. §7 enumerates the shapes that survive analysis; + the choice depends on operational and semantic preferences out of scope + here. +- Re-derive the v2 design. It treats the spec as given. +- Cover the orthogonal "is the DB itself deterministic" question + (floating-point, hash-map iteration, OS RNG, etc.). That's a separate + audit, mostly DB-internal. + +--- + +## 1. Precompiles: foundations + +### 1.1 What a precompile is + +A precompile is a fixed-address handler the Ethereum Virtual Machine +consults instead of running user-supplied bytecode. When the EVM +encounters a `CALL`, `STATICCALL`, or `DELEGATECALL` to one of a small set +of addresses, it does not enter the bytecode interpreter for that call. +It dispatches to a native (Rust, in reth's case) function that takes the +call's calldata, gas budget, caller, value, and a handle into the +journalled EVM state, and returns either output bytes plus a gas charge, +or a halt status. + +The canonical Ethereum precompile set is: + +| Address | Purpose | +|---|---| +| `0x01` | `ECRECOVER` — recover an address from an ECDSA signature | +| `0x02` | `SHA256` — SHA-2 digest | +| `0x03` | `RIPEMD160` | +| `0x04` | `IDENTITY` — copy input to output | +| `0x05` | `MODEXP` — modular exponentiation | +| `0x06`-`0x08` | `BN256` curve operations (add, mul, pairing) | +| `0x09` | `BLAKE2F` | +| `0x0a` | `POINT_EVALUATION` — KZG proof verification (EIP-4844) | +| `0x0b`-`0x11` | BLS12-381 family (EIP-2537) | + +Every entry is a pure cryptographic primitive. Every entry's output is a +deterministic function of its calldata bytes. None of them read state, +block context, or anything outside their input. + +The reason precompiles exist at all is performance. `MODEXP` in EVM +bytecode is possible but staggeringly expensive; `MODEXP` as a native +function written in C or Rust is fast. The precompile mechanism gives the +EVM a way to expose efficient native implementations of operations that +would otherwise be ruinous to do in-EVM, while keeping the cost predictable +(precompiles charge gas like any other call) and the semantics +deterministic (every node runs the same native function). + +A custom precompile, in an L2/L3 context, is the same mechanism applied +to chain-specific functionality. Optimism's stack has had pre-deployed +system contracts since Bedrock; some of those have native precompile +acceleration. EigenLayer's precompiles, the BLS12-381 set in EIP-2537, +and various chains' SNARK-verifier precompiles all follow the same +pattern: a fixed address, a native function, gas accounted in calldata. + +The Arkiv precompile in the v2 design is in the same family of mechanism +but does something none of the canonical precompiles do: it talks to a +sibling process over JSON-RPC. That difference is the entire subject of +this document. + +### 1.2 What a precompile sees + +A precompile in modern reth (revm-precompile 34, alloy-evm 0.33) is +called with the following input shape: + +```rust +pub struct PrecompileInput<'a> { + pub data: &'a [u8], // calldata + pub gas: u64, // gas limit available + pub reservoir: u64, // EIP-8037 state-gas reservoir + pub caller: Address, // msg.sender + pub value: U256, // call value + pub target_address: Address, + pub is_static: bool, // STATICCALL? + pub bytecode_address: Address, + pub internals: EvmInternals<'a>, // hooks back into journalled state +} +``` + +Plus the block environment, which is bound at `EvmFactory::create_evm` +time (so it is part of the EVM instance the precompile is registered on, +rather than per-call): + +- block number, timestamp, base fee, blob base fee, prev randao, + beneficiary (coinbase), gas limit; +- chain ID via the EVM config; +- `EvmInternals` provides reads (and, with care, writes) of journalled + EVM state — account balances, code, storage slots. + +That is the complete set of inputs available. Every value in the list is +**consensus-deterministic** — every honest node executing this transaction +on this block sees the same bytes. + +### 1.3 What a precompile does *not* see + +The list of things `PrecompileInput` does not expose is more interesting +than the list of things it does: + +- **Which execution path called it.** A precompile cannot tell whether + it is running inside the canonical block executor, the gas estimator, + the mempool admission validator, the engine-API forkchoice validator, + or `debug_traceBlock`. The EVM dispatches identically in all cases. +- **The transaction hash.** During payload building and engine-API + validation, the tx hash *is* known and is consensus-stable. During gas + estimation against pending state it is not. There is no field on + `PrecompileInput` to surface this either way. +- **The position of the current call in the broader execution.** Block + index, transaction index within the block, call-frame depth, intra-tx + precompile invocation counter — none are surfaced. +- **Whether the surrounding transaction will end up canonical.** This is + a future fact about consensus that no execution path can know at the + time the precompile runs. +- **Whether the surrounding call frame will eventually revert.** The + precompile produces its result and gas charge in isolation; the EVM + decides revert behaviour later, by which point the precompile has + long since returned. + +The precompile API was designed under the assumption that the answer is +a function of input + journalled state, full stop. Anything not on that +list isn't on `PrecompileInput` because nothing the standard precompiles +do needs it. + +### 1.4 The precompile contract: consensus-pure return + +The property the EVM relies on is simple: **two honest nodes given the +same precompile inputs must compute the same outputs**. If they don't, +they produce different receipts, different state roots, different block +hashes, and the chain forks at that block. + +The strongest version of this property — "output is a function of +calldata bytes alone" — is what every standard precompile satisfies. But +the EVM relies on a slightly weaker version that admits richer inputs as +long as those inputs are themselves consensus-deterministic: + +> The precompile's return must be a deterministic function of +> consensus-visible state: calldata, caller, value, the call-site context +> (`is_static`, addresses), the journalled EVM state at the call site, +> and the block environment. + +`SLOAD` reads journalled storage; that's allowed. `BLOBHASH` (an opcode, +not a precompile, but the same principle applies to opcodes that have +precompile-like signatures) reads `tx.blob_versioned_hashes[i]`; also +allowed. Block-environment opcodes read `block.timestamp`, +`block.coinbase`, etc. All of these are consensus-deterministic — every +node has them — even though none of them are functions of calldata alone. + +A precompile may read any of these. It may read state via `EvmInternals`. +It may read the block env. None of that is a problem for consensus. + +What it may **not** do, without extra work to bring the source into the +consensus envelope, is read or write anything outside this set: the wall +clock, an OS RNG, a sibling process, a TCP socket. None of those are +consensus-determined; different nodes can — and routinely do — disagree +about what they would return at any given moment. + +### 1.5 The standard set is all pure of input bytes + +It's worth spelling out: there is no "impure precompile" in the canonical +Ethereum spec, in the sense of "depends on state outside calldata". Every +single one is a stateless function from bytes to bytes: + +```rust +fn ecrecover(data: &[u8]) -> Result { ... } +fn sha256(data: &[u8]) -> Result { ... } +fn modexp(data: &[u8]) -> Result { ... } +// ... +``` + +This is the strongest possible form of "consensus-pure". The precompile +mechanism *permits* richer inputs (via `EvmInternals` for journalled +state access), but no canonical precompile actually uses them. Custom +precompiles in L2 and L3 deployments that read storage do exist — for +example, precompiles that act as efficient lookup tables against system +contract storage — and they're consensus-safe. But "talks to a sibling +process" is not on the list of things any precompile in production does +on a public chain today. + +### 1.6 Stateful precompiles via `EvmInternals` + +`EvmInternals` is the API surface that lets a precompile read (and, with +care, write) journalled EVM state. The relevant operations include: + +- Read an account's balance, nonce, code hash, code. +- Read a storage slot. +- Write a storage slot (rare; gas accounting and revert semantics are + subtle). +- Spawn a sub-call into another contract or precompile. + +Writes via `EvmInternals` go through the journal, which means they +participate in the EVM's normal revert / commit machinery. If the +surrounding call frame reverts, the journal undoes the writes. If the +transaction reverts, same. If the block ends up not canonical, the +entire state diff is discarded by consensus. + +The journal is the EVM's tool for owning the consequences of every +mutation it permits. Nothing the EVM authorises is irreversible inside +the EVM's universe. This is what makes intra-tx revert safe, what makes +DELEGATECALL into untrusted code safe, and — critically for what follows +— what *isn't* available for mutations that go outside the EVM's +universe. + +### 1.7 An aside on registration: `PrecompilesMap`, `EvmFactory`, `create_evm` + +Because the resolution discussion in §7 turns on where in the reth stack +a precompile gets installed, it's worth a brief tour. + +A precompile is attached to a specific EVM instance via `PrecompilesMap`, +a mutable container that the EVM consults on every call. `PrecompilesMap` +is populated at EVM construction time by `EvmFactory::create_evm` (or +`create_evm_with_inspector` for tracing), which is called once per fresh +EVM instance. + +Reth creates fresh EVM instances *frequently*: once per gas estimation +RPC call, once per mempool admission, once per pending-state query, once +per block payload build, once per canonical block execution, once per +engine-API validation, once per `debug_traceBlock` request. Each call +to `create_evm` produces a brand-new EVM with its own `PrecompilesMap`. +The same `EvmFactory` is reused across all of them. + +The Arkiv-side wiring is therefore: + +- One `EvmFactory` (`ArkivOpEvmFactory`) wraps `OpEvmFactory` and + installs the Arkiv precompile in every `EvmEnv` it produces. +- That factory holds an `Arc` — the IPC handle to the DB. +- Every fresh EVM the factory produces has the precompile in its + `PrecompilesMap`, sharing the same DB handle. + +The factory is deliberately path-agnostic. It does not know — and has no +mechanism to know — *which* of the seven execution paths is asking for +an EVM at any given moment. This is the fact that §6 turns on. + +--- + +## 2. The Arkiv L3 architecture in brief + +### 2.1 Components + +The v2 design splits the L3 node into four cooperating subsystems plus +one off-process state machine: + +``` +┌───────────────────────────────────────────────────────────┐ +│ arkiv-op-reth │ +│ │ +│ EntityRegistry SC ──── (EVM call) ──→ Arkiv Precompile │ +│ (ownership + │ │ +│ lifetime + fees) │ JSON-RPC │ +│ │ │ +│ Custom BlockExecutor ─────────────────────┤ │ +│ (finish: arkiv_commitBlock + system call) │ +│ │ │ +│ Arkiv ExEx ───────────────────────────────┤ │ +│ (ChainReverted, ChainReorged only) │ │ +│ ▼ │ +└─────────────────────────────────────────────┬─────────────┘ + │ + ▼ + ┌─────────────────────────┐ + │ EntityDB (separate │ + │ process; HTTP JSON-RPC)│ + └─────────────────────────┘ +``` + +The EVM contract surface is intentionally narrow: `EntityRegistry` holds +only `(owner, expiresAt)` per entity plus a per-block `arkiv_stateRoot` +slot. Everything else — payload, attributes, query index, the entity +trie — lives in EntityDB. + +### 2.2 The Arkiv precompile's role + +The precompile is the per-transaction synchronous bridge from EVM +execution into EntityDB. Its responsibilities are: + +1. Decode the operation batch from calldata. +2. Compute static gas from calldata (`Σ f(op_type, payload_bytes, + attr_count, ...)`), with no DB-side lookups. +3. Charge that gas to the EVM up front. +4. Dispatch the batch to EntityDB synchronously via `arkiv_applyTx`. +5. Map the DB's response (`ok` / `revert` / `fatal`) onto a + `PrecompileResult`. + +It is stateless in the EVM sense — no journalled storage of its own — but +holds an `Arc` for IPC. It is registered at a fixed +address (`0x...0900` in the v2 spec) via the custom `EvmFactory` +described above. + +Crucially, **the precompile is the only path through which per-tx +operations reach EntityDB**. There is no parallel ExEx-driven path for +data; the v2 design eliminated that to make tx-level atomicity possible. + +### 2.3 The two-level staging discipline + +EntityDB maintains a three-tier write hierarchy: + +``` +arkiv_applyTx → per-tx staging (in-memory; discardable) + status == ok ↓ promote + → per-block staging (CacheStore; in-memory) +arkiv_commitBlock ↓ flush + → PebbleDB (durable, single atomic batch) +``` + +Each tier is meant to be tied to a specific EVM event: + +| Tier | Tied to | Reverts when | +|---|---|---| +| Per-tx staging | EVM tx execution | DB returns `revert`; or the EVM tx reverts before `commitBlock` (in principle) | +| Per-block staging (`CacheStore`) | The block being assembled | `BlockExecutor::finish` returns `Err`; block doesn't seal | +| Durable (PebbleDB) | Block sealing | Reorg, via `arkiv_revert` / `arkiv_reorg` from the ExEx | + +The atomicity claim made by the spec is: **a tx's chain effects and DB +effects commit or revert together**. A DB-rejected payload reverts the +EVM tx atomically (via `Err(PrecompileError)` from the precompile), and +a successfully-processed tx's DB writes are durable iff the surrounding +block seals. + +This is the central innovation of v2 over v1 (which had post-seal +asynchronous commit and could silently desynchronise chain and DB +state). It is also where the multi-execution problem bites, as §5 will +develop. + +### 2.4 In-block state-root commitment + +The custom `BlockExecutor` wraps `OpBlockExecutor` and overrides +`finish`. Inside `finish`, before reth computes the L3 state root for +block N, the wrapper: + +1. Checks a precompile-set fatal flag; if set, returns `Err(...)` and + the block does not seal. +2. Calls `arkiv_commitBlock(N, blockHash_N)` on the DB. The DB flushes + per-block staging to PebbleDB atomically and returns `arkiv_stateRoot_N`. +3. Issues `evm.transact_system_call(SYSTEM_ADDRESS, ENTITY_REGISTRY_ADDRESS, + abi.encode(N, arkiv_stateRoot_N))` to invoke + `EntityRegistry.setArkivStateRoot(N, root)`. +4. Delegates to `inner.finish()`. + +Because the system call's storage write completes before reth computes +the L3 state root, block N's state root *already* commits to +`arkiv_stateRoot_N`. There is no N+1 lag. + +This pattern is exactly the EIP-4788 (parent beacon block root) shape: +a system call from inside `finish` writes a value into a known contract's +storage in the same block whose state root is about to be computed. The +v2 design is reusing a well-trodden reth pattern for a non-canonical +purpose. + +--- + +## 3. The core challenge: out-of-band coupling + +### 3.1 What "out-of-band" means in this context + +"Out-of-band" here has a specific technical meaning. Every other +interaction the EVM has with state — `SLOAD`, `SSTORE`, `BALANCE`, +`CALL` into a contract, even reads of the block environment — happens +through channels the EVM owns and can reason about: + +- The journalled state machine, with full revert / commit authority. +- The block environment, fixed at the start of the block. +- The transaction envelope, fixed at the start of the tx. + +The Arkiv precompile's interaction with EntityDB is **not** through any +of these channels. It is over a TCP socket to a sibling process, via a +JSON-RPC protocol that lives outside the EVM's universe. The EVM has no +mechanism to: + +- Roll the DB call back if the surrounding call frame reverts. +- Roll it back if the surrounding tx reverts after the call returns. +- Notice if the DB returned different bytes on a different node. +- Coordinate timing or ordering with the DB beyond the synchronous reply. + +This is what "out-of-band" means: the channel is invisible to every +mechanism the EVM has for owning the consequences of mutations. + +### 3.2 Reads vs writes — the load-bearing distinction + +§1.4 noted that the EVM permits richer-than-calldata inputs as long as +they are consensus-deterministic. `SLOAD`, `BLOBHASH`, block-env opcodes +all qualify. None of them are pure functions of calldata, but all of +them are pure functions of consensus state. + +The crucial thing every one of those constructs has in common: **the +act of consulting the value does not change anything observable**. +Calling `SLOAD` six times during the various execution paths reads the +same value six times and the world is identical to having called it +once. `BLOBHASH` is a read of the tx envelope; reading it is +inconsequential. `TIMESTAMP`, `COINBASE`, `BASEFEE` — all reads of +fixed block data; idempotent by nature of being reads. + +The Arkiv precompile is doing something different. The natural reading +of the v2 spec is that `arkiv_applyTx` advances DB staging state — that +is, it has a side effect on something other than journalled EVM state. +Even within the carefully-engineered staging hierarchy, the precompile +is fundamentally a write operation against a state machine that lives +outside the EVM. + +The asymmetry between reads and writes, in this context, is: + +| Property | Read-style (e.g. `SLOAD`, `BLOBHASH`) | Write-style (Arkiv precompile) | +|---|---|---| +| Effect of repeated execution | Identical observation, no mutation | Each call mutates staging | +| Rollback authority | EVM journal (for `SLOAD`/`SSTORE`) | None inside the EVM; DB-internal only | +| Path independence | Same answer on every path | Each path leaves a trace | +| Failure semantics | Deterministic (read of consensus state) | Network failure is per-path, per-node | + +Reads of consensus state are categorically safer than writes to external +state. This isn't a polemic point; it's the reason the canonical +precompile set has zero examples of out-of-band writes. + +### 3.3 The EVM has no rollback authority over external state + +The EVM's revert machinery is layered: + +- **Call frame revert.** A `REVERT` opcode (or an exhausted gas budget, + or a few other fault conditions) discards the current frame's journal + entries. +- **Transaction revert.** If the top-level call ends in a revert, all + state changes in the tx — across every nested call, every contract + the tx touched — are unwound. +- **Block revert.** A failed block doesn't seal; nothing the block did + is canonical. +- **Reorg.** A canonical block that gets replaced by a different chain + has all of its state changes implicitly undone — the new canonical + chain doesn't reference them. + +Every one of these mechanisms operates on the journalled state. The +EVM has the journal entries, applies them on commit, throws them away +on revert. This is uncontroversial machinery; it is how the EVM has +worked since 2015. + +A precompile that writes to an external system bypasses every layer: + +- A `REVERT` opcode in the calling contract does **not** undo the DB + call; the DB has already responded. +- A tx revert does **not** undo the DB call. +- A block that fails to seal does **not** undo any DB calls made during + its execution. +- A reorg does **not** undo any DB calls made during the + now-non-canonical chain. + +The DB has to handle all of these revert events itself, on its own +schedule, with its own machinery. The v2 design's two-level staging is +an attempt to put DB-side machinery in place for the first three (with +the ExEx handling the fourth). This is the right kind of design move, +but the staging mechanism interacts with the multi-execution model in +ways the v2 spec doesn't yet address — which §5 develops. + +### 3.4 The implicit assumption that has to hold + +For the v2 design to be consensus-safe, an assumption has to hold: + +> **EntityDB is part of the protocol.** Its state is a deterministic +> function of canonical chain history. Every honest node, given the +> same sequence of canonical blocks, reaches the same DB state. Any +> deviation is detected (because `arkiv_stateRoot` is committed +> on-chain). + +This is a strong assumption. It implies: + +- The DB has no non-determinism internally (no floating-point, no + hash-map iteration order, no OS RNG, no wall-clock dependence). +- The DB is reorg-aware — its state can be unwound to a previous block + and re-rolled forward differently, deterministically. +- The DB-side handling of multi-execution (§5) does not produce + different state across nodes. +- The DB is available on every node (transport failure on one node and + not another is a fork — the failing node returns `fatal`, the others + return `ok`). + +The v2 spec takes most of this as given (the DB-internal trajectory in +[`arkiv-storage-service/architecture.md`](../arkiv-storage-service/architecture.md) +covers determinism and reorg handling). What it doesn't yet address is +the multi-execution piece, which is the subject of the rest of this +document. + +--- + +## 4. The multi-execution problem + +### 4.1 Why the EVM re-executes the same transaction + +It is tempting to think of "executing a transaction" as a singular event +— the user submits, the chain processes it once, end of story. The +reality is the opposite: from the moment a tx leaves the user's wallet +until it ends up in a fully-synced node's history, it is executed many +times, by many different code paths, for many different reasons. None +of these executions are spurious or removable; each serves a purpose +the chain cannot operate without. + +This is true of every Ethereum-class client. It is not a reth-specific +implementation detail; it is intrinsic to how a permissionless, +gas-metered, mempool-driven blockchain works. + +### 4.2 The execution paths in detail + +The seven paths a single user-level transaction commonly traverses, in +roughly the order it encounters them: + +#### Path 1: `eth_estimateGas` + +Before the user signs the tx, their wallet (or `cast send`, or a dApp's +RPC client) typically calls `eth_estimateGas` to figure out an +appropriate gas limit. The node executes the tx against the current +pending state, observes how much gas it consumes, and returns that +number plus a safety margin. + +Why it can't be skipped: gas is finite and pre-paid. A user-supplied +gas limit that's too low burns the user's gas without making progress. +Wallets that don't probe before signing are routinely hostile to users. + +Where it touches the precompile: `EvmFactory::create_evm` is called to +build a fresh EVM for the estimation; that EVM has the Arkiv precompile +installed; the tx's call to `EntityRegistry.execute(...)` goes through +the precompile. + +#### Path 2: Mempool admission + +When the signed tx hits the node's mempool, the mempool validator +(`OpTransactionValidator` for OP-stack chains) partially executes it +to verify a number of properties: the sender has balance, the gas +limit is plausible against current state, no obvious pre-flight +faults. This is a real EVM execution against a snapshot of pending +state. + +Why it can't be skipped: the alternative is admitting transactions +that are guaranteed to fail, which DoSes the mempool. Every Ethereum +client does some form of mempool-level pre-flight. + +Where it touches the precompile: same — fresh EVM, precompile +installed, dispatch happens. + +#### Path 3: Pending-block / `eth_call` against pending state + +Tools and dApps that want to know "what would the result be if I +submitted X right now?" call `eth_call` against the pending block. +The node executes the call against pending state; if the call goes +through `EntityRegistry.execute(...)`, the precompile fires. + +Why it can't be skipped: pending state is a useful query target; a +chain that doesn't expose it is much less ergonomic. + +Where it touches the precompile: again, fresh EVM, precompile +installed. + +#### Path 4: Block payload building + +When the node is the proposer (or the block-builder, in a +proposer-builder split), it has to assemble candidate blocks. For each +candidate, every tx is executed against the candidate's state, in +order, with full receipts produced. This is the path that determines +which ordering of pending txs makes the best block. + +Why it can't be skipped: this is *how* a block is assembled. There is +no shortcut. + +Where it touches the precompile: every tx's execution during payload +construction goes through the precompile. The proposer builds at least +one candidate block per slot. + +#### Path 5: Canonical block execution + +Once a block is sealed and considered canonical (post-engine-API +acceptance), the block executor produces the canonical state diff for +that block. This is the "real" execution — the one whose state root +goes on chain. + +Where it touches the precompile: canonical execution goes through the +precompile. This is the only path the Arkiv staging discipline is +explicitly designed for. + +#### Path 6: Engine API validation + +The engine API delivers blocks for validation: the consensus client +hands the execution client a payload, says "is this valid?", and +expects a yes/no plus the resulting state root. The execution client +re-executes the entire block to verify. + +Why it can't be skipped: an execution client that doesn't validate +delivered payloads is trusting the consensus client implicitly, which +defeats the point of having two separately-implemented clients. + +Where it touches the precompile: the validator constructs an EVM, the +EVM has the precompile, every tx in the payload runs through the +precompile. + +#### Path 7: Reorg replay and `debug_traceBlock` + +Reorgs cause blocks to be unwound and replaced; the new canonical chain +has to be executed forward from the common ancestor. Historical replay +(`debug_traceBlock`, snapshot regeneration, full sync from genesis) +re-executes blocks that have already been canonical. + +Why it can't be skipped: reorgs are a normal, expected event on any +PoS chain (and OP-stack rollups). Historical replay is a debugging +necessity. Re-syncing nodes is operational reality. + +Where it touches the precompile: every replayed block re-runs every tx +through the precompile. + +### 4.3 Why the precompile fires on every path + +All seven paths converge on `EvmFactory::create_evm`. There is no path +in reth that constructs an EVM by some other route. Therefore every +path's EVM has the same `PrecompilesMap`, including the Arkiv +precompile. + +There is no signal in `PrecompileInput` or `EvmEnv` that distinguishes +"I am running inside the canonical block executor" from "I am running +inside a gas estimator". §6.2 returns to whether such discrimination +could be added; for the moment, the relevant fact is that **the +precompile cannot tell these paths apart**, and it must produce the +same return on all of them, or the chain forks. + +### 4.4 Empirical evidence + +The working POC ([`crates/arkiv-node/src/precompile.rs`](../crates/arkiv-node/src/precompile.rs)) +is configured to log every JSON-RPC call from the precompile to a +mock-entitydb. A single `cast send` of `callPrecompile(0xdeadbeef)` +produces six byte-identical requests on the mock — same caller, same +calldata, same value, only the JSON-RPC envelope `id` advancing: + +```json +{ "id": 77, "jsonrpc": "2.0", "method": "arkiv_precompileWrite", + "params": [{ "caller": "0x9fe4…6e0", "data": "0xdeadbeef", "value": "0x0" }] } +{ "id": 78, …same params… } +{ "id": 79, …same params… } +… +{ "id": 82, …same params… } +``` + +This is not a bug in the POC. It is the structural property §4.2 +described, manifesting in concrete telemetry. Each request corresponds +to one of the seven execution paths, in roughly the order reth invokes +them. See [`custom-precompile.md`](custom-precompile.md) §6.1 for the +full breakdown. + +The working POC is intentionally tolerant of this duplication — the +mock returns a constant `0x000…0`, so all six calls produce identical +EVM-visible returns and no fork results. As §3.4 noted, this only works +because the mock is trivially deterministic. The moment EntityDB's +response or stored state depends on prior writes, the duplication +becomes consensus-affecting. + +--- + +## 5. How multi-execution interacts with the v2 staging discipline + +### 5.1 The hierarchy is sound for one linear execution stream + +The two-level staging design (§2.3) is sound *if* the only execution +stream is canonical: payload build → `commitBlock` → durable. Trace it +through: + +1. The proposer builds block N by executing each tx in order. For each + tx: `arkiv_applyTx` writes to per-tx staging; on `ok`, promotes to + per-block staging. +2. The proposer's `BlockExecutor::finish` calls `arkiv_commitBlock`. The + DB flushes per-block staging to PebbleDB atomically. +3. The state root is system-called into `EntityRegistry`. Block N seals. + +In this linear stream, every EVM event has a corresponding DB event: + +| EVM event | DB event | +|---|---| +| `arkiv_applyTx` returns `ok` | per-tx staging exists | +| EVM tx commits | per-tx promotes to per-block | +| `arkiv_applyTx` returns `revert` | per-tx staging discarded | +| EVM tx reverts (after precompile returns ok) | ??? | +| `BlockExecutor::finish` runs | `arkiv_commitBlock` flushes per-block | +| Block doesn't seal | per-block staging is now in PebbleDB but no canonical block references its state root | + +That penultimate row is already a question — it's not addressed by the +v2 spec — but in the canonical-only world it can probably be handled +by the staging machinery if the DB observes "the precompile returned +ok but no commitBlock was issued, so unwind" on some timeout or +session boundary. + +In the canonical-only world, the design largely works. + +### 5.2 It does not compose across the seven paths + +The actual reth runtime does not have one execution stream. It has +seven (or more, depending on how you count `debug_traceBlock`, +`eth_call` against historical state, etc.). Each path constructs its +own EVM, dispatches through the same precompile, hits the same DB +endpoint. + +The DB has *one* per-block staging area at a time. There is no +mechanism in the v2 spec for the DB to maintain seven separate +contexts and discard six of them at the right times. The staging +hierarchy was designed assuming a single-threaded canonical execution; +multi-execution is the case it doesn't model. + +What actually happens: + +1. Wallet calls `eth_estimateGas` for tx X. `arkiv_applyTx` writes to + per-tx staging. +2. Wallet does not submit yet (or submits, no matter which). +3. The status returned to the precompile is `ok`; per-tx staging + promotes to per-block staging. +4. ... and now per-block staging contains the effects of a tx that has + not been included in any block. + +Every subsequent path that hits `arkiv_applyTx` for the same tx (mempool +admission, pending-block queries, payload building, validation, eventual +canonical execution) tries to apply the same tx to a per-block staging +that already has its effects in it. What does the DB do? + +- If it just appends to per-block staging, the same write happens N + times and accumulates (problematic for any non-idempotent operation). +- If it deduplicates by some payload-derived key, it has to be careful + not to dedupe legitimate identical user calls in different blocks + (the problem from + [`custom-precompile.md`](custom-precompile.md) §6.1 — distinguishing + replays from legitimate replays). +- If it carries no awareness of canonical-vs-speculative, every + speculative execution permanently dirties per-block staging. + +None of these answers is in the v2 spec. + +### 5.3 Concrete failure modes + +Three concrete bad outcomes the v2 design as written admits: + +#### Gas estimation pollutes per-block staging + +A user calls `eth_estimateGas` for an `EntityRegistry.execute([create +0x...])`. The precompile dispatches `arkiv_applyTx`. The DB validates, +returns `ok`, the precompile returns to the EVM, the per-tx staging +promotes to per-block. The user looks at the gas estimate and decides +not to submit. + +The next block's `arkiv_commitBlock` flushes per-block staging to +PebbleDB. The "create" the user never submitted is now durable. The +state root reflects an entity that no canonical transaction created. + +Two nodes in the network may have wildly different gas-estimation +traffic patterns. They would compute different `arkiv_stateRoot`s. +They would fork. + +#### Engine API validation produces a different state root + +The proposer builds block N. Every tx's `arkiv_applyTx` writes to +per-block staging. The block is sealed and broadcast. + +The validator on a different node receives the block. It re-executes +every tx, including their `arkiv_applyTx` calls. The validator's per-block +staging *also* has all those writes — but the validator started with the +canonical state at block N-1, not with the proposer's pre-build state. + +If the proposer had had any prior pre-canonical traffic that polluted +its per-block staging (gas estimations, mempool admissions of txs not +in block N, etc.), the validator's `arkiv_stateRoot_N` will differ +from the proposer's. The block is rejected as invalid. + +#### Reorg replay re-applies operations from staging that no longer exists + +Block N is canonical, `arkiv_stateRoot_N` is committed, durable state +contains block N's writes. A reorg replaces block N with N'. The ExEx +calls `arkiv_revert` (or `arkiv_reorg`); the DB reverts to N-1's state +via repopulation from the trie. + +The new chain's transactions need to be re-applied. The v2 spec says +the ExEx must "reconstruct the new chain's tx batches from logs (or +from the contract's own decoded calldata via receipts) because the +precompile's per-tx staging on the old chain has been discarded". + +This is correct — but the same machinery that re-applies via `applyTx` +during reorg replay is the precompile path, which is also the path +that fires during gas estimation and mempool admission. The DB is now +fielding `applyTx` calls from two distinct contexts (reorg replay vs +ongoing pre-canonical traffic) with no way to tell them apart. + +### 5.4 The atomicity claim is path-restricted + +The v2 spec's atomicity claim — "DB effects commit or revert with the +EVM tx" — is true *along the canonical-execution path*. It is not true +across all paths. Specifically: + +- A DB-side `revert` on the canonical path correctly rolls back the EVM + tx. ✓ +- A DB-side `revert` on the gas-estimation path causes the gas + estimation to fail — but the tx was never going to be submitted, and + no DB-side staging was supposed to land for it. The DB still saw the + call though. ✗ (silent state pollution on `ok`) +- An EVM-side revert on the canonical path *after* the precompile + returned `ok` does not roll back the per-tx staging promotion. ✗ + (already promoted to per-block) +- A block that fails to seal after `arkiv_commitBlock` succeeded leaves + the DB durable state ahead of the chain. ✗ (chain↔DB drift) + +The third item is particularly subtle: `arkiv_applyTx` returns `ok` and +the DB promotes per-tx → per-block staging. The EVM tx then reverts for +some other reason (e.g., a check in a separate contract called later +in the same tx). The DB has no way to know about that revert; it has +already promoted. When `arkiv_commitBlock` runs, the per-block staging +flushes to durable, and the chain has a tx that reverted but DB state +that reflects success. That is a silent atomicity violation. + +The v2 spec's response to this would presumably be "the contract calls +the precompile *last*, after all its own checks pass, so post-precompile +EVM-side reverts are eliminated by construction". That works for the +specific shape of `EntityRegistry.execute(Op[])` if you can guarantee +no opcode in the tx after the precompile call can revert. In practice +this is a discipline, not a property the EVM enforces — and it does +not extend to txs that route to the precompile via other contracts. + +--- + +## 6. Why this is hard to fix from inside the precompile + +### 6.1 What the precompile can and cannot know + +Recap §1.2 / §1.3 in the context of "what could we use to discriminate +paths": + +The precompile knows: calldata, caller, value, target/bytecode address, +`is_static`, gas budget, journalled EVM state at the call site, block +environment (number, timestamp, base fee, ...). + +The precompile does not know: which execution path called it, the +transaction hash (always — during pre-canonical paths the tx hash may +not be final), the position of the current call within the tx, +whether the tx will commit or revert, whether the block will seal. + +There is no field on `PrecompileInput` that encodes "I am running +inside the canonical block executor". There is no API call the +precompile can make to ask the EVM "are you the executor?". There is +no journalled-state value reth populates differently per path. + +### 6.2 The discrimination problem (briefly) + +[`custom-precompile.md`](custom-precompile.md) §6.1.5 enumerates four +candidate mechanisms a precompile could use to discriminate paths and +why none of them is clean: + +| Mechanism | Why it doesn't work cleanly | +|---|---| +| Static factory swap (different `EvmConfig` per path) | reth's node builder exposes one `ConfigureEvm` slot; routing requires upstream cooperation | +| `DB` type discrimination (downcast the database handle) | `Any` downcasts against reth-internal types; "canonical execution" isn't a single DB type | +| `BlockEnv` discrimination | gas estimation, payload building, canonical execution all target the same block number | +| Thread-local / atomic flag | re-execution paths set/clear it differently; concurrency breaks invariants | + +The brief version: every mechanism that lets the precompile know +"which path am I in" couples consensus correctness to reth-internal +plumbing that changes across versions, or makes the precompile a +function of out-of-band state. Both are unacceptable. + +### 6.3 Context-sensitivity violates the precompile contract + +The deeper objection — beyond engineering brittleness — is the one §1.4 +named: a precompile that varies its behaviour with hidden context is in +violation of the EVM's contract with it. + +A precompile that writes only on canonical execution and is a no-op +elsewhere is computing different *behaviour* across paths, even if its +EVM-visible *return value* is identical on every path. The EVM contract +says "deterministic function of consensus state". Hidden context isn't +consensus state. It's not in the journal, not in the block env, not in +the tx envelope. Two nodes can disagree on it (one is doing gas +estimation right now, another is doing payload building) without +disagreeing on any consensus input. + +The escape hatch is narrow but real: if the EVM-visible return is +*completely* independent of whether the side effect happened, then +varying the side effect across paths is consistent with the contract. +The catch is that the precompile then can't return anything that +depends on the DB write having taken effect — no row IDs, no "current +size", no "newly-allocated key". And if the EVM-visible return is +purely a function of input, the side effect is logically detached from +the EVM result, which is exactly the situation where moving the side +effect to the ExEx (where it already runs once per canonical block by +construction) would be cleaner. + +This is the design pressure behind §7.5. + +--- + +## 7. Resolution shapes and their trade-offs + +There are no "clean" answers — only design moves that trade one set of +constraints against another. Five shapes are worth naming. + +### 7.1 Session-id-aware DB + +Make `arkiv_applyTx` carry a session ID; have the DB maintain N +parallel staging contexts, one per session, with discard semantics +attached to each. + +The session ID has to come from somewhere. Candidates: + +- The `EvmFactory` sets it when creating the EVM. But then *something + upstream* — reth's node builder — has to route different sessions to + different paths, and that upstream signal does not currently exist. +- The block executor sets it at start of canonical execution; gas + estimation defaults to a "discard" session. But this requires + threading session IDs through every reth path the precompile touches, + including gas estimators that have no concept of sessions today. + +**Trade-off:** clean conceptual model, mismatched against what reth +currently exposes. Most invasive of the options. + +### 7.2 Idempotent staging keyed by consensus-stable identifiers + +`arkiv_applyTx` is keyed by `(blockNumber, txHash, opIndex)` or similar. +The DB checks the key on every call: if a key has already been seen, the +call is a no-op (returns the same status as the first call). Multi-execution +becomes harmless because every replay collapses to the same staging +record. + +The challenge: `txHash` is not always available at `applyTx` time. During +gas estimation, the tx hasn't been signed; it has no canonical hash. The +key would have to be something computable from the call context that +*happens* to be the same on every execution path that is going to apply +this op — which is essentially the same thing as "consensus-stable +identifier", and it is hard to construct one without information the +precompile doesn't currently get. + +A weaker version: use `(blockNumber, sender, callerNonce, opPayloadHash)`. +This is consensus-stable for canonical-path executions but doesn't help +for gas estimation (where the nonce isn't yet committed) or pending-block +queries (same). + +**Trade-off:** lighter-weight than 7.1, but the key construction is +fiddly and requires the precompile to receive context it doesn't get +today. Requires plumbing changes to reth (or to alloy-evm's +`PrecompileInput`). + +### 7.3 `EvmFactory` routing per call site + +Reth exposes hooks for different EVM configurations per call site: one +factory for canonical execution (with the real DB endpoint), another for +gas estimation / mempool / pending-block queries (with a no-op DB +endpoint, or no precompile at all). + +The good news: this is conceptually clean. Canonical execution writes to +the DB; everything else doesn't touch it. The bad news: reth doesn't +have this routing today. The `ConfigureEvm` slot on the node builder is +single. Adding distinct slots is a non-trivial upstream change. + +A workaround: the proposer/builder runs a separate node process from the +RPC node. The RPC node has no DB endpoint configured (precompile is a +no-op or returns a synthetic response). The proposer node has the real +DB. They co-locate but don't share the precompile path. This pushes the +problem out of code into deployment. + +**Trade-off:** clean if upstream cooperates, deployment-fragile if it +doesn't. Doesn't address the canonical-execution / engine-validation +duplication on the same node — both paths are on the proposer. + +### 7.4 Full DB idempotency with semantic restrictions + +Make every `arkiv_applyTx` operation intrinsically idempotent: applying +it twice produces the same DB state as applying it once. Combined with +deterministic keying, multi-execution is harmless because every replay +converges on the same state. + +This works for set-style operations (`create`, `update`, `transfer`, +`extend`, `delete`, `expire` — the v2 op set, which is set-style by +construction). It does not work for append-style operations (event +logging, counter increment, sequence allocation). + +The reorg / speculative-execution problem (§3.3) is orthogonal to +idempotency — even idempotent writes leak if the surrounding tx isn't +canonical. To address that, the DB needs chain-awareness in addition to +idempotency. + +**Trade-off:** lightest implementation cost, hard semantic restriction +on what the precompile can do. The v2 op set fits. Future ops that don't +fit (a per-entity event stream, say) would have to live elsewhere. + +### 7.5 Read-only precompile + ExEx-side writes + +The precompile becomes pure: it reads DB state at the parent block (or +journalled EVM state via `EvmInternals`), computes whatever the EVM +caller needs, and returns. It does not advance DB state. The actual +writes happen via the ExEx, post-canonical, exactly as v1's design +specified. + +To preserve v2's in-block `arkiv_stateRoot` commitment, the precompile +must be able to compute "what the state root *would* be after this tx's +ops" deterministically from the prior root and the ops alone. This is +feasible for any DB whose state-root computation is itself a pure +function of (prior root, op batch) — which Merkle-root-style DBs all +are, given access to the relevant subtrees. + +The price: the precompile needs read access to enough DB state to +compute the new root. Either the DB is embedded in-process (so reads +are cheap and synchronous), or the precompile is OK with making a +read-only IPC call to the DB per `applyTx`. The latter is the same +shape as the current write call but without the side effect. + +**Trade-off:** sidesteps the entire multi-execution problem at the cost +of either embedding the DB in-process or restricting precompile work +to what's computable from a one-shot read. Maintains v1's clean +separation of "EVM does EVM things, ExEx does DB writes". + +### 7.6 Comparison + +| Shape | Multi-exec safe? | Reorg-safe? | Plumbing cost | Semantic restriction | +|---|---|---|---|---| +| 7.1 Session-aware | Yes | With chain-aware sessions | High (reth changes) | None | +| 7.2 Consensus-keyed idempotency | Yes | With chain-aware DB | Medium (txHash plumbing) | None | +| 7.3 `EvmFactory` routing | Yes (proposer-only) | Same as canonical-only design | High (reth changes) or operational | None | +| 7.4 Full idempotency + semantic | Yes (with caveats) | Needs chain-awareness | Low | Set-style ops only | +| 7.5 Read-only precompile + ExEx writes | Yes (by construction) | Same as v1 | Low (precompile rewrite) | Compute-from-state-only | + +Combinations are possible. 7.4 + 7.5 ("the precompile is read-only +*and* the DB is fully idempotent") is the most defensive shape: even +read-only precompile traffic is harmless to the DB, even pre-canonical +reads are well-defined. + +--- + +## 8. The deeper question: can a sibling DB be in the consensus envelope? + +The whole problem reduces to one question: **is EntityDB part of the +protocol or not?** + +If yes — every node runs an instance, its state is a deterministic +function of canonical chain history, transport between EVM and DB is +reliable, the DB is reorg-aware, idempotent, and embedded — then the +Arkiv precompile is *morally* like `BLOBHASH`: it reads (or writes +deterministically into) a richer state envelope than calldata, but +every node deterministically agrees on what that envelope contains. +The chain is safe. + +If no — the DB is "an off-chain index" the node happens to talk to, as +in v1's framing — then the precompile shouldn't exist. The ExEx is the +appropriate channel, because the ExEx runs once per canonical commit +and is the only mechanism a sibling-process consumer can rely on +without inheriting the multi-execution problem. + +The v2 design bets on the first answer. The bet is reasonable, but it +implies a *list* of properties the DB has to provide — determinism, +reorg-awareness, idempotency, in-process embedding (probably), +multi-execution tolerance — and the v2 spec, as currently written, +makes most of them but not the last. That last property is what this +document has been about. + +The choice between resolution shapes in §7 is essentially a choice about +*how* to provide multi-execution tolerance. None of them eliminates the +need for the other DB properties; all of them assume those are in place. + +--- + +## 9. Open questions + +The following are concrete open questions §12 of the v2 spec should +absorb. + +1. **Multi-execution tolerance.** Which of the §7 shapes does the design + commit to? The current spec assumes single-stream canonical execution + and does not address gas estimation, mempool admission, pending-block + queries, engine-API validation, reorg replay, or `debug_traceBlock`. + This is the gap. + +2. **Atomicity-after-precompile-returns.** What happens if `arkiv_applyTx` + returns `ok`, the precompile promotes per-tx → per-block staging, and + the surrounding EVM tx then reverts for an unrelated reason + (out-of-gas later in the tx, a separate `require` failure in another + contract called after `EntityRegistry.execute`)? The current spec + implicitly assumes this can't happen because `EntityRegistry.execute` + is the last thing the tx does, but that is a discipline, not an + enforced property. + +3. **Transport failures and per-node forks.** Network failure between + reth and EntityDB on one node and not another causes the failing + node to return `fatal` while others return `ok`. This is a fork at + the receipt level even with a perfectly deterministic DB. The v2 + spec acknowledges `fatal` halts the block but doesn't address the + "different nodes' DBs are reachable at different rates" case + directly. + +4. **Bootstrap and pruning** (already in v2 §12.1, §12.3). Linked here + because the resolution chosen for multi-execution tolerance affects + what bootstrap has to reproduce — full per-tx staging history, just + per-block, just durable, etc. + +--- + +## 10. Glossary + +- **Canonical execution.** The block executor's run of a sealed, + validated, accepted block, producing the chain-of-record state diff. + Distinct from speculative execution paths (gas estimation, mempool + admission, payload building before sealing, validation). +- **Consensus envelope.** The set of state every honest node + deterministically agrees on. Includes journalled EVM state, block + header, tx envelope, chain config. Excludes wall clock, OS RNG, + sibling-process state (unless the sibling is itself a deterministic + function of consensus inputs, by construction). +- **EvmFactory / `create_evm`.** The reth/alloy-evm hook that produces + a fresh EVM instance with its `PrecompilesMap` populated. Called + once per execution-path entry. +- **`PrecompilesMap`.** The mutable container of precompiles attached + to a specific EVM instance. The EVM consults it on every call. +- **`PrecompileInput`.** The struct passed to a precompile's `call` + method. See §1.2 for fields. +- **`EvmInternals`.** The API surface inside `PrecompileInput` that + lets the precompile read (and write) journalled EVM state. +- **Out-of-band state.** State that is not in the consensus envelope. + Reads of it are per-node-different in general; writes to it are + invisible to the EVM's revert machinery. +- **Per-tx / per-block / durable staging.** EntityDB's three-level + write hierarchy. Per-tx is in-memory and discardable; per-block is + in-memory accumulator (`CacheStore`); durable is PebbleDB. +- **`arkiv_applyTx` / `arkiv_commitBlock`.** The two JSON-RPC methods + the precompile and the custom `BlockExecutor` use to talk to + EntityDB. See v2 spec §6. +- **Multi-execution.** The property that a single user-level + transaction is executed many times across reth's various paths. See + §4. + +--- From 5920350d926c5ac954808569414b3c929410b30e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20=C3=93=20Mhuiris?= Date: Fri, 8 May 2026 16:17:11 +0100 Subject: [PATCH 3/3] doc --- docs/verifiable-indexing-tradeoff.md | 1238 ++++++++++++++++++++++++++ 1 file changed, 1238 insertions(+) create mode 100644 docs/verifiable-indexing-tradeoff.md diff --git a/docs/verifiable-indexing-tradeoff.md b/docs/verifiable-indexing-tradeoff.md new file mode 100644 index 0000000..b1d78d8 --- /dev/null +++ b/docs/verifiable-indexing-tradeoff.md @@ -0,0 +1,1238 @@ +# Verifiable Indexes in a Database Chain + +A first-principles analysis of the design tension between indexed-query +verifiability, consensus over index state, and the latency budget that +bounds permissionless validators. Companion piece to +[`precompile-out-of-band-coupling.md`](precompile-out-of-band-coupling.md), +which examines the same tension from the implementation side. + +> **Status.** Analysis document. Frames the problem space; does not +> prescribe a single resolution. Treats the v2 design spec +> ([`reth-exex-entity-db-design-v2.md`](reth-exex-entity-db-design-v2.md)) +> as the concrete instance under examination, and Arkiv's first-principles +> document (`first-principles.md`) as the source of the constraints in +> tension. + +--- + +## Contents + +- [0. Purpose and scope](#0-purpose-and-scope) +- [1. Foundations](#1-foundations) + - [1.1 What an index is, and what it costs](#11-what-an-index-is-and-what-it-costs) + - [1.2 Synchronous vs asynchronous index maintenance](#12-synchronous-vs-asynchronous-index-maintenance) + - [1.3 What "verifiability" means, in five strengths](#13-what-verifiability-means-in-five-strengths) + - [1.4 CAP, PACELC, and what they actually say](#14-cap-pacelc-and-what-they-actually-say) + - [1.5 Blockchain consensus, in one paragraph](#15-blockchain-consensus-in-one-paragraph) + - [1.6 What "in the consensus envelope" means](#16-what-in-the-consensus-envelope-means) +- [2. The Arkiv goal: a queryable blockchain](#2-the-arkiv-goal-a-queryable-blockchain) + - [2.1 What this requires that Bitcoin / Ethereum don't have](#21-what-this-requires-that-bitcoin--ethereum-dont-have) + - [2.2 What "first-class data" means concretely](#22-what-first-class-data-means-concretely) +- [3. The core tension](#3-the-core-tension) + - [3.1 Verifiable indexes require consensus on index state](#31-verifiable-indexes-require-consensus-on-index-state) + - [3.2 Consensus on index state forces index work into the block-execution path](#32-consensus-on-index-state-forces-index-work-into-the-block-execution-path) + - [3.3 Block time is bounded by liveness and validator capacity](#33-block-time-is-bounded-by-liveness-and-validator-capacity) + - [3.4 Index work scales with state size](#34-index-work-scales-with-state-size) + - [3.5 The triangle](#35-the-triangle) +- [4. The tension within Arkiv's principles framework](#4-the-tension-within-arkivs-principles-framework) + - [4.1 Provable Execution](#41-provable-execution) + - [4.2 Data Verifiability — the load-bearing invariant](#42-data-verifiability--the-load-bearing-invariant) + - [4.3 Permissionless Operation](#43-permissionless-operation) + - [4.4 Sustainability and Data First (the soft pull)](#44-sustainability-and-data-first-the-soft-pull) + - [4.5 The §4.7 escape hatch and what it implies](#45-the-47-escape-hatch-and-what-it-implies) +- [5. Existing systems and where they sit](#5-existing-systems-and-where-they-sit) + - [5.1 Bitcoin / Ethereum — no built-in indexing](#51-bitcoin--ethereum--no-built-in-indexing) + - [5.2 The Graph — eventual-consistency indexer](#52-the-graph--eventual-consistency-indexer) + - [5.3 Cosmos SDK chains with custom modules](#53-cosmos-sdk-chains-with-custom-modules) + - [5.4 zk-indexed and SNARK-verified systems](#54-zk-indexed-and-snark-verified-systems) + - [5.5 Where Arkiv's v2 design sits](#55-where-arkivs-v2-design-sits) +- [6. Three coherent positions in the design space](#6-three-coherent-positions-in-the-design-space) + - [6.1 Strong: indexes in the state root](#61-strong-indexes-in-the-state-root) + - [6.2 Hybrid: data items committed, indexes signed](#62-hybrid-data-items-committed-indexes-signed) + - [6.3 Weak: signed responses with challenge games](#63-weak-signed-responses-with-challenge-games) + - [6.4 Comparison](#64-comparison) + - [6.5 Combinations and pivots](#65-combinations-and-pivots) +- [7. Implications for the architecture](#7-implications-for-the-architecture) + - [7.1 The pivot decision the architecture has not yet named](#71-the-pivot-decision-the-architecture-has-not-yet-named) + - [7.2 What an ADR for §4.7 would have to settle](#72-what-an-adr-for-47-would-have-to-settle) + - [7.3 The downstream consequences](#73-the-downstream-consequences) +- [8. Open questions](#8-open-questions) +- [9. Glossary](#9-glossary) + +--- + +## 0. Purpose and scope + +This document does two things: + +1. **Formalises** the architectural tension at the heart of the + database-chain ambition — between making indexed query results + cryptographically verifiable, requiring index state to be part of + consensus, and the latency and capacity bounds that govern + permissionless validators. +2. **Locates** that tension within Arkiv's existing principles and + invariants, identifies where the v2 design has tightened a + constraint beyond what the principles strictly require, and surfaces + the design choices the architecture has not yet made explicit. + +Intended reader: someone who has worked with blockchains and databases +independently but has not had to think hard about what it means to +combine them under blockchain trust guarantees. The first major section +(§1) is foundational; readers conversant with PACELC, eventual +consistency, and the cost of index maintenance can skim it and pick up +at §2. + +What this document does **not** do: + +- Recommend a specific architectural choice. §6 enumerates the three + coherent positions; the choice depends on values out of scope here. +- Re-derive the v2 design or the principles document. Both are treated + as given. +- Cover the orthogonal "is the DB itself deterministic" question + (DB-internal concern) or the multi-execution problem (covered in the + precompile companion doc). +- Argue that one position is universally better. Each of the three + positions in §6 is in production somewhere; they suit different + workloads and different threat models. + +--- + +## 1. Foundations + +### 1.1 What an index is, and what it costs + +A database **index** is auxiliary data structured to accelerate queries +beyond the raw lookup-by-primary-key the storage engine provides natively. +The canonical example: a B-tree on a non-primary column of a Postgres +table. Without it, queries by that column do a full table scan, O(N); +with it, they do a logarithmic descent, O(log N). The index is a +parasitic data structure — it carries no information of its own, +existing only to make a particular access pattern fast. + +Indexes are not free. Every insertion into the underlying table forces +an insertion into every index defined over it. The cost depends on the +index type: + +- **B-tree / B+-tree** (sorted single-column or composite indexes): + O(log N) hashes / pointer-chases per insert. The de-facto standard. +- **LSM-tree** (PebbleDB, RocksDB): amortised O(log N) per write, with + background compaction producing periodic latency spikes (typically + P99 / P999). +- **Inverted index** (full-text search): tokenise the input, write + entries for each token. Insert cost roughly proportional to the + number of unique tokens in the document. +- **Bitmap index** (low-cardinality categorical columns): one bitmap + per category, every insert flips a bit. The bitmap itself can be + large; rewrite cost grows with cardinality unless the implementation + uses differential or chunked storage. +- **Spatial / multi-dimensional indexes** (R-trees, KD-trees): O(log N) + but with worse constants and more disk I/O. +- **Composite indexes**: O(log N) per index, multiplying the constant + factor by the number of indexes in play. +- **Merkle Patricia Trie** (Ethereum's account/state index): O(log_16 + N) keccaks per update. Dominated by hashing cost rather than disk + I/O at moderate scale; both at billion-scale. + +Two costs scale with state size beyond the asymptotic complexity: + +- **Cache effects.** As state grows, the working set exceeds RAM, and + every index level that misses cache becomes a disk seek. The + asymptotic complexity is unchanged; the constant factor jumps 100× + or more once paging dominates. This is what your colleague is + probably gesturing at when they say "linear in state growth" — not + literally O(N) per insert, but a regime change in the constant + factor that *feels* like linearity at the scales engineers care + about. +- **Compaction overhead** (LSM-style structures). Background work to + consolidate write-amplified storage. Not on the per-write critical + path in steady state, but produces P99 latency spikes that synchronous + systems can't ignore. + +The takeaway: even O(log N) indexes are expensive in absolute terms, +and they get more expensive at scale. Production database engineering +is largely the management of these costs. A naive design that worked +well at 10⁶ entities can collapse at 10⁹. + +### 1.2 Synchronous vs asynchronous index maintenance + +Two ways to keep indexes consistent with a base table: + +**Synchronous.** Every write to the table also writes to every index, +within the same transaction. Reads of the index always see exactly the +post-write state — ACID semantics for query results. + +- Cost: write latency includes index update time. Indexes constrain + throughput. +- Benefit: query results are always up-to-date with respect to + committed writes. No staleness window. + +**Asynchronous.** Writes go to the table immediately. Index updates +happen in the background, possibly batched, possibly out-of-order, +possibly minutes behind. + +- Cost: query results may be stale. Reads need to either tolerate the + staleness or wait for the index to catch up. +- Benefit: write throughput decoupled from index maintenance cost. + +Most production databases support both modes for different indexes, +and almost every system at meaningful scale uses async indexes for at +least some queries. Postgres `CREATE INDEX CONCURRENTLY` is async; +standard `CREATE INDEX` is sync. Elasticsearch is async by default +(refresh interval, typically 1s). Search engines are predominantly +async. Read replicas are eventually-consistent async copies of the +write master. + +The choice between sync and async is a fundamental database design +dimension, not a low-level optimisation. It governs what consistency +guarantee the system can offer, what throughput it can achieve, and +how its costs scale. + +For a database chain, this dimension takes on additional weight: the +consistency model of indexes interacts with the chain's consensus +model. A chain that wants synchronous indexes is choosing to put index +work on the consensus-critical path, with all the latency and capacity +implications that follow. A chain that accepts asynchronous indexes is +choosing eventual consistency for query results, with all the +verifiability implications that follow. + +### 1.3 What "verifiability" means, in five strengths + +Verifiability is the property that a data consumer can check the data's +correctness without having to trust the source. It comes in graded +strengths. + +| Strength | Mechanism | Trust required | +|---|---|---| +| **None** | Trust the source's response | Full trust in the source | +| **Authenticated** | Source signs the response; consumer verifies the signature | Trust in the source's identity, not its honesty | +| **Inclusion proof** | Source returns data + Merkle proof against a known commitment | Trust in the commitment (typically anchored elsewhere) | +| **Computational proof** | Source returns data + zk-proof of a specific computation | Only the proof system itself | +| **Challenge / fraud proof** | Source's claim stands unless someone publishes contradicting evidence within a window | Trust that *some* honest party is watching, plus economic security from bonded participants | + +Each strength has different infrastructure requirements: + +- **Authenticated** requires PKI; no chain involvement. +- **Inclusion proofs** require the verifier to know the commitment + (typically a chain state root) and to be able to verify hashes. +- **Computational proofs** require running a verifier algorithm + against the proof; verification cost is independent of the + computation's size. +- **Challenge games** require a watchful counterparty, an economic + mechanism (bonds, slashing) that punishes provable fraud, and an + on-chain dispute resolution path. + +For a database query, the analogues are: + +- **None.** Query a centralised index, trust the response. Web2 default. +- **Authenticated.** Query a signed-response index. The responder + commits; you can prove malfeasance later, but at query time you + trust them. +- **Inclusion proof.** Query an index that is itself committed to in + chain state; the response includes a Merkle proof against the + commitment. The verifier checks the proof against a recent block + header. +- **Computational proof.** Query an index and receive a zk-proof that + the response is the correct output of a known query function over + committed state. The verifier checks the proof against the + committed state root. +- **Challenge game.** Query a signed-response index; if the response + is wrong, an honest party can challenge via an on-chain dispute that + ends with the responder slashed. + +The choice among these is the single largest architectural lever in the +design of a database chain. It determines what infrastructure has to be +deployed, what threat model is in play, what the user's verification +experience looks like, and — crucially for what follows — what the +node operator has to maintain. + +### 1.4 CAP, PACELC, and what they actually say + +Distributed systems literature has produced a number of frameworks for +reasoning about tradeoffs in shared-state systems. Two are commonly +cited; only one is the right tool for what's discussed here. + +**CAP** (Brewer, 2000): in a distributed system, during a network +partition, you can preserve **Consistency** or **Availability**, not +both. **Partition tolerance** is taken as given because partitions are +unavoidable in any real-world system. + +CAP is often invoked loosely as "you can have any two of C, A, P". This +framing is technically incorrect — P isn't optional, it's the +precondition under which you choose between C and A — but the +underlying intuition (that distributed systems force tradeoffs between +consistency and availability) is sound. + +**PACELC** (Abadi, 2012): a strict refinement of CAP that adds the +"else" branch CAP doesn't address. *In case of Partition*, choose +Availability or Consistency; *Else* (i.e., when the network is +healthy), choose Latency or Consistency. + +PACELC is the correct frame for blockchain-with-indexes. Even when +consensus is making progress and the network is healthy, you face a +continuous tradeoff between **block-time aggressiveness** (latency) and +**index-state synchronisation** (consistency). Strong consistency +between index state and chain state requires synchronisation; +synchronisation costs latency; latency budget is finite. + +Most blockchains are CP/EC in PACELC terms: they prioritise +consistency over availability under partition (the chain halts), and +consistency over latency in the absence of partition (block time is +gated by consensus rounds, not by the speed of the fastest validator). + +A queryable blockchain that wants synchronous indexes is also CP/EC. +An eventually-consistent indexer (The Graph, etc.) is CP/EL: the chain +itself is consistent, but query results trade consistency for lower +latency. + +The colleague's CAP reference is in the right neighbourhood but +slightly misaimed; PACELC is the better tool. The point survives the +correction, though: there is a real, structural tradeoff, and it isn't +a tradeoff you can engineer your way out of by being clever — it's a +choice between two coherent design positions. + +### 1.5 Blockchain consensus, in one paragraph + +A blockchain is a state machine whose transitions are agreed upon by a +network of validators via consensus. State transitions are +deterministic functions of the current state and an ordered batch of +transactions; given the same starting state and the same transaction +batch, every honest validator computes the same resulting state. State +is committed to via a Merkle root (Ethereum's state trie); the Merkle +root is part of every block header; validators reach consensus on the +block header — including the state root — and from there everything +falls out. A transaction's effects are durable iff its containing +block is canonical; query results are verifiable iff the queried state +is committed under the canonical state root. + +Two properties matter for what follows: + +- **Determinism.** Every honest validator executing a block computes + the same state. Non-negotiable. Non-determinism in block execution + forks the chain. +- **Bounded execution time.** Every honest validator must finish + executing a block within some window, or block production stalls. + The window depends on the consensus protocol, the network topology, + and the operational requirements of the validator set. + +These two properties — determinism and bounded execution time — are +what every constraint in the rest of this document derives from. + +### 1.6 What "in the consensus envelope" means + +A piece of state is "in the consensus envelope" iff every validator +agrees on it via the consensus protocol. The mechanism is consensus on +the block header, which commits to the state root, which (via Merkle +proofs) commits to every individual piece of state. + +State **inside** the envelope: + +- EVM account balances, contract storage, code. +- Block timestamp, base fee, randomness (`prevrandao`), beneficiary. +- Transaction envelope contents (calldata, signature, blob hashes for + EIP-4844). +- Anything reachable via `eth_getProof` against a canonical block + header. + +State **outside** the envelope: + +- Wall-clock time on a specific validator. +- OS RNG output. +- The contents of a sibling process the validator happens to be + running. +- A query response from an off-chain indexer. + +Inside-envelope state is consensus-deterministic by construction; +outside-envelope state is whatever the validator's local environment +says it is. Verifiability via inline proof requires the queried state +to be inside the envelope. Verifiability via challenge game does not. + +The whole question of "verifiable indexes" is the question of whether +index state can be brought inside the envelope — and at what cost. + +--- + +## 2. The Arkiv goal: a queryable blockchain + +Arkiv's stated goal, from the first-principles document: + +> A modular database chain architecture that makes data a first-class +> citizen in web3. It combines the trust guarantees of blockchain +> (ownership, immutability, tamper-proof provenance) with the query +> capabilities of traditional databases (filters, indexes, SQL-like +> queries). + +Decomposed: + +- **Trust guarantees of blockchain**: cryptographic verifiability, + deterministic execution, censorship resistance, decentralised + operation. The full Web3 invariant set. +- **Query capabilities of traditional databases**: structured data, + indexed access, expressive query language (filters, ranges, + composites, sorting, paging). The full SQL-flavoured database surface. + +Existing systems pick one side or the other. Bitcoin and Ethereum +provide trust guarantees but no built-in indexed queries; you query +state by exact key, and richer queries require external infrastructure +the chain itself does not endorse. Postgres, MongoDB, Elasticsearch +provide indexed queries but no chain-level trust. Hybrid systems +exist (The Graph, blockchain explorers, custom indexers) but always +trade off in one direction or the other — they bolt indexes onto an +unchanged chain, with verifiability mechanisms layered on top. + +Arkiv's claim is that you can build a system where both are first-class +properties of the same artefact. The interesting question is what that +costs. + +### 2.1 What this requires that Bitcoin / Ethereum don't have + +A chain like Ethereum gives you, by way of indexed access: + +- A state trie indexed by `(account, storage_slot)`. Lookups by key + are O(log N) and verifiable via `eth_getProof`. +- That's it. Anything else — "find all accounts that hold token X", + "list the most recent transactions on contract Y", "find blocks + where event Z was emitted with parameter W" — requires an external + indexer. + +For Arkiv's ambition you also need: + +- **Secondary indexes** keyed by data attributes, not just by storage + slot. ("Find all entities where `attribute.priority = 5`.") +- **Range and prefix queries** over those attributes. ("Find entities + with `priority >= 5`.") +- **Composite queries** combining multiple attributes. ("Find + entities with `type = "note" AND priority >= 5`.") +- **Pagination** for any query that returns many results. +- **Sorting** by attribute or insertion order. +- **Verifiability** for each of the above, against on-chain state. + +Each of these is straightforward in a traditional database. None of +them are present in Ethereum's built-in surface. Building any of them +in a way that satisfies blockchain trust guarantees is the technical +challenge. + +The natural way to provide them is a sibling state machine +(EntityDB, in Arkiv's case) that maintains the indexes. The natural +question — the subject of this document — is: what does that sibling +state machine's relationship with chain consensus need to be? + +### 2.2 What "first-class data" means concretely + +The first-principles document describes the goal as data being +"owned, tamper-proof, queryable, composable": + +- **Owned**: each data item has a clear on-chain owner. Modifications + are authorised. (`Owner Authorization` invariant.) +- **Tamper-proof**: each data item is committed to under chain state. + Modifications go through transactions. (`Transaction Provenance` + + `Data Verifiability` invariants.) +- **Queryable**: secondary indexes provide rich query access — not + just key-value lookup. (Implied by Data First principle; mechanism + not yet specified at the principles level.) +- **Composable**: data items can be referenced by other data and + other contracts. (Implied; needs a stable identifier scheme, which + the entity-key derivation provides.) + +The first three properties have natural mechanisms in the existing +chain primitive set. **Queryable** is the one that does not. It +requires either: + +(a) An *index* that is itself part of chain state, queryable via the +standard verifiability mechanisms. +(b) An indexer outside the chain, with verifiability mechanisms +layered on top (signed responses, challenge games, computational +proofs). + +(a) and (b) have different cost profiles, different verifiability +strengths, and different implications for what a permissionless node +operator has to do. The choice between them is the subject of §3 +through §6. + +--- + +## 3. The core tension + +State as cleanly as possible: + +> **Verifiable indexes require consensus on index state.** +> **Consensus on index state forces index work into the +> block-execution path.** +> **Block-execution work is bounded by validator capacity and +> block-time aggressiveness.** +> **Index work scales with state size.** +> +> Therefore: **index richness × state size × block-time aggressiveness +> is bounded by validator capacity, and the bound tightens +> monotonically as the chain grows.** + +Each link merits unpacking. + +### 3.1 Verifiable indexes require consensus on index state + +The strongest form of verifiability — inline Merkle proofs against the +chain state root — requires the queried state to be inside the +consensus envelope. For an index, that means the index data +structure's own state has to be committed under the chain state root. + +The mechanism: the index sits in some Merkle-shaped data structure +(a trie, a sparse Merkle tree, a verkle tree); the structure's root +is stored in chain state; when a query returns a result, it includes +a Merkle path from the result back to the index root, and the verifier +checks the path against the on-chain root. + +This is exactly how `eth_getProof` works for storage slots, and how +the v2 design's `arkiv_stateRoot` works for entity records. The +structural property: the index has to be a Merkle data structure, and +its root has to be in chain state. + +Weaker forms of verifiability — challenge games, signed responses — +do not require the index to be in chain state. They require the +*items the index points at* to be verifiable, but the index itself +can be eventually-consistent or even centralised, with the challenge +mechanism backing it. + +This first link is therefore conditional: **strong verifiability** +requires consensus on index state. Weaker verifiability does not. The +choice between strengths is what the rest of this document hinges on. + +### 3.2 Consensus on index state forces index work into the block-execution path + +If the index is in chain state, then every state transition that +modifies indexed data must update the index *as part of the +transition*. The state root after the block must be the state root +that includes the post-update index. There is no place for the index +update to live except inside block execution. + +Concretely: a transaction that creates a new entity has to perform +all the index updates that follow from the new entity's existence +(adding it to bitmaps for every attribute it has, updating sort +indexes, etc.) before the block in which the transaction is included +can be sealed. The state root has to commit to all those updates. + +This is what "synchronous indexing" means in a blockchain context. It +is forced by the verifiability strength chosen in §3.1; it isn't a +choice the design can route around. + +The alternative — async indexing with eventual consistency — gives up +the strength claim. It does not give up verifiability entirely, but it +moves verifiability to a different mechanism (challenge games or +similar). + +### 3.3 Block time is bounded by liveness and validator capacity + +Block time is not a free parameter that can be dialled up to +accommodate slow indexes. It is bounded by: + +- **Network propagation.** Blocks have to be propagated to all + validators within a fraction of the block time, or consensus + forks. For a global validator set on commodity internet, this + imposes a floor of tens to hundreds of milliseconds. +- **Consensus protocol overhead.** PoS protocols require multiple + rounds of message-passing per slot. Each round is bounded below by + network latency. +- **MEV and ordering concerns.** Faster blocks make MEV games more + expensive to play; slower blocks give attackers more opportunity. + Most chains pick a sweet spot. +- **User experience.** Slow blocks (>10s) make the chain feel + unresponsive to users. Fast blocks (<1s) are an aspirational + property, not a low-cost one. +- **Validator capacity.** Every block has to be executed within the + block time on every validator's hardware. The slowest required + hardware is the floor. + +OP Stack mainnet currently runs 2-second blocks. Arkiv's L3 design +spec aspires to 1-second blocks, with subsecond as a stretch goal. + +A 1-second block time means every validator has up to ~1 second to +execute the block. After subtracting consensus overhead, network +propagation slack, and a margin for safety, the actual on-validator +execution budget is more like 200-500ms. Within that budget the +validator must: + +- Execute every transaction in the block (EVM work). +- Update every index every transaction touches. +- Compute the new state root. +- Persist the new state to durable storage. +- Sign and propagate the resulting block. + +Index work is one line in that list, but it can grow without bound if +state grows without bound. Which leads to the next link. + +### 3.4 Index work scales with state size + +§1.1 made this case. To restate it in the chain context: + +- Trie inserts are O(log N), but with constant factors that grow + with disk I/O once cache is exceeded. +- Bitmap maintenance is O(K) where K is the cardinality of the + bitmap (i.e., the number of entities sharing a particular attribute + value), unless the implementation is clever about chunked or + differential storage. +- LSM compaction overhead grows with state size and produces + unpredictable P99 spikes. +- Merkle root computation requires hashing every node up the path; the + path length is log N but the number of paths touched per block grows + with the number of operations per block. + +For a chain with 10⁶ active entities, none of this is a bottleneck +for any modern hardware. For 10⁹, every constant factor that was +ignorable becomes load-bearing. The chain that worked fine at launch +finds itself under increasing pressure as adoption grows — exactly the +opposite of the "scales gracefully with adoption" property a database +chain is trying to advertise. + +### 3.5 The triangle + +Three properties that all want to be simultaneously true: + +``` + Strong query verifiability + (inline proofs) + /\ + / \ + / \ + / \ + / \ + Aggressive / \ Permissionless + block time/ \ validator set + / \ + / \ + /__________________\ + Index work fits in block budget + at any plausible state size +``` + +Pick any two; the third is constrained. + +- **Strong verifiability + aggressive block time** ⇒ validators + cannot be permissionless at scale; only well-resourced + professional operators can keep up. +- **Strong verifiability + permissionless validators** ⇒ block time + cannot be too aggressive; a generous budget is needed for + validators on commodity hardware. +- **Aggressive block time + permissionless validators** ⇒ + verifiability has to weaken; indexes cannot be in the state root + if they're going to grow. + +The triangle is not a claim that the corners are unreachable. It's a +claim that you cannot get arbitrarily close to all three simultaneously. +At small state sizes all three are tractable. As state grows, the +constraints visible at large scale start to bite, and the design has +to commit to which corner it gives ground on. + +This is the database-chain version of CAP/PACELC. It isn't CAP +literally — partition tolerance isn't the dimension under tension here +— but the *shape* is the same: three properties, only two simultaneously +achievable in the strongest forms, and the choice has to be made +consciously rather than discovered under load. + +--- + +## 4. The tension within Arkiv's principles framework + +The first-principles document defines nine architecture invariants. The +ones that participate directly in the tension above are listed below, +with what each pulls toward. + +### 4.1 Provable Execution + +> All state transitions must be provable in the settlement layer's +> dispute mechanism. ... Any modifications to the execution layer must +> have corresponding implementations for provability. + +What it pulls toward: deterministic execution, bounded per-block work, +fault-proof completeness. Every operation in a block has to be +re-executable in a fault proof; if the operation is heavy, the fault +proof is heavy too. + +This invariant is satisfied as long as execution is deterministic and +the operations are bounded. It doesn't directly mandate where indexes +live. It does mandate that *whatever* the design does, it must be +re-executable in the proof system, which has its own latency and +resource budget. + +### 4.2 Data Verifiability — the load-bearing invariant + +This is where the design decision actually lives. The invariant text +distinguishes two cases: + +> **Data item verification**: Each data item has a deterministic +> hash computed from its content. This hash is part of the state +> Merkle tree. Clients can verify any data item by checking its hash +> and Merkle proof against the on-chain state root. + +This is **strong inline-proof verifiability for individual data items**. +Each data item is in chain state via a hash commitment; the proof is a +Merkle path against the state root. + +> **Query verification**: Query results are signed by the responding +> node. If a node returns an incorrect result set — omitting matching +> items or including non-matching ones — any party can challenge by +> providing data item content that contradicts the signed result. + +This is **challenge-based verifiability for query results**. Signed +responses + bonded disputes + challenge resolution. + +The two are very different in their requirements: + +| Property | Inline proof verifiability | Challenge-based verifiability | +|---|---|---| +| What's in chain state | The data items themselves (Merkle commitment) | The data items themselves; the index need not be | +| Index location | Must be in state root | Can be off-chain, eventually consistent | +| Verification cost (client) | Hash + Merkle path check | Verify signature; in dispute, run challenge | +| Verification time (steady state) | One RPC round-trip | One RPC round-trip | +| Verification time (under fraud) | Detected immediately | Detected within challenge window | +| Trust required | None (cryptographic) | Bonded honesty (economic) | +| Permissionless | Yes (anyone with a verifier) | Yes (anyone with bond + watchful counterparty) | +| Fits async indexing | No | Yes | +| Per-block validator cost | High (sync index update + Merkle root) | Low (just write the data items) | + +The crucial observation: the principles framework already accepts the +challenge-based model for queries. §4.7 even flags it as +work-in-progress, with an "ADR required" tag for the dispute mechanism. +The strong-verifiability claim is reserved for *data items*, where the +inline-proof model applies and indexes don't enter the picture. + +### 4.3 Permissionless Operation + +> Anyone can verify, operate nodes, and deploy database chains without +> permission. + +What it pulls toward: low resource floor for validators, no +gatekeeper, public data availability. + +The permissionless property bounds what a validator can be asked to +maintain. If running a validator requires a 1TB SSD with NVMe fsync +performance to keep up with index updates, the set of operators that +can validate shrinks. If the set shrinks too far, "permissionless" is +permissionless in name only. + +There is no fixed line here — what counts as "anyone" is a values +question — but the direction of the pull is clear: the lower the +resource floor, the more meaningfully permissionless the chain. + +### 4.4 Sustainability and Data First (the soft pull) + +Two principles, not invariants, but they pull in opposing directions +on this question. + +**Sustainability** wants value to exceed cost. Heavy in-consensus +indexing is expensive: per-validator hardware, per-block compute, +storage growth. The cost has to be passed somewhere — to users via +fees, to operators via subsidies, to investors via token issuance. The +heavier the indexing, the harder the economics. + +**Data First** wants rich query capability. Range queries, full-text +search, composite filters, sort orders — the more capable the query +language, the more useful the chain is. Each capability typically +requires its own index. More indexes mean more per-block work. + +These two pull against each other inside the tradeoff triangle. +Sustainability favours minimal indexing; Data First favours rich +indexing. Where the architecture lands is a values choice that +isn't determined by the invariants alone. + +### 4.5 The §4.7 escape hatch and what it implies + +Re-read the Data Verifiability invariant carefully. The query-side +mechanism it specifies is: + +> Query results are signed by the responding node. If a node returns +> an incorrect result set — omitting matching items or including +> non-matching ones — any party can challenge by providing data item +> content that contradicts the signed result. + +Followed by: + +> **Architecture Decision Record Required**: Query verification is +> design work remaining. The mechanism described above is +> directionally correct, but details — challenge game design, +> economics/bonds, completeness proofs, on-chain vs off-chain +> resolution — require dedicated decision record. + +This is a deliberate concession. The principles framework, as +currently written, does **not** require query results to come with +inline Merkle proofs. It accepts a challenge-game model as +directionally correct, and flags the details as ADR-pending. + +This concession is significant. It implies that the principles +framework permits — and the ADR process is expected to consider — +designs in which: + +- Indexes are not in chain state. +- Query results are signed responses with bonded honesty. +- Disputes are resolved on-chain via fraud proofs over data items. + +A design that takes this concession seriously would land in column 3 +of §6's design space. It would not require synchronous in-consensus +indexing. It would not be subject to most of the latency and capacity +pressure §3.5 describes. + +The v2 design *over-strengthens* the verifiability commitment for +queries, by putting the index state root (`arkiv_stateRoot`) into +chain state via the system call from `BlockExecutor::finish`. This +provides inline-proof verifiability for queries — which is stronger +than the principles require — at the cost of forcing all index work +onto the synchronous block-execution path, with all the latency and +capacity implications that follow. + +This is not a criticism of the v2 design. Stronger verifiability is +genuinely valuable, and there are use cases where the cost is +worthwhile. But it is an architectural choice that the framework +permits without mandating, and the cost of that choice is what every +prior section of this document has been describing. + +The §4.7 ADR is therefore the pivot point. Resolving it explicitly — +choosing the strength of query verifiability the system commits to — +determines almost everything else about the design space. + +--- + +## 5. Existing systems and where they sit + +It helps to look at how existing systems have resolved this tension, +because every coherent point in the design space has at least one +example in production. + +### 5.1 Bitcoin / Ethereum — no built-in indexing + +Bitcoin and Ethereum take the simplest position: **no built-in indexed +queries**. The state is committed to under a Merkle root; you can do +key-based lookups (`getStorageAt`, `getBalance`, `getTransactionByHash`) +with inline proofs, but anything else requires an external indexer. + +Where this lands in the framework: + +- Verifiability strength: inline proof, but only for key-based lookups. +- Index location: outside the chain entirely, in clients (block + explorers, wallets, dApp backends). +- Per-validator index cost: zero. +- Query richness: built-in is minimal; richness is provided by the + ecosystem of off-chain indexers, with their own trust assumptions. + +This is the column-zero position: the simplest possible answer to the +tension is "don't have indexes". Everyone using Ethereum for +non-trivial queries deals with this by running their own indexer or +trusting someone else's. + +### 5.2 The Graph — eventual-consistency indexer + +The Graph layers an indexed-query system on top of Ethereum (and other +chains) without modifying the chain itself. Indexers run subgraphs — +WASM programs that subscribe to chain events and maintain a Postgres +database keyed however they like. Queries hit the indexer's Postgres +and return results. + +Verifiability: indexers stake GRT tokens. Wrong results can in +principle be challenged by a separate "fisherman" role that submits +fraud proofs. In practice the dispute mechanism has had limited use +and most users trust indexers. + +Where this lands: + +- Verifiability strength: challenge-based, weakly enforced in + practice. +- Index location: outside the chain, in indexer Postgres instances. +- Per-validator index cost: zero (chain validators are unaffected; + separate indexer set runs the indexes). +- Query richness: high (full SQL via Postgres or GraphQL). + +This is the column-three position taken to its extreme: the chain +itself does no indexing work, the index is fully off-chain, and +verifiability is by challenge. + +### 5.3 Cosmos SDK chains with custom modules + +Cosmos SDK chains can implement custom modules that maintain in-chain +indexes. Example: the cosmos-sdk's `bank` module maintains a balance +index per address per denom, queryable via `QueryAllBalances`. The +index is part of chain state; it's updated synchronously with every +balance-changing transaction; queries return inline-proof-verifiable +results. + +Where this lands: + +- Verifiability strength: inline proof against chain state root. +- Index location: in chain state. +- Per-validator index cost: proportional to the index's complexity; + for `bank` it's a per-address-per-denom map, manageable. +- Query richness: limited to whatever the module's specific + `Query*` methods expose. No general-purpose query language. + +This is the column-one position with a constraint: only specific, +designed-in queries are available. There is no general "find all X +where Y matches" — only the queries the module's authors anticipated. +For chains where the query set is known and limited, this works well. +For databases-as-product, it's too restrictive. + +### 5.4 zk-indexed and SNARK-verified systems + +A growing class of designs use zk-proofs to verify query results +against on-chain state. The querier runs the query, generates a +SNARK that attests the query is correctly evaluated against committed +state, and returns (result, proof). The verifier checks the proof +against the on-chain state root. + +Examples: HyperOracle, Brevis, Axiom (in some configurations), +Lagrange's State Committee. + +Where this lands: + +- Verifiability strength: computational proof (cryptographically + strong, no trust required). +- Index location: outside the chain (the prover maintains its own + data structures); the chain only stores commitments. +- Per-validator index cost: zero (validators verify proofs, not + rebuild indexes). +- Query richness: limited by what the proof system can express + efficiently. Generic SQL is hard; specific common patterns + (aggregations, filters over event logs, etc.) are tractable. + +This is a column-two position that takes the form "data items are +committed, indexes are off-chain, verifiability is computational +rather than economic". As proof systems improve, this position becomes +more viable for general queries. + +### 5.5 Where Arkiv's v2 design sits + +The v2 design lands squarely in column one (strong / synchronous / +in-state-root), with one subtle modification: the index state root +(`arkiv_stateRoot`) is committed in-block via the system call from +`BlockExecutor::finish`, so query results have inline-proof +verifiability against chain state at the same block they describe. + +This is more aggressive than the cosmos-sdk position because the +indexes Arkiv supports are richer (annotation bitmaps, prefix +indexes, etc.) and the v2 spec aspires to general query capability +rather than designed-in queries only. + +It is more aggressive than the Postgres-on-blockchain or +zk-indexed positions because the index work is fully synchronous and +fully in chain state, with the per-validator cost that implies. + +Within the design space outlined in §6, v2 is committing to the +hardest corner of the triangle. The cost — and the question this +document is asking — is whether that commitment is the one the +principles actually require, or one the design has chosen +over-strongly. + +--- + +## 6. Three coherent positions in the design space + +The space is parameterised by where indexes live and how query +results are verified. Three positions are coherent. + +### 6.1 Strong: indexes in the state root + +**Where indexes live**: in chain state, committed under the chain +state root via a Merkle-shaped data structure (entity trie + bitmap +roots, or similar). + +**How query results are verified**: inline Merkle proof against the +on-chain state root. + +**Per-block validator work**: full index update for every operation in +every transaction. Synchronous with block production; all of it must +fit in the block's execution budget. + +**Threat model**: trustless. Wrong results are not just challengeable, +they're impossible — a node returning a wrong result simply can't +produce a Merkle proof for it. + +**Examples in production**: Cosmos SDK custom modules (for restricted +query sets); Arkiv v2 (for general indexed queries). + +**Cost profile**: heaviest. Block time bounded by index work; state +growth puts continuous pressure on the budget; the validator set is +constrained to operators who can keep up. + +### 6.2 Hybrid: data items committed, indexes signed + +**Where indexes live**: indexes themselves are off-chain (or +in-process but off the consensus path); the data items the indexes +point at are committed under the chain state root. + +**How query results are verified**: the index responder signs the +result. If the result is wrong (the responder claims an item that +doesn't match the query, or omits one that does), any party can +provide the contradicting data item content (via inline Merkle proof +against the chain state root) and trigger a dispute that ends with +the responder slashed. + +**Per-block validator work**: only the data-item commit. Indexes are +maintained on the responder's own schedule (typically asynchronously, +reading from chain state). + +**Threat model**: economic. Honesty is bonded. Wrong results trigger +slashing within the challenge window; persistent malice is +unprofitable. + +**Examples in production**: this is what Arkiv's first-principles §4.7 +describes and tags ADR-required. Variants exist in the wild (data +availability committees, optimistic rollups for indexing). + +**Cost profile**: medium. Validators do less work; indexers do more. +The economic mechanism (bonds, dispute resolution) has its own +infrastructure cost. Query richness is limited only by what the +indexer can compute, not by what fits in a block. + +### 6.3 Weak: signed responses with challenge games + +**Where indexes live**: fully off-chain. The chain has no index +state; only the per-data-item commitments live on-chain. + +**How query results are verified**: signed responses, with challenge +games over the *index's input* (the data items it claims to be +indexing). A wrong result can be disputed, but the disputer has to +provide the corrected result themselves. + +**Per-block validator work**: zero index work. Validators execute +state transitions over data items; indexes are someone else's +problem. + +**Threat model**: economic, with weaker guarantees than column 2. +The index responder might be wrong in subtle ways that aren't easy +to challenge (omitting items rather than including wrong ones is +harder to detect). Watching is required. + +**Examples in production**: The Graph; most blockchain explorers; +custom indexers maintained by dApp teams. + +**Cost profile**: lightest for the chain; heaviest for users (who +have to either trust indexers or run their own). Query richness is +unbounded — full SQL or whatever the indexer supports. + +### 6.4 Comparison + +| Property | Strong (col 1) | Hybrid (col 2) | Weak (col 3) | +|---|---|---|---| +| Verifiability strength | Inline Merkle proof | Signed + challenge | Signed + challenge | +| Index in chain state | Yes | No | No | +| Per-block validator work | High | Low | Zero | +| Query richness ceiling | Bounded by block time | Bounded by indexer capacity | Bounded by indexer capacity | +| State-growth pressure | Continuous on validators | On indexers, not validators | None on validators | +| User trust required | None | Bond-backed honesty | Bond-backed honesty + watchful party | +| Permissionless validator | At small state size | Yes | Yes | +| Permissionless indexer | Same as validator | Yes (with bond) | Yes (with bond) | +| Challenge mechanism | Not needed | Required | Required | +| First-principles compliance | Stronger than required | Matches §4.7 framing | Matches §4.7 framing | + +Column 1 is what Arkiv v2 implements. Columns 2 and 3 are what the +Arkiv principles permit and (per §4.7's ADR-required tag) explicitly +contemplate. + +### 6.5 Combinations and pivots + +The columns are not strictly mutually exclusive. Realistic designs +combine elements: + +- **Item-level inline + query-level challenge.** Individual data items + are inline-verifiable (their hashes are in chain state), but query + result sets are signed-and-challengeable. This is what Arkiv's + §4.7 invariant text actually describes — and it's a column-2 + position with stronger item-level guarantees than naive column 2. +- **Strong indexes on a subset of data, weak on the rest.** Some + attributes are indexed in-state-root (high-value, low-cardinality); + others are off-chain (long-tail). Validators only do the work for + the strong subset. +- **Strong commitment, asynchronous build.** The chain commits to a + "version number" of the index state (per-block, monotone); the + actual index is built off-chain to match each version. Queries + return results + index version + signed assertion. Disputes prove + that the index at a given version doesn't match the chain at the + corresponding block. This is a column-2 position with a chain-side + hook. +- **Hybrid evolution.** Start in column 1 with a small state, plan + to migrate to column 2 if state growth makes column 1 untenable. + Requires the verification mechanism to be ADR-defined upfront so + the transition doesn't break clients. + +The point isn't that the design has to be one of three. The point is +that any coherent design lands in some combination of columns, and the +combination determines the cost profile, threat model, and growth +trajectory. + +--- + +## 7. Implications for the architecture + +### 7.1 The pivot decision the architecture has not yet named + +The v2 spec commits to column 1 implicitly, by including +`arkiv_stateRoot` in the EVM state via the system call from +`BlockExecutor::finish`. This is a significant architectural commitment +— it forces synchronous indexing, it puts state-growth pressure on +validators, it constrains block time aggressiveness — and it is +made by a single sentence buried in the BlockExecutor wiring. + +The first-principles document, in §4.7, gestures at column 2 (or 3) +and tags the dispute mechanism as ADR-required. + +These two documents are in **silent tension**. The principles say +"queries are challenge-verifiable, ADR pending". The design spec +implements "queries are inline-proof-verifiable via in-state-root +indexes". An ADR for §4.7 would have to either: + +- Confirm column 1 (and note that the v2 design's strong + verifiability is the chosen position; the latency/capacity + constraints are accepted as the cost). +- Choose column 2 or 3 (and note that the v2 design needs to be + revisited to drop the in-block `arkiv_stateRoot` commitment in + favour of off-chain indexes with a challenge mechanism). +- Choose a hybrid (and specify which queries get inline proofs and + which get challenges). + +This is the decision the architecture has not yet named, and it's the +biggest architectural lever in the design. + +### 7.2 What an ADR for §4.7 would have to settle + +Concretely, the ADR would have to answer: + +1. **Verifiability strength for queries.** Inline proof, challenge + game, or hybrid? +2. **If challenge game**: what is the dispute mechanism? On-chain or + off-chain resolution? What bonds? What dispute window? +3. **If hybrid**: which queries get which treatment? On what basis + does the system route a query to inline-proof or challenge? +4. **Completeness proofs.** Equality queries can be inline-proven + (Merkle proof of the matching items). Range, prefix, glob queries + cannot — there's no Merkle proof of "no match outside this set". + How is completeness verified for those? +5. **Indexer set.** If indexes are off-chain (column 2 or 3), who + runs the indexers? Validators? A separate set with its own + incentives? Permissioned for a launch period? +6. **Reorg semantics for indexes.** Indexes have to track the chain. + In-state indexes (column 1) reorg automatically with chain state. + Off-chain indexes (column 2 or 3) need their own reorg-handling + protocol. What is it? +7. **Migration path.** If the system starts in one column and needs + to move to another (e.g., column 1 at launch, column 2 at scale), + what is the migration protocol? Can clients keep working across + the transition? + +These are not cosmetic questions. They determine the deployment shape, +the operator economics, the user verification experience, and the +chain's growth trajectory. + +### 7.3 The downstream consequences + +The pivot decision in §7.1 has downstream consequences in three areas +the v2 spec already touches: + +- **Block-time targets.** Column 1 with aspirational subsecond block + time is the tightest version of the constraint triangle. Column 2 + or 3 give the block time back as an unconstrained design parameter. +- **Validator hardware floor.** Column 1 raises the floor as state + grows. Column 2 or 3 keep it stable. +- **Out-of-band precompile coupling.** The whole content of + [`precompile-out-of-band-coupling.md`](precompile-out-of-band-coupling.md) + exists because the v2 design puts DB writes on the synchronous + precompile path (column 1's premise). Column 2 or 3 designs put + the DB writes on the ExEx path (post-canonical), and the + multi-execution problem disappears. + +In other words: the §4.7 ADR doesn't just settle a verifiability +question. It settles a cluster of questions that the design has been +treating as independent. The latency budget, the precompile shape, +the state-growth trajectory, and the verifier UX are all downstream +of the same root choice. + +--- + +## 8. Open questions + +These are concrete questions an ADR for §4.7 (and the surrounding +architecture work) should resolve. + +1. **Verifiability strength for queries.** Inline proof, challenge, + or hybrid? The v2 design assumes column 1; the principles permit + any column. Pin this explicitly. + +2. **Cost projections for column 1 at target scale.** What is the + projected per-block index update cost at 10⁷ entities, 10⁸, + 10⁹? Against the target block-time budget, with the projected + transaction-per-block load? If the projection breaks the budget + at any of those scales, when? + +3. **Cost projections for column 2 or 3.** What is the projected + challenge-game cost — both the chain-side cost (dispute + resolution) and the off-chain cost (indexer infrastructure, + bond capital)? At what scale does the column-2/3 cost cross + the column-1 cost? + +4. **Completeness for non-equality queries.** Range, prefix, glob. + Whatever column the design lands in, completeness is harder + than equality. What's the answer? + +5. **Migration path between columns.** If the launch design is + column 1 and the long-term design is column 2, what does + migration look like? Must the verifiability mechanism be + forward-compatible from day one? + +6. **Indexer permissionlessness.** If column 2 or 3, the indexer + set has to be permissionless for the chain to satisfy the + Permissionless Operation invariant. What does that look like + operationally? + +7. **Reorg-aware indexing.** Column 1 gets reorg handling for free + from chain consensus. Columns 2 and 3 require an explicit reorg + protocol for the index. What is it? + +8. **Validator capacity floor.** What is the minimum hardware spec + the chain commits to supporting at each scale? This is the + concrete form of the Permissionless Operation invariant. + +The first three are quantitative and require measurement. The rest +are design questions answerable by analysis. + +--- + +## 9. Glossary + +- **CAP / PACELC.** Distributed-systems frameworks for reasoning + about availability vs consistency tradeoffs. CAP under partition; + PACELC under both partition and steady-state operation. PACELC is + the right tool for blockchain-with-indexes; CAP is too narrow. +- **Challenge game / fraud proof.** A verifiability mechanism in + which claims are accepted by default but can be refuted by anyone + publishing contradicting evidence within a time window. Cheap in + the no-fraud case; requires economic security and watchful + counterparties. +- **Computational proof.** A verifiability mechanism in which the + responder produces a SNARK or STARK attesting that the response + is the correct output of a known function over committed inputs. + Cryptographically strong; verifier cost is independent of + computation size. +- **Consensus envelope.** The set of state every honest validator + agrees on via consensus. State inside the envelope is + consensus-deterministic; state outside is per-validator-local. +- **Consistency (in PACELC sense).** All readers see the same data. + Strong consistency is immediate; eventual consistency is + bounded-by-time. +- **Database chain.** A chain whose primary value proposition is + storing and querying structured data, as opposed to running smart + contracts or settling assets. Arkiv is one. The Graph is not (it + layers on top of other chains). +- **Eventual consistency.** A consistency model where reads + eventually reflect all committed writes, but the exact lag is not + bounded by the protocol — only by the implementation. +- **Inline proof verifiability.** A verifiability mechanism where + the response includes a self-contained proof (typically Merkle) + against an on-chain commitment. Cryptographically strong; trust + required is only in the chain commitment. +- **In the state root.** Said of state that's committed under the + chain's Merkle state root, accessible via `eth_getProof` against + a canonical block header. +- **Permissionless.** Said of a role that any party can adopt + without prior approval — validator, indexer, prover, querier. + Bounded by the resource floor required to perform the role. +- **Synchronous indexing.** Index updates happen as part of the + same write that updates the underlying data. Reads see + immediately-consistent index state. +- **Asynchronous indexing.** Index updates happen lazily after the + underlying write commits. Reads may see stale index state during + the lag window. + +---