diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index bd539929147..44b66b4178e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -152,7 +152,7 @@ jobs: with: cache-on-failure: true - uses: taiki-e/install-action@cargo-udeps - - run: cargo udeps --workspace --lib --examples --tests --benches --all-features --locked + - run: cargo udeps --workspace --lib --examples --tests --benches --all-features --locked --exclude "reth-optimism-*" --exclude op-reth book: name: book diff --git a/Cargo.lock b/Cargo.lock index 23503d90756..f9556c0d165 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1345,6 +1345,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bn254" +version = "0.1.0" +source = "git+https://github.com/scroll-tech/bn254#81e1dcc92ee9a2798b13b84b24de182e9c42256e" +dependencies = [ + "ff", + "getrandom 0.2.15", + "rand 0.8.5", + "rand_core 0.6.4", + "sp1-intrinsics", + "subtle", +] + [[package]] name = "boa_ast" version = "0.19.1" @@ -2623,6 +2636,7 @@ dependencies = [ "reth-primitives", "reth-provider", "reth-revm", + "reth-scroll-primitives", "reth-stages", "revm", "serde", @@ -3150,6 +3164,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ + "bitvec", "rand_core 0.6.4", "subtle", ] @@ -5737,6 +5752,15 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +[[package]] +name = "poseidon-bn254" +version = "0.1.0" +source = "git+https://github.com/scroll-tech/poseidon-bn254?rev=526a64a#526a64a81419bcab6bd8280a8a3f9808189e0373" +dependencies = [ + "bn254", + "itertools 0.13.0", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -6936,6 +6960,7 @@ dependencies = [ "reth-primitives", "reth-primitives-traits", "reth-provider", + "reth-scroll-primitives", "reth-stages-types", "reth-trie", "reth-trie-db", @@ -7527,6 +7552,7 @@ dependencies = [ "reth-execution-types", "reth-primitives", "reth-revm", + "reth-scroll-primitives", "reth-testing-utils", "revm-primitives", "secp256k1", @@ -8559,10 +8585,10 @@ dependencies = [ "reth-codecs", "reth-ethereum-forks", "reth-primitives-traits", + "reth-scroll-revm", "reth-static-file-types", "reth-testing-utils", "reth-trie-common", - "revm-primitives", "rstest", "secp256k1", "serde", @@ -8592,7 +8618,8 @@ dependencies = [ "proptest-arbitrary-interop", "rand 0.8.5", "reth-codecs", - "revm-primitives", + "reth-scroll-primitives", + "reth-scroll-revm", "roaring", "serde", "serde_json", @@ -8636,6 +8663,7 @@ dependencies = [ "reth-optimism-primitives", "reth-primitives", "reth-prune-types", + "reth-scroll-primitives", "reth-stages-types", "reth-storage-api", "reth-storage-errors", @@ -9055,6 +9083,54 @@ dependencies = [ "serde_json", ] +[[package]] +name = "reth-scroll-execution" +version = "1.1.1" +dependencies = [ + "auto_impl", + "reth-revm", + "reth-scroll-primitives", + "reth-scroll-revm", + "reth-scroll-storage", +] + +[[package]] +name = "reth-scroll-primitives" +version = "1.1.1" +dependencies = [ + "alloy-primitives", + "arbitrary", + "bytes", + "modular-bitfield", + "poseidon-bn254", + "reth-codecs", + "serde", + "test-fuzz", +] + +[[package]] +name = "reth-scroll-revm" +version = "1.1.1" +dependencies = [ + "reth-scroll-primitives", + "revm", + "serde", +] + +[[package]] +name = "reth-scroll-storage" +version = "1.1.1" +dependencies = [ + "alloy-primitives", + "eyre", + "reth-codecs", + "reth-primitives-traits", + "reth-revm", + "reth-scroll-primitives", + "reth-scroll-revm", + "reth-storage-errors", +] + [[package]] name = "reth-stages" version = "1.1.1" @@ -9093,6 +9169,7 @@ dependencies = [ "reth-prune", "reth-prune-types", "reth-revm", + "reth-scroll-primitives", "reth-stages-api", "reth-static-file", "reth-storage-errors", @@ -9366,6 +9443,7 @@ dependencies = [ "proptest-arbitrary-interop", "reth-codecs", "reth-primitives-traits", + "reth-scroll-primitives", "revm-primitives", "serde", ] @@ -9388,6 +9466,7 @@ dependencies = [ "reth-metrics", "reth-primitives", "reth-provider", + "reth-scroll-primitives", "reth-storage-errors", "reth-trie", "reth-trie-common", @@ -10388,6 +10467,14 @@ dependencies = [ "sha1", ] +[[package]] +name = "sp1-intrinsics" +version = "0.0.0" +source = "git+https://github.com/scroll-tech/sp1-intrinsics.git?branch=master#7e038e60db0b2e847f6d8f49e148ccac8c6fc394" +dependencies = [ + "cfg-if", +] + [[package]] name = "spin" version = "0.9.8" diff --git a/Cargo.toml b/Cargo.toml index 2f2f9aa884a..4b803229252 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,6 +100,10 @@ members = [ "crates/rpc/rpc-testing-util/", "crates/rpc/rpc-types-compat/", "crates/rpc/rpc/", + "crates/scroll/execution", + "crates/scroll/primitives", + "crates/scroll/revm", + "crates/scroll/storage", "crates/stages/api/", "crates/stages/stages/", "crates/stages/types/", @@ -403,6 +407,10 @@ reth-rpc-eth-types = { path = "crates/rpc/rpc-eth-types", default-features = fal reth-rpc-layer = { path = "crates/rpc/rpc-layer" } 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-revm = { path = "crates/scroll/revm" } +reth-scroll-storage = { path = "crates/scroll/storage" } reth-stages = { path = "crates/stages/stages" } reth-stages-api = { path = "crates/stages/api" } reth-stages-types = { path = "crates/stages/types" } @@ -470,6 +478,10 @@ alloy-transport-http = { version = "0.6.4", features = [ alloy-transport-ipc = { version = "0.6.4", default-features = false } alloy-transport-ws = { version = "0.6.4", default-features = false } +# scroll +# TODO (scroll): point to crates.io/tag once the crate is published/a tag is created. +poseidon-bn254 = { git = "https://github.com/scroll-tech/poseidon-bn254", rev = "526a64a", features = ["bn254"] } + # op op-alloy-rpc-types = "0.6.5" op-alloy-rpc-types-engine = "0.6.5" diff --git a/crates/ethereum/evm/Cargo.toml b/crates/ethereum/evm/Cargo.toml index 17e870e6111..0dd0ab5a02a 100644 --- a/crates/ethereum/evm/Cargo.toml +++ b/crates/ethereum/evm/Cargo.toml @@ -39,6 +39,8 @@ secp256k1.workspace = true serde_json.workspace = true alloy-genesis.workspace = true +reth-scroll-primitives.workspace = true + [features] default = ["std"] std = [ @@ -52,3 +54,4 @@ std = [ "revm-primitives/std", "secp256k1/std" ] +scroll = [] diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index fa14e260d65..b5e5aecf730 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -325,6 +325,10 @@ mod tests { balance: U256::ZERO, bytecode_hash: Some(keccak256(BEACON_ROOTS_CODE.clone())), nonce: 1, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::from_bytecode( + &BEACON_ROOTS_CODE, + )), }; db.insert_account( @@ -344,6 +348,10 @@ mod tests { nonce: 1, balance: U256::ZERO, bytecode_hash: Some(keccak256(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone())), + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::from_bytecode( + &WITHDRAWAL_REQUEST_PREDEPLOY_CODE, + )), }; db.insert_account( @@ -707,6 +715,10 @@ mod tests { balance: U256::ZERO, bytecode_hash: Some(keccak256(HISTORY_STORAGE_CODE.clone())), nonce: 1, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::from_bytecode( + &HISTORY_STORAGE_CODE, + )), }; db.insert_account( @@ -1059,7 +1071,13 @@ mod tests { db.insert_account( sender_address, - Account { nonce: 1, balance: U256::from(ETH_TO_WEI), bytecode_hash: None }, + Account { + nonce: 1, + balance: U256::from(ETH_TO_WEI), + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }, None, HashMap::default(), ); @@ -1141,7 +1159,13 @@ mod tests { // Insert the sender account into the state with a nonce of 1 and a balance of 1 ETH in Wei db.insert_account( sender_address, - Account { nonce: 1, balance: U256::from(ETH_TO_WEI), bytecode_hash: None }, + Account { + nonce: 1, + balance: U256::from(ETH_TO_WEI), + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }, None, HashMap::default(), ); diff --git a/crates/optimism/bin/Cargo.toml b/crates/optimism/bin/Cargo.toml index 77166763100..8337980d46f 100644 --- a/crates/optimism/bin/Cargo.toml +++ b/crates/optimism/bin/Cargo.toml @@ -46,6 +46,7 @@ optimism = [ "reth-optimism-rpc/optimism", "reth-provider/optimism" ] +scroll = [] dev = [ "reth-optimism-cli/dev" diff --git a/crates/optimism/bin/src/lib.rs b/crates/optimism/bin/src/lib.rs index 21c28f7c547..3e9550949c1 100644 --- a/crates/optimism/bin/src/lib.rs +++ b/crates/optimism/bin/src/lib.rs @@ -26,6 +26,9 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] // The `optimism` feature must be enabled to use this crate. #![cfg(feature = "optimism")] +// Don't use the crate if `scroll` feature is used. +#![cfg_attr(feature = "scroll", allow(unused_crate_dependencies))] +#![cfg(not(feature = "scroll"))] /// Re-exported from `reth_optimism_cli`. pub mod cli { diff --git a/crates/optimism/bin/src/main.rs b/crates/optimism/bin/src/main.rs index 6494298ba39..7ba70272f77 100644 --- a/crates/optimism/bin/src/main.rs +++ b/crates/optimism/bin/src/main.rs @@ -1,6 +1,9 @@ #![allow(missing_docs, rustdoc::missing_crate_level_docs)] // The `optimism` feature must be enabled to use this crate. #![cfg(feature = "optimism")] +// Don't use the crate if `scroll` feature is used. +#![cfg_attr(feature = "scroll", allow(unused_crate_dependencies))] +#![cfg(not(feature = "scroll"))] use clap::Parser; use reth_node_builder::{engine_tree_config::TreeConfig, EngineNodeLauncher}; diff --git a/crates/optimism/cli/Cargo.toml b/crates/optimism/cli/Cargo.toml index 198e5377ec4..dc99e47f539 100644 --- a/crates/optimism/cli/Cargo.toml +++ b/crates/optimism/cli/Cargo.toml @@ -121,3 +121,4 @@ serde = [ "reth-execution-types/serde", "reth-provider/serde" ] +scroll = [] diff --git a/crates/optimism/cli/src/lib.rs b/crates/optimism/cli/src/lib.rs index 23eaa99b521..b02c128cbf3 100644 --- a/crates/optimism/cli/src/lib.rs +++ b/crates/optimism/cli/src/lib.rs @@ -9,6 +9,9 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] // The `optimism` feature must be enabled to use this crate. #![cfg(feature = "optimism")] +// Don't use the crate if `scroll` feature is used. +#![cfg_attr(feature = "scroll", allow(unused_crate_dependencies))] +#![cfg(not(feature = "scroll"))] /// Optimism chain specification parser. pub mod chainspec; diff --git a/crates/optimism/evm/Cargo.toml b/crates/optimism/evm/Cargo.toml index f6b22ad14c8..cb1715a08fd 100644 --- a/crates/optimism/evm/Cargo.toml +++ b/crates/optimism/evm/Cargo.toml @@ -69,3 +69,4 @@ optimism = [ "revm/optimism", "revm-primitives/optimism" ] +scroll = [] diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index 9569c1cb8b5..2d5884fa902 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -9,6 +9,9 @@ #![cfg_attr(not(feature = "std"), no_std)] // The `optimism` feature must be enabled to use this crate. #![cfg(feature = "optimism")] +// Don't use the crate if `scroll` feature is used. +#![cfg_attr(feature = "scroll", allow(unused_crate_dependencies))] +#![cfg(not(feature = "scroll"))] extern crate alloc; diff --git a/crates/optimism/node/Cargo.toml b/crates/optimism/node/Cargo.toml index 9a80c83deec..bce0eb3a16c 100644 --- a/crates/optimism/node/Cargo.toml +++ b/crates/optimism/node/Cargo.toml @@ -117,3 +117,4 @@ test-utils = [ "revm/test-utils", "reth-optimism-node/test-utils", ] +scroll = [] diff --git a/crates/optimism/node/src/lib.rs b/crates/optimism/node/src/lib.rs index 7af0f3b8a72..8bf8225bdef 100644 --- a/crates/optimism/node/src/lib.rs +++ b/crates/optimism/node/src/lib.rs @@ -8,6 +8,9 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] // The `optimism` feature must be enabled to use this crate. #![cfg(feature = "optimism")] +// Don't use the crate if `scroll` feature is used. +#![cfg_attr(feature = "scroll", allow(unused_crate_dependencies))] +#![cfg(not(feature = "scroll"))] /// CLI argument parsing for the optimism node. pub mod args; diff --git a/crates/optimism/node/tests/e2e/main.rs b/crates/optimism/node/tests/e2e/main.rs index 7f4b22ba7e0..b3143d7c40b 100644 --- a/crates/optimism/node/tests/e2e/main.rs +++ b/crates/optimism/node/tests/e2e/main.rs @@ -1,4 +1,7 @@ #![allow(missing_docs)] +// Don't use the crate if `scroll` feature is used. +#![cfg_attr(feature = "scroll", allow(unused_crate_dependencies))] +#![cfg(not(feature = "scroll"))] #[cfg(feature = "optimism")] mod p2p; diff --git a/crates/optimism/node/tests/it/main.rs b/crates/optimism/node/tests/it/main.rs index d0533fc4541..2bfdeffc1c4 100644 --- a/crates/optimism/node/tests/it/main.rs +++ b/crates/optimism/node/tests/it/main.rs @@ -1,4 +1,7 @@ #![allow(missing_docs)] +// Don't use the crate if `scroll` feature is used. +#![cfg_attr(feature = "scroll", allow(unused_crate_dependencies))] +#![cfg(not(feature = "scroll"))] #[cfg(feature = "optimism")] mod builder; diff --git a/crates/optimism/payload/Cargo.toml b/crates/optimism/payload/Cargo.toml index 7f47da7e236..8873da14605 100644 --- a/crates/optimism/payload/Cargo.toml +++ b/crates/optimism/payload/Cargo.toml @@ -59,4 +59,5 @@ optimism = [ "revm/optimism", "reth-execution-types/optimism", "reth-optimism-consensus/optimism" -] \ No newline at end of file +] +scroll = [] \ No newline at end of file diff --git a/crates/optimism/payload/src/lib.rs b/crates/optimism/payload/src/lib.rs index 8447026d783..ee1f0a81dce 100644 --- a/crates/optimism/payload/src/lib.rs +++ b/crates/optimism/payload/src/lib.rs @@ -10,6 +10,9 @@ #![allow(clippy::useless_let_if_seq)] // The `optimism` feature must be enabled to use this crate. #![cfg(feature = "optimism")] +// Don't use the crate if `scroll` feature is used. +#![cfg_attr(feature = "scroll", allow(unused_crate_dependencies))] +#![cfg(not(feature = "scroll"))] pub mod builder; pub use builder::OpPayloadBuilder; diff --git a/crates/optimism/rpc/Cargo.toml b/crates/optimism/rpc/Cargo.toml index 17fafef7096..dfa5ddb0a78 100644 --- a/crates/optimism/rpc/Cargo.toml +++ b/crates/optimism/rpc/Cargo.toml @@ -74,3 +74,4 @@ optimism = [ "reth-optimism-consensus/optimism", "reth-optimism-payload-builder/optimism" ] +scroll = [] diff --git a/crates/optimism/rpc/src/lib.rs b/crates/optimism/rpc/src/lib.rs index 0fa0debdf33..d8ee748783b 100644 --- a/crates/optimism/rpc/src/lib.rs +++ b/crates/optimism/rpc/src/lib.rs @@ -9,6 +9,9 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] // The `optimism` feature must be enabled to use this crate. #![cfg(feature = "optimism")] +// Don't use the crate if `scroll` feature is used. +#![cfg_attr(feature = "scroll", allow(unused_crate_dependencies))] +#![cfg(not(feature = "scroll"))] pub mod error; pub mod eth; diff --git a/crates/primitives-traits/Cargo.toml b/crates/primitives-traits/Cargo.toml index 651583f8e4d..6ad16c4090c 100644 --- a/crates/primitives-traits/Cargo.toml +++ b/crates/primitives-traits/Cargo.toml @@ -20,7 +20,9 @@ alloy-genesis.workspace = true alloy-primitives.workspace = true alloy-rlp.workspace = true -revm-primitives = { workspace = true, features = ["serde"] } +# revm-primitives scroll re-export +revm-primitives = { package = "reth-scroll-revm", path = "../scroll/revm", features = ["serde"] } +reth-scroll-primitives = { workspace = true, optional = true } # misc byteorder = "1" @@ -62,7 +64,8 @@ std = [ ] test-utils = [ "arbitrary", - "reth-codecs/test-utils" + "reth-codecs/test-utils", + "revm-primitives/test-utils" ] arbitrary = [ "std", @@ -73,10 +76,15 @@ arbitrary = [ "dep:proptest-arbitrary-interop", "alloy-eips/arbitrary", "revm-primitives/arbitrary", - "reth-codecs/arbitrary" + "reth-codecs/arbitrary", + "reth-scroll-primitives?/arbitrary" ] serde-bincode-compat = [ "serde_with", "alloy-consensus/serde-bincode-compat", "alloy-eips/serde-bincode-compat" ] +scroll = [ + "reth-scroll-primitives", + "revm-primitives/scroll" +] diff --git a/crates/primitives-traits/src/account.rs b/crates/primitives-traits/src/account.rs index ae58973edd7..ffdd4d29887 100644 --- a/crates/primitives-traits/src/account.rs +++ b/crates/primitives-traits/src/account.rs @@ -34,6 +34,15 @@ pub struct Account { pub balance: U256, /// Hash of the account's bytecode. pub bytecode_hash: Option, + /// The extension for a Scroll account. This `Option` should always be `Some` and is used + /// in order to maintain backward compatibility in case additional fields are added on the + /// `Account` due to the way storage compaction is performed. + /// Adding the `code_size` and the `poseidon_code_hash` fields on the `Account` without the + /// extension caused the used bits of the bitflag struct to reach 16 bits, meaning no + /// additional bitflag was available. See [reth codecs](reth_codecs::test_utils) for more + /// details. + #[cfg(feature = "scroll")] + pub account_extension: Option, } impl Account { @@ -158,6 +167,10 @@ impl From<&GenesisAccount> for Account { nonce: value.nonce.unwrap_or_default(), balance: value.balance, bytecode_hash: value.code.as_ref().map(keccak256), + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::from_bytecode( + value.code.as_ref().unwrap_or_default(), + )), } } } @@ -169,6 +182,8 @@ impl From for Account { balance: revm_acc.balance, nonce: revm_acc.nonce, bytecode_hash: (code_hash != KECCAK_EMPTY).then_some(code_hash), + #[cfg(feature = "scroll")] + account_extension: Some((revm_acc.code_size, revm_acc.poseidon_code_hash).into()), } } } @@ -180,6 +195,39 @@ impl From for AccountInfo { nonce: reth_acc.nonce, code_hash: reth_acc.bytecode_hash.unwrap_or(KECCAK_EMPTY), code: None, + #[cfg(feature = "scroll")] + code_size: reth_acc.account_extension.unwrap_or_default().code_size, + #[cfg(feature = "scroll")] + poseidon_code_hash: reth_acc + .account_extension + .unwrap_or_default() + .poseidon_code_hash + .unwrap_or(reth_scroll_primitives::POSEIDON_EMPTY), + } + } +} + +#[cfg(feature = "scroll")] +impl From for revm_primitives::shared::AccountInfo { + fn from(reth_acc: Account) -> Self { + Self { + balance: reth_acc.balance, + nonce: reth_acc.nonce, + code_hash: reth_acc.bytecode_hash.unwrap_or(KECCAK_EMPTY), + code: None, + } + } +} + +// TODO (scroll): remove at last Scroll `Account` related PR. +#[cfg(feature = "scroll")] +impl From for Account { + fn from(revm_acc: revm_primitives::shared::AccountInfo) -> Self { + Self { + balance: revm_acc.balance, + nonce: revm_acc.nonce, + bytecode_hash: (revm_acc.code_hash != KECCAK_EMPTY).then_some(revm_acc.code_hash), + account_extension: None, } } } @@ -208,7 +256,13 @@ mod tests { #[test] fn test_empty_account() { - let mut acc = Account { nonce: 0, balance: U256::ZERO, bytecode_hash: None }; + let mut acc = Account { + nonce: 0, + balance: U256::ZERO, + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(Default::default()), + }; // Nonce 0, balance 0, and bytecode hash set to None is considered empty. assert!(acc.is_empty()); @@ -260,12 +314,23 @@ mod tests { #[test] fn test_account_has_bytecode() { // Account with no bytecode (None) - let acc_no_bytecode = Account { nonce: 1, balance: U256::from(1000), bytecode_hash: None }; + let acc_no_bytecode = Account { + nonce: 1, + balance: U256::from(1000), + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(Default::default()), + }; assert!(!acc_no_bytecode.has_bytecode(), "Account should not have bytecode"); // Account with bytecode hash set to KECCAK_EMPTY (should have bytecode) - let acc_empty_bytecode = - Account { nonce: 1, balance: U256::from(1000), bytecode_hash: Some(KECCAK_EMPTY) }; + let acc_empty_bytecode = Account { + nonce: 1, + balance: U256::from(1000), + bytecode_hash: Some(KECCAK_EMPTY), + #[cfg(feature = "scroll")] + account_extension: Some(Default::default()), + }; assert!(acc_empty_bytecode.has_bytecode(), "Account should have bytecode"); // Account with a non-empty bytecode hash @@ -273,6 +338,10 @@ mod tests { nonce: 1, balance: U256::from(1000), bytecode_hash: Some(B256::from_slice(&[0x11u8; 32])), + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::from_bytecode( + &[0x11u8; 32], + )), }; assert!(acc_with_bytecode.has_bytecode(), "Account should have bytecode"); } @@ -280,12 +349,23 @@ mod tests { #[test] fn test_account_get_bytecode_hash() { // Account with no bytecode (should return KECCAK_EMPTY) - let acc_no_bytecode = Account { nonce: 0, balance: U256::ZERO, bytecode_hash: None }; + let acc_no_bytecode = Account { + nonce: 0, + balance: U256::ZERO, + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(Default::default()), + }; assert_eq!(acc_no_bytecode.get_bytecode_hash(), KECCAK_EMPTY, "Should return KECCAK_EMPTY"); // Account with bytecode hash set to KECCAK_EMPTY - let acc_empty_bytecode = - Account { nonce: 1, balance: U256::from(1000), bytecode_hash: Some(KECCAK_EMPTY) }; + let acc_empty_bytecode = Account { + nonce: 1, + balance: U256::from(1000), + bytecode_hash: Some(KECCAK_EMPTY), + #[cfg(feature = "scroll")] + account_extension: Some(Default::default()), + }; assert_eq!( acc_empty_bytecode.get_bytecode_hash(), KECCAK_EMPTY, @@ -294,8 +374,15 @@ mod tests { // Account with a valid bytecode hash let bytecode_hash = B256::from_slice(&[0x11u8; 32]); - let acc_with_bytecode = - Account { nonce: 1, balance: U256::from(1000), bytecode_hash: Some(bytecode_hash) }; + let acc_with_bytecode = Account { + nonce: 1, + balance: U256::from(1000), + bytecode_hash: Some(bytecode_hash), + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::from_bytecode( + &[0x11u8; 32], + )), + }; assert_eq!( acc_with_bytecode.get_bytecode_hash(), bytecode_hash, diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 34d04c94edc..3507952699f 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -16,7 +16,6 @@ workspace = true reth-primitives-traits.workspace = true reth-ethereum-forks.workspace = true reth-static-file-types.workspace = true -revm-primitives = { workspace = true, features = ["serde"] } reth-codecs = { workspace = true, optional = true } # ethereum @@ -29,6 +28,9 @@ alloy-serde = { workspace = true, optional = true } alloy-eips = { workspace = true, features = ["serde"] } alloy-trie = { workspace = true, features = ["serde"] } +# scroll +revm-primitives = { package = "reth-scroll-revm", path = "../scroll/revm", features = ["serde"] } + # optimism op-alloy-rpc-types = { workspace = true, optional = true } op-alloy-consensus = { workspace = true, features = [ @@ -67,7 +69,7 @@ reth-codecs = { workspace = true, features = ["test-utils"] } reth-primitives-traits = { workspace = true, features = ["arbitrary"] } reth-testing-utils.workspace = true reth-trie-common.workspace = true -revm-primitives = { workspace = true, features = ["arbitrary"] } +revm-primitives = { package = "reth-scroll-revm", path = "../scroll/revm", features = ["arbitrary"] } alloy-eips = { workspace = true, features = ["arbitrary"] } alloy-genesis.workspace = true @@ -143,7 +145,7 @@ alloy-compat = [ "dep:alloy-rpc-types", "dep:alloy-serde", "dep:op-alloy-rpc-types", - "dep:alloy-network", + "dep:alloy-network", ] test-utils = [ "reth-primitives-traits/test-utils", @@ -151,6 +153,7 @@ test-utils = [ "reth-codecs?/test-utils", "reth-trie-common/test-utils", "arbitrary", + "revm-primitives/test-utils" ] serde-bincode-compat = [ "alloy-consensus/serde-bincode-compat", diff --git a/crates/scroll/execution/Cargo.toml b/crates/scroll/execution/Cargo.toml new file mode 100644 index 00000000000..6abb7d1cd7f --- /dev/null +++ b/crates/scroll/execution/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "reth-scroll-execution" +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 +reth-revm.workspace = true + +# scroll +reth-scroll-primitives = { workspace = true, optional = true } +reth-scroll-revm.workspace = true +reth-scroll-storage = { workspace = true, optional = true } + +# misc +auto_impl.workspace = true + +[features] +scroll = [ + "reth-scroll-primitives", + "reth-scroll-revm/scroll", + "reth-scroll-storage/scroll", +] +test-utils = [ + "reth-scroll-revm/test-utils", + "reth-revm/test-utils" +] \ No newline at end of file diff --git a/crates/scroll/execution/src/context.rs b/crates/scroll/execution/src/context.rs new file mode 100644 index 00000000000..6ddd47dc531 --- /dev/null +++ b/crates/scroll/execution/src/context.rs @@ -0,0 +1,68 @@ +#[cfg(feature = "test-utils")] +use reth_revm::{database::StateProviderDatabase, db::CacheDB}; +use reth_revm::{Database, State}; + +/// Finalize the execution of the type and return the output +pub trait FinalizeExecution { + /// Finalize the state and return the output. + fn finalize(&mut self) -> Output; +} + +impl FinalizeExecution + for State +{ + fn finalize(&mut self) -> reth_scroll_revm::states::ScrollBundleState { + let bundle = self.take_bundle(); + (bundle, self.database.context()).into() + } +} + +/// A type that returns additional execution context. +pub trait ContextFul: WithContext {} +impl ContextFul for T where T: WithContext {} + +/// Types that can provide a context. +#[auto_impl::auto_impl(&, &mut)] +pub trait WithContext { + /// The context returned. + type Context; + + /// Returns the context from the type. + fn context(&self) -> &Self::Context; +} + +#[cfg(not(feature = "scroll"))] +type ExecutionContext = (); +#[cfg(feature = "scroll")] +type ExecutionContext = reth_scroll_primitives::ScrollPostExecutionContext; + +#[cfg(feature = "scroll")] +impl WithContext for reth_scroll_storage::ScrollStateProviderDatabase { + type Context = ExecutionContext; + + fn context(&self) -> &Self::Context { + &self.post_execution_context + } +} + +#[cfg(feature = "test-utils")] +static DEFAULT_CONTEXT: std::sync::LazyLock = + std::sync::LazyLock::new(Default::default); + +#[cfg(feature = "test-utils")] +impl WithContext for StateProviderDatabase { + type Context = ExecutionContext; + + fn context(&self) -> &Self::Context { + &DEFAULT_CONTEXT + } +} + +#[cfg(feature = "test-utils")] +impl WithContext for CacheDB { + type Context = ExecutionContext; + + fn context(&self) -> &Self::Context { + &DEFAULT_CONTEXT + } +} diff --git a/crates/scroll/execution/src/lib.rs b/crates/scroll/execution/src/lib.rs new file mode 100644 index 00000000000..3a7b2353563 --- /dev/null +++ b/crates/scroll/execution/src/lib.rs @@ -0,0 +1,6 @@ +//! Scroll execution related implementations. + +#![warn(unused_crate_dependencies)] + +pub use context::{ContextFul, FinalizeExecution, WithContext}; +mod context; diff --git a/crates/scroll/primitives/Cargo.toml b/crates/scroll/primitives/Cargo.toml new file mode 100644 index 00000000000..a8c17e0ff51 --- /dev/null +++ b/crates/scroll/primitives/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "reth-scroll-primitives" +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] +# alloy +alloy-primitives.workspace = true + +# reth +reth-codecs.workspace = true + +# scroll +poseidon-bn254 = { workspace = true, features = ["bn254"] } + +# misc +arbitrary = { workspace = true, features = ["derive"], optional = true } + +# required by reth-codecs +bytes.workspace = true +modular-bitfield.workspace = true +serde.workspace = true + +[dev-dependencies] +alloy-primitives = { workspace = true, features = ["arbitrary"] } +arbitrary = { workspace = true, features = ["derive"] } +test-fuzz.workspace = true + +[features] +default = ["std"] +std = [ + "serde/std", + "alloy-primitives/std" +] +arbitrary = [ + "dep:arbitrary", + "alloy-primitives/arbitrary", + "reth-codecs/arbitrary" +] diff --git a/crates/scroll/primitives/src/account_extension.rs b/crates/scroll/primitives/src/account_extension.rs new file mode 100644 index 00000000000..c48181f9ea2 --- /dev/null +++ b/crates/scroll/primitives/src/account_extension.rs @@ -0,0 +1,43 @@ +use crate::{hash_code, POSEIDON_EMPTY}; +use alloy_primitives::B256; +use reth_codecs::Compact; +use serde::{Deserialize, Serialize}; + +/// The extension for a Scroll account's representation in storage. +/// +/// The extension is used in order to maintain backwards compatibility if more fields need to be +/// added to the account (see [reth codecs](reth_codecs::test_utils) for details). +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, Compact)] +#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] +pub struct AccountExtension { + /// Size in bytes of the account's bytecode. + pub code_size: u64, + /// Poseidon hash of the account's bytecode. `None` means there is no + /// bytecode for the account. + pub poseidon_code_hash: Option, +} + +impl AccountExtension { + /// Creates an empty [`AccountExtension`]. + pub const fn empty() -> Self { + Self { code_size: 0, poseidon_code_hash: None } + } + + /// Creates an [`AccountExtension`] from the provided bytecode. + pub fn from_bytecode>(code: &T) -> Self { + let code = code.as_ref(); + Self { + code_size: code.len() as u64, + poseidon_code_hash: (!code.is_empty()).then_some(hash_code(code)), + } + } +} + +impl From<(u64, B256)> for AccountExtension { + fn from(value: (u64, B256)) -> Self { + Self { + code_size: value.0, + poseidon_code_hash: (value.1 != POSEIDON_EMPTY).then_some(value.1), + } + } +} diff --git a/crates/scroll/primitives/src/execution_context.rs b/crates/scroll/primitives/src/execution_context.rs new file mode 100644 index 00000000000..0175e70cb1a --- /dev/null +++ b/crates/scroll/primitives/src/execution_context.rs @@ -0,0 +1,12 @@ +use alloy_primitives::{map::HashMap, B256}; + +/// A Keccak code hash. +type KeccakHash = B256; +/// A Poseidon code hash. +type PoseidonHash = B256; +/// Size of a contract's code in bytes. +type CodeSize = u64; + +/// Scroll post execution context maps a Keccak code hash of a contract's bytecode to its code size +/// and Poseidon code hash. +pub type ScrollPostExecutionContext = HashMap; diff --git a/crates/scroll/primitives/src/lib.rs b/crates/scroll/primitives/src/lib.rs new file mode 100644 index 00000000000..2a18616b307 --- /dev/null +++ b/crates/scroll/primitives/src/lib.rs @@ -0,0 +1,12 @@ +//! Primitive types for the Scroll extension of `Reth`. + +#![warn(unused_crate_dependencies)] + +pub use execution_context::ScrollPostExecutionContext; +mod execution_context; + +pub use account_extension::AccountExtension; +mod account_extension; + +pub use poseidon::{hash_code, POSEIDON_EMPTY}; +mod poseidon; diff --git a/crates/scroll/primitives/src/poseidon.rs b/crates/scroll/primitives/src/poseidon.rs new file mode 100644 index 00000000000..4a5010167f5 --- /dev/null +++ b/crates/scroll/primitives/src/poseidon.rs @@ -0,0 +1,10 @@ +use alloy_primitives::{b256, B256}; + +/// The Poseidon hash of the empty string `""`. +pub const POSEIDON_EMPTY: B256 = + b256!("2098f5fb9e239eab3ceac3f27b81e481dc3124d55ffed523a839ee8446b64864"); + +/// Poseidon code hash +pub fn hash_code(code: &[u8]) -> B256 { + poseidon_bn254::hash_code(code).into() +} diff --git a/crates/scroll/revm/Cargo.toml b/crates/scroll/revm/Cargo.toml new file mode 100644 index 00000000000..44956d812b1 --- /dev/null +++ b/crates/scroll/revm/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "reth-scroll-revm" +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] +# revm +revm.workspace = true + +# scroll +reth-scroll-primitives.workspace = true + +# misc +serde = { workspace = true, optional = true } + +[features] +default = ["std"] +arbitrary = [ + "revm/arbitrary", + "reth-scroll-primitives/arbitrary" +] +asm-keccak = ["revm/asm-keccak"] +c-kzg = ["revm/c-kzg"] +optimism = ["revm/optimism"] +serde = [ + "revm/serde", + "serde/std" +] +scroll = [] +test-utils = ["revm/test-utils"] +std = [ + "revm/std", + "serde/std" +] diff --git a/crates/scroll/revm/src/lib.rs b/crates/scroll/revm/src/lib.rs new file mode 100644 index 00000000000..3ace4cd19bd --- /dev/null +++ b/crates/scroll/revm/src/lib.rs @@ -0,0 +1,65 @@ +//! Scroll `revm` types redefinitions. Account types are redefined with two additional fields +//! `code_size` and `poseidon_code_hash`, which are used during computation of the state root. + +#![warn(unused_crate_dependencies)] + +pub mod states; + +#[cfg(feature = "optimism")] +pub use revm::primitives::OptimismFields; + +pub use revm::{ + db::*, + inspector_handle_register, + primitives::{ + keccak256, AuthorizationList, Bytecode, BytecodeDecodeError, JumpTable, + LegacyAnalyzedBytecode, TxEnv, TxKind, + }, + Evm, EvmBuilder, GetInspector, +}; + +#[cfg(feature = "scroll")] +pub use crate::states::ScrollAccountInfo as AccountInfo; +#[cfg(not(feature = "scroll"))] +pub use revm::primitives::AccountInfo; +pub use states::ScrollAccountInfo; + +/// Shared module, available for all feature flags. +pub mod shared { + pub use revm::primitives::AccountInfo; +} + +/// Match the `revm` module structure +pub mod interpreter { + pub use revm::interpreter::*; +} + +/// Match the `revm` module structure +pub mod precompile { + pub use revm::precompile::*; +} + +/// Match the `revm-primitives` module structure +pub mod primitives { + #[cfg(feature = "scroll")] + pub use crate::states::ScrollAccountInfo as AccountInfo; + pub use revm::primitives::*; +} + +/// Match the `revm` module structure +pub mod db { + #[cfg(feature = "scroll")] + pub use crate::states::{ + ScrollBundleAccount as BundleAccount, ScrollBundleState as BundleState, + }; + pub use revm::db::*; + /// Match the `revm` module structure + pub mod states { + #[cfg(feature = "scroll")] + pub use crate::states::{ + ScrollBundleBuilder as BundleBuilder, ScrollBundleState as BundleState, + ScrollPlainStateReverts as PlainStateReverts, ScrollStateChangeset as StateChangeset, + }; + pub use revm::db::states::*; + } +} diff --git a/crates/scroll/revm/src/states/account_info.rs b/crates/scroll/revm/src/states/account_info.rs new file mode 100644 index 00000000000..e9464470be4 --- /dev/null +++ b/crates/scroll/revm/src/states/account_info.rs @@ -0,0 +1,156 @@ +use reth_scroll_primitives::{hash_code, ScrollPostExecutionContext, POSEIDON_EMPTY}; +use revm::primitives::{AccountInfo, Bytecode, B256, KECCAK_EMPTY, U256}; + +/// The Scroll account information. Code copy of [`AccountInfo`]. Provides additional `code_size` +/// and `poseidon_code_hash` fields needed in the state root computation. +#[derive(Clone, Debug, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ScrollAccountInfo { + /// Account balance. + pub balance: U256, + /// Account nonce. + pub nonce: u64, + /// Account code keccak hash. + pub code_hash: B256, + /// code: if None, `code_by_hash` will be used to fetch it if code needs to be loaded from + /// inside `revm`. + pub code: Option, + /// Account code size. + pub code_size: u64, + /// Account code Poseidon hash. [`POSEIDON_EMPTY`] if code is None or empty. + pub poseidon_code_hash: B256, +} + +impl From<(AccountInfo, &ScrollPostExecutionContext)> for ScrollAccountInfo { + fn from((info, context): (AccountInfo, &ScrollPostExecutionContext)) -> Self { + let context = context.get(&info.code_hash).copied(); + let (code_size, poseidon_code_hash) = context + .or_else(|| { + info.code + .as_ref() + .map(|code| (code.len() as u64, hash_code(code.original_byte_slice()))) + }) + .unwrap_or((0, POSEIDON_EMPTY)); + Self { + balance: info.balance, + nonce: info.nonce, + code_hash: info.code_hash, + code: info.code, + code_size, + poseidon_code_hash, + } + } +} + +// This conversion causes a loss of information. +impl From for AccountInfo { + fn from(info: ScrollAccountInfo) -> Self { + Self { + balance: info.balance, + nonce: info.nonce, + code_hash: info.code_hash, + code: info.code, + } + } +} + +impl Default for ScrollAccountInfo { + fn default() -> Self { + Self { + balance: U256::ZERO, + code_hash: KECCAK_EMPTY, + code: Some(Bytecode::default()), + nonce: 0, + code_size: 0, + poseidon_code_hash: POSEIDON_EMPTY, + } + } +} + +impl PartialEq for ScrollAccountInfo { + fn eq(&self, other: &Self) -> bool { + self.balance == other.balance && + self.nonce == other.nonce && + self.code_hash == other.code_hash + } +} + +impl ScrollAccountInfo { + /// Creates a new [`ScrollAccountInfo`] with the given fields. + pub fn new( + balance: U256, + nonce: u64, + code_hash: B256, + code: Bytecode, + poseidon_code_hash: B256, + ) -> Self { + let code_size = code.len() as u64; + Self { balance, nonce, code: Some(code), code_hash, code_size, poseidon_code_hash } + } + + /// Returns account info without the code. + pub fn without_code(mut self) -> Self { + self.take_bytecode(); + self + } + + /// Returns if an account is empty. + /// + /// An account is empty if the following conditions are met. + /// - code hash is zero or set to the Keccak256 hash of the empty string `""` + /// - balance is zero + /// - nonce is zero + pub fn is_empty(&self) -> bool { + let code_empty = self.is_empty_code_hash() || self.code_hash.is_zero(); + code_empty && self.balance.is_zero() && self.nonce == 0 + } + + /// Returns `true` if the account is not empty. + pub fn exists(&self) -> bool { + !self.is_empty() + } + + /// Returns `true` if account has no nonce and code. + pub fn has_no_code_and_nonce(&self) -> bool { + self.is_empty_code_hash() && self.nonce == 0 + } + + /// Return bytecode hash associated with this account. + /// If account does not have code, it returns `KECCAK_EMPTY` hash. + pub const fn code_hash(&self) -> B256 { + self.code_hash + } + + /// Returns true if the code hash is the Keccak256 hash of the empty string `""`. + #[inline] + pub fn is_empty_code_hash(&self) -> bool { + self.code_hash == KECCAK_EMPTY + } + + /// Take bytecode from account. Code will be set to None. + pub fn take_bytecode(&mut self) -> Option { + self.code.take() + } + + /// Returns a [`ScrollAccountInfo`] with only balance. + pub fn from_balance(balance: U256) -> Self { + Self { balance, ..Default::default() } + } + + /// Returns a [`ScrollAccountInfo`] with defaults for balance and nonce. + /// Computes the Keccak and Poseidon hash of the provided bytecode. + pub fn from_bytecode(bytecode: Bytecode) -> Self { + let hash = bytecode.hash_slow(); + let code_size = bytecode.len() as u64; + let poseidon_code_hash = hash_code(bytecode.bytecode()); + + Self { + balance: U256::ZERO, + nonce: 1, + code: Some(bytecode), + code_hash: hash, + code_size, + poseidon_code_hash, + } + } +} diff --git a/crates/scroll/revm/src/states/bundle.rs b/crates/scroll/revm/src/states/bundle.rs new file mode 100644 index 00000000000..f3f6f2f2b63 --- /dev/null +++ b/crates/scroll/revm/src/states/bundle.rs @@ -0,0 +1,750 @@ +use super::bundle_account::ScrollBundleAccount; +use crate::states::{ + changes::{ScrollPlainStateReverts, ScrollStateChangeset}, + reverts::ScrollReverts, + ScrollAccountInfo, ScrollAccountInfoRevert, ScrollAccountRevert, +}; +use reth_scroll_primitives::ScrollPostExecutionContext; +use revm::{ + db::{ + states::{PlainStorageChangeset, StorageSlot}, + AccountStatus, BundleState, OriginalValuesKnown, RevertToSlot, + }, + primitives::{ + map::{Entry, HashMap, HashSet}, + Address, Bytecode, B256, KECCAK_EMPTY, U256, + }, +}; +use std::{ + collections::{hash_map, BTreeMap, BTreeSet}, + ops::RangeInclusive, +}; + +/// An code copy of the [`BundleState`] modified with Scroll compatible fields. +#[derive(Default, Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ScrollBundleState { + /// Account state. + pub state: HashMap, + /// All created contracts in this block. + pub contracts: HashMap, + /// Changes to revert. + /// + /// Note: Inside vector is *not* sorted by address. + /// But it is unique by address. + pub reverts: ScrollReverts, + /// The size of the plain state in the bundle state. + pub state_size: usize, + /// The size of reverts in the bundle state. + pub reverts_size: usize, +} + +impl From<(BundleState, &ScrollPostExecutionContext)> for ScrollBundleState { + fn from((bundle, context): (BundleState, &ScrollPostExecutionContext)) -> Self { + // we need to clone the reverts because there is no way to take ownership of + // the inner Vec from the `Reverts` wrapper. + let reverts = bundle + .reverts + .iter() + .map(|reverts| { + reverts + .iter() + .map(|(add, revert)| (*add, (revert.clone(), context).into())) + .collect() + }) + .collect(); + + let state = bundle + .state + .into_iter() + .map(|(add, account)| (add, (account, context).into())) + .collect(); + + Self { + state, + contracts: bundle.contracts, + reverts: ScrollReverts::new(reverts), + state_size: bundle.state_size, + reverts_size: bundle.reverts_size, + } + } +} + +impl From<(BundleState, &())> for ScrollBundleState { + fn from((bundle, _): (BundleState, &())) -> Self { + (bundle, &ScrollPostExecutionContext::default()).into() + } +} + +impl ScrollBundleState { + /// Return builder instance for further manipulation + pub fn builder(revert_range: RangeInclusive) -> ScrollBundleBuilder { + ScrollBundleBuilder::new(revert_range) + } + + /// Create it with new and old values of both Storage and [`ScrollAccountInfo`]. + pub fn new( + state: impl IntoIterator< + Item = ( + Address, + Option, + Option, + HashMap, + ), + >, + reverts: impl IntoIterator< + Item = impl IntoIterator< + Item = ( + Address, + Option>, + impl IntoIterator, + ), + >, + >, + contracts: impl IntoIterator, + ) -> Self { + // Create state from iterator. + let mut state_size = 0; + let state = state + .into_iter() + .map(|(address, original, present, storage)| { + let account = ScrollBundleAccount::new( + original, + present, + storage + .into_iter() + .map(|(k, (o_val, p_val))| (k, StorageSlot::new_changed(o_val, p_val))) + .collect(), + AccountStatus::Changed, + ); + state_size += account.size_hint(); + (address, account) + }) + .collect(); + + // Create reverts from iterator. + let mut reverts_size = 0; + let reverts = reverts + .into_iter() + .map(|block_reverts| { + block_reverts + .into_iter() + .map(|(address, account, storage)| { + let account = match account { + Some(Some(account)) => ScrollAccountInfoRevert::RevertTo(account), + Some(None) => ScrollAccountInfoRevert::DeleteIt, + None => ScrollAccountInfoRevert::DoNothing, + }; + let revert = ScrollAccountRevert { + account, + storage: storage + .into_iter() + .map(|(k, v)| (k, RevertToSlot::Some(v))) + .collect(), + previous_status: AccountStatus::Changed, + wipe_storage: false, + }; + reverts_size += revert.size_hint(); + (address, revert) + }) + .collect::>() + }) + .collect::>(); + + Self { + state, + contracts: contracts.into_iter().collect(), + reverts: ScrollReverts::new(reverts), + state_size, + reverts_size, + } + } + + /// Returns the approximate size of changes in the bundle state. + /// The estimation is not precise, because the information about the number of + /// destroyed entries that need to be removed is not accessible to the bundle state. + pub fn size_hint(&self) -> usize { + self.state_size + self.reverts_size + self.contracts.len() + } + + /// Return reference to the state. + pub const fn state(&self) -> &HashMap { + &self.state + } + + /// Is bundle state empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Return number of changed accounts. + pub fn len(&self) -> usize { + self.state.len() + } + + /// Get account from state + pub fn account(&self, address: &Address) -> Option<&ScrollBundleAccount> { + self.state.get(address) + } + + /// Get bytecode from state + pub fn bytecode(&self, hash: &B256) -> Option { + self.contracts.get(hash).cloned() + } + + /// Extend the bundle with other state + /// + /// Update the `other` state only if `other` is not flagged as destroyed. + pub fn extend_state(&mut self, other_state: HashMap) { + for (address, other_account) in other_state { + match self.state.entry(address) { + Entry::Occupied(mut entry) => { + let this = entry.get_mut(); + self.state_size -= this.size_hint(); + + // if other was destroyed. replace `this` storage with + // the `other one. + if other_account.was_destroyed() { + this.storage = other_account.storage; + } else { + // otherwise extend this storage with other + for (key, storage_slot) in other_account.storage { + // update present value or insert storage slot. + this.storage.entry(key).or_insert(storage_slot).present_value = + storage_slot.present_value; + } + } + this.info = other_account.info; + this.status.transition(other_account.status); + + // Update the state size + self.state_size += this.size_hint(); + } + hash_map::Entry::Vacant(entry) => { + // just insert if empty + self.state_size += other_account.size_hint(); + entry.insert(other_account); + } + } + } + } + + /// Extend the state with state that is build on top of it. + /// + /// If storage was wiped in `other` state, copy `this` plain state + /// and put it inside `other` revert (if there is no duplicates of course). + /// + /// If `this` and `other` accounts were both destroyed invalidate second + /// wipe flag (from `other`). As wiping from database should be done only once + /// and we already transferred all potentially missing storages to the `other` revert. + pub fn extend(&mut self, mut other: Self) { + // iterate over reverts and if its storage is wiped try to add previous bundle + // state as there is potential missing slots. + for (address, revert) in other.reverts.iter_mut().flatten() { + if revert.wipe_storage { + // If there is wipe storage in `other` revert + // we need to move storage from present state. + if let Some(this_account) = self.state.get_mut(address) { + // As this account was destroyed inside `other` bundle. + // we are fine to wipe/drain this storage and put it inside revert. + for (key, value) in this_account.storage.drain() { + revert + .storage + .entry(key) + .or_insert(RevertToSlot::Some(value.present_value)); + } + + // nullify `other` wipe as primary database wipe is done in `this`. + if this_account.was_destroyed() { + revert.wipe_storage = false; + } + } + } + + // Increment reverts size for each of the updated reverts. + self.reverts_size += revert.size_hint(); + } + // Extension of state + self.extend_state(other.state); + // Contract can be just extended, when counter is introduced we will take into account that. + self.contracts.extend(other.contracts); + // Reverts can be just extended + self.reverts.extend(other.reverts); + } + + /// Consume the bundle state and return plain state. + pub fn into_plain_state(self, is_value_known: OriginalValuesKnown) -> ScrollStateChangeset { + // pessimistically pre-allocate assuming _all_ accounts changed. + let state_len = self.state.len(); + let mut accounts = Vec::with_capacity(state_len); + let mut storage = Vec::with_capacity(state_len); + + for (address, account) in self.state { + // append account info if it is changed. + let was_destroyed = account.was_destroyed(); + if is_value_known.is_not_known() || account.is_info_changed() { + let info = account.info.map(ScrollAccountInfo::without_code); + accounts.push((address, info)); + } + + // append storage changes + + // NOTE: Assumption is that revert is going to remove whole plain storage from + // database so we can check if plain state was wiped or not. + let mut account_storage_changed = Vec::with_capacity(account.storage.len()); + + for (key, slot) in account.storage { + // If storage was destroyed that means that storage was wiped. + // In that case we need to check if present storage value is different then ZERO. + let destroyed_and_not_zero = was_destroyed && !slot.present_value.is_zero(); + + // If account is not destroyed check if original values was changed, + // so we can update it. + let not_destroyed_and_changed = !was_destroyed && slot.is_changed(); + + if is_value_known.is_not_known() || + destroyed_and_not_zero || + not_destroyed_and_changed + { + account_storage_changed.push((key, slot.present_value)); + } + } + + if !account_storage_changed.is_empty() || was_destroyed { + // append storage changes to account. + storage.push(PlainStorageChangeset { + address, + wipe_storage: was_destroyed, + storage: account_storage_changed, + }); + } + } + let contracts = self + .contracts + .into_iter() + // remove empty bytecodes + .filter(|(b, _)| *b != KECCAK_EMPTY) + .collect::>(); + ScrollStateChangeset { accounts, storage, contracts } + } + + /// Consume the bundle state and split it into reverts and plain state. + pub fn into_plain_state_and_reverts( + mut self, + is_value_known: OriginalValuesKnown, + ) -> (ScrollStateChangeset, ScrollPlainStateReverts) { + let reverts = self.take_all_reverts(); + let plain_state = self.into_plain_state(is_value_known); + (plain_state, reverts.into_plain_state_reverts()) + } + + /// Take first N raw reverts from the [`ScrollBundleState`]. + pub fn take_n_reverts(&mut self, reverts_to_take: usize) -> ScrollReverts { + // split is done as [0, num) and [num, len]. + if reverts_to_take > self.reverts.len() { + return self.take_all_reverts(); + } + let (detach, this) = self.reverts.split_at(reverts_to_take); + let detached_reverts = ScrollReverts::new(detach.to_vec()); + self.reverts_size = + this.iter().flatten().fold(0, |acc, (_, revert)| acc + revert.size_hint()); + self.reverts = ScrollReverts::new(this.to_vec()); + detached_reverts + } + + /// Return and clear all reverts from [`ScrollBundleState`] + pub fn take_all_reverts(&mut self) -> ScrollReverts { + self.reverts_size = 0; + core::mem::take(&mut self.reverts) + } + + /// Reverts the state changes by N transitions back. + /// + /// See also [`Self::revert_latest`] + pub fn revert(&mut self, mut num_transitions: usize) { + if num_transitions == 0 { + return; + } + + while self.revert_latest() { + num_transitions -= 1; + if num_transitions == 0 { + // break the loop. + break; + } + } + } + + /// Reverts the state changes of the latest transition + /// + /// Note: This is the same as `BundleState::revert(1)` + /// + /// Returns true if the state was reverted. + pub fn revert_latest(&mut self) -> bool { + // revert the latest recorded state + if let Some(reverts) = self.reverts.pop() { + for (address, revert_account) in reverts { + self.reverts_size -= revert_account.size_hint(); + match self.state.entry(address) { + Entry::Occupied(mut entry) => { + let account = entry.get_mut(); + self.state_size -= account.size_hint(); + if account.revert(revert_account) { + entry.remove(); + } else { + self.state_size += account.size_hint(); + } + } + Entry::Vacant(entry) => { + // create empty account that we will revert on. + // Only place where this account is not existing is if revert is DeleteIt. + let mut account = ScrollBundleAccount::new( + None, + None, + HashMap::default(), + AccountStatus::LoadedNotExisting, + ); + if !account.revert(revert_account) { + self.state_size += account.size_hint(); + entry.insert(account); + } + } + } + } + return true; + } + + false + } +} + +/// This builder is used to help to facilitate the initialization of [`ScrollBundleState`] struct. +/// This is a code copy of the [`revm::db::states::BundleBuilder`] to accommodate the +/// [`ScrollBundleState`]. +#[derive(Debug)] +pub struct ScrollBundleBuilder { + states: HashSet
, + state_original: HashMap, + state_present: HashMap, + state_storage: HashMap>, + + reverts: BTreeSet<(u64, Address)>, + revert_range: RangeInclusive, + revert_account: HashMap<(u64, Address), Option>>, + revert_storage: HashMap<(u64, Address), Vec<(U256, U256)>>, + + contracts: HashMap, +} + +impl Default for ScrollBundleBuilder { + fn default() -> Self { + Self { + states: HashSet::default(), + state_original: HashMap::default(), + state_present: HashMap::default(), + state_storage: HashMap::default(), + reverts: BTreeSet::new(), + revert_range: 0..=0, + revert_account: HashMap::default(), + revert_storage: HashMap::default(), + contracts: HashMap::default(), + } + } +} + +impl ScrollBundleBuilder { + /// Create builder instance + /// + /// `revert_range` indicates the size of [`ScrollBundleState`] `reverts` field + pub fn new(revert_range: RangeInclusive) -> Self { + Self { revert_range, ..Default::default() } + } + + /// Apply a transformation to the builder. + pub fn apply(self, f: F) -> Self + where + F: FnOnce(Self) -> Self, + { + f(self) + } + + /// Apply a mutable transformation to the builder. + pub fn apply_mut(&mut self, f: F) -> &mut Self + where + F: FnOnce(&mut Self), + { + f(self); + self + } + + /// Collect address info of [`ScrollBundleState`] state + pub fn state_address(mut self, address: Address) -> Self { + self.set_state_address(address); + self + } + + /// Collect account info of [`ScrollBundleState`] state + pub fn state_original_account_info( + mut self, + address: Address, + original: ScrollAccountInfo, + ) -> Self { + self.set_state_original_account_info(address, original); + self + } + + /// Collect account info of [`ScrollBundleState`] state + pub fn state_present_account_info( + mut self, + address: Address, + present: ScrollAccountInfo, + ) -> Self { + self.set_state_present_account_info(address, present); + self + } + + /// Collect storage info of [`ScrollBundleState`] state + pub fn state_storage(mut self, address: Address, storage: HashMap) -> Self { + self.set_state_storage(address, storage); + self + } + + /// Collect address info of [`ScrollBundleState`] reverts + /// + /// `block_number` must respect `revert_range`, or the input + /// will be ignored during the final build process + pub fn revert_address(mut self, block_number: u64, address: Address) -> Self { + self.set_revert_address(block_number, address); + self + } + + /// Collect account info of [`ScrollBundleState`] reverts + /// + /// `block_number` must respect `revert_range`, or the input + /// will be ignored during the final build process + pub fn revert_account_info( + mut self, + block_number: u64, + address: Address, + account: Option>, + ) -> Self { + self.set_revert_account_info(block_number, address, account); + self + } + + /// Collect storage info of [`ScrollBundleState`] reverts + /// + /// `block_number` must respect `revert_range`, or the input + /// will be ignored during the final build process + pub fn revert_storage( + mut self, + block_number: u64, + address: Address, + storage: Vec<(U256, U256)>, + ) -> Self { + self.set_revert_storage(block_number, address, storage); + self + } + + /// Collect contracts info + pub fn contract(mut self, address: B256, bytecode: Bytecode) -> Self { + self.set_contract(address, bytecode); + self + } + + /// Set address info of [`ScrollBundleState`] state. + pub fn set_state_address(&mut self, address: Address) -> &mut Self { + self.states.insert(address); + self + } + + /// Set original account info of [`ScrollBundleState`] state. + pub fn set_state_original_account_info( + &mut self, + address: Address, + original: ScrollAccountInfo, + ) -> &mut Self { + self.states.insert(address); + self.state_original.insert(address, original); + self + } + + /// Set present account info of [`ScrollBundleState`] state. + pub fn set_state_present_account_info( + &mut self, + address: Address, + present: ScrollAccountInfo, + ) -> &mut Self { + self.states.insert(address); + self.state_present.insert(address, present); + self + } + + /// Set storage info of [`ScrollBundleState`] state. + pub fn set_state_storage( + &mut self, + address: Address, + storage: HashMap, + ) -> &mut Self { + self.states.insert(address); + self.state_storage.insert(address, storage); + self + } + + /// Set address info of [`ScrollBundleState`] reverts. + pub fn set_revert_address(&mut self, block_number: u64, address: Address) -> &mut Self { + self.reverts.insert((block_number, address)); + self + } + + /// Set account info of [`ScrollBundleState`] reverts. + pub fn set_revert_account_info( + &mut self, + block_number: u64, + address: Address, + account: Option>, + ) -> &mut Self { + self.reverts.insert((block_number, address)); + self.revert_account.insert((block_number, address), account); + self + } + + /// Set storage info of [`ScrollBundleState`] reverts. + pub fn set_revert_storage( + &mut self, + block_number: u64, + address: Address, + storage: Vec<(U256, U256)>, + ) -> &mut Self { + self.reverts.insert((block_number, address)); + self.revert_storage.insert((block_number, address), storage); + self + } + + /// Set contracts info. + pub fn set_contract(&mut self, address: B256, bytecode: Bytecode) -> &mut Self { + self.contracts.insert(address, bytecode); + self + } + + /// Create [`ScrollBundleState`] instance based on collected information + pub fn build(mut self) -> ScrollBundleState { + let mut state_size = 0; + let state = self + .states + .into_iter() + .map(|address| { + let storage = self + .state_storage + .remove(&address) + .map(|s| { + s.into_iter() + .map(|(k, (o_val, p_val))| (k, StorageSlot::new_changed(o_val, p_val))) + .collect() + }) + .unwrap_or_default(); + let bundle_account = ScrollBundleAccount::new( + self.state_original.remove(&address), + self.state_present.remove(&address), + storage, + AccountStatus::Changed, + ); + state_size += bundle_account.size_hint(); + (address, bundle_account) + }) + .collect(); + + let mut reverts_size = 0; + let mut reverts_map = BTreeMap::new(); + for block_number in self.revert_range { + reverts_map.insert(block_number, Vec::new()); + } + self.reverts.into_iter().for_each(|(block_number, address)| { + let account = + match self.revert_account.remove(&(block_number, address)).unwrap_or_default() { + Some(Some(account)) => ScrollAccountInfoRevert::RevertTo(account), + Some(None) => ScrollAccountInfoRevert::DeleteIt, + None => ScrollAccountInfoRevert::DoNothing, + }; + let storage = self + .revert_storage + .remove(&(block_number, address)) + .map(|s| s.into_iter().map(|(k, v)| (k, RevertToSlot::Some(v))).collect()) + .unwrap_or_default(); + let account_revert = ScrollAccountRevert { + account, + storage, + previous_status: AccountStatus::Changed, + wipe_storage: false, + }; + + if reverts_map.contains_key(&block_number) { + reverts_size += account_revert.size_hint(); + reverts_map + .entry(block_number) + .or_insert(Vec::new()) + .push((address, account_revert)); + } + }); + + ScrollBundleState { + state, + contracts: self.contracts, + reverts: ScrollReverts::new(reverts_map.into_values().collect()), + state_size, + reverts_size, + } + } + + /// Getter for `states` field + pub const fn get_states(&self) -> &HashSet
{ + &self.states + } + + /// Mutable getter for `states` field + pub fn get_states_mut(&mut self) -> &mut HashSet
{ + &mut self.states + } + + /// Mutable getter for `state_original` field + pub fn get_state_original_mut(&mut self) -> &mut HashMap { + &mut self.state_original + } + + /// Mutable getter for `state_present` field + pub fn get_state_present_mut(&mut self) -> &mut HashMap { + &mut self.state_present + } + + /// Mutable getter for `state_storage` field + pub fn get_state_storage_mut(&mut self) -> &mut HashMap> { + &mut self.state_storage + } + + /// Mutable getter for `reverts` field + pub fn get_reverts_mut(&mut self) -> &mut BTreeSet<(u64, Address)> { + &mut self.reverts + } + + /// Mutable getter for `revert_range` field + pub fn get_revert_range_mut(&mut self) -> &mut RangeInclusive { + &mut self.revert_range + } + + /// Mutable getter for `revert_account` field + pub fn get_revert_account_mut( + &mut self, + ) -> &mut HashMap<(u64, Address), Option>> { + &mut self.revert_account + } + + /// Mutable getter for `revert_storage` field + pub fn get_revert_storage_mut(&mut self) -> &mut HashMap<(u64, Address), Vec<(U256, U256)>> { + &mut self.revert_storage + } + + /// Mutable getter for `contracts` field + pub fn get_contracts_mut(&mut self) -> &mut HashMap { + &mut self.contracts + } +} diff --git a/crates/scroll/revm/src/states/bundle_account.rs b/crates/scroll/revm/src/states/bundle_account.rs new file mode 100644 index 00000000000..29be890314d --- /dev/null +++ b/crates/scroll/revm/src/states/bundle_account.rs @@ -0,0 +1,130 @@ +use crate::states::{ScrollAccountInfo, ScrollAccountInfoRevert, ScrollAccountRevert}; +use reth_scroll_primitives::ScrollPostExecutionContext; +use revm::{ + db::{ + states::StorageSlot, AccountStatus, BundleAccount, RevertToSlot, StorageWithOriginalValues, + }, + interpreter::primitives::U256, + primitives::map::HashMap, +}; + +/// The scroll account bundle. Originally defined in [`BundleAccount`], a +/// scroll version of the bundle is needed for the [`crate::states::ScrollBundleState`]. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ScrollBundleAccount { + /// The current account's information + pub info: Option, + /// The original account's information + pub original_info: Option, + /// Contains both original and present state. + /// When extracting changeset we compare if original value is different from present value. + /// If it is different we add it to changeset. + /// + /// If Account was destroyed we ignore original value and compare present state with + /// [`U256::ZERO`]. + pub storage: StorageWithOriginalValues, + /// Account status. + pub status: AccountStatus, +} + +impl From<(BundleAccount, &ScrollPostExecutionContext)> for ScrollBundleAccount { + fn from((account, context): (BundleAccount, &ScrollPostExecutionContext)) -> Self { + let info = account.info.map(|info| (info, context).into()); + let original_info = account.original_info.map(|info| (info, context).into()); + Self { info, original_info, storage: account.storage, status: account.status } + } +} + +impl ScrollBundleAccount { + /// Creates a [`ScrollBundleAccount`]. + pub const fn new( + original_info: Option, + present_info: Option, + storage: StorageWithOriginalValues, + status: AccountStatus, + ) -> Self { + Self { info: present_info, original_info, storage, status } + } + + /// The approximate size of changes needed to store this account. + /// `1 + storage_len` + pub fn size_hint(&self) -> usize { + 1 + self.storage.len() + } + + /// Return storage slot if it exists. + /// + /// In case we know that account is newly created or destroyed, return `Some(U256::ZERO)` + pub fn storage_slot(&self, slot: U256) -> Option { + let slot = self.storage.get(&slot).map(|s| s.present_value); + if slot.is_some() { + slot + } else if self.status.is_storage_known() { + Some(U256::ZERO) + } else { + None + } + } + + /// Fetch account info if it exists. + pub fn account_info(&self) -> Option { + self.info.clone() + } + + /// Was this account destroyed. + pub fn was_destroyed(&self) -> bool { + self.status.was_destroyed() + } + + /// Return true of account info was changed. + pub fn is_info_changed(&self) -> bool { + self.info != self.original_info + } + + /// Return true if contract was changed + pub fn is_contract_changed(&self) -> bool { + self.info.as_ref().map(|a| a.code_hash) != self.original_info.as_ref().map(|a| a.code_hash) + } + + /// Revert account to previous state and return true if account can be removed. + pub fn revert(&mut self, revert: ScrollAccountRevert) -> bool { + self.status = revert.previous_status; + + match revert.account { + ScrollAccountInfoRevert::DoNothing => (), + ScrollAccountInfoRevert::DeleteIt => { + self.info = None; + if self.original_info.is_none() { + self.storage = HashMap::default(); + return true; + } + // set all storage to zero but preserve original values. + self.storage.iter_mut().for_each(|(_, v)| { + v.present_value = U256::ZERO; + }); + return false; + } + ScrollAccountInfoRevert::RevertTo(info) => self.info = Some(info), + }; + // revert storage + for (key, slot) in revert.storage { + match slot { + RevertToSlot::Some(value) => { + // Don't overwrite original values if present + // if storage is not present set original value as current value. + self.storage + .entry(key) + .or_insert_with(|| StorageSlot::new(value)) + .present_value = value; + } + RevertToSlot::Destroyed => { + // if it was destroyed this means that storage was created and we need to remove + // it. + self.storage.remove(&key); + } + } + } + false + } +} diff --git a/crates/scroll/revm/src/states/changes.rs b/crates/scroll/revm/src/states/changes.rs new file mode 100644 index 00000000000..5343d47c9e0 --- /dev/null +++ b/crates/scroll/revm/src/states/changes.rs @@ -0,0 +1,67 @@ +use crate::states::ScrollAccountInfo; +use reth_scroll_primitives::ScrollPostExecutionContext; +use revm::{ + db::states::{PlainStateReverts, PlainStorageChangeset, PlainStorageRevert, StateChangeset}, + primitives::{Address, Bytecode, B256}, +}; + +/// Code copy equivalent of the [`StateChangeset`] to accommodate for the [`ScrollAccountInfo`]. +#[derive(Debug)] +pub struct ScrollStateChangeset { + /// Vector of **not** sorted accounts information. + pub accounts: Vec<(Address, Option)>, + /// Vector of **not** sorted storage. + pub storage: Vec, + /// Vector of contracts by bytecode hash. **not** sorted. + pub contracts: Vec<(B256, Bytecode)>, +} + +impl From<(StateChangeset, &ScrollPostExecutionContext)> for ScrollStateChangeset { + fn from((changeset, context): (StateChangeset, &ScrollPostExecutionContext)) -> Self { + Self { + accounts: changeset + .accounts + .into_iter() + .map(|(add, acc)| (add, acc.map(|a| (a, context).into()))) + .collect(), + storage: changeset.storage, + contracts: changeset.contracts, + } + } +} + +/// Code copy of the [`PlainStateReverts`] to accommodate for [`ScrollAccountInfo`]. +#[derive(Clone, Debug, Default)] +pub struct ScrollPlainStateReverts { + /// Vector of account with removed contracts bytecode + /// + /// Note: If [`ScrollAccountInfo`] is None means that account needs to be removed. + pub accounts: Vec)>>, + /// Vector of storage with its address. + pub storage: Vec>, +} + +impl From<(PlainStateReverts, &ScrollPostExecutionContext)> for ScrollPlainStateReverts { + fn from((reverts, context): (PlainStateReverts, &ScrollPostExecutionContext)) -> Self { + Self { + accounts: reverts + .accounts + .into_iter() + .map(|accounts| { + accounts + .into_iter() + .map(|(add, acc)| (add, acc.map(|a| (a, context).into()))) + .collect() + }) + .collect(), + storage: reverts.storage, + } + } +} + +impl ScrollPlainStateReverts { + /// Constructs new [`ScrollPlainStateReverts`] with pre-allocated capacity. + pub fn with_capacity(capacity: usize) -> Self { + Self { accounts: Vec::with_capacity(capacity), storage: Vec::with_capacity(capacity) } + } +} diff --git a/crates/scroll/revm/src/states/mod.rs b/crates/scroll/revm/src/states/mod.rs new file mode 100644 index 00000000000..c6f9e969f80 --- /dev/null +++ b/crates/scroll/revm/src/states/mod.rs @@ -0,0 +1,16 @@ +//! Scroll `revm` states types redefinitions. + +pub use account_info::ScrollAccountInfo; +mod account_info; + +pub use bundle::{ScrollBundleBuilder, ScrollBundleState}; +mod bundle; + +pub use bundle_account::ScrollBundleAccount; +mod bundle_account; + +pub use changes::{ScrollPlainStateReverts, ScrollStateChangeset}; +mod changes; + +pub use reverts::{ScrollAccountInfoRevert, ScrollAccountRevert, ScrollReverts}; +mod reverts; diff --git a/crates/scroll/revm/src/states/reverts.rs b/crates/scroll/revm/src/states/reverts.rs new file mode 100644 index 00000000000..750796eb9f3 --- /dev/null +++ b/crates/scroll/revm/src/states/reverts.rs @@ -0,0 +1,145 @@ +use crate::states::{changes::ScrollPlainStateReverts, ScrollAccountInfo}; +use reth_scroll_primitives::ScrollPostExecutionContext; +use revm::{ + db::{ + states::{reverts::AccountInfoRevert, PlainStorageRevert}, + AccountRevert, AccountStatus, RevertToSlot, + }, + primitives::{map::HashMap, Address, U256}, +}; +use std::ops::{Deref, DerefMut}; + +/// Code copy of a [`revm::db::states::reverts::Reverts`] compatible with the +/// [`crate::states::ScrollBundleState`]. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ScrollReverts(Vec>); + +impl Deref for ScrollReverts { + type Target = Vec>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for ScrollReverts { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl ScrollReverts { + /// Create new reverts + pub const fn new(reverts: Vec>) -> Self { + Self(reverts) + } + + /// Extend reverts with other reverts. + pub fn extend(&mut self, other: Self) { + self.0.extend(other.0); + } + + /// Sort account inside transition by their address. + pub fn sort(&mut self) { + for revert in &mut self.0 { + revert.sort_by_key(|(address, _)| *address); + } + } + + /// Consume reverts and create plain state reverts. + /// + /// Note that account are sorted by address. + pub fn into_plain_state_reverts(mut self) -> ScrollPlainStateReverts { + let mut state_reverts = ScrollPlainStateReverts::with_capacity(self.0.len()); + for reverts in self.0.drain(..) { + // pessimistically pre-allocate assuming _all_ accounts changed. + let mut accounts = Vec::with_capacity(reverts.len()); + let mut storage = Vec::with_capacity(reverts.len()); + for (address, revert_account) in reverts { + match revert_account.account { + ScrollAccountInfoRevert::RevertTo(acc) => accounts.push((address, Some(acc))), + ScrollAccountInfoRevert::DeleteIt => accounts.push((address, None)), + ScrollAccountInfoRevert::DoNothing => (), + } + if revert_account.wipe_storage || !revert_account.storage.is_empty() { + storage.push(PlainStorageRevert { + address, + wiped: revert_account.wipe_storage, + storage_revert: revert_account.storage.into_iter().collect::>(), + }); + } + } + state_reverts.accounts.push(accounts); + state_reverts.storage.push(storage); + } + state_reverts + } +} + +/// Code copy of a [`AccountRevert`] compatible with [`ScrollReverts`]. +#[derive(Clone, Default, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ScrollAccountRevert { + /// Account info revert + pub account: ScrollAccountInfoRevert, + /// Storage revert + pub storage: HashMap, + /// Previous status + pub previous_status: AccountStatus, + /// If true wipes storage + pub wipe_storage: bool, +} + +impl From<(AccountRevert, &ScrollPostExecutionContext)> for ScrollAccountRevert { + fn from((account, context): (AccountRevert, &ScrollPostExecutionContext)) -> Self { + Self { + account: (account.account, context).into(), + storage: account.storage, + previous_status: account.previous_status, + wipe_storage: account.wipe_storage, + } + } +} + +impl ScrollAccountRevert { + /// The approximate size of changes needed to store this account revert. + /// `1 + storage_reverts_len` + pub fn size_hint(&self) -> usize { + 1 + self.storage.len() + } + + /// Returns `true` if there is nothing to revert, + /// by checking that: + /// * both account info and storage have been left untouched + /// * we don't need to wipe storage + pub fn is_empty(&self) -> bool { + self.account == ScrollAccountInfoRevert::DoNothing && + self.storage.is_empty() && + !self.wipe_storage + } +} + +/// Code copy of a [`AccountInfoRevert`] compatible with the +/// [`ScrollAccountInfo`]. +#[derive(Clone, Default, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum ScrollAccountInfoRevert { + #[default] + /// Nothing changed + DoNothing, + /// Account was created and on revert we need to remove it with all storage. + DeleteIt, + /// Account was changed and on revert we need to put old state. + RevertTo(ScrollAccountInfo), +} + +impl From<(AccountInfoRevert, &ScrollPostExecutionContext)> for ScrollAccountInfoRevert { + fn from((account, context): (AccountInfoRevert, &ScrollPostExecutionContext)) -> Self { + match account { + AccountInfoRevert::DoNothing => Self::DoNothing, + AccountInfoRevert::DeleteIt => Self::DeleteIt, + AccountInfoRevert::RevertTo(account) => Self::RevertTo((account, context).into()), + } + } +} diff --git a/crates/scroll/storage/Cargo.toml b/crates/scroll/storage/Cargo.toml new file mode 100644 index 00000000000..9c5cc3744d5 --- /dev/null +++ b/crates/scroll/storage/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "reth-scroll-storage" +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] +# alloy +alloy-primitives.workspace = true + +# reth +reth-revm.workspace = true +reth-storage-errors.workspace = true + +# scroll +reth-scroll-primitives.workspace = true +reth-scroll-revm.workspace = true + +[dev-dependencies] +eyre.workspace = true +reth-codecs = { workspace = true, features = ["test-utils"] } +reth-primitives-traits.workspace = true +reth-revm = { workspace = true, features = ["test-utils"] } + +[features] +scroll = [ + "reth-primitives-traits/scroll", + "reth-scroll-revm/scroll", +] diff --git a/crates/scroll/storage/src/lib.rs b/crates/scroll/storage/src/lib.rs new file mode 100644 index 00000000000..1810369a809 --- /dev/null +++ b/crates/scroll/storage/src/lib.rs @@ -0,0 +1,185 @@ +//! Scroll storage implementation. + +#![cfg_attr(all(not(test), feature = "scroll"), warn(unused_crate_dependencies))] +// The `scroll` feature must be enabled to use this crate. +#![cfg(feature = "scroll")] + +use alloy_primitives::{Address, B256, U256}; +use reth_revm::{database::EvmStateProvider, primitives::Bytecode, Database, DatabaseRef}; +use reth_scroll_primitives::{AccountExtension, ScrollPostExecutionContext}; +use reth_scroll_revm::shared::AccountInfo; +use reth_storage_errors::provider::ProviderError; +use std::ops::{Deref, DerefMut}; + +/// A similar construct as `StateProviderDatabase` which captures additional Scroll context for +/// touched accounts during execution. +#[derive(Clone, Debug)] +pub struct ScrollStateProviderDatabase { + /// Scroll post execution context. + pub post_execution_context: ScrollPostExecutionContext, + /// The database. + pub db: DB, +} + +impl Deref for ScrollStateProviderDatabase { + type Target = DB; + + fn deref(&self) -> &Self::Target { + &self.db + } +} + +impl DerefMut for ScrollStateProviderDatabase { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.db + } +} + +impl ScrollStateProviderDatabase { + /// Creates a [`ScrollStateProviderDatabase`] from the provided DB. + pub fn new(db: DB) -> Self { + Self { db, post_execution_context: Default::default() } + } + + /// Consume [`ScrollStateProviderDatabase`] and return inner [`EvmStateProvider`]. + pub fn into_inner(self) -> DB { + self.db + } +} + +impl Database for ScrollStateProviderDatabase { + type Error = ProviderError; + + /// Retrieves basic account information for a given address. Caches the Scroll account extension + /// for the touched account if it has bytecode. + fn basic(&mut self, address: Address) -> Result, Self::Error> { + let Some(account) = self.db.basic_account(address)? else { return Ok(None) }; + let Some(code_hash) = account.bytecode_hash else { return Ok(Some(account.into())) }; + + if let Some(AccountExtension { code_size, poseidon_code_hash: Some(hash) }) = + account.account_extension + { + self.post_execution_context.entry(code_hash).or_insert((code_size, hash)); + } + + Ok(Some(account.into())) + } + + fn code_by_hash(&mut self, code_hash: B256) -> Result { + Ok(self.db.bytecode_by_hash(code_hash)?.unwrap_or_default().0) + } + + fn storage(&mut self, address: Address, index: U256) -> Result { + Ok(self.db.storage(address, B256::new(index.to_be_bytes()))?.unwrap_or_default()) + } + + fn block_hash(&mut self, number: u64) -> Result { + Ok(self.db.block_hash(number)?.unwrap_or_default()) + } +} + +impl DatabaseRef for ScrollStateProviderDatabase { + type Error = ProviderError; + + fn basic_ref(&self, address: Address) -> Result, Self::Error> { + Ok(self.db.basic_account(address)?.map(Into::into)) + } + + fn code_by_hash_ref(&self, code_hash: B256) -> Result { + Ok(self.db.bytecode_by_hash(code_hash)?.unwrap_or_default().0) + } + + fn storage_ref(&self, address: Address, index: U256) -> Result { + Ok(self.db.storage(address, B256::new(index.to_be_bytes()))?.unwrap_or_default()) + } + + fn block_hash_ref(&self, number: u64) -> Result { + Ok(self.db.block_hash(number)?.unwrap_or_default()) + } +} + +#[cfg(test)] +mod tests { + use crate::ScrollStateProviderDatabase; + use alloy_primitives::{keccak256, Address, Bytes, U256}; + 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}; + + #[test] + fn test_ensure_account_backwards_compatibility() { + // See `reth-codecs/src/test-utils` for details. + assert_eq!(Account::bitflag_encoded_bytes(), 2); + assert_eq!(AccountExtension::bitflag_encoded_bytes(), 1); + + // In case of failure, refer to the documentation of the + // [`validate_bitflag_backwards_compat`] macro for detailed instructions on handling + // it. + validate_bitflag_backwards_compat!(Account, UnusedBits::NotZero); + validate_bitflag_backwards_compat!(AccountExtension, UnusedBits::NotZero); + } + + #[test] + fn test_scroll_post_execution_context() -> eyre::Result<()> { + let mut db = StateProviderTest::default(); + + // insert an eoa in the db + let eoa_address = Address::random(); + let eoa = Account { + nonce: 0, + balance: U256::MAX, + bytecode_hash: None, + account_extension: Some(AccountExtension::empty()), + }; + db.insert_account(eoa_address, eoa, None, Default::default()); + + // insert a contract account in the db + let contract_address = Address::random(); + let bytecode = Bytes::copy_from_slice(&[0x0, 0x1, 0x2, 0x3, 0x4, 0x5]); + let bytecode_hash = keccak256(&bytecode); + let contract = Account { + nonce: 0, + balance: U256::MAX, + bytecode_hash: Some(bytecode_hash), + account_extension: Some(AccountExtension::from_bytecode(&bytecode)), + }; + db.insert_account(contract_address, contract, Some(bytecode.clone()), Default::default()); + + // insert an empty contract account in the db + let empty_contract_address = Address::random(); + let empty_bytecode = Bytes::copy_from_slice(&[]); + let empty_contract = Account { + nonce: 0, + balance: U256::MAX, + bytecode_hash: None, + account_extension: Some(AccountExtension::empty()), + }; + db.insert_account( + empty_contract_address, + empty_contract, + Some(empty_bytecode), + Default::default(), + ); + + let mut provider = ScrollStateProviderDatabase::new(db); + + // check eoa is in db + let _ = provider.basic(eoa_address)?.unwrap(); + // check contract is in db + let _ = provider.basic(contract_address)?.unwrap(); + // check empty contract is in db + let _ = provider.basic(empty_contract_address)?.unwrap(); + + // check provider context only contains contract + let post_execution_context = provider.post_execution_context; + assert_eq!(post_execution_context.len(), 1); + + // check post execution context is correct for contract + let (code_size, poseidon_code_hash) = post_execution_context.get(&bytecode_hash).unwrap(); + assert_eq!(*code_size, 6); + assert_eq!(*poseidon_code_hash, hash_code(&bytecode)); + + Ok(()) + } +} diff --git a/crates/stages/stages/Cargo.toml b/crates/stages/stages/Cargo.toml index eedd5f9ca41..74ad23fbc7a 100644 --- a/crates/stages/stages/Cargo.toml +++ b/crates/stages/stages/Cargo.toml @@ -39,6 +39,9 @@ reth-trie-db = { workspace = true, features = ["metrics"] } reth-testing-utils = { workspace = true, optional = true } +# scroll +reth-scroll-primitives = { workspace = true, optional = true } + alloy-primitives.workspace = true alloy-consensus.workspace = true @@ -74,6 +77,8 @@ reth-trie = { workspace = true, features = ["test-utils"] } reth-provider = { workspace = true, features = ["test-utils"] } reth-network-peers.workspace = true +reth-scroll-primitives.workspace = true + alloy-rlp.workspace = true itertools.workspace = true tokio = { workspace = true, features = ["rt", "sync", "macros"] } @@ -114,6 +119,7 @@ test-utils = [ "reth-trie/test-utils", "reth-prune-types/test-utils", ] +scroll = ["reth-scroll-primitives"] [[bench]] name = "criterion" diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index 16234ad483f..a62d947e722 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -880,13 +880,26 @@ mod tests { db_tx .put::( acc1, - Account { nonce: 0, balance: U256::ZERO, bytecode_hash: Some(code_hash) }, + Account { + nonce: 0, + balance: U256::ZERO, + bytecode_hash: Some(code_hash), + // TODO (scroll): use `from_bytecode` + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }, ) .unwrap(); db_tx .put::( acc2, - Account { nonce: 0, balance, bytecode_hash: None }, + Account { + nonce: 0, + balance, + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }, ) .unwrap(); db_tx.put::(code_hash, Bytecode::new_raw(code.to_vec().into())).unwrap(); @@ -936,19 +949,29 @@ mod tests { // check post state let account1 = address!("1000000000000000000000000000000000000000"); - let account1_info = - Account { balance: U256::ZERO, nonce: 0x00, bytecode_hash: Some(code_hash) }; + let account1_info = Account { + balance: U256::ZERO, + nonce: 0x00, + bytecode_hash: Some(code_hash), + // TODO (scroll): use `from_bytecode`. + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }; let account2 = address!("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"); let account2_info = Account { balance: U256::from(0x1bc16d674ece94bau128), nonce: 0x00, bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), }; let account3 = address!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b"); let account3_info = Account { balance: U256::from(0x3635c9adc5de996b46u128), nonce: 0x01, bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), }; // assert accounts @@ -1024,9 +1047,22 @@ mod tests { let db_tx = provider.tx_ref(); let acc1 = address!("1000000000000000000000000000000000000000"); - let acc1_info = Account { nonce: 0, balance: U256::ZERO, bytecode_hash: Some(code_hash) }; + let acc1_info = Account { + nonce: 0, + balance: U256::ZERO, + bytecode_hash: Some(code_hash), + // TODO (scroll): use `from_bytecode`. + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }; let acc2 = address!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b"); - let acc2_info = Account { nonce: 0, balance, bytecode_hash: None }; + let acc2_info = Account { + nonce: 0, + balance, + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }; db_tx.put::(acc1, acc1_info).unwrap(); db_tx.put::(acc2, acc2_info).unwrap(); @@ -1143,9 +1179,21 @@ mod tests { let code_hash = keccak256(code); // pre state - let caller_info = Account { nonce: 0, balance, bytecode_hash: None }; - let destroyed_info = - Account { nonce: 0, balance: U256::ZERO, bytecode_hash: Some(code_hash) }; + let caller_info = Account { + nonce: 0, + balance, + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }; + let destroyed_info = Account { + nonce: 0, + balance: U256::ZERO, + bytecode_hash: Some(code_hash), + // TODO (scroll): use `from_bytecode`. + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }; // set account let provider = test_db.factory.provider_rw().unwrap(); @@ -1204,7 +1252,9 @@ mod tests { Account { nonce: 0, balance: U256::from(0x1bc16d674eca30a0u64), - bytecode_hash: None + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), } ), ( @@ -1212,7 +1262,9 @@ mod tests { Account { nonce: 1, balance: U256::from(0xde0b6b3a761cf60u64), - bytecode_hash: None + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), } ) ] diff --git a/crates/stages/stages/src/stages/hashing_account.rs b/crates/stages/stages/src/stages/hashing_account.rs index 1ca0e1aa132..8bdaf6ce557 100644 --- a/crates/stages/stages/src/stages/hashing_account.rs +++ b/crates/stages/stages/src/stages/hashing_account.rs @@ -105,6 +105,8 @@ impl AccountHashingStage { nonce: nonce - 1, balance: balance - U256::from(1), bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), }; let acc_before_tx = AccountBeforeTx { address: *addr, info: Some(prev_acc) }; acc_changeset_cursor.append(t, acc_before_tx)?; @@ -399,6 +401,10 @@ mod tests { nonce: nonce - 1, balance: balance - U256::from(1), bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some( + reth_scroll_primitives::AccountExtension::empty(), + ), }; let hashed_addr = keccak256(address); if let Some((_, acc)) = hashed_acc_cursor.seek_exact(hashed_addr)? { diff --git a/crates/stages/stages/src/stages/mod.rs b/crates/stages/stages/src/stages/mod.rs index 9d7cc685a7e..7f351efc60e 100644 --- a/crates/stages/stages/src/stages/mod.rs +++ b/crates/stages/stages/src/stages/mod.rs @@ -117,7 +117,14 @@ mod tests { .tx_ref() .put::( address!("1000000000000000000000000000000000000000"), - Account { nonce: 0, balance: U256::ZERO, bytecode_hash: Some(code_hash) }, + Account { + nonce: 0, + balance: U256::ZERO, + bytecode_hash: Some(code_hash), + // TODO (scroll): use `from_bytecode`. + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }, ) .unwrap(); provider_rw @@ -128,6 +135,8 @@ mod tests { nonce: 0, balance: U256::from(0x3635c9adc5dea00000u128), bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), }, ) .unwrap(); diff --git a/crates/storage/db-common/Cargo.toml b/crates/storage/db-common/Cargo.toml index 9e4954357f8..662d1fdb029 100644 --- a/crates/storage/db-common/Cargo.toml +++ b/crates/storage/db-common/Cargo.toml @@ -27,6 +27,9 @@ reth-node-types.workspace = true alloy-genesis.workspace = true alloy-primitives.workspace = true +# scroll +reth-scroll-primitives = { workspace = true, optional = true } + # misc eyre.workspace = true thiserror.workspace = true @@ -46,3 +49,6 @@ alloy-consensus.workspace = true [lints] workspace = true + +[features] +scroll = ["reth-scroll-primitives"] diff --git a/crates/storage/db-common/src/init.rs b/crates/storage/db-common/src/init.rs index e14796d2686..48fb946a111 100644 --- a/crates/storage/db-common/src/init.rs +++ b/crates/storage/db-common/src/init.rs @@ -214,6 +214,12 @@ where nonce: account.nonce.unwrap_or_default(), balance: account.balance, bytecode_hash, + #[cfg(feature = "scroll")] + account_extension: Some( + reth_scroll_primitives::AccountExtension::from_bytecode( + account.code.as_ref().unwrap_or_default(), + ), + ), }), storage, ), diff --git a/crates/storage/db/Cargo.toml b/crates/storage/db/Cargo.toml index 6042b5faa81..7400d94a89c 100644 --- a/crates/storage/db/Cargo.toml +++ b/crates/storage/db/Cargo.toml @@ -115,6 +115,7 @@ arbitrary = [ ] optimism = ["reth-primitives/optimism", "reth-db-api/optimism"] disable-lock = [] +scroll = [] [[bench]] name = "hash_keys" diff --git a/crates/storage/db/src/implementation/mdbx/mod.rs b/crates/storage/db/src/implementation/mdbx/mod.rs index 10f3b228230..1d0be757466 100644 --- a/crates/storage/db/src/implementation/mdbx/mod.rs +++ b/crates/storage/db/src/implementation/mdbx/mod.rs @@ -1172,6 +1172,8 @@ mod tests { nonce: 18446744073709551615, bytecode_hash: Some(B256::random()), balance: U256::MAX, + #[cfg(feature = "scroll")] + account_extension: Some((10u64, B256::random()).into()), }; let key = Address::from_str("0xa2c122be93b0074270ebee7f6b7292c7deb45047") .expect(ERROR_ETH_ADDRESS); diff --git a/crates/storage/provider/Cargo.toml b/crates/storage/provider/Cargo.toml index 399e3e000b9..499536eb2cb 100644 --- a/crates/storage/provider/Cargo.toml +++ b/crates/storage/provider/Cargo.toml @@ -41,6 +41,9 @@ alloy-rpc-types-engine.workspace = true alloy-consensus.workspace = true revm.workspace = true +# scroll +reth-scroll-primitives = { workspace = true, optional = true } + # optimism reth-optimism-primitives = { workspace = true, optional = true } @@ -78,6 +81,8 @@ reth-trie = { workspace = true, features = ["test-utils"] } reth-testing-utils.workspace = true reth-ethereum-engine-primitives.workspace = true +reth-scroll-primitives.workspace = true + parking_lot.workspace = true tempfile.workspace = true assert_matches.workspace = true @@ -128,3 +133,4 @@ test-utils = [ "reth-prune-types/test-utils", "reth-stages-types/test-utils", ] +scroll = ["reth-scroll-primitives"] diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index 29ba70e2049..39a71b92443 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -542,13 +542,49 @@ mod tests { ) .unwrap(); - let acc_plain = Account { nonce: 100, balance: U256::ZERO, bytecode_hash: None }; - let acc_at15 = Account { nonce: 15, balance: U256::ZERO, bytecode_hash: None }; - let acc_at10 = Account { nonce: 10, balance: U256::ZERO, bytecode_hash: None }; - let acc_at7 = Account { nonce: 7, balance: U256::ZERO, bytecode_hash: None }; - let acc_at3 = Account { nonce: 3, balance: U256::ZERO, bytecode_hash: None }; - - let higher_acc_plain = Account { nonce: 4, balance: U256::ZERO, bytecode_hash: None }; + let acc_plain = Account { + nonce: 100, + balance: U256::ZERO, + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }; + let acc_at15 = Account { + nonce: 15, + balance: U256::ZERO, + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }; + let acc_at10 = Account { + nonce: 10, + balance: U256::ZERO, + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }; + let acc_at7 = Account { + nonce: 7, + balance: U256::ZERO, + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }; + let acc_at3 = Account { + nonce: 3, + balance: U256::ZERO, + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }; + + let higher_acc_plain = Account { + nonce: 4, + balance: U256::ZERO, + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }; // setup tx.put::(1, AccountBeforeTx { address: ADDRESS, info: None }) diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 9661ab2057c..772e7a32967 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -83,7 +83,13 @@ impl ExtendedAccount { /// Create new instance of extended account pub fn new(nonce: u64, balance: U256) -> Self { Self { - account: Account { nonce, balance, bytecode_hash: None }, + account: Account { + nonce, + balance, + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }, bytecode: None, storage: Default::default(), } @@ -91,6 +97,11 @@ impl ExtendedAccount { /// Set bytecode and bytecode hash on the extended account pub fn with_bytecode(mut self, bytecode: Bytes) -> Self { + #[cfg(feature = "scroll")] + { + self.account.account_extension = + Some(reth_scroll_primitives::AccountExtension::from_bytecode(&bytecode)); + } let hash = keccak256(&bytecode); self.account.bytecode_hash = Some(hash); self.bytecode = Some(Bytecode::new_raw(bytecode)); diff --git a/crates/storage/provider/src/writer/mod.rs b/crates/storage/provider/src/writer/mod.rs index 1c3894e9cfd..e329e44bb5b 100644 --- a/crates/storage/provider/src/writer/mod.rs +++ b/crates/storage/provider/src/writer/mod.rs @@ -1413,7 +1413,13 @@ mod tests { type PreState = BTreeMap)>; let mut prestate: PreState = (0..10) .map(|key| { - let account = Account { nonce: 1, balance: U256::from(key), bytecode_hash: None }; + let account = Account { + nonce: 1, + balance: U256::from(key), + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }; let storage = (1..11).map(|key| (B256::with_last_byte(key), U256::from(key))).collect(); (Address::with_last_byte(key), (account, storage)) @@ -1544,8 +1550,13 @@ mod tests { assert_state_root(&state, &prestate, "changed nonce"); // recreate account 1 - let account1_new = - Account { nonce: 56, balance: U256::from(123), bytecode_hash: Some(B256::random()) }; + let account1_new = Account { + nonce: 56, + balance: U256::from(123), + bytecode_hash: Some(B256::random()), + #[cfg(feature = "scroll")] + account_extension: Some((10, B256::random()).into()), + }; prestate.insert(address1, (account1_new, BTreeMap::default())); state.commit(HashMap::from_iter([( address1, diff --git a/crates/trie/common/Cargo.toml b/crates/trie/common/Cargo.toml index 0616e259710..c0998764e40 100644 --- a/crates/trie/common/Cargo.toml +++ b/crates/trie/common/Cargo.toml @@ -22,6 +22,8 @@ alloy-consensus.workspace = true alloy-genesis.workspace = true revm-primitives.workspace = true +reth-scroll-primitives = { workspace = true, optional = true } + bytes.workspace = true derive_more.workspace = true serde.workspace = true @@ -40,6 +42,8 @@ proptest-arbitrary-interop.workspace = true hash-db = "=0.15.2" plain_hasher = "0.2" +reth-scroll-primitives.workspace = true + [features] test-utils = [ "dep:plain_hasher", @@ -56,5 +60,7 @@ arbitrary = [ "alloy-primitives/arbitrary", "nybbles/arbitrary", "revm-primitives/arbitrary", - "reth-codecs/arbitrary" + "reth-codecs/arbitrary", + "reth-scroll-primitives?/arbitrary" ] +scroll = ["reth-scroll-primitives"] diff --git a/crates/trie/common/src/account.rs b/crates/trie/common/src/account.rs index 0808837063c..b6666cfb7d4 100644 --- a/crates/trie/common/src/account.rs +++ b/crates/trie/common/src/account.rs @@ -135,7 +135,11 @@ mod tests { Account { nonce: 10, balance: U256::from(1000), - bytecode_hash: Some(keccak256([0x60, 0x61])) + bytecode_hash: Some(keccak256([0x60, 0x61])), + #[cfg(feature = "scroll")] + account_extension: Some( + reth_scroll_primitives::AccountExtension::from_bytecode(&[0x60, 0x61]) + ) }, expected_storage_root )), diff --git a/crates/trie/common/src/proofs.rs b/crates/trie/common/src/proofs.rs index f6eaf3960ec..67c04e0cb75 100644 --- a/crates/trie/common/src/proofs.rs +++ b/crates/trie/common/src/proofs.rs @@ -55,6 +55,11 @@ impl MultiProof { nonce: account.nonce, bytecode_hash: (account.code_hash != KECCAK_EMPTY) .then_some(account.code_hash), + // TODO (scroll): set the extension to the correct value. + #[cfg(feature = "scroll")] + account_extension: Some( + reth_scroll_primitives::AccountExtension::empty(), + ), }) } } diff --git a/crates/trie/db/Cargo.toml b/crates/trie/db/Cargo.toml index 55fa9a851b1..370ee6a33ea 100644 --- a/crates/trie/db/Cargo.toml +++ b/crates/trie/db/Cargo.toml @@ -55,6 +55,8 @@ reth-trie = { workspace = true, features = ["test-utils"] } alloy-consensus.workspace = true +reth-scroll-primitives.workspace = true + # trie triehash = "0.8" @@ -86,3 +88,4 @@ test-utils = [ "reth-trie/test-utils", "revm/test-utils" ] +scroll = [] diff --git a/crates/trie/db/src/state.rs b/crates/trie/db/src/state.rs index 6e2cea5051d..9b07a95f85a 100644 --- a/crates/trie/db/src/state.rs +++ b/crates/trie/db/src/state.rs @@ -87,7 +87,13 @@ pub trait DatabaseStateRoot<'a, TX>: Sized { /// let mut hashed_state = HashedPostState::default(); /// hashed_state.accounts.insert( /// [0x11; 32].into(), - /// Some(Account { nonce: 1, balance: U256::from(10), bytecode_hash: None }), + /// Some(Account { + /// nonce: 1, + /// balance: U256::from(10), + /// bytecode_hash: None, + /// // TODO (scroll): remove at last Scroll `Account` related PR. + /// ..Default::default() + /// }), /// ); /// /// // Calculate the state root diff --git a/crates/trie/db/tests/proof.rs b/crates/trie/db/tests/proof.rs index 79a2ce96fce..e03be116747 100644 --- a/crates/trie/db/tests/proof.rs +++ b/crates/trie/db/tests/proof.rs @@ -199,7 +199,10 @@ fn holesky_deposit_contract_proof() { info: Some(Account { balance: U256::ZERO, nonce: 0, - bytecode_hash: Some(B256::from_str("0x2034f79e0e33b0ae6bef948532021baceb116adf2616478703bec6b17329f1cc").unwrap()) + bytecode_hash: Some(B256::from_str("0x2034f79e0e33b0ae6bef948532021baceb116adf2616478703bec6b17329f1cc").unwrap()), + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()) + }), storage_root: B256::from_str("0x556a482068355939c95a3412bdb21213a301483edb1b64402fb66ac9f3583599").unwrap(), proof: convert_to_proof([ diff --git a/crates/trie/db/tests/trie.rs b/crates/trie/db/tests/trie.rs index aee26436479..133dd4556a3 100644 --- a/crates/trie/db/tests/trie.rs +++ b/crates/trie/db/tests/trie.rs @@ -146,14 +146,26 @@ fn test_empty_account() { ( Address::random(), ( - Account { nonce: 0, balance: U256::from(0), bytecode_hash: None }, + Account { + nonce: 0, + balance: U256::from(0), + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }, BTreeMap::from([(B256::with_last_byte(0x4), U256::from(12))]), ), ), ( Address::random(), ( - Account { nonce: 0, balance: U256::from(0), bytecode_hash: None }, + Account { + nonce: 0, + balance: U256::from(0), + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }, BTreeMap::default(), ), ), @@ -164,6 +176,10 @@ fn test_empty_account() { nonce: 155, balance: U256::from(414241124u32), bytecode_hash: Some(keccak256("test")), + #[cfg(feature = "scroll")] + account_extension: Some( + reth_scroll_primitives::AccountExtension::from_bytecode(b"test"), + ), }, BTreeMap::from([ (B256::ZERO, U256::from(3)), @@ -187,6 +203,8 @@ fn test_empty_storage_root() { nonce: 155, balance: U256::from(414241124u32), bytecode_hash: Some(keccak256(code)), + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::from_bytecode(&code)), }; insert_account(tx.tx_ref(), address, account, &Default::default()); tx.commit().unwrap(); @@ -211,6 +229,8 @@ fn test_storage_root() { nonce: 155, balance: U256::from(414241124u32), bytecode_hash: Some(keccak256(code)), + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::from_bytecode(&code)), }; insert_account(tx.tx_ref(), address, account, &storage); @@ -357,7 +377,13 @@ fn account_and_storage_trie() { // Insert first account let key1 = B256::from_str("b000000000000000000000000000000000000000000000000000000000000000").unwrap(); - let account1 = Account { nonce: 0, balance: U256::from(3).mul(ether), bytecode_hash: None }; + let account1 = Account { + nonce: 0, + balance: U256::from(3).mul(ether), + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }; hashed_account_cursor.upsert(key1, account1).unwrap(); hash_builder.add_leaf(Nibbles::unpack(key1), &encode_account(account1, None)); @@ -377,8 +403,13 @@ fn account_and_storage_trie() { assert_eq!(key3[1], 0x41); let code_hash = B256::from_str("5be74cad16203c4905c068b012a2e9fb6d19d036c410f16fd177f337541440dd").unwrap(); - let account3 = - Account { nonce: 0, balance: U256::from(2).mul(ether), bytecode_hash: Some(code_hash) }; + let account3 = Account { + nonce: 0, + balance: U256::from(2).mul(ether), + bytecode_hash: Some(code_hash), + #[cfg(feature = "scroll")] + account_extension: Some((10, B256::random()).into()), + }; hashed_account_cursor.upsert(key3, account3).unwrap(); for (hashed_slot, value) in storage { if hashed_storage_cursor @@ -460,7 +491,13 @@ fn account_and_storage_trie() { let address4b = Address::from_str("4f61f2d5ebd991b85aa1677db97307caf5215c91").unwrap(); let key4b = keccak256(address4b); assert_eq!(key4b.0[0], key4a.0[0]); - let account4b = Account { nonce: 0, balance: U256::from(5).mul(ether), bytecode_hash: None }; + let account4b = Account { + nonce: 0, + balance: U256::from(5).mul(ether), + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::empty()), + }; hashed_account_cursor.upsert(key4b, account4b).unwrap(); let mut prefix_set = PrefixSetMut::default(); @@ -725,7 +762,13 @@ fn extension_node_storage_trie( fn extension_node_trie( tx: &DatabaseProviderRW>, N>, ) -> B256 { - let a = Account { nonce: 0, balance: U256::from(1u64), bytecode_hash: Some(B256::random()) }; + let a = Account { + nonce: 0, + balance: U256::from(1u64), + bytecode_hash: Some(B256::random()), + #[cfg(feature = "scroll")] + account_extension: Some((10, B256::random()).into()), + }; let val = encode_account(a, None); let mut hashed_accounts = tx.tx_ref().cursor_write::().unwrap(); diff --git a/deny.toml b/deny.toml index 8d0807f9de5..6dc51dba0a9 100644 --- a/deny.toml +++ b/deny.toml @@ -67,6 +67,11 @@ 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. +[licenses.private] +ignore = true +ignore-sources = ["https://github.com/scroll-tech/poseidon-bn254", "https://github.com/scroll-tech/bn254"] + [[licenses.clarify]] name = "ring" expression = "LicenseRef-ring" @@ -93,4 +98,7 @@ allow-git = [ "https://github.com/foundry-rs/block-explorers", "https://github.com/bluealloy/revm", "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" ] diff --git a/testing/ef-tests/Cargo.toml b/testing/ef-tests/Cargo.toml index 2fc0c751244..eb754cba779 100644 --- a/testing/ef-tests/Cargo.toml +++ b/testing/ef-tests/Cargo.toml @@ -18,6 +18,7 @@ asm-keccak = [ "alloy-primitives/asm-keccak", "revm/asm-keccak", ] +scroll = [] [dependencies] reth-chainspec.workspace = true @@ -35,6 +36,8 @@ reth-revm = { workspace = true, features = ["std"] } revm = { workspace = true, features = ["secp256k1", "blst", "c-kzg"] } +reth-scroll-primitives = { workspace = true, optional = true } + alloy-rlp.workspace = true alloy-primitives.workspace = true alloy-eips.workspace = true diff --git a/testing/ef-tests/src/models.rs b/testing/ef-tests/src/models.rs index 292b32e8ce0..3e3a25e45a1 100644 --- a/testing/ef-tests/src/models.rs +++ b/testing/ef-tests/src/models.rs @@ -162,6 +162,10 @@ impl State { balance: account.balance, nonce: account.nonce.to::(), bytecode_hash: code_hash, + #[cfg(feature = "scroll")] + account_extension: Some(reth_scroll_primitives::AccountExtension::from_bytecode( + &account.code, + )), }; tx.put::(address, reth_account)?; tx.put::(hashed_address, reth_account)?; diff --git a/testing/testing-utils/Cargo.toml b/testing/testing-utils/Cargo.toml index 3e0f58a7bd0..6d07043adc3 100644 --- a/testing/testing-utils/Cargo.toml +++ b/testing/testing-utils/Cargo.toml @@ -24,3 +24,6 @@ secp256k1 = { workspace = true, features = ["rand"] } [dev-dependencies] alloy-eips.workspace = true + +[features] +scroll = [] diff --git a/testing/testing-utils/src/generators.rs b/testing/testing-utils/src/generators.rs index 582298feab9..32a12b04453 100644 --- a/testing/testing-utils/src/generators.rs +++ b/testing/testing-utils/src/generators.rs @@ -386,7 +386,16 @@ pub fn random_eoa_account(rng: &mut R) -> (Address, Account) { let balance = U256::from(rng.gen::()); let addr = rng.gen(); - (addr, Account { nonce, balance, bytecode_hash: None }) + ( + addr, + Account { + nonce, + balance, + bytecode_hash: None, + #[cfg(feature = "scroll")] + account_extension: Some(Default::default()), + }, + ) } /// Generate random Externally Owned Accounts