mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-29 00:58:11 -05:00
feat: Selfdestruct storage changeset (#974)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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>(
|
||||
|
||||
Reference in New Issue
Block a user