From 883d5081cc63a4173ad19a13d50f724e79e94d4d Mon Sep 17 00:00:00 2001 From: frisitano <35734660+frisitano@users.noreply.github.com> Date: Sun, 8 Dec 2024 14:26:37 +0000 Subject: [PATCH] Introduce Scroll Binary Patricia Merkle Trie Components (#36) * feat: introduce scroll trie hash builder * refactor: refactor HashBuilder implementation * feat: introduce StateCommitment in StateProviders * feat: introduce binary partricia trie state components * refactor: introduce StateCommimentProvider * feat: introduce HashedPostStateProvider * feat: HashedPostState from reverts * feat: introduce HashedStorageProvider * lint: revm/test-utils feature propogation * fix: add Send + Sync bound on introduced storage state api methods * feat: introduce KeyHasherProvider * feat: introduce StateRootProviderExt and integrate it (and StateRootProvider) with StateCommitment * chore: address PR feedback and enhance test coverage * fix: add merge files * fix lint * fix lint * fmt * add KeyHasher generic to DatabaseHashedStorage::from_reverts trait * add merge files * add merge files * fix: propagate feature * add merge files * cleanup Cargo.toml files * fix: Cargo.toml dependencies * refactor: refactor Cargo.toml and put tests behind scroll feature * lints and replace keccak with poseidon for HashedStorage instantiation * fix deny license and add scroll specific tests to ci * fix unit github workflow * lint and deny * fix Cargo.toml * add go build to allowed sources * update Cargo.lock * update unit ci workflow to exclude --workspace by default * fix ci and address PR feedback * replace TODO(frisitano) with TODO(scroll) * replace use of unwrap(..) in library code with expect(..) * add zktrie specification to crates/scroll/trie * chore: fix clsoing bracket in Cargo.toml --- .github/workflows/unit.yml | 20 +- Cargo.lock | 626 ++++++++++------- Cargo.toml | 4 + crates/primitives-traits/src/account.rs | 21 +- crates/primitives/src/alloy_compat.rs | 2 +- crates/primitives/src/transaction/mod.rs | 2 +- crates/rpc/rpc-eth-types/src/simulate.rs | 3 +- .../primitives/src/account_extension.rs | 4 +- crates/scroll/primitives/src/lib.rs | 4 +- crates/scroll/primitives/src/poseidon.rs | 77 +++ crates/scroll/revm/src/states/account_info.rs | 5 +- crates/scroll/revm/src/test_utils.rs | 2 +- crates/scroll/state-commitment/Cargo.toml | 79 +++ crates/scroll/state-commitment/src/account.rs | 38 ++ .../scroll/state-commitment/src/commitment.rs | 26 + crates/scroll/state-commitment/src/key.rs | 26 + crates/scroll/state-commitment/src/lib.rs | 23 + crates/scroll/state-commitment/src/root.rs | 633 ++++++++++++++++++ crates/scroll/state-commitment/src/test.rs | 255 +++++++ .../scroll/state-commitment/src/test_utils.rs | 112 ++++ crates/scroll/state-commitment/src/value.rs | 55 ++ crates/scroll/storage/src/lib.rs | 2 +- crates/scroll/trie/Cargo.toml | 28 + crates/scroll/trie/README.md | 5 + crates/scroll/trie/assets/arch.png | Bin 0 -> 54797 bytes crates/scroll/trie/assets/deletion.png | Bin 0 -> 37275 bytes crates/scroll/trie/assets/insertion.png | Bin 0 -> 33852 bytes crates/scroll/trie/assets/zktrie.md | 186 +++++ crates/scroll/trie/src/branch.rs | 155 +++++ crates/scroll/trie/src/hash_builder.rs | 585 ++++++++++++++++ crates/scroll/trie/src/leaf.rs | 22 + crates/scroll/trie/src/lib.rs | 29 + crates/scroll/trie/src/sub_tree.rs | 44 ++ crates/tracing/Cargo.toml | 2 +- crates/trie/trie/Cargo.toml | 6 +- crates/trie/trie/src/key.rs | 89 +++ crates/trie/trie/src/lib.rs | 3 + crates/trie/trie/src/node_iter.rs | 11 +- crates/trie/trie/src/state.rs | 24 +- crates/trie/trie/src/walker.rs | 16 +- deny.toml | 8 +- 41 files changed, 2945 insertions(+), 287 deletions(-) create mode 100644 crates/scroll/state-commitment/Cargo.toml create mode 100644 crates/scroll/state-commitment/src/account.rs create mode 100644 crates/scroll/state-commitment/src/commitment.rs create mode 100644 crates/scroll/state-commitment/src/key.rs create mode 100644 crates/scroll/state-commitment/src/lib.rs create mode 100644 crates/scroll/state-commitment/src/root.rs create mode 100644 crates/scroll/state-commitment/src/test.rs create mode 100644 crates/scroll/state-commitment/src/test_utils.rs create mode 100644 crates/scroll/state-commitment/src/value.rs create mode 100644 crates/scroll/trie/Cargo.toml create mode 100644 crates/scroll/trie/README.md create mode 100644 crates/scroll/trie/assets/arch.png create mode 100644 crates/scroll/trie/assets/deletion.png create mode 100644 crates/scroll/trie/assets/insertion.png create mode 100644 crates/scroll/trie/assets/zktrie.md create mode 100644 crates/scroll/trie/src/branch.rs create mode 100644 crates/scroll/trie/src/hash_builder.rs create mode 100644 crates/scroll/trie/src/leaf.rs create mode 100644 crates/scroll/trie/src/lib.rs create mode 100644 crates/scroll/trie/src/sub_tree.rs create mode 100644 crates/trie/trie/src/key.rs diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 50f5fa6e38e8..fe76e1a4ceb7 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -26,23 +26,27 @@ jobs: matrix: include: - type: ethereum - args: --features "asm-keccak ethereum" --locked + args: --features "asm-keccak ethereum" --locked --workspace --exclude ef-tests partition: 1 total_partitions: 2 - type: ethereum - args: --features "asm-keccak ethereum" --locked + args: --features "asm-keccak ethereum" --locked --workspace --exclude ef-tests partition: 2 total_partitions: 2 - type: optimism - args: --features "asm-keccak optimism" --locked --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum" + args: --features "asm-keccak optimism" --locked --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum" --workspace --exclude ef-tests partition: 1 total_partitions: 2 - type: optimism - args: --features "asm-keccak optimism" --locked --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum" + args: --features "asm-keccak optimism" --locked --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum" --workspace --exclude ef-tests partition: 2 total_partitions: 2 + - type: scroll + args: -p reth-scroll-state-commitment --locked --features "scroll" + partition: 1 + total_partitions: 1 - type: book - args: --manifest-path book/sources/Cargo.toml + args: --manifest-path book/sources/Cargo.toml --workspace --exclude ef-tests partition: 1 total_partitions: 1 timeout-minutes: 30 @@ -62,9 +66,9 @@ jobs: - name: Run tests run: | cargo nextest run \ - ${{ matrix.args }} --workspace \ - --exclude ef-tests --no-tests=warn \ - --partition hash:${{ matrix.partition }}/2 \ + ${{ matrix.args }} \ + --no-tests=warn \ + --partition hash:${{ matrix.partition }}/${{ matrix.total_partitions }} \ -E "!kind(test)" state: diff --git a/Cargo.lock b/Cargo.lock index 5bd0a599758d..e57c4247d174 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,15 +100,15 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.1.47" +version = "0.1.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c5c520273946ecf715c0010b4e3503d7eba9893cd9ce6b7fff5654c4a3c470" +checksum = "a0161082e0edd9013d23083465cc04b20e44b7a15646d36ba7b0cdb7cd6fe18f" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -116,7 +116,7 @@ dependencies = [ "num_enum", "proptest", "serde", - "strum", + "strum 0.26.3", ] [[package]] @@ -160,9 +160,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2364c782a245cf8725ea6dbfca5f530162702b5d685992ea03ce64529136cc" +checksum = "80759b3f57b3b20fa7cd8fef6479930fc95461b58ff8adea6e87e618449c8a1d" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -191,9 +191,9 @@ dependencies = [ [[package]] name = "alloy-eip7702" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6cee6a35793f3db8a5ffe60e86c695f321d081a567211245f503e8c498fce8" +checksum = "4c986539255fb839d1533c128e190e557e52ff652c9ef62939e233a81dd93f7e" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -239,9 +239,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84c506bf264110fa7e90d9924f742f40ef53c6572ea56a0b0bd714a567ed389" +checksum = "ac4b22b3e51cac09fd2adfcc73b55f447b4df669f983c13f7894ec82b607c63f" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -318,9 +318,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fce5dbd6a4f118eecc4719eaa9c7ffc31c315e6c5ccde3642db927802312425" +checksum = "9db948902dfbae96a73c2fbf1f7abec62af034ab883e4c777c3fd29702bd6e2c" dependencies = [ "alloy-rlp", "arbitrary", @@ -331,9 +331,9 @@ dependencies = [ "derive_more", "foldhash", "getrandom 0.2.15", - "hashbrown 0.15.1", + "hashbrown 0.15.2", "hex-literal", - "indexmap 2.6.0", + "indexmap 2.7.0", "itoa", "k256", "keccak-asm", @@ -342,7 +342,7 @@ dependencies = [ "proptest-derive", "rand 0.8.5", "ruint", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "serde", "sha3", "tiny-keccak", @@ -427,7 +427,7 @@ checksum = "2b09cae092c27b6f1bde952653a22708691802e57bfef4a2973b80bea21efd3f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -534,7 +534,7 @@ dependencies = [ "jsonwebtoken", "rand 0.8.5", "serde", - "strum", + "strum 0.26.3", ] [[package]] @@ -643,56 +643,56 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9343289b4a7461ed8bab8618504c995c049c082b70c7332efd7b32125633dc05" +checksum = "3bfd7853b65a2b4f49629ec975fee274faf6dff15ab8894c620943398ef283c0" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "alloy-sol-macro-expander" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4222d70bec485ceccc5d8fd4f2909edd65b5d5e43d4aca0b5dcee65d519ae98f" +checksum = "82ec42f342d9a9261699f8078e57a7a4fda8aaa73c1a212ed3987080e6a9cd13" dependencies = [ "alloy-sol-macro-input", "const-hex", - "heck", - "indexmap 2.6.0", + "heck 0.5.0", + "indexmap 2.7.0", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "syn-solidity", "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e17f2677369571b976e51ea1430eb41c3690d344fef567b840bfc0b01b6f83a" +checksum = "ed2c50e6a62ee2b4f7ab3c6d0366e5770a21cad426e109c2f40335a1b3aff3df" dependencies = [ "const-hex", "dunce", - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa64d80ae58ffaafdff9d5d84f58d03775f66c84433916dc9a64ed16af5755da" +checksum = "ac17c6e89a50fb4a758012e4b409d9a0ba575228e69b539fe37d7a1bd507ca4a" dependencies = [ "serde", "winnow", @@ -700,9 +700,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6520d427d4a8eb7aa803d852d7a52ceb0c519e784c292f64bb339e636918cf27" +checksum = "c9dc0fffe397aa17628160e16b89f704098bf3c9d74d5d369ebc239575936de5" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -785,9 +785,9 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b2e366c0debf0af77766c23694a3f863b02633050e71e096e257ffbd395e50" +checksum = "3a5fd8fea044cc9a8c8a50bb6f28e31f0385d820f116c5b98f6f4e55d6e5590b" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -875,9 +875,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "aquamarine" @@ -890,7 +890,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1113,7 +1113,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1124,7 +1124,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1162,7 +1162,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1268,7 +1268,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1376,9 +1376,9 @@ dependencies = [ "bitflags 2.6.0", "boa_interner", "boa_macros", - "indexmap 2.6.0", + "indexmap 2.7.0", "num-bigint", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", ] [[package]] @@ -1402,7 +1402,7 @@ dependencies = [ "fast-float", "hashbrown 0.14.5", "icu_normalizer", - "indexmap 2.6.0", + "indexmap 2.7.0", "intrusive-collections", "itertools 0.13.0", "num-bigint", @@ -1414,7 +1414,7 @@ dependencies = [ "portable-atomic", "rand 0.8.5", "regress", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "ryu-js", "serde", "serde_json", @@ -1448,10 +1448,10 @@ dependencies = [ "boa_gc", "boa_macros", "hashbrown 0.14.5", - "indexmap 2.6.0", + "indexmap 2.7.0", "once_cell", "phf", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "static_assertions", ] @@ -1463,7 +1463,7 @@ checksum = "240f4126219a83519bad05c9a40bfc0303921eeb571fc2d7e44c17ffac99d3f1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "synstructure", ] @@ -1483,7 +1483,7 @@ dependencies = [ "num-bigint", "num-traits", "regress", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", ] [[package]] @@ -1500,7 +1500,7 @@ checksum = "ae85205289bab1f2c7c8a30ddf0541cf89ba2ff7dbd144feef50bbfa664288d4" dependencies = [ "fast-float", "paste", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "sptr", "static_assertions", ] @@ -1585,7 +1585,7 @@ checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1596,9 +1596,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" dependencies = [ "serde", ] @@ -1629,9 +1629,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] @@ -1673,9 +1673,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" dependencies = [ "jobserver", "libc", @@ -1774,9 +1774,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.21" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", @@ -1784,9 +1784,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.21" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -1800,17 +1800,17 @@ version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "coins-bip32" @@ -1886,8 +1886,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24f165e7b643266ea80cb858aed492ad9280e3e05ce24d4a99d7d7b889b6a4d9" dependencies = [ "crossterm", - "strum", - "strum_macros", + "strum 0.26.3", + "strum_macros 0.26.4", "unicode-width 0.2.0", ] @@ -1937,9 +1937,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487981fa1af147182687064d0a2c336586d337a606595ced9ffb0c685c250c73" +checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" dependencies = [ "cfg-if", "cpufeatures", @@ -2147,7 +2147,7 @@ checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ "bitflags 2.6.0", "crossterm_winapi", - "mio 1.0.2", + "mio 1.0.3", "parking_lot", "rustix", "signal-hook", @@ -2257,7 +2257,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2281,7 +2281,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2292,7 +2292,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2414,7 +2414,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2435,7 +2435,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "unicode-xid", ] @@ -2549,7 +2549,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2697,10 +2697,10 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2711,7 +2711,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2731,7 +2731,7 @@ checksum = "3bf679796c0322556351f287a51b49e48f7c4986e727b5dd78c972d30e2e16cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2742,12 +2742,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2765,12 +2765,11 @@ dependencies = [ [[package]] name = "ethereum_ssz" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfbba28f4f3f32d92c06a64f5bf6c4537b5d4e21f28c689bd2bbaecfea4e0d3e" +checksum = "036c84bd29bff35e29bbee3c8fc0e2fb95db12b6f2f3cae82a827fbc97256f3a" dependencies = [ "alloy-primitives", - "derivative", "ethereum_serde_utils", "itertools 0.13.0", "serde", @@ -2781,14 +2780,14 @@ dependencies = [ [[package]] name = "ethereum_ssz_derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d37845ba7c16bf4be8be4b5786f03a2ba5f2fda0d7f9e7cb2282f69cff420d7" +checksum = "9dc8e67e1f770f5aa4c2c2069aaaf9daee7ac21bed357a71b911b37a58966cfb" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -3351,7 +3350,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -3402,9 +3401,9 @@ checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" [[package]] name = "generator" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb949699c3e4df3a183b1d2142cb24277057055ed23c68ed58894f76c517223" +checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" dependencies = [ "cfg-if", "libc", @@ -3517,6 +3516,14 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gobuild" +version = "0.1.0-alpha.2" +source = "git+https://github.com/scroll-tech/gobuild.git#24935c2b8f677841f22acd6710957621bb294e0e" +dependencies = [ + "cc", +] + [[package]] name = "group" version = "0.13.0" @@ -3540,7 +3547,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.6.0", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -3587,9 +3594,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", @@ -3616,6 +3623,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -3701,9 +3714,9 @@ dependencies = [ [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -3735,9 +3748,9 @@ dependencies = [ [[package]] name = "http-range-header" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" [[package]] name = "http-types" @@ -3877,7 +3890,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4027,7 +4040,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4094,7 +4107,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4135,13 +4148,13 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "arbitrary", "equivalent", - "hashbrown 0.15.1", + "hashbrown 0.15.2", "serde", ] @@ -4164,7 +4177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88" dependencies = [ "ahash", - "indexmap 2.6.0", + "indexmap 2.7.0", "is-terminal", "itoa", "log", @@ -4216,7 +4229,7 @@ dependencies = [ "pretty_assertions", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4317,9 +4330,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jni" @@ -4352,10 +4365,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -4419,7 +4433,7 @@ dependencies = [ "parking_lot", "pin-project", "rand 0.8.5", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "serde", "serde_json", "thiserror 1.0.69", @@ -4460,11 +4474,11 @@ version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06c01ae0007548e73412c08e2285ffe5d723195bf268bce67b1b77c3bb2a14d" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4630,9 +4644,9 @@ checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets 0.52.6", @@ -4797,7 +4811,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.1", + "hashbrown 0.15.2", ] [[package]] @@ -4865,9 +4879,9 @@ dependencies = [ [[package]] name = "metrics" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae428771d17306715c5091d446327d1cfdedc82185c65ba8423ab404e45bf10" +checksum = "7a7deb012b3b2767169ff203fadb4c6b0b82b947512e5eb9e0b78c2e186ad9e3" dependencies = [ "ahash", "portable-atomic", @@ -4882,7 +4896,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4892,7 +4906,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b6f8152da6d7892ff1b7a1c0fa3f435e92b5918ad67035c3bb432111d9a29b" dependencies = [ "base64 0.22.1", - "indexmap 2.6.0", + "indexmap 2.7.0", "metrics", "metrics-util", "quanta", @@ -4901,9 +4915,9 @@ dependencies = [ [[package]] name = "metrics-process" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ca8ecd85575fbb143b2678cb123bb818779391ec0f745b1c4a9dbabadde407" +checksum = "4a82c8add4382f29a122fa64fff1891453ed0f6b2867d971e7d60cb8dfa322ff" dependencies = [ "libc", "libproc", @@ -4923,8 +4937,8 @@ checksum = "15b482df36c13dd1869d73d14d28cd4855fbd6cfc32294bee109908a9f4a4ed7" dependencies = [ "crossbeam-epoch", "crossbeam-utils", - "hashbrown 0.15.1", - "indexmap 2.6.0", + "hashbrown 0.15.2", + "indexmap 2.7.0", "metrics", "ordered-float", "quanta", @@ -4996,11 +5010,10 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi 0.3.9", "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", @@ -5030,7 +5043,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -5199,6 +5212,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "num-format" version = "0.4.4" @@ -5278,7 +5302,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -5344,7 +5368,7 @@ dependencies = [ "derive_more", "serde", "serde_with", - "thiserror 2.0.3", + "thiserror 2.0.4", ] [[package]] @@ -5359,7 +5383,7 @@ dependencies = [ "alloy-sol-types", "serde", "serde_repr", - "thiserror 2.0.3", + "thiserror 2.0.4", ] [[package]] @@ -5395,7 +5419,7 @@ dependencies = [ "op-alloy-consensus", "op-alloy-genesis", "serde", - "thiserror 2.0.3", + "thiserror 2.0.4", "tracing", "unsigned-varint", ] @@ -5435,7 +5459,7 @@ dependencies = [ "op-alloy-protocol", "serde", "snap", - "thiserror 2.0.3", + "thiserror 2.0.4", ] [[package]] @@ -5515,9 +5539,9 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.7.0" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be4817d39f3272f69c59fe05d0535ae6456c2dc2fa1ba02910296c7e0a5c590" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" dependencies = [ "arbitrary", "arrayvec", @@ -5526,20 +5550,19 @@ dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec-derive", - "rustversion", "serde", ] [[package]] name = "parity-scale-codec-derive" -version = "3.7.0" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8781a75c6205af67215f382092b6e0a4ff3734798523e69073d4bcd294ec767b" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.89", + "syn 1.0.109", ] [[package]] @@ -5654,7 +5677,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -5683,7 +5706,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -5865,7 +5888,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -5916,7 +5939,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6014,7 +6037,7 @@ checksum = "6ff7ff745a347b87471d859a377a9a404361e7efc2a971d73424a6d183c0fc77" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6066,10 +6089,10 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "rustls", "socket2", - "thiserror 2.0.3", + "thiserror 2.0.4", "tokio", "tracing", ] @@ -6084,11 +6107,11 @@ dependencies = [ "getrandom 0.2.15", "rand 0.8.5", "ring", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.3", + "thiserror 2.0.4", "tinyvec", "tracing", "web-time", @@ -6218,8 +6241,8 @@ dependencies = [ "itertools 0.13.0", "lru", "paste", - "strum", - "strum_macros", + "strum 0.26.3", + "strum_macros 0.26.4", "unicode-segmentation", "unicode-truncate", "unicode-width 0.1.14", @@ -6826,7 +6849,7 @@ dependencies = [ "proc-macro2", "quote", "similar-asserts", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6930,10 +6953,10 @@ dependencies = [ "reth-storage-errors", "reth-tracing", "reth-trie-common", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "serde", "serde_json", - "strum", + "strum 0.26.3", "sysinfo", "tempfile", "test-fuzz", @@ -7513,7 +7536,7 @@ dependencies = [ "once_cell", "proptest", "proptest-derive", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "serde", "thiserror-no-std", ] @@ -7805,7 +7828,7 @@ dependencies = [ "criterion", "dashmap 6.1.0", "derive_more", - "indexmap 2.6.0", + "indexmap 2.7.0", "parking_lot", "pprof", "rand 0.8.5", @@ -7905,7 +7928,7 @@ dependencies = [ "reth-tokio-util", "reth-tracing", "reth-transaction-pool", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "schnellru", "secp256k1", "serde", @@ -8139,7 +8162,7 @@ dependencies = [ "secp256k1", "serde", "shellexpand", - "strum", + "strum 0.26.3", "thiserror 1.0.69", "tokio", "toml", @@ -8740,7 +8763,7 @@ dependencies = [ "reth-testing-utils", "reth-trie", "reth-trie-db", - "strum", + "strum 0.26.3", "tempfile", "tokio", "tracing", @@ -8772,7 +8795,7 @@ dependencies = [ "reth-testing-utils", "reth-tokio-util", "reth-tracing", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "thiserror 1.0.69", "tokio", "tracing", @@ -9137,7 +9160,7 @@ dependencies = [ "reth-errors", "reth-network-api", "serde", - "strum", + "strum 0.26.3", ] [[package]] @@ -9198,6 +9221,36 @@ dependencies = [ "serde", ] +[[package]] +name = "reth-scroll-state-commitment" +version = "1.1.2" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-rlp", + "metrics", + "poseidon-bn254", + "proptest", + "proptest-arbitrary-interop", + "reth-db", + "reth-db-api", + "reth-execution-errors", + "reth-metrics", + "reth-primitives", + "reth-primitives-traits", + "reth-provider", + "reth-scroll-execution", + "reth-scroll-primitives", + "reth-scroll-state-commitment", + "reth-scroll-trie", + "reth-trie", + "reth-trie-common", + "reth-trie-db", + "tracing", + "zktrie", + "zktrie_rust", +] + [[package]] name = "reth-scroll-storage" version = "1.1.2" @@ -9212,6 +9265,19 @@ dependencies = [ "reth-storage-errors", ] +[[package]] +name = "reth-scroll-trie" +version = "1.1.2" +dependencies = [ + "alloy-primitives", + "alloy-trie", + "hex-literal", + "proptest-arbitrary-interop", + "reth-scroll-primitives", + "reth-trie", + "tracing", +] + [[package]] name = "reth-stages" version = "1.1.2" @@ -9342,7 +9408,7 @@ dependencies = [ "clap", "derive_more", "serde", - "strum", + "strum 0.26.3", ] [[package]] @@ -9470,7 +9536,7 @@ dependencies = [ "reth-storage-api", "reth-tasks", "reth-tracing", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "schnellru", "serde", "serde_json", @@ -9506,6 +9572,7 @@ dependencies = [ "reth-storage-errors", "reth-trie-common", "serde_json", + "smallvec", "tracing", "triehash", ] @@ -9795,9 +9862,9 @@ dependencies = [ [[package]] name = "roaring" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4b84ba6e838ceb47b41de5194a60244fac43d9fe03b71dbe8c5a201081d6d1" +checksum = "f81dc953b2244ddd5e7860cb0bb2a790494b898ef321d4aff8e260efab60cc88" dependencies = [ "bytemuck", "byteorder", @@ -9845,7 +9912,7 @@ dependencies = [ "regex", "relative-path", "rustc_version 0.4.1", - "syn 2.0.89", + "syn 2.0.90", "unicode-ident", ] @@ -9894,9 +9961,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" dependencies = [ "rand 0.8.5", ] @@ -9940,9 +10007,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.18" +version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ "log", "once_cell", @@ -10247,7 +10314,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10256,7 +10323,7 @@ version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.0", "itoa", "memchr", "ryu", @@ -10282,7 +10349,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10316,7 +10383,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.6.0", + "indexmap 2.7.0", "serde", "serde_derive", "serde_json", @@ -10333,7 +10400,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10356,7 +10423,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10455,7 +10522,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio 1.0.2", + "mio 1.0.3", "signal-hook", ] @@ -10550,9 +10617,9 @@ checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -10560,9 +10627,9 @@ dependencies = [ [[package]] name = "soketto" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37468c595637c10857701c990f93a40ce0e357cedb0953d1c26c8d8027f9bb53" +checksum = "2e859df029d160cb88608f5d7df7fb4753fd20fdfb4de5644f3d8b8440841721" dependencies = [ "base64 0.22.1", "bytes", @@ -10628,13 +10695,32 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + [[package]] name = "strum" version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "strum_macros", + "strum_macros 0.26.4", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", ] [[package]] @@ -10643,11 +10729,11 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "rustversion", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10671,9 +10757,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "symbolic-common" -version = "12.12.1" +version = "12.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d4d73159efebfb389d819fd479afb2dbd57dcb3e3f4b7fcfa0e675f5a46c1cb" +checksum = "e5ba5365997a4e375660bed52f5b42766475d5bc8ceb1bb13fea09c469ea0f49" dependencies = [ "debugid", "memmap2", @@ -10683,9 +10769,9 @@ dependencies = [ [[package]] name = "symbolic-demangle" -version = "12.12.1" +version = "12.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a767859f6549c665011970874c3f541838b4835d5aaaa493d3ee383918be9f10" +checksum = "beff338b2788519120f38c59ff4bb15174f52a183e547bac3d6072c2c0aa48aa" dependencies = [ "cpp_demangle", "rustc-demangle", @@ -10705,9 +10791,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.89" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -10716,14 +10802,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76fe0a3e1476bdaa0775b9aec5b869ed9520c2b2fedfe9c6df3618f8ea6290b" +checksum = "da0523f59468a2696391f2a772edc089342aacd53c3caa2ac3264e598edf119b" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10749,7 +10835,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10820,13 +10906,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7e6b4c7391a38f0f026972ec2200bcfd1ec45533aa266fdae5858d011afc500" dependencies = [ "darling", - "heck", + "heck 0.5.0", "itertools 0.13.0", "once_cell", "prettyplease", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10859,11 +10945,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +checksum = "2f49a1853cf82743e3b7950f77e0f4d622ca36cf4317cba00c767838bac8d490" dependencies = [ - "thiserror-impl 2.0.3", + "thiserror-impl 2.0.4", ] [[package]] @@ -10874,18 +10960,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "thiserror-impl" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +checksum = "8381894bb3efe0c4acac3ded651301ceee58a15d47c2e34885ed1908ad667061" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10960,9 +11046,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -10984,9 +11070,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -11038,14 +11124,14 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.41.1" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", "libc", - "mio 1.0.2", + "mio 1.0.3", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -11062,17 +11148,16 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ "rustls", - "rustls-pki-types", "tokio", ] @@ -11106,9 +11191,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -11146,7 +11231,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.0", "serde", "serde_spanned", "toml_datetime", @@ -11233,9 +11318,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -11257,20 +11342,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -11288,9 +11373,9 @@ dependencies = [ [[package]] name = "tracing-journald" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba316a74e8fc3c3896a850dba2375928a9fa171b085ecddfc7c054d39970f3fd" +checksum = "fc0b4143302cf1022dac868d521e36e8b27691f72c84b3311750d5188ebba657" dependencies = [ "libc", "tracing-core", @@ -11322,9 +11407,9 @@ dependencies = [ [[package]] name = "tracing-serde" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" dependencies = [ "serde", "tracing-core", @@ -11332,9 +11417,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -11661,7 +11746,7 @@ checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -11712,9 +11797,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" dependencies = [ "cfg-if", "once_cell", @@ -11723,36 +11808,37 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -11760,22 +11846,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" [[package]] name = "wasm-streams" @@ -11806,9 +11892,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" dependencies = [ "js-sys", "wasm-bindgen", @@ -11932,7 +12018,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -11943,7 +12029,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -11954,7 +12040,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -11965,7 +12051,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -12240,7 +12326,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "synstructure", ] @@ -12262,7 +12348,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -12282,7 +12368,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "synstructure", ] @@ -12303,7 +12389,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -12325,7 +12411,31 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", +] + +[[package]] +name = "zktrie" +version = "0.3.0" +source = "git+https://github.com/scroll-tech/zktrie.git?rev=309160464c1cd2b87a578ed6d9b6e98205ae4640#309160464c1cd2b87a578ed6d9b6e98205ae4640" +dependencies = [ + "gobuild", + "zktrie_rust", +] + +[[package]] +name = "zktrie_rust" +version = "0.3.0" +source = "git+https://github.com/scroll-tech/zktrie.git?rev=309160464c1cd2b87a578ed6d9b6e98205ae4640#309160464c1cd2b87a578ed6d9b6e98205ae4640" +dependencies = [ + "hex", + "lazy_static", + "log", + "num", + "num-derive", + "num-traits", + "strum 0.24.1", + "strum_macros 0.24.3", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8dbe0c61e915..0e26d2e6849d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,6 +104,8 @@ members = [ "crates/scroll/primitives", "crates/scroll/revm", "crates/scroll/storage", + "crates/scroll/state-commitment", + "crates/scroll/trie", "crates/stages/api/", "crates/stages/stages/", "crates/stages/types/", @@ -409,6 +411,8 @@ reth-rpc-server-types = { path = "crates/rpc/rpc-server-types" } reth-rpc-types-compat = { path = "crates/rpc/rpc-types-compat" } reth-scroll-execution = { path = "crates/scroll/execution" } reth-scroll-primitives = { path = "crates/scroll/primitives" } +reth-scroll-state-commitment = { path = "crates/scroll/state-commitment" } +reth-scroll-trie = { path = "crates/scroll/trie" } reth-scroll-revm = { path = "crates/scroll/revm" } reth-scroll-storage = { path = "crates/scroll/storage" } reth-stages = { path = "crates/stages/stages" } diff --git a/crates/primitives-traits/src/account.rs b/crates/primitives-traits/src/account.rs index 27c03dc96b6e..d2d6dffb5723 100644 --- a/crates/primitives-traits/src/account.rs +++ b/crates/primitives-traits/src/account.rs @@ -87,6 +87,25 @@ impl Account { } } +#[cfg(feature = "scroll")] +impl Account { + /// Returns the code size (number of bytes) for the code in this account. + /// In case of no bytecode, returns 0. + pub fn get_code_size(&self) -> u64 { + self.account_extension.as_ref().unwrap().code_size + } + + /// Returns the account poseidon code hash. + /// In the case of no bytecode returns [`reth_scroll_primitives::poseidon::POSEIDON_EMPTY`] + pub fn get_poseidon_code_hash(&self) -> B256 { + self.account_extension + .as_ref() + .unwrap() + .poseidon_code_hash + .unwrap_or(reth_scroll_primitives::poseidon::POSEIDON_EMPTY) + } +} + /// Bytecode for an account. /// /// A wrapper around [`revm::primitives::Bytecode`][RevmBytecode] with encoding/decoding support. @@ -235,7 +254,7 @@ impl From for AccountInfo { .account_extension .unwrap_or_default() .poseidon_code_hash - .unwrap_or(reth_scroll_primitives::POSEIDON_EMPTY), + .unwrap_or(reth_scroll_primitives::poseidon::POSEIDON_EMPTY), } } } diff --git a/crates/primitives/src/alloy_compat.rs b/crates/primitives/src/alloy_compat.rs index f190316bcc8e..550b79f6d682 100644 --- a/crates/primitives/src/alloy_compat.rs +++ b/crates/primitives/src/alloy_compat.rs @@ -166,7 +166,7 @@ impl TryFrom for TransactionSigned { Transaction::L1Message(reth_scroll_primitives::TxL1Message { queue_index: fields.queue_index, gas_limit: inner.gas_limit(), - to: inner.to().ok_or(ConversionError::Custom( + to: inner.to().ok_or_else(|| ConversionError::Custom( "Scroll L1 message transaction do not support create transaction" .to_string(), ))?, diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index eff579cba59e..ef3037db505a 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -425,7 +425,7 @@ impl Transaction { /// Returns true if the transaction is a Scroll L1 messaging transaction. #[cfg(all(feature = "scroll", not(feature = "optimism")))] #[inline] - pub fn is_l1_message(&self) -> bool { + pub const fn is_l1_message(&self) -> bool { matches!(self, Self::L1Message(_)) } diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index a10b4afff9d7..323253216e11 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -233,7 +233,8 @@ pub fn build_block>( // let storage = hashed_state // .storages // .entry(hashed_address) - // .or_insert_with(|| HashedStorage::new(account.account_state.is_storage_cleared())); + // .or_insert_with(|| + // HashedStorage::new(account.account_state.is_storage_cleared())); // for (slot, value) in &account.storage { // let slot = B256::from(*slot); diff --git a/crates/scroll/primitives/src/account_extension.rs b/crates/scroll/primitives/src/account_extension.rs index 6c732c18c38f..39709b55dd03 100644 --- a/crates/scroll/primitives/src/account_extension.rs +++ b/crates/scroll/primitives/src/account_extension.rs @@ -1,4 +1,4 @@ -use crate::{hash_code, POSEIDON_EMPTY}; +use crate::poseidon::{hash_code, POSEIDON_EMPTY}; use alloy_primitives::B256; use serde::{Deserialize, Serialize}; @@ -35,6 +35,8 @@ impl AccountExtension { impl From<(u64, B256)> for AccountExtension { fn from(value: (u64, B256)) -> Self { + // TODO (scroll): Looks like we can create an [`AccountExtension`] with non-zero + // code size with poseidon_code_hash = None Self { code_size: value.0, poseidon_code_hash: (value.1 != POSEIDON_EMPTY).then_some(value.1), diff --git a/crates/scroll/primitives/src/lib.rs b/crates/scroll/primitives/src/lib.rs index 2f2796db6e23..ac4d81a40525 100644 --- a/crates/scroll/primitives/src/lib.rs +++ b/crates/scroll/primitives/src/lib.rs @@ -13,5 +13,5 @@ pub use l1_transaction::{ }; pub mod l1_transaction; -pub use poseidon::{hash_code, POSEIDON_EMPTY}; -mod poseidon; +/// Poseidon hashing primitives. +pub mod poseidon; diff --git a/crates/scroll/primitives/src/poseidon.rs b/crates/scroll/primitives/src/poseidon.rs index 4a5010167f51..4a559c551d40 100644 --- a/crates/scroll/primitives/src/poseidon.rs +++ b/crates/scroll/primitives/src/poseidon.rs @@ -1,10 +1,87 @@ use alloy_primitives::{b256, B256}; +pub use poseidon_bn254::{hash_with_domain, Fr, PrimeField}; /// The Poseidon hash of the empty string `""`. pub const POSEIDON_EMPTY: B256 = b256!("2098f5fb9e239eab3ceac3f27b81e481dc3124d55ffed523a839ee8446b64864"); +/// The root hash of an empty binary Merle Patricia trie. +pub const EMPTY_ROOT_HASH: B256 = B256::ZERO; + +/// Type that is used to represent a field element in binary representation. +pub type FieldElementBytes = ::Repr; + +/// The number of bytes in the binary representation of a field element. +pub const FIELD_ELEMENT_REPR_BYTES: usize = core::mem::size_of::(); + +// Half the number of bytes in the binary representation of a field element. +const HALF_FIELD_ELEMENT_REPR_BYTES: usize = FIELD_ELEMENT_REPR_BYTES / 2; + +/// The domain multiplier per field element. +pub const DOMAIN_MULTIPLIER_PER_FIELD_ELEMENT: u64 = 256; + +/// The domain for hashing two field elements. +pub const DOMAIN_TWO_FIELD_ELEMENTS: Fr = + Fr::from_raw([DOMAIN_MULTIPLIER_PER_FIELD_ELEMENT * 2, 0, 0, 0]); + +/// Hash two field elements using poseidon. +pub fn hash(element_1: Fr, element_2: Fr) -> Fr { + hash_with_domain(&[element_1, element_2], DOMAIN_TWO_FIELD_ELEMENTS) +} + /// Poseidon code hash pub fn hash_code(code: &[u8]) -> B256 { poseidon_bn254::hash_code(code).into() } + +/// Split and transform input be bytes into two field elements and hash using poseidon. +/// +/// # Panics +/// +/// This function will panic if more than 32 bytes are provided as input. +pub fn split_and_hash_be_bytes>(bytes: T) -> Fr { + debug_assert!( + bytes.as_ref().len() <= FIELD_ELEMENT_REPR_BYTES, + "bytes length should be less than or equal to field element bytes" + ); + let (bytes_lo, bytes_hi) = split_and_parse_field_elements(bytes.as_ref()); + hash(bytes_lo, bytes_hi) +} + +/// Parse input bytes into two field elements which represent the lower bytes and the upper bytes. +/// +/// # Panics +/// +/// This function will panic if more than 32 bytes are provided as input. +fn split_and_parse_field_elements(bytes: &[u8]) -> (Fr, Fr) { + debug_assert!( + bytes.len() <= FIELD_ELEMENT_REPR_BYTES, + "bytes length should be less than or equal to field element bytes" + ); + let mut bytes_lo = FieldElementBytes::default(); + let mut bytes_hi = FieldElementBytes::default(); + + if bytes.len() > (HALF_FIELD_ELEMENT_REPR_BYTES) { + bytes_lo[HALF_FIELD_ELEMENT_REPR_BYTES..] + .copy_from_slice(&bytes[..HALF_FIELD_ELEMENT_REPR_BYTES]); + bytes_hi[HALF_FIELD_ELEMENT_REPR_BYTES..bytes.len()] + .copy_from_slice(&bytes[HALF_FIELD_ELEMENT_REPR_BYTES..]); + } else { + bytes_lo[HALF_FIELD_ELEMENT_REPR_BYTES..(HALF_FIELD_ELEMENT_REPR_BYTES + bytes.len())] + .copy_from_slice(bytes) + } + + let bytes_lo = field_element_from_be_bytes(bytes_lo); + let bytes_hi = field_element_from_be_bytes(bytes_hi); + (bytes_lo, bytes_hi) +} + +/// Parses a field element from big endian bytes. +/// +/// # Panics +/// +/// This function will panic if the bytes are not a valid field element. +pub fn field_element_from_be_bytes(mut bytes: FieldElementBytes) -> Fr { + bytes.reverse(); + Fr::from_repr_vartime(bytes).expect("valid field element") +} diff --git a/crates/scroll/revm/src/states/account_info.rs b/crates/scroll/revm/src/states/account_info.rs index ad21e46f14ce..ffd6785118ed 100644 --- a/crates/scroll/revm/src/states/account_info.rs +++ b/crates/scroll/revm/src/states/account_info.rs @@ -1,4 +1,7 @@ -use reth_scroll_primitives::{hash_code, ScrollPostExecutionContext, POSEIDON_EMPTY}; +use reth_scroll_primitives::{ + poseidon::{hash_code, POSEIDON_EMPTY}, + ScrollPostExecutionContext, +}; use revm::primitives::{AccountInfo, Bytecode, B256, KECCAK_EMPTY, U256}; /// The Scroll account information. Code copy of [`AccountInfo`]. Provides additional `code_size` diff --git a/crates/scroll/revm/src/test_utils.rs b/crates/scroll/revm/src/test_utils.rs index f6538100dd87..b06844699178 100644 --- a/crates/scroll/revm/src/test_utils.rs +++ b/crates/scroll/revm/src/test_utils.rs @@ -5,7 +5,7 @@ use crate::{ }, ScrollAccountInfo, }; -use reth_scroll_primitives::{hash_code, POSEIDON_EMPTY}; +use reth_scroll_primitives::poseidon::{hash_code, POSEIDON_EMPTY}; use revm::db::{ states::{reverts::AccountInfoRevert, PlainStateReverts, StateChangeset}, AccountRevert, diff --git a/crates/scroll/state-commitment/Cargo.toml b/crates/scroll/state-commitment/Cargo.toml new file mode 100644 index 000000000000..cb90692ccaaa --- /dev/null +++ b/crates/scroll/state-commitment/Cargo.toml @@ -0,0 +1,79 @@ +[package] +name = "reth-scroll-state-commitment" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +reth-db.workspace = true +reth-execution-errors.workspace = true +reth-primitives.workspace = true +reth-primitives-traits.workspace = true +reth-scroll-execution.workspace = true +reth-scroll-primitives.workspace = true +reth-scroll-trie.workspace = true +reth-trie.workspace = true +reth-trie-db.workspace = true + +# alloy +alloy-consensus = { workspace = true, optional = true} +alloy-primitives.workspace = true +alloy-rlp.workspace = true + +# `metrics` feature +reth-metrics = { workspace = true, optional = true } +metrics = { workspace = true, optional = true } + +# zktrie +poseidon-bn254.workspace = true +zktrie_rust = { git = "https://github.com/scroll-tech/zktrie.git", rev = "309160464c1cd2b87a578ed6d9b6e98205ae4640", optional = true } +zktrie = { git = "https://github.com/scroll-tech/zktrie.git", rev = "309160464c1cd2b87a578ed6d9b6e98205ae4640", features = ["rs_zktrie"], optional = true } + +# misc +tracing.workspace = true + +[dev-dependencies] +reth-db-api.workspace = true +reth-primitives = { workspace = true, features = ["test-utils", "arbitrary"] } +reth-scroll-state-commitment = { workspace = true, features = ["test-utils"]} +reth-trie = { workspace = true, features = ["test-utils" ] } +reth-trie-common = { workspace = true, features = ["test-utils", "arbitrary"] } +reth-provider = { workspace = true, features = ["test-utils" ] } +alloy-consensus.workspace = true +zktrie_rust = { git = "https://github.com/scroll-tech/zktrie.git", rev = "309160464c1cd2b87a578ed6d9b6e98205ae4640" } +zktrie = { git = "https://github.com/scroll-tech/zktrie.git", rev = "309160464c1cd2b87a578ed6d9b6e98205ae4640", features = ["rs_zktrie"] } +proptest.workspace = true +proptest-arbitrary-interop.workspace = true + +[features] +scroll = [ + "reth-trie/scroll", + "reth-primitives-traits/scroll", + "reth-provider/scroll", + "reth-trie/scroll" +] +test-utils = [ + "dep:zktrie_rust", + "dep:zktrie", + "dep:alloy-consensus", + "reth-db/test-utils", + "reth-db-api/test-utils", + "reth-primitives/test-utils", + "reth-primitives-traits/test-utils", + "reth-provider/test-utils", + "reth-scroll-execution/test-utils", + "reth-scroll-state-commitment/test-utils", + "reth-trie/test-utils", + "reth-trie-common/test-utils", + "reth-trie-db/test-utils" +] +metrics = ["reth-metrics", "dep:metrics"] + + diff --git a/crates/scroll/state-commitment/src/account.rs b/crates/scroll/state-commitment/src/account.rs new file mode 100644 index 000000000000..ba615b5c20eb --- /dev/null +++ b/crates/scroll/state-commitment/src/account.rs @@ -0,0 +1,38 @@ +use alloy_primitives::{B256, U256}; +use reth_primitives_traits::Account; + +/// A Scroll account as represented in the trie. +#[derive(Debug)] +pub struct ScrollTrieAccount { + /// nonce + pub nonce: u64, + /// code size + pub code_size: u64, + /// balance + pub balance: U256, + /// storage root + pub storage_root: B256, + /// keccak code hash + pub code_hash: B256, + /// poseidon code hash + pub poseidon_code_hash: B256, +} + +impl From<(Account, B256)> for ScrollTrieAccount { + fn from((account, storage_root): (Account, B256)) -> Self { + Self { + nonce: account.nonce, + balance: account.balance, + storage_root, + code_hash: account.get_bytecode_hash(), + #[cfg(feature = "scroll")] + poseidon_code_hash: account.get_poseidon_code_hash(), + #[cfg(feature = "scroll")] + code_size: account.get_code_size(), + #[cfg(not(feature = "scroll"))] + poseidon_code_hash: B256::default(), + #[cfg(not(feature = "scroll"))] + code_size: 0, + } + } +} diff --git a/crates/scroll/state-commitment/src/commitment.rs b/crates/scroll/state-commitment/src/commitment.rs new file mode 100644 index 000000000000..bf6c27721706 --- /dev/null +++ b/crates/scroll/state-commitment/src/commitment.rs @@ -0,0 +1,26 @@ +use super::{PoseidonKeyHasher, StateRoot, StorageRoot}; +use reth_db::transaction::DbTx; +use reth_trie_db::{DatabaseHashedCursorFactory, DatabaseTrieCursorFactory, StateCommitment}; + +/// The state commitment type for Scroll's binary Merkle Patricia Trie. +#[derive(Debug)] +#[non_exhaustive] +pub struct BinaryMerklePatriciaTrie; + +impl StateCommitment for BinaryMerklePatriciaTrie { + type KeyHasher = PoseidonKeyHasher; + type StateRoot<'a, TX: DbTx + 'a> = + StateRoot, DatabaseHashedCursorFactory<'a, TX>>; + type StorageRoot<'a, TX: DbTx + 'a> = + StorageRoot, DatabaseHashedCursorFactory<'a, TX>>; + // TODO(scroll): replace with scroll proof type + type StateProof<'a, TX: DbTx + 'a> = reth_trie::proof::Proof< + DatabaseTrieCursorFactory<'a, TX>, + DatabaseHashedCursorFactory<'a, TX>, + >; + // TODO(scroll): replace with scroll witness type + type StateWitness<'a, TX: DbTx + 'a> = reth_trie::witness::TrieWitness< + DatabaseTrieCursorFactory<'a, TX>, + DatabaseHashedCursorFactory<'a, TX>, + >; +} diff --git a/crates/scroll/state-commitment/src/key.rs b/crates/scroll/state-commitment/src/key.rs new file mode 100644 index 000000000000..844f2c4f70f7 --- /dev/null +++ b/crates/scroll/state-commitment/src/key.rs @@ -0,0 +1,26 @@ +use alloy_primitives::B256; +use reth_scroll_primitives::poseidon::{ + split_and_hash_be_bytes, PrimeField, FIELD_ELEMENT_REPR_BYTES, +}; +use reth_trie::KeyHasher; + +/// An implementation of a key hasher that uses Poseidon. +#[derive(Clone, Debug, Default)] +pub struct PoseidonKeyHasher; + +impl KeyHasher for PoseidonKeyHasher { + /// Hashes the key using the Poseidon hash function. + /// + /// The bytes are expected to be provided in big endian format. + /// + /// Panics if the number of bytes provided is greater than the number of bytes in the + /// binary representation of a field element (32). + /// + /// Returns the hash digest in little endian representation with bits reversed. + fn hash_key>(bytes: T) -> B256 { + debug_assert!(bytes.as_ref().len() <= FIELD_ELEMENT_REPR_BYTES); + let mut bytes = split_and_hash_be_bytes(bytes.as_ref()).to_repr(); + bytes.iter_mut().for_each(|byte| *byte = byte.reverse_bits()); + bytes.into() + } +} diff --git a/crates/scroll/state-commitment/src/lib.rs b/crates/scroll/state-commitment/src/lib.rs new file mode 100644 index 000000000000..a6995479e175 --- /dev/null +++ b/crates/scroll/state-commitment/src/lib.rs @@ -0,0 +1,23 @@ +//! The implementation of scrolls binary Merkle Patricia Trie used a cryptographic state commitment. + +mod account; +pub use account::ScrollTrieAccount; + +mod commitment; +pub use commitment::BinaryMerklePatriciaTrie; + +mod root; +pub use root::{StateRoot, StorageRoot}; + +mod key; +pub use key::PoseidonKeyHasher; + +mod value; +pub use value::PoseidonValueHasher; + +/// test utils for the state commitment +#[cfg(feature = "test-utils")] +pub mod test_utils; + +#[cfg(all(test, feature = "scroll"))] +mod test; diff --git a/crates/scroll/state-commitment/src/root.rs b/crates/scroll/state-commitment/src/root.rs new file mode 100644 index 000000000000..675a2dbdbbaf --- /dev/null +++ b/crates/scroll/state-commitment/src/root.rs @@ -0,0 +1,633 @@ +use super::{PoseidonKeyHasher, PoseidonValueHasher, ScrollTrieAccount}; +use alloy_primitives::{Address, BlockNumber, B256}; +use reth_db::transaction::DbTx; +use reth_execution_errors::{StateRootError, StorageRootError}; +use reth_scroll_primitives::poseidon::EMPTY_ROOT_HASH; +use reth_scroll_trie::HashBuilder; +use reth_trie::{ + hashed_cursor::{HashedCursorFactory, HashedPostStateCursorFactory, HashedStorageCursor}, + key::BitsCompatibility, + node_iter::{TrieElement, TrieNodeIter}, + prefix_set::{PrefixSet, TriePrefixSets}, + stats::TrieTracker, + trie_cursor::{InMemoryTrieCursorFactory, TrieCursorFactory}, + updates::{StorageTrieUpdates, TrieUpdates}, + walker::TrieWalker, + HashedPostState, HashedPostStateSorted, HashedStorage, IntermediateStateRootState, KeyHasher, + Nibbles, StateRootProgress, TrieInput, +}; +use tracing::{debug, trace}; + +#[cfg(feature = "metrics")] +use reth_trie::metrics::{StateRootMetrics, TrieRootMetrics, TrieType}; + +// TODO(scroll): Instead of introducing this new type we should make StateRoot generic over +// the [`HashBuilder`] and key traversal types + +/// `StateRoot` is used to compute the root node of a state trie. +#[derive(Debug)] +pub struct StateRoot { + /// The factory for trie cursors. + pub trie_cursor_factory: T, + /// The factory for hashed cursors. + pub hashed_cursor_factory: H, + /// A set of prefix sets that have changed. + pub prefix_sets: TriePrefixSets, + /// Previous intermediate state. + previous_state: Option, + /// The number of updates after which the intermediate progress should be returned. + threshold: u64, + #[cfg(feature = "metrics")] + /// State root metrics. + metrics: StateRootMetrics, +} + +impl StateRoot { + /// Creates [`StateRoot`] with `trie_cursor_factory` and `hashed_cursor_factory`. All other + /// parameters are set to reasonable defaults. + /// + /// The cursors created by given factories are then used to walk through the accounts and + /// calculate the state root value with. + pub fn new(trie_cursor_factory: T, hashed_cursor_factory: H) -> Self { + Self { + trie_cursor_factory, + hashed_cursor_factory, + prefix_sets: TriePrefixSets::default(), + previous_state: None, + threshold: 100_000, + #[cfg(feature = "metrics")] + metrics: StateRootMetrics::default(), + } + } + + /// Set the prefix sets. + pub fn with_prefix_sets(mut self, prefix_sets: TriePrefixSets) -> Self { + self.prefix_sets = prefix_sets; + self + } + + /// Set the threshold. + pub const fn with_threshold(mut self, threshold: u64) -> Self { + self.threshold = threshold; + self + } + + /// Set the threshold to maximum value so that intermediate progress is not returned. + pub const fn with_no_threshold(mut self) -> Self { + self.threshold = u64::MAX; + self + } + + /// Set the previously recorded intermediate state. + pub fn with_intermediate_state(mut self, state: Option) -> Self { + self.previous_state = state; + self + } + + /// Set the hashed cursor factory. + pub fn with_hashed_cursor_factory(self, hashed_cursor_factory: HF) -> StateRoot { + StateRoot { + trie_cursor_factory: self.trie_cursor_factory, + hashed_cursor_factory, + prefix_sets: self.prefix_sets, + threshold: self.threshold, + previous_state: self.previous_state, + #[cfg(feature = "metrics")] + metrics: self.metrics, + } + } + + /// Set the trie cursor factory. + pub fn with_trie_cursor_factory(self, trie_cursor_factory: TF) -> StateRoot { + StateRoot { + trie_cursor_factory, + hashed_cursor_factory: self.hashed_cursor_factory, + prefix_sets: self.prefix_sets, + threshold: self.threshold, + previous_state: self.previous_state, + #[cfg(feature = "metrics")] + metrics: self.metrics, + } + } +} + +impl StateRoot +where + T: TrieCursorFactory + Clone, + H: HashedCursorFactory + Clone, +{ + /// Walks the intermediate nodes of existing state trie (if any) and hashed entries. Feeds the + /// nodes into the hash builder. Collects the updates in the process. + /// + /// Ignores the threshold. + /// + /// # Returns + /// + /// The intermediate progress of state root computation and the trie updates. + pub fn root_with_updates(self) -> Result<(B256, TrieUpdates), StateRootError> { + match self.with_no_threshold().calculate(true)? { + StateRootProgress::Complete(root, _, updates) => Ok((root, updates)), + StateRootProgress::Progress(..) => unreachable!(), // unreachable threshold + } + } + + /// Walks the intermediate nodes of existing state trie (if any) and hashed entries. Feeds the + /// nodes into the hash builder. + /// + /// # Returns + /// + /// The state root hash. + pub fn root(self) -> Result { + match self.calculate(false)? { + StateRootProgress::Complete(root, _, _) => Ok(root), + StateRootProgress::Progress(..) => unreachable!(), // update retenion is disabled + } + } + + /// Walks the intermediate nodes of existing state trie (if any) and hashed entries. Feeds the + /// nodes into the hash builder. Collects the updates in the process. + /// + /// # Returns + /// + /// The intermediate progress of state root computation. + pub fn root_with_progress(self) -> Result { + self.calculate(true) + } + + fn calculate(self, retain_updates: bool) -> Result { + trace!(target: "trie::state_root", "calculating state root"); + let mut tracker = TrieTracker::default(); + let mut trie_updates = TrieUpdates::default(); + + let trie_cursor = self.trie_cursor_factory.account_trie_cursor()?; + + let hashed_account_cursor = self.hashed_cursor_factory.hashed_account_cursor()?; + let (mut hash_builder, mut account_node_iter) = match self.previous_state { + Some(state) => { + let hash_builder = state.hash_builder.with_updates(retain_updates).into(); + + let walker = TrieWalker::from_stack( + trie_cursor, + state.walker_stack, + self.prefix_sets.account_prefix_set, + ) + .with_deletions_retained(retain_updates); + let node_iter = TrieNodeIter::new(walker, hashed_account_cursor) + .with_last_hashed_key(state.last_account_key); + (hash_builder, node_iter) + } + None => { + let hash_builder = HashBuilder::default().with_updates(retain_updates); + let walker = TrieWalker::new(trie_cursor, self.prefix_sets.account_prefix_set) + .with_deletions_retained(retain_updates); + let node_iter = TrieNodeIter::new(walker, hashed_account_cursor); + (hash_builder, node_iter) + } + }; + + let mut hashed_entries_walked = 0; + let mut updated_storage_nodes = 0; + while let Some(node) = account_node_iter.try_next()? { + match node { + TrieElement::Branch(node) => { + tracker.inc_branch(); + hash_builder.add_branch(node.key, node.value, node.children_are_in_trie); + } + TrieElement::Leaf(hashed_address, account) => { + tracker.inc_leaf(); + hashed_entries_walked += 1; + + // We assume we can always calculate a storage root without + // OOMing. This opens us up to a potential DOS vector if + // a contract had too many storage entries and they were + // all buffered w/o us returning and committing our intermediate + // progress. + // TODO: We can consider introducing the TrieProgress::Progress/Complete + // abstraction inside StorageRoot, but let's give it a try as-is for now. + let storage_root_calculator = StorageRoot::new_hashed( + self.trie_cursor_factory.clone(), + self.hashed_cursor_factory.clone(), + hashed_address, + #[cfg(feature = "metrics")] + self.metrics.storage_trie.clone(), + ) + .with_prefix_set( + self.prefix_sets + .storage_prefix_sets + .get(&hashed_address) + .cloned() + .unwrap_or_default(), + ); + + let storage_root = if retain_updates { + let (root, storage_slots_walked, updates) = + storage_root_calculator.root_with_updates()?; + hashed_entries_walked += storage_slots_walked; + // We only walk over hashed address once, so it's safe to insert. + updated_storage_nodes += updates.len(); + trie_updates.insert_storage_updates(hashed_address, updates); + root + } else { + storage_root_calculator.root()? + }; + + let account = ScrollTrieAccount::from((account, storage_root)); + let account_hash = PoseidonValueHasher::hash_account(account); + hash_builder.add_leaf( + Nibbles::unpack_and_truncate_bits(hashed_address), + account_hash.as_slice(), + ); + + // Decide if we need to return intermediate progress. + let total_updates_len = updated_storage_nodes + + account_node_iter.walker.removed_keys_len() + + hash_builder.updates_len(); + if retain_updates && total_updates_len as u64 >= self.threshold { + let (walker_stack, walker_deleted_keys) = account_node_iter.walker.split(); + trie_updates.removed_nodes.extend(walker_deleted_keys); + let (hash_builder, hash_builder_updates) = hash_builder.split(); + trie_updates.account_nodes.extend(hash_builder_updates); + + let state = IntermediateStateRootState { + hash_builder: hash_builder.into(), + walker_stack, + last_account_key: hashed_address, + }; + + return Ok(StateRootProgress::Progress( + Box::new(state), + hashed_entries_walked, + trie_updates, + )) + } + } + } + } + + let root = hash_builder.root(); + + let removed_keys = account_node_iter.walker.take_removed_keys(); + trie_updates.finalize( + hash_builder.into(), + removed_keys, + self.prefix_sets.destroyed_accounts, + ); + + let stats = tracker.finish(); + + #[cfg(feature = "metrics")] + self.metrics.state_trie.record(stats); + + trace!( + target: "trie::state_root", + %root, + duration = ?stats.duration(), + branches_added = stats.branches_added(), + leaves_added = stats.leaves_added(), + "calculated state root" + ); + + Ok(StateRootProgress::Complete(root, hashed_entries_walked, trie_updates)) + } +} + +/// `StorageRoot` is used to compute the root node of an account storage trie. +#[derive(Debug)] +pub struct StorageRoot { + /// A reference to the database transaction. + pub trie_cursor_factory: T, + /// The factory for hashed cursors. + pub hashed_cursor_factory: H, + /// The hashed address of an account. + pub hashed_address: B256, + /// The set of storage slot prefixes that have changed. + pub prefix_set: PrefixSet, + /// Storage root metrics. + #[cfg(feature = "metrics")] + metrics: TrieRootMetrics, +} + +impl StorageRoot { + /// Creates a new storage root calculator given a raw address. + pub fn new( + trie_cursor_factory: T, + hashed_cursor_factory: H, + address: Address, + #[cfg(feature = "metrics")] metrics: TrieRootMetrics, + ) -> Self { + Self::new_hashed( + trie_cursor_factory, + hashed_cursor_factory, + PoseidonKeyHasher::hash_key(address), + #[cfg(feature = "metrics")] + metrics, + ) + } + + /// Creates a new storage root calculator given a hashed address. + pub fn new_hashed( + trie_cursor_factory: T, + hashed_cursor_factory: H, + hashed_address: B256, + #[cfg(feature = "metrics")] metrics: TrieRootMetrics, + ) -> Self { + Self { + trie_cursor_factory, + hashed_cursor_factory, + hashed_address, + prefix_set: PrefixSet::default(), + #[cfg(feature = "metrics")] + metrics, + } + } + + /// Set the changed prefixes. + pub fn with_prefix_set(mut self, prefix_set: PrefixSet) -> Self { + self.prefix_set = prefix_set; + self + } + + /// Set the hashed cursor factory. + pub fn with_hashed_cursor_factory(self, hashed_cursor_factory: HF) -> StorageRoot { + StorageRoot { + trie_cursor_factory: self.trie_cursor_factory, + hashed_cursor_factory, + hashed_address: self.hashed_address, + prefix_set: self.prefix_set, + #[cfg(feature = "metrics")] + metrics: self.metrics, + } + } + + /// Set the trie cursor factory. + pub fn with_trie_cursor_factory(self, trie_cursor_factory: TF) -> StorageRoot { + StorageRoot { + trie_cursor_factory, + hashed_cursor_factory: self.hashed_cursor_factory, + hashed_address: self.hashed_address, + prefix_set: self.prefix_set, + #[cfg(feature = "metrics")] + metrics: self.metrics, + } + } +} + +impl StorageRoot +where + T: TrieCursorFactory, + H: HashedCursorFactory, +{ + /// Walks the hashed storage table entries for a given address and calculates the storage root. + /// + /// # Returns + /// + /// The storage root and storage trie updates for a given address. + pub fn root_with_updates(self) -> Result<(B256, usize, StorageTrieUpdates), StorageRootError> { + self.calculate(true) + } + + /// Walks the hashed storage table entries for a given address and calculates the storage root. + /// + /// # Returns + /// + /// The storage root. + pub fn root(self) -> Result { + let (root, _, _) = self.calculate(false)?; + Ok(root) + } + + /// Walks the hashed storage table entries for a given address and calculates the storage root. + /// + /// # Returns + /// + /// The storage root, number of walked entries and trie updates + /// for a given address if requested. + pub fn calculate( + self, + retain_updates: bool, + ) -> Result<(B256, usize, StorageTrieUpdates), StorageRootError> { + trace!(target: "trie::storage_root", hashed_address = ?self.hashed_address, "calculating storage root"); + + let mut hashed_storage_cursor = + self.hashed_cursor_factory.hashed_storage_cursor(self.hashed_address)?; + + // short circuit on empty storage + if hashed_storage_cursor.is_storage_empty()? { + return Ok((EMPTY_ROOT_HASH, 0, StorageTrieUpdates::deleted())) + } + + let mut tracker = TrieTracker::default(); + let trie_cursor = self.trie_cursor_factory.storage_trie_cursor(self.hashed_address)?; + let walker = + TrieWalker::new(trie_cursor, self.prefix_set).with_deletions_retained(retain_updates); + + let mut hash_builder = HashBuilder::default().with_updates(retain_updates); + + let mut storage_node_iter = TrieNodeIter::new(walker, hashed_storage_cursor); + while let Some(node) = storage_node_iter.try_next()? { + match node { + TrieElement::Branch(node) => { + tracker.inc_branch(); + hash_builder.add_branch(node.key, node.value, node.children_are_in_trie); + } + TrieElement::Leaf(hashed_slot, value) => { + let hashed_value = PoseidonValueHasher::hash_storage(value); + tracker.inc_leaf(); + hash_builder.add_leaf( + Nibbles::unpack_and_truncate_bits(hashed_slot), + hashed_value.as_ref(), + ); + } + } + } + + let root = hash_builder.root(); + + let mut trie_updates = StorageTrieUpdates::default(); + let removed_keys = storage_node_iter.walker.take_removed_keys(); + trie_updates.finalize(hash_builder.into(), removed_keys); + + let stats = tracker.finish(); + + #[cfg(feature = "metrics")] + self.metrics.record(stats); + + trace!( + target: "trie::storage_root", + %root, + hashed_address = %self.hashed_address, + duration = ?stats.duration(), + branches_added = stats.branches_added(), + leaves_added = stats.leaves_added(), + "calculated storage root" + ); + + let storage_slots_walked = stats.leaves_added() as usize; + Ok((root, storage_slots_walked, trie_updates)) + } +} + +use reth_trie_db::{ + DatabaseHashedCursorFactory, DatabaseStateRoot, DatabaseStorageRoot, DatabaseTrieCursorFactory, + PrefixSetLoader, +}; +use std::ops::RangeInclusive; + +impl<'a, TX: DbTx> DatabaseStateRoot<'a, TX> + for StateRoot, DatabaseHashedCursorFactory<'a, TX>> +{ + fn from_tx(tx: &'a TX) -> Self { + Self::new(DatabaseTrieCursorFactory::new(tx), DatabaseHashedCursorFactory::new(tx)) + } + + fn incremental_root_calculator( + tx: &'a TX, + range: RangeInclusive, + ) -> Result { + let loaded_prefix_sets = PrefixSetLoader::<_, PoseidonKeyHasher>::new(tx).load(range)?; + Ok(Self::from_tx(tx).with_prefix_sets(loaded_prefix_sets)) + } + + fn incremental_root( + tx: &'a TX, + range: RangeInclusive, + ) -> Result { + debug!(target: "trie::loader", ?range, "incremental state root"); + Self::incremental_root_calculator(tx, range)?.root() + } + + fn incremental_root_with_updates( + tx: &'a TX, + range: RangeInclusive, + ) -> Result<(B256, TrieUpdates), StateRootError> { + debug!(target: "trie::loader", ?range, "incremental state root"); + Self::incremental_root_calculator(tx, range)?.root_with_updates() + } + + fn incremental_root_with_progress( + tx: &'a TX, + range: RangeInclusive, + ) -> Result { + debug!(target: "trie::loader", ?range, "incremental state root with progress"); + Self::incremental_root_calculator(tx, range)?.root_with_progress() + } + + fn overlay_root(tx: &'a TX, post_state: HashedPostState) -> Result { + let prefix_sets = post_state.construct_prefix_sets().freeze(); + let state_sorted = post_state.into_sorted(); + StateRoot::new( + DatabaseTrieCursorFactory::new(tx), + HashedPostStateCursorFactory::new(DatabaseHashedCursorFactory::new(tx), &state_sorted), + ) + .with_prefix_sets(prefix_sets) + .root() + } + + fn overlay_root_with_updates( + tx: &'a TX, + post_state: HashedPostState, + ) -> Result<(B256, TrieUpdates, HashedPostStateSorted), StateRootError> { + let prefix_sets = post_state.construct_prefix_sets().freeze(); + let state_sorted = post_state.into_sorted(); + let (root, updates) = StateRoot::new( + DatabaseTrieCursorFactory::new(tx), + HashedPostStateCursorFactory::new(DatabaseHashedCursorFactory::new(tx), &state_sorted), + ) + .with_prefix_sets(prefix_sets) + .root_with_updates()?; + Ok((root, updates, state_sorted)) + } + + fn overlay_root_from_nodes(tx: &'a TX, input: TrieInput) -> Result { + let state_sorted = input.state.into_sorted(); + let nodes_sorted = input.nodes.into_sorted(); + StateRoot::new( + InMemoryTrieCursorFactory::new(DatabaseTrieCursorFactory::new(tx), &nodes_sorted), + HashedPostStateCursorFactory::new(DatabaseHashedCursorFactory::new(tx), &state_sorted), + ) + .with_prefix_sets(input.prefix_sets.freeze()) + .root() + } + + fn overlay_root_from_nodes_with_updates( + tx: &'a TX, + input: TrieInput, + ) -> Result<(B256, TrieUpdates), StateRootError> { + let state_sorted = input.state.into_sorted(); + let nodes_sorted = input.nodes.into_sorted(); + StateRoot::new( + InMemoryTrieCursorFactory::new(DatabaseTrieCursorFactory::new(tx), &nodes_sorted), + HashedPostStateCursorFactory::new(DatabaseHashedCursorFactory::new(tx), &state_sorted), + ) + .with_prefix_sets(input.prefix_sets.freeze()) + .root_with_updates() + } + + fn root(tx: &'a TX) -> Result { + Self::from_tx(tx).root() + } + + fn root_with_updates(tx: &'a TX) -> Result<(B256, TrieUpdates), StateRootError> { + Self::from_tx(tx).root_with_updates() + } + + fn root_from_prefix_sets_with_updates( + tx: &'a TX, + prefix_sets: TriePrefixSets, + ) -> Result<(B256, TrieUpdates), StateRootError> { + Self::from_tx(tx).with_prefix_sets(prefix_sets).root_with_updates() + } + + fn root_with_progress( + tx: &'a TX, + state: Option, + ) -> Result { + Self::from_tx(tx).with_intermediate_state(state).root_with_progress() + } +} + +impl<'a, TX: DbTx> DatabaseStorageRoot<'a, TX> + for StorageRoot, DatabaseHashedCursorFactory<'a, TX>> +{ + fn from_tx(tx: &'a TX, address: Address) -> Self { + Self::new( + DatabaseTrieCursorFactory::new(tx), + DatabaseHashedCursorFactory::new(tx), + address, + #[cfg(feature = "metrics")] + TrieRootMetrics::new(TrieType::Storage), + ) + } + + fn from_tx_hashed(tx: &'a TX, hashed_address: B256) -> Self { + Self::new_hashed( + DatabaseTrieCursorFactory::new(tx), + DatabaseHashedCursorFactory::new(tx), + hashed_address, + #[cfg(feature = "metrics")] + TrieRootMetrics::new(TrieType::Storage), + ) + } + + fn overlay_root( + tx: &'a TX, + address: Address, + hashed_storage: HashedStorage, + ) -> Result { + let prefix_set = hashed_storage.construct_prefix_set().freeze(); + let state_sorted = HashedPostState::from_hashed_storage( + PoseidonKeyHasher::hash_key(address), + hashed_storage, + ) + .into_sorted(); + StorageRoot::new( + DatabaseTrieCursorFactory::new(tx), + HashedPostStateCursorFactory::new(DatabaseHashedCursorFactory::new(tx), &state_sorted), + address, + #[cfg(feature = "metrics")] + TrieRootMetrics::new(TrieType::Storage), + ) + .with_prefix_set(prefix_set) + .root() + } +} diff --git a/crates/scroll/state-commitment/src/test.rs b/crates/scroll/state-commitment/src/test.rs new file mode 100644 index 000000000000..0423d6ed3975 --- /dev/null +++ b/crates/scroll/state-commitment/src/test.rs @@ -0,0 +1,255 @@ +#![allow(missing_docs)] + +use alloy_primitives::{Address, Uint, B256, U256}; +use proptest::{prelude::ProptestConfig, proptest}; +use proptest_arbitrary_interop::arb; +use reth_db::{ + cursor::{DbCursorRO, DbCursorRW, DbDupCursorRW}, + tables, + transaction::DbTxMut, +}; +use reth_primitives::{Account, StorageEntry}; +use reth_provider::test_utils::create_test_provider_factory; +use reth_scroll_state_commitment::{ + test_utils::{b256_clear_last_byte, b256_reverse_bits, u256_clear_msb}, + PoseidonKeyHasher, StateRoot, StorageRoot, +}; + +use reth_scroll_state_commitment::test_utils::b256_clear_first_byte; +use reth_trie::{ + trie_cursor::InMemoryTrieCursorFactory, updates::TrieUpdates, HashedPostState, HashedStorage, + KeyHasher, +}; +use reth_trie_db::{DatabaseStateRoot, DatabaseStorageRoot, DatabaseTrieCursorFactory}; +use std::collections::BTreeMap; + +proptest! { + #![proptest_config(ProptestConfig { + cases: 6, ..ProptestConfig::default() + })] + + #[test] + fn fuzz_in_memory_account_nodes(mut init_state: BTreeMap)>, state_updates: [BTreeMap>; 10]) { + let init_state: BTreeMap = init_state.into_iter().map(|(hashed_address, (nonce, balance, code))| { + let hashed_address = b256_clear_last_byte(hashed_address); + let balance = u256_clear_msb(balance); + #[cfg(feature = "scroll")] + let account_extension = code.map( |(_, code_hash, code_size)| (code_size, b256_clear_first_byte(code_hash)).into()).or_else(|| Some(Default::default())); + let account = Account { balance, nonce: nonce.into(), bytecode_hash: code.as_ref().map(|(code_hash, _, _)| *code_hash), + #[cfg(feature = "scroll")] + account_extension + }; + (hashed_address, account) + }).collect(); + let state_updates: Vec> = state_updates.into_iter().map(|update| { + update.into_iter().map(|(hashed_address, update)| { + let hashed_address = b256_clear_last_byte(hashed_address); + let account = update.map(|balance| Account { + balance: u256_clear_msb(balance), + #[cfg(feature = "scroll")] + account_extension: Some(Default::default()), + ..Default::default() }); + (hashed_address, account) + }).collect::>() + }).collect(); + + + let factory = create_test_provider_factory(); + let provider = factory.provider_rw().unwrap(); + let mut hashed_account_cursor = provider.tx_ref().cursor_write::().unwrap(); + + // Insert init state into database + for (hashed_address, account) in init_state.clone() { + hashed_account_cursor.upsert(b256_reverse_bits(hashed_address), account).unwrap(); + } + + // Compute initial root and updates + let (_, mut trie_nodes) = StateRoot::from_tx(provider.tx_ref()) + .root_with_updates() + .unwrap(); + + let mut state = init_state; + for state_update in state_updates { + // Insert state updates into database + let mut hashed_state = HashedPostState::default(); + for (hashed_address, account) in state_update { + if let Some(account) = account { + hashed_account_cursor.upsert(b256_reverse_bits(hashed_address), account).unwrap(); + hashed_state.accounts.insert(b256_reverse_bits(hashed_address), Some(account)); + state.insert(hashed_address, account); + } else { + hashed_state.accounts.insert(b256_reverse_bits(hashed_address), None); + state.remove(&hashed_address); + } + } + + // Compute root with in-memory trie nodes overlay + let (state_root, trie_updates) = StateRoot::from_tx(provider.tx_ref()) + .with_prefix_sets(hashed_state.construct_prefix_sets().freeze()) + .with_trie_cursor_factory(InMemoryTrieCursorFactory::new( + DatabaseTrieCursorFactory::new(provider.tx_ref()), &trie_nodes.clone().into_sorted()) + ) + .root_with_updates() + .unwrap(); + + trie_nodes.extend(trie_updates); + + // Verify the result + let expected_root = reth_scroll_state_commitment::test_utils::state_root( + state.iter().map(|(key, account)| (*key, (*account, std::iter::empty()))) + ); + assert_eq!(expected_root.0, state_root.0); + + } + } + + #[test] + fn fuzz_in_memory_storage_nodes(mut init_storage: BTreeMap, storage_updates: [(bool, BTreeMap); 10]) { + let hashed_address = B256::random(); + let factory = create_test_provider_factory(); + let provider = factory.provider_rw().unwrap(); + let mut hashed_storage_cursor = + provider.tx_ref().cursor_write::().unwrap(); + + // Insert init state into database + let init_storage: BTreeMap = init_storage.into_iter().map(|(slot, value)| { + let hashed_slot = b256_clear_last_byte(slot); + hashed_storage_cursor + .upsert(hashed_address, StorageEntry { key: b256_reverse_bits(hashed_slot), value }) + .unwrap(); + (hashed_slot, value) + }).collect(); + let storage_updates: Vec<(bool, BTreeMap)> = storage_updates.into_iter().map(|(is_deleted, updates)| { + let updates = updates.into_iter().map(|(slot, value)| { + let slot = b256_clear_last_byte(slot); + (slot, value) + }).collect(); + (is_deleted, updates) + }).collect(); + + // Compute initial storage root and updates + let (_, _, mut storage_trie_nodes) = + StorageRoot::from_tx_hashed(provider.tx_ref(), hashed_address).root_with_updates().unwrap(); + + let mut storage = init_storage; + for (is_deleted, mut storage_update) in storage_updates { + // Insert state updates into database + if is_deleted && hashed_storage_cursor.seek_exact(hashed_address).unwrap().is_some() { + hashed_storage_cursor.delete_current_duplicates().unwrap(); + } + let mut hashed_storage = HashedStorage::new(is_deleted); + for (hashed_slot, value) in storage_update.clone() { + hashed_storage_cursor + .upsert(hashed_address, StorageEntry { key: b256_reverse_bits(hashed_slot), value }) + .unwrap(); + hashed_storage.storage.insert(b256_reverse_bits(hashed_slot), value); + } + + // Compute root with in-memory trie nodes overlay + let mut trie_nodes = TrieUpdates::default(); + trie_nodes.insert_storage_updates(hashed_address, storage_trie_nodes.clone()); + let (storage_root, _, trie_updates) = + StorageRoot::from_tx_hashed(provider.tx_ref(), hashed_address) + .with_prefix_set(hashed_storage.construct_prefix_set().freeze()) + .with_trie_cursor_factory(InMemoryTrieCursorFactory::new( + DatabaseTrieCursorFactory::new(provider.tx_ref()), + &trie_nodes.into_sorted(), + )) + .root_with_updates() + .unwrap(); + + storage_trie_nodes.extend(trie_updates); + + // Verify the result + if is_deleted { + storage.clear(); + } + storage.append(&mut storage_update); + let expected_root = reth_scroll_state_commitment::test_utils::storage_root(storage.clone()); + assert_eq!(expected_root, storage_root); + } + } +} + +#[test] +fn test_basic_state_root_with_updates_succeeds() { + let address_1 = Address::with_last_byte(0); + let address_2 = Address::with_last_byte(3); + let address_3 = Address::with_last_byte(7); + let account_1 = Account { + balance: Uint::from(1), + #[cfg(feature = "scroll")] + account_extension: Some(Default::default()), + ..Default::default() + }; + let account_2 = Account { + balance: Uint::from(2), + #[cfg(feature = "scroll")] + account_extension: Some(Default::default()), + ..Default::default() + }; + let account_3 = Account { + balance: Uint::from(3), + #[cfg(feature = "scroll")] + account_extension: Some(Default::default()), + ..Default::default() + }; + + let factory = create_test_provider_factory(); + let tx = factory.provider_rw().unwrap(); + + insert_account(tx.tx_ref(), address_1, account_1, &Default::default()); + insert_account(tx.tx_ref(), address_2, account_2, &Default::default()); + insert_account(tx.tx_ref(), address_3, account_3, &Default::default()); + + tx.commit().unwrap(); + + let tx = factory.provider_rw().unwrap(); + let (_root, _updates) = StateRoot::from_tx(tx.tx_ref()).root_with_updates().unwrap(); +} + +fn insert_account( + tx: &impl DbTxMut, + address: Address, + account: Account, + storage: &BTreeMap, +) { + let hashed_address = PoseidonKeyHasher::hash_key(address); + tx.put::(hashed_address, account).unwrap(); + insert_storage(tx, hashed_address, storage); +} + +fn insert_storage(tx: &impl DbTxMut, hashed_address: B256, storage: &BTreeMap) { + for (k, v) in storage { + tx.put::( + hashed_address, + StorageEntry { key: PoseidonKeyHasher::hash_key(k), value: *v }, + ) + .unwrap(); + } +} +#[test] +fn arbitrary_storage_root() { + proptest!(ProptestConfig::with_cases(10), |(item in arb::<(Address, std::collections::BTreeMap)>())| { + let (address, storage) = item; + + let hashed_address = PoseidonKeyHasher::hash_key(address); + let factory = create_test_provider_factory(); + let tx = factory.provider_rw().unwrap(); + let storage: BTreeMap = storage.into_iter().map(|(key, value)| { + let key = b256_clear_last_byte(key); + tx.tx_ref().put::( + hashed_address, + StorageEntry { key, value }, + ) + .unwrap(); + (b256_reverse_bits(key), value) + }).collect(); + tx.commit().unwrap(); + + let tx = factory.provider_rw().unwrap(); + let got = StorageRoot::from_tx(tx.tx_ref(), address).root().unwrap(); + let expected = reth_scroll_state_commitment::test_utils::storage_root(storage.into_iter()); + assert_eq!(expected, got); + }); +} diff --git a/crates/scroll/state-commitment/src/test_utils.rs b/crates/scroll/state-commitment/src/test_utils.rs new file mode 100644 index 000000000000..8791c6eeb36a --- /dev/null +++ b/crates/scroll/state-commitment/src/test_utils.rs @@ -0,0 +1,112 @@ +use alloy_consensus::constants::KECCAK_EMPTY; +use alloy_primitives::{B256, U256}; +use poseidon_bn254::{hash_with_domain, Fr, PrimeField}; +use reth_primitives::Account; +use zktrie::HashField; +use zktrie_rust::{db::SimpleDb, hash::AsHash, types::Hashable}; + +const ACCOUNT_COMPRESSION_FLAG: u32 = 8; +const STORAGE_COMPRESSION_FLAG: u32 = 1; + +/// Reverses the ordering of bits in a [`B256`] type. +pub fn b256_reverse_bits(b256: B256) -> B256 { + let mut b256 = b256.0; + for byte in &mut b256 { + *byte = byte.reverse_bits(); + } + B256::from(b256) +} + +/// Clear the last byte of a [`B256`] type. +pub fn b256_clear_last_byte(mut b256: B256) -> B256 { + // set the largest byte to 0 + >::as_mut(&mut b256)[31] = 0; + b256 +} + +/// Clear the first byte of a [`B256`] type. +pub fn b256_clear_first_byte(mut b256: B256) -> B256 { + // set the smallest byte to 0 + >::as_mut(&mut b256)[0] = 0; + b256 +} + +/// Clear the most significant byte of a [`U256`] type. +pub fn u256_clear_msb(mut balance: U256) -> U256 { + // set the most significant 8 bits to 0 + unsafe { + balance.as_limbs_mut()[3] &= 0x00FFFFFFFFFFFFFF; + } + balance +} + +/// Calculates the state root of a set of accounts and their storage entries using the zktrie +/// implementation. +pub fn state_root(accounts: I) -> B256 +where + I: IntoIterator, + S: IntoIterator, +{ + let mut trie = zktrie(); + for (address, (account, storage)) in accounts { + let key = parse_key_as_hash(address); + let mut account_bytes = Vec::with_capacity(5); + + #[cfg(feature = "scroll")] + let code_size = account.get_code_size(); + #[cfg(not(feature = "scroll"))] + let code_size = 0; + + #[cfg(feature = "scroll")] + let poseidon_code_hash = account.get_poseidon_code_hash(); + #[cfg(not(feature = "scroll"))] + let poseidon_code_hash = B256::default(); + + account_bytes.push(U256::from_limbs([account.nonce, code_size, 0, 0]).to_be_bytes()); + account_bytes.push(account.balance.to_be_bytes()); + account_bytes.push(storage_root(storage).0); + account_bytes.push(account.bytecode_hash.unwrap_or(KECCAK_EMPTY).0); + account_bytes.push(poseidon_code_hash.0); + + trie.try_update(&key, ACCOUNT_COMPRESSION_FLAG, account_bytes).unwrap(); + } + trie.prepare_root().unwrap(); + let root = trie.root().to_bytes(); + B256::from_slice(&root) +} + +/// Calculates the storage root of a set of storage entries using the zktrie implementation. +pub fn storage_root(storage: S) -> B256 +where + S: IntoIterator, +{ + let mut storage_trie = zktrie(); + for (key, value) in storage { + let key = parse_key_as_hash(key); + storage_trie.try_update(&key, STORAGE_COMPRESSION_FLAG, vec![value.to_be_bytes()]).unwrap(); + } + storage_trie.prepare_root().unwrap(); + let root = storage_trie.root().to_bytes(); + B256::from_slice(&root) +} + +fn parse_key_as_hash(key: B256) -> AsHash { + let mut key = key.0; + key.reverse(); + AsHash::from_bytes(&key).unwrap() +} + +fn poseidon_hash_scheme(a: &[u8; 32], b: &[u8; 32], domain: &[u8; 32]) -> Option<[u8; 32]> { + let a = Fr::from_repr_vartime(*a)?; + let b = Fr::from_repr_vartime(*b)?; + let domain = Fr::from_repr_vartime(*domain)?; + Some(hash_with_domain(&[a, b], domain).to_repr()) +} + +fn zktrie() -> zktrie_rust::raw::ZkTrieImpl, SimpleDb, 248> { + zktrie::init_hash_scheme_simple(poseidon_hash_scheme); + zktrie_rust::raw::ZkTrieImpl::, SimpleDb, 248>::new_zktrie_impl( + SimpleDb::new(), + ) + .unwrap() +} diff --git a/crates/scroll/state-commitment/src/value.rs b/crates/scroll/state-commitment/src/value.rs new file mode 100644 index 000000000000..387d7fd96408 --- /dev/null +++ b/crates/scroll/state-commitment/src/value.rs @@ -0,0 +1,55 @@ +use crate::ScrollTrieAccount; +use alloy_primitives::{B256, U256}; +use reth_scroll_primitives::poseidon::{ + field_element_from_be_bytes, hash_with_domain, split_and_hash_be_bytes, FieldElementBytes, Fr, + PrimeField, DOMAIN_MULTIPLIER_PER_FIELD_ELEMENT, +}; + +/// An implementation of a value hasher that uses Poseidon. +/// +/// This hash provides hashing of a [`ScrollTrieAccount`] and a storage entry ([`U256`]). +#[derive(Debug)] +pub struct PoseidonValueHasher; + +impl PoseidonValueHasher { + /// The number of field elements in the account hashing. + const ACCOUNT_HASHING_FIELD_ELEMENTS: u64 = 5; + + /// The domain for hashing the account. + const ACCOUNT_HASHING_DOMAIN: Fr = Fr::from_raw([ + Self::ACCOUNT_HASHING_FIELD_ELEMENTS * DOMAIN_MULTIPLIER_PER_FIELD_ELEMENT, + 0, + 0, + 0, + ]); + + /// Hashes the account using Poseidon hash function. + pub(crate) fn hash_account(account: ScrollTrieAccount) -> B256 { + // combine nonce and code size and parse into field element + let nonce_code_size_bytes = field_element_from_be_bytes( + // TODO(scroll): Replace with native handling of bytes instead of using U256. + U256::from_limbs([account.nonce, account.code_size, 0, 0]).to_be_bytes(), + ); + + // parse remaining field elements + let balance = field_element_from_be_bytes(account.balance.to_be_bytes()); + let keccak_code_hash = split_and_hash_be_bytes(account.code_hash); + let storage_root = field_element_from_be_bytes(account.storage_root.0); + let poseidon_code_hash = field_element_from_be_bytes(account.poseidon_code_hash.0); + + // hash field elements + let digest_1 = + hash_with_domain(&[nonce_code_size_bytes, balance], Self::ACCOUNT_HASHING_DOMAIN); + let digest_2 = + hash_with_domain(&[storage_root, keccak_code_hash], Self::ACCOUNT_HASHING_DOMAIN); + let digest = hash_with_domain(&[digest_1, digest_2], Self::ACCOUNT_HASHING_DOMAIN); + let digest = hash_with_domain(&[digest, poseidon_code_hash], Self::ACCOUNT_HASHING_DOMAIN); + + digest.to_repr().into() + } + + /// Hashes the storage entry using Poseidon hash function. + pub(crate) fn hash_storage(entry: U256) -> B256 { + split_and_hash_be_bytes::(entry.to_be_bytes()).to_repr().into() + } +} diff --git a/crates/scroll/storage/src/lib.rs b/crates/scroll/storage/src/lib.rs index 1810369a8096..5e333750e0b2 100644 --- a/crates/scroll/storage/src/lib.rs +++ b/crates/scroll/storage/src/lib.rs @@ -105,7 +105,7 @@ mod tests { use reth_codecs::{test_utils::UnusedBits, validate_bitflag_backwards_compat}; use reth_primitives_traits::Account; use reth_revm::{test_utils::StateProviderTest, Database}; - use reth_scroll_primitives::{hash_code, AccountExtension}; + use reth_scroll_primitives::{poseidon::hash_code, AccountExtension}; #[test] fn test_ensure_account_backwards_compatibility() { diff --git a/crates/scroll/trie/Cargo.toml b/crates/scroll/trie/Cargo.toml new file mode 100644 index 000000000000..c9886b436781 --- /dev/null +++ b/crates/scroll/trie/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "reth-scroll-trie" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +reth-scroll-primitives.workspace = true +reth-trie.workspace = true +alloy-trie = { workspace = true, features = ["serde"] } +alloy-primitives.workspace = true +tracing.workspace = true + +[dev-dependencies] +hex-literal = "0.4" +proptest-arbitrary-interop.workspace = true + +[features] +scroll = [ + "reth-trie/scroll" +] \ No newline at end of file diff --git a/crates/scroll/trie/README.md b/crates/scroll/trie/README.md new file mode 100644 index 000000000000..8645b5efadd5 --- /dev/null +++ b/crates/scroll/trie/README.md @@ -0,0 +1,5 @@ +# scroll-trie + +Fast binary Merkle-Patricia Trie (zktrie) state root calculator and proof generator for prefix-sorted bits. + +Please see the specification of zktrie [here](assets/zktrie.md). \ No newline at end of file diff --git a/crates/scroll/trie/assets/arch.png b/crates/scroll/trie/assets/arch.png new file mode 100644 index 0000000000000000000000000000000000000000..aca3f8d0c5ce4eedb5d40b8dedb39d226e57efbe GIT binary patch literal 54797 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
    -9R17$h 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

    62bb*C9PM+BO06 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 literal 0 HcmV?d00001 diff --git a/crates/scroll/trie/assets/insertion.png b/crates/scroll/trie/assets/insertion.png new file mode 100644 index 0000000000000000000000000000000000000000..942338a07f2ea46e33fc899fd222b6ae6671f5e4 GIT binary patch literal 33852 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 +zkTrie Structure +

    Figure 1. zkTrie Structure
    + + +In essence, zkTrie is a sparse binary Merkle Patricia Trie, depicted in the above figure. +Before diving into the Sparse Binary Merkle Patricia Trie, let's briefly touch on Merkle Trees and Patricia Tries. +* **Merkle Tree**: A Merkle Tree is a tree where each leaf node represents a hash of a data block, and each non-leaf node represents the hash of its child nodes. +* **Patricia Trie**: A Patricia Trie is a type of radix tree or compressed trie used to store key-value pairs efficiently. It encodes the nodes with same prefix of the key to share the common path, where the path is determined by the value of the node key. + +As illustrated in the Figure 1, there are three types of nodes in the zkTrie. +- Parent Node (type: 0): Given the zkTrie is a binary tree, a parent node has two children. +- Leaf Node (type: 1): A leaf node holds the data of a key-value pair. +- Empty Node (type: 2): An empty node is a special type of node, indicating the sub-trie that shares the same prefix is empty. + +In zkTrie, we use Poseidon hash to compute the node hash because it's more friendly and efficient to prove it in the zk circuit. + +## 2. Tree Construction + +Given a key-value pair, we first compute a *secure key* for the corresponding leaf node by hashing the original key (i.e., account address and storage key) using the Poseidon hash function. This can make the key uniformly distributed over the key space. The node key hashing method is described in the [Node Hashing](#3-node-hashing) section below. + +We then encode the path of a new leaf node by traversing the secure key from Least Significant Bit (LSB) to the Most Significant Bit (MSB). At each step, if the bit is 0, we will traverse to the left child; otherwise, traverse to the right child. + +We limit the maximum depth of zkTrie to 248, meaning that the tree will only traverse the lower 248 bits of the key. This is because the secure key space is a finite field used by Poseidon hash that doesn't occupy the full range of power of 2. This leads to an ambiguous bit representation of the key in a finite field and thus causes a soundness issue in the zk circuit. But if we truncate the key to lower 248 bits, the key space can fully occupy the range of $2^{248}$ and won't have the ambiguity in the bit representation. + +We also apply an optimization to reduce the tree depth by contracting a subtree that has only one leaf node to a single leaf node. For example, in the Figure 1, the tree has three nodes in total, with keys `0100`, `0010`, and `1010`. Because there is only one node that has key with suffix `00`, the leaf node for key `0100` only traverses the suffix `00` and doesn't fully expand its key which would have resulted in depth of 4. + +## 3. Node Hashing + +In this section, we will describe how leaf secure key and node merkle hash are computed. We use Poseidon hash in both hashing computation, denoted as `h` in the doc below. + + + +### 3.1 Empty Node + +The node hash of an empty node is 0. + +### 3.2 Parent Node + +The parent node hash is computed as follows + +```go +parentNodeHash = h(leftChildHash, rightChildHash) +``` + +### 3.3 Leaf Node + +The node hash of a leaf node is computed as follows + +```go +leafNodeHash = h(h(1, nodeKey), valueHash) +``` + +The leaf node can hold two types of values: Ethereum accounts and storage key-value pairs. Next, we will describe how the node key and value hash are computed for each leaf node type. + +#### Ethereum Account Leaf Node +For an Ethereum Account Leaf Node, it consists of an Ethereum address and a state account struct. The secure key is derived from the Ethereum address. +``` +address[0:20] (20 bytes in big-endian) +valHi = address[0:16] +valLo = address[16:20] * 2^96 (padding 12 bytes of 0 at the end) +nodeKey = h(valHi, valLo) +``` + +A state account struct in the Scroll consists of the following fields (`Fr` indicates the finite field used in Poseidon hash and is a 254-bit value) + +- `Nonce`: u64 +- `Balance`: u256, but treated as Fr +- `StorageRoot`: Fr +- `KeccakCodeHash`: u256 +- `PoseidonCodeHash`: Fr +- `CodeSize`: u64 + +Before computing the value hash, the state account is first marshaled into a list of `u256` values. The marshaling scheme is + +``` +(The following scheme assumes the big-endian encoding) +[0:32] (bytes in big-endian) + [0:16] Reserved with all 0 + [16:24] CodeSize, uint64 in big-endian + [24:32] Nonce, uint64 in big-endian +[32:64] Balance +[64:96] StorageRoot +[96:128] KeccakCodeHash +[128:160] PoseidonCodehash +(total 160 bytes) +``` + +The marshal function also returns a `flag` value along with a vector of `u256` values. The `flag` is a bitmap that indicates whether a `u256` value CANNOT be treated as a field element (Fr). The `flag` value for state account is 8, shown below. + +``` ++--------------------+---------+------+----------+----------+ +| 0 | 1 | 2 | 3 | 4 | (index) ++--------------------+---------+------+----------+----------+ +| nonce||codesize||0 | balance | root | keccak | poseidon | (u256) ++--------------------+---------+------+----------+----------+ +| 0 | 0 | 0 | 1 | 0 | (flag bits) ++--------------------+---------+------+----------+----------+ +(LSB) (MSB) +``` + +The value hash is computed in two steps: +1. Convert the value that cannot be represented as a field element of the Poseidon hash to the field element. +2. Combine field elements in a binary tree structure till the tree root is treated as the value hash. + +In the first step, when the bit in the `flag` is 1 indicating the `u256` value that cannot be treated as a field element, we split the value into a high-128bit value and a low-128bit value, and then pass them to a Poseidon hash to derive a field element value, `h(valueHi, valueLo)`. + +Based on the definition, the value hash of the state account is computed as follows. + +``` +valueHash = +h( + h( + h(nonce||codesize||0, balance), + h( + storageRoot, + h(keccakCodeHash[0:16], keccakCodeHash[16:32]), // convert Keccak codehash to a field element + ), + ), + poseidonCodeHash, +) +``` + +#### Storage Leaf Node + +For a Storage Leaf Node, it is a key-value pair, which both are a `u256` value. The secure key of this leaf node is derived from the storage key. + +``` +storageKey[0:32] (32 bytes in big-endian) +valHi = storageKey[0:16] +valLo = storageKey[16:32] +nodeKey = h(valHi, valLo) +``` + +The storage value is a `u256` value. The `flag` for the storage value is 1, showed below. + +``` ++--------------+ +| 0 | (index) ++--------------+ +| storageValue | (u256) ++--------------+ +| 1 | (flag bits) ++--------------+ +``` + +The value hash is computed as follows + +```go +valueHash = h(storageValue[0:16], storageValue[16:32]) +``` + +## 4. Tree Operations + +### 4.1 Insertion + +
    +zkTrie Structure +
    Figure 2. Insert a new leaf node to zkTrie
    +
    + +When we insert a new leaf node to the existing zkTrie, there could be two cases illustrated in the Figure 2. + +1. When traversing the path of the node key, it reaches an empty node (Figure 2(b)). In this case, we just need to replace this empty node by this leaf node and backtrace the path to update the merkle hash of parent nodes till the root. +2. When traversing the path of the node key, it reaches another leaf node `b` (Figure 2(c)). In this case, we need to push down the existing leaf node `b` until the next bit in the node keys of two leaf nodes differs. At each push-down step, we need to insert an empty sibling node when necessary. When we reach the level where the bits differ, we then place two leaf nodes `b` and `c` as the left child and the right child depending on their bits. At last, we backtrace the path and update the merkle hash of all parent nodes. + +### 4.2 Deletion + +
    +zkTrie Structure +
    Figure 3. Delete a leaf node from the zkTrie
    +
    + + +The deletion of a leaf node is similar to the insertion. There are two cases illustrated in the Figure 3. + +1. The sibling node of to-be-deleted leaf node is a parent node (Figure 3(b)). In this case, we can just replace the node `a` by an empty node and update the node hash of its ancestors till the root node. +2. The node of to-be-deleted leaf node is a leaf node (Figure 3(c)). Similarly, we first replace the leaf node by an empty node and start to contract its sibling node upwards until its sibling node is not an empty node. For example, in Figure 3(c), we first replace the leaf node `b` by an empty node. During the contraction, since the sibling of node `c` now becomes an empty node, we move node `c` one level upward to replace its parent node. The new sibling of node `c`, node `e`, is still an empty node. So again we move node `c` upward. Now that the sibling of node `c` is node `a`, the deletion process is finished. + +Note that the sibling of a leaf node in a valid zkTrie cannot be an empty node. Otherwise, we should always prune the subtree and move the leaf node upwards. diff --git a/crates/scroll/trie/src/branch.rs b/crates/scroll/trie/src/branch.rs new file mode 100644 index 000000000000..4599749c1ef1 --- /dev/null +++ b/crates/scroll/trie/src/branch.rs @@ -0,0 +1,155 @@ +use super::{ + BRANCH_NODE_LBRB_DOMAIN, BRANCH_NODE_LBRT_DOMAIN, BRANCH_NODE_LTRB_DOMAIN, + BRANCH_NODE_LTRT_DOMAIN, +}; +use alloy_primitives::{hex, B256}; +use alloy_trie::TrieMask; +use core::{fmt, ops::Range, slice::Iter}; +use reth_scroll_primitives::poseidon::{hash_with_domain, Fr, PrimeField}; + +#[allow(unused_imports)] +use alloc::vec::Vec; + +/// The range of valid child indexes. +pub(crate) const CHILD_INDEX_RANGE: Range = 0..2; + +/// A trie mask to extract the two child indexes from a branch node. +pub(crate) const CHILD_INDEX_MASK: TrieMask = TrieMask::new(0b11); + +/// A reference to branch node and its state mask. +/// NOTE: The stack may contain more items that specified in the state mask. +#[derive(Clone)] +pub(crate) struct BranchNodeRef<'a> { + /// Reference to the collection of hash nodes. + /// NOTE: The referenced stack might have more items than the number of children + /// for this node. We should only ever access items starting from + /// [`BranchNodeRef::first_child_index`]. + pub stack: &'a [B256], + /// Reference to bitmask indicating the presence of children at + /// the respective nibble positions. + pub state_mask: TrieMask, +} + +impl fmt::Debug for BranchNodeRef<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("BranchNodeRef") + .field("stack", &self.stack.iter().map(hex::encode).collect::>()) + .field("state_mask", &self.state_mask) + .field("first_child_index", &self.first_child_index()) + .finish() + } +} + +impl<'a> BranchNodeRef<'a> { + /// Create a new branch node from the stack of nodes. + #[inline] + pub(crate) const fn new(stack: &'a [B256], state_mask: TrieMask) -> Self { + Self { stack, state_mask } + } + + /// Returns the stack index of the first child for this node. + /// + /// # Panics + /// + /// If the stack length is less than number of children specified in state mask. + /// Means that the node is in inconsistent state. + #[inline] + pub(crate) fn first_child_index(&self) -> usize { + self.stack + .len() + .checked_sub((self.state_mask & CHILD_INDEX_MASK).count_ones() as usize) + .expect("branch node stack is in inconsistent state") + } + + #[inline] + fn children(&self) -> impl Iterator)> + '_ { + BranchChildrenIter::new(self) + } + + /// Given the hash mask of children, return an iterator over stack items + /// that match the mask. + #[inline] + pub(crate) fn child_hashes(&self, hash_mask: TrieMask) -> impl Iterator + '_ { + self.children() + .filter_map(|(i, c)| c.map(|c| (i, c))) + .filter(move |(index, _)| hash_mask.is_bit_set(*index)) + .map(|(_, child)| B256::from_slice(&child[..])) + } + + pub(crate) fn hash(&self) -> B256 { + let mut children_iter = self.children(); + + let left_child = children_iter + .next() + .map(|(_, c)| *c.unwrap_or_default()) + .expect("branch node has two children"); + let left_child = + Fr::from_repr_vartime(left_child.0).expect("left child is a valid field element"); + let right_child = children_iter + .next() + .map(|(_, c)| *c.unwrap_or_default()) + .expect("branch node has two children"); + let right_child = + Fr::from_repr_vartime(right_child.0).expect("right child is a valid field element"); + + hash_with_domain(&[left_child, right_child], self.hashing_domain()).to_repr().into() + } + + fn hashing_domain(&self) -> Fr { + match *self.state_mask { + 0b1011 => BRANCH_NODE_LBRT_DOMAIN, + 0b1111 => BRANCH_NODE_LTRT_DOMAIN, + 0b0111 => BRANCH_NODE_LTRB_DOMAIN, + 0b0011 => BRANCH_NODE_LBRB_DOMAIN, + _ => unreachable!("invalid branch node state mask"), + } + } +} + +/// Iterator over branch node children. +#[derive(Debug)] +struct BranchChildrenIter<'a> { + range: Range, + state_mask: TrieMask, + stack_iter: Iter<'a, B256>, +} + +impl<'a> BranchChildrenIter<'a> { + /// Create new iterator over branch node children. + fn new(node: &BranchNodeRef<'a>) -> Self { + Self { + range: CHILD_INDEX_RANGE, + state_mask: node.state_mask, + stack_iter: node.stack[node.first_child_index()..].iter(), + } + } +} + +impl<'a> Iterator for BranchChildrenIter<'a> { + type Item = (u8, Option<&'a B256>); + + #[inline] + fn next(&mut self) -> Option { + let i = self.range.next()?; + let value = self + .state_mask + .is_bit_set(i) + .then(|| unsafe { self.stack_iter.next().unwrap_unchecked() }); + Some((i, value)) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let len = self.len(); + (len, Some(len)) + } +} + +impl core::iter::FusedIterator for BranchChildrenIter<'_> {} + +impl ExactSizeIterator for BranchChildrenIter<'_> { + #[inline] + fn len(&self) -> usize { + self.range.len() + } +} diff --git a/crates/scroll/trie/src/hash_builder.rs b/crates/scroll/trie/src/hash_builder.rs new file mode 100644 index 000000000000..39d2bed347fd --- /dev/null +++ b/crates/scroll/trie/src/hash_builder.rs @@ -0,0 +1,585 @@ +use crate::{ + branch::{BranchNodeRef, CHILD_INDEX_MASK}, + leaf::HashLeaf, + sub_tree::SubTreeRef, +}; +use alloy_primitives::{map::HashMap, B256}; +use alloy_trie::{ + hash_builder::{HashBuilderValue, HashBuilderValueRef}, + nodes::LeafNodeRef, + proof::{ProofNodes, ProofRetainer}, + BranchNodeCompact, Nibbles, TrieMask, +}; +use core::cmp; +use reth_scroll_primitives::poseidon::EMPTY_ROOT_HASH; +use tracing::trace; + +#[derive(Debug, Default)] +#[allow(missing_docs)] +pub struct HashBuilder { + pub key: Nibbles, + pub value: HashBuilderValue, + pub stack: Vec, + + // TODO(scroll): Introduce terminator / leaf masks + pub state_masks: Vec, + pub tree_masks: Vec, + pub hash_masks: Vec, + + pub stored_in_database: bool, + + pub updated_branch_nodes: Option>, + pub proof_retainer: Option, +} + +impl HashBuilder { + /// Enables the Hash Builder to store updated branch nodes. + /// + /// Call [`HashBuilder::split`] to get the updates to branch nodes. + pub fn with_updates(mut self, retain_updates: bool) -> Self { + self.set_updates(retain_updates); + self + } + + /// Enable specified proof retainer. + pub fn with_proof_retainer(mut self, retainer: ProofRetainer) -> Self { + self.proof_retainer = Some(retainer); + self + } + + /// Enables the Hash Builder to store updated branch nodes. + /// + /// Call [`HashBuilder::split`] to get the updates to branch nodes. + pub fn set_updates(&mut self, retain_updates: bool) { + if retain_updates { + self.updated_branch_nodes = Some(HashMap::default()); + } + } + + /// Splits the [`HashBuilder`] into a [`HashBuilder`] and hash builder updates. + pub fn split(mut self) -> (Self, HashMap) { + let updates = self.updated_branch_nodes.take(); + (self, updates.unwrap_or_default()) + } + + /// Take and return retained proof nodes. + pub fn take_proof_nodes(&mut self) -> ProofNodes { + self.proof_retainer.take().map(ProofRetainer::into_proof_nodes).unwrap_or_default() + } + + /// The number of total updates accrued. + /// Returns `0` if [`Self::with_updates`] was not called. + pub fn updates_len(&self) -> usize { + self.updated_branch_nodes.as_ref().map(|u| u.len()).unwrap_or(0) + } + + /// Print the current stack of the Hash Builder. + pub fn print_stack(&self) { + println!("============ STACK ==============="); + for item in &self.stack { + println!("{}", alloy_primitives::hex::encode(item)); + } + println!("============ END STACK ==============="); + } + + /// Adds a new leaf element and its value to the trie hash builder. + pub fn add_leaf(&mut self, key: Nibbles, value: &[u8]) { + assert!(key > self.key, "add_leaf key {:?} self.key {:?}", key, self.key); + if !self.key.is_empty() { + self.update(&key); + } + self.set_key_value(key, HashBuilderValueRef::Bytes(value)); + } + + /// Adds a new branch element and its hash to the trie hash builder. + pub fn add_branch(&mut self, key: Nibbles, value: B256, stored_in_database: bool) { + assert!( + key > self.key || (self.key.is_empty() && key.is_empty()), + "add_branch key {:?} self.key {:?}", + key, + self.key + ); + if !self.key.is_empty() { + self.update(&key); + } else if key.is_empty() { + self.stack.push(value); + } + self.set_key_value(key, HashBuilderValueRef::Hash(&value)); + self.stored_in_database = stored_in_database; + } + + /// Returns the current root hash of the trie builder. + pub fn root(&mut self) -> B256 { + // Clears the internal state + if !self.key.is_empty() { + self.update(&Nibbles::default()); + self.key.clear(); + self.value.clear(); + } + let root = self.current_root(); + if root == EMPTY_ROOT_HASH { + if let Some(proof_retainer) = self.proof_retainer.as_mut() { + proof_retainer.retain(&Nibbles::default(), &[]) + } + } + root + } + + #[inline] + fn set_key_value(&mut self, key: Nibbles, value: HashBuilderValueRef<'_>) { + self.log_key_value("old value"); + self.key = key; + self.value.set_from_ref(value); + self.log_key_value("new value"); + } + + fn log_key_value(&self, msg: &str) { + trace!(target: "trie::hash_builder", + key = ?self.key, + value = ?self.value, + "{msg}", + ); + } + + fn current_root(&self) -> B256 { + if let Some(node_ref) = self.stack.last() { + let mut root = *node_ref; + root.reverse(); + root + } else { + EMPTY_ROOT_HASH + } + } + + /// Given a new element, it appends it to the stack and proceeds to loop through the stack state + /// and convert the nodes it can into branch / extension nodes and hash them. This ensures + /// that the top of the stack always contains the merkle root corresponding to the trie + /// built so far. + fn update(&mut self, succeeding: &Nibbles) { + let mut build_extensions = false; + // current / self.key is always the latest added element in the trie + let mut current = self.key.clone(); + debug_assert!(!current.is_empty()); + + trace!(target: "trie::hash_builder", ?current, ?succeeding, "updating merkle tree"); + + let mut i = 0usize; + loop { + let _span = tracing::trace_span!(target: "trie::hash_builder", "loop", i, ?current, build_extensions).entered(); + + let preceding_exists = !self.state_masks.is_empty(); + let preceding_len = self.state_masks.len().saturating_sub(1); + + let common_prefix_len = succeeding.common_prefix_length(current.as_slice()); + let len = cmp::max(preceding_len, common_prefix_len); + assert!(len < current.len(), "len {} current.len {}", len, current.len()); + + trace!( + target: "trie::hash_builder", + ?len, + ?common_prefix_len, + ?preceding_len, + preceding_exists, + "prefix lengths after comparing keys" + ); + + // Adjust the state masks for branch calculation + let extra_digit = current[len]; + if self.state_masks.len() <= len { + let new_len = len + 1; + trace!(target: "trie::hash_builder", new_len, old_len = self.state_masks.len(), "scaling state masks to fit"); + self.state_masks.resize(new_len, TrieMask::default()); + } + self.state_masks[len] |= TrieMask::from_nibble(extra_digit); + trace!( + target: "trie::hash_builder", + ?extra_digit, + groups = ?self.state_masks, + ); + + // Adjust the tree masks for exporting to the DB + if self.tree_masks.len() < current.len() { + self.resize_masks(current.len()); + } + + let mut len_from = len; + if !succeeding.is_empty() || preceding_exists { + len_from += 1; + } + trace!(target: "trie::hash_builder", "skipping {len_from} nibbles"); + + // The key without the common prefix + let short_node_key = current.slice(len_from..); + trace!(target: "trie::hash_builder", ?short_node_key); + + // Concatenate the 2 nodes together + if !build_extensions { + match self.value.as_ref() { + HashBuilderValueRef::Bytes(leaf_value) => { + // TODO(scroll): Replace with terminator masks + // Set the terminator mask for the leaf node + self.state_masks[len] |= TrieMask::new(0b100 << extra_digit); + let leaf_node = LeafNodeRef::new(¤t, leaf_value); + let leaf_hash = leaf_node.hash_leaf(); + trace!( + target: "trie::hash_builder", + ?leaf_node, + ?leaf_hash, + "pushing leaf node", + ); + self.stack.push(leaf_hash); + // self.retain_proof_from_stack(¤t.slice(..len_from)); + } + HashBuilderValueRef::Hash(hash) => { + trace!(target: "trie::hash_builder", ?hash, "pushing branch node hash"); + self.stack.push(*hash); + + if self.stored_in_database { + self.tree_masks[current.len() - 1] |= TrieMask::from_nibble( + current + .last() + .expect("must have at least a single bit in the current key"), + ); + } + self.hash_masks[current.len() - 1] |= TrieMask::from_nibble( + current + .last() + .expect("must have at least a single bit in the current key"), + ); + + build_extensions = true; + } + } + } + + if build_extensions && !short_node_key.is_empty() { + self.update_masks(¤t, len_from); + let stack_last = self.stack.pop().expect("there should be at least one stack item"); + let sub_tree = SubTreeRef::new(&short_node_key, &stack_last); + let sub_tree_root = sub_tree.root(); + + trace!( + target: "trie::hash_builder", + ?short_node_key, + ?sub_tree_root, + "pushing subtree root", + ); + self.stack.push(sub_tree_root); + // self.retain_proof_from_stack(¤t.slice(..len_from)); + self.resize_masks(len_from); + } + + if preceding_len <= common_prefix_len && !succeeding.is_empty() { + trace!(target: "trie::hash_builder", "no common prefix to create branch nodes from, returning"); + return; + } + + // Insert branch nodes in the stack + if !succeeding.is_empty() || preceding_exists { + // Pushes the corresponding branch node to the stack + let children = self.push_branch_node(¤t, len); + // Need to store the branch node in an efficient format outside of the hash builder + self.store_branch_node(¤t, len, children); + } + + self.state_masks.resize(len, TrieMask::default()); + self.resize_masks(len); + + if preceding_len == 0 { + trace!(target: "trie::hash_builder", "0 or 1 state masks means we have no more elements to process"); + return; + } + + current.truncate(preceding_len); + trace!(target: "trie::hash_builder", ?current, "truncated nibbles to {} bytes", preceding_len); + + trace!(target: "trie::hash_builder", groups = ?self.state_masks, "popping empty state masks"); + while self.state_masks.last() == Some(&TrieMask::default()) { + self.state_masks.pop(); + } + + build_extensions = true; + + i += 1; + } + } + + /// Given the size of the longest common prefix, it proceeds to create a branch node + /// from the state mask and existing stack state, and store its RLP to the top of the stack, + /// after popping all the relevant elements from the stack. + /// + /// Returns the hashes of the children of the branch node, only if `updated_branch_nodes` is + /// enabled. + fn push_branch_node(&mut self, _current: &Nibbles, len: usize) -> Vec { + let state_mask = self.state_masks[len]; + let hash_mask = self.hash_masks[len]; + let branch_node = BranchNodeRef::new(&self.stack, state_mask); + // Avoid calculating this value if it's not needed. + let children = if self.updated_branch_nodes.is_some() { + branch_node.child_hashes(hash_mask).collect() + } else { + vec![] + }; + + let branch_hash = branch_node.hash(); + + // TODO: enable proof retention + // self.retain_proof_from_stack(¤t.slice(..len)); + + // Clears the stack from the branch node elements + let first_child_idx = branch_node.first_child_index(); + trace!( + target: "trie::hash_builder", + new_len = first_child_idx, + old_len = self.stack.len(), + "resizing stack to prepare branch node" + ); + self.stack.resize_with(first_child_idx, Default::default); + + trace!(target: "trie::hash_builder", ?branch_hash, "pushing branch node with {state_mask:?} mask + from stack"); + + self.stack.push(branch_hash); + children + } + + /// Given the current nibble prefix and the highest common prefix length, proceeds + /// to update the masks for the next level and store the branch node and the + /// masks in the database. We will use that when consuming the intermediate nodes + /// from the database to efficiently build the trie. + fn store_branch_node(&mut self, current: &Nibbles, len: usize, children: Vec) { + trace!(target: "trie::hash_builder", ?current, ?len, ?children, "store branch node"); + if len > 0 { + let parent_index = len - 1; + self.hash_masks[parent_index] |= TrieMask::from_nibble(current[parent_index]); + } + + let store_in_db_trie = !self.tree_masks[len].is_empty() || !self.hash_masks[len].is_empty(); + if store_in_db_trie { + if len > 0 { + let parent_index = len - 1; + self.tree_masks[parent_index] |= TrieMask::from_nibble(current[parent_index]); + } + + if self.updated_branch_nodes.is_some() { + let common_prefix = current.slice(..len); + let node = BranchNodeCompact::new( + self.state_masks[len] & CHILD_INDEX_MASK, + self.tree_masks[len], + self.hash_masks[len], + children, + (len == 0).then(|| self.current_root()), + ); + trace!(target: "trie::hash_builder", ?node, "storing updated intermediate node"); + self.updated_branch_nodes + .as_mut() + .expect("updates_branch_nodes is some") + .insert(common_prefix, node); + } + } + } + + // TODO(scroll): Enable proof retention + // fn retain_proof_from_stack(&mut self, prefix: &Nibbles) { + // if let Some(proof_retainer) = self.proof_retainer.as_mut() { + // proof_retainer.retain( + // prefix, + // self.stack.last().expect("there should be at least one stack item").as_ref(), + // ); + // } + // } + + fn update_masks(&mut self, current: &Nibbles, len_from: usize) { + if len_from > 0 { + let flag = TrieMask::from_nibble(current[len_from - 1]); + + self.hash_masks[len_from - 1] &= !flag; + + if !self.tree_masks[current.len() - 1].is_empty() { + self.tree_masks[len_from - 1] |= flag; + } + } + } + + fn resize_masks(&mut self, new_len: usize) { + trace!( + target: "trie::hash_builder", + new_len, + old_tree_mask_len = self.tree_masks.len(), + old_hash_mask_len = self.hash_masks.len(), + "resizing tree/hash masks" + ); + self.tree_masks.resize(new_len, TrieMask::default()); + self.hash_masks.resize(new_len, TrieMask::default()); + } +} + +// TODO(scroll): Introduce generic for the HashBuilder. +impl From for HashBuilder { + fn from(hash_builder: reth_trie::HashBuilder) -> Self { + Self { + key: hash_builder.key, + value: hash_builder.value, + stack: hash_builder + .stack + .into_iter() + .map(|x| x.as_slice().try_into().expect("RlpNode contains 32 byte hashes")) + .collect(), + state_masks: hash_builder.groups, + tree_masks: hash_builder.tree_masks, + hash_masks: hash_builder.hash_masks, + stored_in_database: hash_builder.stored_in_database, + updated_branch_nodes: hash_builder.updated_branch_nodes, + proof_retainer: hash_builder.proof_retainer, + } + } +} + +impl From for reth_trie::HashBuilder { + fn from(value: HashBuilder) -> Self { + Self { + key: value.key, + value: value.value, + stack: value + .stack + .into_iter() + .map(|x| { + reth_trie::RlpNode::from_raw(&x.0).expect("32 byte hash can be cast to RlpNode") + }) + .collect(), + groups: value.state_masks, + tree_masks: value.tree_masks, + hash_masks: value.hash_masks, + stored_in_database: value.stored_in_database, + updated_branch_nodes: value.updated_branch_nodes, + proof_retainer: value.proof_retainer, + rlp_buf: Default::default(), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use alloc::collections::BTreeMap; + use hex_literal::hex; + use reth_scroll_primitives::poseidon::{hash_with_domain, Fr, PrimeField}; + use reth_trie::key::BitsCompatibility; + + #[test] + fn test_convert_to_bit_representation() { + let nibbles = Nibbles::unpack_and_truncate_bits(vec![7, 8]); + let expected = [0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0]; + assert_eq!(nibbles.as_slice(), expected); + } + + #[test] + fn test_convert_to_bit_representation_truncation() { + // 64 byte nibble + let hex = hex!("0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f01020304"); + assert_eq!(hex.len(), 64); + let nibbles = Nibbles::unpack_and_truncate_bits(hex); + assert_eq!(nibbles.len(), 248); + } + + #[test] + fn test_basic_trie() { + // Test a basic trie consisting of three key value pairs: + // (0, 0, 0, 0, ... , 0) + // (0, 0, 0, 1, ... , 0) + // (0, 0, 1, 0, ... , 0) + // (1, 1, 1, 0, ... , 0) + // (1, 1, 1, 1, ... , 0) + // The branch associated with key 0xF will be collapsed into a single leaf. + + let leaf_1_key = Nibbles::from_nibbles_unchecked([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]); + let leaf_2_key = Nibbles::from_nibbles_unchecked([ + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]); + let leaf_3_key = Nibbles::from_nibbles_unchecked([ + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]); + let leaf_4_key = Nibbles::from_nibbles_unchecked([ + 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]); + let leaf_5_key = Nibbles::from_nibbles_unchecked([ + 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]); + let leaf_keys = [ + leaf_1_key.clone(), + leaf_2_key.clone(), + leaf_3_key.clone(), + leaf_4_key.clone(), + leaf_5_key.clone(), + ]; + + let leaf_values = leaf_keys + .into_iter() + .enumerate() + .map(|(i, key)| { + let mut leaf_value = [0u8; 32]; + leaf_value[0] = i as u8 + 1; + (key, leaf_value) + }) + .collect::>(); + + let leaf_hashes: BTreeMap<_, _> = leaf_values + .iter() + .map(|(key, value)| { + let key_fr = Fr::from_repr_vartime(key.encode_leaf_key()) + .expect("key is valid field element"); + let value = Fr::from_repr_vartime(*value).expect("value is a valid field element"); + let hash = hash_with_domain(&[key_fr, value], crate::LEAF_NODE_DOMAIN); + (key.clone(), hash) + }) + .collect(); + + let mut hb = HashBuilder::default().with_updates(true); + + for (key, val) in &leaf_values { + hb.add_leaf(key.clone(), val); + } + + let root = hb.root(); + + // node_000 -> hash(leaf_1, leaf_2) LTRT + // node_00 -> hash(node_000, leaf_3) LBRT + // node_0 -> hash(node_00, EMPTY) LBRT + // node_111 -> hash(leaf_4, leaf_5) LTRT + // node_11 -> hash(EMPTY, node_111) LTRB + // node_1 -> hash(EMPTY, node_11) LTRB + // root -> hash(node_0, node_1) LBRB + + let expected: B256 = { + let node_000 = hash_with_domain( + &[*leaf_hashes.get(&leaf_1_key).unwrap(), *leaf_hashes.get(&leaf_2_key).unwrap()], + crate::BRANCH_NODE_LTRT_DOMAIN, + ); + let node_00 = hash_with_domain( + &[node_000, *leaf_hashes.get(&leaf_3_key).unwrap()], + crate::BRANCH_NODE_LBRT_DOMAIN, + ); + let node_0 = hash_with_domain(&[node_00, Fr::zero()], crate::BRANCH_NODE_LBRT_DOMAIN); + let node_111 = hash_with_domain( + &[*leaf_hashes.get(&leaf_4_key).unwrap(), *leaf_hashes.get(&leaf_5_key).unwrap()], + crate::BRANCH_NODE_LTRT_DOMAIN, + ); + let node_11 = hash_with_domain(&[Fr::zero(), node_111], crate::BRANCH_NODE_LTRB_DOMAIN); + let node_1 = hash_with_domain(&[Fr::zero(), node_11], crate::BRANCH_NODE_LTRB_DOMAIN); + + let mut root = + hash_with_domain(&[node_0, node_1], crate::BRANCH_NODE_LBRB_DOMAIN).to_repr(); + root.reverse(); + root.into() + }; + + assert_eq!(expected, root); + } +} diff --git a/crates/scroll/trie/src/leaf.rs b/crates/scroll/trie/src/leaf.rs new file mode 100644 index 000000000000..17be70943834 --- /dev/null +++ b/crates/scroll/trie/src/leaf.rs @@ -0,0 +1,22 @@ +use super::LEAF_NODE_DOMAIN; +use alloy_primitives::B256; +use reth_scroll_primitives::poseidon::{hash_with_domain, Fr, PrimeField}; +use reth_trie::{key::BitsCompatibility, LeafNodeRef}; + +/// A trait used to hash the leaf node. +pub(crate) trait HashLeaf { + /// Hash the leaf node. + fn hash_leaf(&self) -> B256; +} + +impl HashLeaf for LeafNodeRef<'_> { + fn hash_leaf(&self) -> B256 { + let leaf_key = Fr::from_repr_vartime(self.key.encode_leaf_key()) + .expect("leaf key is a valid field element"); + let leaf_value = Fr::from_repr_vartime( + <[u8; 32]>::try_from(self.value).expect("leaf value is 32 bytes"), + ) + .expect("leaf value is a valid field element"); + hash_with_domain(&[leaf_key, leaf_value], LEAF_NODE_DOMAIN).to_repr().into() + } +} diff --git a/crates/scroll/trie/src/lib.rs b/crates/scroll/trie/src/lib.rs new file mode 100644 index 000000000000..52109073d973 --- /dev/null +++ b/crates/scroll/trie/src/lib.rs @@ -0,0 +1,29 @@ +#![doc = include_str!("../README.md")] + +#[macro_use] +#[allow(unused_imports)] +extern crate alloc; + +mod branch; +mod hash_builder; +mod leaf; +mod sub_tree; + +pub use hash_builder::HashBuilder; + +use reth_scroll_primitives::poseidon::Fr; + +/// The hashing domain for leaf nodes. +pub const LEAF_NODE_DOMAIN: Fr = Fr::from_raw([4, 0, 0, 0]); + +/// The hashing domain for a branch node with two terminal children. +pub const BRANCH_NODE_LTRT_DOMAIN: Fr = Fr::from_raw([6, 0, 0, 0]); + +/// The hashing domain for a branch node with a left terminal child and a right branch child. +pub const BRANCH_NODE_LTRB_DOMAIN: Fr = Fr::from_raw([7, 0, 0, 0]); + +/// The hashing domain for a branch node with a left branch child and a right terminal child. +pub const BRANCH_NODE_LBRT_DOMAIN: Fr = Fr::from_raw([8, 0, 0, 0]); + +/// The hashing domain for a branch node with two branch children. +pub const BRANCH_NODE_LBRB_DOMAIN: Fr = Fr::from_raw([9, 0, 0, 0]); diff --git a/crates/scroll/trie/src/sub_tree.rs b/crates/scroll/trie/src/sub_tree.rs new file mode 100644 index 000000000000..eed916b8ae7b --- /dev/null +++ b/crates/scroll/trie/src/sub_tree.rs @@ -0,0 +1,44 @@ +use super::{BRANCH_NODE_LBRT_DOMAIN, BRANCH_NODE_LTRB_DOMAIN}; +use alloy_primitives::{hex, B256}; +use alloy_trie::Nibbles; +use core::fmt; +use reth_scroll_primitives::poseidon::{hash_with_domain, Fr, PrimeField}; + +/// [`SubTreeRef`] is a structure that allows for calculation of the root of a sparse binary Merkle +/// tree consisting of a single leaf node. +pub(crate) struct SubTreeRef<'a> { + /// The key to the child node. + pub key: &'a Nibbles, + /// A pointer to the child node. + pub child: &'a B256, +} + +impl<'a> SubTreeRef<'a> { + /// Creates a new subtree with the given key and a pointer to the child. + #[inline] + pub(crate) const fn new(key: &'a Nibbles, child: &'a B256) -> Self { + Self { key, child } + } + + pub(crate) fn root(&self) -> B256 { + let mut tree_root = + Fr::from_repr_vartime(self.child.0).expect("child is a valid field element"); + for bit in self.key.as_slice().iter().rev() { + tree_root = if *bit == 0 { + hash_with_domain(&[tree_root, Fr::zero()], BRANCH_NODE_LBRT_DOMAIN) + } else { + hash_with_domain(&[Fr::zero(), tree_root], BRANCH_NODE_LTRB_DOMAIN) + }; + } + tree_root.to_repr().into() + } +} + +impl fmt::Debug for SubTreeRef<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SubTreeRef") + .field("key", &self.key) + .field("node", &hex::encode(self.child)) + .finish() + } +} diff --git a/crates/tracing/Cargo.toml b/crates/tracing/Cargo.toml index 59631365d603..d944b5eeeb61 100644 --- a/crates/tracing/Cargo.toml +++ b/crates/tracing/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] tracing.workspace = true -tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt", "json"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt", "ansi", "json"] } tracing-appender.workspace = true tracing-journald = "0.3" tracing-logfmt = "0.3.3" diff --git a/crates/trie/trie/Cargo.toml b/crates/trie/trie/Cargo.toml index f815076129fe..4306fb2f262b 100644 --- a/crates/trie/trie/Cargo.toml +++ b/crates/trie/trie/Cargo.toml @@ -36,6 +36,9 @@ tracing.workspace = true rayon.workspace = true auto_impl.workspace = true itertools.workspace = true +smallvec = { version = "1.0", default-features = false, features = [ + "const_new", +] } # `metrics` feature reth-metrics = { workspace = true, optional = true } @@ -66,7 +69,8 @@ serde = [ "alloy-trie/serde", "revm/serde", "reth-trie-common/serde", - "reth-primitives-traits/serde" + "reth-primitives-traits/serde", + "smallvec/serde" ] test-utils = [ "triehash", diff --git a/crates/trie/trie/src/key.rs b/crates/trie/trie/src/key.rs new file mode 100644 index 000000000000..f90da1e5ada8 --- /dev/null +++ b/crates/trie/trie/src/key.rs @@ -0,0 +1,89 @@ +use smallvec::SmallVec; + +use crate::Nibbles; + +/// The maximum number of bits a key can contain. +pub const MAX_BITS: usize = 248; + +/// The maximum number of bytes a key can contain. +const MAX_BYTES: usize = 31; + +// TODO(scroll): Refactor this into a trait that is more generic and can be used by any +// implementation that requires converting between nibbles and bits. Better yet we should use a +// trait that allows for defining the key type via a GAT as opposed to using Nibbles. + +/// A trait for converting a `Nibbles` representation to a bit representation. +pub trait BitsCompatibility: Sized { + /// Unpacks the bits from the provided bytes such that there is a byte for each bit in the + /// input. The representation is big-endian with respect to the input. + /// + /// We truncate the Nibbles such that we only have [`MAX_BITS`] (248) bits. + fn unpack_and_truncate_bits>(data: T) -> Self; + + /// Pack the bits into a byte representation. + fn pack_bits(&self) -> SmallVec<[u8; 32]>; + + /// Encodes a leaf key represented as [`Nibbles`] into it's canonical little-endian + /// representation. + fn encode_leaf_key(&self) -> [u8; 32]; + + /// Increment the key to the next key. + fn increment_bit(&self) -> Option; +} + +impl BitsCompatibility for Nibbles { + fn unpack_and_truncate_bits>(data: T) -> Self { + let data = data.as_ref(); + let unpacked_len = core::cmp::min(data.len() * 8, MAX_BITS); + let mut bits = Vec::with_capacity(unpacked_len); + + for byte in data.iter().take(MAX_BYTES) { + for i in (0..8).rev() { + bits.push(byte >> i & 1); + } + } + + Self::from_vec_unchecked(bits) + } + + fn pack_bits(&self) -> SmallVec<[u8; 32]> { + let mut result = SmallVec::with_capacity((self.len() + 7) / 8); + + for bits in self.as_slice().chunks(8) { + let mut byte = 0; + for (bit_index, bit) in bits.iter().enumerate() { + byte |= *bit << (7 - bit_index); + } + result.push(byte); + } + + result + } + + fn encode_leaf_key(&self) -> [u8; 32] { + // This is strange we are now representing the leaf key using big endian?? + let mut result = [0u8; 32]; + for (byte_index, bytes) in self.as_slice().chunks(8).enumerate() { + for (bit_index, byte) in bytes.iter().enumerate() { + result[byte_index] |= byte << bit_index; + } + } + + result + } + + fn increment_bit(&self) -> Option { + let mut incremented = self.clone(); + + for nibble in incremented.as_mut_vec_unchecked().iter_mut().rev() { + if *nibble < 1 { + *nibble += 1; + return Some(incremented); + } + + *nibble = 0; + } + + None + } +} diff --git a/crates/trie/trie/src/lib.rs b/crates/trie/trie/src/lib.rs index 1e7eeb9b52b8..c823ffc77de0 100644 --- a/crates/trie/trie/src/lib.rs +++ b/crates/trie/trie/src/lib.rs @@ -13,6 +13,9 @@ )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +/// A module for working with trie keys. +pub mod key; + /// The implementation of forward-only in-memory cursor. pub mod forward_cursor; diff --git a/crates/trie/trie/src/node_iter.rs b/crates/trie/trie/src/node_iter.rs index 60219eedd7cb..e39ac9f16769 100644 --- a/crates/trie/trie/src/node_iter.rs +++ b/crates/trie/trie/src/node_iter.rs @@ -2,6 +2,9 @@ use crate::{hashed_cursor::HashedCursor, trie_cursor::TrieCursor, walker::TrieWa use alloy_primitives::B256; use reth_storage_errors::db::DatabaseError; +#[cfg(feature = "scroll")] +use crate::key::BitsCompatibility; + /// Represents a branch node in the trie. #[derive(Debug)] pub struct TrieBranchNode { @@ -106,7 +109,13 @@ where if let Some((hashed_key, value)) = self.current_hashed_entry.take() { // If the walker's key is less than the unpacked hashed key, // reset the checked status and continue - if self.walker.key().is_some_and(|key| key < &Nibbles::unpack(hashed_key)) { + if self.walker.key().is_some_and(|key| { + #[cfg(not(feature = "scroll"))] + let cmp = key < &Nibbles::unpack(hashed_key); + #[cfg(feature = "scroll")] + let cmp = key < &Nibbles::unpack_and_truncate_bits(hashed_key); + cmp + }) { self.current_walker_key_checked = false; continue } diff --git a/crates/trie/trie/src/state.rs b/crates/trie/trie/src/state.rs index bcac327bd42c..d0891e783597 100644 --- a/crates/trie/trie/src/state.rs +++ b/crates/trie/trie/src/state.rs @@ -13,6 +13,9 @@ use reth_trie_common::KeyHasher; use revm::db::{states::CacheAccount, AccountStatus, BundleAccount}; use std::borrow::Cow; +#[cfg(feature = "scroll")] +use crate::key::BitsCompatibility; + /// Representation of in-memory hashed state. #[derive(PartialEq, Eq, Clone, Default, Debug)] pub struct HashedPostState { @@ -118,7 +121,12 @@ impl HashedPostState { let mut account_prefix_set = PrefixSetMut::with_capacity(self.accounts.len()); let mut destroyed_accounts = HashSet::default(); for (hashed_address, account) in &self.accounts { - account_prefix_set.insert(Nibbles::unpack(hashed_address)); + // TODO(scroll): replace with key abstraction + #[cfg(feature = "scroll")] + let nibbles = Nibbles::unpack_and_truncate_bits(hashed_address); + #[cfg(not(feature = "scroll"))] + let nibbles = Nibbles::unpack(hashed_address); + account_prefix_set.insert(nibbles); if account.is_none() { destroyed_accounts.insert(*hashed_address); @@ -129,7 +137,12 @@ impl HashedPostState { let mut storage_prefix_sets = HashMap::with_capacity_and_hasher(self.storages.len(), Default::default()); for (hashed_address, hashed_storage) in &self.storages { - account_prefix_set.insert(Nibbles::unpack(hashed_address)); + // TODO(scroll): replace this with abstraction. + #[cfg(feature = "scroll")] + let nibbles = Nibbles::unpack_and_truncate_bits(hashed_address); + #[cfg(not(feature = "scroll"))] + let nibbles = Nibbles::unpack(hashed_address); + account_prefix_set.insert(nibbles); storage_prefix_sets.insert(*hashed_address, hashed_storage.construct_prefix_set()); } @@ -253,7 +266,12 @@ impl HashedStorage { } else { let mut prefix_set = PrefixSetMut::with_capacity(self.storage.len()); for hashed_slot in self.storage.keys() { - prefix_set.insert(Nibbles::unpack(hashed_slot)); + // TODO(scroll): replace this with key abstraction. + #[cfg(feature = "scroll")] + let nibbles = Nibbles::unpack_and_truncate_bits(hashed_slot); + #[cfg(not(feature = "scroll"))] + let nibbles = Nibbles::unpack(hashed_slot); + prefix_set.insert(nibbles); } prefix_set } diff --git a/crates/trie/trie/src/walker.rs b/crates/trie/trie/src/walker.rs index d1c5247966da..db76049279fd 100644 --- a/crates/trie/trie/src/walker.rs +++ b/crates/trie/trie/src/walker.rs @@ -9,6 +9,9 @@ use reth_storage_errors::db::DatabaseError; #[cfg(feature = "metrics")] use crate::metrics::WalkerMetrics; +#[cfg(feature = "scroll")] +use crate::key::BitsCompatibility; + /// `TrieWalker` is a structure that enables traversal of a Merkle trie. /// It allows moving through the trie in a depth-first manner, skipping certain branches /// if they have not changed. @@ -100,9 +103,18 @@ impl TrieWalker { self.key() .and_then(|key| { if self.can_skip_current_node { - key.increment().map(|inc| inc.pack()) + // TODO(scroll): replace this with key abstraction. + #[cfg(not(feature = "scroll"))] + let key = key.increment().map(|inc| inc.pack()); + #[cfg(feature = "scroll")] + let key = key.increment_bit().map(|inc| inc.pack_bits()); + key } else { - Some(key.pack()) + #[cfg(feature = "scroll")] + let key = Some(key.pack_bits()); + #[cfg(not(feature = "scroll"))] + let key = Some(key.pack()); + key } }) .map(|mut key| { diff --git a/deny.toml b/deny.toml index 6dc51dba0a93..498ac05beaf0 100644 --- a/deny.toml +++ b/deny.toml @@ -67,10 +67,10 @@ exceptions = [ { allow = ["MPL-2.0"], name = "webpki-roots" }, ] -# Skip the poseidon-bn254 and bn254 crates for license verification. We should at some point publish a license for them. +# Skip the poseidon-bn254, bn254 and zktrie crates for license verification. We should at some point publish a license for them. [licenses.private] ignore = true -ignore-sources = ["https://github.com/scroll-tech/poseidon-bn254", "https://github.com/scroll-tech/bn254"] +ignore-sources = ["https://github.com/scroll-tech/poseidon-bn254", "https://github.com/scroll-tech/bn254", "https://github.com/scroll-tech/zktrie.git"] [[licenses.clarify]] name = "ring" @@ -100,5 +100,7 @@ allow-git = [ "https://github.com/paradigmxyz/revm-inspectors", "https://github.com/scroll-tech/bn254", "https://github.com/scroll-tech/sp1-intrinsics", - "https://github.com/scroll-tech/poseidon-bn254" + "https://github.com/scroll-tech/poseidon-bn254", + "https://github.com/scroll-tech/zktrie.git", + "https://github.com/scroll-tech/gobuild.git" ]