From 1a8d09b15502937df5c303ef3131ce7c6f46fee3 Mon Sep 17 00:00:00 2001 From: jouzo Date: Mon, 5 Jun 2023 13:09:19 +0200 Subject: [PATCH 01/20] Return nonce on evm_prevalidate_raw_tx --- lib/ain-rs-exports/src/lib.rs | 40 +++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/lib/ain-rs-exports/src/lib.rs b/lib/ain-rs-exports/src/lib.rs index 558cb699dd..409b14763c 100644 --- a/lib/ain-rs-exports/src/lib.rs +++ b/lib/ain-rs-exports/src/lib.rs @@ -36,6 +36,8 @@ pub mod ffi { extern "Rust" { fn evm_get_balance(address: &str, block_number: [u8; 32]) -> Result; + fn evm_get_nonce(address: &str, block_number: [u8; 32]) -> Result; + fn evm_add_balance( context: u64, address: &str, @@ -48,11 +50,13 @@ pub mod ffi { amount: [u8; 32], native_tx_hash: [u8; 32], ) -> Result; - fn evm_prevalidate_raw_tx(tx: &str) -> Result; + + fn evm_prevalidate_raw_tx(tx: &str) -> Result; fn evm_get_context() -> u64; fn evm_discard_context(context: u64) -> Result<()>; fn evm_queue_tx(context: u64, raw_tx: &str, native_tx_hash: [u8; 32]) -> Result; + fn evm_finalize( context: u64, update_state: bool, @@ -142,6 +146,30 @@ pub fn evm_get_balance(address: &str, block_number: [u8; 32]) -> Result Result> { + let account = address.parse()?; + let nonce = RUNTIME + .handlers + .evm + .get_nonce(account, U256::from(block_number)) + .unwrap_or_default(); // convert to try_evm_get_balance - Default to 0 for now + Ok(nonce.as_u64()) +} + /// EvmIn. Send DFI to an EVM account. /// /// # Arguments @@ -227,13 +255,13 @@ pub fn evm_sub_balance( /// /// # Returns /// -/// Returns `true` if the transaction is valid, logs the error and returns `false` otherwise. -pub fn evm_prevalidate_raw_tx(tx: &str) -> Result> { +/// Returns the transaction nonce as a u64 if the transaction is valid, logs and throws the error otherwise. +pub fn evm_prevalidate_raw_tx(tx: &str) -> Result> { match RUNTIME.handlers.evm.validate_raw_tx(tx) { - Ok(_) => Ok(true), + Ok(signed_tx) => Ok(signed_tx.nonce().as_u64()), Err(e) => { - debug!("evm_prevalidate_raw_tx fails with error: {:?}", e); - Ok(false) + debug!("evm_prevalidate_raw_tx fails with error: {e}"); + Err(e) } } } From aa0c97dfbd0b82970743f8389368f9cd337e8ee9 Mon Sep 17 00:00:00 2001 From: Peter Bushnell Date: Mon, 5 Jun 2023 12:22:56 +0100 Subject: [PATCH 02/20] Catch thrown error from evm_prevalidate_raw_tx --- src/masternodes/mn_checks.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index 4135718edd..86f46c8fd8 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -3930,9 +3931,12 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor { if (obj.evmTx.size() > static_cast(EVM_TX_SIZE)) return Res::Err("evm tx size too large"); - if (!evm_prevalidate_raw_tx(HexStr(obj.evmTx))) + try { + evm_prevalidate_raw_tx(HexStr(obj.evmTx)); + } catch (const rust::Error&) { return Res::Err("evm tx failed to validate"); - + } + if (!evm_queue_tx(evmContext, HexStr(obj.evmTx), tx.GetHash().ToArrayReversed())) return Res::Err("evm tx failed to queue"); From b7c9fe9c01735527f66cf3b16d3cd3be0a141112 Mon Sep 17 00:00:00 2001 From: jouzo Date: Mon, 5 Jun 2023 13:44:04 +0200 Subject: [PATCH 03/20] Returns sender address in evm_prevalidate_raw_tx --- lib/ain-rs-exports/src/lib.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/ain-rs-exports/src/lib.rs b/lib/ain-rs-exports/src/lib.rs index 409b14763c..ce0ecceb1c 100644 --- a/lib/ain-rs-exports/src/lib.rs +++ b/lib/ain-rs-exports/src/lib.rs @@ -34,6 +34,11 @@ pub mod ffi { miner_fee: u64, } + pub struct ValidateTxResult { + nonce: u64, + sender: [u8; 20], + } + extern "Rust" { fn evm_get_balance(address: &str, block_number: [u8; 32]) -> Result; fn evm_get_nonce(address: &str, block_number: [u8; 32]) -> Result; @@ -51,7 +56,7 @@ pub mod ffi { native_tx_hash: [u8; 32], ) -> Result; - fn evm_prevalidate_raw_tx(tx: &str) -> Result; + fn evm_prevalidate_raw_tx(tx: &str) -> Result; fn evm_get_context() -> u64; fn evm_discard_context(context: u64) -> Result<()>; @@ -255,10 +260,13 @@ pub fn evm_sub_balance( /// /// # Returns /// -/// Returns the transaction nonce as a u64 if the transaction is valid, logs and throws the error otherwise. -pub fn evm_prevalidate_raw_tx(tx: &str) -> Result> { +/// Returns the transaction nonce and sender address if the transaction is valid, logs and throws the error otherwise. +pub fn evm_prevalidate_raw_tx(tx: &str) -> Result> { match RUNTIME.handlers.evm.validate_raw_tx(tx) { - Ok(signed_tx) => Ok(signed_tx.nonce().as_u64()), + Ok(signed_tx) => Ok(ffi::ValidateTxResult { + nonce: signed_tx.nonce().as_u64(), + sender: signed_tx.sender.to_fixed_bytes(), + }), Err(e) => { debug!("evm_prevalidate_raw_tx fails with error: {e}"); Err(e) From ff479dbcad746e1b53a6017d40d469c06899176e Mon Sep 17 00:00:00 2001 From: Peter Bushnell Date: Mon, 5 Jun 2023 13:36:33 +0100 Subject: [PATCH 04/20] Check if nonce is the expected value --- src/miner.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/miner.cpp b/src/miner.cpp index 00b60c8208..ddb4e69fcc 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -776,6 +777,22 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda // Only check custom TXs if (txType != CustomTxType::None) { + if (txType == CustomTxType::EvmTx) { + auto txMessage = customTypeToMessage(txType); + const auto obj = std::get(txMessage); + try { + const auto txResult = evm_prevalidate_raw_tx(HexStr(obj.evmTx)); + const auto nonce = evm_get_nonce(txResult.sender); + if (nonce != txResult.nonce) { + customTxPassed = false; + break; + } + } catch (const rust::Error&) { + customTxPassed = false; + break; + } + } + const auto res = ApplyCustomTx(view, coins, tx, chainparams.GetConsensus(), nHeight, pblock->nTime, nullptr, 0, evmContext); // Not okay invalidate, undo and skip From 093f222358194f3b93e2754b9d85a7783a8bda53 Mon Sep 17 00:00:00 2001 From: jouzo Date: Mon, 5 Jun 2023 14:57:27 +0200 Subject: [PATCH 05/20] Get evm nonce and balance at latest block height --- lib/ain-rs-exports/src/lib.rs | 24 ++++++++++++------------ src/masternodes/rpc_accounts.cpp | 5 +++-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/ain-rs-exports/src/lib.rs b/lib/ain-rs-exports/src/lib.rs index ce0ecceb1c..7d79047719 100644 --- a/lib/ain-rs-exports/src/lib.rs +++ b/lib/ain-rs-exports/src/lib.rs @@ -40,8 +40,8 @@ pub mod ffi { } extern "Rust" { - fn evm_get_balance(address: &str, block_number: [u8; 32]) -> Result; - fn evm_get_nonce(address: &str, block_number: [u8; 32]) -> Result; + fn evm_get_balance(address: [u8; 20]) -> Result; + fn evm_get_nonce(address: [u8; 20]) -> Result; fn evm_add_balance( context: u64, @@ -125,12 +125,11 @@ pub fn create_and_sign_tx(ctx: ffi::CreateTransactionContext) -> Result, Ok(signed.encode().into()) } -/// Retrieves the balance of an EVM account at a specific block number. +/// Retrieves the balance of an EVM account at latest block height. /// /// # Arguments /// /// * `address` - The EVM address of the account. -/// * `block_number` - The block number as a byte array. /// /// # Errors /// @@ -139,24 +138,24 @@ pub fn create_and_sign_tx(ctx: ffi::CreateTransactionContext) -> Result, /// # Returns /// /// Returns the balance of the account as a `u64` on success. -pub fn evm_get_balance(address: &str, block_number: [u8; 32]) -> Result> { - let account = address.parse()?; +pub fn evm_get_balance(address: [u8; 20]) -> Result> { + let account = H160::from(address); + let (_, latest_block_number) = RUNTIME.handlers.block.get_latest_block_hash_and_number(); let mut balance = RUNTIME .handlers .evm - .get_balance(account, U256::from(block_number)) + .get_balance(account, latest_block_number) .unwrap_or_default(); // convert to try_evm_get_balance - Default to 0 for now balance /= WEI_TO_GWEI; balance /= GWEI_TO_SATS; Ok(balance.as_u64()) } -/// Retrieves the nonce of an EVM account at a specific block number. +/// Retrieves the nonce of an EVM account at latest block height. /// /// # Arguments /// /// * `address` - The EVM address of the account. -/// * `block_number` - The block number as a byte array. /// /// # Errors /// @@ -165,12 +164,13 @@ pub fn evm_get_balance(address: &str, block_number: [u8; 32]) -> Result Result> { - let account = address.parse()?; +pub fn evm_get_nonce(address: [u8; 20]) -> Result> { + let account = H160::from(address); + let (_, latest_block_number) = RUNTIME.handlers.block.get_latest_block_hash_and_number(); let nonce = RUNTIME .handlers .evm - .get_nonce(account, U256::from(block_number)) + .get_nonce(account, latest_block_number) .unwrap_or_default(); // convert to try_evm_get_balance - Default to 0 for now Ok(nonce.as_u64()) } diff --git a/src/masternodes/rpc_accounts.cpp b/src/masternodes/rpc_accounts.cpp index 1e56f24dac..9e458770a0 100644 --- a/src/masternodes/rpc_accounts.cpp +++ b/src/masternodes/rpc_accounts.cpp @@ -586,8 +586,9 @@ UniValue gettokenbalances(const JSONRPCRequest& request) { if (eth_lookup) { for (const auto keyID : pwallet->GetEthKeys()) { - const arith_uint256 height = targetHeight; - const auto evmAmount = evm_get_balance(HexStr(keyID.begin(), keyID.end()), ArithToUint256(height).ToArrayReversed()); + std::array address{}; + std::copy(keyID.begin(), keyID.end(), address.begin()); + const auto evmAmount = evm_get_balance(address); totalBalances.Add({{}, static_cast(evmAmount)}); } } From b44c214cd36ed5c813de2d8de438641841d75999 Mon Sep 17 00:00:00 2001 From: jouzo Date: Tue, 6 Jun 2023 08:50:01 +0200 Subject: [PATCH 06/20] With RustRes instead of throw --- lib/ain-grpc/src/rpc/eth.rs | 2 +- lib/ain-rs-exports/src/lib.rs | 33 +++++++++++++++++++++++++-------- src/masternodes/mn_checks.cpp | 12 +++++++----- src/miner.cpp | 18 ++++++++++-------- 4 files changed, 43 insertions(+), 22 deletions(-) diff --git a/lib/ain-grpc/src/rpc/eth.rs b/lib/ain-grpc/src/rpc/eth.rs index a4979ae109..47ff105ab1 100644 --- a/lib/ain-grpc/src/rpc/eth.rs +++ b/lib/ain-grpc/src/rpc/eth.rs @@ -576,7 +576,7 @@ impl MetachainRPCServer for MetachainRPCModule { Ok(format!("{:#x}", signed_tx.transaction.hash())) } else { - debug!(target:"rpc","[send_raw_transaction] Could not publish raw transaction: {tx}"); + debug!(target:"rpc","[send_raw_transaction] Could not publish raw transaction: {tx} reason: {res_string}"); Err(Error::Custom(format!( "Could not publish raw transaction: {tx} reason: {res_string}" ))) diff --git a/lib/ain-rs-exports/src/lib.rs b/lib/ain-rs-exports/src/lib.rs index 7d79047719..c65a0885cd 100644 --- a/lib/ain-rs-exports/src/lib.rs +++ b/lib/ain-rs-exports/src/lib.rs @@ -12,6 +12,8 @@ use ethereum::{EnvelopedEncodable, TransactionAction, TransactionSignature}; use primitive_types::{H160, H256, U256}; use transaction::{LegacyUnsignedTransaction, TransactionError, LOWER_H256}; +use crate::ffi::RustRes; + pub const WEI_TO_GWEI: u64 = 1_000_000_000; pub const GWEI_TO_SATS: u64 = 10; @@ -34,11 +36,17 @@ pub mod ffi { miner_fee: u64, } + #[derive(Default)] pub struct ValidateTxResult { nonce: u64, sender: [u8; 20], } + pub struct RustRes { + ok: bool, + reason: String, + } + extern "Rust" { fn evm_get_balance(address: [u8; 20]) -> Result; fn evm_get_nonce(address: [u8; 20]) -> Result; @@ -56,7 +64,7 @@ pub mod ffi { native_tx_hash: [u8; 32], ) -> Result; - fn evm_prevalidate_raw_tx(tx: &str) -> Result; + fn evm_try_prevalidate_raw_tx(result: &mut RustRes, tx: &str) -> Result; fn evm_get_context() -> u64; fn evm_discard_context(context: u64) -> Result<()>; @@ -261,15 +269,24 @@ pub fn evm_sub_balance( /// # Returns /// /// Returns the transaction nonce and sender address if the transaction is valid, logs and throws the error otherwise. -pub fn evm_prevalidate_raw_tx(tx: &str) -> Result> { +pub fn evm_try_prevalidate_raw_tx( + result: &mut RustRes, + tx: &str, +) -> Result> { match RUNTIME.handlers.evm.validate_raw_tx(tx) { - Ok(signed_tx) => Ok(ffi::ValidateTxResult { - nonce: signed_tx.nonce().as_u64(), - sender: signed_tx.sender.to_fixed_bytes(), - }), + Ok(signed_tx) => { + result.ok = true; + Ok(ffi::ValidateTxResult { + nonce: signed_tx.nonce().as_u64(), + sender: signed_tx.sender.to_fixed_bytes(), + }) + } Err(e) => { - debug!("evm_prevalidate_raw_tx fails with error: {e}"); - Err(e) + debug!("evm_try_prevalidate_raw_tx fails with error: {e}"); + result.ok = false; + result.reason = e.to_string(); + + Ok(ffi::ValidateTxResult::default()) } } } diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index 86f46c8fd8..38e30cc49b 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -3931,12 +3931,14 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor { if (obj.evmTx.size() > static_cast(EVM_TX_SIZE)) return Res::Err("evm tx size too large"); - try { - evm_prevalidate_raw_tx(HexStr(obj.evmTx)); - } catch (const rust::Error&) { - return Res::Err("evm tx failed to validate"); + RustRes result; + evm_try_prevalidate_raw_tx(result, HexStr(obj.evmTx)); + + if (!result.ok) { + LogPrintf("------ EVM reason : %s ----\n", result.reason); + return Res::Err("evm tx failed to validate %s", result.reason); } - + if (!evm_queue_tx(evmContext, HexStr(obj.evmTx), tx.GetHash().ToArrayReversed())) return Res::Err("evm tx failed to queue"); diff --git a/src/miner.cpp b/src/miner.cpp index ddb4e69fcc..eef275bfd3 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -780,14 +780,16 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda if (txType == CustomTxType::EvmTx) { auto txMessage = customTypeToMessage(txType); const auto obj = std::get(txMessage); - try { - const auto txResult = evm_prevalidate_raw_tx(HexStr(obj.evmTx)); - const auto nonce = evm_get_nonce(txResult.sender); - if (nonce != txResult.nonce) { - customTxPassed = false; - break; - } - } catch (const rust::Error&) { + + RustRes result; + const auto txResult = evm_try_prevalidate_raw_tx(result, HexStr(obj.evmTx)); + if (!result.ok) { + customTxPassed = false; + break; + } + + const auto nonce = evm_get_nonce(txResult.sender); + if (nonce != txResult.nonce) { customTxPassed = false; break; } From ba4eac0622ccffadf100b01d39c62472a09c3212 Mon Sep 17 00:00:00 2001 From: jouzo Date: Tue, 6 Jun 2023 11:02:29 +0200 Subject: [PATCH 07/20] Use RustRes in evm_try_queue_tx --- lib/ain-rs-exports/src/lib.rs | 25 +++++++++++++++++++++---- src/masternodes/mn_checks.cpp | 9 ++++++--- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/ain-rs-exports/src/lib.rs b/lib/ain-rs-exports/src/lib.rs index c65a0885cd..425599948d 100644 --- a/lib/ain-rs-exports/src/lib.rs +++ b/lib/ain-rs-exports/src/lib.rs @@ -68,7 +68,12 @@ pub mod ffi { fn evm_get_context() -> u64; fn evm_discard_context(context: u64) -> Result<()>; - fn evm_queue_tx(context: u64, raw_tx: &str, native_tx_hash: [u8; 32]) -> Result; + fn evm_try_queue_tx( + result: &mut RustRes, + context: u64, + raw_tx: &str, + native_tx_hash: [u8; 32], + ) -> Result; fn evm_finalize( context: u64, @@ -336,15 +341,27 @@ fn evm_discard_context(context: u64) -> Result<(), Box> { /// # Returns /// /// Returns `true` if the transaction is successfully queued, `false` otherwise. -fn evm_queue_tx(context: u64, raw_tx: &str, hash: [u8; 32]) -> Result> { +fn evm_try_queue_tx( + result: &mut RustRes, + context: u64, + raw_tx: &str, + hash: [u8; 32], +) -> Result> { let signed_tx: SignedTx = raw_tx.try_into()?; match RUNTIME .handlers .evm .queue_tx(context, signed_tx.into(), hash) { - Ok(_) => Ok(true), - Err(_) => Ok(false), + Ok(_) => { + result.ok = true; + Ok(true) + } + Err(e) => { + result.ok = false; + result.reason = e.to_string(); + Ok(false) + } } } diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index 38e30cc49b..a464e84254 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -3935,12 +3935,15 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor { evm_try_prevalidate_raw_tx(result, HexStr(obj.evmTx)); if (!result.ok) { - LogPrintf("------ EVM reason : %s ----\n", result.reason); + LogPrintf("[evm_try_prevalidate_raw_tx] failed, reason : %s\n", result.reason); return Res::Err("evm tx failed to validate %s", result.reason); } - if (!evm_queue_tx(evmContext, HexStr(obj.evmTx), tx.GetHash().ToArrayReversed())) - return Res::Err("evm tx failed to queue"); + evm_try_queue_tx(result, evmContext, HexStr(obj.evmTx), tx.GetHash().ToArrayReversed()); + if (!result.ok) { + LogPrintf("[evm_try_queue_tx] failed, reason : %s\n", result.reason); + return Res::Err("evm tx failed to queue %s\n", result.reason); + } return Res::Ok(); } From b8c72cd1701485a122bd6e6b9dd64ddbd956ebdc Mon Sep 17 00:00:00 2001 From: Peter Bushnell Date: Tue, 6 Jun 2023 10:22:04 +0100 Subject: [PATCH 08/20] Parse metadata into txMessage --- src/miner.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/miner.cpp b/src/miner.cpp index eef275bfd3..33ed2be74a 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -773,12 +773,17 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda AddCoins(coins, tx, nHeight, false); // do not check std::vector metadata; - CustomTxType txType = GuessCustomTxType(tx, metadata); + CustomTxType txType = GuessCustomTxType(tx, metadata, true); // Only check custom TXs if (txType != CustomTxType::None) { if (txType == CustomTxType::EvmTx) { auto txMessage = customTypeToMessage(txType); + if (!CustomMetadataParse(nHeight, Params().GetConsensus(), metadata, txMessage)) { + customTxPassed = false; + break; + } + const auto obj = std::get(txMessage); RustRes result; From 580b096b641706947b7e372100f67884eeb67921 Mon Sep 17 00:00:00 2001 From: jouzo Date: Tue, 6 Jun 2023 22:43:07 +0200 Subject: [PATCH 09/20] Add evm_get_nonce_in_context FFI method --- lib/ain-evm/src/evm.rs | 22 ++++++++++++++++++++++ lib/ain-evm/src/tx_queue.rs | 25 +++++++++++++++++++++++++ lib/ain-rs-exports/src/lib.rs | 25 +++++++++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/lib/ain-evm/src/evm.rs b/lib/ain-evm/src/evm.rs index 45a1703244..43c4eb5203 100644 --- a/lib/ain-evm/src/evm.rs +++ b/lib/ain-evm/src/evm.rs @@ -316,6 +316,28 @@ impl EVMHandler { debug!("Account {:x?} nonce {:x?}", address, nonce); Ok(nonce) } + + pub fn get_nonce_in_context(&self, context: u64, address: H160) -> Result { + let nonce = self + .tx_queues + .get_nonce(context, address) + .unwrap_or_else(|| { + let latest_block = self + .storage + .get_latest_block() + .map(|b| b.header.number) + .unwrap_or_else(|| U256::zero()); + + self.get_nonce(address, latest_block) + .unwrap_or_else(|_| U256::zero()) + }); + + debug!( + "Account {:x?} nonce {:x?} in context {context}", + address, nonce + ); + Ok(nonce) + } } use std::fmt; diff --git a/lib/ain-evm/src/tx_queue.rs b/lib/ain-evm/src/tx_queue.rs index fdf17501f8..1fec88f468 100644 --- a/lib/ain-evm/src/tx_queue.rs +++ b/lib/ain-evm/src/tx_queue.rs @@ -1,3 +1,4 @@ +use ethereum_types::{H160, U256}; use rand::Rng; use std::{ collections::HashMap, @@ -82,6 +83,14 @@ impl TransactionQueueMap { .get(&context_id) .map_or(0, TransactionQueue::len) } + + pub fn get_nonce(&self, context_id: u64, address: H160) -> Option { + self.queues + .read() + .unwrap() + .get(&context_id) + .map_or(None, |queue| queue.get_nonce(address)) + } } #[derive(Debug)] @@ -95,12 +104,14 @@ type QueueTxWithNativeHash = (QueueTx, NativeTxHash); #[derive(Debug, Default)] pub struct TransactionQueue { transactions: Mutex>, + account_nonces: Mutex>, } impl TransactionQueue { pub fn new() -> Self { Self { transactions: Mutex::new(Vec::new()), + account_nonces: Mutex::new(HashMap::new()), } } @@ -117,12 +128,26 @@ impl TransactionQueue { } pub fn queue_tx(&self, tx: QueueTxWithNativeHash) { + if let QueueTx::SignedTx(signed_tx) = &tx.0 { + self.account_nonces + .lock() + .unwrap() + .insert(signed_tx.sender, signed_tx.nonce()); + } self.transactions.lock().unwrap().push(tx); } pub fn len(&self) -> usize { self.transactions.lock().unwrap().len() } + + pub fn get_nonce(&self, address: H160) -> Option { + self.account_nonces + .lock() + .unwrap() + .get(&address) + .map(ToOwned::to_owned) + } } impl From for QueueTx { diff --git a/lib/ain-rs-exports/src/lib.rs b/lib/ain-rs-exports/src/lib.rs index 425599948d..258cd74e7d 100644 --- a/lib/ain-rs-exports/src/lib.rs +++ b/lib/ain-rs-exports/src/lib.rs @@ -50,6 +50,7 @@ pub mod ffi { extern "Rust" { fn evm_get_balance(address: [u8; 20]) -> Result; fn evm_get_nonce(address: [u8; 20]) -> Result; + fn evm_get_nonce_in_context(context: u64, address: [u8; 20]) -> Result; fn evm_add_balance( context: u64, @@ -188,6 +189,30 @@ pub fn evm_get_nonce(address: [u8; 20]) -> Result> { Ok(nonce.as_u64()) } +/// Retrieves the nonce of an EVM account in a specific context +/// +/// # Arguments +/// +/// * `context` - The context queue number. +/// * `address` - The EVM address of the account. +/// +/// # Errors +/// +/// Throws an Error if the address is not a valid EVM address. +/// +/// # Returns +/// +/// Returns the nonce of the account in a specific context as a `u64` on success. +pub fn evm_get_nonce_in_context(context: u64, address: [u8; 20]) -> Result> { + let address = H160::from(address); + let nonce = RUNTIME + .handlers + .evm + .get_nonce_in_context(context, address) + .unwrap_or_default(); // convert to try_evm_get_balance - Default to 0 for now + Ok(nonce.as_u64()) +} + /// EvmIn. Send DFI to an EVM account. /// /// # Arguments From 717b8c99c2c2969377f15bc8c995e1fe8f9cdc6f Mon Sep 17 00:00:00 2001 From: jouzo Date: Tue, 6 Jun 2023 22:45:05 +0200 Subject: [PATCH 10/20] Document TransactionQueue nonce handling --- lib/ain-evm/src/tx_queue.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/lib/ain-evm/src/tx_queue.rs b/lib/ain-evm/src/tx_queue.rs index 1fec88f468..142b5f1803 100644 --- a/lib/ain-evm/src/tx_queue.rs +++ b/lib/ain-evm/src/tx_queue.rs @@ -21,6 +21,9 @@ impl Default for TransactionQueueMap { } } +/// Holds multiple `TransactionQueue`s, each associated with a unique context ID. +/// +/// Context IDs are randomly generated and used to access distinct transaction queues. impl TransactionQueueMap { pub fn new() -> Self { TransactionQueueMap { @@ -28,6 +31,8 @@ impl TransactionQueueMap { } } + /// `get_context` generates a unique random ID, creates a new `TransactionQueue` for that ID, + /// and then returns the ID. pub fn get_context(&self) -> u64 { let mut rng = rand::thread_rng(); loop { @@ -41,10 +46,13 @@ impl TransactionQueueMap { } } + /// Try to remove and return the `TransactionQueue` associated with the provided + /// context ID. pub fn remove(&self, context_id: u64) -> Option { self.queues.write().unwrap().remove(&context_id) } + /// Clears the `TransactionQueue` vector associated with the provided context ID. pub fn clear(&self, context_id: u64) -> Result<(), QueueError> { self.queues .read() @@ -54,6 +62,12 @@ impl TransactionQueueMap { .map(TransactionQueue::clear) } + /// Attempts to add a new transaction to the `TransactionQueue` associated with the + /// provided context ID. If the transaction is a `SignedTx`, it also updates the + /// corresponding account's nonce. The method assumes that transactions are signed and nonces + /// are properly handled before being added to the queue. Nonces for each account's transactions + /// should be in increasing order, e.g., if the last queued transaction for an account has nonce 3, + /// the next one should have nonce 4. pub fn queue_tx( &self, context_id: u64, @@ -68,6 +82,9 @@ impl TransactionQueueMap { .map(|queue| queue.queue_tx((tx, hash))) } + /// `drain_all` returns all transactions from the `TransactionQueue` associated with the + /// provided context ID, removing them from the queue. Transactions are returned in the + /// order they were added. pub fn drain_all(&self, context_id: u64) -> Vec { self.queues .read() @@ -76,6 +93,8 @@ impl TransactionQueueMap { .map_or(Vec::new(), TransactionQueue::drain_all) } + /// `len` returns the number of transactions in the `TransactionQueue` associated with the + /// provided context ID. pub fn len(&self, context_id: u64) -> usize { self.queues .read() @@ -84,6 +103,10 @@ impl TransactionQueueMap { .map_or(0, TransactionQueue::len) } + /// `get_nonce` returns the latest nonce for the account with the provided address in the + /// `TransactionQueue` associated with the provided context ID. This method assumes that + /// only signed transactions (which include a nonce) are added to the queue using `queue_tx` + /// and that their nonces are in increasing order. pub fn get_nonce(&self, context_id: u64, address: H160) -> Option { self.queues .read() @@ -101,6 +124,9 @@ pub enum QueueTx { type QueueTxWithNativeHash = (QueueTx, NativeTxHash); +/// The `TransactionQueue` holds a queue of transactions and a map of account nonces. +/// It's used to manage and process transactions for different accounts. +/// #[derive(Debug, Default)] pub struct TransactionQueue { transactions: Mutex>, From 884329ebffcaac57484c7b9902c9ea1966e29864 Mon Sep 17 00:00:00 2001 From: jouzo Date: Wed, 7 Jun 2023 07:23:45 +0200 Subject: [PATCH 11/20] Return InvalidNonce if queued nonce is not in increasing order --- lib/ain-evm/src/tx_queue.rs | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/lib/ain-evm/src/tx_queue.rs b/lib/ain-evm/src/tx_queue.rs index 142b5f1803..0c09337347 100644 --- a/lib/ain-evm/src/tx_queue.rs +++ b/lib/ain-evm/src/tx_queue.rs @@ -64,10 +64,18 @@ impl TransactionQueueMap { /// Attempts to add a new transaction to the `TransactionQueue` associated with the /// provided context ID. If the transaction is a `SignedTx`, it also updates the - /// corresponding account's nonce. The method assumes that transactions are signed and nonces - /// are properly handled before being added to the queue. Nonces for each account's transactions - /// should be in increasing order, e.g., if the last queued transaction for an account has nonce 3, - /// the next one should have nonce 4. + /// corresponding account's nonce. + /// Nonces for each account's transactions must be in strictly increasing order. This means that if the last + /// queued transaction for an account has nonce 3, the next one should have nonce 4. If a `SignedTx` with a nonce + /// that is not one more than the previous nonce is added, an error is returned. This helps to ensure the integrity + /// of the transaction queue and enforce correct nonce usage. + /// + /// # Errors + /// + /// Returns `QueueError::NoSuchContext` if no queue is associated with the given context ID. + /// Returns `QueueError::InvalidNonce` if a `SignedTx` is provided with a nonce that is not one more than the + /// previous nonce of transactions from the same sender in the queue. + /// pub fn queue_tx( &self, context_id: u64, @@ -79,7 +87,7 @@ impl TransactionQueueMap { .unwrap() .get(&context_id) .ok_or(QueueError::NoSuchContext) - .map(|queue| queue.queue_tx((tx, hash))) + .map(|queue| queue.queue_tx((tx, hash)))? } /// `drain_all` returns all transactions from the `TransactionQueue` associated with the @@ -153,14 +161,18 @@ impl TransactionQueue { .collect::>() } - pub fn queue_tx(&self, tx: QueueTxWithNativeHash) { + pub fn queue_tx(&self, tx: QueueTxWithNativeHash) -> Result<(), QueueError> { if let QueueTx::SignedTx(signed_tx) = &tx.0 { - self.account_nonces - .lock() - .unwrap() - .insert(signed_tx.sender, signed_tx.nonce()); + let mut account_nonces = self.account_nonces.lock().unwrap(); + if let Some(nonce) = account_nonces.get(&signed_tx.sender) { + if signed_tx.nonce() != nonce + 1 { + return Err(QueueError::InvalidNonce((signed_tx.clone(), *nonce))); + } + } + account_nonces.insert(signed_tx.sender, signed_tx.nonce()); } self.transactions.lock().unwrap().push(tx); + Ok(()) } pub fn len(&self) -> usize { @@ -185,12 +197,14 @@ impl From for QueueTx { #[derive(Debug)] pub enum QueueError { NoSuchContext, + InvalidNonce((Box, U256)), } 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::InvalidNonce((tx, nonce)) => write!(f, "Invalid nonce {:x?} for tx {:x?}. Previous queued nonce is {}. TXs should be queued in increasing nonce order.", tx.nonce(), tx.transaction.hash(), nonce), } } } From a82d33e98a2b2275ab6738fefbdc08ae19155d1e Mon Sep 17 00:00:00 2001 From: jouzo Date: Wed, 7 Jun 2023 07:40:54 +0200 Subject: [PATCH 12/20] Add unit test for nonce order --- lib/ain-evm/src/tx_queue.rs | 83 +++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/lib/ain-evm/src/tx_queue.rs b/lib/ain-evm/src/tx_queue.rs index 0c09337347..70b44c4a15 100644 --- a/lib/ain-evm/src/tx_queue.rs +++ b/lib/ain-evm/src/tx_queue.rs @@ -210,3 +210,86 @@ impl std::fmt::Display for QueueError { } impl std::error::Error for QueueError {} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use ethereum_types::{H256, U256}; + + use crate::transaction::bridge::BalanceUpdate; + + use super::*; + + #[test] + fn test_invalid_nonce_order() -> Result<(), QueueError> { + let queue = TransactionQueue::new(); + + // Nonce 2, sender 0xe61a3a6eb316d773c773f4ce757a542f673023c6 + let tx1 = QueueTx::SignedTx(Box::new(SignedTx::try_from("f869028502540be400832dc6c0943e338e722607a8c1eab615579ace4f6dedfa19fa80840adb1a9a2aa0adb0386f95848d33b49ee6057c34e530f87f696a29b4e1b04ef90b2a58bbedbca02f500cf29c5c4245608545e7d9d35b36ef0365e5c52d96e69b8f07920d32552f").unwrap())); + + // Nonce 2, sender 0x6bc42fd533d6cb9d973604155e1f7197a3b0e703 + let tx2 = QueueTx::SignedTx(Box::new(SignedTx::try_from("f869028502540be400832dc6c0943e338e722607a8c1eab615579ace4f6dedfa19fa80840adb1a9a2aa09588b47d2cd3f474d6384309cca5cb8e360cb137679f0a1589a1c184a15cb27ca0453ddbf808b83b279cac3226b61a9d83855aba60ae0d3a8407cba0634da7459d").unwrap())); + + // Nonce 0, sender 0xe61a3a6eb316d773c773f4ce757a542f673023c6 + let tx3 = QueueTx::SignedTx(Box::new(SignedTx::try_from("f869808502540be400832dc6c0943e338e722607a8c1eab615579ace4f6dedfa19fa80840adb1a9a2aa03d28d24808c3de08c606c5544772ded91913f648ad56556f181905208e206c85a00ecd0ba938fb89fc4a17ea333ea842c7305090dee9236e2b632578f9e5045cb3").unwrap())); + + queue.queue_tx((tx1, H256::from_low_u64_be(1).into()))?; + queue.queue_tx((tx2, H256::from_low_u64_be(2).into()))?; + // Should fail as nonce 2 is already queued for this sender + let queued = queue.queue_tx((tx3, H256::from_low_u64_be(3).into())); + assert!(matches!(queued, Err(QueueError::InvalidNonce { .. }))); + Ok(()) + } + + #[test] + fn test_invalid_nonce_order_with_transfer_domain() -> Result<(), QueueError> { + let queue = TransactionQueue::new(); + + // Nonce 2, sender 0xe61a3a6eb316d773c773f4ce757a542f673023c6 + let tx1 = QueueTx::SignedTx(Box::new(SignedTx::try_from("f869028502540be400832dc6c0943e338e722607a8c1eab615579ace4f6dedfa19fa80840adb1a9a2aa0adb0386f95848d33b49ee6057c34e530f87f696a29b4e1b04ef90b2a58bbedbca02f500cf29c5c4245608545e7d9d35b36ef0365e5c52d96e69b8f07920d32552f").unwrap())); + + // Nonce 2, sender 0x6bc42fd533d6cb9d973604155e1f7197a3b0e703 + let tx2 = QueueTx::SignedTx(Box::new(SignedTx::try_from("f869028502540be400832dc6c0943e338e722607a8c1eab615579ace4f6dedfa19fa80840adb1a9a2aa09588b47d2cd3f474d6384309cca5cb8e360cb137679f0a1589a1c184a15cb27ca0453ddbf808b83b279cac3226b61a9d83855aba60ae0d3a8407cba0634da7459d").unwrap())); + + // sender 0x6bc42fd533d6cb9d973604155e1f7197a3b0e703 + let tx3 = QueueTx::BridgeTx(BridgeTx::EvmIn(BalanceUpdate { + address: H160::from_str("0x6bc42fd533d6cb9d973604155e1f7197a3b0e703").unwrap(), + amount: U256::one(), + })); + + // Nonce 0, sender 0xe61a3a6eb316d773c773f4ce757a542f673023c6 + let tx4 = QueueTx::SignedTx(Box::new(SignedTx::try_from("f869808502540be400832dc6c0943e338e722607a8c1eab615579ace4f6dedfa19fa80840adb1a9a2aa03d28d24808c3de08c606c5544772ded91913f648ad56556f181905208e206c85a00ecd0ba938fb89fc4a17ea333ea842c7305090dee9236e2b632578f9e5045cb3").unwrap())); + + queue.queue_tx((tx1, H256::from_low_u64_be(1).into()))?; + queue.queue_tx((tx2, H256::from_low_u64_be(2).into()))?; + queue.queue_tx((tx3, H256::from_low_u64_be(3).into()))?; + // Should fail as nonce 2 is already queued for this sender + let queued = queue.queue_tx((tx4, H256::from_low_u64_be(4).into())); + assert!(matches!(queued, Err(QueueError::InvalidNonce { .. }))); + Ok(()) + } + + #[test] + fn test_valid_nonce_order() -> Result<(), QueueError> { + let queue = TransactionQueue::new(); + + // Nonce 0, sender 0xe61a3a6eb316d773c773f4ce757a542f673023c6 + let tx1 = QueueTx::SignedTx(Box::new(SignedTx::try_from("f869808502540be400832dc6c0943e338e722607a8c1eab615579ace4f6dedfa19fa80840adb1a9a2aa03d28d24808c3de08c606c5544772ded91913f648ad56556f181905208e206c85a00ecd0ba938fb89fc4a17ea333ea842c7305090dee9236e2b632578f9e5045cb3").unwrap())); + + // Nonce 1, sender 0xe61a3a6eb316d773c773f4ce757a542f673023c6 + let tx2 = QueueTx::SignedTx(Box::new(SignedTx::try_from("f869018502540be400832dc6c0943e338e722607a8c1eab615579ace4f6dedfa19fa80840adb1a9a2aa0dd1fad9a8465969354d567e8a74af3f6de3e56abbe1b71154d7929d0bd5cc170a0353190adb50e3cfae82a77c2ea56b49a86f72bd0071a70d1c25c49827654aa68").unwrap())); + + // Nonce 2, sender 0xe61a3a6eb316d773c773f4ce757a542f673023c6 + let tx3 = QueueTx::SignedTx(Box::new(SignedTx::try_from("f869028502540be400832dc6c0943e338e722607a8c1eab615579ace4f6dedfa19fa80840adb1a9a2aa0adb0386f95848d33b49ee6057c34e530f87f696a29b4e1b04ef90b2a58bbedbca02f500cf29c5c4245608545e7d9d35b36ef0365e5c52d96e69b8f07920d32552f").unwrap())); + + // Nonce 2, sender 0x6bc42fd533d6cb9d973604155e1f7197a3b0e703 + let tx4 = QueueTx::SignedTx(Box::new(SignedTx::try_from("f869028502540be400832dc6c0943e338e722607a8c1eab615579ace4f6dedfa19fa80840adb1a9a2aa09588b47d2cd3f474d6384309cca5cb8e360cb137679f0a1589a1c184a15cb27ca0453ddbf808b83b279cac3226b61a9d83855aba60ae0d3a8407cba0634da7459d").unwrap())); + + queue.queue_tx((tx1, H256::from_low_u64_be(1).into()))?; + queue.queue_tx((tx2, H256::from_low_u64_be(2).into()))?; + queue.queue_tx((tx3, H256::from_low_u64_be(3).into()))?; + queue.queue_tx((tx4, H256::from_low_u64_be(4).into()))?; + Ok(()) + } +} From 65a5108ebe4524a971853fd31626f456ae0d470a Mon Sep 17 00:00:00 2001 From: jouzo Date: Wed, 7 Jun 2023 07:48:19 +0200 Subject: [PATCH 13/20] Use FFI evm_get_nonce_in_context --- lib/ain-evm/src/evm.rs | 23 +++++++++++++++++++++-- lib/ain-rs-exports/src/lib.rs | 20 ++++++-------------- src/miner.cpp | 2 +- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/lib/ain-evm/src/evm.rs b/lib/ain-evm/src/evm.rs index 43c4eb5203..e72fb4ad26 100644 --- a/lib/ain-evm/src/evm.rs +++ b/lib/ain-evm/src/evm.rs @@ -317,7 +317,26 @@ impl EVMHandler { Ok(nonce) } - pub fn get_nonce_in_context(&self, context: u64, address: H160) -> Result { + /// Retrieves the nonce for the specified account within a particular context. + /// + /// The method first attempts to retrieve the nonce from the transaction queue associated with the provided context. + /// If no nonce is found in the transaction queue, that means that no transactions have been queued for this account + /// in this context), it falls back to retrieving the nonce from the storage at the latest block + /// If no nonce is found in the storage (i.e., no transactions for this account have been committed yet), + /// the nonce is defaulted to zero. + /// + /// This method provides a unified view of the nonce for an account, taking into account both transactions that are + /// waiting to be processed in the queue and transactions that have already been processed and committed to the storage. + /// + /// # Arguments + /// + /// * `context` - The context queue number. + /// * `address` - The EVM address of the account whose nonce we want to retrieve. + /// + /// # Returns + /// + /// Returns the nonce as a `U256`. Defaults to U256::zero() + pub fn get_nonce_in_context(&self, context: u64, address: H160) -> U256 { let nonce = self .tx_queues .get_nonce(context, address) @@ -336,7 +355,7 @@ impl EVMHandler { "Account {:x?} nonce {:x?} in context {context}", address, nonce ); - Ok(nonce) + nonce } } diff --git a/lib/ain-rs-exports/src/lib.rs b/lib/ain-rs-exports/src/lib.rs index 258cd74e7d..e3a99cf781 100644 --- a/lib/ain-rs-exports/src/lib.rs +++ b/lib/ain-rs-exports/src/lib.rs @@ -50,7 +50,7 @@ pub mod ffi { extern "Rust" { fn evm_get_balance(address: [u8; 20]) -> Result; fn evm_get_nonce(address: [u8; 20]) -> Result; - fn evm_get_nonce_in_context(context: u64, address: [u8; 20]) -> Result; + fn evm_get_nonce_in_context(context: u64, address: [u8; 20]) -> u64; fn evm_add_balance( context: u64, @@ -185,7 +185,7 @@ pub fn evm_get_nonce(address: [u8; 20]) -> Result> { .handlers .evm .get_nonce(account, latest_block_number) - .unwrap_or_default(); // convert to try_evm_get_balance - Default to 0 for now + .unwrap_or_default(); Ok(nonce.as_u64()) } @@ -196,21 +196,13 @@ pub fn evm_get_nonce(address: [u8; 20]) -> Result> { /// * `context` - The context queue number. /// * `address` - The EVM address of the account. /// -/// # Errors -/// -/// Throws an Error if the address is not a valid EVM address. -/// /// # Returns /// -/// Returns the nonce of the account in a specific context as a `u64` on success. -pub fn evm_get_nonce_in_context(context: u64, address: [u8; 20]) -> Result> { +/// Returns the nonce of the account in a specific context as a `u64` +pub fn evm_get_nonce_in_context(context: u64, address: [u8; 20]) -> u64 { let address = H160::from(address); - let nonce = RUNTIME - .handlers - .evm - .get_nonce_in_context(context, address) - .unwrap_or_default(); // convert to try_evm_get_balance - Default to 0 for now - Ok(nonce.as_u64()) + let nonce = RUNTIME.handlers.evm.get_nonce_in_context(context, address); + nonce.as_u64() } /// EvmIn. Send DFI to an EVM account. diff --git a/src/miner.cpp b/src/miner.cpp index 33ed2be74a..fc3b7ab548 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -793,7 +793,7 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda break; } - const auto nonce = evm_get_nonce(txResult.sender); + const auto nonce = evm_get_nonce_in_context(evmContext, txResult.sender); if (nonce != txResult.nonce) { customTxPassed = false; break; From 34fa06ddfd3ec8f19b85fe92e16b43e50ae607c9 Mon Sep 17 00:00:00 2001 From: jouzo Date: Wed, 7 Jun 2023 08:12:12 +0200 Subject: [PATCH 14/20] Get the next valid nonce --- lib/ain-evm/src/evm.rs | 16 ++++++++-------- lib/ain-evm/src/tx_queue.rs | 11 ++++++----- lib/ain-rs-exports/src/lib.rs | 13 ++++++++----- src/miner.cpp | 2 +- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/lib/ain-evm/src/evm.rs b/lib/ain-evm/src/evm.rs index e72fb4ad26..570e19bd39 100644 --- a/lib/ain-evm/src/evm.rs +++ b/lib/ain-evm/src/evm.rs @@ -317,12 +317,12 @@ impl EVMHandler { Ok(nonce) } - /// Retrieves the nonce for the specified account within a particular context. + /// Retrieves the next valid nonce for the specified account within a particular context. /// - /// The method first attempts to retrieve the nonce from the transaction queue associated with the provided context. - /// If no nonce is found in the transaction queue, that means that no transactions have been queued for this account - /// in this context), it falls back to retrieving the nonce from the storage at the latest block - /// If no nonce is found in the storage (i.e., no transactions for this account have been committed yet), + /// The method first attempts to retrieve the next valid nonce from the transaction queue associated with the + /// provided context. If no nonce is found in the transaction queue, that means that no transactions have been + /// queued for this account in this context. It falls back to retrieving the nonce from the storage at the latest + /// block. If no nonce is found in the storage (i.e., no transactions for this account have been committed yet), /// the nonce is defaulted to zero. /// /// This method provides a unified view of the nonce for an account, taking into account both transactions that are @@ -335,11 +335,11 @@ impl EVMHandler { /// /// # Returns /// - /// Returns the nonce as a `U256`. Defaults to U256::zero() - pub fn get_nonce_in_context(&self, context: u64, address: H160) -> U256 { + /// Returns the next valid nonce as a `U256`. Defaults to U256::zero() + pub fn get_next_valid_nonce_in_context(&self, context: u64, address: H160) -> U256 { let nonce = self .tx_queues - .get_nonce(context, address) + .get_next_valid_nonce(context, address) .unwrap_or_else(|| { let latest_block = self .storage diff --git a/lib/ain-evm/src/tx_queue.rs b/lib/ain-evm/src/tx_queue.rs index 70b44c4a15..548a07db5b 100644 --- a/lib/ain-evm/src/tx_queue.rs +++ b/lib/ain-evm/src/tx_queue.rs @@ -111,16 +111,16 @@ impl TransactionQueueMap { .map_or(0, TransactionQueue::len) } - /// `get_nonce` returns the latest nonce for the account with the provided address in the - /// `TransactionQueue` associated with the provided context ID. This method assumes that + /// `get_next_valid_nonce` returns the next valid nonce for the account with the provided address + /// in the `TransactionQueue` associated with the provided context ID. This method assumes that /// only signed transactions (which include a nonce) are added to the queue using `queue_tx` /// and that their nonces are in increasing order. - pub fn get_nonce(&self, context_id: u64, address: H160) -> Option { + pub fn get_next_valid_nonce(&self, context_id: u64, address: H160) -> Option { self.queues .read() .unwrap() .get(&context_id) - .map_or(None, |queue| queue.get_nonce(address)) + .map_or(None, |queue| queue.get_next_valid_nonce(address)) } } @@ -179,12 +179,13 @@ impl TransactionQueue { self.transactions.lock().unwrap().len() } - pub fn get_nonce(&self, address: H160) -> Option { + pub fn get_next_valid_nonce(&self, address: H160) -> Option { self.account_nonces .lock() .unwrap() .get(&address) .map(ToOwned::to_owned) + .map(|nonce| nonce + 1) } } diff --git a/lib/ain-rs-exports/src/lib.rs b/lib/ain-rs-exports/src/lib.rs index e3a99cf781..9a71afa7ed 100644 --- a/lib/ain-rs-exports/src/lib.rs +++ b/lib/ain-rs-exports/src/lib.rs @@ -50,7 +50,7 @@ pub mod ffi { extern "Rust" { fn evm_get_balance(address: [u8; 20]) -> Result; fn evm_get_nonce(address: [u8; 20]) -> Result; - fn evm_get_nonce_in_context(context: u64, address: [u8; 20]) -> u64; + fn evm_get_next_valid_nonce_in_context(context: u64, address: [u8; 20]) -> u64; fn evm_add_balance( context: u64, @@ -189,7 +189,7 @@ pub fn evm_get_nonce(address: [u8; 20]) -> Result> { Ok(nonce.as_u64()) } -/// Retrieves the nonce of an EVM account in a specific context +/// Retrieves the next valid nonce of an EVM account in a specific context /// /// # Arguments /// @@ -198,10 +198,13 @@ pub fn evm_get_nonce(address: [u8; 20]) -> Result> { /// /// # Returns /// -/// Returns the nonce of the account in a specific context as a `u64` -pub fn evm_get_nonce_in_context(context: u64, address: [u8; 20]) -> u64 { +/// Returns the next valid nonce of the account in a specific context as a `u64` +pub fn evm_get_next_valid_nonce_in_context(context: u64, address: [u8; 20]) -> u64 { let address = H160::from(address); - let nonce = RUNTIME.handlers.evm.get_nonce_in_context(context, address); + let nonce = RUNTIME + .handlers + .evm + .get_next_valid_nonce_in_context(context, address); nonce.as_u64() } diff --git a/src/miner.cpp b/src/miner.cpp index fc3b7ab548..102123152f 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -793,7 +793,7 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda break; } - const auto nonce = evm_get_nonce_in_context(evmContext, txResult.sender); + const auto nonce = evm_get_next_valid_nonce_in_context(evmContext, txResult.sender); if (nonce != txResult.nonce) { customTxPassed = false; break; From 55885e570d0480bcb80b3a55a0e23c0acf9242de Mon Sep 17 00:00:00 2001 From: Peter Bushnell Date: Wed, 7 Jun 2023 11:11:48 +0100 Subject: [PATCH 15/20] Track failed nonces and try them once other TXs are added --- src/miner.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/miner.cpp b/src/miner.cpp index e0ac06af5d..3abddcb06d 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -637,6 +637,8 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda indexed_modified_transaction_set mapModifiedTx; // Keep track of entries that failed inclusion, to avoid duplicate work CTxMemPool::setEntries failedTx; + // Keep track of EVM entries that failed nonce check + std::multimap failedNonces; // Start by adding all descendants of previously added txs to mapModifiedTx // and modifying them for their already included ancestors @@ -657,7 +659,7 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda // Copy of the view CCoinsViewCache coinsView(&::ChainstateActive().CoinsTip()); - while (mi != mempool.mapTx.get().end() || !mapModifiedTx.empty()) + while (mi != mempool.mapTx.get().end() || !mapModifiedTx.empty() || !failedNonces.empty()) { // First try to find a new transaction in mapTx to evaluate. if (mi != mempool.mapTx.get().end() && @@ -671,7 +673,11 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda bool fUsingModified = false; modtxscoreiter modit = mapModifiedTx.get().begin(); - if (mi == mempool.mapTx.get().end()) { + if (mi == mempool.mapTx.get().end() && mapModifiedTx.empty()) { + const auto it = failedNonces.begin(); + iter = it->second; + failedNonces.erase(it); + } else if (mi == mempool.mapTx.get().end()) { // We're out of entries in mapTx; use the entry from mapModifiedTx iter = modit->iter; fUsingModified = true; @@ -796,6 +802,10 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda const auto nonce = evm_get_next_valid_nonce_in_context(evmContext, txResult.sender); if (nonce != txResult.nonce) { + // Only add if not already in failed TXs to prevent adding on second attempt. + if (!failedTx.count(iter)) { + failedNonces.emplace(nonce, iter); + } customTxPassed = false; break; } From 63f986f4e99a0dfcb7af7126f9221a60378db7b6 Mon Sep 17 00:00:00 2001 From: jouzo Date: Wed, 7 Jun 2023 12:40:04 +0200 Subject: [PATCH 16/20] Prevalidate all TXs with nonce > account nonce --- lib/ain-evm/src/evm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ain-evm/src/evm.rs b/lib/ain-evm/src/evm.rs index 570e19bd39..dd1e8ffc1c 100644 --- a/lib/ain-evm/src/evm.rs +++ b/lib/ain-evm/src/evm.rs @@ -166,7 +166,7 @@ impl EVMHandler { signed_tx.nonce() ); debug!("[validate_raw_tx] nonce : {:#?}", nonce); - if nonce != signed_tx.nonce() { + if nonce > signed_tx.nonce() { return Err(anyhow!( "Invalid nonce. Account nonce {}, signed_tx nonce {}", nonce, From 85124671ac7c95ea4437e18a1b7c9fe5e448cace Mon Sep 17 00:00:00 2001 From: Peter Bushnell Date: Wed, 7 Jun 2023 12:13:45 +0100 Subject: [PATCH 17/20] Test transactions added in order --- src/validation.cpp | 58 ++++++++++++++++++---------------- test/functional/feature_evm.py | 33 ++++++++++--------- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/src/validation.cpp b/src/validation.cpp index 32e016c776..487925989f 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -540,38 +540,40 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // Check for conflicts with in-memory transactions std::set setConflicts; - for (const CTxIn &txin : tx.vin) - { - const CTransaction* ptxConflicting = pool.GetConflictTx(txin.prevout); - if (ptxConflicting) { - if (!setConflicts.count(ptxConflicting->GetHash())) - { - // Allow opt-out of transaction replacement by setting - // nSequence > MAX_BIP125_RBF_SEQUENCE (SEQUENCE_FINAL-2) on all inputs. - // - // SEQUENCE_FINAL-1 is picked to still allow use of nLockTime by - // non-replaceable transactions. All inputs rather than just one - // is for the sake of multi-party protocols, where we don't - // want a single party to be able to disable replacement. - // - // The opt-out ignores descendants as anyone relying on - // first-seen mempool behavior should be checking all - // unconfirmed ancestors anyway; doing otherwise is hopelessly - // insecure. - bool fReplacementOptOut = true; - for (const CTxIn &_txin : ptxConflicting->vin) + if (!IsEVMTx(tx)) { + for (const CTxIn &txin : tx.vin) + { + const CTransaction* ptxConflicting = pool.GetConflictTx(txin.prevout); + if (ptxConflicting) { + if (!setConflicts.count(ptxConflicting->GetHash())) { - if (_txin.nSequence <= MAX_BIP125_RBF_SEQUENCE) + // Allow opt-out of transaction replacement by setting + // nSequence > MAX_BIP125_RBF_SEQUENCE (SEQUENCE_FINAL-2) on all inputs. + // + // SEQUENCE_FINAL-1 is picked to still allow use of nLockTime by + // non-replaceable transactions. All inputs rather than just one + // is for the sake of multi-party protocols, where we don't + // want a single party to be able to disable replacement. + // + // The opt-out ignores descendants as anyone relying on + // first-seen mempool behavior should be checking all + // unconfirmed ancestors anyway; doing otherwise is hopelessly + // insecure. + bool fReplacementOptOut = true; + for (const CTxIn &_txin : ptxConflicting->vin) { - fReplacementOptOut = false; - break; + if (_txin.nSequence <= MAX_BIP125_RBF_SEQUENCE) + { + fReplacementOptOut = false; + break; + } + } + if (fReplacementOptOut) { + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_DUPLICATE, "txn-mempool-conflict"); } - } - if (fReplacementOptOut) { - return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_DUPLICATE, "txn-mempool-conflict"); - } - setConflicts.insert(ptxConflicting->GetHash()); + setConflicts.insert(ptxConflicting->GetHash()); + } } } } diff --git a/test/functional/feature_evm.py b/test/functional/feature_evm.py index 9c8f3aaaad..8018c78e6e 100755 --- a/test/functional/feature_evm.py +++ b/test/functional/feature_evm.py @@ -150,9 +150,6 @@ def run_test(self): self.nodes[0].generate(1) self.sync_blocks() - # Try and send a TX with a high nonce - assert_raises_rpc_error(-32600, "evm tx failed to validate", self.nodes[0].evmtx, ethAddress, 1, 21, 21000, to_address, 1) - # Check Eth balances before transfer assert_equal(int(self.nodes[0].eth_getBalance(ethAddress)[2:], 16), 10000000000000000000) assert_equal(int(self.nodes[0].eth_getBalance(to_address)[2:], 16), 0) @@ -161,7 +158,8 @@ def run_test(self): miner_before = Decimal(self.nodes[0].getaccount(self.nodes[0].get_genesis_keys().ownerAuthAddress)[0].split('@')[0]) # Test EVM Tx - tx = self.nodes[0].evmtx(ethAddress, 0, 21, 21000, to_address, 1) + tx = self.nodes[0].evmtx(ethAddress, 0, 21, 21001, to_address, 1) + tx2 = self.nodes[0].evmtx(ethAddress, 1, 21, 21001, to_address, 1) raw_tx = self.nodes[0].getrawtransaction(tx) self.sync_mempools() @@ -170,26 +168,31 @@ def run_test(self): assert_equal(result[0]['blockHash'], '0x0000000000000000000000000000000000000000000000000000000000000000') assert_equal(result[0]['blockNumber'], 'null') assert_equal(result[0]['from'], ethAddress) - assert_equal(result[0]['gas'], '0x5208') + assert_equal(result[0]['gas'], '0x5209') assert_equal(result[0]['gasPrice'], '0x4e3b29200') - assert_equal(result[0]['hash'], '0x8c99e9f053e033078e33c2756221f38fd529b914165090a615f27961de687497') + assert_equal(result[0]['hash'], '0xadf0fbeb972cdc4a82916d12ffc6019f60005de6dde1bbc7cb4417fe5a7b1bcb') assert_equal(result[0]['input'], '0x') assert_equal(result[0]['nonce'], '0x0') assert_equal(result[0]['to'], to_address.lower()) assert_equal(result[0]['transactionIndex'], '0x0') assert_equal(result[0]['value'], '0xde0b6b3a7640000') - assert_equal(result[0]['v'], '0x25') - assert_equal(result[0]['r'], '0x37f41c543402c9b02b35b45ef43ac31a63dcbeba0c622249810ecdec00aee376') - assert_equal(result[0]['s'], '0x5eb2be77eb0c7a1875a53ba15fc6afe246fbffe869157edbde64270e41ba045e') + assert_equal(result[0]['v'], '0x26') + assert_equal(result[0]['r'], '0x3a0587be1a14bd5e68bc883e627f3c0999cff9458e30ea8049f17bd7369d7d9c') + assert_equal(result[0]['s'], '0x1876f296657bc56499cc6398617f97b2327fa87189c0a49fb671b4361876142a') - # Check mempools for TX - assert_equal(self.nodes[0].getrawmempool(), [tx]) - assert_equal(self.nodes[1].getrawmempool(), [tx]) + # Check mempools for TX in reverse order of nonce + assert_equal(self.nodes[0].getrawmempool(), [tx2, tx]) + assert_equal(self.nodes[1].getrawmempool(), [tx2, tx]) self.nodes[0].generate(1) + # Check TXs in block in correct order + block_txs = self.nodes[0].getblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount()))['tx'] + assert_equal(block_txs[1], tx) + assert_equal(block_txs[2], tx2) + # Check Eth balances before transfer - assert_equal(int(self.nodes[0].eth_getBalance(ethAddress)[2:], 16), 9000000000000000000) - assert_equal(int(self.nodes[0].eth_getBalance(to_address)[2:], 16), 1000000000000000000) + assert_equal(int(self.nodes[0].eth_getBalance(ethAddress)[2:], 16), 8000000000000000000) + assert_equal(int(self.nodes[0].eth_getBalance(to_address)[2:], 16), 2000000000000000000) # Check miner account balance after transfer miner_after = Decimal(self.nodes[0].getaccount(self.nodes[0].get_genesis_keys().ownerAuthAddress)[0].split('@')[0]) @@ -201,7 +204,7 @@ def run_test(self): # Check EVM Tx shows in block on EVM side block = self.nodes[0].eth_getBlockByNumber("latest", False) - assert_equal(block['transactions'], ['0x8c99e9f053e033078e33c2756221f38fd529b914165090a615f27961de687497']) + assert_equal(block['transactions'], ['0xadf0fbeb972cdc4a82916d12ffc6019f60005de6dde1bbc7cb4417fe5a7b1bcb', '0x66c380af8f76295bab799d1228af75bd3c436b7bbeb9d93acd8baac9377a851a']) # Check pending TXs now empty assert_equal(self.nodes[0].eth_pendingTransactions(), []) From bc0999bc7b8818df1f44b9951d283565359b5f7f Mon Sep 17 00:00:00 2001 From: Peter Bushnell Date: Wed, 7 Jun 2023 12:16:45 +0100 Subject: [PATCH 18/20] Time order mempool TXs --- test/functional/feature_evm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/feature_evm.py b/test/functional/feature_evm.py index 8018c78e6e..9fa521b35b 100755 --- a/test/functional/feature_evm.py +++ b/test/functional/feature_evm.py @@ -46,8 +46,8 @@ def set_test_params(self): self.num_nodes = 2 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'], - ['-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'] + ['-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', '-txordering=1'], + ['-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', '-txordering=1'] ] def run_test(self): From d970c2be235d214739c8ff9081f823e177d8362b Mon Sep 17 00:00:00 2001 From: jouzo Date: Wed, 7 Jun 2023 16:23:46 +0200 Subject: [PATCH 19/20] Remove commented out gas check in validate raw tx --- lib/ain-evm/src/evm.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/ain-evm/src/evm.rs b/lib/ain-evm/src/evm.rs index dd1e8ffc1c..24690868c4 100644 --- a/lib/ain-evm/src/evm.rs +++ b/lib/ain-evm/src/evm.rs @@ -175,11 +175,6 @@ impl EVMHandler { .into()); } - // TODO validate balance to pay gas - // if account.balance < MIN_GAS { - // return Err(anyhow!("Insufficiant balance to pay fees").into()); - // } - Ok(signed_tx) } From ebffb8297af0b2a88d2dbcf20e4be7cdbbc7e49d Mon Sep 17 00:00:00 2001 From: Peter Bushnell Date: Thu, 8 Jun 2023 04:11:43 +0100 Subject: [PATCH 20/20] Add more EVM TXs for test --- test/functional/feature_evm.py | 57 ++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/test/functional/feature_evm.py b/test/functional/feature_evm.py index 9fa521b35b..cff80ff16a 100755 --- a/test/functional/feature_evm.py +++ b/test/functional/feature_evm.py @@ -46,8 +46,8 @@ def set_test_params(self): self.num_nodes = 2 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', '-txordering=1'], - ['-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', '-txordering=1'] + ['-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'], + ['-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 run_test(self): @@ -158,41 +158,45 @@ def run_test(self): miner_before = Decimal(self.nodes[0].getaccount(self.nodes[0].get_genesis_keys().ownerAuthAddress)[0].split('@')[0]) # Test EVM Tx - tx = self.nodes[0].evmtx(ethAddress, 0, 21, 21001, to_address, 1) + tx3 = self.nodes[0].evmtx(ethAddress, 2, 21, 21001, to_address, 1) tx2 = self.nodes[0].evmtx(ethAddress, 1, 21, 21001, to_address, 1) + tx = self.nodes[0].evmtx(ethAddress, 0, 21, 21001, to_address, 1) + tx4 = self.nodes[0].evmtx(ethAddress, 3, 21, 21001, to_address, 1) raw_tx = self.nodes[0].getrawtransaction(tx) self.sync_mempools() # Check the pending TXs result = self.nodes[0].eth_pendingTransactions() - assert_equal(result[0]['blockHash'], '0x0000000000000000000000000000000000000000000000000000000000000000') - assert_equal(result[0]['blockNumber'], 'null') - assert_equal(result[0]['from'], ethAddress) - assert_equal(result[0]['gas'], '0x5209') - assert_equal(result[0]['gasPrice'], '0x4e3b29200') - assert_equal(result[0]['hash'], '0xadf0fbeb972cdc4a82916d12ffc6019f60005de6dde1bbc7cb4417fe5a7b1bcb') - assert_equal(result[0]['input'], '0x') - assert_equal(result[0]['nonce'], '0x0') - assert_equal(result[0]['to'], to_address.lower()) - assert_equal(result[0]['transactionIndex'], '0x0') - assert_equal(result[0]['value'], '0xde0b6b3a7640000') - assert_equal(result[0]['v'], '0x26') - assert_equal(result[0]['r'], '0x3a0587be1a14bd5e68bc883e627f3c0999cff9458e30ea8049f17bd7369d7d9c') - assert_equal(result[0]['s'], '0x1876f296657bc56499cc6398617f97b2327fa87189c0a49fb671b4361876142a') - - # Check mempools for TX in reverse order of nonce - assert_equal(self.nodes[0].getrawmempool(), [tx2, tx]) - assert_equal(self.nodes[1].getrawmempool(), [tx2, tx]) + assert_equal(result[2]['blockHash'], '0x0000000000000000000000000000000000000000000000000000000000000000') + assert_equal(result[2]['blockNumber'], 'null') + assert_equal(result[2]['from'], ethAddress) + assert_equal(result[2]['gas'], '0x5209') + assert_equal(result[2]['gasPrice'], '0x4e3b29200') + assert_equal(result[2]['hash'], '0xadf0fbeb972cdc4a82916d12ffc6019f60005de6dde1bbc7cb4417fe5a7b1bcb') + assert_equal(result[2]['input'], '0x') + assert_equal(result[2]['nonce'], '0x0') + assert_equal(result[2]['to'], to_address.lower()) + assert_equal(result[2]['transactionIndex'], '0x0') + assert_equal(result[2]['value'], '0xde0b6b3a7640000') + assert_equal(result[2]['v'], '0x26') + assert_equal(result[2]['r'], '0x3a0587be1a14bd5e68bc883e627f3c0999cff9458e30ea8049f17bd7369d7d9c') + assert_equal(result[2]['s'], '0x1876f296657bc56499cc6398617f97b2327fa87189c0a49fb671b4361876142a') + + # Check mempools for TXs + assert_equal(self.nodes[0].getrawmempool(), [tx3, tx2, tx4, tx]) + assert_equal(self.nodes[1].getrawmempool(), [tx3, tx2, tx4, tx]) self.nodes[0].generate(1) # Check TXs in block in correct order block_txs = self.nodes[0].getblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount()))['tx'] assert_equal(block_txs[1], tx) assert_equal(block_txs[2], tx2) + assert_equal(block_txs[3], tx3) + assert_equal(block_txs[4], tx4) # Check Eth balances before transfer - assert_equal(int(self.nodes[0].eth_getBalance(ethAddress)[2:], 16), 8000000000000000000) - assert_equal(int(self.nodes[0].eth_getBalance(to_address)[2:], 16), 2000000000000000000) + assert_equal(int(self.nodes[0].eth_getBalance(ethAddress)[2:], 16), 6000000000000000000) + assert_equal(int(self.nodes[0].eth_getBalance(to_address)[2:], 16), 4000000000000000000) # Check miner account balance after transfer miner_after = Decimal(self.nodes[0].getaccount(self.nodes[0].get_genesis_keys().ownerAuthAddress)[0].split('@')[0]) @@ -204,7 +208,12 @@ def run_test(self): # Check EVM Tx shows in block on EVM side block = self.nodes[0].eth_getBlockByNumber("latest", False) - assert_equal(block['transactions'], ['0xadf0fbeb972cdc4a82916d12ffc6019f60005de6dde1bbc7cb4417fe5a7b1bcb', '0x66c380af8f76295bab799d1228af75bd3c436b7bbeb9d93acd8baac9377a851a']) + assert_equal(block['transactions'], [ + '0xadf0fbeb972cdc4a82916d12ffc6019f60005de6dde1bbc7cb4417fe5a7b1bcb', + '0x66c380af8f76295bab799d1228af75bd3c436b7bbeb9d93acd8baac9377a851a', + '0x02b05a6646feb65bf9491f9551e02678263239dc2512d73c9ad6bc80dc1c13ff', + '0x1d4c8a49ad46d9362c805d6cdf9a8937ba115eec9def17b3efe23a09ee694e5c' + ]) # Check pending TXs now empty assert_equal(self.nodes[0].eth_pendingTransactions(), [])