diff --git a/lib/ain-evm/src/backend.rs b/lib/ain-evm/src/backend.rs index e46b3bd6af..689964087e 100644 --- a/lib/ain-evm/src/backend.rs +++ b/lib/ain-evm/src/backend.rs @@ -1,4 +1,8 @@ -use std::{error::Error, sync::Arc}; +use std::{ + collections::{HashMap, HashSet}, + error::Error, + sync::Arc, +}; use anyhow::format_err; use ethereum::{Account, Log}; @@ -14,7 +18,7 @@ use crate::{ fee::calculate_gas_fee, storage::{traits::BlockStorage, Storage}, transaction::SignedTx, - trie::TrieDBStore, + trie::{TrieDBStore, GENESIS_STATE_ROOT}, Result, }; @@ -38,11 +42,86 @@ pub struct Vicinity { pub block_randomness: Option, } +#[derive(Debug, Clone)] +struct OverlayData { + account: Account, + code: Option>, + storage: HashMap, +} + +#[derive(Debug, Clone)] +struct Overlay { + state: HashMap, + changeset: Vec>, + deletes: HashSet, +} + +impl Overlay { + pub fn new() -> Self { + Self { + state: HashMap::new(), + changeset: Vec::new(), + deletes: HashSet::new(), + } + } + + pub fn apply( + &mut self, + address: H160, + account: Account, + code: Option>, + mut storage: HashMap, + reset_storage: bool, + ) { + if !reset_storage { + if let Some(existing_storage) = self.storage(&address) { + for (k, v) in existing_storage { + storage.entry(*k).or_insert_with(|| *v); + } + } + } + + let data = OverlayData { + account, + code, + storage, + }; + self.state.insert(address, data.clone()); + } + + fn mark_delete(&mut self, address: H160) { + self.deletes.insert(address); + } + + // Keeps track of the number of TXs in the changeset. + // Should be called after a TX has been fully processed. + fn inc(&mut self) { + self.changeset.push(self.state.clone()); + } + + pub fn storage(&self, address: &H160) -> Option<&HashMap> { + self.state.get(address).map(|d| &d.storage) + } + + pub fn storage_val(&self, address: &H160, index: &H256) -> Option { + self.storage(address).and_then(|d| d.get(index).cloned()) + } + + pub fn get_account(&self, address: &H160) -> Option { + self.state.get(address).map(|d| d.account.to_owned()) + } + + pub fn get_code(&self, address: &H160) -> Option> { + self.state.get(address).and_then(|d| d.code.clone()) + } +} + pub struct EVMBackend { state: MptOnce, trie_store: Arc, storage: Arc, pub vicinity: Vicinity, + overlay: Overlay, } impl EVMBackend { @@ -62,9 +141,14 @@ impl EVMBackend { trie_store, storage, vicinity, + overlay: Overlay::new(), }) } + pub fn increase_tx_count(&mut self) { + self.overlay.inc() + } + pub fn apply>( &mut self, address: H160, @@ -73,70 +157,107 @@ impl EVMBackend { storage: I, reset_storage: bool, ) -> Result { - let account = self.get_account(&address).unwrap_or(Account { + let mut account = self.get_account(&address).unwrap_or(Account { nonce: U256::zero(), balance: U256::zero(), storage_root: H256::zero(), code_hash: H256::zero(), }); - let mut storage_trie = if reset_storage || is_empty_account(&account) { + if reset_storage || is_empty_account(&account) { self.trie_store .trie_db .trie_create(address.as_bytes(), None, true) - .map_err(|e| BackendError::TrieCreationFailed(e.to_string()))? - } else { - self.trie_store - .trie_db - .trie_restore(address.as_bytes(), None, account.storage_root.into()) - .map_err(|e| BackendError::TrieRestoreFailed(e.to_string()))? - }; + .map_err(|e| BackendError::TrieCreationFailed(e.to_string()))?; + account.storage_root = GENESIS_STATE_ROOT; + } - storage.into_iter().for_each(|(k, v)| { - debug!("Apply::Modify storage, key: {:x} value: {:x}", k, v); - let _ = storage_trie.insert(k.as_bytes(), v.as_bytes()); - storage_trie.commit(); - }); + if let Some(code) = &code { + account.code_hash = Hasher::hash(code); + } - let code_hash = match code { - None => account.code_hash, - Some(code) => { - let code_hash = Hasher::hash(&code); - self.storage.put_code(code_hash, code)?; - code_hash - } - }; + if let Some(basic) = basic { + account.balance = basic.balance; + account.nonce = basic.nonce; + } - let new_account = match basic { - Some(basic) => Account { - nonce: basic.nonce, - balance: basic.balance, - code_hash, - storage_root: storage_trie.commit().into(), + self.overlay.apply( + address, + account.clone(), + code, + storage.into_iter().collect(), + reset_storage, + ); + Ok(account) + } + + pub fn clear_overlay(&mut self) { + self.overlay.state.clear() + } + + pub fn reset_from_tx(&mut self, index: usize) { + self.overlay.state = self + .overlay + .changeset + .get(index) + .cloned() + .unwrap_or_default(); + self.overlay.changeset.truncate(index + 1); + } + + fn apply_overlay(&mut self, is_miner: bool) -> Result<()> { + for ( + address, + OverlayData { + ref mut account, + code, + storage, }, - None => Account { - nonce: account.nonce, - balance: account.balance, - code_hash, - storage_root: storage_trie.commit().into(), - }, - }; + ) in self.overlay.state.drain() + { + if self.overlay.deletes.contains(&address) { + self.state + .remove(address.as_bytes()) + .expect("Error removing address in state"); + + if !is_miner { + self.trie_store.trie_db.trie_remove(address.as_bytes()); + } - self.state - .insert(address.as_bytes(), new_account.rlp_bytes().as_ref()) - .map_err(|e| BackendError::TrieError(format!("{e}")))?; - self.state.commit(); + continue; + } - Ok(new_account) - } + if !storage.is_empty() { + let mut storage_trie = self + .trie_store + .trie_db + .trie_restore(address.as_bytes(), None, account.storage_root.into()) + .map_err(|e| BackendError::TrieRestoreFailed(e.to_string()))?; + + storage.into_iter().for_each(|(k, v)| { + debug!( + "Apply::Modify storage {address:?}, key: {:x} value: {:x}", + k, v + ); + let _ = storage_trie.insert(k.as_bytes(), v.as_bytes()); + }); + account.storage_root = storage_trie.commit().into(); + } - pub fn commit(&mut self) -> H256 { - self.state.commit().into() + if let Some(code) = code { + self.storage.put_code(account.code_hash, code)?; + } + + self.state + .insert(address.as_bytes(), account.rlp_bytes().as_ref()) + .map_err(|e| BackendError::TrieError(format!("{e}")))?; + } + Ok(()) } - // Read-only state root. Does not commit changes to database - pub fn root(&self) -> H256 { - self.state.root().into() + pub fn commit(&mut self, is_miner: bool) -> Result { + self.apply_overlay(is_miner)?; + Ok(self.state.commit().into()) } pub fn update_vicinity_from_tx(&mut self, tx: &SignedTx) { @@ -172,7 +293,6 @@ impl EVMBackend { self.apply(sender, Some(new_basic), None, Vec::new(), false) .map_err(|e| BackendError::DeductPrepayGasFailed(e.to_string()))?; - self.commit(); Ok(()) } @@ -203,7 +323,6 @@ impl EVMBackend { self.apply(signed_tx.sender, Some(new_basic), None, Vec::new(), false) .map_err(|e| BackendError::RefundUnusedGasFailed(e.to_string()))?; - self.commit(); Ok(()) } @@ -211,10 +330,12 @@ impl EVMBackend { impl EVMBackend { pub fn get_account(&self, address: &H160) -> Option { - self.state - .get(address.as_bytes()) - .unwrap_or(None) - .and_then(|addr| Account::decode(&Rlp::new(addr.as_bytes_ref())).ok()) + self.overlay.get_account(address).or_else(|| { + self.state + .get(address.as_bytes()) + .unwrap_or(None) + .and_then(|addr| Account::decode(&Rlp::new(addr.as_bytes_ref())).ok()) + }) } pub fn get_nonce(&self, address: &H160) -> U256 { @@ -229,24 +350,8 @@ impl EVMBackend { .unwrap_or_default() } - pub fn get_contract_storage(&self, contract: H160, storage_index: &[u8]) -> Result { - let Some(account) = self.get_account(&contract) else { - return Ok(U256::zero()); - }; - - let state = self - .trie_store - .trie_db - .trie_restore(contract.as_ref(), None, account.storage_root.into()) - .map_err(|e| BackendError::TrieRestoreFailed(e.to_string()))?; - - Ok(U256::from( - state - .get(storage_index) - .unwrap_or_default() - .unwrap_or_default() - .as_slice(), - )) + pub fn get_contract_storage(&self, contract: H160, index: H256) -> Result { + Ok(U256::from(self.storage(contract, index).as_bytes())) } pub fn deploy_contract( @@ -321,7 +426,7 @@ impl Backend for EVMBackend { } fn exists(&self, address: H160) -> bool { - self.state.contains(address.as_bytes()).unwrap_or(false) + self.get_account(&address).is_some() } fn basic(&self, address: H160) -> Basic { @@ -336,23 +441,35 @@ impl Backend for EVMBackend { fn code(&self, address: H160) -> Vec { trace!(target: "backend", "[EVMBackend] code for address {:x?}", address); - self.get_account(&address) - .and_then(|account| self.storage.get_code_by_hash(account.code_hash).unwrap()) - .unwrap_or_default() + self.overlay.get_code(&address).unwrap_or_else(|| { + self.get_account(&address) + .and_then(|account| { + self.storage + .get_code_by_hash(account.code_hash) + .ok() + .flatten() + }) + .unwrap_or_default() + }) } fn storage(&self, address: H160, index: H256) -> H256 { trace!(target: "backend", "[EVMBackend] Getting storage for address {:x?} at index {:x?}", address, index); - self.get_account(&address) - .and_then(|account| { + let Some(account) = self.get_account(&address) else { + return H256::zero(); + }; + + self.overlay + .storage_val(&address, &index) + .unwrap_or_else(|| { self.trie_store .trie_db - .trie_restore(address.as_bytes(), None, account.storage_root.into()) + .trie_restore(address.as_ref(), None, account.storage_root.into()) .ok() + .and_then(|trie| trie.get(index.as_bytes()).ok().flatten()) + .map(|res| H256::from_slice(res.as_ref())) + .unwrap_or_default() }) - .and_then(|trie| trie.get(index.as_bytes()).ok().flatten()) - .map(|res| H256::from_slice(res.as_ref())) - .unwrap_or_default() } fn original_storage(&self, address: H160, index: H256) -> Option { @@ -388,18 +505,14 @@ impl ApplyBackend for EVMBackend { if is_empty_account(&new_account) && delete_empty { debug!("Deleting empty address {:x?}", address); - self.trie_store.trie_db.trie_remove(address.as_bytes()); - self.state - .remove(address.as_bytes()) - .expect("Error removing address in state"); + self.overlay.mark_delete(address); } } Apply::Delete { address } => { debug!("Deleting address {:x?}", address); - self.trie_store.trie_db.trie_remove(address.as_bytes()); - self.state - .remove(address.as_bytes()) - .expect("Error removing address in state"); + self.apply(address, None, None, vec![], false) + .expect("Error applying state"); + self.overlay.mark_delete(address); } } } diff --git a/lib/ain-evm/src/blocktemplate.rs b/lib/ain-evm/src/blocktemplate.rs index 83074d4cc3..139583e61c 100644 --- a/lib/ain-evm/src/blocktemplate.rs +++ b/lib/ain-evm/src/blocktemplate.rs @@ -2,7 +2,11 @@ use ethereum::{Block, ReceiptV3, TransactionV2}; use ethereum_types::{Bloom, H160, H256, U256}; use crate::{ - backend::Vicinity, core::XHash, evm::ExecTxState, receipt::Receipt, transaction::SignedTx, + backend::{EVMBackend, Vicinity}, + core::XHash, + evm::ExecTxState, + receipt::Receipt, + transaction::SignedTx, }; type Result = std::result::Result; @@ -13,9 +17,8 @@ pub type ReceiptAndOptionalContractAddress = (ReceiptV3, Option); pub struct TemplateTxItem { pub tx: Box, pub tx_hash: XHash, - pub gas_used: U256, pub gas_fees: U256, - pub state_root: H256, + pub gas_used: U256, pub logs_bloom: Bloom, pub receipt_v3: ReceiptAndOptionalContractAddress, } @@ -24,15 +27,13 @@ impl TemplateTxItem { pub fn new_system_tx( tx: Box, receipt_v3: ReceiptAndOptionalContractAddress, - state_root: H256, logs_bloom: Bloom, ) -> Self { TemplateTxItem { tx, tx_hash: Default::default(), - gas_used: U256::zero(), gas_fees: U256::zero(), - state_root, + gas_used: U256::zero(), logs_bloom, receipt_v3, } @@ -52,11 +53,10 @@ pub struct BlockData { /// 4. Backend vicinity /// 5. Block template timestamp /// 6. DVM block number -/// 7. Initial state root +/// 7. EVM backend /// /// The template is used to construct a valid EVM block. /// -#[derive(Clone, Debug, Default)] pub struct BlockTemplate { pub transactions: Vec, pub block_data: Option, @@ -65,7 +65,7 @@ pub struct BlockTemplate { pub parent_hash: H256, pub dvm_block: u64, pub timestamp: u64, - pub initial_state_root: H256, + pub backend: EVMBackend, } impl BlockTemplate { @@ -74,7 +74,7 @@ impl BlockTemplate { parent_hash: H256, dvm_block: u64, timestamp: u64, - initial_state_root: H256, + backend: EVMBackend, ) -> Self { Self { transactions: Vec::new(), @@ -84,7 +84,7 @@ impl BlockTemplate { parent_hash, dvm_block, timestamp, - initial_state_root, + backend, } } @@ -97,9 +97,8 @@ impl BlockTemplate { self.transactions.push(TemplateTxItem { tx: tx_update.tx, tx_hash, - gas_used: tx_update.gas_used, gas_fees: tx_update.gas_fees, - state_root: tx_update.state_root, + gas_used: tx_update.gas_used, logs_bloom: tx_update.logs_bloom, receipt_v3: tx_update.receipt, }); @@ -123,25 +122,13 @@ impl BlockTemplate { self.total_gas_used = self.transactions.iter().try_fold(U256::zero(), |acc, tx| { acc.checked_add(tx.gas_used) .ok_or(BlockTemplateError::ValueOverflow) - })? + })?; + self.backend.reset_from_tx(index); } Ok(removed_txs) } - pub fn get_state_root_from_native_hash(&self, hash: XHash) -> Option { - self.transactions - .iter() - .find(|tx_item| tx_item.tx_hash == hash) - .map(|tx_item| tx_item.state_root) - } - - pub fn get_latest_state_root(&self) -> H256 { - self.transactions - .last() - .map_or(self.initial_state_root, |tx_item| tx_item.state_root) - } - pub fn get_latest_logs_bloom(&self) -> Bloom { self.transactions .last() diff --git a/lib/ain-evm/src/contract.rs b/lib/ain-evm/src/contract.rs index 7ae95c32a7..ec53a178a4 100644 --- a/lib/ain-evm/src/contract.rs +++ b/lib/ain-evm/src/contract.rs @@ -230,7 +230,7 @@ pub fn bridge_dst20_in( let contract_balance_storage_index = get_address_storage_index(H256::zero(), fixed_address); let total_supply_index = H256::from_low_u64_be(2); - let total_supply = backend.get_contract_storage(contract, total_supply_index.as_bytes())?; + let total_supply = backend.get_contract_storage(contract, total_supply_index)?; let new_total_supply = total_supply .checked_add(amount) @@ -265,7 +265,7 @@ pub fn bridge_dst20_out( let contract_balance_storage_index = get_address_storage_index(H256::zero(), fixed_address); let total_supply_index = H256::from_low_u64_be(2); - let total_supply = backend.get_contract_storage(contract, total_supply_index.as_bytes())?; + let total_supply = backend.get_contract_storage(contract, total_supply_index)?; let new_total_supply = total_supply .checked_sub(amount) @@ -310,8 +310,7 @@ pub fn bridge_dfi( let FixedContract { fixed_address, .. } = get_transfer_domain_contract(); let total_supply_index = H256::from_low_u64_be(1); - let total_supply = - backend.get_contract_storage(fixed_address, total_supply_index.as_bytes())?; + let total_supply = backend.get_contract_storage(fixed_address, total_supply_index)?; let new_total_supply = if direction == TransferDirection::EvmOut { total_supply.checked_sub(amount) diff --git a/lib/ain-evm/src/core.rs b/lib/ain-evm/src/core.rs index 56f7a90c90..3b8601f4a3 100644 --- a/lib/ain-evm/src/core.rs +++ b/lib/ain-evm/src/core.rs @@ -81,7 +81,6 @@ impl SignedTxCache { } struct TxValidationCache { - validated: spin::Mutex>, stateless: spin::Mutex>, } @@ -94,37 +93,19 @@ impl Default for TxValidationCache { impl TxValidationCache { pub fn new(capacity: usize) -> Self { Self { - validated: spin::Mutex::new(LruCache::new(NonZeroUsize::new(capacity).unwrap())), stateless: spin::Mutex::new(LruCache::new(NonZeroUsize::new(capacity).unwrap())), } } - pub fn get(&self, key: &(H256, String)) -> Option { - self.validated.lock().get(key).cloned() - } - pub fn get_stateless(&self, key: &str) -> Option { self.stateless.lock().get(key).cloned() } - pub fn set(&self, key: (H256, String), value: ValidateTxInfo) -> ValidateTxInfo { - let mut cache = self.validated.lock(); - cache.put(key, value.clone()); - value - } - pub fn set_stateless(&self, key: String, value: ValidateTxInfo) -> ValidateTxInfo { let mut cache = self.stateless.lock(); cache.put(key, value.clone()); value } - - // To be used on new block or any known state changes. Only clears fully validated TX cache. - // Stateless cache can be kept across blocks and is handled by LRU itself - pub fn clear(&self) { - let mut cache = self.validated.lock(); - cache.clear() - } } pub struct EVMCoreService { @@ -341,16 +322,6 @@ impl EVMCoreService { tx: &str, template: &BlockTemplate, ) -> Result { - let state_root = template.get_latest_state_root(); - debug!("[validate_raw_tx] state_root : {:#?}", state_root); - - if let Some(tx_info) = self - .tx_validation_cache - .get(&(state_root, String::from(tx))) - { - return Ok(tx_info); - } - debug!("[validate_raw_tx] raw transaction : {:#?}", tx); let ValidateTxInfo { @@ -410,7 +381,7 @@ impl EVMCoreService { // Start of stateful checks // Validate tx prepay fees with account balance - let backend = self.get_backend(state_root)?; + let backend = &template.backend; let balance = backend.get_balance(&signed_tx.sender); debug!("[validate_raw_tx] Account balance : {:x?}", balance); if balance < max_prepay_fee { @@ -434,13 +405,10 @@ impl EVMCoreService { .into()); } - Ok(self.tx_validation_cache.set( - (state_root, String::from(tx)), - ValidateTxInfo { - signed_tx, - max_prepay_fee, - }, - )) + Ok(ValidateTxInfo { + signed_tx, + max_prepay_fee, + }) } /// Validates a raw transfer domain tx. @@ -495,12 +463,6 @@ impl EVMCoreService { tx ); - let state_root = template.get_latest_state_root(); - debug!( - "[validate_raw_transferdomain_tx] state_root : {:#?}", - state_root - ); - let ValidateTxInfo { signed_tx, max_prepay_fee, @@ -695,7 +657,7 @@ impl EVMCoreService { } }; - let backend = self.get_backend(state_root)?; + let backend = &template.backend; let nonce = backend.get_nonce(&signed_tx.sender); debug!( @@ -771,9 +733,7 @@ impl EVMCoreService { template: &BlockTemplate, address: H160, ) -> Result { - let state_root = template.get_latest_state_root(); - let backend = self.get_backend(state_root)?; - let nonce = backend.get_nonce(&address); + let nonce = template.backend.get_nonce(&address); trace!("[get_next_valid_nonce_in_block_template] Account {address:x?} nonce {nonce:x?}"); Ok(nonce) } @@ -807,18 +767,6 @@ impl EVMCoreService { Ok(backend.get_account(&address)) } - pub fn get_latest_contract_storage(&self, contract: H160, storage_index: H256) -> Result { - let state_root = self.get_state_root()?; - let backend = EVMBackend::from_root( - state_root, - Arc::clone(&self.trie_store), - Arc::clone(&self.storage), - Vicinity::default(), - )?; - - backend.get_contract_storage(contract, storage_index.as_bytes()) - } - pub fn get_code(&self, address: H160, block_number: U256) -> Result>> { self.get_account(address, block_number)? .map_or(Ok(None), |account| { @@ -884,6 +832,7 @@ impl EVMCoreService { block_number, state_root ); + EVMBackend::from_root( state_root, Arc::clone(&self.trie_store), @@ -962,8 +911,4 @@ impl EVMCoreService { let mut nonce_store = self.nonce_store.lock(); nonce_store.clear() } - - pub fn clear_transaction_cache(&self) { - self.tx_validation_cache.clear() - } } diff --git a/lib/ain-evm/src/evm.rs b/lib/ain-evm/src/evm.rs index 1105bd789f..dad4822c6e 100644 --- a/lib/ain-evm/src/evm.rs +++ b/lib/ain-evm/src/evm.rs @@ -52,7 +52,6 @@ pub struct EVMServices { pub struct ExecTxState { pub tx: Box, pub receipt: ReceiptAndOptionalContractAddress, - pub state_root: H256, pub logs_bloom: Bloom, pub gas_used: U256, pub gas_fees: U256, @@ -139,8 +138,8 @@ impl EVMServices { pub unsafe fn construct_block_in_template( &self, template: &mut BlockTemplate, + is_miner: bool, ) -> Result { - let state_root = template.get_latest_state_root(); let logs_bloom = template.get_latest_logs_bloom(); let timestamp = template.timestamp; @@ -158,14 +157,7 @@ impl EVMServices { debug!("[construct_block] vicinity: {:?}", template.vicinity); - let mut backend = EVMBackend::from_root( - state_root, - Arc::clone(&self.core.trie_store), - Arc::clone(&self.storage), - template.vicinity.clone(), - )?; - - let mut executor = AinExecutor::new(&mut backend); + let mut executor = AinExecutor::new(&mut template.backend); for template_tx in template.transactions.clone() { all_transactions.push(template_tx.tx); receipts_v3.push(template_tx.receipt_v3); @@ -194,14 +186,15 @@ impl EVMServices { executor .backend .add_balance(beneficiary, total_priority_fees)?; - executor.commit(); + + let state_root = executor.commit(is_miner)?; let extra_data = format!("DFI: {}", template.dvm_block).into_bytes(); let block = Block::new( PartialHeader { parent_hash, beneficiary, - state_root: backend.commit(), + state_root, receipts_root: ReceiptService::get_receipts_root(&receipts_v3), logs_bloom, difficulty, @@ -266,51 +259,29 @@ impl EVMServices { .send(Notification::Block(block.header.hash())) .map_err(|e| format_err!(e.to_string()))?; } - // self.core.block_templates.remove(template); self.core.clear_account_nonce(); - self.core.clear_transaction_cache(); Ok(()) } unsafe fn update_block_template_state_from_tx( &self, - template: &BlockTemplate, + template: &mut BlockTemplate, tx: ExecuteTx, ) -> Result { - let state_root = template.get_latest_state_root(); + let base_fee = template.get_block_base_fee_per_gas(); let mut logs_bloom = template.get_latest_logs_bloom(); - debug!( - "[update_block_template_state_from_tx] state_root : {:#?}", - state_root - ); - let mut backend = EVMBackend::from_root( - state_root, - Arc::clone(&self.core.trie_store), - Arc::clone(&self.storage), - template.vicinity.clone(), - )?; - let mut executor = AinExecutor::new(&mut backend); - - let (parent_hash, _) = self - .block - .get_latest_block_hash_and_number()? - .unwrap_or_default(); // Safe since calculate_base_fee will default to INITIAL_BASE_FEE - let base_fee = self.block.calculate_base_fee(parent_hash)?; - debug!( - "[update_block_template_state_from_tx] Block base fee: {}", - base_fee - ); + let mut executor = AinExecutor::new(&mut template.backend); executor.update_total_gas_used(template.total_gas_used); let apply_tx = executor.execute_tx(tx, base_fee)?; EVMCoreService::logs_bloom(apply_tx.logs, &mut logs_bloom); + template.backend.increase_tx_count(); Ok(ExecTxState { tx: apply_tx.tx, receipt: apply_tx.receipt, - state_root: backend.commit(), logs_bloom, gas_used: apply_tx.used_gas, gas_fees: apply_tx.gas_fee, @@ -330,19 +301,12 @@ impl EVMServices { ) -> Result<()> { // reserve DST20 namespace; let is_evm_genesis_block = template.get_block_number() == U256::zero(); - let state_root = template.get_latest_state_root(); let mut logs_bloom = template.get_latest_logs_bloom(); - let mut backend = EVMBackend::from_root( - state_root, - Arc::clone(&self.core.trie_store), - Arc::clone(&self.storage), - template.vicinity.clone(), - )?; - let mut executor = AinExecutor::new(&mut backend); + let mut executor = AinExecutor::new(&mut template.backend); let base_fee = template.vicinity.block_base_fee_per_gas; debug!( - "[update_block_template_state_from_tx] Block base fee: {}", + "[update_state_in_block_template] Block base fee: {}", base_fee ); @@ -359,7 +323,6 @@ impl EVMServices { trace!("deploying {:x?} bytecode {:?}", address, bytecode); executor.deploy_contract(address, bytecode, storage)?; - executor.commit(); // DFIIntrinsicsRegistry contract deployment TX let (tx, receipt) = deploy_contract_tx( @@ -371,7 +334,6 @@ impl EVMServices { template.transactions.push(TemplateTxItem::new_system_tx( Box::new(tx), (receipt, Some(address)), - executor.commit(), logs_bloom, )); @@ -384,7 +346,6 @@ impl EVMServices { trace!("deploying {:x?} bytecode {:?}", address, bytecode); executor.deploy_contract(address, bytecode, storage)?; - executor.commit(); // DFIIntrinsics contract deployment TX let (tx, receipt) = deploy_contract_tx( @@ -394,7 +355,6 @@ impl EVMServices { template.transactions.push(TemplateTxItem::new_system_tx( Box::new(tx), (receipt, Some(address)), - executor.commit(), logs_bloom, )); @@ -407,7 +367,6 @@ impl EVMServices { trace!("deploying {:x?} bytecode {:?}", address, bytecode); executor.deploy_contract(address, bytecode, storage)?; - executor.commit(); // Transferdomain_v1 contract deployment TX let (tx, receipt) = deploy_contract_tx( @@ -417,7 +376,6 @@ impl EVMServices { template.transactions.push(TemplateTxItem::new_system_tx( Box::new(tx), (receipt, Some(address)), - executor.commit(), logs_bloom, )); @@ -430,7 +388,6 @@ impl EVMServices { trace!("deploying {:x?} bytecode {:?}", address, bytecode); executor.deploy_contract(address, bytecode, storage)?; - executor.commit(); // Transferdomain contract deployment TX let (tx, receipt) = deploy_contract_tx( @@ -440,7 +397,6 @@ impl EVMServices { template.transactions.push(TemplateTxItem::new_system_tx( Box::new(tx), (receipt, Some(address)), - executor.commit(), logs_bloom, )); @@ -453,7 +409,6 @@ impl EVMServices { trace!("deploying {:x?} bytecode {:?}", address, bytecode); executor.deploy_contract(address, bytecode, storage)?; - executor.commit(); // DST20 implementation contract deployment TX let (tx, receipt) = @@ -461,7 +416,6 @@ impl EVMServices { template.transactions.push(TemplateTxItem::new_system_tx( Box::new(tx), (receipt, Some(address)), - executor.commit(), logs_bloom, )); @@ -473,7 +427,6 @@ impl EVMServices { template.transactions.push(TemplateTxItem::new_system_tx( apply_result.tx, apply_result.receipt, - executor.commit(), logs_bloom, )); } @@ -483,9 +436,8 @@ impl EVMServices { } = dfi_intrinsics_v1_deploy_info(template.dvm_block, template.vicinity.block_number)?; executor.update_storage(address, storage)?; - executor.commit(); - template.initial_state_root = backend.commit(); } + template.backend.increase_tx_count(); Ok(()) } } @@ -525,34 +477,30 @@ impl EVMServices { let block_base_fee_per_gas = self.block.calculate_base_fee(parent_hash)?; let block_gas_limit = U256::from(self.storage.get_attributes_or_default()?.block_gas_limit); - let template = BlockTemplate::new( - Vicinity { - beneficiary, - block_number: target_block, - timestamp: U256::from(timestamp), - total_gas_used: U256::zero(), - block_difficulty, - block_gas_limit, - block_base_fee_per_gas, - block_randomness: None, - ..Vicinity::default() - }, - parent_hash, - dvm_block, - timestamp, + let vicinity = Vicinity { + beneficiary, + block_number: target_block, + timestamp: U256::from(timestamp), + block_difficulty, + block_gas_limit, + block_base_fee_per_gas, + block_randomness: None, + ..Vicinity::default() + }; + + let backend = EVMBackend::from_root( initial_state_root, - ); + Arc::clone(&self.core.trie_store), + Arc::clone(&self.storage), + vicinity.clone(), + )?; + + let template = BlockTemplate::new(vicinity, parent_hash, dvm_block, timestamp, backend); Ok(template) } - unsafe fn verify_tx_fees_in_block_template( - &self, - template: &BlockTemplate, - tx: &ExecuteTx, - ) -> Result<()> { + unsafe fn verify_tx_fees(&self, base_fee_per_gas: U256, tx: &ExecuteTx) -> Result<()> { if let ExecuteTx::SignedTx(signed_tx) = tx { - let base_fee_per_gas = template.get_block_base_fee_per_gas(); - let tx_gas_price = signed_tx.gas_price(); if tx_gas_price < base_fee_per_gas { return Err(format_err!( @@ -576,14 +524,10 @@ impl EVMServices { tx: ExecuteTx, hash: XHash, ) -> Result<()> { - self.verify_tx_fees_in_block_template(template, &tx)?; + self.verify_tx_fees(template.get_block_base_fee_per_gas(), &tx)?; let tx_update = self.update_block_template_state_from_tx(template, tx.clone())?; let tx_hash = tx_update.tx.hash(); - debug!( - "[push_tx_in_block_template] Pushing new state_root {:x?}", - tx_update.state_root - ); template.add_tx(tx_update, hash)?; self.filters.add_tx_to_filters(tx_hash); @@ -601,7 +545,7 @@ impl EVMServices { address: H160, template: &BlockTemplate, ) -> Result { - let backend = self.core.get_backend(template.get_latest_state_root())?; + let backend = &template.backend; Ok(match backend.get_account(&address) { None => false, diff --git a/lib/ain-evm/src/executor.rs b/lib/ain-evm/src/executor.rs index 98ededef99..dcf4c25172 100644 --- a/lib/ain-evm/src/executor.rs +++ b/lib/ain-evm/src/executor.rs @@ -10,7 +10,7 @@ use evm::{ use log::{debug, trace}; use crate::{ - backend::EVMBackend, + backend::{BackendError, EVMBackend}, blocktemplate::ReceiptAndOptionalContractAddress, bytes::Bytes, contract::{ @@ -83,8 +83,8 @@ impl<'backend> AinExecutor<'backend> { self.backend.update_vicinity_with_gas_used(gas_used) } - pub fn commit(&mut self) -> H256 { - self.backend.commit() + pub fn commit(&mut self, is_miner: bool) -> Result { + self.backend.commit(is_miner) } pub fn get_nonce(&self, address: &H160) -> U256 { @@ -166,6 +166,9 @@ impl<'backend> AinExecutor<'backend> { } else { calculate_current_prepay_gas_fee(signed_tx, base_fee)? }; + + let old_basic = self.backend.basic(signed_tx.sender); // Keep old basic values to restore state if block size limit exceeded + if !system_tx { self.backend .deduct_prepay_gas_fee(signed_tx.sender, prepay_fee)?; @@ -208,6 +211,9 @@ impl<'backend> AinExecutor<'backend> { .ok_or_else(|| format_err!("total_gas_used overflow"))? > block_gas_limit { + self.backend + .apply(signed_tx.sender, Some(old_basic), None, Vec::new(), false) + .map_err(|e| BackendError::DeductPrepayGasFailed(e.to_string()))?; return Err(format_err!( "[exec] block size limit exceeded, tx cannot make it into the block" ) @@ -217,7 +223,6 @@ impl<'backend> AinExecutor<'backend> { let logs = logs.into_iter().collect::>(); ApplyBackend::apply(self.backend, values, logs.clone(), true); - self.backend.commit(); if !system_tx { self.backend @@ -330,7 +335,6 @@ impl<'backend> AinExecutor<'backend> { let storage = bridge_dfi(self.backend, amount, direction)?; self.update_storage(fixed_address, storage)?; self.add_balance(fixed_address, amount)?; - self.commit(); } let (tx_response, receipt) = @@ -342,7 +346,6 @@ impl<'backend> AinExecutor<'backend> { ) .into()); } - self.commit(); debug!( "[execute_tx] receipt : {:?}, exit_reason {:#?} for signed_tx : {:#x}, logs: {:x?}", @@ -394,12 +397,10 @@ impl<'backend> AinExecutor<'backend> { let DST20BridgeInfo { address, storage } = bridge_dst20_in(self.backend, contract_address, amount)?; self.update_storage(address, storage)?; - self.commit(); } let allowance = dst20_allowance(direction, signed_tx.sender, amount); self.update_storage(contract_address, allowance)?; - self.commit(); let (tx_response, receipt) = self.exec(&signed_tx, U256::MAX, U256::zero(), true)?; @@ -417,8 +418,6 @@ impl<'backend> AinExecutor<'backend> { .into()); } - self.commit(); - debug!( "[execute_tx] receipt : {:?}, exit_reason {:#?} for signed_tx : {:#x}, logs: {:x?}", receipt, diff --git a/lib/ain-evm/src/trie.rs b/lib/ain-evm/src/trie.rs index 5be5d7cdff..7b828ac80a 100644 --- a/lib/ain-evm/src/trie.rs +++ b/lib/ain-evm/src/trie.rs @@ -91,11 +91,10 @@ impl TrieDBStore { false, ) .expect("Could not set account data"); - backend.commit(); } } - let state_root = backend.commit(); + let state_root = backend.commit(false)?; debug!("Loaded genesis state_root : {:#x}", state_root); Ok((state_root, genesis)) } diff --git a/lib/ain-grpc/src/rpc/eth.rs b/lib/ain-grpc/src/rpc/eth.rs index 38ba37d860..53ee8c9412 100644 --- a/lib/ain-grpc/src/rpc/eth.rs +++ b/lib/ain-grpc/src/rpc/eth.rs @@ -405,6 +405,7 @@ impl MetachainRPCServer for MetachainRPCModule { .get_code(address, block_number) .map_err(to_jsonrpsee_custom_error)?; + debug!("code : {:?} for address {address:?}", code); match code { Some(code) => Ok(format!("0x{}", hex::encode(code))), None => Ok(String::from("0x")), diff --git a/lib/ain-rs-exports/src/evm.rs b/lib/ain-rs-exports/src/evm.rs index f32bf922ea..236be834d5 100644 --- a/lib/ain-rs-exports/src/evm.rs +++ b/lib/ain-rs-exports/src/evm.rs @@ -3,7 +3,6 @@ use ain_contracts::{ get_transferdomain_native_transfer_function, FixedContract, }; use ain_evm::{ - blocktemplate::BlockTemplate, core::{TransferDomainTxInfo, XHash}, evm::FinalizedBlockInfo, executor::ExecuteTx, @@ -227,7 +226,7 @@ fn evm_try_unsafe_update_state_in_template( unsafe { SERVICES .evm - .update_state_in_block_template(&mut template.0, mnview_ptr) + .update_state_in_block_template(template.get_inner_mut()?, mnview_ptr) } } @@ -243,7 +242,7 @@ fn evm_try_unsafe_update_state_in_template( /// Returns the next valid nonce of the account in a specific template #[ffi_fallible] fn evm_try_unsafe_get_next_valid_nonce_in_template( - template: &mut BlockTemplateWrapper, + template: &BlockTemplateWrapper, address: &str, ) -> Result { let address = address.parse::().map_err(|_| "Invalid address")?; @@ -252,7 +251,7 @@ fn evm_try_unsafe_get_next_valid_nonce_in_template( let next_nonce = SERVICES .evm .core - .get_next_valid_nonce_in_block_template(&template.0, address)?; + .get_next_valid_nonce_in_block_template(template.get_inner()?, address)?; let nonce = u64::try_from(next_nonce)?; Ok(nonce) @@ -274,7 +273,7 @@ fn evm_try_unsafe_remove_txs_above_hash_in_template( SERVICES .evm .core - .remove_txs_above_hash_in_block_template(&mut template.0, target_hash) + .remove_txs_above_hash_in_block_template(template.get_inner_mut()?, target_hash) } } @@ -305,7 +304,7 @@ fn evm_try_unsafe_add_balance_in_template( unsafe { SERVICES .evm - .push_tx_in_block_template(&mut template.0, exec_tx, native_hash) + .push_tx_in_block_template(template.get_inner_mut()?, exec_tx, native_hash) } } @@ -337,7 +336,7 @@ fn evm_try_unsafe_sub_balance_in_template( unsafe { SERVICES .evm - .push_tx_in_block_template(&mut template.0, exec_tx, native_hash)?; + .push_tx_in_block_template(template.get_inner_mut()?, exec_tx, native_hash)?; Ok(true) } } @@ -367,12 +366,15 @@ fn evm_try_unsafe_sub_balance_in_template( /// Returns the validation result. #[ffi_fallible] fn evm_try_unsafe_validate_raw_tx_in_template( - template: &mut BlockTemplateWrapper, + template: &BlockTemplateWrapper, raw_tx: &str, ) -> Result<()> { debug!("[unsafe_validate_raw_tx_in_template]"); unsafe { - let _ = SERVICES.evm.core.validate_raw_tx(raw_tx, &template.0)?; + let _ = SERVICES + .evm + .core + .validate_raw_tx(raw_tx, template.get_inner()?)?; Ok(()) } } @@ -401,7 +403,7 @@ fn evm_try_unsafe_validate_raw_tx_in_template( /// Returns the validation result. #[ffi_fallible] fn evm_try_unsafe_validate_transferdomain_tx_in_template( - template: &mut BlockTemplateWrapper, + template: &BlockTemplateWrapper, raw_tx: &str, context: ffi::TransferDomainInfo, ) -> Result<()> { @@ -409,7 +411,7 @@ fn evm_try_unsafe_validate_transferdomain_tx_in_template( unsafe { let _ = SERVICES.evm.core.validate_raw_transferdomain_tx( raw_tx, - &template.0, + template.get_inner()?, TransferDomainTxInfo { from: context.from, to: context.to, @@ -429,7 +431,7 @@ fn block_template_err_wrapper() -> &'static mut BlockTemplateWrapper { // to never be used static mut CELL: std::cell::OnceCell = std::cell::OnceCell::new(); unsafe { - let v = CELL.get_or_init(|| BlockTemplateWrapper(BlockTemplate::default())); + let v = CELL.get_or_init(|| BlockTemplateWrapper(None)); #[allow(mutable_transmutes)] std::mem::transmute(v) } @@ -466,7 +468,7 @@ pub fn evm_try_unsafe_create_template( { Ok(template) => cross_boundary_success_return( result, - Box::leak(Box::new(BlockTemplateWrapper(template))), + Box::leak(Box::new(BlockTemplateWrapper(Some(template)))), ), Err(e) => { cross_boundary_error(result, e.to_string()); @@ -521,9 +523,11 @@ fn evm_try_unsafe_push_tx_in_template( .try_get_or_create(raw_tx)?; let tx_hash = signed_tx.hash(); - SERVICES - .evm - .push_tx_in_block_template(&mut template.0, signed_tx.into(), native_hash)?; + SERVICES.evm.push_tx_in_block_template( + template.get_inner_mut()?, + signed_tx.into(), + native_hash, + )?; Ok(ffi::ValidateTxCompletion { tx_hash: format!("{:?}", tx_hash), @@ -546,6 +550,7 @@ fn evm_try_unsafe_push_tx_in_template( #[ffi_fallible] fn evm_try_unsafe_construct_block_in_template( template: &mut BlockTemplateWrapper, + is_miner: bool, ) -> Result { unsafe { let FinalizedBlockInfo { @@ -553,7 +558,9 @@ fn evm_try_unsafe_construct_block_in_template( total_burnt_fees, total_priority_fees, block_number, - } = SERVICES.evm.construct_block_in_template(&mut template.0)?; + } = SERVICES + .evm + .construct_block_in_template(template.get_inner_mut()?, is_miner)?; let total_burnt_fees = u64::try_from(WeiAmount(total_burnt_fees).to_satoshi()?)?; let total_priority_fees = u64::try_from(WeiAmount(total_priority_fees).to_satoshi()?)?; @@ -567,8 +574,8 @@ fn evm_try_unsafe_construct_block_in_template( } #[ffi_fallible] -fn evm_try_unsafe_commit_block(template: &mut BlockTemplateWrapper) -> Result<()> { - unsafe { SERVICES.evm.commit_block(&template.0) } +fn evm_try_unsafe_commit_block(template: &BlockTemplateWrapper) -> Result<()> { + unsafe { SERVICES.evm.commit_block(template.get_inner()?) } } #[ffi_fallible] @@ -742,7 +749,7 @@ fn evm_try_unsafe_create_dst20( unsafe { SERVICES .evm - .push_tx_in_block_template(&mut template.0, system_tx, native_hash) + .push_tx_in_block_template(template.get_inner_mut()?, system_tx, native_hash) } } @@ -770,7 +777,7 @@ fn evm_try_unsafe_bridge_dst20( unsafe { SERVICES .evm - .push_tx_in_block_template(&mut template.0, system_tx, native_hash) + .push_tx_in_block_template(template.get_inner_mut()?, system_tx, native_hash) } } @@ -804,14 +811,14 @@ fn evm_try_get_tx_hash(raw_tx: &str) -> Result { #[ffi_fallible] fn evm_try_unsafe_is_smart_contract_in_template( address: &str, - template: &mut BlockTemplateWrapper, + template: &BlockTemplateWrapper, ) -> Result { let address = address.parse::().map_err(|_| "Invalid address")?; unsafe { SERVICES .evm - .is_smart_contract_in_block_template(address, &template.0) + .is_smart_contract_in_block_template(address, template.get_inner()?) } } diff --git a/lib/ain-rs-exports/src/lib.rs b/lib/ain-rs-exports/src/lib.rs index 973506ba1b..9b1e807f17 100644 --- a/lib/ain-rs-exports/src/lib.rs +++ b/lib/ain-rs-exports/src/lib.rs @@ -2,11 +2,23 @@ mod core; mod evm; mod prelude; -use crate::{core::*, evm::*}; use ain_evm::blocktemplate::BlockTemplate; -#[derive(Debug)] -pub struct BlockTemplateWrapper(BlockTemplate); +use crate::{core::*, evm::*}; + +pub struct BlockTemplateWrapper(Option); + +impl BlockTemplateWrapper { + const ERROR: &'static str = "Inner block template is None"; + + fn get_inner(&self) -> Result<&BlockTemplate, &'static str> { + self.0.as_ref().ok_or(Self::ERROR) + } + + fn get_inner_mut(&mut self) -> Result<&mut BlockTemplate, &'static str> { + self.0.as_mut().ok_or(Self::ERROR) + } +} #[cxx::bridge] pub mod ffi { @@ -171,7 +183,7 @@ pub mod ffi { fn evm_try_unsafe_get_next_valid_nonce_in_template( result: &mut CrossBoundaryResult, - block_template: &mut BlockTemplateWrapper, + block_template: &BlockTemplateWrapper, address: &str, ) -> u64; @@ -197,13 +209,13 @@ pub mod ffi { fn evm_try_unsafe_validate_raw_tx_in_template( result: &mut CrossBoundaryResult, - block_template: &mut BlockTemplateWrapper, + block_template: &BlockTemplateWrapper, raw_tx: &str, ); fn evm_try_unsafe_validate_transferdomain_tx_in_template( result: &mut CrossBoundaryResult, - block_template: &mut BlockTemplateWrapper, + block_template: &BlockTemplateWrapper, raw_tx: &str, context: TransferDomainInfo, ); @@ -218,11 +230,12 @@ pub mod ffi { fn evm_try_unsafe_construct_block_in_template( result: &mut CrossBoundaryResult, block_template: &mut BlockTemplateWrapper, + is_miner: bool, ) -> FinalizeBlockCompletion; fn evm_try_unsafe_commit_block( result: &mut CrossBoundaryResult, - block_template: &mut BlockTemplateWrapper, + block_template: &BlockTemplateWrapper, ); fn evm_try_unsafe_handle_attribute_apply( @@ -288,7 +301,7 @@ pub mod ffi { fn evm_try_unsafe_is_smart_contract_in_template( result: &mut CrossBoundaryResult, address: &str, - block_template: &mut BlockTemplateWrapper, + block_template: &BlockTemplateWrapper, ) -> bool; fn evm_try_get_tx_miner_info_from_raw_tx( diff --git a/src/dfi/validation.cpp b/src/dfi/validation.cpp index cad4b1672d..602eae8c35 100644 --- a/src/dfi/validation.cpp +++ b/src/dfi/validation.cpp @@ -2681,7 +2681,7 @@ static Res ProcessEVMQueue(const CBlock &block, } CrossBoundaryResult result; - const auto blockResult = evm_try_unsafe_construct_block_in_template(result, evmTemplate->GetTemplate()); + const auto blockResult = evm_try_unsafe_construct_block_in_template(result, evmTemplate->GetTemplate(), false); if (!result.ok) { return Res::Err(result.reason.c_str()); } diff --git a/src/miner.cpp b/src/miner.cpp index 96c5294cee..e16d9fc886 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -280,7 +280,8 @@ ResVal> BlockAssembler::CreateNewBlock(const CSc XVM xvm{}; if (isEvmEnabledForBlock) { - auto res = XResultValueLogged(evm_try_unsafe_construct_block_in_template(result, evmTemplate->GetTemplate())); + auto res = + XResultValueLogged(evm_try_unsafe_construct_block_in_template(result, evmTemplate->GetTemplate(), true)); if (!res) { return Res::Err("Failed to construct block"); } diff --git a/test/functional/contracts/SelfDestruct.sol b/test/functional/contracts/SelfDestruct.sol new file mode 100644 index 0000000000..996a7cf8eb --- /dev/null +++ b/test/functional/contracts/SelfDestruct.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; + +contract SelfDestructContract { + address public owner; + + constructor() { + owner = msg.sender; + } + + modifier onlyOwner() { + require(msg.sender == owner, "Only the owner can call this function"); + _; + } + + function killContract(address payable recipient) public onlyOwner { + selfdestruct(recipient); + } +} diff --git a/test/functional/feature_evm.py b/test/functional/feature_evm.py index e21760789e..2704cb3211 100755 --- a/test/functional/feature_evm.py +++ b/test/functional/feature_evm.py @@ -5,6 +5,7 @@ # file LICENSE or http://www.opensource.org/licenses/mit-license.php. """Test EVM behaviour""" from test_framework.evm_key_pair import EvmKeyPair +from test_framework.evm_contract import EVMContract from test_framework.test_framework import DefiTestFramework from test_framework.util import ( assert_equal, @@ -14,6 +15,7 @@ ) from decimal import Decimal import math +from web3 import Web3 class EVMTest(DefiTestFramework): @@ -63,40 +65,6 @@ def set_test_params(self): ], ] - def run_test(self): - # Check ERC55 wallet support - self.erc55_wallet_support() - - # Test TransferDomain, OP_RETURN and EVM Gov vars - self.evm_gov_vars() - - # Fund accounts - self.setup_accounts() - - # Test block ordering by nonce and Eth RBF - self.nonce_order_and_rbf() - - # Check XVM in coinbase - self.validate_xvm_coinbase() - - # EVM rollback - self.evm_rollback() - - # Multiple mempool fee replacement - self.multiple_eth_rbf() - - # Test that node should not crash without chainId param - self.test_tx_without_chainid() - - # Test evmtx auto nonce - self.sendtransaction_auto_nonce() - - # Toggle EVM - self.toggle_evm_enablement() - - # Test Eth on encrypted wallet - self.encrypt_wallet() - def test_tx_without_chainid(self): node = self.nodes[0] @@ -741,6 +709,8 @@ def verify_transferdomain_not_enabled_post_evm_on(): assert_equal(attrs["v0/evm/block/finality_count"], "100") def setup_accounts(self): + self.evm_key_pair = EvmKeyPair.from_node(self.nodes[0]) + # Fund DFI address self.nodes[0].utxostoaccount({self.address: "300@DFI"}) self.nodes[0].generate(1) @@ -1399,6 +1369,152 @@ def encrypt_wallet(self): ) self.nodes[0].generate(1) + def delete_account_from_trie(self): + addressA = self.nodes[0].getnewaddress("", "erc55") + addressB = self.nodes[0].getnewaddress("", "erc55") + + # Fund EVM address + self.nodes[0].transferdomain( + [ + { + "src": {"address": self.address, "amount": "2@DFI", "domain": 2}, + "dst": { + "address": addressA, + "amount": "2@DFI", + "domain": 3, + }, + } + ] + ) + self.nodes[0].generate(1) + + self.nodes[0].evmtx( + addressA, 0, 21, 21001, addressB, 0 + ) # Touch addressB and trigger deletion as empty account + self.nodes[0].generate(1) + + # Deploy SelfDestruct contract to trigger deletion via SELFDESTRUCT opcode + self.nodes[0].transferdomain( + [ + { + "src": {"address": self.address, "amount": "20@DFI", "domain": 2}, + "dst": { + "address": self.evm_key_pair.address, + "amount": "20@DFI", + "domain": 3, + }, + } + ] + ) + self.nodes[0].generate(1) + + self.contract = EVMContract.from_file( + "SelfDestruct.sol", "SelfDestructContract" + ) + abi, bytecode, deployedBytecode = self.contract.compile() + compiled = self.nodes[0].w3.eth.contract(abi=abi, bytecode=bytecode) + + tx = compiled.constructor().build_transaction( + { + "chainId": self.nodes[0].w3.eth.chain_id, + "nonce": self.nodes[0].w3.eth.get_transaction_count( + self.evm_key_pair.address + ), + "maxFeePerGas": 10_000_000_000, + "maxPriorityFeePerGas": 500_000, + "gas": 1_000_000, + } + ) + signed = self.nodes[0].w3.eth.account.sign_transaction( + tx, self.evm_key_pair.privkey + ) + hash = self.nodes[0].w3.eth.send_raw_transaction(signed.rawTransaction) + + self.nodes[0].generate(1) + + receipt = self.nodes[0].w3.eth.wait_for_transaction_receipt(hash) + self.contract_address = receipt["contractAddress"] + self.contract = self.nodes[0].w3.eth.contract( + address=receipt["contractAddress"], abi=abi + ) + + codeBefore = self.nodes[0].eth_getCode(self.contract_address) + assert_equal( + codeBefore[2:], + deployedBytecode, + ) + storageBefore = self.nodes[0].eth_getStorageAt(self.contract_address, "0x0") + assert_equal( + Web3.to_checksum_address(storageBefore[26:]), + self.evm_key_pair.address, + ) + + tx = self.contract.functions.killContract(addressA).build_transaction( + { + "chainId": self.nodes[0].w3.eth.chain_id, + "nonce": self.nodes[0].w3.eth.get_transaction_count( + self.evm_key_pair.address + ), + "gasPrice": 10_000_000_000, + "gas": 10_000_000, + } + ) + signed = self.nodes[0].w3.eth.account.sign_transaction( + tx, self.evm_key_pair.privkey + ) + hash = self.nodes[0].w3.eth.send_raw_transaction(signed.rawTransaction) + self.nodes[0].generate(1) + + codeAfterSelfDestruct = self.nodes[0].eth_getCode(self.contract_address) + assert_equal( + codeAfterSelfDestruct, + "0x", + ) + storageAfterSelfDestruct = self.nodes[0].eth_getStorageAt( + self.contract_address, "0x0" + ) + assert_equal( + storageAfterSelfDestruct, + "0x0000000000000000000000000000000000000000000000000000000000000000", + ) + + def run_test(self): + # Check ERC55 wallet support + self.erc55_wallet_support() + + # Test TransferDomain, OP_RETURN and EVM Gov vars + self.evm_gov_vars() + + # Fund accounts + self.setup_accounts() + + # Test block ordering by nonce and Eth RBF + self.nonce_order_and_rbf() + + # Check XVM in coinbase + self.validate_xvm_coinbase() + + # EVM rollback + self.evm_rollback() + + # Multiple mempool fee replacement + self.multiple_eth_rbf() + + # Test that node should not crash without chainId param + self.test_tx_without_chainid() + + # Test evmtx auto nonce + self.sendtransaction_auto_nonce() + + # Toggle EVM + self.toggle_evm_enablement() + + # Test Eth on encrypted wallet + self.encrypt_wallet() + + # Delete state account + self.delete_account_from_trie() + if __name__ == "__main__": EVMTest().main() diff --git a/test/functional/feature_evm_gas.py b/test/functional/feature_evm_gas.py index d7f675ecc3..13444e3389 100644 --- a/test/functional/feature_evm_gas.py +++ b/test/functional/feature_evm_gas.py @@ -83,7 +83,7 @@ def setup(self): } } ) - self.nodes[0].generate(1) + self.nodes[0].generate(2) self.nodes[0] = self.nodes[0] self.evm_key_pair = EvmKeyPair.from_node(self.nodes[0]) diff --git a/test/functional/feature_evm_rollback.py b/test/functional/feature_evm_rollback.py index 84c7f01758..4631351c24 100755 --- a/test/functional/feature_evm_rollback.py +++ b/test/functional/feature_evm_rollback.py @@ -74,7 +74,7 @@ def setup(self): } } ) - self.nodes[0].generate(1) + self.nodes[0].generate(2) self.nodes[0].transferdomain( [ { diff --git a/test/functional/feature_evm_transferdomain.py b/test/functional/feature_evm_transferdomain.py index 40a481b9df..c74a426582 100755 --- a/test/functional/feature_evm_transferdomain.py +++ b/test/functional/feature_evm_transferdomain.py @@ -210,7 +210,7 @@ def invalid_before_fork_and_disabled(self): } } ) - self.nodes[0].generate(1) + self.nodes[0].generate(2) assert_raises_rpc_error( -32600,