From ac2f3fcd8a3d3e784970ab83c65bd93a656cff00 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 2 Nov 2022 12:59:51 +0100 Subject: [PATCH] feat: Consensus crate and verification functions. (#152) * wip executor * wip * Cleanup added some checks and structure to executor * adding additional block/header checks * add basefee calculation and check * some cleanup * Sanity check test * Test for sanity check * move verification to consensus crate * cleanup * Better Error handling --- Cargo.lock | 51 ++++ Cargo.toml | 1 + crates/consensus/Cargo.toml | 30 +++ crates/consensus/src/config.rs | 24 ++ crates/consensus/src/consensus.rs | 43 +++ crates/consensus/src/lib.rs | 18 ++ crates/consensus/src/proofs.rs | 96 +++++++ crates/consensus/src/verification.rs | 337 ++++++++++++++++++++++++ crates/executor/Cargo.toml | 17 +- crates/executor/src/cfg.rs | 6 - crates/executor/src/config.rs | 5 + crates/executor/src/executor.rs | 52 ++-- crates/executor/src/lib.rs | 5 +- crates/executor/src/revm_wrap.rs | 40 ++- crates/interfaces/src/consensus.rs | 42 ++- crates/interfaces/src/error.rs | 2 +- crates/interfaces/src/executor.rs | 2 +- crates/interfaces/src/provider/block.rs | 15 +- crates/interfaces/src/provider/mod.rs | 2 +- crates/interfaces/src/test_utils.rs | 8 +- crates/libmdbx-rs/src/environment.rs | 4 +- crates/net/discv4/src/lib.rs | 2 +- crates/primitives/Cargo.toml | 1 + crates/primitives/src/block.rs | 4 + crates/primitives/src/header.rs | 6 +- crates/primitives/src/lib.rs | 1 + 26 files changed, 755 insertions(+), 59 deletions(-) create mode 100644 crates/consensus/Cargo.toml create mode 100644 crates/consensus/src/config.rs create mode 100644 crates/consensus/src/consensus.rs create mode 100644 crates/consensus/src/lib.rs create mode 100644 crates/consensus/src/proofs.rs create mode 100644 crates/consensus/src/verification.rs delete mode 100644 crates/executor/src/cfg.rs create mode 100644 crates/executor/src/config.rs diff --git a/Cargo.lock b/Cargo.lock index 256b13e19..659ad061c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1568,6 +1568,12 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "hash-db" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" + [[package]] name = "hash32" version = "0.2.1" @@ -2619,6 +2625,15 @@ dependencies = [ "spki", ] +[[package]] +name = "plain_hasher" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e19e6491bdde87c2c43d70f4c194bc8a758f2eb732df00f61e43f7362e3b4cc" +dependencies = [ + "crunchy", +] + [[package]] name = "plotters" version = "0.3.4" @@ -2989,6 +3004,25 @@ dependencies = [ "codecs-derive", ] +[[package]] +name = "reth-consensus" +version = "0.1.0" +dependencies = [ + "async-trait", + "auto_impl", + "eyre", + "hash-db", + "plain_hasher", + "reth-interfaces", + "reth-primitives", + "reth-rlp", + "rlp", + "sha3", + "thiserror", + "tokio", + "triehash", +] + [[package]] name = "reth-crate-template" version = "0.1.0" @@ -3098,11 +3132,18 @@ name = "reth-executor" version = "0.1.0" dependencies = [ "async-trait", + "auto_impl", "eyre", + "hash-db", + "plain_hasher", "reth-interfaces", "reth-primitives", + "reth-rlp", "revm", + "rlp", + "sha3", "thiserror", + "triehash", ] [[package]] @@ -4393,6 +4434,16 @@ dependencies = [ "syn", ] +[[package]] +name = "triehash" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1631b201eb031b563d2e85ca18ec8092508e262a3196ce9bd10a67ec87b9f5c" +dependencies = [ + "hash-db", + "rlp", +] + [[package]] name = "trust-dns-client" version = "0.20.4" diff --git a/Cargo.toml b/Cargo.toml index d6b599152..53a94b144 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "crate-template", "crates/common/rlp", "crates/common/rlp-derive", + "crates/consensus", "crates/db", "crates/executor", "crates/interfaces", diff --git a/crates/consensus/Cargo.toml b/crates/consensus/Cargo.toml new file mode 100644 index 000000000..aefc34afa --- /dev/null +++ b/crates/consensus/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "reth-consensus" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +repository = "https://github.com/foundry-rs/reth" +readme = "README.md" + +[dependencies] +# reth +reth-primitives = { path = "../primitives" } +reth-interfaces = { path = "../interfaces" } +reth-rlp = {path = "../common/rlp"} + +# common +async-trait = "0.1.57" +thiserror = "1.0.37" +eyre = "0.6.8" +auto_impl = "1.0" +tokio = { version = "1.21.2", features = ["sync"] } + +# proof related +triehash = "0.8" +# See to replace hashers to simplify libraries +plain_hasher = "0.2" +hash-db = "0.15" +# todo replace with faster rlp impl +rlp = { version = "0.5", default-features = false } +# replace with tiny-keccak (it is faster hasher) +sha3 = { version = "0.10", default-features = false } \ No newline at end of file diff --git a/crates/consensus/src/config.rs b/crates/consensus/src/config.rs new file mode 100644 index 000000000..69c52a10b --- /dev/null +++ b/crates/consensus/src/config.rs @@ -0,0 +1,24 @@ +//! Reth block execution/validation configuration and constants +use reth_primitives::BlockNumber; + +/// Initial base fee as defined in: https://eips.ethereum.org/EIPS/eip-1559 +pub const EIP1559_INITIAL_BASE_FEE: u64 = 1_000_000_000; +/// Base fee max change denominator as defined in: https://eips.ethereum.org/EIPS/eip-1559 +pub const EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR: u64 = 8; +/// Elasticity multiplier as defined in: https://eips.ethereum.org/EIPS/eip-1559 +pub const EIP1559_ELASTICITY_MULTIPLIER: u64 = 2; + +/// Configuration for consensus +#[derive(Debug, Clone)] +pub struct Config { + /// EIP-1559 hard fork number + pub london_hard_fork_block: BlockNumber, + /// The Merge/Paris hard fork block number + pub paris_hard_fork_block: BlockNumber, +} + +impl Default for Config { + fn default() -> Self { + Self { london_hard_fork_block: 12965000, paris_hard_fork_block: 15537394 } + } +} diff --git a/crates/consensus/src/consensus.rs b/crates/consensus/src/consensus.rs new file mode 100644 index 000000000..34f72ee17 --- /dev/null +++ b/crates/consensus/src/consensus.rs @@ -0,0 +1,43 @@ +//! Consensus for ethereum network + +use crate::{verification, Config}; +use reth_interfaces::consensus::{Consensus, Error, ForkchoiceState}; +use reth_primitives::{HeaderLocked, H256}; +use tokio::sync::watch; + +/// Ethereum consensus +pub struct EthConsensus { + /// Watcher over the forkchoice state + channel: (watch::Sender, watch::Receiver), + /// Configuration + config: Config, +} + +impl EthConsensus { + /// Create new object + pub fn new(config: Config) -> Self { + Self { + channel: watch::channel(ForkchoiceState { + head_block_hash: H256::zero(), + finalized_block_hash: H256::zero(), + safe_block_hash: H256::zero(), + }), + config, + } + } +} + +impl Consensus for EthConsensus { + fn fork_choice_state(&self) -> watch::Receiver { + self.channel.1.clone() + } + + fn validate_header(&self, header: &HeaderLocked, parent: &HeaderLocked) -> Result<(), Error> { + verification::validate_header_standalone(header, &self.config)?; + verification::validate_header_regarding_parent(parent, header, &self.config) + + // TODO Consensus checks for: + // * mix_hash & nonce PoW stuf + // * extra_data + } +} diff --git a/crates/consensus/src/lib.rs b/crates/consensus/src/lib.rs new file mode 100644 index 000000000..9d9a6e48c --- /dev/null +++ b/crates/consensus/src/lib.rs @@ -0,0 +1,18 @@ +#![warn(missing_docs, unreachable_pub)] +#![deny(unused_must_use, rust_2018_idioms)] +#![doc(test( + no_crate_inject, + attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) +))] + +//! Reth consensus. +pub mod config; +pub mod consensus; +pub mod verification; + +/// Helper function for calculating Merkle proofs and hashes +pub mod proofs; + +pub use config::Config; +pub use consensus::EthConsensus; +pub use reth_interfaces::consensus::Error; diff --git a/crates/consensus/src/proofs.rs b/crates/consensus/src/proofs.rs new file mode 100644 index 000000000..59a0f78f6 --- /dev/null +++ b/crates/consensus/src/proofs.rs @@ -0,0 +1,96 @@ +use hash_db::Hasher; +use plain_hasher::PlainHasher; +use reth_primitives::{Bytes, Header, Log, Receipt, TransactionSigned, H256}; +use reth_rlp::Encodable; +use rlp::RlpStream; +use sha3::{Digest, Keccak256}; +use triehash::sec_trie_root; + +#[derive(Default, Debug, Clone, PartialEq, Eq)] +struct KeccakHasher; +impl Hasher for KeccakHasher { + type Out = H256; + type StdHasher = PlainHasher; + const LENGTH: usize = 32; + fn hash(x: &[u8]) -> Self::Out { + let out = Keccak256::digest(x); + // TODO make more performant, H256 from slice is not good enought. + H256::from_slice(out.as_slice()) + } +} + +/// Calculate Transaction root. Iterate over transaction and create merkle trie of +/// (rlp(index),encoded(tx)) pairs. +pub fn calculate_transaction_root<'a>( + transactions: impl IntoIterator, +) -> H256 { + sec_trie_root::( + transactions + .into_iter() + .enumerate() + .map(|(index, tx)| { + // TODO replace with reth-rlp + let mut stream = RlpStream::new(); + stream.append(&index); + let mut bytes = Vec::new(); + tx.encode(&mut bytes); + (stream.out().freeze().into(), bytes) + }) + .collect::)>>(), + ) +} + +/// Create receipt root for header +pub fn calculate_receipt_root<'a>(receipts: impl IntoIterator) -> H256 { + sec_trie_root::( + receipts + .into_iter() + .enumerate() + .map(|(index, receipt)| { + let mut stream = RlpStream::new(); + stream.append(&index); + let mut bytes = Vec::new(); + receipt.encode(&mut bytes); + (stream.out().freeze().into(), bytes) + }) + .collect::)>>(), + ) +} + +/// Create log hash for header +pub fn calculate_log_root<'a>(logs: impl IntoIterator) -> H256 { + //https://github.com/ethereum/go-ethereum/blob/356bbe343a30789e77bb38f25983c8f2f2bfbb47/cmd/evm/internal/t8ntool/execution.go#L255 + let mut stream = RlpStream::new(); + stream.begin_unbounded_list(); + for log in logs { + stream.begin_list(3); + stream.append(&log.address); + stream.append_list(&log.topics); + stream.append(&log.data); + } + stream.finalize_unbounded_list(); + let out = stream.out().freeze(); + + let out = Keccak256::digest(out); + H256::from_slice(out.as_slice()) +} + +/// Calculate hash over omners/uncles headers +pub fn calculate_omners_root<'a>(_omners: impl IntoIterator) -> H256 { + // RLP Encode + let mut stream = RlpStream::new(); + stream.begin_unbounded_list(); + /* TODO + for omner in omners { + stream.append(omner) + } + */ + stream.finalize_unbounded_list(); + let bytes = stream.out().freeze(); + let out = Keccak256::digest(bytes); + H256::from_slice(out.as_slice()) +} + +// TODO state root + +// TODO bloom diff --git a/crates/consensus/src/verification.rs b/crates/consensus/src/verification.rs new file mode 100644 index 000000000..b50e0c8b9 --- /dev/null +++ b/crates/consensus/src/verification.rs @@ -0,0 +1,337 @@ +//! ALl functions for verification of block +use crate::{config, Config}; +use reth_interfaces::{consensus::Error, provider::HeaderProvider, Result as RethResult}; +use reth_primitives::{BlockLocked, HeaderLocked, TransactionSigned}; +use std::time::SystemTime; + +/// Validate header standalone +pub fn validate_header_standalone( + header: &HeaderLocked, + config: &config::Config, +) -> Result<(), Error> { + // Gas used needs to be less then gas limit. Gas used is going to be check after execution. + if header.gas_used > header.gas_limit { + return Err(Error::HeaderGasUsedExceedsGasLimit { + gas_used: header.gas_used, + gas_limit: header.gas_limit, + }) + } + + // Check if timestamp is in future. Clock can drift but this can be consensus issue. + let present_timestamp = + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs(); + if header.timestamp > present_timestamp { + return Err(Error::TimestampIsInFuture { timestamp: header.timestamp, present_timestamp }) + } + + // Check if base fee is set. + if config.paris_hard_fork_block >= header.number && header.base_fee_per_gas.is_some() { + return Err(Error::BaseFeeMissing) + } + + Ok(()) +} + +/// Validate transactions standlone +pub fn validate_transactions_standalone( + _transactions: &[TransactionSigned], + _config: &Config, +) -> Result<(), Error> { + // TODO + Ok(()) +} + +/// Validate block standalone +pub fn validate_block_standalone(block: &BlockLocked) -> Result<(), Error> { + // check omners hash + let omners_hash = crate::proofs::calculate_omners_root(block.ommers.iter().map(|h| h.as_ref())); + if block.header.ommers_hash != omners_hash { + return Err(Error::BodyOmmnersHashDiff { + got: omners_hash, + expected: block.header.ommers_hash, + }) + } + + // check transaction root + let transaction_root = crate::proofs::calculate_transaction_root(block.body.iter()); + if block.header.transactions_root != transaction_root { + return Err(Error::BodyTransactionRootDiff { + got: transaction_root, + expected: block.header.transactions_root, + }) + } + + // TODO transaction verification, Maybe make it configurable as in check only + // signatures/limits/types + + // check if all transactions limit does not goes over block limit + + // check receipts root + let receipts_root = crate::proofs::calculate_receipt_root(block.receipts.iter()); + if block.header.receipts_root != receipts_root { + return Err(Error::BodyReceiptsRootDiff { + got: receipts_root, + expected: block.header.receipts_root, + }) + } + + Ok(()) +} + +/// Calculate base fee for next block. EIP-1559 spec +pub fn calculate_next_block_base_fee(gas_used: u64, gas_limit: u64, base_fee: u64) -> u64 { + let gas_target = gas_limit / config::EIP1559_ELASTICITY_MULTIPLIER; + + if gas_used == gas_target { + return base_fee + } + if gas_used > gas_target { + let gas_used_delta = gas_used - gas_target; + let base_fee_delta = std::cmp::max( + 1, + base_fee as u128 * gas_used_delta as u128 / + gas_target as u128 / + config::EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR as u128, + ); + base_fee + (base_fee_delta as u64) + } else { + let gas_used_delta = gas_target - gas_used; + let base_fee_per_gas_delta = base_fee as u128 * gas_used_delta as u128 / + gas_target as u128 / + config::EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR as u128; + + base_fee.saturating_sub(base_fee_per_gas_delta as u64) + } +} + +/// Validate block in regards to parent +pub fn validate_header_regarding_parent( + parent: &HeaderLocked, + child: &HeaderLocked, + config: &config::Config, +) -> Result<(), Error> { + // Parent number is consistent. + if parent.number + 1 != child.number { + return Err(Error::ParentBlockNumberMissmatch { + parent_block_number: parent.number, + block_number: child.number, + }) + } + + // timestamp in past check + if child.timestamp < parent.timestamp { + return Err(Error::TimestampIsInPast { + parent_timestamp: parent.timestamp, + timestamp: child.timestamp, + }) + } + + // difficulty check is done by consensus. + if config.paris_hard_fork_block > child.number { + // TODO how this needs to be checked? As ice age did increment it by some formula + } + + let mut parent_gas_limit = parent.gas_limit; + + // By consensus, gas_limit is multiplied by elasticity (*2) on + // on exact block that hardfork happens. + if config.london_hard_fork_block == child.number { + parent_gas_limit = parent.gas_limit * config::EIP1559_ELASTICITY_MULTIPLIER; + } + + // Check gas limit, max diff between child/parent gas_limit should be max_diff=parent_gas/1024 + if child.gas_limit > parent_gas_limit { + if child.gas_limit - parent_gas_limit >= parent_gas_limit / 1024 { + return Err(Error::GasLimitInvalidIncrease { + parent_gas_limit, + child_gas_limit: child.gas_limit, + }) + } + } else if parent_gas_limit - child.gas_limit >= parent_gas_limit / 1024 { + return Err(Error::GasLimitInvalidDecrease { + parent_gas_limit, + child_gas_limit: child.gas_limit, + }) + } + + // EIP-1559 check base fee + if child.number >= config.london_hard_fork_block { + let base_fee = child.base_fee_per_gas.ok_or(Error::BaseFeeMissing)?; + + let expected_base_fee = if config.london_hard_fork_block == child.number { + config::EIP1559_INITIAL_BASE_FEE + } else { + // This BaseFeeMissing will not happen as previous blocks are checked to have them. + calculate_next_block_base_fee( + parent.gas_used, + parent.gas_limit, + parent.base_fee_per_gas.ok_or(Error::BaseFeeMissing)?, + ) + }; + if expected_base_fee != base_fee { + return Err(Error::BaseFeeDiff { expected: expected_base_fee, got: base_fee }) + } + } + + Ok(()) +} + +/// Validate block in regards to chain (parent) +/// +/// Checks: +/// If we already know the block. +/// If parent is known +/// +/// Returns parent block header +pub fn validate_block_regarding_chain( + block: &BlockLocked, + provider: &PROV, +) -> RethResult { + let hash = block.header.hash(); + + // Check if block is known. + if provider.is_known(&hash)? { + return Err(Error::BlockKnown { hash, number: block.header.number }.into()) + } + + // Check if parent is known. + let parent = provider + .header(&block.parent_hash)? + .ok_or(Error::ParentUnknown { hash: block.parent_hash })?; + + // Return parent header. + Ok(parent.lock()) +} + +/// Full validation of block before execution. +pub fn full_validation( + block: &BlockLocked, + provider: PROV, + config: &Config, +) -> RethResult<()> { + validate_header_standalone(&block.header, config)?; + validate_block_standalone(block)?; + let parent = validate_block_regarding_chain(block, &provider)?; + validate_header_regarding_parent(&parent, &block.header, config)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use reth_interfaces::Result; + use reth_primitives::{hex_literal::hex, BlockHash, Header}; + + use super::*; + + #[test] + fn calculate_base_fee_success() { + let base_fee = [ + 1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0, + 1, 2, + ]; + let gas_used = [ + 10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000, + 10000000, + ]; + let gas_limit = [ + 10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000, + 18000000, 18000000, + ]; + let next_base_fee = [ + 1125000000, 1083333333, 1053571428, 1179939062, 1116028649, 918084097, 1063811730, 1, + 2, 3, + ]; + + for i in 0..base_fee.len() { + assert_eq!( + next_base_fee[i], + calculate_next_block_base_fee(gas_used[i], gas_limit[i], base_fee[i]) + ); + } + } + + struct Provider { + is_known: bool, + parent: Option
, + } + + impl Provider { + /// New provider with parent + fn new(parent: Option
) -> Self { + Self { is_known: false, parent } + } + /// New provider where is_known is always true + fn new_known() -> Self { + Self { is_known: true, parent: None } + } + } + + impl HeaderProvider for Provider { + fn is_known(&self, _block_hash: &BlockHash) -> Result { + Ok(self.is_known) + } + + fn header(&self, _block_number: &BlockHash) -> Result> { + Ok(self.parent.clone()) + } + } + /// got test block + fn mock_block() -> (BlockLocked, Header) { + // https://etherscan.io/block/15867168 where transaction root and receipts root are cleared + // empty merkle tree: 0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421 + + let header = Header { + parent_hash: hex!("859fad46e75d9be177c2584843501f2270c7e5231711e90848290d12d7c6dcdd").into(), + ommers_hash: hex!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347").into(), + beneficiary: hex!("4675c7e5baafbffbca748158becba61ef3b0a263").into(), + state_root: hex!("8337403406e368b3e40411138f4868f79f6d835825d55fd0c2f6e17b1a3948e9").into(), + transactions_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(), + receipts_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(), + logs_bloom: hex!("002400000000004000220000800002000000000000000000000000000000100000000000000000100000000000000021020000000800000006000000002100040000000c0004000000000008000008200000000000000000000000008000000001040000020000020000002000000800000002000020000000022010000000000000010002001000000000020200000000000001000200880000004000000900020000000000020000000040000000000000000000000000000080000000000001000002000000000000012000200020000000000000001000000000000020000010321400000000100000000000000000000000000000400000000000000000").into(), + difficulty: 0x00.into(), // total diffuculty: 0xc70d815d562d3cfa955).into(), + number: 0xf21d20, + gas_limit: 0x1c9c380, + gas_used: 0x6e813, + timestamp: 0x635f9657, + extra_data: hex!("")[..].into(), + mix_hash: hex!("f8c29910a0a2fd65b260d83ffa2547a6db279095d109a6e64527d14035263cfc").into(), + nonce: 0x0000000000000000, + base_fee_per_gas: 0x28f0001df.into(), + }; + // size: 0x9b5 + + let mut parent = header.clone(); + parent.gas_used = 17763076; + parent.gas_limit = 30000000; + parent.base_fee_per_gas = Some(0x28041f7f5); + parent.number = parent.number - 1; + + let ommers = Vec::new(); + let receipts = Vec::new(); + let body = Vec::new(); + + (BlockLocked { header: header.lock(), body, receipts, ommers }, parent) + } + + #[test] + fn sanity_check() { + let (block, parent) = mock_block(); + let provider = Provider::new(Some(parent)); + let config = Config::default(); + + assert_eq!(full_validation(&block, provider, &config), Ok(()), "Validation should pass"); + } + + #[test] + fn validate_known_block() { + let (block, _) = mock_block(); + let provider = Provider::new_known(); + let config = Config::default(); + + assert_eq!( + full_validation(&block, provider, &config), + Err(Error::BlockKnown { hash: block.hash(), number: block.number }.into()), + "Should fail with error" + ); + } +} diff --git a/crates/executor/Cargo.toml b/crates/executor/Cargo.toml index 6444fdaee..b811b8a81 100644 --- a/crates/executor/Cargo.toml +++ b/crates/executor/Cargo.toml @@ -7,9 +7,24 @@ repository = "https://github.com/foundry-rs/reth" readme = "README.md" [dependencies] -revm = "2.1" +# reth reth-primitives = { path = "../primitives" } reth-interfaces = { path = "../interfaces" } +reth-rlp = {path = "../common/rlp"} + +revm = "2.1" + +# common async-trait = "0.1.57" thiserror = "1.0.37" eyre = "0.6.8" +auto_impl = "1.0" + +triehash = "0.8" +# See to replace hashers to simplify libraries +plain_hasher = "0.2" +hash-db = "0.15" +# todo replace with faster rlp impl +rlp = { version = "0.5", default-features = false } +# replace with tiny-keccak (it is faster hasher) +sha3 = { version = "0.10", default-features = false } \ No newline at end of file diff --git a/crates/executor/src/cfg.rs b/crates/executor/src/cfg.rs deleted file mode 100644 index 61160412d..000000000 --- a/crates/executor/src/cfg.rs +++ /dev/null @@ -1,6 +0,0 @@ -/// Configuration for executor (TODO) -#[derive(Debug, Clone)] -pub struct Config { - /// Example - pub example: bool, -} diff --git a/crates/executor/src/config.rs b/crates/executor/src/config.rs new file mode 100644 index 000000000..42c7411b1 --- /dev/null +++ b/crates/executor/src/config.rs @@ -0,0 +1,5 @@ +//! Reth block execution/validation configuration and constants + +/// Configuration for executor +#[derive(Debug, Clone)] +pub struct Config {} diff --git a/crates/executor/src/executor.rs b/crates/executor/src/executor.rs index 7486668a6..e5ec0d483 100644 --- a/crates/executor/src/executor.rs +++ b/crates/executor/src/executor.rs @@ -1,41 +1,53 @@ -use crate::{revm_wrap, Config}; -use reth_interfaces::{ - consensus::Consensus, - executor::{BlockExecutor, Error, ExecutorDb}, +use crate::{ + revm_wrap::{self, State, SubState}, + Config, }; +use reth_interfaces::executor::{BlockExecutor, Error, ExecutorDb}; use reth_primitives::BlockLocked; -use revm::{db::EmptyDB, AnalysisKind, Env, SpecId}; +use revm::{AnalysisKind, SpecId, EVM}; /// Main block executor pub struct Executor { /// Configuration, Spec and optional flags. pub config: Config, - /// Database - pub db: Box, - /// Consensus - pub consensus: Box, } impl Executor { /// Create new Executor - pub fn new(config: Config, db: Box, consensus: Box) -> Self { - Self { config, db, consensus } + pub fn new(config: Config) -> Self { + Self { config } } /// Verify block. Execute all transaction and compare results. - pub fn verify(&self, block: &BlockLocked) -> Result<(), Error> { - let mut env = Env::default(); - env.cfg.chain_id = 1.into(); - env.cfg.spec_id = SpecId::LATEST; - env.cfg.perf_all_precompiles_have_balance = true; - env.cfg.perf_analyse_created_bytecodes = AnalysisKind::Raw; + pub fn verify(&self, block: &BlockLocked, db: DB) -> Result<(), Error> { + let db = SubState::new(State::new(db)); + let mut evm = EVM::new(); + evm.database(db); - revm_wrap::fill_block_env(&mut env.block, block); + evm.env.cfg.chain_id = 1.into(); + evm.env.cfg.spec_id = SpecId::LATEST; + evm.env.cfg.perf_all_precompiles_have_balance = true; + evm.env.cfg.perf_analyse_created_bytecodes = AnalysisKind::Raw; - let _database = revm_wrap::TempStateDb::new(EmptyDB::default()); + revm_wrap::fill_block_env(&mut evm.env.block, block); for transaction in block.body.iter() { - revm_wrap::fill_tx_env(&mut env.tx, transaction.as_ref()); + // TODO Check if Transaction is new + revm_wrap::fill_tx_env(&mut evm.env.tx, transaction.as_ref()); + + let res = evm.transact_commit(); + + if res.exit_reason == revm::Return::FatalExternalError { + // stop executing. Fatal error thrown from database + } + + // calculate commulative gas used + + // create receipt + // bloom filter from logs + + // Receipt outcome EIP-658: Embedding transaction status code in receipts + // EIP-658 supperseeded EIP-98 in Byzantium fork } Err(Error::VerificationFailed) diff --git a/crates/executor/src/lib.rs b/crates/executor/src/lib.rs index 2853d2ab6..296fac40a 100644 --- a/crates/executor/src/lib.rs +++ b/crates/executor/src/lib.rs @@ -7,10 +7,9 @@ //! Reth executor executes transaction in block of data. -mod cfg; +pub mod config; /// Executor pub mod executor; /// Wrapper around revm database and types pub mod revm_wrap; - -pub use cfg::Config; +pub use config::Config; diff --git a/crates/executor/src/revm_wrap.rs b/crates/executor/src/revm_wrap.rs index a05e87ba9..c3113f4c1 100644 --- a/crates/executor/src/revm_wrap.rs +++ b/crates/executor/src/revm_wrap.rs @@ -1,21 +1,43 @@ use reth_interfaces::executor::ExecutorDb; use reth_primitives::{BlockLocked, Transaction, TransactionKind, H160, H256, U256}; use revm::{ - db::{CacheDB, Database, EmptyDB}, + db::{CacheDB, DatabaseRef}, BlockEnv, TransactTo, TxEnv, }; use std::convert::Infallible; -/// Temporary stateDB TODO -pub type TempStateDb = CacheDB; +/// SubState of database. Uses revm internal cache with binding to reth DbExecutor trait. +pub type SubState = CacheDB>; /// Wrapper around ExeuctorDb that implements revm database trait -pub struct Wrapper<'a>(&'a dyn ExecutorDb); +pub struct State(DB); -impl<'a> Database for Wrapper<'a> { +impl State { + /// Create new State with generic ExecutorDb. + 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 DbExecutable. + pub fn into_inner(self) -> DB { + self.0 + } +} + +impl DatabaseRef for State { type Error = Infallible; - fn basic(&mut self, address: H160) -> Result, Self::Error> { + fn basic(&self, address: H160) -> Result, Self::Error> { Ok(self.0.basic_account(address).map(|account| revm::AccountInfo { balance: account.balance, nonce: account.nonce, @@ -24,19 +46,19 @@ impl<'a> Database for Wrapper<'a> { })) } - fn code_by_hash(&mut self, code_hash: H256) -> Result { + fn code_by_hash(&self, code_hash: H256) -> Result { let (bytecode, size) = self.0.bytecode_by_hash(code_hash).unwrap_or_default(); Ok(unsafe { revm::Bytecode::new_checked(bytecode.0, size, Some(code_hash)) }) } - fn storage(&mut self, address: H160, index: U256) -> Result { + fn storage(&self, address: H160, index: U256) -> Result { let mut h_index = H256::zero(); index.to_big_endian(h_index.as_bytes_mut()); Ok(U256::from_big_endian(self.0.storage(address, h_index).unwrap_or_default().as_ref())) } - fn block_hash(&mut self, number: U256) -> Result { + fn block_hash(&self, number: U256) -> Result { Ok(self.0.block_hash(number).unwrap_or_default()) } } diff --git a/crates/interfaces/src/consensus.rs b/crates/interfaces/src/consensus.rs index 8de389b5f..9757086cb 100644 --- a/crates/interfaces/src/consensus.rs +++ b/crates/interfaces/src/consensus.rs @@ -1,6 +1,5 @@ use async_trait::async_trait; -use reth_primitives::Header; -use thiserror::Error; +use reth_primitives::{BlockHash, BlockNumber, HeaderLocked, H256}; use tokio::sync::watch::Receiver; /// Re-export forkchoice state @@ -15,13 +14,40 @@ pub trait Consensus: Send + Sync { fn fork_choice_state(&self) -> Receiver; /// Validate if header is correct and follows consensus specification - fn validate_header(&self, header: &Header, parent: &Header) -> Result<(), Error>; + fn validate_header(&self, header: &HeaderLocked, parent: &HeaderLocked) -> Result<(), Error>; } -/// Consensus errors (TODO) -#[derive(Error, Debug)] +/// Consensus Errors +#[allow(missing_docs)] +#[derive(thiserror::Error, Debug, PartialEq, Eq, Clone)] pub enum Error { - /// Explanatory - #[error("Example of consensus error")] - ConsensusError, + #[error("Block used gas ({gas_used:?}) is greater then gas limit ({gas_limit:?})")] + HeaderGasUsedExceedsGasLimit { gas_used: u64, gas_limit: u64 }, + #[error("Block ommner hash ({got:?}) is different then expected: ({expected:?})")] + BodyOmmnersHashDiff { got: H256, expected: H256 }, + #[error("Block transaction root ({got:?}) is different then expected: ({expected:?})")] + BodyTransactionRootDiff { got: H256, expected: H256 }, + #[error("Block receipts root ({got:?}) is different then expected: ({expected:?})")] + BodyReceiptsRootDiff { got: H256, expected: H256 }, + #[error("Block with [hash:{hash:?},number: {number:}] is already known")] + BlockKnown { hash: BlockHash, number: BlockNumber }, + #[error("Block parent [hash:{hash:?}] is not known")] + ParentUnknown { hash: BlockHash }, + #[error("Block number {block_number:?} is missmatch with parent block number {parent_block_number:?}")] + ParentBlockNumberMissmatch { parent_block_number: BlockNumber, block_number: BlockNumber }, + #[error( + "Block timestamp {timestamp:?} is in past in comparison with parent timestamp {parent_timestamp:?}" + )] + TimestampIsInPast { parent_timestamp: u64, timestamp: u64 }, + #[error("Block timestamp {timestamp:?} is in future in comparison of our clock time {present_timestamp:?}")] + TimestampIsInFuture { timestamp: u64, present_timestamp: u64 }, + // TODO make better error msg :) + #[error("Child gas_limit {child_gas_limit:?} max increase is {parent_gas_limit}/1024")] + GasLimitInvalidIncrease { parent_gas_limit: u64, child_gas_limit: u64 }, + #[error("Child gas_limit {child_gas_limit:?} max decrease is {parent_gas_limit}/1024")] + GasLimitInvalidDecrease { parent_gas_limit: u64, child_gas_limit: u64 }, + #[error("Base fee missing")] + BaseFeeMissing, + #[error("Block base fee ({got:?}) is different then expected: ({expected:?})")] + BaseFeeDiff { expected: u64, got: u64 }, } diff --git a/crates/interfaces/src/error.rs b/crates/interfaces/src/error.rs index d1d3f5fb5..67925c6ce 100644 --- a/crates/interfaces/src/error.rs +++ b/crates/interfaces/src/error.rs @@ -2,7 +2,7 @@ pub type Result = std::result::Result; /// Core error variants possible when interacting with the blockchain -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)] #[allow(missing_docs)] pub enum Error { #[error(transparent)] diff --git a/crates/interfaces/src/executor.rs b/crates/interfaces/src/executor.rs index 99ae10c87..925d7edd4 100644 --- a/crates/interfaces/src/executor.rs +++ b/crates/interfaces/src/executor.rs @@ -14,7 +14,7 @@ pub trait BlockExecutor { } /// BlockExecutor Errors -#[derive(Error, Debug, Clone)] +#[derive(Error, Debug, Clone, PartialEq, Eq)] pub enum Error { /// Example of error #[error("Example of error.")] diff --git a/crates/interfaces/src/provider/block.rs b/crates/interfaces/src/provider/block.rs index 34d12cea2..f8c8763e8 100644 --- a/crates/interfaces/src/provider/block.rs +++ b/crates/interfaces/src/provider/block.rs @@ -1,9 +1,22 @@ use crate::Result; +use auto_impl::auto_impl; use reth_primitives::{ rpc::{BlockId, BlockNumber}, - Block, H256, U256, + Block, BlockHash, Header, H256, U256, }; +/// Client trait for fetching `Header` related data. +#[auto_impl(&)] +pub trait HeaderProvider: Send + Sync + 'static { + /// Check if block is known + fn is_known(&self, block_hash: &BlockHash) -> Result { + self.header(block_hash).map(|header| header.is_some()) + } + + /// Get header by block hash + fn header(&self, block_hash: &BlockHash) -> Result>; +} + /// Client trait for fetching `Block` related data. pub trait BlockProvider: Send + Sync + 'static { /// Returns the current info for the chain. diff --git a/crates/interfaces/src/provider/mod.rs b/crates/interfaces/src/provider/mod.rs index eaebb02f4..e3cc2f924 100644 --- a/crates/interfaces/src/provider/mod.rs +++ b/crates/interfaces/src/provider/mod.rs @@ -1,5 +1,5 @@ mod block; mod storage; -pub use block::BlockProvider; +pub use block::{BlockProvider, HeaderProvider}; pub use storage::StorageProvider; diff --git a/crates/interfaces/src/test_utils.rs b/crates/interfaces/src/test_utils.rs index dcc351812..edb4495e2 100644 --- a/crates/interfaces/src/test_utils.rs +++ b/crates/interfaces/src/test_utils.rs @@ -155,9 +155,13 @@ impl Consensus for TestConsensus { self.channel.1.clone() } - fn validate_header(&self, _header: &Header, _parent: &Header) -> Result<(), consensus::Error> { + fn validate_header( + &self, + _header: &HeaderLocked, + _parent: &HeaderLocked, + ) -> Result<(), consensus::Error> { if self.fail_validation { - Err(consensus::Error::ConsensusError) + Err(consensus::Error::BaseFeeMissing) } else { Ok(()) } diff --git a/crates/libmdbx-rs/src/environment.rs b/crates/libmdbx-rs/src/environment.rs index 4a98d3726..3d73e02c4 100644 --- a/crates/libmdbx-rs/src/environment.rs +++ b/crates/libmdbx-rs/src/environment.rs @@ -172,8 +172,8 @@ where /// of used pages as well as free pages in this environment. /// /// ``` - /// # use libmdbx::Environment; - /// # use libmdbx::NoWriteMap; + /// # use reth_libmdbx::Environment; + /// # use reth_libmdbx::NoWriteMap; /// let dir = tempfile::tempdir().unwrap(); /// let env = Environment::::new().open(dir.path()).unwrap(); /// let info = env.info().unwrap(); diff --git a/crates/net/discv4/src/lib.rs b/crates/net/discv4/src/lib.rs index 6e192448e..a0638bcf3 100644 --- a/crates/net/discv4/src/lib.rs +++ b/crates/net/discv4/src/lib.rs @@ -159,7 +159,7 @@ impl Discv4 { /// let(discv4, mut service) = Discv4::bind(socket, local_enr, secret_key, config).await.unwrap(); /// /// // get an update strea - /// let mut updates = service.update_stream(); + /// let updates = service.update_stream(); /// /// let _handle = service.spawn(); /// diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 1a991ef7b..f5549e4b6 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -28,6 +28,7 @@ thiserror = "1" sucds = "0.5.0" arbitrary = { version = "1.1.7", features = ["derive"], optional = true} hex = "0.4" +hex-literal = "0.3" [dev-dependencies] arbitrary = { version = "1.1.7", features = ["derive"]} diff --git a/crates/primitives/src/block.rs b/crates/primitives/src/block.rs index e1fa100da..fe5d34e9c 100644 --- a/crates/primitives/src/block.rs +++ b/crates/primitives/src/block.rs @@ -10,6 +10,8 @@ pub struct Block { pub body: Vec, /// Block receipts. pub receipts: Vec, + /// Ommers/uncles header + pub ommers: Vec, } impl Deref for Block { @@ -28,6 +30,8 @@ pub struct BlockLocked { pub body: Vec, /// Block receipts. pub receipts: Vec, + /// Omners/uncles header + pub ommers: Vec, } impl BlockLocked { diff --git a/crates/primitives/src/header.rs b/crates/primitives/src/header.rs index 28c26a7b0..1bfe383e1 100644 --- a/crates/primitives/src/header.rs +++ b/crates/primitives/src/header.rs @@ -1,4 +1,4 @@ -use crate::{BlockNumber, Bloom, H160, H256, U256}; +use crate::{BlockHash, BlockNumber, Bloom, H160, H256, U256}; use bytes::{BufMut, BytesMut}; use ethers_core::{types::H64, utils::keccak256}; use reth_codecs::main_codec; @@ -180,7 +180,7 @@ pub struct HeaderLocked { /// Locked Header fields. header: Header, /// Locked Header hash. - hash: H256, + hash: BlockHash, } impl AsRef
for HeaderLocked { @@ -211,7 +211,7 @@ impl HeaderLocked { } /// Return header/block hash. - pub fn hash(&self) -> H256 { + pub fn hash(&self) -> BlockHash { self.hash } } diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 04737ebfc..bec355f4c 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -60,6 +60,7 @@ pub use ethers_core::{ #[doc(hidden)] mod __reexport { pub use hex; + pub use hex_literal; pub use tiny_keccak; }