Skip to content

Commit

Permalink
Return nonce informations to miner (#2025)
Browse files Browse the repository at this point in the history
* Return nonce on evm_prevalidate_raw_tx

* Catch thrown error from evm_prevalidate_raw_tx

* Returns sender address in evm_prevalidate_raw_tx

* Check if nonce is the expected value

* Get evm nonce and balance at latest block height

* With RustRes instead of throw

* Use RustRes in evm_try_queue_tx

* Parse metadata into txMessage

* Add evm_get_nonce_in_context FFI method

* Document TransactionQueue nonce handling

* Return InvalidNonce if queued nonce is not in increasing order

* Add unit test for nonce order

* Use FFI evm_get_nonce_in_context

* Get the next valid nonce

* Track failed nonces and try them once other TXs are added

* Prevalidate all TXs with nonce > account nonce

* Test transactions added in order

* Time order mempool TXs

* Remove commented out gas check in validate raw tx

* Add more EVM TXs for test

---------

Co-authored-by: Peter Bushnell <[email protected]>
  • Loading branch information
Jouzo and Bushstar authored Jun 9, 2023
1 parent ca5599a commit c1e4b5a
Show file tree
Hide file tree
Showing 9 changed files with 418 additions and 87 deletions.
48 changes: 42 additions & 6 deletions lib/ain-evm/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
}

Expand Down Expand Up @@ -316,6 +311,47 @@ impl EVMHandler {
debug!("Account {:x?} nonce {:x?}", address, nonce);
Ok(nonce)
}

/// Retrieves the next valid nonce for the specified account within a particular context.
///
/// 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
/// 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 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_next_valid_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
);
nonce
}
}

use std::fmt;
Expand Down
151 changes: 149 additions & 2 deletions lib/ain-evm/src/tx_queue.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use ethereum_types::{H160, U256};
use rand::Rng;
use std::{
collections::HashMap,
Expand All @@ -20,13 +21,18 @@ 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 {
queues: RwLock::new(HashMap::new()),
}
}

/// `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 {
Expand All @@ -40,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<TransactionQueue> {
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()
Expand All @@ -53,6 +62,20 @@ 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.
/// 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,
Expand All @@ -64,9 +87,12 @@ 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
/// 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<QueueTxWithNativeHash> {
self.queues
.read()
Expand All @@ -90,6 +116,18 @@ impl TransactionQueueMap {
.get(&context_id)
.map_or(0, TransactionQueue::len)
}

/// `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_next_valid_nonce(&self, context_id: u64, address: H160) -> Option<U256> {
self.queues
.read()
.unwrap()
.get(&context_id)
.map_or(None, |queue| queue.get_next_valid_nonce(address))
}
}

#[derive(Debug, Clone)]
Expand All @@ -100,15 +138,20 @@ 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<Vec<QueueTxWithNativeHash>>,
account_nonces: Mutex<HashMap<H160, U256>>,
}

impl TransactionQueue {
pub fn new() -> Self {
Self {
transactions: Mutex::new(Vec::new()),
account_nonces: Mutex::new(HashMap::new()),
}
}

Expand All @@ -128,13 +171,32 @@ impl TransactionQueue {
self.transactions.lock().unwrap().clone()
}

pub fn queue_tx(&self, tx: QueueTxWithNativeHash) {
pub fn queue_tx(&self, tx: QueueTxWithNativeHash) -> Result<(), QueueError> {
if let QueueTx::SignedTx(signed_tx) = &tx.0 {
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 {
self.transactions.lock().unwrap().len()
}

pub fn get_next_valid_nonce(&self, address: H160) -> Option<U256> {
self.account_nonces
.lock()
.unwrap()
.get(&address)
.map(ToOwned::to_owned)
.map(|nonce| nonce + 1)
}
}

impl From<SignedTx> for QueueTx {
Expand All @@ -146,14 +208,99 @@ impl From<SignedTx> for QueueTx {
#[derive(Debug)]
pub enum QueueError {
NoSuchContext,
InvalidNonce((Box<SignedTx>, 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),
}
}
}

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(())
}
}
2 changes: 1 addition & 1 deletion lib/ain-grpc/src/rpc/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
)))
Expand Down
Loading

0 comments on commit c1e4b5a

Please sign in to comment.