Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return nonce informations to miner #2025

Merged
merged 21 commits into from
Jun 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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