From 7a24d85d420babead78655ff30a72f811c0b827d Mon Sep 17 00:00:00 2001 From: jouzo Date: Fri, 19 May 2023 16:09:14 +0100 Subject: [PATCH] Rollback latest EVM block --- lib/ain-evm/src/block.rs | 2 +- lib/ain-evm/src/handler.rs | 4 +- lib/ain-evm/src/receipt.rs | 5 +- lib/ain-evm/src/storage/cache.rs | 22 ++++- lib/ain-evm/src/storage/code.rs | 43 +++++++++ lib/ain-evm/src/storage/data_handler.rs | 82 ++++++++++++----- lib/ain-evm/src/storage/mod.rs | 17 +++- lib/ain-evm/src/storage/traits.rs | 6 +- lib/ain-grpc/src/rpc.rs | 2 +- lib/ain-rs-exports/src/lib.rs | 12 ++- src/validation.cpp | 4 + test/functional/feature_evm_rollback.py | 113 ++++++++++++++++++++++++ test/functional/test_runner.py | 1 + 13 files changed, 275 insertions(+), 38 deletions(-) create mode 100644 lib/ain-evm/src/storage/code.rs create mode 100755 test/functional/feature_evm_rollback.py diff --git a/lib/ain-evm/src/block.rs b/lib/ain-evm/src/block.rs index 98db301e4a..a6c661e4bc 100644 --- a/lib/ain-evm/src/block.rs +++ b/lib/ain-evm/src/block.rs @@ -29,7 +29,7 @@ impl BlockHandler { } pub fn connect_block(&self, block: BlockAny) { - self.storage.put_latest_block(&block); + self.storage.put_latest_block(Some(&block)); self.storage.put_block(&block) } } diff --git a/lib/ain-evm/src/handler.rs b/lib/ain-evm/src/handler.rs index 25fca7b13c..f5e406d3ea 100644 --- a/lib/ain-evm/src/handler.rs +++ b/lib/ain-evm/src/handler.rs @@ -171,8 +171,8 @@ impl Handlers { if update_state { debug!( - "[finalize_block] Updating state with new state_root : {:#x}", - block.header.state_root + "[finalize_block] Finalizing block number {:#x}, state_root {:#x}", + block.header.number, block.header.state_root ); self.block.connect_block(block.clone()); self.receipt.put_receipts(receipts); diff --git a/lib/ain-evm/src/receipt.rs b/lib/ain-evm/src/receipt.rs index 6b51da3a31..8b7d64d8e4 100644 --- a/lib/ain-evm/src/receipt.rs +++ b/lib/ain-evm/src/receipt.rs @@ -33,7 +33,7 @@ fn get_contract_address(sender: &H160, nonce: &U256) -> H160 { stream.append(sender); stream.append(nonce); - return H160::from(keccak(stream.as_raw())); + H160::from(keccak(stream.as_raw())) } impl ReceiptHandler { @@ -81,8 +81,7 @@ impl ReceiptHandler { from: signed_tx.sender, to: signed_tx.to(), tx_index: index, - tx_type: EnvelopedEncodable::type_id(&signed_tx.transaction) - .unwrap_or_default(), + tx_type: signed_tx.transaction.type_id().unwrap_or_default(), contract_address: signed_tx .to() .is_none() diff --git a/lib/ain-evm/src/storage/cache.rs b/lib/ain-evm/src/storage/cache.rs index 33fc1935dd..2885f0bc22 100644 --- a/lib/ain-evm/src/storage/cache.rs +++ b/lib/ain-evm/src/storage/cache.rs @@ -5,7 +5,7 @@ use lru::LruCache; use primitive_types::{H256, U256}; use std::borrow::ToOwned; -use super::traits::{BlockStorage, TransactionStorage}; +use super::traits::{BlockStorage, Rollback, TransactionStorage}; #[derive(Debug)] pub struct Cache { @@ -71,9 +71,9 @@ impl BlockStorage for Cache { .map(ToOwned::to_owned) } - fn put_latest_block(&self, block: &BlockAny) { + fn put_latest_block(&self, block: Option<&BlockAny>) { let mut cache = self.latest_block.write().unwrap(); - *cache = Some(block.clone()); + *cache = block.cloned(); } } @@ -130,3 +130,19 @@ impl TransactionStorage for Cache { .put(transaction.hash(), transaction.clone()); } } + +impl Rollback for Cache { + fn disconnect_latest_block(&self) { + if let Some(block) = self.get_latest_block() { + let mut transaction_cache = self.transactions.write().unwrap(); + for tx in &block.transactions { + transaction_cache.pop(&tx.hash()); + } + + self.block_hashes.write().unwrap().pop(&block.header.hash()); + self.blocks.write().unwrap().pop(&block.header.number); + + self.put_latest_block(self.get_block_by_hash(&block.header.parent_hash).as_ref()) + } + } +} diff --git a/lib/ain-evm/src/storage/code.rs b/lib/ain-evm/src/storage/code.rs new file mode 100644 index 0000000000..bbe7790f8b --- /dev/null +++ b/lib/ain-evm/src/storage/code.rs @@ -0,0 +1,43 @@ +use primitive_types::{H256, U256}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +use super::traits::PersistentState; + +/// `CodeHistory` maintains a history of accounts' codes. +/// +/// It tracks the current state (`code_map`), as well as a history (`history`) of code hashes +/// that should be removed if a specific block is rolled back. The correct account code_hash +/// is tracked by the state trie. +/// This structure is solely required for rolling back and preventing ghost entries. +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct CodeHistory { + /// The current state of each code + code_map: HashMap>, + /// A map from block number to a vector of code hashes to remove for that block. + history: HashMap>, +} + +impl PersistentState for CodeHistory {} + +impl CodeHistory { + pub fn insert(&mut self, block_number: U256, code_hash: H256, code: Vec) { + self.code_map.insert(code_hash, code.clone()); + self.history + .entry(block_number) + .or_insert_with(Vec::new) + .push(code_hash); + } + + pub fn get(&self, code_hash: &H256) -> Option<&Vec> { + self.code_map.get(code_hash) + } + + pub fn rollback(&mut self, block_number: U256) { + if let Some(code_hashes) = self.history.remove(&block_number) { + for code_hash in &code_hashes { + self.code_map.remove(code_hash); + } + } + } +} diff --git a/lib/ain-evm/src/storage/data_handler.rs b/lib/ain-evm/src/storage/data_handler.rs index 86f83d8cc9..2cae29a1a8 100644 --- a/lib/ain-evm/src/storage/data_handler.rs +++ b/lib/ain-evm/src/storage/data_handler.rs @@ -6,9 +6,12 @@ use std::borrow::ToOwned; use crate::receipt::Receipt; -use super::traits::{ - BlockStorage, FlushableStorage, PersistentState, PersistentStateError, ReceiptStorage, - TransactionStorage, +use super::{ + code::CodeHistory, + traits::{ + BlockStorage, FlushableStorage, PersistentState, PersistentStateError, ReceiptStorage, + Rollback, TransactionStorage, + }, }; pub static BLOCK_MAP_PATH: &str = "block_map.bin"; @@ -16,20 +19,19 @@ pub static BLOCK_DATA_PATH: &str = "block_data.bin"; pub static LATEST_BLOCK_DATA_PATH: &str = "latest_block_data.bin"; pub static RECEIPT_MAP_PATH: &str = "receipt_map.bin"; pub static CODE_MAP_PATH: &str = "code_map.bin"; -// pub static TRANSACTION_DATA_PATH: &str = "transaction_data.bin"; +pub static TRANSACTION_DATA_PATH: &str = "transaction_data.bin"; type BlockHashtoBlock = HashMap; type Blocks = HashMap; type TxHashToTx = HashMap; type LatestBlockNumber = U256; type TransactionHashToReceipt = HashMap; -type CodeHashToCode = HashMap>; impl PersistentState for BlockHashtoBlock {} impl PersistentState for Blocks {} impl PersistentState for LatestBlockNumber {} impl PersistentState for TransactionHashToReceipt {} -impl PersistentState for CodeHashToCode {} +impl PersistentState for TxHashToTx {} #[derive(Debug)] pub struct BlockchainDataHandler { @@ -42,14 +44,16 @@ pub struct BlockchainDataHandler { blocks: RwLock, latest_block_number: RwLock>, - code_map: RwLock, + code_map: RwLock, } impl BlockchainDataHandler { pub fn new() -> Self { - let blocks = Blocks::load_from_disk(BLOCK_DATA_PATH).expect("Error loading blocks data"); BlockchainDataHandler { - transactions: RwLock::new(HashMap::new()), + transactions: RwLock::new( + TxHashToTx::load_from_disk(TRANSACTION_DATA_PATH) + .expect("Error loading blocks data"), + ), block_map: RwLock::new( BlockHashtoBlock::load_from_disk(BLOCK_MAP_PATH) .expect("Error loading block_map data"), @@ -57,13 +61,15 @@ impl BlockchainDataHandler { latest_block_number: RwLock::new( LatestBlockNumber::load_from_disk(LATEST_BLOCK_DATA_PATH).ok(), ), - blocks: RwLock::new(blocks), + blocks: RwLock::new( + Blocks::load_from_disk(BLOCK_DATA_PATH).expect("Error loading blocks data"), + ), receipts: RwLock::new( TransactionHashToReceipt::load_from_disk(RECEIPT_MAP_PATH) .expect("Error loading receipts data"), ), code_map: RwLock::new( - CodeHashToCode::load_from_disk(CODE_MAP_PATH).expect("Error loading code data"), + CodeHistory::load_from_disk(CODE_MAP_PATH).expect("Error loading code data"), ), } } @@ -80,13 +86,12 @@ impl TransactionStorage for BlockchainDataHandler { } } - fn get_transaction_by_hash(&self, _hash: &H256) -> Option { - None - // self.transactions - // .read() - // .unwrap() - // .get(hash) - // .map(ToOwned::to_owned) + fn get_transaction_by_hash(&self, hash: &H256) -> Option { + self.transactions + .read() + .unwrap() + .get(hash) + .map(ToOwned::to_owned) } fn get_transaction_by_block_hash_and_index( @@ -162,9 +167,9 @@ impl BlockStorage for BlockchainDataHandler { .and_then(|number| self.get_block_by_number(number)) } - fn put_latest_block(&self, block: &BlockAny) { + fn put_latest_block(&self, block: Option<&BlockAny>) { let mut latest_block_number = self.latest_block_number.write().unwrap(); - *latest_block_number = Some(block.header.number); + *latest_block_number = block.map(|b| b.header.number); } } @@ -197,6 +202,10 @@ impl FlushableStorage for BlockchainDataHandler { .write() .unwrap() .save_to_disk(RECEIPT_MAP_PATH)?; + self.transactions + .write() + .unwrap() + .save_to_disk(TRANSACTION_DATA_PATH)?; self.code_map.write().unwrap().save_to_disk(CODE_MAP_PATH) } } @@ -210,7 +219,36 @@ impl BlockchainDataHandler { .map(ToOwned::to_owned) } - pub fn put_code(&self, hash: &H256, code: &[u8]) -> Option> { - self.code_map.write().unwrap().insert(*hash, code.to_vec()) + pub fn put_code(&self, hash: &H256, code: &[u8]) { + let block_number = self + .get_latest_block() + .map(|b| b.header.number) + .unwrap_or_default() + + 1; + self.code_map + .write() + .unwrap() + .insert(block_number, *hash, code.to_vec()) + } +} + +impl Rollback for BlockchainDataHandler { + fn disconnect_latest_block(&self) { + if let Some(block) = self.get_latest_block() { + println!("disconnecting block number : {:x?}", block.header.number); + let mut transactions = self.transactions.write().unwrap(); + let mut receipts = self.receipts.write().unwrap(); + for tx in &block.transactions { + let hash = &tx.hash(); + transactions.remove(hash); + receipts.remove(hash); + } + + self.block_map.write().unwrap().remove(&block.header.hash()); + self.blocks.write().unwrap().remove(&block.header.number); + self.code_map.write().unwrap().rollback(block.header.number); + + self.put_latest_block(self.get_block_by_hash(&block.header.parent_hash).as_ref()) + } } } diff --git a/lib/ain-evm/src/storage/mod.rs b/lib/ain-evm/src/storage/mod.rs index 2ad54b5f26..4fbd51ff38 100644 --- a/lib/ain-evm/src/storage/mod.rs +++ b/lib/ain-evm/src/storage/mod.rs @@ -1,4 +1,5 @@ mod cache; +mod code; mod data_handler; pub mod traits; @@ -11,7 +12,8 @@ use self::{ cache::Cache, data_handler::BlockchainDataHandler, traits::{ - BlockStorage, FlushableStorage, PersistentStateError, ReceiptStorage, TransactionStorage, + BlockStorage, FlushableStorage, PersistentStateError, ReceiptStorage, Rollback, + TransactionStorage, }, }; @@ -66,13 +68,13 @@ impl BlockStorage for Storage { self.cache.get_latest_block().or_else(|| { let latest_block = self.blockchain_data_handler.get_latest_block(); if let Some(ref block) = latest_block { - self.cache.put_latest_block(block) + self.cache.put_latest_block(Some(block)) } latest_block }) } - fn put_latest_block(&self, block: &BlockAny) { + fn put_latest_block(&self, block: Option<&BlockAny>) { self.cache.put_latest_block(block); self.blockchain_data_handler.put_latest_block(block); } @@ -160,7 +162,7 @@ impl Storage { self.blockchain_data_handler.get_code_by_hash(&hash) } - pub fn put_code(&self, hash: H256, code: Vec) -> Option> { + pub fn put_code(&self, hash: H256, code: Vec) { self.blockchain_data_handler.put_code(&hash, &code) } } @@ -173,3 +175,10 @@ impl Storage { ); } } + +impl Rollback for Storage { + fn disconnect_latest_block(&self) { + self.cache.disconnect_latest_block(); + self.blockchain_data_handler.disconnect_latest_block(); + } +} diff --git a/lib/ain-evm/src/storage/traits.rs b/lib/ain-evm/src/storage/traits.rs index 729ae4478b..a893adffc9 100644 --- a/lib/ain-evm/src/storage/traits.rs +++ b/lib/ain-evm/src/storage/traits.rs @@ -17,7 +17,7 @@ pub trait BlockStorage { fn get_block_by_hash(&self, block_hash: &H256) -> Option; fn put_block(&self, block: &BlockAny); fn get_latest_block(&self) -> Option; - fn put_latest_block(&self, block: &BlockAny); + fn put_latest_block(&self, block: Option<&BlockAny>); } pub trait TransactionStorage { @@ -45,6 +45,10 @@ pub trait FlushableStorage { fn flush(&self) -> Result<(), PersistentStateError>; } +pub trait Rollback { + fn disconnect_latest_block(&self); +} + pub trait PersistentState { fn save_to_disk(&self, file_path: &str) -> Result<(), PersistentStateError> where diff --git a/lib/ain-grpc/src/rpc.rs b/lib/ain-grpc/src/rpc.rs index 4bc38c0950..8fefc5231d 100644 --- a/lib/ain-grpc/src/rpc.rs +++ b/lib/ain-grpc/src/rpc.rs @@ -516,7 +516,7 @@ impl MetachainRPCServer for MetachainRPCModule { Some(TransactionMessage::Legacy(mut m)) => { m.nonce = nonce; m.chain_id = Some(chain_id); - m.gas_limit = U256::from(1); + m.gas_limit = gas_limit; if gas_price.is_none() { m.gas_price = self.gas_price().unwrap(); } diff --git a/lib/ain-rs-exports/src/lib.rs b/lib/ain-rs-exports/src/lib.rs index ed45a75798..1f837c9a5e 100644 --- a/lib/ain-rs-exports/src/lib.rs +++ b/lib/ain-rs-exports/src/lib.rs @@ -1,4 +1,7 @@ -use ain_evm::transaction::{self, SignedTx}; +use ain_evm::{ + storage::traits::Rollback, + transaction::{self, SignedTx}, +}; use ain_grpc::{init_evm_runtime, start_servers, stop_evm_runtime}; use ain_evm::runtime::RUNTIME; @@ -64,6 +67,8 @@ pub mod ffi { fn stop_evm_runtime(); fn create_and_sign_tx(ctx: CreateTransactionContext) -> Result>; + + fn evm_disconnect_latest_block() -> Result<()>; } } @@ -200,3 +205,8 @@ fn evm_finalize( pub fn preinit() { ain_grpc::preinit(); } + +fn evm_disconnect_latest_block() -> Result<(), Box> { + RUNTIME.handlers.storage.disconnect_latest_block(); + Ok(()) +} diff --git a/src/validation.cpp b/src/validation.cpp index 520dd104a6..f3a6f1abef 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1857,8 +1857,12 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI mnview.EraseMasternodeLastBlockTime(*nodeId, static_cast(pindex->nHeight)); } } + + evm_disconnect_latest_block(); + mnview.SetLastHeight(pindex->pprev->nHeight); + return fClean ? DISCONNECT_OK : DISCONNECT_UNCLEAN; } diff --git a/test/functional/feature_evm_rollback.py b/test/functional/feature_evm_rollback.py new file mode 100755 index 0000000000..e995b89dd2 --- /dev/null +++ b/test/functional/feature_evm_rollback.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2019 The Bitcoin Core developers +# Copyright (c) DeFi Blockchain Developers +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. +"""Test EVM behaviour""" + +from test_framework.test_framework import DefiTestFramework +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error +) + +class EVMRolllbackTest(DefiTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + self.extra_args = [ + ['-dummypos=0', '-txnotokens=0', '-amkheight=50', '-bayfrontheight=51', '-eunosheight=80', '-fortcanningheight=82', '-fortcanninghillheight=84', '-fortcanningroadheight=86', '-fortcanningcrunchheight=88', '-fortcanningspringheight=90', '-fortcanninggreatworldheight=94', '-fortcanningepilogueheight=96', '-grandcentralheight=101', '-nextnetworkupgradeheight=105', '-subsidytest=1', '-txindex=1'], + ] + + def setup(self): + self.address = self.nodes[0].get_genesis_keys().ownerAuthAddress + self.ethAddress = self.nodes[0].getnewaddress("","eth") + self.toAddress = self.nodes[0].getnewaddress("","eth") + + # Generate chain + self.nodes[0].generate(101) + + assert_raises_rpc_error(-32600, "called before NextNetworkUpgrade height", self.nodes[0].evmtx, self.ethAddress, 0, 21, 21000, self.toAddress, 0.1) + + # Move to fork height + self.nodes[0].generate(4) + + self.nodes[0].getbalance() + self.nodes[0].utxostoaccount({self.address: "201@DFI"}) + self.nodes[0].setgov({"ATTRIBUTES": {'v0/params/feature/evm': 'true'}}) + self.nodes[0].generate(1) + + self.creationAddress = "0xe61a3a6eb316d773c773f4ce757a542f673023c6" + self.nodes[0].importprivkey("957ac3be2a08afe1fafb55bd3e1d479c4ae6d7bf1c9b2a0dcc5caad6929e6617") + + def test_rollback_block(self): + initialBlockHash = self.nodes[0].getbestblockhash() + blockNumberPreInvalidation = self.nodes[0].eth_blockNumber() + blockPreInvalidation = self.nodes[0].eth_getBlockByNumber(blockNumberPreInvalidation) + assert_equal(blockNumberPreInvalidation, "0x2") + assert_equal(blockPreInvalidation['number'], blockNumberPreInvalidation) + + self.nodes[0].invalidateblock(initialBlockHash) + + block = self.nodes[0].eth_getBlockByNumber(blockNumberPreInvalidation) + assert_equal(block, None) + blockByHash = self.nodes[0].eth_getBlockByHash(blockPreInvalidation['hash']) + assert_equal(blockByHash, None) + block = self.nodes[0].eth_getBlockByNumber('latest') + assert_equal(block['number'], '0x1') + + self.nodes[0].reconsiderblock(initialBlockHash) + blockNumber = self.nodes[0].eth_blockNumber() + block = self.nodes[0].eth_getBlockByNumber(blockNumber) + assert_equal(blockNumber, blockNumberPreInvalidation) + assert_equal(block, blockPreInvalidation) + + def test_rollback_transactions(self): + initialBlockHash = self.nodes[0].getbestblockhash() + + hash = self.nodes[0].eth_sendTransaction({ + 'from': self.ethAddress, + 'to': self.toAddress, + 'value': '0xa', + 'gas': '0x7a120', + 'gasPrice': '0x7a120', + }) + self.nodes[0].generate(1) + + blockNumberPreInvalidation = self.nodes[0].eth_blockNumber() + blockPreInvalidation = self.nodes[0].eth_getBlockByNumber(blockNumberPreInvalidation) + assert_equal(blockNumberPreInvalidation, "0x3") + assert_equal(blockPreInvalidation['number'], blockNumberPreInvalidation) + + txPreInvalidation = self.nodes[0].eth_getTransactionByHash(hash) + receiptPreInvalidation = self.nodes[0].eth_getTransactionReceipt(hash) + assert_equal(blockPreInvalidation['transactions'][0], txPreInvalidation['hash']) + assert_equal(blockPreInvalidation['transactions'][0], receiptPreInvalidation['transactionHash']) + + self.nodes[0].invalidateblock(initialBlockHash) + + tx = self.nodes[0].eth_getTransactionByHash(hash) + receipt = self.nodes[0].eth_getTransactionReceipt(hash) + assert_equal(tx, None) + assert_equal(receipt, None) + + self.nodes[0].reconsiderblock(initialBlockHash) + tx = self.nodes[0].eth_getTransactionByHash(hash) + receipt = self.nodes[0].eth_getTransactionReceipt(hash) + assert_equal(blockPreInvalidation['transactions'][0], tx['hash']) + assert_equal(blockPreInvalidation['transactions'][0], receipt['transactionHash']) + + + def run_test(self): + self.setup() + + self.nodes[0].transferdomain(1,{self.address:["100@DFI"]}, {self.creationAddress:["100@DFI"]}) + self.nodes[0].transferdomain(1,{self.address:["100@DFI"]}, {self.ethAddress:["100@DFI"]}) + self.nodes[0].generate(1) + + self.test_rollback_block() + + self.test_rollback_transactions() + +if __name__ == '__main__': + EVMRolllbackTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 393d22027c..2bece828d7 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -293,6 +293,7 @@ 'feature_loan.py', 'feature_evm.py', 'feature_evm_rpc.py', + 'feature_evm_rollback.py', 'feature_evm_rpc_transaction.py', 'feature_evm_smart_contract.py', 'feature_loan_low_interest.py',