Skip to content

Commit

Permalink
feat: fees transactions order in mempool (#492)
Browse files Browse the repository at this point in the history
* feat: fees transactions order in mempool

* fix: fix locking

* fix: lint

* fix: fix tests and refactoring

* fix: make FIFO default tx order

* chore: add tests

* chore: rename transactions_order
  • Loading branch information
Romsters authored Dec 17, 2024
1 parent e279c21 commit 4f35268
Show file tree
Hide file tree
Showing 11 changed files with 307 additions and 66 deletions.
9 changes: 7 additions & 2 deletions crates/cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use anvil_zksync_config::types::{
AccountGenerator, CacheConfig, CacheType, Genesis, LogLevel, ShowCalls, ShowGasDetails,
ShowStorageLogs, ShowVMDetails, SystemContractsOptions,
};
use anvil_zksync_config::TestNodeConfig;
use anvil_zksync_config::{types::TransactionOrder, TestNodeConfig};
use clap::{arg, command, Parser, Subcommand};
use rand::{rngs::StdRng, SeedableRng};
use std::env;
Expand Down Expand Up @@ -251,6 +251,10 @@ pub struct Cli {
/// Disable CORS.
#[arg(long, default_missing_value = "true", num_args(0..=1), conflicts_with = "allow_origin", help_heading = "Server options")]
pub no_cors: Option<bool>,

/// Transaction ordering in the mempool.
#[arg(long, default_value = "fifo")]
pub order: TransactionOrder,
}

#[derive(Debug, Subcommand, Clone)]
Expand Down Expand Up @@ -391,7 +395,8 @@ impl Cli {
.with_block_time(self.block_time)
.with_no_mining(self.no_mining)
.with_allow_origin(self.allow_origin)
.with_no_cors(self.no_cors);
.with_no_cors(self.no_cors)
.with_transaction_order(self.order);

if self.emulate_evm && self.dev_system_contracts != Some(SystemContractsOptions::Local) {
return Err(eyre::eyre!(
Expand Down
2 changes: 1 addition & 1 deletion crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ async fn main() -> anyhow::Result<()> {

let time = TimestampManager::default();
let impersonation = ImpersonationManager::default();
let pool = TxPool::new(impersonation.clone());
let pool = TxPool::new(impersonation.clone(), config.transaction_order);
let sealing_mode = if config.no_mining {
BlockSealerMode::noop()
} else if let Some(block_time) = config.block_time {
Expand Down
10 changes: 10 additions & 0 deletions crates/config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ pub struct TestNodeConfig {
pub allow_origin: String,
/// Disable CORS if true
pub no_cors: bool,
/// How transactions are sorted in the mempool
pub transaction_order: TransactionOrder,
}

impl Default for TestNodeConfig {
Expand Down Expand Up @@ -175,6 +177,7 @@ impl Default for TestNodeConfig {
no_mining: false,

max_transactions: 1000,
transaction_order: TransactionOrder::Fifo,

// Server configuration
allow_origin: "*".to_string(),
Expand Down Expand Up @@ -878,6 +881,13 @@ impl TestNodeConfig {
self
}

// Set transactions order in the mempool
#[must_use]
pub fn with_transaction_order(mut self, transaction_order: TransactionOrder) -> Self {
self.transaction_order = transaction_order;
self
}

// Set allow_origin CORS header
#[must_use]
pub fn with_allow_origin(mut self, allow_origin: String) -> Self {
Expand Down
2 changes: 2 additions & 0 deletions crates/config/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod cache;
mod genesis;
mod log;
mod show_details;
mod transaction_order;

pub use account_generator::AccountGenerator;
pub use cache::{CacheConfig, CacheType};
Expand All @@ -11,6 +12,7 @@ pub use genesis::Genesis;
pub use log::LogLevel;
use serde::Deserialize;
pub use show_details::{ShowCalls, ShowGasDetails, ShowStorageLogs, ShowVMDetails};
pub use transaction_order::{TransactionOrder, TransactionPriority};

#[derive(Deserialize, Default, Debug, Copy, Clone, PartialEq, ValueEnum)]
pub enum SystemContractsOptions {
Expand Down
57 changes: 57 additions & 0 deletions crates/config/src/types/transaction_order.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use std::fmt;
use std::str::FromStr;
use zksync_types::{l2::L2Tx, U256};

/// Metric value for the priority of a transaction.
///
/// The `TransactionPriority` determines the ordering of two transactions.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct TransactionPriority(pub U256);

/// Modes that determine the transaction ordering of the mempool
///
/// This type controls the transaction order via the priority metric of a transaction
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum TransactionOrder {
/// Keep the pool transactions sorted in the order they arrive.
///
/// This will essentially assign every transaction the exact priority so the order is
/// determined by their internal submission number
Fifo,
/// This means that it prioritizes transactions based on the fees paid to the miner.
#[default]
Fees,
}

impl TransactionOrder {
/// Returns the priority of the transactions
pub fn priority(&self, tx: &L2Tx) -> TransactionPriority {
match self {
Self::Fifo => TransactionPriority::default(),
Self::Fees => TransactionPriority(tx.common_data.fee.max_fee_per_gas),
}
}
}

impl FromStr for TransactionOrder {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
let order = match s.as_str() {
"fees" => Self::Fees,
"fifo" => Self::Fifo,
_ => return Err(format!("Unknown TransactionOrder: `{s}`")),
};
Ok(order)
}
}

impl fmt::Display for TransactionOrder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
TransactionOrder::Fifo => f.write_str("fifo"),
TransactionOrder::Fees => f.write_str("fees"),
}
}
}
17 changes: 10 additions & 7 deletions crates/core/src/node/in_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use anvil_zksync_config::constants::{
};
use anvil_zksync_config::types::{
CacheConfig, Genesis, ShowCalls, ShowGasDetails, ShowStorageLogs, ShowVMDetails,
SystemContractsOptions,
SystemContractsOptions, TransactionOrder,
};
use anvil_zksync_config::TestNodeConfig;
use colored::Colorize;
Expand Down Expand Up @@ -1108,7 +1108,7 @@ fn contract_address_from_tx_result(execution_result: &VmExecutionResultAndLogs)
impl Default for InMemoryNode {
fn default() -> Self {
let impersonation = ImpersonationManager::default();
let pool = TxPool::new(impersonation.clone());
let pool = TxPool::new(impersonation.clone(), TransactionOrder::Fifo);
let tx_listener = pool.add_tx_listener();
InMemoryNode::new(
None,
Expand Down Expand Up @@ -1161,7 +1161,7 @@ impl InMemoryNode {
// TODO: Refactor InMemoryNode with a builder pattern
pub fn default_fork(fork: Option<ForkDetails>) -> Self {
let impersonation = ImpersonationManager::default();
let pool = TxPool::new(impersonation.clone());
let pool = TxPool::new(impersonation.clone(), TransactionOrder::Fifo);
let tx_listener = pool.add_tx_listener();
Self::new(
fork,
Expand Down Expand Up @@ -1263,7 +1263,10 @@ impl InMemoryNode {
tracing::debug!(count = txs.len(), "applying transactions");

// Create a temporary tx pool (i.e. state is not shared with the node mempool).
let pool = TxPool::new(self.impersonation.clone());
let pool = TxPool::new(
self.impersonation.clone(),
self.read_inner()?.config.transaction_order,
);
pool.add_txs(txs);

// Lock time so that the produced blocks are guaranteed to be sequential in time.
Expand Down Expand Up @@ -2035,7 +2038,7 @@ mod tests {
DEFAULT_ESTIMATE_GAS_SCALE_FACTOR, DEFAULT_FAIR_PUBDATA_PRICE, DEFAULT_L2_GAS_PRICE,
TEST_NODE_NETWORK_ID,
};
use anvil_zksync_config::types::SystemContractsOptions;
use anvil_zksync_config::types::{SystemContractsOptions, TransactionOrder};
use anvil_zksync_config::TestNodeConfig;
use ethabi::{Token, Uint};
use zksync_types::{utils::deployed_address_create, K256PrivateKey, Nonce};
Expand Down Expand Up @@ -2160,7 +2163,7 @@ mod tests {
raw_storage: external_storage.inner.read().unwrap().raw_storage.clone(),
};
let impersonation = ImpersonationManager::default();
let pool = TxPool::new(impersonation.clone());
let pool = TxPool::new(impersonation.clone(), TransactionOrder::Fifo);
let sealer = BlockSealer::new(BlockSealerMode::immediate(1000, pool.add_tx_listener()));
let node = InMemoryNode::new(
Some(ForkDetails {
Expand Down Expand Up @@ -2200,7 +2203,7 @@ mod tests {
#[tokio::test]
async fn test_transact_returns_data_in_built_in_without_security_mode() {
let impersonation = ImpersonationManager::default();
let pool = TxPool::new(impersonation.clone());
let pool = TxPool::new(impersonation.clone(), TransactionOrder::Fifo);
let sealer = BlockSealer::new(BlockSealerMode::immediate(1000, pool.add_tx_listener()));
let node = InMemoryNode::new(
None,
Expand Down
6 changes: 4 additions & 2 deletions crates/core/src/node/in_memory_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,8 @@ impl InMemoryNode {
}

pub fn remove_pool_transactions(&self, address: Address) -> Result<()> {
self.pool.drop_transactions_by_sender(address);
self.pool
.drop_transactions(|tx| tx.transaction.common_data.initiator_address == address);
Ok(())
}

Expand Down Expand Up @@ -499,6 +500,7 @@ mod tests {
use crate::node::time::{ReadTime, TimestampManager};
use crate::node::InMemoryNode;
use crate::node::{BlockSealer, ImpersonationManager, InMemoryNodeInner, Snapshot, TxPool};
use anvil_zksync_config::types::TransactionOrder;
use std::str::FromStr;
use std::sync::{Arc, RwLock};
use zksync_multivm::interface::storage::ReadStorage;
Expand Down Expand Up @@ -626,7 +628,7 @@ mod tests {
rich_accounts: Default::default(),
previous_states: Default::default(),
};
let pool = TxPool::new(impersonation.clone());
let pool = TxPool::new(impersonation.clone(), TransactionOrder::Fifo);
let sealer = BlockSealer::new(BlockSealerMode::immediate(1000, pool.add_tx_listener()));

let node = InMemoryNode {
Expand Down
Loading

0 comments on commit 4f35268

Please sign in to comment.