From c7289bd9e8b486fa2d6ffbc67084613712223784 Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 23 Jan 2023 14:04:45 +0100 Subject: [PATCH] feat: Selfdestruct storage changeset (#974) --- crates/executor/src/executor.rs | 82 +++++++++ crates/stages/src/stages/execution.rs | 235 ++++++++++++++++++++---- crates/stages/src/test_utils/test_db.rs | 10 + crates/storage/provider/src/block.rs | 41 +++-- 4 files changed, 315 insertions(+), 53 deletions(-) diff --git a/crates/executor/src/executor.rs b/crates/executor/src/executor.rs index 9886860d15..48fd8902ec 100644 --- a/crates/executor/src/executor.rs +++ b/crates/executor/src/executor.rs @@ -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::>(), + ); + + // spec at berlin fork + let chain_spec = ChainSpecBuilder::mainnet().berlin_activated().build(); + + let mut db = SubState::new(State::new(db)); + let transactions: Vec = + 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); + } } diff --git a/crates/stages/src/stages/execution.rs b/crates/stages/src/stages/execution.rs index ef4c47facf..9df2597666 100644 --- a/crates/stages/src/stages/execution.rs +++ b/crates/stages/src/stages/execution.rs @@ -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 Stage 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 Stage 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::(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::>(); - // insert into StorageChangeSet - tx.put::( - 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::()?; - // Always delete old value as duplicate table, put will not override it - tx.delete::( - address, - Some(StorageEntry { key: hkey, value: old_value }), - )?; - if new_value != U256::ZERO { - tx.put::( - address, - StorageEntry { key: hkey, value: new_value }, - )?; + if wipe_storage { + // iterate over storage and save them before entry is deleted. + tx.cursor_read::()? + .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::(address, None)?; + + // insert storage changeset + for (key, _, new_value) in storage { + // old values are already cleared. + if new_value != U256::ZERO { + tx.put::( + 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::(address, Some(old_entry))?; + if new_value != U256::ZERO { + tx.put::(address, new_entry)?; + } } } } @@ -372,10 +393,13 @@ impl Stage 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::(caller_address, caller_info).unwrap(); + db_tx.put::(destroyed_address, destroyed_info).unwrap(); + db_tx.put::(code_hash, code.to_vec()).unwrap(); + // set storage to check when account gets destroyed. + db_tx + .put::( + destroyed_address, + StorageEntry { key: H256::zero(), value: U256::ZERO }, + ) + .unwrap(); + db_tx + .put::( + 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::(destroyed_address), + Ok(None), + "Account was destroyed" + ); + + assert_eq!( + tx.deref().get::(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::().unwrap(); + let plain_storage = test_tx.table::().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::().unwrap(); + let storage_changesets = test_tx.table::().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) } + ) + ] + ); + } } diff --git a/crates/stages/src/test_utils/test_db.rs b/crates/stages/src/test_utils/test_db.rs index e6019fcbd6..06ef448f10 100644 --- a/crates/stages/src/test_utils/test_db.rs +++ b/crates/stages/src/test_utils/test_db.rs @@ -68,6 +68,16 @@ impl TestTransaction { }) } + /// Return full table as BTreeMap + pub(crate) fn table(&self) -> Result, DbError> + where + T::Key: Default + Ord, + { + self.query(|tx| { + tx.cursor_read::()?.walk(T::Key::default())?.collect::, DbError>>() + }) + } + /// Map a collection of values and store them in the database. /// This function commits the transaction before exiting. /// diff --git a/crates/storage/provider/src/block.rs b/crates/storage/provider/src/block.rs index e167c2df54..7029f9f75c 100644 --- a/crates/storage/provider/src/block.rs +++ b/crates/storage/provider/src/block.rs @@ -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::(prev_block_num)? - .ok_or(ProviderError::BlockNumber { block_number: prev_block_num })?; - let prev_body = tx - .get::((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::(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::(prev_block_num)? + .ok_or(ProviderError::BlockNumber { block_number: prev_block_num })?; + let prev_body = tx + .get::((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::(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::(