mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat(stage): Add execution stage (#287)
* Execution stage * StorageProviderRef. cleanup, fmt/clippy * block inclusion * insert blocks for execution stage * Sanity test for execution stage * nits * Update crates/stages/src/stages/execution.rs Co-authored-by: Georgios Konstantopoulos <me@gakonst.com> * Update crates/stages/Cargo.toml Co-authored-by: Georgios Konstantopoulos <me@gakonst.com> * Update crates/stages/src/stages/execution.rs Co-authored-by: Georgios Konstantopoulos <me@gakonst.com> * Update crates/stages/src/stages/execution.rs Co-authored-by: Georgios Konstantopoulos <me@gakonst.com> * feat(execution) Execution change diff. (#323) * Insert to execution changediffs * progress on exec stage unwind * wip * Remove tables:BlockBody from execution stage * unwind passing * Nits cleanup and refactoring * DatabaseIntegrity errors * KECCAK_EMPTY acc to None * Update crates/interfaces/src/db/tables.rs Co-authored-by: Georgios Konstantopoulos <me@gakonst.com> * Update crates/interfaces/src/provider/block.rs Co-authored-by: Georgios Konstantopoulos <me@gakonst.com> * Update crates/stages/src/stages/execution.rs Co-authored-by: Georgios Konstantopoulos <me@gakonst.com> * nits Co-authored-by: Georgios Konstantopoulos <me@gakonst.com> Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
This commit is contained in:
committed by
Georgios Konstantopoulos
parent
75879b2027
commit
a68cb3f357
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -3516,9 +3516,11 @@ dependencies = [
|
||||
"reth-bodies-downloaders",
|
||||
"reth-db",
|
||||
"reth-eth-wire",
|
||||
"reth-executor",
|
||||
"reth-headers-downloaders",
|
||||
"reth-interfaces",
|
||||
"reth-primitives",
|
||||
"reth-rlp",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
|
||||
@ -115,7 +115,7 @@ fn build_struct_field_flags(
|
||||
/// Returns the total number of bytes used by the flags struct.
|
||||
fn pad_flag_struct(total_bits: u8, field_flags: &mut Vec<TokenStream2>) -> u8 {
|
||||
let remaining = 8 - total_bits % 8;
|
||||
let total_bytes = if remaining != 8 {
|
||||
if remaining != 8 {
|
||||
let bsize = format_ident!("B{remaining}");
|
||||
field_flags.push(quote! {
|
||||
#[skip]
|
||||
@ -124,8 +124,7 @@ fn pad_flag_struct(total_bits: u8, field_flags: &mut Vec<TokenStream2>) -> u8 {
|
||||
(total_bits + remaining) / 8
|
||||
} else {
|
||||
total_bits / 8
|
||||
};
|
||||
total_bytes
|
||||
}
|
||||
}
|
||||
|
||||
/// Placeholder struct for when there are no bitfields to be added.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
//! Cursor wrapper for libmdbx-sys.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use std::{borrow::Cow, marker::PhantomData};
|
||||
|
||||
use crate::utils::*;
|
||||
use reth_interfaces::db::{
|
||||
@ -108,11 +108,14 @@ impl<'tx, K: TransactionKind, T: DupSort> DbDupCursorRO<'tx, T> for Cursor<'tx,
|
||||
key: T::Key,
|
||||
subkey: T::SubKey,
|
||||
) -> Result<DupWalker<'cursor, 'tx, T, Self>, Error> {
|
||||
// encode key and decode it after.
|
||||
let key = key.encode().as_ref().to_vec();
|
||||
|
||||
let start = self
|
||||
.inner
|
||||
.get_both_range(key.encode().as_ref(), subkey.encode().as_ref())
|
||||
.get_both_range(key.as_ref(), subkey.encode().as_ref())
|
||||
.map_err(|e| Error::Read(e.into()))?
|
||||
.map(decode_one::<T>);
|
||||
.map(|val| decoder::<T>((Cow::Owned(key), val)));
|
||||
|
||||
Ok(DupWalker::<'cursor, 'tx, T, Self> { cursor: self, start, _tx_phantom: PhantomData {} })
|
||||
}
|
||||
|
||||
@ -356,7 +356,7 @@ mod tests {
|
||||
let mut cursor = tx.cursor_dup::<PlainStorageState>().unwrap();
|
||||
let mut walker = cursor.walk_dup(key, H256::from_low_u64_be(1)).unwrap();
|
||||
assert_eq!(
|
||||
value11,
|
||||
(key, value11),
|
||||
walker
|
||||
.next()
|
||||
.expect("element should exist.")
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
use crate::{
|
||||
revm_wrap::{self, SubState},
|
||||
revm_wrap::{self, to_reth_acc, SubState},
|
||||
Config,
|
||||
};
|
||||
use hashbrown::hash_map::Entry;
|
||||
use reth_interfaces::{executor::Error, provider::StateProvider};
|
||||
use reth_primitives::{
|
||||
bloom::logs_bloom, Account, Address, Bloom, Log, Receipt, SealedHeader,
|
||||
TransactionSignedEcRecovered, H256, U256,
|
||||
bloom::logs_bloom, Account, Address, Bloom, Header, Log, Receipt, TransactionSignedEcRecovered,
|
||||
H256, U256,
|
||||
};
|
||||
use revm::{
|
||||
db::AccountState, Account as RevmAccount, AccountInfo, AnalysisKind, Bytecode, ExecutionResult,
|
||||
@ -20,15 +20,37 @@ pub struct Executor {
|
||||
pub config: Config,
|
||||
}
|
||||
|
||||
/// Contains old/new account change
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum AccountInfoChangeSet {
|
||||
/// Account is newly created.
|
||||
Created {
|
||||
/// Newly created acc
|
||||
new: Account,
|
||||
},
|
||||
/// If account is deleted (selfdestructed) or if we have touched
|
||||
/// a empty account, we would need to remove/destroy it.
|
||||
/// (Look at state clearing [EIP-158](https://eips.ethereum.org/EIPS/eip-158))
|
||||
Destroyed {
|
||||
/// Old account.
|
||||
old: Account,
|
||||
},
|
||||
/// Account is changed.
|
||||
Changed {
|
||||
/// New account after transaction change.
|
||||
new: Account,
|
||||
/// Old account before transaction.
|
||||
old: Account,
|
||||
},
|
||||
/// There is not change in account information (nonce/balance).
|
||||
NoChange,
|
||||
}
|
||||
|
||||
/// Diff change set that is neede for creating history index and updating current world state.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AccountChangeSet {
|
||||
/// Old and New account.
|
||||
/// If account is deleted (selfdestructed) we would have (Some,None).
|
||||
/// If account is newly created we would have (None,Some).
|
||||
/// If it is only a storage change we would have (None,None)
|
||||
/// If account is changed (balance,nonce) we would have (Some,Some)
|
||||
pub account: (Option<Account>, Option<Account>),
|
||||
/// Old and New account account change.
|
||||
pub account: AccountInfoChangeSet,
|
||||
/// Storage containing key -> (OldValue,NewValue). in case that old value is not existing
|
||||
/// we can expect to have U256::zero(), same with new value.
|
||||
pub storage: BTreeMap<U256, (U256, U256)>,
|
||||
@ -67,15 +89,7 @@ pub fn commit_changes<DB: StateProvider>(
|
||||
change.insert(
|
||||
address,
|
||||
AccountChangeSet {
|
||||
account: (
|
||||
Some(Account {
|
||||
nonce: db_account.info.nonce,
|
||||
balance: db_account.info.balance,
|
||||
bytecode_hash: Some(db_account.info.code_hash), /* TODO none if
|
||||
* KECCAK_EMPTY */
|
||||
}),
|
||||
None,
|
||||
),
|
||||
account: AccountInfoChangeSet::Destroyed { old: to_reth_acc(&db_account.info) },
|
||||
storage: BTreeMap::new(),
|
||||
wipe_storage: true,
|
||||
},
|
||||
@ -107,30 +121,33 @@ pub fn commit_changes<DB: StateProvider>(
|
||||
// get old account that is going to be overwritten or none if it does not exist
|
||||
// and get new account that was just inserted. new account mut ref is used for
|
||||
// inserting storage
|
||||
let (old_account, has_change, new_account) = match db.accounts.entry(address) {
|
||||
let (account_info_changeset, new_account) = match db.accounts.entry(address) {
|
||||
Entry::Vacant(entry) => {
|
||||
let entry = entry.insert(Default::default());
|
||||
entry.info = account.info.clone();
|
||||
(None, true, entry)
|
||||
// account was not existing, so this means new account is created
|
||||
(AccountInfoChangeSet::Created { new: to_reth_acc(&entry.info) }, entry)
|
||||
}
|
||||
Entry::Occupied(entry) => {
|
||||
let entry = entry.into_mut();
|
||||
let (old_account, has_change) =
|
||||
|
||||
// account is present inside cache but is markes as NotExisting.
|
||||
let account_changeset =
|
||||
if matches!(entry.account_state, AccountState::NotExisting) {
|
||||
(None, true)
|
||||
AccountInfoChangeSet::Created { new: to_reth_acc(&account.info) }
|
||||
} else if entry.info != account.info {
|
||||
AccountInfoChangeSet::Changed {
|
||||
old: to_reth_acc(&entry.info),
|
||||
new: to_reth_acc(&account.info),
|
||||
}
|
||||
} else {
|
||||
(Some(entry.info.clone()), entry.info != account.info)
|
||||
AccountInfoChangeSet::NoChange
|
||||
};
|
||||
entry.info = account.info.clone();
|
||||
(old_account, has_change, entry)
|
||||
(account_changeset, entry)
|
||||
}
|
||||
};
|
||||
// cast from revm type to reth type
|
||||
let old_account = old_account.map(|acc| Account {
|
||||
balance: acc.balance,
|
||||
nonce: acc.nonce,
|
||||
bytecode_hash: Some(acc.code_hash), // TODO if KECCAK_EMPTY return None
|
||||
});
|
||||
|
||||
let mut wipe_storage = false;
|
||||
|
||||
new_account.account_state = if account.storage_cleared {
|
||||
@ -153,24 +170,7 @@ pub fn commit_changes<DB: StateProvider>(
|
||||
// Insert into change.
|
||||
change.insert(
|
||||
address,
|
||||
AccountChangeSet {
|
||||
account: if has_change {
|
||||
(
|
||||
old_account,
|
||||
Some(new_account).map(|acc| {
|
||||
Account {
|
||||
balance: acc.info.balance,
|
||||
nonce: acc.info.nonce,
|
||||
bytecode_hash: Some(acc.info.code_hash), // TODO if KECCAK_EMPTY return None
|
||||
}
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
},
|
||||
storage,
|
||||
wipe_storage,
|
||||
},
|
||||
AccountChangeSet { account: account_info_changeset, storage, wipe_storage },
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -193,7 +193,7 @@ pub struct TransactionStatePatch {
|
||||
|
||||
/// Execute and verify block
|
||||
pub fn execute_and_verify_receipt<DB: StateProvider>(
|
||||
header: &SealedHeader,
|
||||
header: &Header,
|
||||
transactions: &[TransactionSignedEcRecovered],
|
||||
config: &Config,
|
||||
db: &mut SubState<DB>,
|
||||
@ -232,7 +232,7 @@ pub fn verify_receipt<'a>(
|
||||
/// Verify block. Execute all transaction and compare results.
|
||||
/// Return diff is on transaction granularity. We are returning vector of
|
||||
pub fn execute<DB: StateProvider>(
|
||||
header: &SealedHeader,
|
||||
header: &Header,
|
||||
transactions: &[TransactionSignedEcRecovered],
|
||||
config: &Config,
|
||||
db: &mut SubState<DB>,
|
||||
@ -281,6 +281,8 @@ pub fn execute<DB: StateProvider>(
|
||||
revm::Return::SelfDestruct
|
||||
);
|
||||
|
||||
// TODO add handling of other errors
|
||||
|
||||
// Add spend gas.
|
||||
cumulative_gas_used += gas_used;
|
||||
|
||||
@ -403,13 +405,8 @@ mod tests {
|
||||
HashMap::new(),
|
||||
);
|
||||
|
||||
let account3_old_info = Account {
|
||||
balance: 0x3635c9adc5dea00000u128.into(),
|
||||
nonce: 0x00,
|
||||
bytecode_hash: Some(H256(hex!(
|
||||
"c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
))),
|
||||
};
|
||||
let account3_old_info =
|
||||
Account { balance: 0x3635c9adc5dea00000u128.into(), nonce: 0x00, bytecode_hash: None };
|
||||
|
||||
db.insert_account(
|
||||
H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b")),
|
||||
@ -436,45 +433,31 @@ mod tests {
|
||||
assert_eq!(patch.new_bytecodes.len(), 0, "Should have zero new bytecodes");
|
||||
|
||||
let account1 = H160(hex!("1000000000000000000000000000000000000000"));
|
||||
let _account1_info = Account {
|
||||
balance: 0x00.into(),
|
||||
nonce: 0x00,
|
||||
bytecode_hash: Some(H256(hex!(
|
||||
"c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
))),
|
||||
};
|
||||
let _account1_info = Account { balance: 0x00.into(), nonce: 0x00, bytecode_hash: None };
|
||||
let account2 = H160(hex!("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"));
|
||||
let account2_info = Account {
|
||||
balance: (0x1bc16d674ece94bau128 - 0x1bc16d674ec80000u128).into(), /* TODO remove
|
||||
* 2eth block
|
||||
* reward */
|
||||
// TODO remove 2eth block reward
|
||||
balance: (0x1bc16d674ece94bau128 - 0x1bc16d674ec80000u128).into(),
|
||||
nonce: 0x00,
|
||||
bytecode_hash: Some(H256(hex!(
|
||||
"c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
))),
|
||||
bytecode_hash: None,
|
||||
};
|
||||
let account3 = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b"));
|
||||
let account3_info = Account {
|
||||
balance: 0x3635c9adc5de996b46u128.into(),
|
||||
nonce: 0x01,
|
||||
bytecode_hash: Some(H256(hex!(
|
||||
"c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
))),
|
||||
};
|
||||
let account3_info =
|
||||
Account { balance: 0x3635c9adc5de996b46u128.into(), nonce: 0x01, bytecode_hash: None };
|
||||
|
||||
assert_eq!(
|
||||
patch.state_diff.get(&account1).unwrap().account,
|
||||
(None, None),
|
||||
AccountInfoChangeSet::NoChange,
|
||||
"No change to account"
|
||||
);
|
||||
assert_eq!(
|
||||
patch.state_diff.get(&account2).unwrap().account,
|
||||
(None, Some(account2_info)),
|
||||
AccountInfoChangeSet::Created { new: account2_info },
|
||||
"New acccount"
|
||||
);
|
||||
assert_eq!(
|
||||
patch.state_diff.get(&account3).unwrap().account,
|
||||
(Some(account3_old_info), Some(account3_info)),
|
||||
AccountInfoChangeSet::Changed { old: account3_old_info, new: account3_info },
|
||||
"Change to account state"
|
||||
);
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use reth_interfaces::{provider::StateProvider, Error};
|
||||
use reth_primitives::{
|
||||
Header, Transaction, TransactionKind, TransactionSignedEcRecovered, TxEip1559, TxEip2930,
|
||||
TxLegacy, H160, H256, KECCAK_EMPTY, U256,
|
||||
Account, Header, Transaction, TransactionKind, TransactionSignedEcRecovered, TxEip1559,
|
||||
TxEip2930, TxLegacy, H160, H256, KECCAK_EMPTY, U256,
|
||||
};
|
||||
use revm::{
|
||||
db::{CacheDB, DatabaseRef},
|
||||
@ -179,3 +179,17 @@ pub fn is_log_equal(revm_log: &revm::Log, reth_log: &reth_primitives::Log) -> bo
|
||||
.zip(reth_log.topics.iter())
|
||||
.any(|(revm_topic, reth_topic)| revm_topic != reth_topic)
|
||||
}
|
||||
|
||||
/// Create reth primitive [Account] from [revm::AccountInfo].
|
||||
/// Check if revm bytecode hash is [KECCAK_EMPTY] and put None to reth [Account]
|
||||
pub fn to_reth_acc(revm_acc: &revm::AccountInfo) -> Account {
|
||||
Account {
|
||||
balance: revm_acc.balance,
|
||||
nonce: revm_acc.nonce,
|
||||
bytecode_hash: if revm_acc.code_hash == KECCAK_EMPTY {
|
||||
None
|
||||
} else {
|
||||
Some(revm_acc.code_hash)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,7 +251,7 @@ pub struct DupWalker<'cursor, 'tx, T: DupSort, CURSOR: DbDupCursorRO<'tx, T>> {
|
||||
/// Cursor to be used to walk through the table.
|
||||
pub cursor: &'cursor mut CURSOR,
|
||||
/// Value where to start the walk.
|
||||
pub start: Option<Result<T::Value, Error>>,
|
||||
pub start: IterPairResult<T>,
|
||||
/// Phantom data for 'tx. As it is only used for `DbDupCursorRO`.
|
||||
pub _tx_phantom: PhantomData<&'tx T>,
|
||||
}
|
||||
@ -259,13 +259,13 @@ pub struct DupWalker<'cursor, 'tx, T: DupSort, CURSOR: DbDupCursorRO<'tx, T>> {
|
||||
impl<'cursor, 'tx, T: DupSort, CURSOR: DbDupCursorRO<'tx, T>> std::iter::Iterator
|
||||
for DupWalker<'cursor, 'tx, T, CURSOR>
|
||||
{
|
||||
type Item = Result<T::Value, Error>;
|
||||
type Item = Result<(T::Key, T::Value), Error>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let start = self.start.take();
|
||||
if start.is_some() {
|
||||
return start
|
||||
}
|
||||
self.cursor.next_dup_val().transpose()
|
||||
self.cursor.next_dup().transpose()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,13 +13,15 @@ use reth_primitives::{Account, Address, TxNumber};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Account as it is saved inside [`AccountChangeSet`]. [`Address`] is the subkey.
|
||||
/// TODO there should be `not_existing` boolean or Account be made as `Option` to
|
||||
/// handle scenario where account was not present before transaction.
|
||||
#[main_codec]
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq)]
|
||||
pub struct AccountBeforeTx {
|
||||
/// Address for the account. Acts as `DupSort::SubKey`.
|
||||
address: Address,
|
||||
pub address: Address,
|
||||
/// Account state before the transaction.
|
||||
info: Account,
|
||||
pub info: Account,
|
||||
}
|
||||
|
||||
/// [`TxNumber`] concatenated with [`Address`]. Used as a key for [`StorageChangeSet`]
|
||||
|
||||
@ -22,7 +22,7 @@ pub type NumTransactions = u64;
|
||||
///
|
||||
/// The [TxNumber]s for all the transactions in the block are `base_tx_id..(base_tx_id +
|
||||
/// tx_amount)`.
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
#[derive(Debug, Default, Eq, PartialEq, Clone)]
|
||||
#[main_codec]
|
||||
pub struct StoredBlockBody {
|
||||
/// The ID of the first transaction in the block.
|
||||
@ -33,6 +33,13 @@ pub struct StoredBlockBody {
|
||||
pub ommers: Vec<Header>,
|
||||
}
|
||||
|
||||
impl StoredBlockBody {
|
||||
/// Return next block tx id.
|
||||
pub fn next_block_tx_id(&self) -> TxNumber {
|
||||
self.base_tx_id + self.tx_amount
|
||||
}
|
||||
}
|
||||
|
||||
/// Hash of the block header. Value for [`CanonicalHeaders`]
|
||||
pub type HeaderHash = H256;
|
||||
|
||||
|
||||
@ -151,6 +151,9 @@ table!(
|
||||
|
||||
table!(
|
||||
/// Stores all smart contract bytecodes.
|
||||
/// There will be multiple accounts that have same bytecode
|
||||
/// So we would need to introduce reference counter.
|
||||
/// This will be small optimization on state.
|
||||
Bytecodes => H256 => Bytecode);
|
||||
|
||||
dupsort!(
|
||||
@ -208,15 +211,20 @@ table!(
|
||||
|
||||
dupsort!(
|
||||
/// Stores the state of an account before a certain transaction changed it.
|
||||
/// Change on state can be: account is created, selfdestructed, touched while empty
|
||||
/// or changed (balance,nonce).
|
||||
AccountChangeSet => TxNumber => [Address] AccountBeforeTx);
|
||||
|
||||
dupsort!(
|
||||
/// Stores the state of a storage key before a certain transaction changed it.
|
||||
/// If [`StorageEntry::value`] is zero, this means storage was not existing
|
||||
/// and needs to be removed.
|
||||
StorageChangeSet => TxNumberAddress => [H256] StorageEntry);
|
||||
|
||||
table!(
|
||||
/// Stores the transaction sender for each transaction.
|
||||
TxSenders => TxNumber => Address); // Is it necessary? if so, inverted index index so we dont repeat addresses?
|
||||
/// It is needed to speed up execution stage and allows fetching signer without doing transaction signed recovery
|
||||
TxSenders => TxNumber => Address);
|
||||
|
||||
table!(
|
||||
/// Configuration values.
|
||||
|
||||
@ -1,8 +1,15 @@
|
||||
use crate::Result;
|
||||
use crate::{
|
||||
db::{
|
||||
models::{BlockNumHash, StoredBlockBody},
|
||||
tables, DbTx, DbTxMut,
|
||||
},
|
||||
provider::Error as ProviderError,
|
||||
Result,
|
||||
};
|
||||
use auto_impl::auto_impl;
|
||||
use reth_primitives::{
|
||||
rpc::{BlockId, BlockNumber},
|
||||
Block, BlockHash, BlockHashOrNumber, Header, H256, U256,
|
||||
Block, BlockHash, BlockHashOrNumber, BlockLocked, Header, H256, U256,
|
||||
};
|
||||
|
||||
/// Client trait for fetching `Header` related data.
|
||||
@ -99,3 +106,61 @@ pub struct ChainInfo {
|
||||
/// Safe block
|
||||
pub safe_finalized: Option<reth_primitives::BlockNumber>,
|
||||
}
|
||||
|
||||
/// Get value from [tables::CumulativeTxCount] by block hash
|
||||
/// as the table is indexed by NumHash key we are obtaining number from
|
||||
/// [tables::HeaderNumbers]
|
||||
pub fn get_cumulative_tx_count_by_hash<'a, TX: DbTxMut<'a> + DbTx<'a>>(
|
||||
tx: &TX,
|
||||
block_hash: H256,
|
||||
) -> Result<u64> {
|
||||
let block_number = tx
|
||||
.get::<tables::HeaderNumbers>(block_hash)?
|
||||
.ok_or(ProviderError::BlockHashNotExist { block_hash })?;
|
||||
|
||||
let block_num_hash = BlockNumHash((block_number, block_hash));
|
||||
|
||||
tx.get::<tables::CumulativeTxCount>(block_num_hash)?
|
||||
.ok_or_else(|| ProviderError::BlockBodyNotExist { block_num_hash }.into())
|
||||
}
|
||||
|
||||
/// Fill block to database. Useful for tests.
|
||||
/// Check parent dependency in [tables::HeaderNumbers] and in [tables::CumulativeTxCount] tables.
|
||||
/// Inserts blocks data to [tables::CanonicalHeaders], [tables::Headers], [tables::HeaderNumbers],
|
||||
/// and transactions data to [tables::TxSenders], [tables::Transactions],
|
||||
/// [tables::CumulativeTxCount] and [tables::BlockBodies]
|
||||
pub fn insert_canonical_block<'a, TX: DbTxMut<'a> + DbTx<'a>>(
|
||||
tx: &TX,
|
||||
block: &BlockLocked,
|
||||
) -> Result<()> {
|
||||
let block_num_hash = BlockNumHash((block.number, block.hash()));
|
||||
tx.put::<tables::CanonicalHeaders>(block.number, block.hash())?;
|
||||
// Put header with canonical hashes.
|
||||
tx.put::<tables::Headers>(block_num_hash, block.header.as_ref().clone())?;
|
||||
tx.put::<tables::HeaderNumbers>(block.hash(), block.number)?;
|
||||
|
||||
let start_tx_number =
|
||||
if block.number == 0 { 0 } else { get_cumulative_tx_count_by_hash(tx, block.parent_hash)? };
|
||||
|
||||
// insert body
|
||||
tx.put::<tables::BlockBodies>(
|
||||
block_num_hash,
|
||||
StoredBlockBody {
|
||||
base_tx_id: start_tx_number,
|
||||
tx_amount: block.body.len() as u64,
|
||||
ommers: block.ommers.iter().map(|h| h.as_ref().clone()).collect(),
|
||||
},
|
||||
)?;
|
||||
|
||||
let mut tx_number = start_tx_number;
|
||||
for eth_tx in block.body.iter() {
|
||||
let rec_tx = eth_tx.clone().into_ecrecovered().unwrap();
|
||||
tx.put::<tables::TxSenders>(tx_number, rec_tx.signer())?;
|
||||
tx.put::<tables::Transactions>(tx_number, rec_tx.as_ref().clone())?;
|
||||
tx_number += 1;
|
||||
}
|
||||
|
||||
tx.put::<tables::CumulativeTxCount>(block_num_hash, tx_number)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -5,7 +5,10 @@ mod block;
|
||||
mod storage;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use storage::{StateProviderImplHistory, StateProviderImplLatest};
|
||||
pub use storage::{
|
||||
StateProviderImplHistory, StateProviderImplLatest, StateProviderImplRefHistory,
|
||||
StateProviderImplRefLatest,
|
||||
};
|
||||
|
||||
use crate::db::Database;
|
||||
|
||||
|
||||
@ -50,23 +50,70 @@ impl<DB: Database> StateProviderFactory for ProviderImpl<DB> {
|
||||
}
|
||||
}
|
||||
|
||||
/// State provider with given hash
|
||||
/// State provider for given transaction number
|
||||
pub struct StateProviderImplHistory<'a, TX: DbTx<'a>> {
|
||||
/// Transaction
|
||||
db: TX,
|
||||
/// Database transaction
|
||||
tx: TX,
|
||||
/// Transaction number is main indexer of account and storage changes
|
||||
transaction_number: TxNumber,
|
||||
/// Phantom lifetime `'a`
|
||||
_phantom: PhantomData<&'a TX>,
|
||||
}
|
||||
|
||||
impl<'a, TX: DbTx<'a>> StateProviderImplHistory<'a, TX> {
|
||||
/// Create new StateProvider from history transaction number
|
||||
pub fn new(db: TX, transaction_number: TxNumber) -> Self {
|
||||
Self { db, transaction_number, _phantom: PhantomData {} }
|
||||
pub fn new(tx: TX, transaction_number: TxNumber) -> Self {
|
||||
Self { tx, transaction_number, _phantom: PhantomData {} }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TX: DbTx<'a>> AccountProvider for StateProviderImplHistory<'a, TX> {
|
||||
/// Get basic account information.
|
||||
fn basic_account(&self, address: Address) -> Result<Option<Account>> {
|
||||
StateProviderImplRefHistory::new(&self.tx, self.transaction_number).basic_account(address)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TX: DbTx<'a>> StateProvider for StateProviderImplHistory<'a, TX> {
|
||||
fn storage(&self, account: Address, storage_key: StorageKey) -> Result<Option<StorageValue>> {
|
||||
StateProviderImplRefHistory::new(&self.tx, self.transaction_number)
|
||||
.storage(account, storage_key)
|
||||
}
|
||||
|
||||
fn bytecode_by_hash(&self, code_hash: H256) -> Result<Option<Bytes>> {
|
||||
StateProviderImplRefHistory::new(&self.tx, self.transaction_number)
|
||||
.bytecode_by_hash(code_hash)
|
||||
}
|
||||
|
||||
fn block_hash(&self, number: U256) -> Result<Option<H256>> {
|
||||
StateProviderImplRefHistory::new(&self.tx, self.transaction_number).block_hash(number)
|
||||
}
|
||||
}
|
||||
/// State provider with given hash
|
||||
///
|
||||
/// It will access:
|
||||
/// [tables::AccountHistory]
|
||||
/// [tables::Bytecodes]
|
||||
/// [tables::StorageHistory]
|
||||
/// [tables::AccountChangeSet]
|
||||
/// [tables::StorageChangeSet]
|
||||
pub struct StateProviderImplRefHistory<'a, 'b, TX: DbTx<'a>> {
|
||||
/// Transaction
|
||||
tx: &'b TX,
|
||||
/// Transaction number is main indexer of account and storage changes
|
||||
transaction_number: TxNumber,
|
||||
/// Phantom lifetime `'a`
|
||||
_phantom: PhantomData<&'a TX>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, TX: DbTx<'a>> StateProviderImplRefHistory<'a, 'b, TX> {
|
||||
/// Create new StateProvider from history transaction number
|
||||
pub fn new(tx: &'b TX, transaction_number: TxNumber) -> Self {
|
||||
Self { tx, transaction_number, _phantom: PhantomData {} }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, TX: DbTx<'a>> AccountProvider for StateProviderImplRefHistory<'a, 'b, TX> {
|
||||
/// Get basic account information.
|
||||
fn basic_account(&self, _address: Address) -> Result<Option<Account>> {
|
||||
// TODO add when AccountHistory is defined
|
||||
@ -74,12 +121,12 @@ impl<'a, TX: DbTx<'a>> AccountProvider for StateProviderImplHistory<'a, TX> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TX: DbTx<'a>> StateProvider for StateProviderImplHistory<'a, TX> {
|
||||
impl<'a, 'b, TX: DbTx<'a>> StateProvider for StateProviderImplRefHistory<'a, 'b, TX> {
|
||||
/// Get storage.
|
||||
fn storage(&self, account: Address, storage_key: StorageKey) -> Result<Option<StorageValue>> {
|
||||
// TODO when StorageHistory is defined
|
||||
let transaction_number =
|
||||
self.db.get::<tables::StorageHistory>(Vec::new())?.map(|_integer_list|
|
||||
self.tx.get::<tables::StorageHistory>(Vec::new())?.map(|_integer_list|
|
||||
// TODO select integer that is one less from transaction_number
|
||||
self.transaction_number);
|
||||
|
||||
@ -87,7 +134,7 @@ impl<'a, TX: DbTx<'a>> StateProvider for StateProviderImplHistory<'a, TX> {
|
||||
return Ok(None)
|
||||
}
|
||||
let num = transaction_number.unwrap();
|
||||
let mut cursor = self.db.cursor_dup::<tables::StorageChangeSet>()?;
|
||||
let mut cursor = self.tx.cursor_dup::<tables::StorageChangeSet>()?;
|
||||
|
||||
if let Some((_, entry)) = cursor.seek_exact((num, account).into())? {
|
||||
if entry.key == storage_key {
|
||||
@ -105,38 +152,74 @@ impl<'a, TX: DbTx<'a>> StateProvider for StateProviderImplHistory<'a, TX> {
|
||||
|
||||
/// Get account code by its hash
|
||||
fn bytecode_by_hash(&self, code_hash: H256) -> Result<Option<Bytes>> {
|
||||
self.db.get::<tables::Bytecodes>(code_hash).map_err(Into::into).map(|r| r.map(Bytes::from))
|
||||
self.tx.get::<tables::Bytecodes>(code_hash).map_err(Into::into).map(|r| r.map(Bytes::from))
|
||||
}
|
||||
|
||||
/// Get block hash by number.
|
||||
fn block_hash(&self, number: U256) -> Result<Option<H256>> {
|
||||
self.db.get::<tables::CanonicalHeaders>(number.as_u64()).map_err(Into::into)
|
||||
self.tx.get::<tables::CanonicalHeaders>(number.as_u64()).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
/// State Provider over latest state
|
||||
/// State provider for latests state
|
||||
pub struct StateProviderImplLatest<'a, TX: DbTx<'a>> {
|
||||
/// database transaction
|
||||
db: TX,
|
||||
/// Phantom data over lifetime
|
||||
phantom: PhantomData<&'a TX>,
|
||||
/// Phantom lifetime `'a`
|
||||
_phantom: PhantomData<&'a TX>,
|
||||
}
|
||||
|
||||
impl<'a, TX: DbTx<'a>> StateProviderImplLatest<'a, TX> {
|
||||
/// Create new state provider
|
||||
pub fn new(db: TX) -> Self {
|
||||
Self { db, phantom: PhantomData {} }
|
||||
Self { db, _phantom: PhantomData {} }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TX: DbTx<'a>> AccountProvider for StateProviderImplLatest<'a, TX> {
|
||||
/// Get basic account information.
|
||||
fn basic_account(&self, address: Address) -> Result<Option<Account>> {
|
||||
self.db.get::<tables::PlainAccountState>(address).map_err(Into::into)
|
||||
StateProviderImplRefLatest::new(&self.db).basic_account(address)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TX: DbTx<'a>> StateProvider for StateProviderImplLatest<'a, TX> {
|
||||
fn storage(&self, account: Address, storage_key: StorageKey) -> Result<Option<StorageValue>> {
|
||||
StateProviderImplRefLatest::new(&self.db).storage(account, storage_key)
|
||||
}
|
||||
|
||||
fn bytecode_by_hash(&self, code_hash: H256) -> Result<Option<Bytes>> {
|
||||
StateProviderImplRefLatest::new(&self.db).bytecode_by_hash(code_hash)
|
||||
}
|
||||
|
||||
fn block_hash(&self, number: U256) -> Result<Option<H256>> {
|
||||
StateProviderImplRefLatest::new(&self.db).block_hash(number)
|
||||
}
|
||||
}
|
||||
|
||||
/// State Provider over latest state that takes tx reference
|
||||
pub struct StateProviderImplRefLatest<'a, 'b, TX: DbTx<'a>> {
|
||||
/// database transaction
|
||||
db: &'b TX,
|
||||
/// Phantom data over lifetime
|
||||
phantom: PhantomData<&'a TX>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, TX: DbTx<'a>> StateProviderImplRefLatest<'a, 'b, TX> {
|
||||
/// Create new state provider
|
||||
pub fn new(db: &'b TX) -> Self {
|
||||
Self { db, phantom: PhantomData {} }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, TX: DbTx<'a>> AccountProvider for StateProviderImplRefLatest<'a, 'b, TX> {
|
||||
/// Get basic account information.
|
||||
fn basic_account(&self, address: Address) -> Result<Option<Account>> {
|
||||
self.db.get::<tables::PlainAccountState>(address).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, TX: DbTx<'a>> StateProvider for StateProviderImplRefLatest<'a, 'b, TX> {
|
||||
/// Get storage.
|
||||
fn storage(&self, account: Address, storage_key: StorageKey) -> Result<Option<StorageValue>> {
|
||||
let mut cursor = self.db.cursor_dup::<tables::PlainStorageState>()?;
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
use reth_primitives::{BlockHash, BlockNumber};
|
||||
|
||||
use crate::db::models::BlockNumHash;
|
||||
|
||||
/// KV error type. They are using u32 to represent error code.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone)]
|
||||
@ -10,4 +12,6 @@ pub enum Error {
|
||||
BlockTxNumberNotExists { block_hash: BlockHash },
|
||||
#[error("Block hash {block_hash:?} does not exists in Headers table")]
|
||||
BlockHashNotExist { block_hash: BlockHash },
|
||||
#[error("Block body not exists {block_num_hash:?}")]
|
||||
BlockBodyNotExist { block_num_hash: BlockNumHash },
|
||||
}
|
||||
|
||||
@ -3,7 +3,10 @@ pub mod db_provider;
|
||||
mod error;
|
||||
mod state;
|
||||
|
||||
pub use block::{BlockProvider, ChainInfo, HeaderProvider};
|
||||
pub use block::{
|
||||
get_cumulative_tx_count_by_hash, insert_canonical_block, BlockProvider, ChainInfo,
|
||||
HeaderProvider,
|
||||
};
|
||||
pub use db_provider::{self as db, ProviderImpl};
|
||||
pub use error::Error;
|
||||
pub use state::{AccountProvider, StateProvider, StateProviderFactory};
|
||||
|
||||
@ -782,6 +782,14 @@ impl TransactionSignedEcRecovered {
|
||||
self.signer
|
||||
}
|
||||
|
||||
/// Create [TransactionSignedEcRecovered] from [TransactionSigned] and author of transaction
|
||||
pub fn from_signed_transactions_and_signer(
|
||||
signed_transaction: TransactionSigned,
|
||||
signer: Address,
|
||||
) -> Self {
|
||||
Self { signed_transaction, signer }
|
||||
}
|
||||
|
||||
/// Transform back to [`TransactionSigned`]
|
||||
pub fn into_signed(self) -> TransactionSigned {
|
||||
self.signed_transaction
|
||||
|
||||
@ -8,14 +8,20 @@ readme = "README.md"
|
||||
description = "Staged syncing primitives used in reth."
|
||||
|
||||
[dependencies]
|
||||
# reth libs
|
||||
reth-primitives = { path = "../primitives" }
|
||||
reth-interfaces = { path = "../interfaces" }
|
||||
reth-db = { path = "../db" }
|
||||
reth-executor = { path = "../executor" }
|
||||
reth-rlp = { path = "../common/rlp" }
|
||||
|
||||
#async
|
||||
tokio = { version = "1.21.2", features = ["sync"] }
|
||||
|
||||
async-trait = "0.1.57"
|
||||
thiserror = "1.0.37"
|
||||
tracing = "0.1.36"
|
||||
tracing-futures = "0.2.5"
|
||||
tokio = { version = "1.21.2", features = ["sync"] }
|
||||
aquamarine = "0.1.12"
|
||||
metrics = "0.20.1"
|
||||
futures-util = "0.3.25"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use crate::pipeline::PipelineEvent;
|
||||
use reth_interfaces::{consensus, db::Error as DbError};
|
||||
use reth_primitives::{BlockNumber, H256};
|
||||
use reth_interfaces::{consensus, db::Error as DbError, executor};
|
||||
use reth_primitives::{BlockNumber, TxNumber, H256};
|
||||
use thiserror::Error;
|
||||
use tokio::sync::mpsc::error::SendError;
|
||||
|
||||
@ -19,6 +19,15 @@ pub enum StageError {
|
||||
/// The stage encountered a database error.
|
||||
#[error("An internal database error occurred: {0}")]
|
||||
Database(#[from] DbError),
|
||||
#[error("Stage encountered a execution error in block {block}: {error}.")]
|
||||
/// The stage encountered a execution error
|
||||
ExecutionError {
|
||||
/// The block that failed execution.
|
||||
block: BlockNumber,
|
||||
/// The underlying execution error.
|
||||
#[source]
|
||||
error: executor::Error,
|
||||
},
|
||||
/// The stage encountered a database integrity error.
|
||||
#[error("A database integrity error occurred: {0}")]
|
||||
DatabaseIntegrity(#[from] DatabaseIntegrityError),
|
||||
@ -34,6 +43,7 @@ pub enum StageError {
|
||||
/// A database integrity error.
|
||||
/// The sender stage error
|
||||
#[derive(Error, Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum DatabaseIntegrityError {
|
||||
// TODO(onbjerg): What's the difference between this and the one below?
|
||||
/// The canonical hash for a block is missing from the database.
|
||||
@ -70,6 +80,14 @@ pub enum DatabaseIntegrityError {
|
||||
/// The block number key
|
||||
number: BlockNumber,
|
||||
},
|
||||
#[error("Gap in transaction table. Missing tx number #{missing}.")]
|
||||
TransactionsGap { missing: TxNumber },
|
||||
#[error("Gap in transaction signer table. Missing tx number #{missing}.")]
|
||||
TransactionsSignerGap { missing: TxNumber },
|
||||
#[error("Got to the end of transaction table")]
|
||||
EndOfTransactionTable,
|
||||
#[error("Got to the end of transaction table")]
|
||||
EndOfTransactionSenderTable,
|
||||
/// The total difficulty from the block header is missing.
|
||||
#[error("Total difficulty not found for block #{number}")]
|
||||
TotalDifficulty {
|
||||
|
||||
580
crates/stages/src/stages/execution.rs
Normal file
580
crates/stages/src/stages/execution.rs
Normal file
@ -0,0 +1,580 @@
|
||||
use crate::{
|
||||
db::StageDB, DatabaseIntegrityError, ExecInput, ExecOutput, Stage, StageError, StageId,
|
||||
UnwindInput, UnwindOutput,
|
||||
};
|
||||
use reth_executor::{
|
||||
config::SpecUpgrades,
|
||||
executor::{AccountChangeSet, AccountInfoChangeSet},
|
||||
revm_wrap::{State, SubState},
|
||||
Config,
|
||||
};
|
||||
use reth_interfaces::{
|
||||
db::{
|
||||
models::{AccountBeforeTx, BlockNumHash, TxNumberAddress},
|
||||
tables, Database, DbCursorRO, DbCursorRW, DbDupCursorRO, DbTx, DbTxMut,
|
||||
},
|
||||
provider::db::StateProviderImplRefLatest,
|
||||
};
|
||||
use reth_primitives::{Address, StorageEntry, TransactionSignedEcRecovered, H256, U256};
|
||||
use std::{fmt::Debug, ops::DerefMut};
|
||||
|
||||
const EXECUTION: StageId = StageId("Execution");
|
||||
|
||||
/// The execution stage executes all transactions and
|
||||
/// update history indexes.
|
||||
///
|
||||
/// Input tables:
|
||||
/// [tables::CanonicalHeaders] get next block to execute.
|
||||
/// [tables::Headers] get for revm environment variables.
|
||||
/// [tables::CumulativeTxCount] to get tx number
|
||||
/// [tables::Transactions] to execute
|
||||
///
|
||||
/// For state access [StateProvider] provides us latest state and history state
|
||||
/// For latest most recent state [StateProvider] would need (Used for execution Stage):
|
||||
/// [tables::PlainAccountState]
|
||||
/// [tables::Bytecodes]
|
||||
/// [tables::PlainStorageState]
|
||||
///
|
||||
/// Tables updated after state finishes execution:
|
||||
/// [tables::PlainAccountState]
|
||||
/// [tables::PlainStorageState]
|
||||
/// [tables::Bytecodes]
|
||||
/// [tables::AccountChangeSet]
|
||||
/// [tables::StorageChangeSet]
|
||||
///
|
||||
/// For unwinds we are accessing:
|
||||
/// [tables::CumulativeTxCount] get tx index to know what needs to be unwinded
|
||||
/// [tables::AccountHistory] to remove change set and apply old values to
|
||||
/// [tables::PlainAccountState] [tables::StorageHistory] to remove change set and apply old values
|
||||
/// to [tables::PlainStorageState]
|
||||
#[derive(Debug)]
|
||||
pub struct ExecutionStage;
|
||||
|
||||
/// Specify batch sizes of block in execution
|
||||
/// TODO make this as config
|
||||
const BATCH_SIZE: u64 = 1000;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<DB: Database> Stage<DB> for ExecutionStage {
|
||||
/// Return the id of the stage
|
||||
fn id(&self) -> StageId {
|
||||
EXECUTION
|
||||
}
|
||||
|
||||
/// Execute the stage
|
||||
async fn execute(
|
||||
&mut self,
|
||||
db: &mut StageDB<'_, DB>,
|
||||
input: ExecInput,
|
||||
) -> Result<ExecOutput, StageError> {
|
||||
let db_tx = db.deref_mut();
|
||||
// none and zero are same as for genesis block (zeroed block) we are making assumption to
|
||||
// not have transaction.
|
||||
let last_block = input.stage_progress.unwrap_or_default();
|
||||
let start_block = last_block + 1;
|
||||
|
||||
// Get next canonical block hashes to execute.
|
||||
let mut canonicals = db_tx.cursor::<tables::CanonicalHeaders>()?;
|
||||
// Get header with canonical hashes.
|
||||
let mut headers = db_tx.cursor::<tables::Headers>()?;
|
||||
// Get bodies (to get tx index) with canonical hashes.
|
||||
let mut cumulative_tx_count = db_tx.cursor::<tables::CumulativeTxCount>()?;
|
||||
// Get transaction of the block that we are executing.
|
||||
let mut tx = db_tx.cursor::<tables::Transactions>()?;
|
||||
// Skip sender recovery and load signer from database.
|
||||
let mut tx_sender = db_tx.cursor::<tables::TxSenders>()?;
|
||||
|
||||
// get canonical blocks (num,hash)
|
||||
let canonical_batch = canonicals
|
||||
.walk(start_block)?
|
||||
.take(BATCH_SIZE as usize)
|
||||
.map(|i| i.map(BlockNumHash))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// no more canonical blocks, we are done with execution.
|
||||
if canonical_batch.is_empty() {
|
||||
return Ok(ExecOutput { done: true, reached_tip: true, stage_progress: last_block })
|
||||
}
|
||||
|
||||
// get headers from canonical numbers
|
||||
let headers_batch = canonical_batch
|
||||
.iter()
|
||||
.map(|ch_index| {
|
||||
// TODO see if walker next has better performance then seek_exact calls.
|
||||
headers.seek_exact(*ch_index).map_err(StageError::Database).and_then(|res| {
|
||||
res.ok_or_else(|| {
|
||||
DatabaseIntegrityError::Header {
|
||||
number: ch_index.number(),
|
||||
hash: ch_index.hash(),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.map(|(_, header)| header)
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// get last tx count so that we can know amount of transaction in the block.
|
||||
let mut last_tx_count = if last_block == 0 {
|
||||
0u64
|
||||
} else {
|
||||
// headers_batch is not empty,
|
||||
let parent_hash = headers_batch[0].parent_hash;
|
||||
|
||||
let (_, tx_cnt) = cumulative_tx_count
|
||||
.seek_exact(BlockNumHash((last_block, parent_hash)))?
|
||||
.ok_or(DatabaseIntegrityError::CumulativeTxCount {
|
||||
number: last_block,
|
||||
hash: parent_hash,
|
||||
})?;
|
||||
tx_cnt
|
||||
};
|
||||
|
||||
let cumulative_tx_count_batch = canonical_batch
|
||||
.iter()
|
||||
.map(|ch_index| {
|
||||
// TODO see if walker next has better performance then seek_exact calls.
|
||||
cumulative_tx_count
|
||||
.seek_exact(*ch_index)
|
||||
.map_err(StageError::Database)
|
||||
.and_then(|res| {
|
||||
res.ok_or_else(|| {
|
||||
DatabaseIntegrityError::CumulativeTxCount {
|
||||
number: ch_index.number(),
|
||||
hash: ch_index.hash(),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
})
|
||||
.map(|(_, cumulative_tx_count)| {
|
||||
let ret = (last_tx_count, cumulative_tx_count);
|
||||
last_tx_count = cumulative_tx_count;
|
||||
ret
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// Fetch transactions, execute them and generate results
|
||||
let mut block_change_patches = Vec::with_capacity(canonical_batch.len());
|
||||
for (header, body_range) in headers_batch.iter().zip(cumulative_tx_count_batch.iter()) {
|
||||
let start_tx_index = body_range.0;
|
||||
let end_tx_index = body_range.1;
|
||||
let body_tx_cnt = end_tx_index - start_tx_index;
|
||||
// iterate over all transactions
|
||||
let mut tx_walker = tx.walk(start_tx_index)?;
|
||||
let mut transactions = Vec::with_capacity(body_tx_cnt as usize);
|
||||
// get next N transactions.
|
||||
for index in start_tx_index..end_tx_index {
|
||||
let (tx_index, tx) =
|
||||
tx_walker.next().ok_or(DatabaseIntegrityError::EndOfTransactionTable)??;
|
||||
if tx_index != index {
|
||||
return Err(DatabaseIntegrityError::TransactionsGap { missing: tx_index }.into())
|
||||
}
|
||||
transactions.push(tx);
|
||||
}
|
||||
|
||||
// take signers
|
||||
let mut tx_sender_walker = tx_sender.walk(start_tx_index)?;
|
||||
let mut signers = Vec::with_capacity(body_tx_cnt as usize);
|
||||
for index in start_tx_index..end_tx_index {
|
||||
let (tx_index, tx) = tx_sender_walker
|
||||
.next()
|
||||
.ok_or(DatabaseIntegrityError::EndOfTransactionSenderTable)??;
|
||||
if tx_index != index {
|
||||
return Err(
|
||||
DatabaseIntegrityError::TransactionsSignerGap { missing: tx_index }.into()
|
||||
)
|
||||
}
|
||||
signers.push(tx);
|
||||
}
|
||||
// create ecRecovered transaction by matching tx and its signer
|
||||
let recovered_transactions: Vec<_> = transactions
|
||||
.into_iter()
|
||||
.zip(signers.into_iter())
|
||||
.map(|(tx, signer)| {
|
||||
TransactionSignedEcRecovered::from_signed_transactions_and_signer(tx, signer)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// for now use default eth config
|
||||
let config = Config { chain_id: 1.into(), spec_upgrades: SpecUpgrades::new_ethereum() };
|
||||
|
||||
let mut state_provider =
|
||||
SubState::new(State::new(StateProviderImplRefLatest::new(db_tx)));
|
||||
|
||||
// executiong and store output to results
|
||||
block_change_patches.push((
|
||||
reth_executor::executor::execute_and_verify_receipt(
|
||||
header,
|
||||
&recovered_transactions,
|
||||
&config,
|
||||
&mut state_provider,
|
||||
)
|
||||
.map_err(|error| StageError::ExecutionError { block: header.number, error })?,
|
||||
start_tx_index,
|
||||
));
|
||||
}
|
||||
|
||||
// apply changes to plain database.
|
||||
for (results, start_tx_index) in block_change_patches.into_iter() {
|
||||
for (index, result) in results.into_iter().enumerate() {
|
||||
let tx_index = start_tx_index + index as u64;
|
||||
// insert account change set
|
||||
for (address, AccountChangeSet { account, wipe_storage, storage }) in
|
||||
result.state_diff.into_iter()
|
||||
{
|
||||
match account {
|
||||
AccountInfoChangeSet::Changed { old, new } => {
|
||||
// insert old account in AccountChangeSet
|
||||
// check for old != new was already done
|
||||
db_tx.put::<tables::AccountChangeSet>(
|
||||
tx_index,
|
||||
AccountBeforeTx { address, info: old },
|
||||
)?;
|
||||
db_tx.put::<tables::PlainAccountState>(address, new)?;
|
||||
}
|
||||
AccountInfoChangeSet::Created { new } => {
|
||||
// TODO put None accounts inside changeset when `AccountBeforeTx` get
|
||||
// fixed
|
||||
db_tx.put::<tables::PlainAccountState>(address, new)?;
|
||||
}
|
||||
AccountInfoChangeSet::Destroyed { old } => {
|
||||
db_tx.delete::<tables::PlainAccountState>(address, None)?;
|
||||
db_tx.put::<tables::AccountChangeSet>(
|
||||
tx_index,
|
||||
AccountBeforeTx { address, info: old },
|
||||
)?;
|
||||
}
|
||||
AccountInfoChangeSet::NoChange => {
|
||||
// do nothing storage account didn't change
|
||||
}
|
||||
}
|
||||
|
||||
// wipe storage
|
||||
if wipe_storage {
|
||||
// TODO insert all changes to StorageChangeSet
|
||||
db_tx.delete::<tables::PlainStorageState>(address, None)?;
|
||||
}
|
||||
// insert storage changeset
|
||||
let storage_id = TxNumberAddress((tx_index, address));
|
||||
for (key, (old_value, new_value)) in storage {
|
||||
let mut hkey = H256::zero();
|
||||
key.to_big_endian(&mut hkey.0);
|
||||
|
||||
// insert into StorageChangeSet
|
||||
db_tx.put::<tables::StorageChangeSet>(
|
||||
storage_id.clone(),
|
||||
StorageEntry { key: hkey, value: old_value },
|
||||
)?;
|
||||
|
||||
if new_value.is_zero() {
|
||||
db_tx.delete::<tables::PlainStorageState>(
|
||||
address,
|
||||
Some(StorageEntry { key: hkey, value: old_value }),
|
||||
)?;
|
||||
} else {
|
||||
db_tx.put::<tables::PlainStorageState>(
|
||||
address,
|
||||
StorageEntry { key: hkey, value: new_value },
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
// insert bytecode
|
||||
for (hash, bytecode) in result.new_bytecodes.into_iter() {
|
||||
// make different types of bytecode. Checked and maybe even analyzed (needs to
|
||||
// be packed). Currently save only raw bytes.
|
||||
db_tx.put::<tables::Bytecodes>(
|
||||
hash,
|
||||
bytecode.bytes()[..bytecode.len()].to_vec(),
|
||||
)?;
|
||||
|
||||
// NOTE: bytecode bytes are not inserted in change set and it stand in saparate
|
||||
// table
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let last_block = last_block + canonical_batch.len() as u64;
|
||||
let is_done = canonical_batch.len() < BATCH_SIZE as usize;
|
||||
Ok(ExecOutput { done: is_done, reached_tip: true, stage_progress: last_block })
|
||||
}
|
||||
|
||||
/// Unwind the stage.
|
||||
async fn unwind(
|
||||
&mut self,
|
||||
db: &mut StageDB<'_, DB>,
|
||||
input: UnwindInput,
|
||||
) -> Result<UnwindOutput, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let unwind_from = input.stage_progress;
|
||||
let unwind_to = input.unwind_to;
|
||||
let _bad_block = input.bad_block;
|
||||
|
||||
// get block body tx indexes
|
||||
let db_tx = db.deref_mut();
|
||||
|
||||
// Get transaction of the block that we are executing.
|
||||
let mut account_changeset = db_tx.cursor_dup_mut::<tables::AccountChangeSet>()?;
|
||||
// Skip sender recovery and load signer from database.
|
||||
let mut storage_changeset = db_tx.cursor_dup_mut::<tables::StorageChangeSet>()?;
|
||||
|
||||
// get from tx_number
|
||||
let unwind_from_hash = db_tx
|
||||
.get::<tables::CanonicalHeaders>(unwind_from)?
|
||||
.ok_or(DatabaseIntegrityError::CanonicalHeader { number: unwind_from })?;
|
||||
|
||||
let from_tx_number = db_tx
|
||||
.get::<tables::CumulativeTxCount>(BlockNumHash((unwind_from, unwind_from_hash)))?
|
||||
.ok_or(DatabaseIntegrityError::CumulativeTxCount {
|
||||
number: unwind_from,
|
||||
hash: unwind_from_hash,
|
||||
})?;
|
||||
|
||||
// get to tx_number
|
||||
let to_tx_number = if unwind_to == 0 {
|
||||
0
|
||||
} else {
|
||||
let parent_number = unwind_to - 1;
|
||||
let parent_hash = db_tx
|
||||
.get::<tables::CanonicalHeaders>(parent_number)?
|
||||
.ok_or(DatabaseIntegrityError::CanonicalHeader { number: parent_number })?;
|
||||
|
||||
db_tx
|
||||
.get::<tables::CumulativeTxCount>(BlockNumHash((parent_number, parent_hash)))?
|
||||
.ok_or(DatabaseIntegrityError::CumulativeTxCount {
|
||||
number: parent_number,
|
||||
hash: parent_hash,
|
||||
})?
|
||||
};
|
||||
|
||||
if to_tx_number > from_tx_number {
|
||||
panic!("Parents CumulativeTxCount {to_tx_number} is higer then present block #{unwind_to} TxCount {from_tx_number}")
|
||||
}
|
||||
let num_of_tx = (from_tx_number - to_tx_number) as usize;
|
||||
|
||||
// if there is no transaction ids, this means blocks were empty and block reward change set
|
||||
// is not present.
|
||||
if num_of_tx == 0 {
|
||||
return Ok(UnwindOutput { stage_progress: unwind_to })
|
||||
}
|
||||
|
||||
// get all batches for account change
|
||||
// Check if walk and walk_dup would do the same thing
|
||||
let account_changeset_batch = account_changeset
|
||||
.walk_dup(to_tx_number, Address::zero())?
|
||||
.take(num_of_tx)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// revert all changes to PlainState
|
||||
for (_, changeset) in account_changeset_batch.into_iter().rev() {
|
||||
db_tx.put::<tables::PlainAccountState>(changeset.address, changeset.info)?;
|
||||
// TODO remove account if none when `AccountBeforeTx` get fixed
|
||||
// if account.is_none() {
|
||||
// delete account from plain state. Storage will be cleaned it is own way.
|
||||
//}
|
||||
}
|
||||
|
||||
// get all batches for account change
|
||||
let storage_chageset_batch = storage_changeset
|
||||
.walk_dup(TxNumberAddress((to_tx_number, Address::zero())), H256::zero())?
|
||||
.take(num_of_tx)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// revert all changes to PlainStorage
|
||||
for (TxNumberAddress((_, address)), storage) in storage_chageset_batch.into_iter().rev() {
|
||||
db_tx.put::<tables::PlainStorageState>(address, storage.clone())?;
|
||||
if storage.value == U256::zero() {
|
||||
// delete value that is zero
|
||||
db_tx.delete::<tables::PlainStorageState>(address, Some(storage))?;
|
||||
}
|
||||
}
|
||||
|
||||
// Discard unwinded changesets
|
||||
let mut entry = account_changeset.last()?;
|
||||
while let Some((tx_number, _)) = entry {
|
||||
if tx_number < to_tx_number {
|
||||
break
|
||||
}
|
||||
account_changeset.delete_current()?;
|
||||
entry = account_changeset.prev()?;
|
||||
}
|
||||
|
||||
let mut entry = storage_changeset.last()?;
|
||||
while let Some((TxNumberAddress((tx_number, _)), _)) = entry {
|
||||
if tx_number < to_tx_number {
|
||||
break
|
||||
}
|
||||
storage_changeset.delete_current()?;
|
||||
entry = storage_changeset.prev()?;
|
||||
}
|
||||
|
||||
Ok(UnwindOutput { stage_progress: input.unwind_to })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ops::Deref;
|
||||
|
||||
use super::*;
|
||||
use reth_db::{
|
||||
kv::{test_utils::create_test_db, EnvKind},
|
||||
mdbx::WriteMap,
|
||||
};
|
||||
use reth_interfaces::provider::insert_canonical_block;
|
||||
use reth_primitives::{hex_literal::hex, keccak256, Account, BlockLocked, H160, U256};
|
||||
use reth_rlp::Decodable;
|
||||
|
||||
#[tokio::test]
|
||||
async fn sanity_execution_of_block() {
|
||||
// TODO cleanup the setup after https://github.com/foundry-rs/reth/issues/332
|
||||
// is merged as it has similar framework
|
||||
let state_db = create_test_db::<WriteMap>(EnvKind::RW);
|
||||
let mut db = StageDB::new(state_db.as_ref()).unwrap();
|
||||
let input = ExecInput {
|
||||
previous_stage: None,
|
||||
/// The progress of this stage the last time it was executed.
|
||||
stage_progress: None,
|
||||
};
|
||||
let mut genesis_rlp = hex!("f901faf901f5a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa045571b40ae66ca7480791bbb2887286e4e4c4b1b298b191c889d6959023a32eda056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000808502540be400808000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0").as_slice();
|
||||
let genesis = BlockLocked::decode(&mut genesis_rlp).unwrap();
|
||||
let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice();
|
||||
let block = BlockLocked::decode(&mut block_rlp).unwrap();
|
||||
insert_canonical_block(db.deref_mut(), &genesis).unwrap();
|
||||
insert_canonical_block(db.deref_mut(), &block).unwrap();
|
||||
db.commit().unwrap();
|
||||
|
||||
// insert pre state
|
||||
let tx = db.deref_mut();
|
||||
let acc1 = H160(hex!("1000000000000000000000000000000000000000"));
|
||||
let acc2 = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b"));
|
||||
let code = hex!("5a465a905090036002900360015500");
|
||||
let balance = U256::from(0x3635c9adc5dea00000u128);
|
||||
let code_hash = keccak256(code);
|
||||
tx.put::<tables::PlainAccountState>(
|
||||
acc1,
|
||||
Account { nonce: 0, balance: 0.into(), bytecode_hash: Some(code_hash) },
|
||||
)
|
||||
.unwrap();
|
||||
tx.put::<tables::PlainAccountState>(
|
||||
acc2,
|
||||
Account { nonce: 0, balance, bytecode_hash: None },
|
||||
)
|
||||
.unwrap();
|
||||
tx.put::<tables::Bytecodes>(code_hash, code.to_vec()).unwrap();
|
||||
db.commit().unwrap();
|
||||
|
||||
// execute
|
||||
let output = ExecutionStage.execute(&mut db, input).await.unwrap();
|
||||
db.commit().unwrap();
|
||||
assert_eq!(output, ExecOutput { stage_progress: 1, done: true, reached_tip: true });
|
||||
let tx = db.deref_mut();
|
||||
// check post state
|
||||
let account1 = H160(hex!("1000000000000000000000000000000000000000"));
|
||||
let account1_info =
|
||||
Account { balance: 0x00.into(), nonce: 0x00, bytecode_hash: Some(code_hash) };
|
||||
let account2 = H160(hex!("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"));
|
||||
let account2_info = Account {
|
||||
// TODO remove 2eth block reward
|
||||
balance: (0x1bc16d674ece94bau128 - 0x1bc16d674ec80000u128).into(),
|
||||
nonce: 0x00,
|
||||
bytecode_hash: None,
|
||||
};
|
||||
let account3 = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b"));
|
||||
let account3_info =
|
||||
Account { balance: 0x3635c9adc5de996b46u128.into(), nonce: 0x01, bytecode_hash: None };
|
||||
|
||||
// assert accounts
|
||||
assert_eq!(
|
||||
tx.get::<tables::PlainAccountState>(account1),
|
||||
Ok(Some(account1_info)),
|
||||
"Post changed of a account"
|
||||
);
|
||||
assert_eq!(
|
||||
tx.get::<tables::PlainAccountState>(account2),
|
||||
Ok(Some(account2_info)),
|
||||
"Post changed of a account"
|
||||
);
|
||||
assert_eq!(
|
||||
tx.get::<tables::PlainAccountState>(account3),
|
||||
Ok(Some(account3_info)),
|
||||
"Post changed of a account"
|
||||
);
|
||||
// assert storage
|
||||
// Get on dupsort would return only first value. This is good enought for this test.
|
||||
assert_eq!(
|
||||
tx.get::<tables::PlainStorageState>(account1),
|
||||
Ok(Some(StorageEntry { key: H256::from_low_u64_be(1), value: 2.into() })),
|
||||
"Post changed of a account"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sanity_execute_unwind() {
|
||||
// TODO cleanup the setup after https://github.com/foundry-rs/reth/issues/332
|
||||
// is merged as it has similar framework
|
||||
|
||||
let state_db = create_test_db::<WriteMap>(EnvKind::RW);
|
||||
let mut db = StageDB::new(state_db.as_ref()).unwrap();
|
||||
let input = ExecInput {
|
||||
previous_stage: None,
|
||||
/// The progress of this stage the last time it was executed.
|
||||
stage_progress: None,
|
||||
};
|
||||
let mut genesis_rlp = hex!("f901faf901f5a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa045571b40ae66ca7480791bbb2887286e4e4c4b1b298b191c889d6959023a32eda056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000808502540be400808000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0").as_slice();
|
||||
let genesis = BlockLocked::decode(&mut genesis_rlp).unwrap();
|
||||
let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice();
|
||||
let block = BlockLocked::decode(&mut block_rlp).unwrap();
|
||||
insert_canonical_block(db.deref_mut(), &genesis).unwrap();
|
||||
insert_canonical_block(db.deref_mut(), &block).unwrap();
|
||||
db.commit().unwrap();
|
||||
|
||||
// variables
|
||||
let code = hex!("5a465a905090036002900360015500");
|
||||
let balance = U256::from(0x3635c9adc5dea00000u128);
|
||||
let code_hash = keccak256(code);
|
||||
// pre state
|
||||
let tx = db.deref_mut();
|
||||
let acc1 = H160(hex!("1000000000000000000000000000000000000000"));
|
||||
let acc1_info = Account { nonce: 0, balance: 0.into(), bytecode_hash: Some(code_hash) };
|
||||
let acc2 = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b"));
|
||||
let acc2_info = Account { nonce: 0, balance, bytecode_hash: None };
|
||||
|
||||
tx.put::<tables::PlainAccountState>(acc1, acc1_info).unwrap();
|
||||
tx.put::<tables::PlainAccountState>(acc2, acc2_info).unwrap();
|
||||
tx.put::<tables::Bytecodes>(code_hash, code.to_vec()).unwrap();
|
||||
db.commit().unwrap();
|
||||
|
||||
// execute
|
||||
let _ = ExecutionStage.execute(&mut db, input).await.unwrap();
|
||||
db.commit().unwrap();
|
||||
|
||||
let o = ExecutionStage
|
||||
.unwind(&mut db, UnwindInput { stage_progress: 1, unwind_to: 0, bad_block: None })
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(o, UnwindOutput { stage_progress: 0 });
|
||||
|
||||
// assert unwind stage
|
||||
let tx = db.deref();
|
||||
assert_eq!(
|
||||
tx.get::<tables::PlainAccountState>(acc1),
|
||||
Ok(Some(acc1_info)),
|
||||
"Pre changed of a account"
|
||||
);
|
||||
assert_eq!(
|
||||
tx.get::<tables::PlainAccountState>(acc2),
|
||||
Ok(Some(acc2_info)),
|
||||
"Post changed of a account"
|
||||
);
|
||||
|
||||
// TODO check this after Option is added to tables::
|
||||
// let acc3 = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b"));
|
||||
// assert_eq!(
|
||||
// tx.get::<tables::PlainAccountState>(acc3),
|
||||
// Ok(Some(Account { balance: 0.into(), nonce: 0, bytecode_hash: Some(KECCAK_EMPTY) })),
|
||||
// "Post changed of a account"
|
||||
// );
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
/// The bodies stage.
|
||||
pub mod bodies;
|
||||
/// The execution stage that generates state diff.
|
||||
pub mod execution;
|
||||
/// The headers stage.
|
||||
pub mod headers;
|
||||
/// The sender recovery stage.
|
||||
|
||||
Reference in New Issue
Block a user