test(execution): chain test state check, execution bug fixes/upgrades (#472)

* temp: header stage backoff stand-in

* feat(execution): Check chain post state, fix StateProviderLatest and evm return

* Disable receipt merkle tree check

* update and merge

* Fix storage double values in dup table

* fmt

* Update bin/reth/src/test_eth_chain/runner.rs

Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>

* Enable receipt root check after byzantium

* Receipt inner rlp without header for proof root

* some cleanup nits

* nit

Co-authored-by: Oliver Nordbjerg <hi@notbjerg.me>
Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
This commit is contained in:
rakita
2022-12-23 18:42:19 +01:00
committed by GitHub
parent f8d1521c50
commit 284391c181
15 changed files with 494 additions and 161 deletions

View File

@ -21,7 +21,7 @@ pub struct BlockchainTestData {
/// Blocks.
pub blocks: Vec<Block>,
/// Post state.
pub post_state: Option<State>,
pub post_state: Option<RootOrState>,
/// Pre state.
pub pre: State,
/// Hash of best block.
@ -112,6 +112,18 @@ pub struct Block {
pub transactions: Option<Vec<Transaction>>,
/// Uncle/ommer headers
pub uncle_headers: Option<Vec<Header>>,
/// Transaction Sequence
pub transaction_sequence: Option<Vec<TransactionSequence>>,
}
/// Transaction Sequence in block
#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct TransactionSequence {
exception: String,
raw_bytes: Bytes,
valid: String,
}
/// Ethereum blockchain test data State.
@ -191,6 +203,9 @@ pub enum ForkSpec {
/// After Merge Init Code test
#[serde(alias = "Merge+3860")]
MergeMeterInitCode,
/// After Merge plus new PUSH0 opcode
#[serde(alias = "Merge+3855")]
MergePush0,
}
impl From<ForkSpec> for reth_executor::SpecUpgrades {
@ -214,6 +229,7 @@ impl From<ForkSpec> for reth_executor::SpecUpgrades {
ForkSpec::Merge => Self::new_paris_activated(),
ForkSpec::MergeEOF => Self::new_paris_activated(),
ForkSpec::MergeMeterInitCode => Self::new_paris_activated(),
ForkSpec::MergePush0 => Self::new_paris_activated(),
ForkSpec::ByzantiumToConstantinopleAt5 | ForkSpec::Constantinople => {
panic!("Overriden with PETERSBURG")
}

View File

@ -1,22 +1,27 @@
use super::models::Test;
use crate::test_eth_chain::models::ForkSpec;
use crate::test_eth_chain::models::{ForkSpec, RootOrState};
use eyre::eyre;
use reth_db::{
cursor::DbCursorRO,
database::Database,
mdbx::{test_utils::create_test_rw_db, WriteMap},
tables,
transaction::{DbTx, DbTxMut},
Error as DbError,
};
use reth_executor::SpecUpgrades;
use reth_primitives::{
keccak256, Account as RethAccount, BigEndianHash, SealedBlock, SealedHeader, StorageEntry, H256,
keccak256, Account as RethAccount, Address, JsonU256, SealedBlock, SealedHeader, StorageEntry,
H256, U256,
};
use reth_rlp::Decodable;
use reth_stages::{stages::execution::ExecutionStage, ExecInput, Stage, Transaction};
use std::{
collections::HashMap,
ffi::OsStr,
path::{Path, PathBuf},
};
use tracing::debug;
use tracing::{debug, info};
/// Tests are test edge cases that are not possible to happen on mainnet, so we are skipping them.
pub fn should_skip(path: &Path) -> bool {
@ -81,7 +86,8 @@ pub async fn run_test(path: PathBuf) -> eyre::Result<()> {
ForkSpec::ByzantiumToConstantinopleAt5 |
ForkSpec::Constantinople |
ForkSpec::MergeEOF |
ForkSpec::MergeMeterInitCode
ForkSpec::MergeMeterInitCode |
ForkSpec::MergePush0,
) {
continue
}
@ -126,10 +132,10 @@ pub async fn run_test(path: PathBuf) -> eyre::Result<()> {
tx.put::<tables::Bytecodes>(code_hash, account.code.to_vec())?;
}
account.storage.iter().try_for_each(|(k, v)| {
tx.put::<tables::PlainStorageState>(
address,
StorageEntry { key: H256::from_uint(&k.0), value: v.0 },
)
tracing::trace!("Update storage: {address} key:{:?} val:{:?}", k.0, v.0);
let mut key = H256::zero();
k.0.to_big_endian(&mut key.0);
tx.put::<tables::PlainStorageState>(address, StorageEntry { key, value: v.0 })
})?;
Ok(())
@ -138,17 +144,120 @@ pub async fn run_test(path: PathBuf) -> eyre::Result<()> {
// Commit the pre suite state
tx.commit()?;
let storage = db.view(|tx| -> Result<_, DbError> {
let mut cursor = tx.cursor_dup::<tables::PlainStorageState>()?;
let walker = cursor.first()?.map(|first| cursor.walk(first.0)).transpose()?;
Ok(walker.map(|mut walker| {
let mut map: HashMap<Address, HashMap<U256, U256>> = HashMap::new();
while let Some(Ok((address, slot))) = walker.next() {
let key = U256::from_big_endian(&slot.key.0);
map.entry(address).or_default().insert(key, slot.value);
}
map
}))
})??;
tracing::trace!("Pre state :{:?}", storage);
// Initialize the execution stage
// Hardcode the chain_id to Ethereums 1.
// Hardcode the chain_id to Ethereum 1.
let mut stage =
ExecutionStage::new(reth_executor::Config { chain_id: 1.into(), spec_upgrades });
// Call execution stage
let input = ExecInput::default();
stage.execute(&mut Transaction::new(db.as_ref())?, input).await?;
{
let mut transaction = Transaction::new(db.as_ref())?;
// ignore error
let _ = stage.execute(&mut transaction, input).await;
transaction.commit()?;
}
// Validate post state
//for post in
match suite.post_state {
Some(RootOrState::Root(root)) => {
info!("Post state is root: #{root:?}")
}
Some(RootOrState::State(state)) => db.view(|tx| -> eyre::Result<()> {
let mut cursor = tx.cursor_dup::<tables::PlainStorageState>()?;
let walker = cursor.first()?.map(|first| cursor.walk(first.0)).transpose()?;
let storage = walker.map(|mut walker| {
let mut map: HashMap<Address, HashMap<U256, U256>> = HashMap::new();
while let Some(Ok((address, slot))) = walker.next() {
let key = U256::from_big_endian(&slot.key.0);
map.entry(address).or_default().insert(key, slot.value);
}
map
});
tracing::trace!("Our storage:{:?}", storage);
for (address, test_account) in state.iter() {
// check account
let our_account = tx
.get::<tables::PlainAccountState>(*address)?
.ok_or(eyre!("Account is missing:{address} expected:{:?}", test_account))?;
if test_account.balance.0 != our_account.balance {
return Err(eyre!(
"Account {address} balance diff, expected {} got{}",
test_account.balance.0,
our_account.balance
))
}
if test_account.nonce.0.as_u64() != our_account.nonce {
return Err(eyre!(
"Account {address} nonce diff, expected {} got {}",
test_account.nonce.0,
our_account.nonce
))
}
if let Some(our_bytecode) = our_account.bytecode_hash {
let test_bytecode = keccak256(test_account.code.as_ref());
if our_bytecode != test_bytecode {
return Err(eyre!(
"Account {address} bytecode diff, expected: {} got: {:?}",
test_account.code,
our_account.bytecode_hash
))
}
} else if !test_account.code.is_empty() {
return Err(eyre!(
"Account {address} bytecode diff, expected {} got empty bytecode",
test_account.code,
))
}
// get walker if present
if let Some(storage) = storage.as_ref() {
// iterate over storages
for (JsonU256(key), JsonU256(value)) in test_account.storage.iter() {
let our_value = storage
.get(address)
.ok_or(eyre!(
"Missing storage from test {storage:?} got {:?}",
test_account.storage
))?
.get(key)
.ok_or(eyre!(
"Slot is missing from table {storage:?} got:{:?}",
test_account.storage
))?;
if value != our_value {
return Err(eyre!(
"Storage diff we got {address}: {storage:?} but expect: {:?}",
test_account.storage
))
}
}
} else if !test_account.storage.is_empty() {
return Err(eyre!(
"Walker is not present, but storage is not empty.{:?}",
test_account.storage
))
}
}
Ok(())
})??,
None => info!("Post state is none"),
}
}
Ok(())
}