feat: Selfdestruct storage changeset (#974)

This commit is contained in:
rakita
2023-01-23 14:04:45 +01:00
committed by GitHub
parent 681f8a601a
commit c7289bd9e8
4 changed files with 315 additions and 53 deletions

View File

@@ -854,4 +854,86 @@ mod tests {
Ok(Some(AccountBeforeTx { address, info: Some(acc2) }))
);
}
#[test]
fn test_selfdestruct() {
// Modified version of eth test. Storage is added for selfdestructed account to see
// that changeset is set.
// Got rlp block from: src/GeneralStateTestsFiller/stArgsZeroOneBalance/suicideNonConst.json
let mut block_rlp = hex!("f9025ff901f7a0c86e8cc0310ae7c531c758678ddbfd16fc51c8cef8cec650b032de9869e8b94fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa050554882fbbda2c2fd93fdc466db9946ea262a67f7a76cc169e714f105ab583da00967f09ef1dfed20c0eacfaa94d5cd4002eda3242ac47eae68972d07b106d192a0e3c8b47fbfc94667ef4cceb17e5cc21e3b1eebd442cebb27f07562b33836290db90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001830f42408238108203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f862f860800a83061a8094095e7baea6a6c7c4c2dfeb977efac326af552d8780801ba072ed817487b84ba367d15d2f039b5fc5f087d0a8882fbdf73e8cb49357e1ce30a0403d800545b8fc544f92ce8124e2255f8c3c6af93f28243a120585d4c4c6a2a3c0").as_slice();
let block = SealedBlock::decode(&mut block_rlp).unwrap();
let mut db = StateProviderTest::default();
let address_caller = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b"));
let address_selfdestruct = H160(hex!("095e7baea6a6c7c4c2dfeb977efac326af552d87"));
// pre state
let pre_account_caller = Account {
balance: U256::from(0x0de0b6b3a7640000u64),
nonce: 0x00,
bytecode_hash: None,
};
db.insert_account(address_caller, pre_account_caller, None, HashMap::new());
// insert account that will selfd
let pre_account_selfdestroyed = Account {
balance: U256::ZERO,
nonce: 0x00,
bytecode_hash: Some(H256(hex!(
"56a7d44a4ecf086c34482ad1feb1007087fc56fae6dbefbd3f416002933f1705"
))),
};
let selfdestroyed_storage =
BTreeMap::from([(H256::zero(), U256::ZERO), (H256::from_low_u64_be(1), U256::from(1))]);
db.insert_account(
address_selfdestruct,
pre_account_selfdestroyed,
Some(hex!("73095e7baea6a6c7c4c2dfeb977efac326af552d8731ff00").into()),
selfdestroyed_storage.clone().into_iter().collect::<HashMap<_, _>>(),
);
// spec at berlin fork
let chain_spec = ChainSpecBuilder::mainnet().berlin_activated().build();
let mut db = SubState::new(State::new(db));
let transactions: Vec<TransactionSignedEcRecovered> =
block.body.iter().map(|tx| tx.try_ecrecovered().unwrap()).collect();
// execute chain and verify receipts
let out =
execute_and_verify_receipt(&block.header, &transactions, &[], &chain_spec, &mut db)
.unwrap();
assert_eq!(out.changesets.len(), 1, "Should executed one transaction");
let changesets = out.changesets[0].clone();
assert_eq!(changesets.new_bytecodes.len(), 0, "Should have zero new bytecodes");
let post_account_caller = Account {
balance: U256::from(0x0de0b6b3a761cf60u64),
nonce: 0x01,
bytecode_hash: None,
};
assert_eq!(
changesets.changeset.get(&address_caller).unwrap().account,
AccountInfoChangeSet::Changed { new: post_account_caller, old: pre_account_caller },
"Caller account has changed and fee is deduced"
);
let selfdestroyer_changeset = changesets.changeset.get(&address_selfdestruct).unwrap();
// check account
assert_eq!(
selfdestroyer_changeset.account,
AccountInfoChangeSet::Destroyed { old: pre_account_selfdestroyed },
"Selfdestroyed account"
);
assert_eq!(selfdestroyer_changeset.wipe_storage, true);
}
}

View File

@@ -3,7 +3,7 @@ use crate::{
Stage, StageError, StageId, UnwindInput, UnwindOutput,
};
use reth_db::{
cursor::DbCursorRO,
cursor::{DbCursorRO, DbCursorRW},
database::Database,
models::{BlockNumHash, StoredBlockBody, TransitionIdAddress},
tables,
@@ -209,7 +209,6 @@ impl<DB: Database> Stage<DB> for ExecutionStage {
for results in block_change_patches.into_iter() {
// insert state change set
for result in results.changesets.into_iter() {
// TODO insert to transitionId to tx_index
for (address, account_change_set) in result.changeset.into_iter() {
let AccountChangeSet { account, wipe_storage, storage } = account_change_set;
// apply account change to db. Updates AccountChangeSet and PlainAccountState
@@ -217,38 +216,60 @@ impl<DB: Database> Stage<DB> for ExecutionStage {
trace!(target: "sync::stages::execution", ?address, current_transition_id, ?account, wipe_storage, "Applying account changeset");
account.apply_to_db(&**tx, address, current_transition_id)?;
// wipe storage
if wipe_storage {
// TODO insert all changes to StorageChangeSet
tx.delete::<tables::PlainStorageState>(address, None)?;
}
// insert storage changeset
let storage_id = TransitionIdAddress((current_transition_id, address));
for (key, (old_value, new_value)) in storage {
let hkey = H256(key.to_be_bytes());
trace!(target: "sync::stages::execution", ?address, current_transition_id, ?hkey, ?old_value, ?new_value, "Applying storage changeset");
// cast key to H256 and trace the change
let storage = storage
.into_iter()
.map(|(key, (old_value,new_value))| {
let hkey = H256(key.to_be_bytes());
trace!(target: "sync::stages::execution", ?address, current_transition_id, ?hkey, ?old_value, ?new_value, "Applying storage changeset");
(hkey, old_value,new_value)
})
.collect::<Vec<_>>();
// insert into StorageChangeSet
tx.put::<tables::StorageChangeSet>(
storage_id.clone(),
StorageEntry { key: hkey, value: old_value },
)?;
tracing::debug!(
target = "sync::stages::execution",
"{address} setting storage:{key} ({old_value} -> {new_value})"
);
let mut cursor_storage_changeset =
tx.cursor_write::<tables::StorageChangeSet>()?;
// Always delete old value as duplicate table, put will not override it
tx.delete::<tables::PlainStorageState>(
address,
Some(StorageEntry { key: hkey, value: old_value }),
)?;
if new_value != U256::ZERO {
tx.put::<tables::PlainStorageState>(
address,
StorageEntry { key: hkey, value: new_value },
)?;
if wipe_storage {
// iterate over storage and save them before entry is deleted.
tx.cursor_read::<tables::PlainStorageState>()?
.walk(address)?
.take_while(|res| {
res.as_ref().map(|(k, _)| *k == address).unwrap_or_default()
})
.try_for_each(|entry| {
let (_, old_value) = entry?;
cursor_storage_changeset.append(storage_id.clone(), old_value)
})?;
// delete all entries
tx.delete::<tables::PlainStorageState>(address, None)?;
// insert storage changeset
for (key, _, new_value) in storage {
// old values are already cleared.
if new_value != U256::ZERO {
tx.put::<tables::PlainStorageState>(
address,
StorageEntry { key, value: new_value },
)?;
}
}
} else {
// insert storage changeset
for (key, old_value, new_value) in storage {
let old_entry = StorageEntry { key, value: old_value };
let new_entry = StorageEntry { key, value: new_value };
// insert into StorageChangeSet
cursor_storage_changeset
.append(storage_id.clone(), old_entry.clone())?;
// Always delete old value as duplicate table, put will not override it
tx.delete::<tables::PlainStorageState>(address, Some(old_entry))?;
if new_value != U256::ZERO {
tx.put::<tables::PlainStorageState>(address, new_entry)?;
}
}
}
}
@@ -372,10 +393,13 @@ impl<DB: Database> Stage<DB> for ExecutionStage {
mod tests {
use std::ops::{Deref, DerefMut};
use crate::test_utils::PREV_STAGE_ID;
use crate::test_utils::{TestTransaction, PREV_STAGE_ID};
use super::*;
use reth_db::mdbx::{test_utils::create_test_db, EnvKind, WriteMap};
use reth_db::{
mdbx::{test_utils::create_test_db, EnvKind, WriteMap},
models::AccountBeforeTx,
};
use reth_primitives::{
hex_literal::hex, keccak256, Account, ChainSpecBuilder, SealedBlock, H160, U256,
};
@@ -511,7 +535,6 @@ mod tests {
tx.commit().unwrap();
// execute
let mut execution_stage = ExecutionStage {
chain_spec: ChainSpecBuilder::mainnet().berlin_activated().build(),
..Default::default()
@@ -546,4 +569,150 @@ mod tests {
"Third account should be unwinded"
);
}
#[tokio::test]
async fn test_selfdestruct() {
let test_tx = TestTransaction::default();
let mut tx = test_tx.inner();
let input = ExecInput {
previous_stage: Some((PREV_STAGE_ID, 1)),
/// The progress of this stage the last time it was executed.
stage_progress: None,
};
let mut genesis_rlp = hex!("f901f8f901f3a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0c9ceb8372c88cb461724d8d3d87e8b933f6fc5f679d4841800e662f4428ffd0da056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000080830f4240808000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0").as_slice();
let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap();
let mut block_rlp = hex!("f9025ff901f7a0c86e8cc0310ae7c531c758678ddbfd16fc51c8cef8cec650b032de9869e8b94fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa050554882fbbda2c2fd93fdc466db9946ea262a67f7a76cc169e714f105ab583da00967f09ef1dfed20c0eacfaa94d5cd4002eda3242ac47eae68972d07b106d192a0e3c8b47fbfc94667ef4cceb17e5cc21e3b1eebd442cebb27f07562b33836290db90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001830f42408238108203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f862f860800a83061a8094095e7baea6a6c7c4c2dfeb977efac326af552d8780801ba072ed817487b84ba367d15d2f039b5fc5f087d0a8882fbdf73e8cb49357e1ce30a0403d800545b8fc544f92ce8124e2255f8c3c6af93f28243a120585d4c4c6a2a3c0").as_slice();
let block = SealedBlock::decode(&mut block_rlp).unwrap();
insert_canonical_block(tx.deref_mut(), &genesis, true).unwrap();
insert_canonical_block(tx.deref_mut(), &block, true).unwrap();
tx.commit().unwrap();
// variables
let caller_address = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b"));
let destroyed_address = H160(hex!("095e7baea6a6c7c4c2dfeb977efac326af552d87"));
let beneficiary_address = H160(hex!("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"));
let code = hex!("73095e7baea6a6c7c4c2dfeb977efac326af552d8731ff00");
let balance = U256::from(0x0de0b6b3a7640000u64);
let code_hash = keccak256(code);
// pre state
let db_tx = tx.deref_mut();
let caller_info = Account { nonce: 0, balance, bytecode_hash: None };
let destroyed_info =
Account { nonce: 0, balance: U256::ZERO, bytecode_hash: Some(code_hash) };
// set account
db_tx.put::<tables::PlainAccountState>(caller_address, caller_info).unwrap();
db_tx.put::<tables::PlainAccountState>(destroyed_address, destroyed_info).unwrap();
db_tx.put::<tables::Bytecodes>(code_hash, code.to_vec()).unwrap();
// set storage to check when account gets destroyed.
db_tx
.put::<tables::PlainStorageState>(
destroyed_address,
StorageEntry { key: H256::zero(), value: U256::ZERO },
)
.unwrap();
db_tx
.put::<tables::PlainStorageState>(
destroyed_address,
StorageEntry { key: H256::from_low_u64_be(1), value: U256::from(1u64) },
)
.unwrap();
tx.commit().unwrap();
// execute
let mut execution_stage = ExecutionStage {
chain_spec: ChainSpecBuilder::mainnet().berlin_activated().build(),
..Default::default()
};
let _ = execution_stage.execute(&mut tx, input).await.unwrap();
tx.commit().unwrap();
// assert unwind stage
assert_eq!(
tx.deref().get::<tables::PlainAccountState>(destroyed_address),
Ok(None),
"Account was destroyed"
);
assert_eq!(
tx.deref().get::<tables::PlainStorageState>(destroyed_address),
Ok(None),
"There is storage for destroyed account"
);
// drops tx so that it returns write privilege to test_tx
drop(tx);
let plain_accounts = test_tx.table::<tables::PlainAccountState>().unwrap();
let plain_storage = test_tx.table::<tables::PlainStorageState>().unwrap();
assert_eq!(
plain_accounts,
vec![
(H160::zero(), Account::default()),
(
beneficiary_address,
Account {
nonce: 0,
balance: U256::from(0x1bc16d674eca30a0u64),
bytecode_hash: None
}
),
(
caller_address,
Account {
nonce: 1,
balance: U256::from(0xde0b6b3a761cf60u64),
bytecode_hash: None
}
)
]
);
assert!(plain_storage.is_empty());
let account_changesets = test_tx.table::<tables::AccountChangeSet>().unwrap();
let storage_changesets = test_tx.table::<tables::StorageChangeSet>().unwrap();
assert_eq!(
account_changesets,
vec![
(
1,
AccountBeforeTx {
address: H160(hex!("0000000000000000000000000000000000000000")),
info: None
}
),
(1, AccountBeforeTx { address: destroyed_address, info: Some(destroyed_info) }),
(1, AccountBeforeTx { address: beneficiary_address, info: None }),
(1, AccountBeforeTx { address: caller_address, info: Some(caller_info) }),
(
2,
AccountBeforeTx {
address: beneficiary_address,
info: Some(Account {
nonce: 0,
balance: U256::from(0x230a0),
bytecode_hash: None
})
}
)
]
);
assert_eq!(
storage_changesets,
vec![
(
(1, destroyed_address).into(),
StorageEntry { key: H256::zero(), value: U256::ZERO }
),
(
(1, destroyed_address).into(),
StorageEntry { key: H256::from_low_u64_be(1), value: U256::from(1u64) }
)
]
);
}
}

View File

@@ -68,6 +68,16 @@ impl TestTransaction {
})
}
/// Return full table as BTreeMap
pub(crate) fn table<T: Table>(&self) -> Result<Vec<(T::Key, T::Value)>, DbError>
where
T::Key: Default + Ord,
{
self.query(|tx| {
tx.cursor_read::<T>()?.walk(T::Key::default())?.collect::<Result<Vec<_>, DbError>>()
})
}
/// Map a collection of values and store them in the database.
/// This function commits the transaction before exiting.
///

View File

@@ -135,26 +135,27 @@ pub fn insert_block<'a, TX: DbTxMut<'a> + DbTx<'a>>(
StoredBlockOmmers { ommers: block.ommers.iter().map(|h| h.as_ref().clone()).collect() },
)?;
let (mut current_tx_id, mut transition_id) = if let Some(embed) = parent_tx_num_transition_id {
embed
} else if block.number == 0 {
(0, 0)
} else {
let prev_block_num = block.number - 1;
let prev_block_hash = tx
.get::<tables::CanonicalHeaders>(prev_block_num)?
.ok_or(ProviderError::BlockNumber { block_number: prev_block_num })?;
let prev_body = tx
.get::<tables::BlockBodies>((prev_block_num, prev_block_hash).into())?
.ok_or(ProviderError::BlockBody {
block_number: prev_block_num,
block_hash: prev_block_hash,
})?;
let last_transition_id = tx
.get::<tables::BlockTransitionIndex>(prev_block_num)?
.ok_or(ProviderError::BlockTransition { block_number: prev_block_num })?;
(prev_body.start_tx_id + prev_body.tx_count, last_transition_id)
};
let (mut current_tx_id, mut transition_id) =
if let Some(parent_tx_num_transition_id) = parent_tx_num_transition_id {
parent_tx_num_transition_id
} else if block.number == 0 {
(0, 0)
} else {
let prev_block_num = block.number - 1;
let prev_block_hash = tx
.get::<tables::CanonicalHeaders>(prev_block_num)?
.ok_or(ProviderError::BlockNumber { block_number: prev_block_num })?;
let prev_body = tx
.get::<tables::BlockBodies>((prev_block_num, prev_block_hash).into())?
.ok_or(ProviderError::BlockBody {
block_number: prev_block_num,
block_hash: prev_block_hash,
})?;
let last_transition_id = tx
.get::<tables::BlockTransitionIndex>(prev_block_num)?
.ok_or(ProviderError::BlockTransition { block_number: prev_block_num })?;
(prev_body.start_tx_id + prev_body.tx_count, last_transition_id)
};
// insert body data
tx.put::<tables::BlockBodies>(