From ee56fe7600f367b7d476b2542bf740165aa0c1f9 Mon Sep 17 00:00:00 2001 From: Dmitry Savonin Date: Tue, 10 Oct 2023 16:02:49 -0500 Subject: [PATCH] integrated rWASM virtual machine --- .gitignore | 1 + Cargo.lock | 110 ++++ Cargo.toml | 6 + bin/reth/Cargo.toml | 9 +- bin/reth/src/args/utils.rs | 7 +- bin/reth/src/chain/import.rs | 3 + bin/reth/src/cli/config.rs | 3 + bin/reth/src/debug_cmd/execution.rs | 3 + bin/reth/src/debug_cmd/in_memory_merkle.rs | 3 + bin/reth/src/debug_cmd/merkle.rs | 4 +- bin/reth/src/lib.rs | 3 + bin/reth/src/node/mod.rs | 10 + bin/reth/src/stage/dump/execution.rs | 3 + bin/reth/src/stage/dump/merkle.rs | 7 +- bin/reth/src/stage/run.rs | 3 + .../primitives/res/genesis/fluent-devnet.json | 78 +++ crates/primitives/src/chain/mod.rs | 2 +- crates/primitives/src/chain/spec.rs | 49 +- crates/primitives/src/lib.rs | 2 +- crates/rwasm/Cargo.toml | 27 + crates/rwasm/rwasm-primitives/Cargo.toml | 15 + crates/rwasm/rwasm-primitives/src/compat.rs | 44 ++ crates/rwasm/rwasm-primitives/src/config.rs | 169 ++++++ crates/rwasm/rwasm-primitives/src/env.rs | 255 +++++++++ crates/rwasm/rwasm-primitives/src/lib.rs | 25 + crates/rwasm/src/database.rs | 104 ++++ crates/rwasm/src/factory.rs | 36 ++ crates/rwasm/src/lib.rs | 32 ++ crates/rwasm/src/processor.rs | 511 ++++++++++++++++++ crates/rwasm/src/state_change.rs | 85 +++ 30 files changed, 1589 insertions(+), 20 deletions(-) create mode 100644 crates/primitives/res/genesis/fluent-devnet.json create mode 100644 crates/rwasm/Cargo.toml create mode 100644 crates/rwasm/rwasm-primitives/Cargo.toml create mode 100644 crates/rwasm/rwasm-primitives/src/compat.rs create mode 100644 crates/rwasm/rwasm-primitives/src/config.rs create mode 100644 crates/rwasm/rwasm-primitives/src/env.rs create mode 100644 crates/rwasm/rwasm-primitives/src/lib.rs create mode 100644 crates/rwasm/src/database.rs create mode 100644 crates/rwasm/src/factory.rs create mode 100644 crates/rwasm/src/lib.rs create mode 100644 crates/rwasm/src/processor.rs create mode 100644 crates/rwasm/src/state_change.rs diff --git a/.gitignore b/.gitignore index c2c144f4a..c798ce319 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ crates/stages/testdata # Prometheus data dir data/ +datadir/ # Proptest data proptest-regressions/ diff --git a/Cargo.lock b/Cargo.lock index 18ccf93cf..e740582cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1938,6 +1938,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + [[package]] name = "dunce" version = "1.0.4" @@ -2571,6 +2577,35 @@ dependencies = [ "num-traits", ] +[[package]] +name = "fluentbase-runtime" +version = "0.1.0" +source = "git+https://github.com/fluentlabs-xyz/fluentbase?branch=devel#ed5cb2499a157c117bfce99b301fd74f043241fd" +dependencies = [ + "fluentbase-rwasm", + "strum 0.25.0", + "strum_macros 0.25.2", + "wasi 0.11.0+wasi-snapshot-preview1", + "wat", +] + +[[package]] +name = "fluentbase-rwasm" +version = "0.30.0" +source = "git+https://github.com/fluentlabs-xyz/fluentbase?branch=devel#ed5cb2499a157c117bfce99b301fd74f043241fd" +dependencies = [ + "byteorder", + "downcast-rs", + "libm", + "num-traits", + "paste", + "smallvec 1.11.0", + "spin 0.9.8", + "strum 0.25.0", + "strum_macros 0.25.2", + "wasmparser-nostd", +] + [[package]] name = "fnv" version = "1.0.7" @@ -3375,6 +3410,12 @@ dependencies = [ "serde", ] +[[package]] +name = "indexmap-nostd" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" + [[package]] name = "inferno" version = "0.11.17" @@ -3748,6 +3789,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "libc" version = "0.2.148" @@ -5264,6 +5311,7 @@ dependencies = [ "reth-rpc-builder", "reth-rpc-engine-api", "reth-rpc-types", + "reth-rwasm", "reth-stages", "reth-tasks", "reth-tracing", @@ -6115,6 +6163,29 @@ dependencies = [ "reth-rpc-types", ] +[[package]] +name = "reth-rwasm" +version = "0.1.0-alpha.8" +dependencies = [ + "fluentbase-runtime", + "fluentbase-rwasm", + "reth-consensus-common", + "reth-interfaces", + "reth-primitives", + "reth-provider", + "reth-rwasm-primitives", + "revm", + "tracing", +] + +[[package]] +name = "reth-rwasm-primitives" +version = "0.1.0-alpha.8" +dependencies = [ + "reth-primitives", + "revm", +] + [[package]] name = "reth-stages" version = "0.1.0-alpha.8" @@ -8249,6 +8320,45 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +[[package]] +name = "wasm-encoder" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34180c89672b3e4825c3a8db4b61a674f1447afd5fe2445b2d22c3d8b6ea086c" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasmparser-nostd" +version = "0.100.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9157cab83003221bfd385833ab587a039f5d6fa7304854042ba358a3b09e0724" +dependencies = [ + "indexmap-nostd", +] + +[[package]] +name = "wast" +version = "66.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da7529bb848d58ab8bf32230fc065b363baee2bd338d5e58c589a1e7d83ad07" +dependencies = [ + "leb128", + "memchr", + "unicode-width", + "wasm-encoder", +] + +[[package]] +name = "wat" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4780374047c65b6b6e86019093fe80c18b66825eb684df778a4e068282a780e7" +dependencies = [ + "wast", +] + [[package]] name = "web-sys" version = "0.3.64" diff --git a/Cargo.toml b/Cargo.toml index 2bd859afc..f0dc0ea9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,8 @@ members = [ "crates/rpc/rpc-engine-api", "crates/rpc/rpc-types", "crates/rpc/rpc-testing-util", + "crates/rwasm", + "crates/rwasm/rwasm-primitives", "crates/stages", "crates/storage/codecs", "crates/storage/db", @@ -101,6 +103,10 @@ reth-rpc-types-compat = { path = "./crates/rpc/rpc-types-compat" } revm = { git = "https://github.com/bluealloy/revm" } revm-primitives = { git = "https://github.com/bluealloy/revm" } +# rwasm +fluentbase-rwasm = { git = "https://github.com/fluentlabs-xyz/fluentbase", branch = "devel" } +fluentbase-runtime = { git = "https://github.com/fluentlabs-xyz/fluentbase", branch = "devel" } + ## eth ethers-core = { version = "2.0", default-features = false } ethers-providers = { version = "2.0", default-features = false } diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 28b0d8118..a453a6c37 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -23,8 +23,9 @@ reth-primitives = { workspace = true, features = ["arbitrary"] } reth-db = { path = "../../crates/storage/db", features = ["mdbx", "test-utils"] } # TODO: Temporary use of the test-utils feature reth-provider = { workspace = true, features = ["test-utils"] } -reth-revm = { path = "../../crates/revm" } -reth-revm-inspectors = { path = "../../crates/revm/revm-inspectors" } +reth-revm = { path = "../../crates/revm", optional = true } +reth-revm-inspectors = { path = "../../crates/revm/revm-inspectors", optional = true } +reth-rwasm = { path = "../../crates/rwasm", optional = true } reth-stages = { path = "../../crates/stages" } reth-interfaces = { workspace = true, features = ["test-utils", "clap"] } reth-transaction-pool.workspace = true @@ -107,7 +108,7 @@ jemallocator = { version = "0.5.0", optional = true } jemalloc-ctl = { version = "0.5.0", optional = true } [features] -default = ["jemalloc"] +default = ["jemalloc", "rwasm"] jemalloc = ["dep:jemallocator", "dep:jemalloc-ctl"] jemalloc-prof = ["jemalloc", "jemallocator?/profiling"] min-error-logs = ["tracing/release_max_level_error"] @@ -115,6 +116,8 @@ min-warn-logs = ["tracing/release_max_level_warn"] min-info-logs = ["tracing/release_max_level_info"] min-debug-logs = ["tracing/release_max_level_debug"] min-trace-logs = ["tracing/release_max_level_trace"] +rwasm = ["dep:reth-rwasm"] +revm = ["dep:reth-revm", "dep:reth-revm-inspectors"] [build-dependencies] vergen = { version = "8.0.0", features = ["build", "cargo", "git", "gitcl"] } diff --git a/bin/reth/src/args/utils.rs b/bin/reth/src/args/utils.rs index b243c6a59..f4588f281 100644 --- a/bin/reth/src/args/utils.rs +++ b/bin/reth/src/args/utils.rs @@ -1,9 +1,12 @@ //! Clap parser utilities use reth_primitives::{ - fs, AllGenesisFormats, BlockHashOrNumber, ChainSpec, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA, + fs, AllGenesisFormats, BlockHashOrNumber, ChainSpec, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA, FLUENT_DEVNET, }; +#[cfg(feature = "revm")] use reth_revm::primitives::B256 as H256; +#[cfg(feature = "rwasm")] +use reth_rwasm::primitives::B256 as H256; use std::{ net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs}, path::PathBuf, @@ -27,6 +30,7 @@ pub fn chain_spec_value_parser(s: &str) -> eyre::Result, eyre::Er "sepolia" => SEPOLIA.clone(), "holesky" => HOLESKY.clone(), "dev" => DEV.clone(), + "fluent-devnet" => FLUENT_DEVNET.clone(), _ => { let raw = fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned()))?; serde_json::from_str(&raw)? @@ -43,6 +47,7 @@ pub fn genesis_value_parser(s: &str) -> eyre::Result, eyre::Error "sepolia" => SEPOLIA.clone(), "holesky" => HOLESKY.clone(), "dev" => DEV.clone(), + "fluent-devnet" => FLUENT_DEVNET.clone(), _ => { let raw = fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned()))?; let genesis: AllGenesisFormats = serde_json::from_str(&raw)?; diff --git a/bin/reth/src/chain/import.rs b/bin/reth/src/chain/import.rs index 19d6ec1d5..137043072 100644 --- a/bin/reth/src/chain/import.rs +++ b/bin/reth/src/chain/import.rs @@ -157,7 +157,10 @@ impl ImportCommand { .into_task(); let (tip_tx, tip_rx) = watch::channel(H256::zero()); + #[cfg(feature = "revm")] let factory = reth_revm::Factory::new(self.chain.clone()); + #[cfg(feature = "rwasm")] + let factory = reth_rwasm::Factory::new(self.chain.clone()); let max_block = file_client.max_block().unwrap_or(0); let mut pipeline = Pipeline::builder() diff --git a/bin/reth/src/cli/config.rs b/bin/reth/src/cli/config.rs index b87ed6be0..c71cba884 100644 --- a/bin/reth/src/cli/config.rs +++ b/bin/reth/src/cli/config.rs @@ -1,6 +1,9 @@ //! Config traits for various node components. +#[cfg(feature = "revm")] use reth_revm::primitives::bytes::BytesMut; +#[cfg(feature = "rwasm")] +use reth_rwasm::primitives::bytes::BytesMut; use reth_rlp::Encodable; use reth_rpc::{eth::gas_oracle::GasPriceOracleConfig, JwtError, JwtSecret}; use reth_rpc_builder::{ diff --git a/bin/reth/src/debug_cmd/execution.rs b/bin/reth/src/debug_cmd/execution.rs index afd838569..48e6fe65d 100644 --- a/bin/reth/src/debug_cmd/execution.rs +++ b/bin/reth/src/debug_cmd/execution.rs @@ -113,7 +113,10 @@ impl Command { let stage_conf = &config.stages; let (tip_tx, tip_rx) = watch::channel(H256::zero()); + #[cfg(feature = "revm")] let factory = reth_revm::Factory::new(self.chain.clone()); + #[cfg(feature = "rwasm")] + let factory = reth_rwasm::Factory::new(self.chain.clone()); let header_mode = HeaderSyncMode::Tip(tip_rx); let pipeline = Pipeline::builder() diff --git a/bin/reth/src/debug_cmd/in_memory_merkle.rs b/bin/reth/src/debug_cmd/in_memory_merkle.rs index bddece765..b0c57f94c 100644 --- a/bin/reth/src/debug_cmd/in_memory_merkle.rs +++ b/bin/reth/src/debug_cmd/in_memory_merkle.rs @@ -164,7 +164,10 @@ impl Command { ) .await?; + #[cfg(feature = "revm")] let executor_factory = reth_revm::Factory::new(self.chain.clone()); + #[cfg(feature = "rwasm")] + let executor_factory = reth_rwasm::Factory::new(self.chain.clone()); let mut executor = executor_factory.with_state(LatestStateProviderRef::new(provider.tx_ref())); diff --git a/bin/reth/src/debug_cmd/merkle.rs b/bin/reth/src/debug_cmd/merkle.rs index 37e41e8fc..5c428119c 100644 --- a/bin/reth/src/debug_cmd/merkle.rs +++ b/bin/reth/src/debug_cmd/merkle.rs @@ -201,8 +201,10 @@ impl Command { checkpoint.block_number != execution_checkpoint_block || checkpoint.stage_checkpoint.is_some() }); - + #[cfg(feature = "revm")] let factory = reth_revm::Factory::new(self.chain.clone()); + #[cfg(feature = "rwasm")] + let factory = reth_rwasm::Factory::new(self.chain.clone()); let mut execution_stage = ExecutionStage::new( factory, ExecutionStageThresholds { diff --git a/bin/reth/src/lib.rs b/bin/reth/src/lib.rs index 2f452eb46..7680e0c89 100644 --- a/bin/reth/src/lib.rs +++ b/bin/reth/src/lib.rs @@ -62,7 +62,10 @@ pub mod blockchain_tree { /// Re-exported from `reth_revm`. pub mod revm { + #[cfg(feature = "revm")] pub use reth_revm::*; + #[cfg(feature = "rwasm")] + pub use reth_rwasm::*; } /// Re-exported from `reth_tasks`. diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index 2e626a1c6..a9b210b70 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -59,7 +59,11 @@ use reth_provider::{ providers::BlockchainProvider, BlockHashReader, BlockReader, CanonStateSubscriptions, HeaderProvider, ProviderFactory, StageCheckpointReader, }; +#[cfg(feature = "revm")] use reth_revm::Factory; +#[cfg(feature = "rwasm")] +use reth_rwasm::Factory; +#[cfg(feature = "revm")] use reth_revm_inspectors::stack::Hook; use reth_rpc_engine_api::EngineApi; use reth_stages::{ @@ -806,9 +810,14 @@ impl NodeCommand { } let (tip_tx, tip_rx) = watch::channel(H256::zero()); + #[cfg(feature = "revm")] use reth_revm_inspectors::stack::InspectorStackConfig; + #[cfg(feature = "revm")] let factory = reth_revm::Factory::new(self.chain.clone()); + #[cfg(feature = "rwasm")] + let factory = reth_rwasm::Factory::new(self.chain.clone()); + #[cfg(feature = "revm")] let stack_config = InspectorStackConfig { use_printer_tracer: self.debug.print_inspector, hook: if let Some(hook_block) = self.debug.hook_block { @@ -822,6 +831,7 @@ impl NodeCommand { }, }; + #[cfg(feature = "revm")] let factory = factory.with_stack_config(stack_config); let prune_modes = prune_config.map(|prune| prune.parts).unwrap_or_default(); diff --git a/bin/reth/src/stage/dump/execution.rs b/bin/reth/src/stage/dump/execution.rs index 67eda8033..b9ea5ff75 100644 --- a/bin/reth/src/stage/dump/execution.rs +++ b/bin/reth/src/stage/dump/execution.rs @@ -7,7 +7,10 @@ use reth_db::{ }; use reth_primitives::{stage::StageCheckpoint, ChainSpec}; use reth_provider::ProviderFactory; +#[cfg(feature = "revm")] use reth_revm::Factory; +#[cfg(feature = "rwasm")] +use reth_rwasm::Factory; use reth_stages::{stages::ExecutionStage, Stage, UnwindInput}; use std::{path::PathBuf, sync::Arc}; use tracing::info; diff --git a/bin/reth/src/stage/dump/merkle.rs b/bin/reth/src/stage/dump/merkle.rs index 55eef819f..a47fe9563 100644 --- a/bin/reth/src/stage/dump/merkle.rs +++ b/bin/reth/src/stage/dump/merkle.rs @@ -66,9 +66,14 @@ async fn unwind_and_copy( MerkleStage::default_unwind().unwind(&provider, unwind).await?; + #[cfg(feature = "revm")] + let execution_factory = reth_revm::Factory::new(db_tool.chain.clone()); + #[cfg(feature = "rwasm")] + let execution_factory = reth_rwasm::Factory::new(db_tool.chain.clone()); + // Bring Plainstate to TO (hashing stage execution requires it) let mut exec_stage = ExecutionStage::new( - reth_revm::Factory::new(db_tool.chain.clone()), + execution_factory, ExecutionStageThresholds { max_blocks: Some(u64::MAX), max_changes: None, diff --git a/bin/reth/src/stage/run.rs b/bin/reth/src/stage/run.rs index d06ee8e7b..354c273b9 100644 --- a/bin/reth/src/stage/run.rs +++ b/bin/reth/src/stage/run.rs @@ -194,7 +194,10 @@ impl Command { } StageEnum::Senders => (Box::new(SenderRecoveryStage::new(batch_size)), None), StageEnum::Execution => { + #[cfg(feature = "revm")] let factory = reth_revm::Factory::new(self.chain.clone()); + #[cfg(feature = "rwasm")] + let factory = reth_rwasm::Factory::new(self.chain.clone()); ( Box::new(ExecutionStage::new( factory, diff --git a/crates/primitives/res/genesis/fluent-devnet.json b/crates/primitives/res/genesis/fluent-devnet.json new file mode 100644 index 000000000..9575e2645 --- /dev/null +++ b/crates/primitives/res/genesis/fluent-devnet.json @@ -0,0 +1,78 @@ +{ + "nonce": "0x0", + "timestamp": "0x6490fdd2", + "extraData": "0x", + "gasLimit": "0x1c9c380", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x5eb6e371a698b8d68f665192350ffcecbbbf322916f4b51bd79bb6887da3f494", + "alloc": { + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x390a4CEdBb65be7511D9E1a35b115376F39DbDF3": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x90F79bf6EB2c4f870365E785982E1f101E93b906": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x976EA74026E726554dB657fA54763abd0C3a0aa9": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xBcd4042DE499D14e55001CcbB24a551F3b954096": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x71bE63f3384f5fb98995898A86B02Fb2426c5788": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xFABB0ac9d68B0B445fB7357272Ff202C5651694a": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xcd3B766CCDd6AE721141F452C550Ca635964ce71": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x2546BcD3c84621e976D8185a91A922aE77ECEc30": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xbDA5747bFD65F08deb54cb465eB87D40e51B197E": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xdD2FD4581271e230360230F9337D5c0430Bf44C0": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199": { + "balance": "0xD3C21BCECCEDA1000000" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +} \ No newline at end of file diff --git a/crates/primitives/src/chain/mod.rs b/crates/primitives/src/chain/mod.rs index 5ffdf9c2d..6bdc70cb1 100644 --- a/crates/primitives/src/chain/mod.rs +++ b/crates/primitives/src/chain/mod.rs @@ -12,7 +12,7 @@ use std::{fmt, str::FromStr}; mod spec; pub use spec::{ AllGenesisFormats, BaseFeeParams, ChainSpec, ChainSpecBuilder, DisplayHardforks, ForkCondition, - ForkTimestamps, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA, + ForkTimestamps, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA, FLUENT_DEVNET, }; // The chain info module. diff --git a/crates/primitives/src/chain/spec.rs b/crates/primitives/src/chain/spec.rs index 7cda28e68..22545fad2 100644 --- a/crates/primitives/src/chain/spec.rs +++ b/crates/primitives/src/chain/spec.rs @@ -225,11 +225,11 @@ pub static DEV: Lazy> = Lazy::new(|| { (Hardfork::MuirGlacier, ForkCondition::Block(0)), (Hardfork::Berlin, ForkCondition::Block(0)), (Hardfork::London, ForkCondition::Block(0)), - ( - Hardfork::Paris, - ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::from(0) }, - ), - (Hardfork::Shanghai, ForkCondition::Timestamp(0)), + // ( + // Hardfork::Paris, + // ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::from(0) }, + // ), + // (Hardfork::Shanghai, ForkCondition::Timestamp(0)), ]), deposit_contract: None, // TODO: do we even have? ..Default::default() @@ -237,6 +237,31 @@ pub static DEV: Lazy> = Lazy::new(|| { .into() }); +pub static FLUENT_DEVNET: Lazy> = Lazy::new(|| { + let genesis = serde_json::from_str(include_str!("../../res/genesis/fluent-devnet.json")) + .expect("Can't deserialize FluentDevnet testnet genesis json"); + ChainSpec { + chain: Chain::dev(), + genesis, + hardforks: BTreeMap::from([ + (Hardfork::Frontier, ForkCondition::Block(0)), + (Hardfork::Homestead, ForkCondition::Block(0)), + (Hardfork::Dao, ForkCondition::Block(0)), + (Hardfork::Tangerine, ForkCondition::Block(0)), + (Hardfork::SpuriousDragon, ForkCondition::Block(0)), + (Hardfork::Byzantium, ForkCondition::Block(0)), + (Hardfork::Constantinople, ForkCondition::Block(0)), + (Hardfork::Petersburg, ForkCondition::Block(0)), + (Hardfork::Istanbul, ForkCondition::Block(0)), + (Hardfork::MuirGlacier, ForkCondition::Block(0)), + (Hardfork::Berlin, ForkCondition::Block(0)), + (Hardfork::London, ForkCondition::Block(0)), + ]), + ..Default::default() + } + .into() +}); + /// BaseFeeParams contains the config parameters that control block base fee computation #[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)] pub struct BaseFeeParams { @@ -488,8 +513,8 @@ impl ChainSpec { for (_, cond) in self.forks_iter() { // handle block based forks and the sepolia merge netsplit block edge case (TTD // ForkCondition with Some(block)) - if let ForkCondition::Block(block) | - ForkCondition::TTD { fork_block: Some(block), .. } = cond + if let ForkCondition::Block(block) + | ForkCondition::TTD { fork_block: Some(block), .. } = cond { if cond.active_at_head(head) { if block != current_applied { @@ -499,7 +524,7 @@ impl ChainSpec { } else { // we can return here because this block fork is not active, so we set the // `next` value - return ForkId { hash: forkhash, next: block } + return ForkId { hash: forkhash, next: block }; } } } @@ -520,7 +545,7 @@ impl ChainSpec { // can safely return here because we have already handled all block forks and // have handled all active timestamp forks, and set the next value to the // timestamp that is known but not active yet - return ForkId { hash: forkhash, next: timestamp } + return ForkId { hash: forkhash, next: timestamp }; } } @@ -918,9 +943,9 @@ impl ForkCondition { /// - The condition is satisfied by the timestamp; /// - or the condition is satisfied by the total difficulty pub fn active_at_head(&self, head: &Head) -> bool { - self.active_at_block(head.number) || - self.active_at_timestamp(head.timestamp) || - self.active_at_ttd(head.total_difficulty, head.difficulty) + self.active_at_block(head.number) + || self.active_at_timestamp(head.timestamp) + || self.active_at_ttd(head.total_difficulty, head.difficulty) } /// Get the total terminal difficulty for this fork condition. diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 3d7356942..72f3a6f27 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -62,7 +62,7 @@ pub use block::{ pub use bloom::Bloom; pub use chain::{ AllGenesisFormats, BaseFeeParams, Chain, ChainInfo, ChainSpec, ChainSpecBuilder, - DisplayHardforks, ForkCondition, ForkTimestamps, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA, + DisplayHardforks, ForkCondition, ForkTimestamps, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA, FLUENT_DEVNET, }; pub use compression::*; pub use constants::{ diff --git a/crates/rwasm/Cargo.toml b/crates/rwasm/Cargo.toml new file mode 100644 index 000000000..9ffdd115d --- /dev/null +++ b/crates/rwasm/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "reth-rwasm" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "reth specific rwasm utilities" + +[dependencies] +# reth +reth-primitives.workspace = true +reth-interfaces.workspace = true +reth-provider.workspace = true +reth-rwasm-primitives = { path = "rwasm-primitives" } +reth-consensus-common = { path = "../consensus/common" } + +# rwasm +fluentbase-rwasm.workspace = true +fluentbase-runtime.workspace = true + +# revm +revm.workspace = true + +# common +tracing.workspace = true \ No newline at end of file diff --git a/crates/rwasm/rwasm-primitives/Cargo.toml b/crates/rwasm/rwasm-primitives/Cargo.toml new file mode 100644 index 000000000..750e0989f --- /dev/null +++ b/crates/rwasm/rwasm-primitives/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "reth-rwasm-primitives" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "core reth specific revm utilities" + +[dependencies] +# reth +reth-primitives.workspace = true + +revm.workspace = true diff --git a/crates/rwasm/rwasm-primitives/src/compat.rs b/crates/rwasm/rwasm-primitives/src/compat.rs new file mode 100644 index 000000000..507580c9b --- /dev/null +++ b/crates/rwasm/rwasm-primitives/src/compat.rs @@ -0,0 +1,44 @@ +use reth_primitives::{Account, Log as RethLog, H160, H256, KECCAK_EMPTY}; +use revm::primitives::{AccountInfo, Log}; + +/// Check equality between [`reth_primitives::Log`] and [`revm::primitives::Log`] +pub fn is_log_equal(revm_log: &Log, reth_log: &reth_primitives::Log) -> bool { + revm_log.topics.len() == reth_log.topics.len() && + revm_log.address.0 == reth_log.address.0 && + revm_log.data == reth_log.data.0 && + !revm_log + .topics + .iter() + .zip(reth_log.topics.iter()) + .any(|(revm_topic, reth_topic)| revm_topic.0 != reth_topic.0) +} + +/// Into reth primitive [Log] from [revm::primitives::Log]. +pub fn into_reth_log(log: Log) -> RethLog { + RethLog { + address: H160(log.address.0), + topics: log.topics.into_iter().map(|h| H256(h.0)).collect(), + data: log.data.into(), + } +} + +/// Create reth primitive [Account] from [revm::primitives::AccountInfo]. +/// Check if revm bytecode hash is [KECCAK_EMPTY] and put None to reth [Account] +pub fn into_reth_acc(revm_acc: AccountInfo) -> Account { + let code_hash = revm_acc.code_hash; + Account { + balance: revm_acc.balance, + nonce: revm_acc.nonce, + bytecode_hash: (code_hash != KECCAK_EMPTY).then_some(code_hash), + } +} + +/// Create revm primitive [AccountInfo] from [reth_primitives::Account]. +pub fn into_revm_acc(reth_acc: Account) -> AccountInfo { + AccountInfo { + balance: reth_acc.balance, + nonce: reth_acc.nonce, + code_hash: reth_acc.bytecode_hash.unwrap_or(KECCAK_EMPTY), + code: None, + } +} diff --git a/crates/rwasm/rwasm-primitives/src/config.rs b/crates/rwasm/rwasm-primitives/src/config.rs new file mode 100644 index 000000000..ec7188007 --- /dev/null +++ b/crates/rwasm/rwasm-primitives/src/config.rs @@ -0,0 +1,169 @@ +//! Reth block execution/validation configuration and constants + +use reth_primitives::{ChainSpec, Hardfork, Head}; + +/// Returns the spec id at the given timestamp. +/// +/// Note: This is only intended to be used after the merge, when hardforks are activated by +/// timestamp. +pub fn revm_spec_by_timestamp_after_merge( + chain_spec: &ChainSpec, + timestamp: u64, +) -> revm::primitives::SpecId { + if chain_spec.is_fork_active_at_timestamp(Hardfork::Shanghai, timestamp) { + revm::primitives::SHANGHAI + } else { + revm::primitives::MERGE + } +} + +/// return revm_spec from spec configuration. +pub fn revm_spec(chain_spec: &ChainSpec, block: Head) -> revm::primitives::SpecId { + if chain_spec.fork(Hardfork::Shanghai).active_at_head(&block) { + revm::primitives::SHANGHAI + } else if chain_spec.fork(Hardfork::Paris).active_at_head(&block) { + revm::primitives::MERGE + } else if chain_spec.fork(Hardfork::London).active_at_head(&block) { + revm::primitives::LONDON + } else if chain_spec.fork(Hardfork::Berlin).active_at_head(&block) { + revm::primitives::BERLIN + } else if chain_spec.fork(Hardfork::Istanbul).active_at_head(&block) { + revm::primitives::ISTANBUL + } else if chain_spec.fork(Hardfork::Petersburg).active_at_head(&block) { + revm::primitives::PETERSBURG + } else if chain_spec.fork(Hardfork::Byzantium).active_at_head(&block) { + revm::primitives::BYZANTIUM + } else if chain_spec.fork(Hardfork::SpuriousDragon).active_at_head(&block) { + revm::primitives::SPURIOUS_DRAGON + } else if chain_spec.fork(Hardfork::Tangerine).active_at_head(&block) { + revm::primitives::TANGERINE + } else if chain_spec.fork(Hardfork::Homestead).active_at_head(&block) { + revm::primitives::HOMESTEAD + } else if chain_spec.fork(Hardfork::Frontier).active_at_head(&block) { + revm::primitives::FRONTIER + } else { + panic!( + "invalid hardfork chainspec: expected at least one hardfork, got {:?}", + chain_spec.hardforks + ) + } +} + +#[cfg(test)] +mod tests { + use crate::config::revm_spec; + use reth_primitives::{ChainSpecBuilder, Head, MAINNET, U256}; + #[test] + fn test_to_revm_spec() { + assert_eq!( + revm_spec(&ChainSpecBuilder::mainnet().paris_activated().build(), Head::default()), + revm::primitives::MERGE + ); + assert_eq!( + revm_spec(&ChainSpecBuilder::mainnet().london_activated().build(), Head::default()), + revm::primitives::LONDON + ); + assert_eq!( + revm_spec(&ChainSpecBuilder::mainnet().berlin_activated().build(), Head::default()), + revm::primitives::BERLIN + ); + assert_eq!( + revm_spec(&ChainSpecBuilder::mainnet().istanbul_activated().build(), Head::default()), + revm::primitives::ISTANBUL + ); + assert_eq!( + revm_spec(&ChainSpecBuilder::mainnet().petersburg_activated().build(), Head::default()), + revm::primitives::PETERSBURG + ); + assert_eq!( + revm_spec(&ChainSpecBuilder::mainnet().byzantium_activated().build(), Head::default()), + revm::primitives::BYZANTIUM + ); + assert_eq!( + revm_spec( + &ChainSpecBuilder::mainnet().spurious_dragon_activated().build(), + Head::default() + ), + revm::primitives::SPURIOUS_DRAGON + ); + assert_eq!( + revm_spec( + &ChainSpecBuilder::mainnet().tangerine_whistle_activated().build(), + Head::default() + ), + revm::primitives::TANGERINE + ); + assert_eq!( + revm_spec(&ChainSpecBuilder::mainnet().homestead_activated().build(), Head::default()), + revm::primitives::HOMESTEAD + ); + assert_eq!( + revm_spec(&ChainSpecBuilder::mainnet().frontier_activated().build(), Head::default()), + revm::primitives::FRONTIER + ); + } + + #[test] + fn test_eth_spec() { + assert_eq!( + revm_spec( + &MAINNET, + Head { + total_difficulty: U256::from(58_750_000_000_000_000_000_010_u128), + difficulty: U256::from(10_u128), + ..Default::default() + } + ), + revm::primitives::MERGE + ); + // TTD trumps the block number + assert_eq!( + revm_spec( + &MAINNET, + Head { + number: 15537394 - 10, + total_difficulty: U256::from(58_750_000_000_000_000_000_010_u128), + difficulty: U256::from(10_u128), + ..Default::default() + } + ), + revm::primitives::MERGE + ); + assert_eq!( + revm_spec(&MAINNET, Head { number: 15537394 - 10, ..Default::default() }), + revm::primitives::LONDON + ); + assert_eq!( + revm_spec(&MAINNET, Head { number: 12244000 + 10, ..Default::default() }), + revm::primitives::BERLIN + ); + assert_eq!( + revm_spec(&MAINNET, Head { number: 12244000 - 10, ..Default::default() }), + revm::primitives::ISTANBUL + ); + assert_eq!( + revm_spec(&MAINNET, Head { number: 7280000 + 10, ..Default::default() }), + revm::primitives::PETERSBURG + ); + assert_eq!( + revm_spec(&MAINNET, Head { number: 7280000 - 10, ..Default::default() }), + revm::primitives::BYZANTIUM + ); + assert_eq!( + revm_spec(&MAINNET, Head { number: 2675000 + 10, ..Default::default() }), + revm::primitives::SPURIOUS_DRAGON + ); + assert_eq!( + revm_spec(&MAINNET, Head { number: 2675000 - 10, ..Default::default() }), + revm::primitives::TANGERINE + ); + assert_eq!( + revm_spec(&MAINNET, Head { number: 1150000 + 10, ..Default::default() }), + revm::primitives::HOMESTEAD + ); + assert_eq!( + revm_spec(&MAINNET, Head { number: 1150000 - 10, ..Default::default() }), + revm::primitives::FRONTIER + ); + } +} diff --git a/crates/rwasm/rwasm-primitives/src/env.rs b/crates/rwasm/rwasm-primitives/src/env.rs new file mode 100644 index 000000000..3efa2b19c --- /dev/null +++ b/crates/rwasm/rwasm-primitives/src/env.rs @@ -0,0 +1,255 @@ +use crate::config::revm_spec; +use reth_primitives::{ + recover_signer, Address, Bytes, Chain, ChainSpec, Head, Header, Transaction, TransactionKind, + TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxEip4844, TxLegacy, U256, +}; +use revm::primitives::{AnalysisKind, BlockEnv, CfgEnv, SpecId, TransactTo, TxEnv}; + +/// Convenience function to call both [fill_cfg_env] and [fill_block_env] +pub fn fill_cfg_and_block_env( + cfg: &mut CfgEnv, + block_env: &mut BlockEnv, + chain_spec: &ChainSpec, + header: &Header, + total_difficulty: U256, +) { + fill_cfg_env(cfg, chain_spec, header, total_difficulty); + let after_merge = cfg.spec_id >= SpecId::MERGE; + fill_block_env(block_env, chain_spec, header, after_merge); +} + +/// Fill [CfgEnv] fields according to the chain spec and given header +pub fn fill_cfg_env( + cfg_env: &mut CfgEnv, + chain_spec: &ChainSpec, + header: &Header, + total_difficulty: U256, +) { + let spec_id = revm_spec( + chain_spec, + Head { + number: header.number, + timestamp: header.timestamp, + difficulty: header.difficulty, + total_difficulty, + hash: Default::default(), + }, + ); + + cfg_env.chain_id = chain_spec.chain().id(); + cfg_env.spec_id = spec_id; + cfg_env.perf_analyse_created_bytecodes = AnalysisKind::Analyse; +} + +/// Fill block environment from Block. +pub fn fill_block_env( + block_env: &mut BlockEnv, + chain_spec: &ChainSpec, + header: &Header, + after_merge: bool, +) { + let coinbase = block_coinbase(chain_spec, header, after_merge); + fill_block_env_with_coinbase(block_env, header, after_merge, coinbase); +} + +/// Fill block environment with coinbase. +#[inline] +pub fn fill_block_env_with_coinbase( + block_env: &mut BlockEnv, + header: &Header, + after_merge: bool, + coinbase: Address, +) { + block_env.number = U256::from(header.number); + block_env.coinbase = coinbase; + block_env.timestamp = U256::from(header.timestamp); + if after_merge { + block_env.prevrandao = Some(header.mix_hash); + block_env.difficulty = U256::ZERO; + } else { + block_env.difficulty = header.difficulty; + block_env.prevrandao = None; + } + block_env.basefee = U256::from(header.base_fee_per_gas.unwrap_or_default()); + block_env.gas_limit = U256::from(header.gas_limit); +} + +/// Return the coinbase address for the given header and chain spec. +pub fn block_coinbase(chain_spec: &ChainSpec, header: &Header, after_merge: bool) -> Address { + if chain_spec.chain == Chain::goerli() && !after_merge { + recover_header_signer(header).expect("failed to recover signer") + } else { + header.beneficiary + } +} + +/// Recover the account from signed header per clique consensus rules. +pub fn recover_header_signer(header: &Header) -> Option
{ + let extra_data_len = header.extra_data.len(); + // Fixed number of extra-data suffix bytes reserved for signer signature. + // 65 bytes fixed as signatures are based on the standard secp256k1 curve. + // Filled with zeros on genesis block. + let signature_start_byte = extra_data_len - 65; + let signature: [u8; 65] = header.extra_data[signature_start_byte..].try_into().ok()?; + let seal_hash = { + let mut header_to_seal = header.clone(); + header_to_seal.extra_data = Bytes::from(&header.extra_data[..signature_start_byte]); + header_to_seal.hash_slow() + }; + recover_signer(&signature, seal_hash.as_fixed_bytes()).ok() +} + +/// Returns a new [TxEnv] filled with the transaction's data. +pub fn tx_env_with_recovered(transaction: &TransactionSignedEcRecovered) -> TxEnv { + let mut tx_env = TxEnv::default(); + fill_tx_env(&mut tx_env, transaction.as_ref(), transaction.signer()); + tx_env +} + +/// Fill transaction environment from [TransactionSignedEcRecovered]. +pub fn fill_tx_env_with_recovered(tx_env: &mut TxEnv, transaction: &TransactionSignedEcRecovered) { + fill_tx_env(tx_env, transaction.as_ref(), transaction.signer()) +} + +/// Fill transaction environment from a [Transaction] and the given sender address. +pub fn fill_tx_env(tx_env: &mut TxEnv, transaction: T, sender: Address) +where + T: AsRef, +{ + tx_env.caller = sender; + match transaction.as_ref() { + Transaction::Legacy(TxLegacy { + nonce, + chain_id, + gas_price, + gas_limit, + to, + value, + input, + }) => { + tx_env.gas_limit = *gas_limit; + tx_env.gas_price = U256::from(*gas_price); + tx_env.gas_priority_fee = None; + tx_env.transact_to = match to { + TransactionKind::Call(to) => TransactTo::Call(*to), + TransactionKind::Create => TransactTo::create(), + }; + tx_env.value = U256::from(*value); + tx_env.data = input.0.clone(); + tx_env.chain_id = *chain_id; + tx_env.nonce = Some(*nonce); + tx_env.access_list.clear(); + } + Transaction::Eip2930(TxEip2930 { + nonce, + chain_id, + gas_price, + gas_limit, + to, + value, + input, + access_list, + }) => { + tx_env.gas_limit = *gas_limit; + tx_env.gas_price = U256::from(*gas_price); + tx_env.gas_priority_fee = None; + tx_env.transact_to = match to { + TransactionKind::Call(to) => TransactTo::Call(*to), + TransactionKind::Create => TransactTo::create(), + }; + tx_env.value = U256::from(*value); + tx_env.data = input.0.clone(); + tx_env.chain_id = Some(*chain_id); + tx_env.nonce = Some(*nonce); + tx_env.access_list = access_list + .0 + .iter() + .map(|l| { + ( + l.address, + l.storage_keys + .iter() + .map(|k| U256::from_be_bytes(k.to_fixed_bytes())) + .collect(), + ) + }) + .collect(); + } + Transaction::Eip1559(TxEip1559 { + nonce, + chain_id, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + to, + value, + input, + access_list, + }) => { + tx_env.gas_limit = *gas_limit; + tx_env.gas_price = U256::from(*max_fee_per_gas); + tx_env.gas_priority_fee = Some(U256::from(*max_priority_fee_per_gas)); + tx_env.transact_to = match to { + TransactionKind::Call(to) => TransactTo::Call(*to), + TransactionKind::Create => TransactTo::create(), + }; + tx_env.value = U256::from(*value); + tx_env.data = input.0.clone(); + tx_env.chain_id = Some(*chain_id); + tx_env.nonce = Some(*nonce); + tx_env.access_list = access_list + .0 + .iter() + .map(|l| { + ( + l.address, + l.storage_keys + .iter() + .map(|k| U256::from_be_bytes(k.to_fixed_bytes())) + .collect(), + ) + }) + .collect(); + } + Transaction::Eip4844(TxEip4844 { + nonce, + chain_id, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + to, + value, + access_list, + blob_versioned_hashes, + max_fee_per_blob_gas, + input, + }) => { + tx_env.gas_limit = *gas_limit; + tx_env.gas_price = U256::from(*max_fee_per_gas); + tx_env.gas_priority_fee = Some(U256::from(*max_priority_fee_per_gas)); + tx_env.transact_to = match to { + TransactionKind::Call(to) => TransactTo::Call(*to), + TransactionKind::Create => TransactTo::create(), + }; + tx_env.value = U256::from(*value); + tx_env.data = input.0.clone(); + tx_env.chain_id = Some(*chain_id); + tx_env.nonce = Some(*nonce); + tx_env.access_list = access_list + .0 + .iter() + .map(|l| { + ( + l.address, + l.storage_keys + .iter() + .map(|k| U256::from_be_bytes(k.to_fixed_bytes())) + .collect(), + ) + }) + .collect(); + tx_env.blob_hashes = blob_versioned_hashes.clone(); + tx_env.max_fee_per_blob_gas = Some(U256::from(*max_fee_per_blob_gas)); + } + } +} diff --git a/crates/rwasm/rwasm-primitives/src/lib.rs b/crates/rwasm/rwasm-primitives/src/lib.rs new file mode 100644 index 000000000..3b5d59d34 --- /dev/null +++ b/crates/rwasm/rwasm-primitives/src/lib.rs @@ -0,0 +1,25 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] +#![warn(missing_docs, unreachable_pub, unused_crate_dependencies)] +#![deny(unused_must_use, rust_2018_idioms)] +#![doc(test( + no_crate_inject, + attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) +))] + +//! revm utils and implementations specific to reth. +pub mod config; + +/// Helpers for configuring revm [Env](revm::primitives::Env) +pub mod env; + +/// Helpers for type compatibility between reth and revm types +mod compat; +pub use compat::*; + +/// Re-exports revm types; +pub use revm::*; diff --git a/crates/rwasm/src/database.rs b/crates/rwasm/src/database.rs new file mode 100644 index 000000000..030553597 --- /dev/null +++ b/crates/rwasm/src/database.rs @@ -0,0 +1,104 @@ +use reth_interfaces::Error; +use reth_primitives::{H160, H256, KECCAK_EMPTY, U256}; +use reth_provider::StateProvider; +use revm::{ + db::{CacheDB, DatabaseRef}, + primitives::{AccountInfo, Bytecode}, + Database, StateDBBox, +}; + +/// SubState of database. Uses revm internal cache with binding to reth StateProvider trait. +pub type SubState = CacheDB>; + +/// State boxed database with reth Error. +pub type RethStateDBBox<'a> = StateDBBox<'a, Error>; + +/// Wrapper around StateProvider that implements revm database trait +#[derive(Debug, Clone)] +pub struct StateProviderDatabase(pub DB); + +impl StateProviderDatabase { + /// Create new State with generic StateProvider. + pub fn new(db: DB) -> Self { + Self(db) + } + + /// Return inner state reference + pub fn state(&self) -> &DB { + &self.0 + } + + /// Return inner state mutable reference + pub fn state_mut(&mut self) -> &mut DB { + &mut self.0 + } + + /// Consume State and return inner StateProvider. + pub fn into_inner(self) -> DB { + self.0 + } +} + +impl Database for StateProviderDatabase { + type Error = Error; + + fn basic(&mut self, address: H160) -> Result, Self::Error> { + Ok(self.0.basic_account(address)?.map(|account| AccountInfo { + balance: account.balance, + nonce: account.nonce, + code_hash: account.bytecode_hash.unwrap_or(KECCAK_EMPTY), + code: None, + })) + } + + fn code_by_hash(&mut self, code_hash: H256) -> Result { + let bytecode = self.0.bytecode_by_hash(code_hash)?; + + Ok(bytecode.map(|b| b.0).unwrap_or_else(Bytecode::new)) + } + + fn storage(&mut self, address: H160, index: U256) -> Result { + let index = H256(index.to_be_bytes()); + let ret = self.0.storage(address, index)?.unwrap_or_default(); + Ok(ret) + } + + fn block_hash(&mut self, number: U256) -> Result { + // The `number` represents the block number, so it is safe to cast it to u64. + Ok(self.0.block_hash(number.try_into().unwrap())?.unwrap_or_default()) + } +} + +impl DatabaseRef for StateProviderDatabase { + type Error = ::Error; + + fn basic(&self, address: H160) -> Result, Self::Error> { + Ok(self.0.basic_account(address)?.map(|account| AccountInfo { + balance: account.balance, + nonce: account.nonce, + code_hash: account.bytecode_hash.unwrap_or(KECCAK_EMPTY), + code: None, + })) + } + + fn code_by_hash(&self, code_hash: H256) -> Result { + let bytecode = self.0.bytecode_by_hash(code_hash)?; + + if let Some(bytecode) = bytecode { + Ok(bytecode.0) + } else { + Ok(Bytecode::new()) + } + } + + fn storage(&self, address: H160, index: U256) -> Result { + let index = H256(index.to_be_bytes()); + let ret = self.0.storage(address, index)?.unwrap_or_default(); + Ok(ret) + } + + fn block_hash(&self, number: U256) -> Result { + // Note: this unwrap is potentially unsafe + Ok(self.0.block_hash(number.try_into().unwrap())?.unwrap_or_default()) + } +} diff --git a/crates/rwasm/src/factory.rs b/crates/rwasm/src/factory.rs new file mode 100644 index 000000000..e30e7a909 --- /dev/null +++ b/crates/rwasm/src/factory.rs @@ -0,0 +1,36 @@ +use crate::{ + database::StateProviderDatabase, + processor::RwasmProcessor, +}; +use reth_primitives::ChainSpec; +use reth_provider::{ExecutorFactory, PrunableBlockExecutor, StateProvider}; +use std::sync::Arc; + +/// Factory that spawn Executor. +#[derive(Clone, Debug)] +pub struct Factory { + chain_spec: Arc, +} + +impl Factory { + /// Create new factory + pub fn new(chain_spec: Arc) -> Self { + Self { chain_spec } + } +} + +impl ExecutorFactory for Factory { + fn with_state<'a, SP: StateProvider + 'a>( + &'a self, + sp: SP, + ) -> Box { + let database_state = StateProviderDatabase::new(sp); + let evm = Box::new(RwasmProcessor::new_with_db(self.chain_spec.clone(), database_state)); + evm + } + + /// Return internal chainspec + fn chain_spec(&self) -> &ChainSpec { + self.chain_spec.as_ref() + } +} diff --git a/crates/rwasm/src/lib.rs b/crates/rwasm/src/lib.rs new file mode 100644 index 000000000..12c8a0d95 --- /dev/null +++ b/crates/rwasm/src/lib.rs @@ -0,0 +1,32 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] +#![warn(missing_docs, unreachable_pub, unused_crate_dependencies)] +#![deny(unused_must_use, rust_2018_idioms)] +#![doc(test( + no_crate_inject, + attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) +))] + +//! revm utils and implementations specific to reth. + +/// Contains glue code for integrating reth database into revm's [Database]. +pub mod database; + +/// revm implementation of reth block and transaction executors. +mod factory; + +/// new revm account state executor +pub mod processor; + +/// State changes that are not related to transactions. +pub mod state_change; + +/// revm executor factory. +pub use factory::Factory; + +/// reexport for convenience +pub use reth_rwasm_primitives::*; diff --git a/crates/rwasm/src/processor.rs b/crates/rwasm/src/processor.rs new file mode 100644 index 000000000..fd1132e71 --- /dev/null +++ b/crates/rwasm/src/processor.rs @@ -0,0 +1,511 @@ +use crate::{ + database::StateProviderDatabase, + env::{fill_cfg_and_block_env, fill_tx_env}, + into_reth_log, + state_change::post_block_balance_increments, + State, +}; +use fluentbase_runtime::Runtime; +use reth_interfaces::{ + executor::{BlockExecutionError, BlockValidationError}, + Error, +}; +use reth_primitives::{ + Address, Block, BlockNumber, Bloom, ChainSpec, Hardfork, Header, PruneMode, PruneModes, + PrunePartError, Receipt, ReceiptWithBloom, TransactionSigned, H256, MINIMUM_PRUNING_DISTANCE, + U256, +}; +use reth_provider::{ + BlockExecutor, BlockExecutorStats, BundleStateWithReceipts, PrunableBlockExecutor, + StateProvider, +}; +use reth_rwasm_primitives::db::states::bundle_state::BundleRetention; +use reth_rwasm_primitives::primitives::{ + Bytes, Env, Eval, ExecutionResult, Output, ResultAndState, +}; +use reth_rwasm_primitives::{DatabaseCommit, StateDBBox}; +use std::any::Any; +use std::marker::PhantomData; +use std::{sync::Arc, time::Instant}; +use tracing::{debug, trace}; + +/// RwasmProcessor is a block executor that uses revm to execute blocks or multiple blocks. +/// +/// Output is obtained by calling `take_output_state` function. +/// +/// It is capable of pruning the data that will be written to the database +/// and implemented [PrunableBlockExecutor] traits. +/// +/// It implemented the [BlockExecutor] that give it the ability to take block +/// apply pre state (Cancun system contract call), execute transaction and apply +/// state change and then apply post execution changes (block reward, withdrawals, irregular DAO +/// hardfork state change). And if `execute_and_verify_receipt` is called it will verify the +/// receipt. +/// +/// InspectorStack are used for optional inspecting execution. And it contains +/// various duration of parts of execution. +pub struct RwasmProcessor<'a> { + /// The configured chain-spec + chain_spec: Arc, + env: Env, + /// The collection of receipts. + /// Outer vector stores receipts for each block sequentially. + /// The inner vector stores receipts ordered by transaction number. + /// + /// If receipt is None it means it is pruned. + receipts: Vec>>, + /// First block will be initialized to `None` + /// and be set to the block number of first block executed. + first_block: Option, + /// The maximum known block. + tip: Option, + /// Pruning configuration. + prune_modes: PruneModes, + /// Memoized address pruning filter. + /// Empty implies that there is going to be addresses to include in the filter in a future + /// block. None means there isn't any kind of configuration. + pruning_address_filter: Option<(u64, Vec
)>, + /// Execution stats + stats: BlockExecutorStats, + marker: PhantomData<&'a dyn Any>, +} + +impl<'a> RwasmProcessor<'a> { + /// Return chain spec. + pub fn chain_spec(&self) -> &Arc { + &self.chain_spec + } + + /// Create a new pocessor with the given chain spec. + pub fn new(chain_spec: Arc) -> Self { + RwasmProcessor { + chain_spec, + env: Default::default(), + receipts: Vec::new(), + first_block: None, + tip: None, + prune_modes: PruneModes::none(), + pruning_address_filter: None, + stats: BlockExecutorStats::default(), + marker: Default::default(), + } + } + + /// Creates a new executor from the given chain spec and database. + pub fn new_with_db( + chain_spec: Arc, + db: StateProviderDatabase, + ) -> Self { + RwasmProcessor::new_with_state(chain_spec) + } + + /// Create a new EVM processor with the given revm state. + pub fn new_with_state(chain_spec: Arc) -> Self { + RwasmProcessor { + chain_spec, + env: Default::default(), + receipts: Vec::new(), + first_block: None, + tip: None, + prune_modes: PruneModes::none(), + pruning_address_filter: None, + stats: BlockExecutorStats::default(), + marker: Default::default(), + } + } + + /// Returns a reference to the database + pub fn db_mut(&mut self) -> &mut StateDBBox<'a, Error> { + panic!("not supported yet") + } + + fn recover_senders( + &mut self, + body: &[TransactionSigned], + senders: Option>, + ) -> Result, BlockExecutionError> { + if let Some(senders) = senders { + if body.len() == senders.len() { + Ok(senders) + } else { + Err(BlockValidationError::SenderRecoveryError.into()) + } + } else { + let time = Instant::now(); + let ret = TransactionSigned::recover_signers(body, body.len()) + .ok_or(BlockValidationError::SenderRecoveryError.into()); + self.stats.sender_recovery_duration += time.elapsed(); + ret + } + } + + /// Initializes the config and block env. + fn init_env(&mut self, header: &Header, total_difficulty: U256) { + // Set state clear flag. + let state_clear_flag = + self.chain_spec.fork(Hardfork::SpuriousDragon).active_at_block(header.number); + + self.db_mut().set_state_clear_flag(state_clear_flag); + + fill_cfg_and_block_env( + &mut self.env.cfg, + &mut self.env.block, + &self.chain_spec, + header, + total_difficulty, + ); + } + + /// Apply post execution state changes, including block rewards, withdrawals, and irregular DAO + /// hardfork state change. + pub fn apply_post_execution_state_change( + &mut self, + block: &Block, + total_difficulty: U256, + ) -> Result<(), BlockExecutionError> { + let balance_increments = post_block_balance_increments( + &self.chain_spec, + block.number, + block.difficulty, + block.beneficiary, + block.timestamp, + total_difficulty, + &block.ommers, + block.withdrawals.as_deref(), + ); + + // increment balances + self.db_mut() + .increment_balances(balance_increments.into_iter().map(|(k, v)| (k, v))) + .map_err(|_| BlockValidationError::IncrementBalanceFailed)?; + + Ok(()) + } + + /// Runs a single transaction in the configured environment and proceeds + /// to return the result and state diff (without applying it). + /// + /// Assumes the rest of the block environment has been filled via `init_block_env`. + pub fn transact( + &mut self, + transaction: &TransactionSigned, + sender: Address, + ) -> Result { + // Fill revm structure. + fill_tx_env(&mut self.env.tx, transaction, sender); + // main execution. + let runtime_linker = Runtime::new_linker(); + let result = Runtime::run_with_linker(&[], &[], &runtime_linker, true); + // some internal error, we can't handle this + if result.is_err() { + let res = ResultAndState { + result: ExecutionResult::Revert { gas_used: 0, output: Default::default() }, + state: Default::default(), + }; + return Ok(res); + } + let result = result.unwrap(); + let boxed_output = Box::leak(result.data().output().clone().into_boxed_slice()); + Ok(ResultAndState { + result: ExecutionResult::Success { + reason: Eval::Stop, + gas_used: 0, + gas_refunded: 0, + logs: vec![], + output: Output::Call(Bytes::from_static(boxed_output)), + }, + state: Default::default(), + }) + } + + /// Runs the provided transactions and commits their state to the run-time database. + /// + /// The returned [BundleStateWithReceipts] can be used to persist the changes to disk, and + /// contains the changes made by each transaction. + /// + /// The changes in [BundleStateWithReceipts] have a transition ID associated with them: there is + /// one transition ID for each transaction (with the first executed tx having transition ID + /// 0, and so on). + /// + /// The second returned value represents the total gas used by this block of transactions. + pub fn execute_transactions( + &mut self, + block: &Block, + total_difficulty: U256, + senders: Option>, + ) -> Result<(Vec, u64), BlockExecutionError> { + // perf: do not execute empty blocks + if block.body.is_empty() { + return Ok((Vec::new(), 0)); + } + + let senders = self.recover_senders(&block.body, senders)?; + + self.init_env(&block.header, total_difficulty); + + let mut cumulative_gas_used = 0; + let mut receipts = Vec::with_capacity(block.body.len()); + for (transaction, sender) in block.body.iter().zip(senders) { + let time = Instant::now(); + // The sum of the transaction’s gas limit, Tg, and the gas utilized in this block prior, + // must be no greater than the block’s gasLimit. + let block_available_gas = block.header.gas_limit - cumulative_gas_used; + if transaction.gas_limit() > block_available_gas { + return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { + transaction_gas_limit: transaction.gas_limit(), + block_available_gas, + } + .into()); + } + // Execute transaction. + let ResultAndState { result, state } = self.transact(transaction, sender)?; + trace!( + target: "evm", + ?transaction, ?result, ?state, + "Executed transaction" + ); + self.stats.execution_duration += time.elapsed(); + let time = Instant::now(); + + self.db_mut().commit(state); + + self.stats.apply_state_duration += time.elapsed(); + + // append gas used + cumulative_gas_used += result.gas_used(); + + // Push transaction changeset and calculate header bloom filter for receipt. + receipts.push(Receipt { + tx_type: transaction.tx_type(), + // Success flag was added in `EIP-658: Embedding transaction status code in + // receipts`. + success: result.is_success(), + cumulative_gas_used, + // convert to reth log + logs: result.into_logs().into_iter().map(into_reth_log).collect(), + }); + } + + Ok((receipts, cumulative_gas_used)) + } + + /// Execute the block, verify gas usage and apply post-block state changes. + fn execute_inner( + &mut self, + block: &Block, + total_difficulty: U256, + senders: Option>, + ) -> Result, BlockExecutionError> { + let (receipts, cumulative_gas_used) = + self.execute_transactions(block, total_difficulty, senders)?; + + // Check if gas used matches the value set in header. + if block.gas_used != cumulative_gas_used { + return Err(BlockValidationError::BlockGasUsed { + got: cumulative_gas_used, + expected: block.gas_used, + gas_spent_by_tx: self + .receipts + .last() + .map(|block_r| { + block_r + .iter() + .enumerate() + .map(|(id, tx_r)| { + ( + id as u64, + tx_r.as_ref() + .expect("receipts have not been pruned") + .cumulative_gas_used, + ) + }) + .collect() + }) + .unwrap_or_default(), + } + .into()); + } + let time = Instant::now(); + self.apply_post_execution_state_change(block, total_difficulty)?; + self.stats.apply_post_execution_state_changes_duration += time.elapsed(); + + let time = Instant::now(); + let retention = if self.tip.map_or(true, |tip| { + !self.prune_modes.should_prune_account_history(block.number, tip) + && !self.prune_modes.should_prune_storage_history(block.number, tip) + }) { + BundleRetention::Reverts + } else { + BundleRetention::PlainState + }; + self.db_mut().merge_transitions(retention); + self.stats.merge_transitions_duration += time.elapsed(); + + if self.first_block.is_none() { + self.first_block = Some(block.number); + } + + Ok(receipts) + } + + /// Save receipts to the executor. + pub fn save_receipts(&mut self, receipts: Vec) -> Result<(), BlockExecutionError> { + let mut receipts = receipts.into_iter().map(Option::Some).collect(); + // Prune receipts if necessary. + self.prune_receipts(&mut receipts)?; + // Save receipts. + self.receipts.push(receipts); + Ok(()) + } + + /// Prune receipts according to the pruning configuration. + fn prune_receipts( + &mut self, + receipts: &mut Vec>, + ) -> Result<(), PrunePartError> { + let (first_block, tip) = match self.first_block.zip(self.tip) { + Some((block, tip)) => (block, tip), + _ => return Ok(()), + }; + + let block_number = first_block + self.receipts.len() as u64; + + // Block receipts should not be retained + if self.prune_modes.receipts == Some(PruneMode::Full) || + // [`PrunePart::Receipts`] takes priority over [`PrunePart::ContractLogs`] + self.prune_modes.should_prune_receipts(block_number, tip) + { + receipts.clear(); + return Ok(()); + } + + // All receipts from the last 128 blocks are required for blockchain tree, even with + // [`PrunePart::ContractLogs`]. + let prunable_receipts = + PruneMode::Distance(MINIMUM_PRUNING_DISTANCE).should_prune(block_number, tip); + if !prunable_receipts { + return Ok(()); + } + + let contract_log_pruner = self.prune_modes.receipts_log_filter.group_by_block(tip, None)?; + + if !contract_log_pruner.is_empty() { + let (prev_block, filter) = self.pruning_address_filter.get_or_insert((0, Vec::new())); + for (_, addresses) in contract_log_pruner.range(*prev_block..=block_number) { + filter.extend(addresses.iter().copied()); + } + } + + for receipt in receipts.iter_mut() { + let inner_receipt = receipt.as_ref().expect("receipts have not been pruned"); + + // If there is an address_filter, and it does not contain any of the + // contract addresses, then remove this receipts + if let Some((_, filter)) = &self.pruning_address_filter { + if !inner_receipt.logs.iter().any(|log| filter.contains(&log.address)) { + receipt.take(); + } + } + } + + Ok(()) + } +} + +impl<'a> BlockExecutor for RwasmProcessor<'a> { + fn execute( + &mut self, + block: &Block, + total_difficulty: U256, + senders: Option>, + ) -> Result<(), BlockExecutionError> { + let receipts = self.execute_inner(block, total_difficulty, senders)?; + self.save_receipts(receipts) + } + + fn execute_and_verify_receipt( + &mut self, + block: &Block, + total_difficulty: U256, + senders: Option>, + ) -> Result<(), BlockExecutionError> { + // execute block + let receipts = self.execute_inner(block, total_difficulty, senders)?; + + // TODO Before Byzantium, receipts contained state root that would mean that expensive + // operation as hashing that is needed for state root got calculated in every + // transaction This was replaced with is_success flag. + // See more about EIP here: https://eips.ethereum.org/EIPS/eip-658 + if self.chain_spec.fork(Hardfork::Byzantium).active_at_block(block.header.number) { + let time = Instant::now(); + if let Err(error) = + verify_receipt(block.header.receipts_root, block.header.logs_bloom, receipts.iter()) + { + debug!(target: "evm", ?error, ?receipts, "receipts verification failed"); + return Err(error); + }; + self.stats.receipt_root_duration += time.elapsed(); + } + + self.save_receipts(receipts) + } + + fn take_output_state(&mut self) -> BundleStateWithReceipts { + // let receipts = std::mem::take(&mut self.receipts); + // BundleStateWithReceipts::new( + // self.evm.db().unwrap().take_bundle(), + // receipts, + // self.first_block.unwrap_or_default(), + // ) + panic!("not implemented yet") + } + + fn stats(&self) -> BlockExecutorStats { + self.stats.clone() + } + + fn size_hint(&self) -> Option { + // self.evm.db.as_ref().map(|db| db.bundle_size_hint()) + panic!("not implemented yet") + } +} + +impl<'a> PrunableBlockExecutor for RwasmProcessor<'a> { + fn set_tip(&mut self, tip: BlockNumber) { + self.tip = Some(tip); + } + + fn set_prune_modes(&mut self, prune_modes: PruneModes) { + self.prune_modes = prune_modes; + } +} + +/// Verify receipts +pub fn verify_receipt<'a>( + expected_receipts_root: H256, + expected_logs_bloom: Bloom, + receipts: impl Iterator + Clone, +) -> Result<(), BlockExecutionError> { + // Check receipts root. + let receipts_with_bloom = receipts.map(|r| r.clone().into()).collect::>(); + let receipts_root = reth_primitives::proofs::calculate_receipt_root(&receipts_with_bloom); + if receipts_root != expected_receipts_root { + return Err(BlockValidationError::ReceiptRootDiff { + got: receipts_root, + expected: expected_receipts_root, + } + .into()); + } + + // Create header log bloom. + let logs_bloom = receipts_with_bloom.iter().fold(Bloom::zero(), |bloom, r| bloom | r.bloom); + if logs_bloom != expected_logs_bloom { + return Err(BlockValidationError::BloomLogDiff { + expected: Box::new(expected_logs_bloom), + got: Box::new(logs_bloom), + } + .into()); + } + + Ok(()) +} diff --git a/crates/rwasm/src/state_change.rs b/crates/rwasm/src/state_change.rs new file mode 100644 index 000000000..6b4cbeff5 --- /dev/null +++ b/crates/rwasm/src/state_change.rs @@ -0,0 +1,85 @@ +use reth_consensus_common::calc; +use reth_primitives::{Address, ChainSpec, Hardfork, Header, Withdrawal, U256}; +use std::collections::HashMap; + +/// Collect all balance changes at the end of the block. +/// +/// Balance changes might include the block reward, uncle rewards, withdrawals, or irregular +/// state changes (DAO fork). +#[allow(clippy::too_many_arguments)] +#[inline] +pub fn post_block_balance_increments( + chain_spec: &ChainSpec, + block_number: u64, + block_difficulty: U256, + beneficiary: Address, + block_timestamp: u64, + total_difficulty: U256, + ommers: &[Header], + withdrawals: Option<&[Withdrawal]>, +) -> HashMap { + let mut balance_increments = HashMap::new(); + + // Add block rewards if they are enabled. + if let Some(base_block_reward) = + calc::base_block_reward(chain_spec, block_number, block_difficulty, total_difficulty) + { + // Ommer rewards + for ommer in ommers { + *balance_increments.entry(ommer.beneficiary).or_default() += + calc::ommer_reward(base_block_reward, block_number, ommer.number); + } + + // Full block reward + *balance_increments.entry(beneficiary).or_default() += + calc::block_reward(base_block_reward, ommers.len()); + } + + // process withdrawals + insert_post_block_withdrawals_balance_increments( + chain_spec, + block_timestamp, + withdrawals, + &mut balance_increments, + ); + + balance_increments +} + +/// Returns a map of addresses to their balance increments if shanghai is active at the given +/// timestamp. +#[inline] +pub fn post_block_withdrawals_balance_increments( + chain_spec: &ChainSpec, + block_timestamp: u64, + withdrawals: &[Withdrawal], +) -> HashMap { + let mut balance_increments = HashMap::with_capacity(withdrawals.len()); + insert_post_block_withdrawals_balance_increments( + chain_spec, + block_timestamp, + Some(withdrawals), + &mut balance_increments, + ); + balance_increments +} + +/// Applies all withdrawal balance increments if shanghai is active at the given timestamp to the +/// given `balance_increments` map. +#[inline] +pub fn insert_post_block_withdrawals_balance_increments( + chain_spec: &ChainSpec, + block_timestamp: u64, + withdrawals: Option<&[Withdrawal]>, + balance_increments: &mut HashMap, +) { + // Process withdrawals + if chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(block_timestamp) { + if let Some(withdrawals) = withdrawals { + for withdrawal in withdrawals { + *balance_increments.entry(withdrawal.address).or_default() += + withdrawal.amount_wei(); + } + } + } +}