mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
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
This commit is contained in:
30
crates/consensus/Cargo.toml
Normal file
30
crates/consensus/Cargo.toml
Normal file
@ -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 }
|
||||
24
crates/consensus/src/config.rs
Normal file
24
crates/consensus/src/config.rs
Normal file
@ -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 }
|
||||
}
|
||||
}
|
||||
43
crates/consensus/src/consensus.rs
Normal file
43
crates/consensus/src/consensus.rs
Normal file
@ -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<ForkchoiceState>, watch::Receiver<ForkchoiceState>),
|
||||
/// 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<ForkchoiceState> {
|
||||
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
|
||||
}
|
||||
}
|
||||
18
crates/consensus/src/lib.rs
Normal file
18
crates/consensus/src/lib.rs
Normal file
@ -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;
|
||||
96
crates/consensus/src/proofs.rs
Normal file
96
crates/consensus/src/proofs.rs
Normal file
@ -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<Item = &'a TransactionSigned>,
|
||||
) -> H256 {
|
||||
sec_trie_root::<KeccakHasher, _, _, _>(
|
||||
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::<Vec<(Bytes, Vec<u8>)>>(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create receipt root for header
|
||||
pub fn calculate_receipt_root<'a>(receipts: impl IntoIterator<Item = &'a Receipt>) -> H256 {
|
||||
sec_trie_root::<KeccakHasher, _, _, _>(
|
||||
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::<Vec<(Bytes, Vec<u8>)>>(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create log hash for header
|
||||
pub fn calculate_log_root<'a>(logs: impl IntoIterator<Item = &'a Log>) -> 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<Item = &'a Header>) -> 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
|
||||
337
crates/consensus/src/verification.rs
Normal file
337
crates/consensus/src/verification.rs
Normal file
@ -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<PROV: HeaderProvider>(
|
||||
block: &BlockLocked,
|
||||
provider: &PROV,
|
||||
) -> RethResult<HeaderLocked> {
|
||||
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<PROV: HeaderProvider>(
|
||||
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<Header>,
|
||||
}
|
||||
|
||||
impl Provider {
|
||||
/// New provider with parent
|
||||
fn new(parent: Option<Header>) -> 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<bool> {
|
||||
Ok(self.is_known)
|
||||
}
|
||||
|
||||
fn header(&self, _block_number: &BlockHash) -> Result<Option<Header>> {
|
||||
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"
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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 }
|
||||
@ -1,6 +0,0 @@
|
||||
/// Configuration for executor (TODO)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {
|
||||
/// Example
|
||||
pub example: bool,
|
||||
}
|
||||
5
crates/executor/src/config.rs
Normal file
5
crates/executor/src/config.rs
Normal file
@ -0,0 +1,5 @@
|
||||
//! Reth block execution/validation configuration and constants
|
||||
|
||||
/// Configuration for executor
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {}
|
||||
@ -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<dyn ExecutorDb>,
|
||||
/// Consensus
|
||||
pub consensus: Box<dyn Consensus>,
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
/// Create new Executor
|
||||
pub fn new(config: Config, db: Box<dyn ExecutorDb>, consensus: Box<dyn Consensus>) -> 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<DB: ExecutorDb>(&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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<EmptyDB>;
|
||||
/// SubState of database. Uses revm internal cache with binding to reth DbExecutor trait.
|
||||
pub type SubState<DB> = CacheDB<State<DB>>;
|
||||
|
||||
/// Wrapper around ExeuctorDb that implements revm database trait
|
||||
pub struct Wrapper<'a>(&'a dyn ExecutorDb);
|
||||
pub struct State<DB: ExecutorDb>(DB);
|
||||
|
||||
impl<'a> Database for Wrapper<'a> {
|
||||
impl<DB: ExecutorDb> State<DB> {
|
||||
/// 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<DB: ExecutorDb> DatabaseRef for State<DB> {
|
||||
type Error = Infallible;
|
||||
|
||||
fn basic(&mut self, address: H160) -> Result<Option<revm::AccountInfo>, Self::Error> {
|
||||
fn basic(&self, address: H160) -> Result<Option<revm::AccountInfo>, 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<revm::Bytecode, Self::Error> {
|
||||
fn code_by_hash(&self, code_hash: H256) -> Result<revm::Bytecode, Self::Error> {
|
||||
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<U256, Self::Error> {
|
||||
fn storage(&self, address: H160, index: U256) -> Result<U256, Self::Error> {
|
||||
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<H256, Self::Error> {
|
||||
fn block_hash(&self, number: U256) -> Result<H256, Self::Error> {
|
||||
Ok(self.0.block_hash(number).unwrap_or_default())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<ForkchoiceState>;
|
||||
|
||||
/// 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 },
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// 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)]
|
||||
|
||||
@ -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.")]
|
||||
|
||||
@ -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<bool> {
|
||||
self.header(block_hash).map(|header| header.is_some())
|
||||
}
|
||||
|
||||
/// Get header by block hash
|
||||
fn header(&self, block_hash: &BlockHash) -> Result<Option<Header>>;
|
||||
}
|
||||
|
||||
/// Client trait for fetching `Block` related data.
|
||||
pub trait BlockProvider: Send + Sync + 'static {
|
||||
/// Returns the current info for the chain.
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
mod block;
|
||||
mod storage;
|
||||
|
||||
pub use block::BlockProvider;
|
||||
pub use block::{BlockProvider, HeaderProvider};
|
||||
pub use storage::StorageProvider;
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
@ -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::<NoWriteMap>::new().open(dir.path()).unwrap();
|
||||
/// let info = env.info().unwrap();
|
||||
|
||||
@ -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();
|
||||
///
|
||||
|
||||
@ -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"]}
|
||||
|
||||
@ -10,6 +10,8 @@ pub struct Block {
|
||||
pub body: Vec<Transaction>,
|
||||
/// Block receipts.
|
||||
pub receipts: Vec<Receipt>,
|
||||
/// Ommers/uncles header
|
||||
pub ommers: Vec<HeaderLocked>,
|
||||
}
|
||||
|
||||
impl Deref for Block {
|
||||
@ -28,6 +30,8 @@ pub struct BlockLocked {
|
||||
pub body: Vec<TransactionSigned>,
|
||||
/// Block receipts.
|
||||
pub receipts: Vec<Receipt>,
|
||||
/// Omners/uncles header
|
||||
pub ommers: Vec<HeaderLocked>,
|
||||
}
|
||||
|
||||
impl BlockLocked {
|
||||
|
||||
@ -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<Header> for HeaderLocked {
|
||||
@ -211,7 +211,7 @@ impl HeaderLocked {
|
||||
}
|
||||
|
||||
/// Return header/block hash.
|
||||
pub fn hash(&self) -> H256 {
|
||||
pub fn hash(&self) -> BlockHash {
|
||||
self.hash
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,6 +60,7 @@ pub use ethers_core::{
|
||||
#[doc(hidden)]
|
||||
mod __reexport {
|
||||
pub use hex;
|
||||
pub use hex_literal;
|
||||
pub use tiny_keccak;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user