diff --git a/src/defid.cpp b/src/defid.cpp index e7ee7a87a5..796d6f9f6d 100644 --- a/src/defid.cpp +++ b/src/defid.cpp @@ -67,8 +67,6 @@ static bool AppInit(int argc, char* argv[]) util::ThreadRename("init"); init_runtime(); - // Test linking to dummy address - evm_add_balance("0xf3088943Fa15Ff33D5C3Af9845dc65073Ae2bAc8", 100); // // Parameters diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index 936dca7c04..f682f0a315 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -771,6 +771,7 @@ Res CCustomTxVisitor::IsOnChainGovernanceEnabled() const { class CCustomTxApplyVisitor : public CCustomTxVisitor { uint64_t time; uint32_t txn; + uint64_t evmContext; public: CCustomTxApplyVisitor(const CTransaction &tx, @@ -779,11 +780,13 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor { CCustomCSView &mnview, const Consensus::Params &consensus, uint64_t time, - uint32_t txn) + uint32_t txn, + const uint64_t evmContext) : CCustomTxVisitor(tx, height, coins, mnview, consensus), time(time), - txn(txn) {} + txn(txn), + evmContext(evmContext) {} Res operator()(const CCreateMasterNodeMessage &obj) const { Require(CheckMasternodeCreationTx()); @@ -3862,7 +3865,9 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor { return Res::Err("evm tx failed to validate"); } - // TODO Execute TX + if (!evm_queue_tx(evmContext, HexStr(obj.evmTx))) { + return Res::Err("evm tx failed to queue"); + } return Res::Ok(); } @@ -3945,12 +3950,13 @@ Res CustomTxVisit(CCustomCSView &mnview, const Consensus::Params &consensus, const CCustomTxMessage &txMessage, uint64_t time, - uint32_t txn) { + uint32_t txn, + const uint64_t evmContext) { if (IsDisabledTx(height, tx, consensus)) { return Res::ErrCode(CustomTxErrCodes::Fatal, "Disabled custom transaction"); } try { - return std::visit(CCustomTxApplyVisitor(tx, height, coins, mnview, consensus, time, txn), txMessage); + return std::visit(CCustomTxApplyVisitor(tx, height, coins, mnview, consensus, time, txn, evmContext), txMessage); } catch (const std::bad_variant_access &e) { return Res::Err(e.what()); } catch (...) { @@ -4035,7 +4041,8 @@ Res ApplyCustomTx(CCustomCSView &mnview, uint32_t height, uint64_t time, uint256 *canSpend, - uint32_t txn) { + uint32_t txn, + const uint64_t evmContext) { auto res = Res::Ok(); if (tx.IsCoinBase() && height > 0) { // genesis contains custom coinbase txs return res; diff --git a/src/masternodes/mn_checks.h b/src/masternodes/mn_checks.h index be1d644615..c89542e928 100644 --- a/src/masternodes/mn_checks.h +++ b/src/masternodes/mn_checks.h @@ -466,7 +466,8 @@ Res ApplyCustomTx(CCustomCSView &mnview, uint32_t height, uint64_t time = 0, uint256 *canSpend = nullptr, - uint32_t txn = 0); + uint32_t txn = 0, + const uint64_t evmContext = 0); Res CustomTxVisit(CCustomCSView &mnview, const CCoinsViewCache &coins, const CTransaction &tx, @@ -474,7 +475,8 @@ Res CustomTxVisit(CCustomCSView &mnview, const Consensus::Params &consensus, const CCustomTxMessage &txMessage, uint64_t time, - uint32_t txn = 0); + uint32_t txn = 0, + const uint64_t evmContext = 0); ResVal ApplyAnchorRewardTx(CCustomCSView &mnview, const CTransaction &tx, int height, diff --git a/src/masternodes/rpc_evm.cpp b/src/masternodes/rpc_evm.cpp index 31acd1568c..1bdd020873 100644 --- a/src/masternodes/rpc_evm.cpp +++ b/src/masternodes/rpc_evm.cpp @@ -57,7 +57,7 @@ UniValue evmtx(const JSONRPCRequest& request) { const uint64_t chainID{1}; const arith_uint256 nonceParam = request.params[1].get_int64(); - const auto nonce = ArithToUint256(nonceParam).ToArray(); + const auto nonce = ArithToUint256(nonceParam); arith_uint256 gasPriceArith = request.params[2].get_int64(); // Price as GWei gasPriceArith *= WEI_IN_GWEI; // Convert to Wei @@ -79,7 +79,7 @@ UniValue evmtx(const JSONRPCRequest& request) { } const arith_uint256 valueParam = AmountFromValue(request.params[5]); - const auto value = ArithToUint256(valueParam * CAMOUNT_TO_WEI * WEI_IN_GWEI).ToArray(); + const auto value = ArithToUint256(valueParam * CAMOUNT_TO_WEI * WEI_IN_GWEI); rust::Vec input{}; if (!request.params[6].isNull()) { @@ -96,7 +96,7 @@ UniValue evmtx(const JSONRPCRequest& request) { std::array privKey{}; std::copy(key.begin(), key.end(), privKey.begin()); - const auto signedTx = create_and_sign_tx(chainID, nonce, gasPrice.ToArrayReversed(), gasLimit.ToArrayReversed(), to, value, input, privKey); + const auto signedTx = create_and_sign_tx(chainID, nonce.ToArrayReversed(), gasPrice.ToArrayReversed(), gasLimit.ToArrayReversed(), to, value.ToArrayReversed(), input, privKey); std::vector evmTx(signedTx.size()); std::copy(signedTx.begin(), signedTx.end(), evmTx.begin()); diff --git a/src/miner.cpp b/src/miner.cpp index 0ceef2d3d2..ab7a85cc7f 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -234,12 +235,20 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc timeOrdering = false; } + const auto evmContext = evm_get_context(); + if (timeOrdering) { - addPackageTxs(nPackagesSelected, nDescendantsUpdated, nHeight, mnview); + addPackageTxs(nPackagesSelected, nDescendantsUpdated, nHeight, mnview, evmContext); } else { - addPackageTxs(nPackagesSelected, nDescendantsUpdated, nHeight, mnview); + addPackageTxs(nPackagesSelected, nDescendantsUpdated, nHeight, mnview, evmContext); } + // TODO Get failed TXs and try to restore to mempool + const auto rustHeader = evm_finalise(evmContext, false); + + std::vector evmHeader{}; + evmHeader.resize(rustHeader.size()); + std::copy(rustHeader.begin(), rustHeader.end(), evmHeader.begin()); // TXs for the creationTx field in new tokens created via token split if (nHeight >= chainparams.GetConsensus().FortCanningCrunchHeight) { @@ -315,6 +324,16 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc coinbaseTx.vout[0].nValue = CalculateCoinbaseReward(blockReward, consensus.dist.masternode); } + if (nHeight >= consensus.NextNetworkUpgradeHeight && !evmHeader.empty()) { + const auto headerIndex = coinbaseTx.vout.size(); + coinbaseTx.vout.resize(headerIndex + 1); + coinbaseTx.vout[headerIndex].nValue = 0; + + CScript script; + script << OP_RETURN << evmHeader; + coinbaseTx.vout[headerIndex].scriptPubKey = script; + } + LogPrint(BCLog::STAKING, "%s: post Eunos logic. Block reward %d Miner share %d foundation share %d\n", __func__, blockReward, coinbaseTx.vout[0].nValue, foundationValue); } @@ -507,7 +526,7 @@ void BlockAssembler::SortForBlock(const CTxMemPool::setEntries& package, std::ve // mapModifiedTxs with the next transaction in the mempool to decide what // transaction package to work on next. template -void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated, int nHeight, CCustomCSView &view) +void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated, int nHeight, CCustomCSView &view, const uint64_t evmContext) { // mapModifiedTx will store sorted packages after they are modified // because some of their txs are already in the block @@ -655,7 +674,7 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda // Only check custom TXs if (txType != CustomTxType::None) { - auto res = ApplyCustomTx(view, coins, tx, chainparams.GetConsensus(), nHeight, pblock->nTime); + const auto res = ApplyCustomTx(view, coins, tx, chainparams.GetConsensus(), nHeight, pblock->nTime, nullptr, 0, evmContext); // Not okay invalidate, undo and skip if (!res.ok) { diff --git a/src/miner.h b/src/miner.h index 2a9a29c5d3..a00b2784c1 100644 --- a/src/miner.h +++ b/src/miner.h @@ -193,7 +193,7 @@ class BlockAssembler * Increments nPackagesSelected / nDescendantsUpdated with corresponding * statistics from the package selection (for logging statistics). */ template - void addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated, int nHeight, CCustomCSView &view) EXCLUSIVE_LOCKS_REQUIRED(mempool.cs); + void addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated, int nHeight, CCustomCSView &view, const uint64_t evmContext) EXCLUSIVE_LOCKS_REQUIRED(mempool.cs); // helper functions for addPackageTxs() /** Remove confirmed (inBlock) entries from given set */ diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index e83b5cf7e4..ff354c6303 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -361,6 +361,10 @@ static const CRPCConvertParam vRPCConvertParams[] = { "listgovproposalvotes", 3, "pagination" }, { "listgovproposals", 2, "cycle" }, { "listgovproposals", 3, "pagination" }, + { "evmtx", 1, "nonce" }, + { "evmtx", 2, "gasPrice" }, + { "evmtx", 3, "gasLimit" }, + { "evmtx", 5, "value" }, }; // clang-format on diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index b8f1f39455..3a97e9a49e 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -16,6 +16,7 @@ name = "ain-evm" version = "0.1.0" dependencies = [ "ain-utils", + "anyhow", "ethereum", "evm", "hex", @@ -37,6 +38,7 @@ dependencies = [ "cxx", "cxx-gen", "proc-macro2", + "rlp", "substrate-build-script-utils", ] @@ -67,6 +69,8 @@ dependencies = [ "evm", "hex", "primitive-types", + "rand", + "rlp", ] [[package]] diff --git a/src/rust/crates/ain-evm-ffi/Cargo.toml b/src/rust/crates/ain-evm-ffi/Cargo.toml index d716b2dc0f..7a8f0a7aa7 100644 --- a/src/rust/crates/ain-evm-ffi/Cargo.toml +++ b/src/rust/crates/ain-evm-ffi/Cargo.toml @@ -15,6 +15,8 @@ ain-evm-runtime = { path = "../ain-evm-runtime" } ain-evm = { path = "../ain-evm" } ain-grpc = { path = "../ain-grpc" } +rlp = "0.5.2" + # Build cxx = "1.0" diff --git a/src/rust/crates/ain-evm-ffi/src/lib.rs b/src/rust/crates/ain-evm-ffi/src/lib.rs index ba5b3b2e55..a171e53429 100644 --- a/src/rust/crates/ain-evm-ffi/src/lib.rs +++ b/src/rust/crates/ain-evm-ffi/src/lib.rs @@ -7,10 +7,15 @@ use std::error::Error; #[cxx::bridge] mod ffi { extern "Rust" { - fn evm_add_balance(address: &str, amount: i64) -> Result<()>; - fn evm_sub_balance(address: &str, amount: i64) -> Result<()>; + fn evm_add_balance(address: &str, amount: [u8; 32]) -> Result<()>; + fn evm_sub_balance(address: &str, amount: [u8; 32]) -> Result<()>; fn evm_validate_raw_tx(tx: &str) -> Result; + fn evm_get_context() -> u64; + fn evm_discard_context(context: u64); + fn evm_queue_tx(context: u64, raw_tx: &str) -> Result; + fn evm_finalise(context: u64, update_state: bool) -> Result>; + fn init_runtime(); fn start_servers(json_addr: &str, grpc_addr: &str) -> Result<()>; fn stop_runtime(); @@ -28,12 +33,12 @@ mod ffi { } } -pub fn evm_add_balance(address: &str, amount: i64) -> Result<(), Box> { - RUNTIME.evm.add_balance(address, amount) +pub fn evm_add_balance(address: &str, amount: [u8; 32]) -> Result<(), Box> { + RUNTIME.evm.add_balance(address, amount.into()) } -pub fn evm_sub_balance(address: &str, amount: i64) -> Result<(), Box> { - RUNTIME.evm.sub_balance(address, amount) +pub fn evm_sub_balance(address: &str, amount: [u8; 32]) -> Result<(), Box> { + RUNTIME.evm.sub_balance(address, amount.into()) } pub fn evm_validate_raw_tx(tx: &str) -> Result> { @@ -42,3 +47,25 @@ pub fn evm_validate_raw_tx(tx: &str) -> Result> { Err(_) => Ok(false), } } + +pub fn evm_get_context() -> u64 { + RUNTIME.evm.get_context() +} + +fn evm_discard_context(context: u64) { + // TODO discard + RUNTIME.evm.discard_context(context) +} + +fn evm_queue_tx(context: u64, raw_tx: &str) -> Result> { + match RUNTIME.evm.queue_tx(context, raw_tx) { + Ok(_) => Ok(true), + Err(_) => Ok(false), + } +} + +use rlp::Encodable; +fn evm_finalise(context: u64, update_state: bool) -> Result, Box> { + let (block, _failed_tx) = RUNTIME.evm.finalize_block(context, update_state)?; + Ok(block.header.rlp_bytes().into()) +} diff --git a/src/rust/crates/ain-evm-state/Cargo.toml b/src/rust/crates/ain-evm-state/Cargo.toml index 3617ec8126..cedbbb33dd 100644 --- a/src/rust/crates/ain-evm-state/Cargo.toml +++ b/src/rust/crates/ain-evm-state/Cargo.toml @@ -10,5 +10,7 @@ bincode = "1.3.3" ethereum = "0.14.0" hex = "0.4.3" anyhow = "1.0" +rand = "0.8.5" +rlp = "0.5.2" ain-evm = { path = "../ain-evm" } diff --git a/src/rust/crates/ain-evm-state/src/handler.rs b/src/rust/crates/ain-evm-state/src/handler.rs index 646fe76ae8..1a4fbcc5e3 100644 --- a/src/rust/crates/ain-evm-state/src/handler.rs +++ b/src/rust/crates/ain-evm-state/src/handler.rs @@ -1,33 +1,29 @@ -use ain_evm::transaction::SignedTx; +use ain_evm::{executor::AinExecutor, traits::Executor, transaction::SignedTx}; use anyhow::anyhow; -use ethereum::{AccessList, TransactionAction, TransactionV2}; +use ethereum::{AccessList, Block, PartialHeader, TransactionV2}; use evm::{ - backend::{Basic, MemoryAccount, MemoryBackend, MemoryVicinity}, - executor::stack::{MemoryStackState, StackExecutor, StackSubstateMetadata}, - ExitReason, Memory, + backend::{MemoryBackend, MemoryVicinity}, + ExitReason, }; use hex::FromHex; use primitive_types::{H160, H256, U256}; use std::error::Error; use std::sync::{Arc, RwLock}; -use std::{collections::BTreeMap, sync::Mutex}; -use crate::traits::PersistentState; -use crate::{EVMState, CONFIG, EVM_STATE_PATH, GAS_LIMIT}; +use crate::tx_queue::TransactionQueueMap; +use crate::EVMState; #[derive(Clone, Debug)] pub struct EVMHandler { pub state: Arc>, - pub tx_queue: Arc>>, + pub tx_queues: Arc, } impl EVMHandler { pub fn new() -> Self { Self { - state: Arc::new(RwLock::new( - EVMState::load_from_disk(EVM_STATE_PATH).unwrap(), - )), - tx_queue: Arc::new(Mutex::new(Vec::new())), + state: Arc::new(RwLock::new(EVMState::new())), + tx_queues: Arc::new(TransactionQueueMap::new()), } } @@ -41,50 +37,17 @@ impl EVMHandler { access_list: AccessList, ) -> (ExitReason, Vec) { // TODO Add actual gas, chain_id, block_number from header - let vicinity = MemoryVicinity { - gas_price: U256::zero(), - origin: caller.unwrap_or_default(), - block_hashes: Vec::new(), - block_number: Default::default(), - block_coinbase: Default::default(), - block_timestamp: Default::default(), - block_difficulty: Default::default(), - block_gas_limit: Default::default(), - chain_id: U256::one(), - block_base_fee_per_gas: U256::zero(), - }; + let vicinity = get_vicinity(caller, None); + let state = self.state.read().unwrap().clone(); let backend = MemoryBackend::new(&vicinity, state); - - let metadata = StackSubstateMetadata::new(GAS_LIMIT, &CONFIG); - let state = MemoryStackState::new(metadata, &backend); - let precompiles = BTreeMap::new(); // TODO Add precompile crate - let mut executor = StackExecutor::new_with_precompiles(state, &CONFIG, &precompiles); - let access_list = access_list - .into_iter() - .map(|x| (x.address, x.storage_keys)) - .collect::>(); - match to { - Some(address) => executor.transact_call( - caller.unwrap_or_default(), - address, - value, - data.to_vec(), - gas_limit, - access_list.into(), - ), - None => executor.transact_create( - caller.unwrap_or_default(), - value, - data.to_vec(), - gas_limit, - access_list, - ), - } + let tx_response = + AinExecutor::new(backend).call(caller, to, value, data, gas_limit, access_list, false); + (tx_response.exit_reason, tx_response.data) } // TODO wrap in EVM transaction and dryrun with evm_call - pub fn add_balance(&self, address: &str, value: i64) -> Result<(), Box> { + 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(); @@ -92,42 +55,28 @@ impl EVMHandler { Ok(()) } - pub fn sub_balance(&self, address: &str, value: i64) -> Result<(), Box> { + 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.into() { + if account.balance > value { account.balance = account.balance - value; } Ok(()) } - fn get_account(&self, address: &H160) -> MemoryAccount { - self.state - .read() - .unwrap() - .get(&address) - .map(|account| account.clone()) - .unwrap_or_else(|| MemoryAccount { - nonce: U256::default(), - balance: U256::default(), - storage: BTreeMap::new(), - code: Vec::new(), - }) - } - - pub fn validate_raw_tx(&self, tx: &str) -> Result<(), Box> { + pub fn validate_raw_tx(&self, tx: &str) -> Result> { let buffer = >::from_hex(tx)?; let tx: TransactionV2 = ethereum::EnvelopedDecodable::decode(&buffer) .map_err(|_| anyhow!("Error: decoding raw tx to TransactionV2"))?; // TODO Validate gas limit and chain_id - let sign_tx: SignedTx = tx.try_into()?; + let signed_tx: SignedTx = tx.try_into()?; // TODO validate account nonce and balance to pay gas - // let account = self.get_account(&sign_tx.sender); - // if account.nonce >= sign_tx.nonce() { + // let account = self.get_account(&signed_tx.sender); + // if account.nonce >= signed_tx.nonce() { // return Err(anyhow!("Invalid nonce").into()); // } // if account.balance < MIN_GAS { @@ -135,15 +84,206 @@ impl EVMHandler { // } match self.call( - Some(sign_tx.sender), - sign_tx.to(), - sign_tx.value(), - sign_tx.data(), - sign_tx.gas_limit().as_u64(), - sign_tx.access_list(), + Some(signed_tx.sender), + signed_tx.to(), + signed_tx.value(), + signed_tx.data(), + signed_tx.gas_limit().as_u64(), + signed_tx.access_list(), ) { - (exit_reason, _) if exit_reason.is_succeed() => Ok(()), + (exit_reason, _) if exit_reason.is_succeed() => Ok(signed_tx), _ => Err(anyhow!("Error calling EVM").into()), } } + + pub fn get_context(&self) -> u64 { + self.tx_queues.get_context() + } + + pub fn discard_context(&self, context: u64) { + self.tx_queues.clear(context) + } + + pub fn queue_tx(&self, context: u64, raw_tx: &str) -> Result<(), Box> { + let signed_tx = self.validate_raw_tx(raw_tx)?; + self.tx_queues.add_signed_tx(context, signed_tx); + Ok(()) + } + + pub fn finalize_block( + &self, + context: u64, + update_state: bool, + ) -> Result<(Block, Vec), Box> { + 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 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); + } else { + failed_tx_hashes.push(signed_tx.transaction) + } + } + + if update_state { + let mut state = self.state.write().unwrap(); + *state = executor.backend().state().clone(); + } + + let block = Block::new( + PartialHeader { + parent_hash: Default::default(), + beneficiary: Default::default(), + state_root: Default::default(), + receipts_root: Default::default(), + logs_bloom: Default::default(), + difficulty: Default::default(), + number: Default::default(), + gas_limit: Default::default(), + gas_used: Default::default(), + timestamp: Default::default(), + extra_data: Default::default(), + mix_hash: Default::default(), + nonce: Default::default(), + }, + tx_hashes, + Vec::new(), + ); + Ok((block, failed_tx_hashes)) + } +} + +// TBD refine what vicinity we need. gas_price and origin only ? +fn get_vicinity(origin: Option, gas_price: Option) -> MemoryVicinity { + MemoryVicinity { + gas_price: gas_price.unwrap_or_else(|| U256::MAX), + origin: origin.unwrap_or_default(), + block_hashes: Vec::new(), + block_number: Default::default(), + block_coinbase: Default::default(), + block_timestamp: Default::default(), + block_difficulty: Default::default(), + block_gas_limit: U256::MAX, + chain_id: U256::one(), + block_base_fee_per_gas: U256::MAX, + } +} + +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/lib.rs b/src/rust/crates/ain-evm-state/src/lib.rs index 2ca2035015..c3908a5d1a 100644 --- a/src/rust/crates/ain-evm-state/src/lib.rs +++ b/src/rust/crates/ain-evm-state/src/lib.rs @@ -1,18 +1,17 @@ pub mod handler; pub mod traits; +pub mod tx_queue; use std::collections::BTreeMap; use crate::traits::PersistentState; pub use evm::backend::Backend; use evm::backend::MemoryAccount; -use evm::Config; use primitive_types::H160; use std::fs::File; use std::io::{Read, Write}; use std::path::Path; -pub static CONFIG: Config = Config::london(); pub static GAS_LIMIT: u64 = u64::MAX; pub static EVM_STATE_PATH: &str = "evm_state.bin"; @@ -60,8 +59,8 @@ mod tests { #[test] fn test_load_non_existent_file() { - let state = EVMState::load_from_disk("non_existent_file.bin"); - assert!(state.is_err()); + let state = EVMState::load_from_disk("non_existent_file.bin").unwrap(); + assert_eq!(state, EVMState::default()); } #[test] diff --git a/src/rust/crates/ain-evm-state/src/tx_queue.rs b/src/rust/crates/ain-evm-state/src/tx_queue.rs new file mode 100644 index 0000000000..5d1c262c56 --- /dev/null +++ b/src/rust/crates/ain-evm-state/src/tx_queue.rs @@ -0,0 +1,92 @@ +use rand::Rng; +use std::{ + collections::HashMap, + sync::{Mutex, RwLock}, +}; + +use ain_evm::transaction::SignedTx; + +#[derive(Debug)] +pub struct TransactionQueueMap { + queues: RwLock>, +} + +impl TransactionQueueMap { + pub fn new() -> Self { + TransactionQueueMap { + queues: RwLock::new(HashMap::new()), + } + } + + pub fn get_context(&self) -> 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()); + return context; + } + } + } + + pub fn clear(&self, index: u64) { + if let Some(queue) = self.queues.read().unwrap().get(&index) { + queue.clear() + } + } + + pub fn add_signed_tx(&self, index: u64, signed_tx: SignedTx) { + if let Some(queue) = self.queues.read().unwrap().get(&index) { + queue.add_signed_tx(signed_tx) + } + } + + pub fn drain_all(&self, index: u64) -> Vec { + match self.queues.read().unwrap().get(&index) { + Some(queue) => queue.drain_all(), + None => Vec::new(), + } + } + + pub fn len(&self, index: u64) -> usize { + match self.queues.read().unwrap().get(&index) { + Some(queue) => queue.len(), + None => 0, + } + } +} + +#[derive(Debug)] +pub struct TransactionQueue { + txs: Mutex>, +} + +impl TransactionQueue { + pub fn new() -> Self { + Self { + txs: Mutex::new(Vec::new()), + } + } + + pub fn clear(&self) { + self.txs.lock().unwrap().clear() + } + + pub fn drain_all(&self) -> Vec { + self.txs + .lock() + .unwrap() + .drain(..) + .collect::>() + } + + pub fn add_signed_tx(&self, signed_tx: SignedTx) { + self.txs.lock().unwrap().push(signed_tx) + } + + pub fn len(&self) -> usize { + self.txs.lock().unwrap().len() + } +} diff --git a/src/rust/crates/ain-evm/Cargo.toml b/src/rust/crates/ain-evm/Cargo.toml index cac6bc185a..30606f0cc4 100644 --- a/src/rust/crates/ain-evm/Cargo.toml +++ b/src/rust/crates/ain-evm/Cargo.toml @@ -12,5 +12,6 @@ libsecp256k1 = "0.7.1" ethereum = "0.14.0" sha3 = "0.10.6" hex = "0.4.3" +anyhow = "1.0" ain-utils = { path = "../ain-utils" } diff --git a/src/rust/crates/ain-evm/src/executor.rs b/src/rust/crates/ain-evm/src/executor.rs new file mode 100644 index 0000000000..910b0448ea --- /dev/null +++ b/src/rust/crates/ain-evm/src/executor.rs @@ -0,0 +1,106 @@ +use std::collections::BTreeMap; + +use crate::{traits::Executor, transaction::SignedTx}; +use evm::{ + backend::{ApplyBackend, Backend}, + executor::stack::{MemoryStackState, StackExecutor, StackSubstateMetadata}, + Config, ExitReason, +}; + +use ethereum::{AccessList, Log}; + +use primitive_types::{H160, U256}; + +#[derive(Debug)] +pub struct AinExecutor { + backend: B, +} + +impl AinExecutor +where + B: Backend + ApplyBackend, +{ + pub fn new(backend: B) -> Self { + Self { backend } + } + + pub fn backend(&self) -> &B { + &self.backend + } +} + +impl Executor for AinExecutor +where + B: Backend + ApplyBackend, +{ + const CONFIG: Config = Config::london(); + + fn call( + &mut self, + caller: Option, + to: Option, + value: U256, + data: &[u8], + gas_limit: u64, + access_list: AccessList, + apply: bool, + ) -> TxResponse { + let metadata = StackSubstateMetadata::new(gas_limit, &Self::CONFIG); + let state = MemoryStackState::new(metadata, &self.backend); + let precompiles = BTreeMap::new(); // TODO Add precompile crate + let mut executor = StackExecutor::new_with_precompiles(state, &Self::CONFIG, &precompiles); + let access_list = access_list + .into_iter() + .map(|x| (x.address, x.storage_keys)) + .collect::>(); + let (exit_reason, data) = match to { + Some(address) => executor.transact_call( + caller.unwrap_or_default(), + address, + value, + data.to_vec(), + gas_limit, + access_list.into(), + ), + None => executor.transact_create( + caller.unwrap_or_default(), + value, + data.to_vec(), + gas_limit, + access_list, + ), + }; + + let (values, logs) = executor.into_state().deconstruct(); + let logs = logs.into_iter().collect::>(); + if apply && exit_reason.is_succeed() { + self.backend.apply(values, logs.clone(), true); + } + + TxResponse { + exit_reason, + data, + logs: logs, + } + } + + fn exec(&mut self, signed_tx: &SignedTx) -> TxResponse { + let apply = true; + self.call( + Some(signed_tx.sender), + signed_tx.to(), + signed_tx.value(), + signed_tx.data(), + signed_tx.gas_limit().as_u64(), + signed_tx.access_list(), + apply, + ) + } +} + +#[derive(Debug)] +pub struct TxResponse { + pub exit_reason: ExitReason, + pub data: Vec, + pub logs: Vec, +} diff --git a/src/rust/crates/ain-evm/src/lib.rs b/src/rust/crates/ain-evm/src/lib.rs index 5866856b58..5ee5a4abf1 100644 --- a/src/rust/crates/ain-evm/src/lib.rs +++ b/src/rust/crates/ain-evm/src/lib.rs @@ -1,3 +1,5 @@ +pub mod executor; +pub mod traits; pub mod transaction; use ethereum::{EnvelopedEncodable, TransactionAction, TransactionSignature}; diff --git a/src/rust/crates/ain-evm/src/traits.rs b/src/rust/crates/ain-evm/src/traits.rs new file mode 100644 index 0000000000..b983e47fcc --- /dev/null +++ b/src/rust/crates/ain-evm/src/traits.rs @@ -0,0 +1,22 @@ +use ethereum::AccessList; +use evm::Config; +use primitive_types::{H160, U256}; + +use crate::{executor::TxResponse, transaction::SignedTx}; + +pub trait Executor { + const CONFIG: Config = Config::london(); + + fn call( + &mut self, + caller: Option, + to: Option, + value: U256, + data: &[u8], + gas_limit: u64, + access_list: AccessList, + apply: bool, + ) -> TxResponse; + + fn exec(&mut self, tx: &SignedTx) -> TxResponse; +} diff --git a/src/rust/crates/ain-evm/src/transaction.rs b/src/rust/crates/ain-evm/src/transaction.rs index d11c7fddb1..363e26edba 100644 --- a/src/rust/crates/ain-evm/src/transaction.rs +++ b/src/rust/crates/ain-evm/src/transaction.rs @@ -123,6 +123,21 @@ impl TryFrom for SignedTx { } } +use anyhow::anyhow; +use hex::FromHex; + +impl TryFrom<&str> for SignedTx { + type Error = Box; + + fn try_from(src: &str) -> Result { + let buffer = >::from_hex(src)?; + let tx: TransactionV2 = ethereum::EnvelopedDecodable::decode(&buffer) + .map_err(|_| anyhow!("Error: decoding raw tx to TransactionV2"))?; + + tx.try_into().map_err(|e: libsecp256k1::Error| e.into()) + } +} + impl SignedTx { pub fn nonce(&self) -> U256 { match &self.transaction { @@ -133,11 +148,7 @@ impl SignedTx { } pub fn to(&self) -> Option { - let action = match &self.transaction { - TransactionV2::Legacy(t) => t.action, - TransactionV2::EIP2930(t) => t.action, - TransactionV2::EIP1559(t) => t.action, - }; + let action = self.action(); match action { TransactionAction::Call(to) => Some(to), TransactionAction::Create => None, @@ -176,6 +187,14 @@ impl SignedTx { } } + pub fn gas_price(&self) -> U256 { + match &self.transaction { + TransactionV2::Legacy(tx) => tx.gas_price, + TransactionV2::EIP2930(tx) => tx.gas_price, + TransactionV2::EIP1559(tx) => tx.max_fee_per_gas.min(tx.max_priority_fee_per_gas), // TODO verify calculation + } + } + pub fn data(&self) -> &[u8] { match &self.transaction { TransactionV2::Legacy(tx) => tx.input.as_ref(), diff --git a/src/rust/crates/ain-utils/src/lib.rs b/src/rust/crates/ain-utils/src/lib.rs index 7f31f9d456..334064ac6e 100644 --- a/src/rust/crates/ain-utils/src/lib.rs +++ b/src/rust/crates/ain-utils/src/lib.rs @@ -10,7 +10,12 @@ pub fn recover_public_key( recovery_id: u8, ) -> Result { let msg = libsecp256k1::Message::parse(hash.as_fixed_bytes()); - let signature = Signature::parse_standard_slice(&[r.as_bytes(), &s.as_bytes()].concat())?; + + let mut standard_slice = [0u8; 64]; + standard_slice[..32].copy_from_slice(r.as_fixed_bytes()); + standard_slice[32..].copy_from_slice(s.as_fixed_bytes()); + let signature = Signature::parse_standard_slice(&standard_slice)?; + let recovery_id = RecoveryId::parse(recovery_id)?; libsecp256k1::recover(&msg, &signature, &recovery_id) } diff --git a/src/validation.cpp b/src/validation.cpp index 2f72c313cd..80001c5f68 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -2289,7 +2290,7 @@ static void LogApplyCustomTx(const CTransaction &tx, const int64_t start) { * Validity checks that depend on the UTXO set are also done; ConnectBlock () * can fail if those validity checks fail (among other reasons). */ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, - CCoinsViewCache& view, CCustomCSView& mnview, const CChainParams& chainparams, bool & rewardedAnchors, bool fJustCheck) + CCoinsViewCache& view, CCustomCSView& mnview, const CChainParams& chainparams, bool & rewardedAnchors, bool fJustCheck, const int64_t evmContext) { AssertLockHeld(cs_main); assert(pindex); @@ -2633,7 +2634,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl } const auto applyCustomTxTime = GetTimeMicros(); - const auto res = ApplyCustomTx(accountsView, view, tx, chainparams.GetConsensus(), pindex->nHeight, pindex->GetBlockTime(), nullptr, i); + const auto res = ApplyCustomTx(accountsView, view, tx, chainparams.GetConsensus(), pindex->nHeight, pindex->GetBlockTime(), nullptr, i, evmContext); LogApplyCustomTx(tx, applyCustomTxTime); if (!res.ok && (res.code & CustomTxErrCodes::Fatal)) { if (pindex->nHeight >= chainparams.GetConsensus().EunosHeight) { @@ -2833,6 +2834,8 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl } mnview.SetLastHeight(pindex->nHeight); + evm_finalise(evmContext, true); + auto &checkpoints = chainparams.Checkpoints().mapCheckpoints; auto it = checkpoints.lower_bound(pindex->nHeight); if (it != checkpoints.begin()) { @@ -3275,9 +3278,11 @@ bool CChainState::ConnectTip(CValidationState& state, const CChainParams& chainp CCoinsViewCache view(&CoinsTip()); CCustomCSView mnview(*pcustomcsview, paccountHistoryDB.get(), pburnHistoryDB.get(), pvaultHistoryDB.get()); bool rewardedAnchors{}; - bool rv = ConnectBlock(blockConnecting, state, pindexNew, view, mnview, chainparams, rewardedAnchors); + const auto evmContext = evm_get_context(); + bool rv = ConnectBlock(blockConnecting, state, pindexNew, view, mnview, chainparams, rewardedAnchors, false, evmContext); GetMainSignals().BlockChecked(blockConnecting, state); if (!rv) { + evm_discard_context(evmContext); if (state.IsInvalid()) { InvalidBlockFound(pindexNew, state); } diff --git a/src/validation.h b/src/validation.h index 750ea2f3fe..2cefd2bd2d 100644 --- a/src/validation.h +++ b/src/validation.h @@ -734,7 +734,7 @@ class CChainState { // Block (dis)connection on a given view: DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view, CCustomCSView& cache, std::vector & disconnectedAnchorConfirms); bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, - CCoinsViewCache& view, CCustomCSView& cache, const CChainParams& chainparams, bool & rewardedAnchors, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + CCoinsViewCache& view, CCustomCSView& cache, const CChainParams& chainparams, bool & rewardedAnchors, bool fJustCheck = false, const int64_t evmContext = 0) EXCLUSIVE_LOCKS_REQUIRED(cs_main); // Apply the effects of a block disconnection on the UTXO set. bool DisconnectTip(CValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index f32293dbf4..67a80432a7 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -156,6 +156,23 @@ std::shared_ptr LoadWallet(interfaces::Chain& chain, const WalletLocati return wallet; } +std::array GetKeyFromWallets(rust::Vec input) { + CKey key; + CKeyID keyID; + std::copy(input.begin(), input.end(), keyID.begin()); + + for (const auto &wallet : GetWallets()) { + if (wallet->GetKey(keyID, key)) { + break; + } + } + + std::array result{}; + std::copy(key.begin(), key.end(), result.begin()); + + return result; +} + std::shared_ptr LoadWallet(interfaces::Chain& chain, const std::string& name, std::string& error, std::string& warning) { return LoadWallet(chain, WalletLocation(name), error, warning); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 14e93839c0..d976bded45 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include