From 326c823ee372e322d0700e153364e865bd93d33b Mon Sep 17 00:00:00 2001 From: Jouzo <15011228+Jouzo@users.noreply.github.com> Date: Thu, 6 Apr 2023 08:07:42 +0100 Subject: [PATCH] EVM in/out refinements (#1868) * Return error on sub balance failure. * Change to expected return type * Update EVM in/out TX * Use array instead of rust::Vec * Pass context to CustomTxVisit * Add temporary state to TransactionQueue * Improve readability * Block integration test --------- Co-authored-by: Bushstar --- src/amount.h | 2 + src/masternodes/mn_checks.cpp | 52 +++- src/masternodes/rpc_evm.cpp | 3 - src/rust/crates/ain-evm-ffi/src/lib.rs | 32 ++- src/rust/crates/ain-evm-state/src/evm.rs | 266 ++---------------- src/rust/crates/ain-evm-state/src/traits.rs | 134 +++++++++ src/rust/crates/ain-evm-state/src/tx_queue.rs | 108 +++++-- src/rust/crates/ain-evm-state/tests/block.rs | 110 ++++++++ src/wallet/wallet.cpp | 4 +- src/wallet/wallet.h | 3 +- 10 files changed, 425 insertions(+), 289 deletions(-) create mode 100644 src/rust/crates/ain-evm-state/tests/block.rs diff --git a/src/amount.h b/src/amount.h index dd26bdc686b..67db644848f 100644 --- a/src/amount.h +++ b/src/amount.h @@ -77,6 +77,8 @@ struct DCT_ID { static constexpr CAmount COIN = 100000000; static constexpr CAmount CENT = 1000000; +static constexpr int64_t WEI_IN_GWEI = 1000000000; +static constexpr int64_t CAMOUNT_TO_WEI = 10; //Converts the given value to decimal format string with COIN precision. inline std::string GetDecimalString(CAmount nValue) diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index 7afd8c7faaa..960f04fb705 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -3834,12 +3834,6 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor { const auto sumFrom = SumAllTransfers(obj.from); const auto sumTo = SumAllTransfers(obj.to); - if (obj.type == CTransferBalanceType::EvmIn || obj.type == CTransferBalanceType::EvmOut) { - for (const auto& [id, _] : sumFrom.balances) - if (id != DCT_ID{0}) - return Res::Err("For EVM in/out transfers only DFI token is currently supported"); - } - if (sumFrom != sumTo) return Res::Err("sum of inputs (from) != sum of outputs (to)"); @@ -3851,18 +3845,54 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor { if (!res) return res; } else if (obj.type == CTransferBalanceType::EvmIn) { - for (const auto& [addr, _] : obj.to) { + res = SubBalancesDelShares(obj.from); + if (!res) + return res; + + for (const auto& [addr, balances] : obj.to) { CTxDestination dest; if (ExtractDestination(addr, dest)) { if (dest.index() != WitV16KeyEthHashType) { return Res::Err("To address must be an ETH address in case of \"evmin\" transfertype"); } } + + const auto toAddress = std::get(dest); + + for (const auto& [id, amount] : balances.balances) { + if (id != DCT_ID{0}) { + return Res::Err("For EVM out transfers, only DFI token is currently supported"); + } + + arith_uint256 balanceIn = amount; + balanceIn *= CAMOUNT_TO_WEI * WEI_IN_GWEI; + evm_add_balance(evmContext, HexStr(toAddress.begin(), toAddress.end()), ArithToUint256(balanceIn).ToArrayReversed()); + } } - res = SubBalancesDelShares(obj.from); - if (!res) - return res; } else if (obj.type == CTransferBalanceType::EvmOut) { + for (const auto& [addr, balances] : obj.from) { + CTxDestination dest; + if (ExtractDestination(addr, dest)) { + if (dest.index() != WitV16KeyEthHashType) { + return Res::Err("Invalid destination"); + } + } + + const auto fromAddress = std::get(dest); + + for (const auto& [id, amount] : balances.balances) { + if (id != DCT_ID{0}) { + return Res::Err("For EVM out transfers, only DFI token is currently supported"); + } + + arith_uint256 balanceIn = amount; + balanceIn *= CAMOUNT_TO_WEI * WEI_IN_GWEI; + if (!evm_sub_balance(evmContext, HexStr(fromAddress.begin(), fromAddress.end()), ArithToUint256(balanceIn).ToArrayReversed())) { + return Res::Err("Not enough balance in %s to cover EVM out", EncodeDestination(dest)); + } + } + } + res = AddBalancesSetShares(obj.to); if (!res) return res; @@ -4085,7 +4115,7 @@ Res ApplyCustomTx(CCustomCSView &mnview, PopulateVaultHistoryData(mnview.GetHistoryWriters(), view, txMessage, txType, height, txn, tx.GetHash()); } - res = CustomTxVisit(view, coins, tx, height, consensus, txMessage, time, txn); + res = CustomTxVisit(view, coins, tx, height, consensus, txMessage, time, txn, evmContext); if (res) { if (canSpend && txType == CustomTxType::UpdateMasternode) { diff --git a/src/masternodes/rpc_evm.cpp b/src/masternodes/rpc_evm.cpp index 1bdd020873a..fed5bc3c220 100644 --- a/src/masternodes/rpc_evm.cpp +++ b/src/masternodes/rpc_evm.cpp @@ -3,9 +3,6 @@ #include #include -const int64_t WEI_IN_GWEI = 1000000000; -const int64_t CAMOUNT_TO_WEI = 10; - UniValue evmtx(const JSONRPCRequest& request) { auto pwallet = GetWallet(request); diff --git a/src/rust/crates/ain-evm-ffi/src/lib.rs b/src/rust/crates/ain-evm-ffi/src/lib.rs index 26ba028c112..6d68a981c66 100644 --- a/src/rust/crates/ain-evm-ffi/src/lib.rs +++ b/src/rust/crates/ain-evm-ffi/src/lib.rs @@ -7,8 +7,8 @@ use std::error::Error; #[cxx::bridge] mod ffi { extern "Rust" { - fn evm_add_balance(address: &str, amount: [u8; 32]) -> Result<()>; - fn evm_sub_balance(address: &str, amount: [u8; 32]) -> Result<()>; + fn evm_add_balance(context: u64, address: &str, amount: [u8; 32]) -> Result<()>; + fn evm_sub_balance(context: u64, address: &str, amount: [u8; 32]) -> Result; fn evm_validate_raw_tx(tx: &str) -> Result; fn evm_get_context() -> u64; @@ -33,12 +33,32 @@ mod ffi { } } -pub fn evm_add_balance(address: &str, amount: [u8; 32]) -> Result<(), Box> { - RUNTIME.handlers.evm.add_balance(address, amount.into()) +pub fn evm_add_balance( + context: u64, + address: &str, + amount: [u8; 32], +) -> Result<(), Box> { + let address = address.parse()?; + Ok(RUNTIME + .handlers + .evm + .add_balance(context, address, amount.into())) } -pub fn evm_sub_balance(address: &str, amount: [u8; 32]) -> Result<(), Box> { - RUNTIME.handlers.evm.sub_balance(address, amount.into()) +pub fn evm_sub_balance( + context: u64, + address: &str, + amount: [u8; 32], +) -> Result> { + let address = address.parse()?; + match RUNTIME + .handlers + .evm + .sub_balance(context, address, amount.into()) + { + Ok(_) => Ok(true), + Err(_) => Ok(false), + } } pub fn evm_validate_raw_tx(tx: &str) -> Result> { diff --git a/src/rust/crates/ain-evm-state/src/evm.rs b/src/rust/crates/ain-evm-state/src/evm.rs index 00e1c89de69..110acdd6ff9 100644 --- a/src/rust/crates/ain-evm-state/src/evm.rs +++ b/src/rust/crates/ain-evm-state/src/evm.rs @@ -4,13 +4,12 @@ use ain_evm::{executor::AinExecutor, traits::Executor, transaction::SignedTx}; use anyhow::anyhow; use ethereum::{AccessList, Block, PartialHeader, TransactionV2}; use evm::backend::MemoryAccount; -use evm::executor::stack::{MemoryStackState, StackExecutor, StackSubstateMetadata}; use evm::{ backend::{MemoryBackend, MemoryVicinity}, - Config, ExitReason, + ExitReason, }; use hex::FromHex; -use primitive_types::{H160, H256, U256}; +use primitive_types::{H160, U256}; use std::collections::BTreeMap; use std::error::Error; use std::fs::File; @@ -18,8 +17,6 @@ use std::io::{Read, Write}; use std::path::Path; use std::sync::{Arc, RwLock}; -pub static CONFIG: Config = Config::london(); -pub static GAS_LIMIT: u64 = u64::MAX; pub static EVM_STATE_PATH: &str = "evm_state.bin"; pub type EVMState = BTreeMap; @@ -87,22 +84,19 @@ impl EVMHandler { } // TODO wrap in EVM transaction and dryrun with evm_call - pub fn add_balance(&self, address: &str, value: U256) -> Result<(), Box> { - let to = address.parse()?; - let mut state = self.state.write().unwrap(); - let mut account = state.entry(to).or_default(); - account.balance = account.balance + value; - Ok(()) + pub fn add_balance(&self, context: u64, address: H160, value: U256) { + self.tx_queues.add_balance(context, address, value) } - pub fn sub_balance(&self, address: &str, value: U256) -> Result<(), Box> { - let address = address.parse()?; - let mut state = self.state.write().unwrap(); - let mut account = state.get_mut(&address).unwrap(); - if account.balance > value { - account.balance = account.balance - value; - } - Ok(()) + pub fn sub_balance( + &self, + context: u64, + address: H160, + value: U256, + ) -> Result<(), Box> { + self.tx_queues + .sub_balance(context, address, value) + .map_err(|e| e.into()) } pub fn validate_raw_tx(&self, tx: &str) -> Result> { @@ -137,7 +131,8 @@ impl EVMHandler { } pub fn get_context(&self) -> u64 { - self.tx_queues.get_context() + let state = self.state.read().unwrap().clone(); + self.tx_queues.get_context(state) } pub fn discard_context(&self, context: u64) { @@ -158,14 +153,13 @@ impl EVMHandler { let mut tx_hashes = Vec::with_capacity(self.tx_queues.len(context)); let mut failed_tx_hashes = Vec::with_capacity(self.tx_queues.len(context)); let vicinity = get_vicinity(None, None); - let state = self.state.read().unwrap().clone(); + let state = self.tx_queues.state(context).expect("Wrong context"); let backend = MemoryBackend::new(&vicinity, state); let mut executor = AinExecutor::new(backend); for signed_tx in self.tx_queues.drain_all(context) { let tx_response = executor.exec(&signed_tx); println!("tx_response : {:#?}", tx_response); - if tx_response.exit_reason.is_succeed() { // responses.push() tx_hashes.push(signed_tx.transaction); @@ -174,6 +168,8 @@ impl EVMHandler { } } + self.tx_queues.remove(context); + if update_state { let mut state = self.state.write().unwrap(); *state = executor.backend().state().clone(); @@ -217,229 +213,3 @@ fn get_vicinity(origin: Option, gas_price: Option) -> MemoryVicinity block_base_fee_per_gas: U256::MAX, } } - -#[cfg(test)] -mod tests { - use super::*; - use primitive_types::{H256, U256}; - - fn create_account( - nonce: U256, - balance: U256, - code: Vec, - storage: BTreeMap, - ) -> MemoryAccount { - MemoryAccount { - nonce, - balance, - code, - storage, - } - } - - #[test] - fn test_load_non_existent_file() { - let state = EVMState::load_from_disk("non_existent_file.bin").unwrap(); - assert_eq!(state, EVMState::default()); - } - - #[test] - fn test_empty_file() { - let state = BTreeMap::new(); - let path = "empty_test.bin"; - - // Save to an empty file - state.save_to_disk(path).unwrap(); - - let new_state = EVMState::load_from_disk(path).unwrap(); - - assert_eq!(state, new_state); - } - - #[test] - fn test_invalid_file_format() { - let invalid_data = b"invalid_data"; - let path = "invalid_file_format.bin"; - - // Write invalid data to a file - let mut file = File::create(path).unwrap(); - file.write_all(invalid_data).unwrap(); - - let state = EVMState::load_from_disk(path); - - assert!(state.is_err()); - } - - #[test] - fn test_save_and_load_empty_backend() { - let path = "test_empty_backend.bin"; - let state = BTreeMap::new(); - - state.save_to_disk(path).unwrap(); - - let loaded_backend = EVMState::load_from_disk(path).unwrap(); - - assert_eq!(state, loaded_backend); - } - - #[test] - fn test_save_and_load_single_account() { - let path = "test_single_account.bin"; - let mut state = BTreeMap::new(); - - let account = create_account( - U256::from(1), - U256::from(1000), - vec![1, 2, 3], - BTreeMap::new(), - ); - let address = H160::from_low_u64_be(1); - state.insert(address, account); - - state.save_to_disk(path).unwrap(); - - let loaded_backend = EVMState::load_from_disk(path).unwrap(); - - assert_eq!(state, loaded_backend); - } - - #[test] - fn test_save_and_load_multiple_accounts() { - let path = "test_multiple_accounts.bin"; - let mut state = BTreeMap::new(); - - let account1 = create_account( - U256::from(1), - U256::from(1000), - vec![1, 2, 3], - BTreeMap::new(), - ); - let address1 = H160::from_low_u64_be(1); - state.insert(address1, account1); - - let account2 = create_account( - U256::from(2), - U256::from(2000), - vec![4, 5, 6], - BTreeMap::new(), - ); - let address2 = H160::from_low_u64_be(2); - state.insert(address2, account2); - - state.save_to_disk(path).unwrap(); - - let loaded_backend = EVMState::load_from_disk(path).unwrap(); - - assert_eq!(state, loaded_backend); - } -} - -mod tests { - use ain_evm::transaction::SignedTx; - use evm::backend::MemoryAccount; - use primitive_types::{H160, H256, U256}; - - use super::EVMHandler; - use rlp::Encodable; - #[test] - fn test_finalize_block_and_update_test() { - let handler = EVMHandler::new(); - handler - .add_balance( - "0x6745f998a96050bb9b0449e6bd4358138a519679", - U256::from_str_radix("100000000000000000000", 10).unwrap(), - ) - .unwrap(); - let context = handler.get_context(); - - let tx1: SignedTx = "f86b02830186a0830186a094a8f7c4c78c36e54c3950ad58dad24ca5e0191b2989056bc75e2d631000008025a0b0842b0c78dd7fc33584ec9a81ab5104fe70169878de188ba6c11fe7605e298aa0735dc483f625f17d68d1e1fae779b7160612628e6dde9eecf087892fe60bba4e".try_into().unwrap(); - handler.tx_queues.add_signed_tx(context, tx1.clone()); - - handler - .add_balance( - "0xc0cd829081485e70348975d325fe5275140277bd", - U256::from_str_radix("100000000000000000000", 10).unwrap(), - ) - .unwrap(); - let tx2: SignedTx = "f86b02830186a0830186a094a8f7c4c78c36e54c3950ad58dad24ca5e0191b2989056bc75e2d631000008025a01465e2d999c34b22bf4b8b5c9439918e46341f4f0da1b00a6b0479c541161d4aa074abe79c51bf57086e1e84b57ee483cbb2ecf30e8222bc0472436fabfc57dda8".try_into().unwrap(); - handler.tx_queues.add_signed_tx(context, tx2.clone()); - - let tx3: SignedTx = "f86b02830186a0830186a094a8f7c4c78c36e54c3950ad58dad24ca5e0191b2989056bc75e2d631000008025a070b21a24cec13c0569099ee2f8221268103fd609646b73f7c9e85efeb7af5c8ea03d5de75bc12ce28a80f7c0401df6021cc82a334cb1c802c8b9d46223c5c8eb40".try_into().unwrap(); - handler.tx_queues.add_signed_tx(context, tx3.clone()); - - assert_eq!(handler.tx_queues.len(context), 3); - assert_eq!(handler.tx_queues.len(handler.get_context()), 0); - - let (block, failed_txs) = handler.finalize_block(context, true).unwrap(); - assert_eq!( - block.transactions, - vec![tx1, tx2] - .into_iter() - .map(|t| t.transaction) - .collect::>() - ); - assert_eq!( - failed_txs, - vec![tx3] - .into_iter() - .map(|t| t.transaction) - .collect::>() - ); - - let state = handler.state.read().unwrap(); - assert_eq!( - state - .get( - &"0xa8f7c4c78c36e54c3950ad58dad24ca5e0191b29" - .parse() - .unwrap() - ) - .unwrap() - .balance, - U256::from_str_radix("200000000000000000000", 10).unwrap() - ); - assert_eq!( - state - .get( - &"0x6745f998a96050bb9b0449e6bd4358138a519679" - .parse() - .unwrap() - ) - .unwrap() - .balance, - U256::from_str_radix("0", 10).unwrap() - ); - assert_eq!( - state - .get( - &"0xc0cd829081485e70348975d325fe5275140277bd" - .parse() - .unwrap() - ) - .unwrap() - .balance, - U256::from_str_radix("0", 10).unwrap() - ); - } - - #[test] - fn test_finalize_block_and_do_not_update_test() { - let handler = EVMHandler::new(); - handler - .add_balance( - "0x4a1080c5533cb89edc4b65013f08f78868e382de", - U256::from_str_radix("100000000000000000000", 10).unwrap(), - ) - .unwrap(); - let context = handler.get_context(); - - let tx1: SignedTx = "f86b02830186a0830186a094a8f7c4c78c36e54c3950ad58dad24ca5e0191b2989056bc75e2d631000008025a0b0842b0c78dd7fc33584ec9a81ab5104fe70169878de188ba6c11fe7605e298aa0735dc483f625f17d68d1e1fae779b7160612628e6dde9eecf087892fe60bba4e".try_into().unwrap(); - handler.tx_queues.add_signed_tx(context, tx1.clone()); - - let old_state = handler.state.read().unwrap(); - let _ = handler.finalize_block(context, false).unwrap(); - - let new_state = handler.state.read().unwrap(); - assert_eq!(*new_state, *old_state); - } -} diff --git a/src/rust/crates/ain-evm-state/src/traits.rs b/src/rust/crates/ain-evm-state/src/traits.rs index d9f2ec02209..1de5416d283 100644 --- a/src/rust/crates/ain-evm-state/src/traits.rs +++ b/src/rust/crates/ain-evm-state/src/traits.rs @@ -4,3 +4,137 @@ pub trait PersistentState { where Self: Sized; } + +#[cfg(test)] +mod tests { + use super::*; + use crate::evm::EVMState; + use crate::traits::PersistentState; + use crate::tx_queue::TransactionQueueMap; + use ain_evm::{executor::AinExecutor, traits::Executor, transaction::SignedTx}; + use anyhow::anyhow; + use ethereum::{AccessList, Block, PartialHeader, TransactionV2}; + use evm::backend::MemoryAccount; + use evm::{ + backend::{MemoryBackend, MemoryVicinity}, + ExitReason, + }; + use hex::FromHex; + use primitive_types::{H160, H256, U256}; + use std::collections::BTreeMap; + use std::error::Error; + use std::fs::File; + use std::io::{Read, Write}; + use std::path::Path; + use std::sync::{Arc, RwLock}; + + fn create_account( + nonce: U256, + balance: U256, + code: Vec, + storage: BTreeMap, + ) -> MemoryAccount { + MemoryAccount { + nonce, + balance, + code, + storage, + } + } + + #[test] + fn test_load_non_existent_file() { + let state = EVMState::load_from_disk("non_existent_file.bin").unwrap(); + assert_eq!(state, EVMState::default()); + } + + #[test] + fn test_empty_file() { + let state = BTreeMap::new(); + let path = "empty_test.bin"; + + // Save to an empty file + state.save_to_disk(path).unwrap(); + + let new_state = EVMState::load_from_disk(path).unwrap(); + + assert_eq!(state, new_state); + } + + #[test] + fn test_invalid_file_format() { + let invalid_data = b"invalid_data"; + let path = "invalid_file_format.bin"; + + // Write invalid data to a file + let mut file = File::create(path).unwrap(); + file.write_all(invalid_data).unwrap(); + + let state = EVMState::load_from_disk(path); + + assert!(state.is_err()); + } + + #[test] + fn test_save_and_load_empty_backend() { + let path = "test_empty_backend.bin"; + let state = BTreeMap::new(); + + state.save_to_disk(path).unwrap(); + + let loaded_backend = EVMState::load_from_disk(path).unwrap(); + + assert_eq!(state, loaded_backend); + } + + #[test] + fn test_save_and_load_single_account() { + let path = "test_single_account.bin"; + let mut state = BTreeMap::new(); + + let account = create_account( + U256::from(1), + U256::from(1000), + vec![1, 2, 3], + BTreeMap::new(), + ); + let address = H160::from_low_u64_be(1); + state.insert(address, account); + + state.save_to_disk(path).unwrap(); + + let loaded_backend = EVMState::load_from_disk(path).unwrap(); + + assert_eq!(state, loaded_backend); + } + + #[test] + fn test_save_and_load_multiple_accounts() { + let path = "test_multiple_accounts.bin"; + let mut state = BTreeMap::new(); + + let account1 = create_account( + U256::from(1), + U256::from(1000), + vec![1, 2, 3], + BTreeMap::new(), + ); + let address1 = H160::from_low_u64_be(1); + state.insert(address1, account1); + + let account2 = create_account( + U256::from(2), + U256::from(2000), + vec![4, 5, 6], + BTreeMap::new(), + ); + let address2 = H160::from_low_u64_be(2); + state.insert(address2, account2); + + state.save_to_disk(path).unwrap(); + + let loaded_backend = EVMState::load_from_disk(path).unwrap(); + + assert_eq!(state, loaded_backend); + } +} diff --git a/src/rust/crates/ain-evm-state/src/tx_queue.rs b/src/rust/crates/ain-evm-state/src/tx_queue.rs index 5d1c262c568..fd6e089cdd7 100644 --- a/src/rust/crates/ain-evm-state/src/tx_queue.rs +++ b/src/rust/crates/ain-evm-state/src/tx_queue.rs @@ -1,3 +1,4 @@ +use primitive_types::{H160, U256}; use rand::Rng; use std::{ collections::HashMap, @@ -6,6 +7,8 @@ use std::{ use ain_evm::transaction::SignedTx; +use crate::evm::EVMState; + #[derive(Debug)] pub struct TransactionQueueMap { queues: RwLock>, @@ -18,64 +21,97 @@ impl TransactionQueueMap { } } - pub fn get_context(&self) -> u64 { + pub fn get_context(&self, state: EVMState) -> u64 { let mut rng = rand::thread_rng(); loop { let context = rng.gen(); let mut write_guard = self.queues.write().unwrap(); if !write_guard.contains_key(&context) { - write_guard.insert(context, TransactionQueue::new()); + write_guard.insert(context, TransactionQueue::new(state)); return context; } } } - pub fn clear(&self, index: u64) { - if let Some(queue) = self.queues.read().unwrap().get(&index) { + pub fn remove(&self, context_id: u64) -> Option { + self.queues.write().unwrap().remove(&context_id) + } + + pub fn clear(&self, context_id: u64) { + if let Some(queue) = self.queues.read().unwrap().get(&context_id) { queue.clear() } } - pub fn add_signed_tx(&self, index: u64, signed_tx: SignedTx) { - if let Some(queue) = self.queues.read().unwrap().get(&index) { + pub fn add_signed_tx(&self, context_id: u64, signed_tx: SignedTx) { + if let Some(queue) = self.queues.read().unwrap().get(&context_id) { queue.add_signed_tx(signed_tx) } } - pub fn drain_all(&self, index: u64) -> Vec { - match self.queues.read().unwrap().get(&index) { + pub fn drain_all(&self, context_id: u64) -> Vec { + match self.queues.read().unwrap().get(&context_id) { Some(queue) => queue.drain_all(), None => Vec::new(), } } - pub fn len(&self, index: u64) -> usize { - match self.queues.read().unwrap().get(&index) { + pub fn len(&self, context_id: u64) -> usize { + match self.queues.read().unwrap().get(&context_id) { Some(queue) => queue.len(), None => 0, } } + + pub fn add_balance(&self, context_id: u64, address: H160, value: U256) { + if let Some(queue) = self.queues.read().unwrap().get(&context_id) { + queue.add_balance(address, value) + } + } + + pub fn sub_balance( + &self, + context_id: u64, + address: H160, + value: U256, + ) -> Result<(), QueueError> { + if let Some(queue) = self.queues.read().unwrap().get(&context_id) { + queue.sub_balance(address, value) + } else { + Err(QueueError::NoSuchContext) + } + } + + pub fn state(&self, context_id: u64) -> Option { + if let Some(queue) = self.queues.read().unwrap().get(&context_id) { + Some(queue.state()) + } else { + None + } + } } #[derive(Debug)] pub struct TransactionQueue { - txs: Mutex>, + transactions: Mutex>, + state: RwLock, } impl TransactionQueue { - pub fn new() -> Self { + pub fn new(state: EVMState) -> Self { Self { - txs: Mutex::new(Vec::new()), + transactions: Mutex::new(Vec::new()), + state: RwLock::new(state), } } pub fn clear(&self) { - self.txs.lock().unwrap().clear() + self.transactions.lock().unwrap().clear() } pub fn drain_all(&self) -> Vec { - self.txs + self.transactions .lock() .unwrap() .drain(..) @@ -83,10 +119,48 @@ impl TransactionQueue { } pub fn add_signed_tx(&self, signed_tx: SignedTx) { - self.txs.lock().unwrap().push(signed_tx) + self.transactions.lock().unwrap().push(signed_tx) } pub fn len(&self) -> usize { - self.txs.lock().unwrap().len() + self.transactions.lock().unwrap().len() + } + + pub fn state(&self) -> EVMState { + self.state.read().unwrap().clone() + } + + pub fn add_balance(&self, address: H160, value: U256) { + let mut state = self.state.write().unwrap(); + let mut account = state.entry(address).or_default(); + account.balance = account.balance + value; + } + + pub fn sub_balance(&self, address: H160, value: U256) -> Result<(), QueueError> { + let mut state = self.state.write().unwrap(); + let mut account = state.get_mut(&address).unwrap(); + if account.balance > value { + account.balance = account.balance - value; + Ok(()) + } else { + Err(QueueError::InsufficientBalance) + } + } +} + +#[derive(Debug)] +pub enum QueueError { + NoSuchContext, + InsufficientBalance, +} + +impl std::fmt::Display for QueueError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + QueueError::NoSuchContext => write!(f, "No transaction queue for this context"), + QueueError::InsufficientBalance => write!(f, "Insufficient balance"), + } } } + +impl std::error::Error for QueueError {} diff --git a/src/rust/crates/ain-evm-state/tests/block.rs b/src/rust/crates/ain-evm-state/tests/block.rs new file mode 100644 index 00000000000..2ed46437b19 --- /dev/null +++ b/src/rust/crates/ain-evm-state/tests/block.rs @@ -0,0 +1,110 @@ +use ain_evm::transaction::SignedTx; +use primitive_types::U256; + +use ain_evm_state::handler::Handlers; + +#[test] +fn test_finalize_block_and_do_not_update_state() { + let handler = Handlers::new(); + let context = handler.evm.get_context(); + handler.evm.add_balance( + context, + "0x4a1080c5533cb89edc4b65013f08f78868e382de" + .parse() + .unwrap(), + U256::from_str_radix("100000000000000000000", 10).unwrap(), + ); + + let tx1: SignedTx = "f86b02830186a0830186a094a8f7c4c78c36e54c3950ad58dad24ca5e0191b2989056bc75e2d631000008025a0b0842b0c78dd7fc33584ec9a81ab5104fe70169878de188ba6c11fe7605e298aa0735dc483f625f17d68d1e1fae779b7160612628e6dde9eecf087892fe60bba4e".try_into().unwrap(); + println!("tx1 : {:#?}", tx1); + handler.evm.tx_queues.add_signed_tx(context, tx1.clone()); + + let old_state = handler.evm.state.read().unwrap(); + let _ = handler.evm.finalize_block(context, false).unwrap(); + + let new_state = handler.evm.state.read().unwrap(); + assert_eq!(*new_state, *old_state); +} + +#[test] +fn test_finalize_block_and_update_state() { + let handler = Handlers::new(); + let context = handler.evm.get_context(); + handler.evm.add_balance( + context, + "0x6745f998a96050bb9b0449e6bd4358138a519679" + .parse() + .unwrap(), + U256::from_str_radix("100000000000000000000", 10).unwrap(), + ); + + let tx1: SignedTx = "f86b02830186a0830186a094a8f7c4c78c36e54c3950ad58dad24ca5e0191b2989056bc75e2d631000008025a0b0842b0c78dd7fc33584ec9a81ab5104fe70169878de188ba6c11fe7605e298aa0735dc483f625f17d68d1e1fae779b7160612628e6dde9eecf087892fe60bba4e".try_into().unwrap(); + handler.evm.tx_queues.add_signed_tx(context, tx1.clone()); + + handler.evm.add_balance( + context, + "0xc0cd829081485e70348975d325fe5275140277bd" + .parse() + .unwrap(), + U256::from_str_radix("100000000000000000000", 10).unwrap(), + ); + let tx2: SignedTx = "f86b02830186a0830186a094a8f7c4c78c36e54c3950ad58dad24ca5e0191b2989056bc75e2d631000008025a01465e2d999c34b22bf4b8b5c9439918e46341f4f0da1b00a6b0479c541161d4aa074abe79c51bf57086e1e84b57ee483cbb2ecf30e8222bc0472436fabfc57dda8".try_into().unwrap(); + handler.evm.tx_queues.add_signed_tx(context, tx2.clone()); + + let tx3: SignedTx = "f86b02830186a0830186a094a8f7c4c78c36e54c3950ad58dad24ca5e0191b2989056bc75e2d631000008025a070b21a24cec13c0569099ee2f8221268103fd609646b73f7c9e85efeb7af5c8ea03d5de75bc12ce28a80f7c0401df6021cc82a334cb1c802c8b9d46223c5c8eb40".try_into().unwrap(); + handler.evm.tx_queues.add_signed_tx(context, tx3.clone()); + + assert_eq!(handler.evm.tx_queues.len(context), 3); + assert_eq!(handler.evm.tx_queues.len(handler.evm.get_context()), 0); + + let (block, failed_txs) = handler.evm.finalize_block(context, true).unwrap(); + assert_eq!( + block.transactions, + vec![tx1, tx2] + .into_iter() + .map(|t| t.transaction) + .collect::>() + ); + assert_eq!( + failed_txs, + vec![tx3] + .into_iter() + .map(|t| t.transaction) + .collect::>() + ); + + let state = handler.evm.state.read().unwrap(); + assert_eq!( + state + .get( + &"0xa8f7c4c78c36e54c3950ad58dad24ca5e0191b29" + .parse() + .unwrap() + ) + .unwrap() + .balance, + U256::from_str_radix("200000000000000000000", 10).unwrap() + ); + assert_eq!( + state + .get( + &"0x6745f998a96050bb9b0449e6bd4358138a519679" + .parse() + .unwrap() + ) + .unwrap() + .balance, + U256::from_str_radix("0", 10).unwrap() + ); + assert_eq!( + state + .get( + &"0xc0cd829081485e70348975d325fe5275140277bd" + .parse() + .unwrap() + ) + .unwrap() + .balance, + U256::from_str_radix("0", 10).unwrap() + ); +} diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 67a80432a75..1bb27f4d52d 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -156,7 +156,7 @@ std::shared_ptr LoadWallet(interfaces::Chain& chain, const WalletLocati return wallet; } -std::array GetKeyFromWallets(rust::Vec input) { +std::array GetKeyFromWallets(std::array input) { CKey key; CKeyID keyID; std::copy(input.begin(), input.end(), keyID.begin()); @@ -167,7 +167,7 @@ std::array GetKeyFromWallets(rust::Vec input) { } } - std::array result{}; + std::array result{}; std::copy(key.begin(), key.end(), result.begin()); return result; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index d976bded457..4a297c7689a 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include