feat: add evm env provider (#1525)

Co-authored-by: Roman Krasiuk <rokrassyuk@gmail.com>
This commit is contained in:
Matthias Seitz
2023-02-24 11:21:18 +01:00
committed by GitHub
parent 6410d02bb8
commit 5844ce10f3
23 changed files with 234 additions and 42 deletions

2
Cargo.lock generated
View File

@@ -4842,6 +4842,8 @@ dependencies = [
"reth-db",
"reth-interfaces",
"reth-primitives",
"reth-revm-primitives",
"revm-primitives",
"thiserror",
]

View File

@@ -462,6 +462,10 @@ mod tests {
Ok(None)
}
fn header_td_by_number(&self, _number: BlockNumber) -> Result<Option<U256>> {
Ok(None)
}
fn headers_range(&self, _range: impl RangeBounds<BlockNumber>) -> Result<Vec<Header>> {
Ok(vec![])
}

View File

@@ -11,12 +11,12 @@ use reth_provider::StateProvider;
use reth_revm::{
config::{WEI_2ETH, WEI_3ETH, WEI_5ETH},
database::SubState,
env::{fill_block_env, fill_cfg_env, fill_tx_env},
env::{fill_cfg_and_block_env, fill_tx_env},
into_reth_log, to_reth_acc,
};
use revm::{
db::AccountState,
primitives::{Account as RevmAccount, AccountInfo, Bytecode, ResultAndState, SpecId},
primitives::{Account as RevmAccount, AccountInfo, Bytecode, ResultAndState},
EVM,
};
use std::collections::{BTreeMap, HashMap};
@@ -65,9 +65,7 @@ where
/// Initializes the config and block env.
fn init_env(&mut self, header: &Header, total_difficulty: U256) {
fill_cfg_env(&mut self.evm.env.cfg, self.chain_spec, header, total_difficulty);
let after_merge = self.evm.env.cfg.spec_id >= SpecId::MERGE;
fill_block_env(&mut self.evm.env.block, header, after_merge);
fill_cfg_and_block_env(&mut self.evm.env, self.chain_spec, header, total_difficulty);
}
/// Commit change to database and return change diff that is used to update state and create

View File

@@ -15,7 +15,7 @@ pub enum Error {
Database(#[from] crate::db::Error),
#[error(transparent)]
Provider(#[from] crate::provider::Error),
Provider(#[from] crate::provider::ProviderError),
#[error(transparent)]
Network(#[from] reth_network_api::NetworkError),

View File

@@ -1,9 +1,9 @@
use reth_primitives::{Address, BlockHash, BlockNumber, TransitionId, TxNumber, H256};
/// KV error type. They are using u32 to represent error code.
/// Bundled errors variants thrown by various providers.
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone)]
pub enum Error {
pub enum ProviderError {
/// The header hash is missing from the database.
#[error("Block number {block_number} does not exist in database")]
CanonicalHeader { block_number: BlockNumber },
@@ -68,4 +68,7 @@ pub enum Error {
/// Reached the end of the transaction sender table.
#[error("Got to the end of the transaction sender table")]
EndOfTransactionSenderTable,
/// Thrown when required header related data was not found but was required.
#[error("requested data not found")]
HeaderNotFound,
}

View File

@@ -3,7 +3,19 @@ use reth_primitives::{
Address, ChainSpec, Head, Header, Transaction, TransactionKind, TransactionSigned, TxEip1559,
TxEip2930, TxLegacy, U256,
};
use revm::primitives::{AnalysisKind, BlockEnv, CfgEnv, TransactTo, TxEnv};
use revm::primitives::{AnalysisKind, BlockEnv, CfgEnv, Env, SpecId, TransactTo, TxEnv};
/// Convenience function to call both [fill_cfg_env] and [fill_block_env]
pub fn fill_cfg_and_block_env(
env: &mut Env,
chain_spec: &ChainSpec,
header: &Header,
total_difficulty: U256,
) {
fill_cfg_env(&mut env.cfg, chain_spec, header, total_difficulty);
let after_merge = env.cfg.spec_id >= SpecId::MERGE;
fill_block_env(&mut env.block, header, after_merge);
}
/// Fill [CfgEnv] fields according to the chain spec and given header
pub fn fill_cfg_env(

View File

@@ -1,7 +1,6 @@
use crate::pipeline::PipelineEvent;
use reth_interfaces::{
consensus, db::Error as DbError, executor, p2p::error::DownloadError,
provider::Error as ProviderError,
consensus, db::Error as DbError, executor, p2p::error::DownloadError, provider::ProviderError,
};
use reth_primitives::BlockNumber;
use reth_provider::TransactionError;

View File

@@ -426,7 +426,7 @@ mod tests {
use crate::{StageId, UnwindOutput};
use assert_matches::assert_matches;
use reth_db::mdbx::{self, test_utils, EnvKind};
use reth_interfaces::{consensus, provider::Error as ProviderError, sync::NoopSyncStateUpdate};
use reth_interfaces::{consensus, provider::ProviderError, sync::NoopSyncStateUpdate};
use tokio_stream::StreamExt;
use utils::TestStage;

View File

@@ -13,7 +13,7 @@ use reth_db::{
use reth_interfaces::{
consensus::Consensus,
p2p::bodies::{downloader::BodyDownloader, response::BlockResponse},
provider::Error as ProviderError,
provider::ProviderError,
};
use reth_provider::Transaction;
use std::sync::Arc;

View File

@@ -10,7 +10,7 @@ use reth_db::{
transaction::{DbTx, DbTxMut},
};
use reth_executor::execution_result::AccountChangeSet;
use reth_interfaces::provider::Error as ProviderError;
use reth_interfaces::provider::ProviderError;
use reth_primitives::{Address, Block, ChainSpec, Hardfork, StorageEntry, H256, MAINNET, U256};
use reth_provider::{LatestStateProviderRef, Transaction};
use reth_revm::database::{State, SubState};

View File

@@ -9,7 +9,7 @@ use reth_db::{
use reth_interfaces::{
consensus::{Consensus, ForkchoiceState},
p2p::headers::downloader::{HeaderDownloader, SyncTarget},
provider::Error as ProviderError,
provider::ProviderError,
};
use reth_primitives::{BlockNumber, SealedHeader};
use reth_provider::Transaction;

View File

@@ -8,7 +8,7 @@ use reth_db::{
tables,
transaction::{DbTx, DbTxMut},
};
use reth_interfaces::{consensus::Error, provider::Error as ProviderError};
use reth_interfaces::{consensus::Error, provider::ProviderError};
use reth_primitives::{ChainSpec, Hardfork, EMPTY_OMMER_ROOT, MAINNET, U256};
use reth_provider::Transaction;
use tracing::*;

View File

@@ -11,6 +11,7 @@ description = "Reth storage provider."
# reth
reth-primitives = { path = "../../primitives" }
reth-interfaces = { path = "../../interfaces" }
reth-revm-primitives = { path = "../../revm/revm-primitives" }
reth-db = { path = "../db" }
# misc
@@ -19,6 +20,7 @@ auto_impl = "1.0"
# feature test-utils
parking_lot = { version = "0.12", optional = true }
revm-primitives = "1.0"
[dev-dependencies]
reth-db = { path = "../db", features = ["test-utils"] }

View File

@@ -11,8 +11,8 @@
/// Various provider traits.
mod traits;
pub use traits::{
AccountProvider, BlockHashProvider, BlockIdProvider, BlockProvider, HeaderProvider,
StateProvider, StateProviderFactory, TransactionsProvider, WithdrawalsProvider,
AccountProvider, BlockHashProvider, BlockIdProvider, BlockProvider, EvmEnvProvider,
HeaderProvider, StateProvider, StateProviderFactory, TransactionsProvider, WithdrawalsProvider,
};
/// Provider trait implementations.
@@ -35,4 +35,4 @@ pub use utils::{insert_block, insert_canonical_block};
pub mod test_utils;
/// Re-export provider error.
pub use reth_interfaces::provider::Error;
pub use reth_interfaces::provider::ProviderError;

View File

@@ -1,6 +1,6 @@
use crate::{
BlockHashProvider, BlockIdProvider, BlockProvider, Error, HeaderProvider, StateProviderFactory,
TransactionsProvider, WithdrawalsProvider,
BlockHashProvider, BlockIdProvider, BlockProvider, EvmEnvProvider, HeaderProvider,
ProviderError, StateProviderFactory, TransactionsProvider, WithdrawalsProvider,
};
use reth_db::{
cursor::DbCursorRO,
@@ -10,9 +10,14 @@ use reth_db::{
};
use reth_interfaces::Result;
use reth_primitives::{
Block, BlockHash, BlockId, BlockNumber, ChainInfo, ChainSpec, Hardfork, Header,
Block, BlockHash, BlockId, BlockNumber, ChainInfo, ChainSpec, Hardfork, Head, Header,
TransactionSigned, TxHash, TxNumber, Withdrawal, H256, U256,
};
use reth_revm_primitives::{
config::revm_spec,
env::{fill_block_env, fill_cfg_and_block_env, fill_cfg_env},
};
use revm_primitives::{BlockEnv, CfgEnv, Env, SpecId};
use std::{ops::RangeBounds, sync::Arc};
mod state;
@@ -74,6 +79,10 @@ impl<DB: Database> HeaderProvider for ShareableDatabase<DB> {
})?
}
fn header_td_by_number(&self, number: BlockNumber) -> Result<Option<U256>> {
self.db.view(|tx| Ok(tx.get::<tables::HeaderTD>(number)?.map(|td| td.0)))?
}
fn headers_range(&self, range: impl RangeBounds<BlockNumber>) -> Result<Vec<Header>> {
self.db
.view(|tx| {
@@ -119,7 +128,7 @@ impl<DB: Database> BlockProvider for ShareableDatabase<DB> {
let id = BlockId::Number(number.into());
let tx = self.db.tx()?;
let transactions =
self.transactions_by_block(id)?.ok_or(Error::BlockBody { number })?;
self.transactions_by_block(id)?.ok_or(ProviderError::BlockBody { number })?;
let ommers = tx.get::<tables::BlockOmmers>(header.number)?.map(|o| o.ommers);
let withdrawals = self.withdrawals_by_block(id, header.timestamp)?;
@@ -231,6 +240,60 @@ impl<DB: Database> WithdrawalsProvider for ShareableDatabase<DB> {
}
}
impl<DB: Database> EvmEnvProvider for ShareableDatabase<DB> {
fn fill_env_at(&self, env: &mut Env, at: BlockId) -> Result<()> {
let hash = self.block_hash_for_id(at)?.ok_or(ProviderError::HeaderNotFound)?;
let header = self.header(&hash)?.ok_or(ProviderError::HeaderNotFound)?;
self.fill_env_with_header(env, &header)
}
fn fill_env_with_header(&self, env: &mut Env, header: &Header) -> Result<()> {
let total_difficulty =
self.header_td_by_number(header.number)?.ok_or(ProviderError::HeaderNotFound)?;
fill_cfg_and_block_env(env, &self.chain_spec, header, total_difficulty);
Ok(())
}
fn fill_block_env_at(&self, block_env: &mut BlockEnv, at: BlockId) -> Result<()> {
let hash = self.block_hash_for_id(at)?.ok_or(ProviderError::HeaderNotFound)?;
let header = self.header(&hash)?.ok_or(ProviderError::HeaderNotFound)?;
self.fill_block_env_with_header(block_env, &header)
}
fn fill_block_env_with_header(&self, block_env: &mut BlockEnv, header: &Header) -> Result<()> {
let total_difficulty =
self.header_td_by_number(header.number)?.ok_or(ProviderError::HeaderNotFound)?;
let spec_id = revm_spec(
&self.chain_spec,
Head {
number: header.number,
timestamp: header.timestamp,
difficulty: header.difficulty,
total_difficulty,
// Not required
hash: Default::default(),
},
);
let after_merge = spec_id >= SpecId::MERGE;
fill_block_env(block_env, header, after_merge);
Ok(())
}
fn fill_cfg_env_at(&self, cfg: &mut CfgEnv, at: BlockId) -> Result<()> {
let hash = self.block_hash_for_id(at)?.ok_or(ProviderError::HeaderNotFound)?;
let header = self.header(&hash)?.ok_or(ProviderError::HeaderNotFound)?;
self.fill_cfg_env_with_header(cfg, &header)
}
fn fill_cfg_env_with_header(&self, cfg: &mut CfgEnv, header: &Header) -> Result<()> {
let total_difficulty =
self.header_td_by_number(header.number)?.ok_or(ProviderError::HeaderNotFound)?;
fill_cfg_env(cfg, &self.chain_spec, header, total_difficulty);
Ok(())
}
}
impl<DB: Database> StateProviderFactory for ShareableDatabase<DB> {
type HistorySP<'a> = HistoricalStateProvider<'a,<DB as DatabaseGAT<'a>>::TX> where Self: 'a;
type LatestSP<'a> = LatestStateProvider<'a,<DB as DatabaseGAT<'a>>::TX> where Self: 'a;
@@ -245,7 +308,7 @@ impl<DB: Database> StateProviderFactory for ShareableDatabase<DB> {
// get transition id
let transition = tx
.get::<tables::BlockTransitionIndex>(block_number)?
.ok_or(Error::BlockTransition { block_number })?;
.ok_or(ProviderError::BlockTransition { block_number })?;
Ok(HistoricalStateProvider::new(tx, transition))
}
@@ -253,13 +316,14 @@ impl<DB: Database> StateProviderFactory for ShareableDatabase<DB> {
fn history_by_block_hash(&self, block_hash: BlockHash) -> Result<Self::HistorySP<'_>> {
let tx = self.db.tx()?;
// get block number
let block_number =
tx.get::<tables::HeaderNumbers>(block_hash)?.ok_or(Error::BlockHash { block_hash })?;
let block_number = tx
.get::<tables::HeaderNumbers>(block_hash)?
.ok_or(ProviderError::BlockHash { block_hash })?;
// get transition id
let transition = tx
.get::<tables::BlockTransitionIndex>(block_number)?
.ok_or(Error::BlockTransition { block_number })?;
.ok_or(ProviderError::BlockTransition { block_number })?;
Ok(HistoricalStateProvider::new(tx, transition))
}

View File

@@ -1,6 +1,6 @@
use crate::{
providers::state::macros::delegate_provider_impls, AccountProvider, BlockHashProvider, Error,
StateProvider,
providers::state::macros::delegate_provider_impls, AccountProvider, BlockHashProvider,
ProviderError, StateProvider,
};
use reth_db::{
cursor::{DbCursorRO, DbDupCursorRO},
@@ -56,7 +56,7 @@ impl<'a, 'b, TX: DbTx<'a>> AccountProvider for HistoricalStateProviderRef<'a, 'b
.tx
.cursor_dup_read::<tables::AccountChangeSet>()?
.seek_by_key_subkey(changeset_transition_id, address)?
.ok_or(Error::AccountChangeset {
.ok_or(ProviderError::AccountChangeset {
transition_id: changeset_transition_id,
address,
})?;
@@ -95,7 +95,7 @@ impl<'a, 'b, TX: DbTx<'a>> StateProvider for HistoricalStateProviderRef<'a, 'b,
.tx
.cursor_dup_read::<tables::StorageChangeSet>()?
.seek_by_key_subkey((changeset_transition_id, address).into(), storage_key)?
.ok_or(Error::StorageChangeset {
.ok_or(ProviderError::StorageChangeset {
transition_id: changeset_transition_id,
address,
storage_key,

View File

@@ -1,13 +1,14 @@
use crate::{
AccountProvider, BlockHashProvider, BlockIdProvider, BlockProvider, HeaderProvider,
StateProvider, StateProviderFactory, TransactionsProvider,
AccountProvider, BlockHashProvider, BlockIdProvider, BlockProvider, EvmEnvProvider,
HeaderProvider, StateProvider, StateProviderFactory, TransactionsProvider,
};
use parking_lot::Mutex;
use reth_interfaces::Result;
use reth_primitives::{
keccak256, Account, Address, Block, BlockHash, BlockId, BlockNumberOrTag, Bytes, ChainInfo,
Header, StorageKey, StorageValue, TransactionSigned, TxHash, H256, U256,
keccak256, Account, Address, Block, BlockHash, BlockId, BlockNumber, BlockNumberOrTag, Bytes,
ChainInfo, Header, StorageKey, StorageValue, TransactionSigned, TxHash, H256, U256,
};
use revm_primitives::{BlockEnv, CfgEnv, Env};
use std::{collections::HashMap, ops::RangeBounds, sync::Arc};
/// A mock implementation for Provider interfaces.
@@ -108,6 +109,15 @@ impl HeaderProvider for MockEthProvider {
}))
}
fn header_td_by_number(&self, number: BlockNumber) -> Result<Option<U256>> {
let lock = self.headers.lock();
let sum = lock
.values()
.filter(|h| h.number <= number)
.fold(U256::ZERO, |td, h| td + h.difficulty);
Ok(Some(sum))
}
fn headers_range(
&self,
range: impl RangeBounds<reth_primitives::BlockNumber>,
@@ -211,6 +221,11 @@ impl AccountProvider for MockEthProvider {
}
impl StateProvider for MockEthProvider {
fn storage(&self, account: Address, storage_key: StorageKey) -> Result<Option<StorageValue>> {
let lock = self.accounts.lock();
Ok(lock.get(&account).and_then(|account| account.storage.get(&storage_key)).cloned())
}
fn bytecode_by_hash(&self, code_hash: H256) -> Result<Option<Bytes>> {
let lock = self.accounts.lock();
Ok(lock.values().find_map(|account| {
@@ -222,10 +237,35 @@ impl StateProvider for MockEthProvider {
}
}))
}
}
fn storage(&self, account: Address, storage_key: StorageKey) -> Result<Option<StorageValue>> {
let lock = self.accounts.lock();
Ok(lock.get(&account).and_then(|account| account.storage.get(&storage_key)).cloned())
impl EvmEnvProvider for MockEthProvider {
fn fill_env_at(&self, _env: &mut Env, _at: BlockId) -> Result<()> {
unimplemented!()
}
fn fill_env_with_header(&self, _env: &mut Env, _header: &Header) -> Result<()> {
unimplemented!()
}
fn fill_block_env_at(&self, _block_env: &mut BlockEnv, _at: BlockId) -> Result<()> {
unimplemented!()
}
fn fill_block_env_with_header(
&self,
_block_env: &mut BlockEnv,
_header: &Header,
) -> Result<()> {
unimplemented!()
}
fn fill_cfg_env_at(&self, _cfg: &mut CfgEnv, _at: BlockId) -> Result<()> {
unimplemented!()
}
fn fill_cfg_env_with_header(&self, _cfg: &mut CfgEnv, _header: &Header) -> Result<()> {
unimplemented!()
}
}

View File

@@ -1,12 +1,13 @@
use crate::{
AccountProvider, BlockHashProvider, BlockIdProvider, BlockProvider, HeaderProvider,
StateProvider, StateProviderFactory, TransactionsProvider,
AccountProvider, BlockHashProvider, BlockIdProvider, BlockProvider, EvmEnvProvider,
HeaderProvider, StateProvider, StateProviderFactory, TransactionsProvider,
};
use reth_interfaces::Result;
use reth_primitives::{
Account, Address, Block, BlockHash, BlockId, BlockNumber, Bytes, ChainInfo, Header, StorageKey,
StorageValue, TransactionSigned, TxHash, TxNumber, H256, U256,
};
use revm_primitives::{BlockEnv, CfgEnv, Env};
use std::ops::RangeBounds;
/// Supports various api interfaces for testing purposes.
@@ -75,6 +76,10 @@ impl HeaderProvider for NoopProvider {
Ok(None)
}
fn header_td_by_number(&self, _number: BlockNumber) -> Result<Option<U256>> {
Ok(None)
}
fn headers_range(&self, _range: impl RangeBounds<BlockNumber>) -> Result<Vec<Header>> {
Ok(vec![])
}
@@ -96,6 +101,36 @@ impl StateProvider for NoopProvider {
}
}
impl EvmEnvProvider for NoopProvider {
fn fill_env_at(&self, _env: &mut Env, _at: BlockId) -> Result<()> {
Ok(())
}
fn fill_env_with_header(&self, _env: &mut Env, _header: &Header) -> Result<()> {
Ok(())
}
fn fill_block_env_at(&self, _block_env: &mut BlockEnv, _at: BlockId) -> Result<()> {
Ok(())
}
fn fill_block_env_with_header(
&self,
_block_env: &mut BlockEnv,
_header: &Header,
) -> Result<()> {
Ok(())
}
fn fill_cfg_env_at(&self, _cfg: &mut CfgEnv, _at: BlockId) -> Result<()> {
Ok(())
}
fn fill_cfg_env_with_header(&self, _cfg: &mut CfgEnv, _header: &Header) -> Result<()> {
Ok(())
}
}
impl StateProviderFactory for NoopProvider {
type HistorySP<'a> = NoopProvider where Self: 'a;
type LatestSP<'a> = NoopProvider where Self: 'a;

View File

@@ -0,0 +1,27 @@
use reth_interfaces::Result;
use reth_primitives::{BlockId, Header};
use revm_primitives::{BlockEnv, CfgEnv, Env};
/// A provider type that knows chain specific information required to configure an [Env]
///
/// This type is mainly used to provide required data to configure the EVM environment.
#[auto_impl::auto_impl(&, Arc)]
pub trait EvmEnvProvider: Send + Sync {
/// Fills the [CfgEnv] and [BlockEnv] fields with values specific to the given [BlockId].
fn fill_env_at(&self, env: &mut Env, at: BlockId) -> Result<()>;
/// Fills the [CfgEnv] and [BlockEnv] fields with values specific to the given [Header].
fn fill_env_with_header(&self, env: &mut Env, header: &Header) -> Result<()>;
/// Fills the [BlockEnv] fields with values specific to the given [BlockId].
fn fill_block_env_at(&self, block_env: &mut BlockEnv, at: BlockId) -> Result<()>;
/// Fills the [BlockEnv] fields with values specific to the given [Header].
fn fill_block_env_with_header(&self, block_env: &mut BlockEnv, header: &Header) -> Result<()>;
/// Fills the [CfgEnv] fields with values specific to the given [BlockId].
fn fill_cfg_env_at(&self, cfg: &mut CfgEnv, at: BlockId) -> Result<()>;
/// Fills the [CfgEnv] fields with values specific to the given [Header].
fn fill_cfg_env_with_header(&self, cfg: &mut CfgEnv, header: &Header) -> Result<()>;
}

View File

@@ -28,6 +28,9 @@ pub trait HeaderProvider: Send + Sync {
/// Get total difficulty by block hash.
fn header_td(&self, hash: &BlockHash) -> Result<Option<U256>>;
/// Get total difficulty by block number.
fn header_td_by_number(&self, number: BlockNumber) -> Result<Option<U256>>;
/// Get headers in range of block numbers
fn headers_range(&self, range: impl RangeBounds<BlockNumber>) -> Result<Vec<Header>>;
}

View File

@@ -12,6 +12,9 @@ pub use block_hash::BlockHashProvider;
mod block_id;
pub use block_id::BlockIdProvider;
mod evm_env;
pub use evm_env::EvmEnvProvider;
mod header;
pub use header::HeaderProvider;

View File

@@ -7,7 +7,7 @@ use reth_db::{
tables,
transaction::{DbTx, DbTxMut},
};
use reth_interfaces::{db::Error as DbError, provider::Error as ProviderError};
use reth_interfaces::{db::Error as DbError, provider::ProviderError};
use reth_primitives::{BlockHash, BlockNumber, Header, TransitionId, TxNumber, U256};
use std::{
fmt::Debug,

View File

@@ -3,7 +3,7 @@ use reth_db::{
tables,
transaction::{DbTx, DbTxMut},
};
use reth_interfaces::{provider::Error as ProviderError, Result};
use reth_interfaces::{provider::ProviderError, Result};
use reth_primitives::{SealedBlock, U256};
/// Insert block data into corresponding tables. Used mainly for testing & internal tooling.