Skip to content
Draft
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
14 changes: 14 additions & 0 deletions crates/anvil/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,19 @@ pub struct NodeArgs {
#[arg(short, long, visible_alias = "blockTime", value_name = "SECONDS", value_parser = duration_from_secs_f64)]
pub block_time: Option<Duration>,

/// Maximum time (in seconds) into a slot at which a transaction can still be included.
///
/// When interval mining is active, simulates a validator cutoff: transactions submitted after
/// `slot_start + max_tx_inclusion_time_in_slot` are excluded from the current block and
/// reconsidered in the next one.
#[arg(
long,
visible_alias = "maxTxInclusionTimeInSlot",
value_name = "SECONDS",
requires = "block_time"
)]
pub max_tx_inclusion_time_in_slot: Option<u64>,
Comment on lines +91 to +102
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes sense


/// Slots in an epoch
#[arg(long, value_name = "SLOTS_IN_AN_EPOCH", default_value_t = 32)]
pub slots_in_an_epoch: u64,
Expand Down Expand Up @@ -287,6 +300,7 @@ impl NodeArgs {
.with_disable_default_create2_deployer(self.evm.disable_default_create2_deployer)
.with_disable_pool_balance_checks(self.evm.disable_pool_balance_checks)
.with_slots_in_an_epoch(self.slots_in_an_epoch)
.with_max_tx_inclusion_time_in_slot(self.max_tx_inclusion_time_in_slot)
.with_memory_limit(self.evm.memory_limit)
.with_cache_path(self.cache_path))
}
Expand Down
10 changes: 10 additions & 0 deletions crates/anvil/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ pub struct NodeConfig {
pub disable_pool_balance_checks: bool,
/// Slots in an epoch
pub slots_in_an_epoch: u64,
/// Maximum time (in seconds) into a slot at which a transaction can still be included.
pub max_tx_inclusion_time_in_slot: Option<u64>,
/// The memory limit per EVM execution in bytes.
pub memory_limit: Option<u64>,
/// Factory used by `anvil` to extend the EVM's precompiles.
Expand Down Expand Up @@ -496,6 +498,7 @@ impl Default for NodeConfig {
disable_default_create2_deployer: false,
disable_pool_balance_checks: false,
slots_in_an_epoch: 32,
max_tx_inclusion_time_in_slot: None,
memory_limit: None,
precompile_factory: None,
networks: Default::default(),
Expand Down Expand Up @@ -793,6 +796,13 @@ impl NodeConfig {
self
}

/// Sets the maximum time (in seconds) into a slot at which a transaction can still be included.
#[must_use]
pub fn with_max_tx_inclusion_time_in_slot(mut self, v: Option<u64>) -> Self {
self.max_tx_inclusion_time_in_slot = v;
self
}

/// Sets the port to use
#[must_use]
pub fn with_port(mut self, port: u16) -> Self {
Expand Down
30 changes: 28 additions & 2 deletions crates/anvil/src/eth/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ use alloy_rpc_types::{
txpool::{TxpoolContent, TxpoolInspect, TxpoolInspectSummary, TxpoolStatus},
};
use alloy_rpc_types_eth::FillTransaction;
use alloy_rpc_types_eth::erc4337::TransactionConditional;
use alloy_serde::WithOtherFields;
use alloy_sol_types::{SolCall, SolValue, sol};
use alloy_transport::TransportErrorKind;
Expand Down Expand Up @@ -1169,6 +1170,7 @@ impl EthApi {
provides: vec![to_marker(nonce, *pending_transaction.sender())],
pending_transaction,
priority,
conditions: self.compute_tx_conditions(),
};

let tx = self.pool.add_transaction(pool_transaction)?;
Expand Down Expand Up @@ -3437,13 +3439,37 @@ impl EthApi {
) -> Result<TxHash> {
let from = *pending_transaction.sender();
let priority = self.transaction_priority(&pending_transaction.transaction);
let pool_transaction =
PoolTransaction { requires, provides, pending_transaction, priority };
let pool_transaction = PoolTransaction {
requires,
provides,
pending_transaction,
priority,
conditions: self.compute_tx_conditions(),
};
let tx = self.pool.add_transaction(pool_transaction)?;
trace!(target: "node", "Added transaction: [{:?}] sender={:?}", tx.hash(), from);
Ok(*tx.hash())
}

/// Computes optional `TransactionConditional` based on the `max_tx_inclusion_time_in_slot`
/// setting. If the transaction arrives after `last_block_timestamp + max_time`, it is too
/// late for the next block, so we set `block_number_min = current_block + 2`.
fn compute_tx_conditions(&self) -> Option<TransactionConditional> {
let max_time = self.backend.max_tx_inclusion_time_in_slot()?;
let current_time = self.backend.time().current_time();
let current_block = self.backend.best_number();
let last_block_ts = self.backend.time().last_timestamp();

if current_time > last_block_ts.saturating_add(max_time) {
Some(TransactionConditional {
block_number_min: Some(current_block + 2),
..Default::default()
})
} else {
None
}
}

/// Returns the current state root
pub async fn state_root(&self) -> Option<B256> {
self.backend.get_db().read().await.maybe_state_root()
Expand Down
36 changes: 34 additions & 2 deletions crates/anvil/src/eth/backend/mem/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use alloy_chains::NamedChain;
use alloy_consensus::{
Blob, BlockHeader, EnvKzgSettings, Header, Signed, Transaction as TransactionTrait,
TrieAccount, TxEnvelope, Typed2718,
conditional::BlockConditionalAttributes,
proofs::{calculate_receipt_root, calculate_transaction_root},
transaction::Recovered,
};
Expand Down Expand Up @@ -228,6 +229,8 @@ pub struct Backend {
mining: Arc<tokio::sync::Mutex<()>>,
/// Disable pool balance checks
disable_pool_balance_checks: bool,
/// Maximum time (in seconds) into a slot at which a transaction can still be included.
max_tx_inclusion_time_in_slot: Option<u64>,
}

impl Backend {
Expand Down Expand Up @@ -297,9 +300,19 @@ impl Backend {
states = states.disk_path(cache_path);
}

let (slots_in_an_epoch, precompile_factory, disable_pool_balance_checks) = {
let (
slots_in_an_epoch,
precompile_factory,
disable_pool_balance_checks,
max_tx_inclusion_time_in_slot,
) = {
let cfg = node_config.read().await;
(cfg.slots_in_an_epoch, cfg.precompile_factory.clone(), cfg.disable_pool_balance_checks)
(
cfg.slots_in_an_epoch,
cfg.precompile_factory.clone(),
cfg.disable_pool_balance_checks,
cfg.max_tx_inclusion_time_in_slot,
)
};

let backend = Self {
Expand All @@ -325,6 +338,7 @@ impl Backend {
precompile_factory,
mining: Arc::new(tokio::sync::Mutex::new(())),
disable_pool_balance_checks,
max_tx_inclusion_time_in_slot,
};

if let Some(interval_block_time) = automine_block_time {
Expand Down Expand Up @@ -654,6 +668,11 @@ impl Backend {
self.blockchain.storage.read().best_number
}

/// Returns the configured max tx inclusion time in slot, if any.
pub fn max_tx_inclusion_time_in_slot(&self) -> Option<u64> {
self.max_tx_inclusion_time_in_slot
}

/// Sets the block number
pub fn set_block_number(&self, number: u64) {
let mut env = self.env.write();
Expand Down Expand Up @@ -1311,6 +1330,18 @@ impl Backend {
// to ensure the timestamp is as close as possible to the actual execution.
env.evm_env.block_env.timestamp = U256::from(self.time.next_timestamp());

// Filter out transactions whose conditions are not met for this block.
let block_timestamp: u64 = env.evm_env.block_env.timestamp.to();
let block_attrs = BlockConditionalAttributes::new(block_number, block_timestamp);
let pool_transactions: Vec<_> = pool_transactions
.into_iter()
.filter(|tx| {
tx.conditions
.as_ref()
.map_or(true, |c| c.matches_block_attributes(&block_attrs))
})
.collect();

let executor = TransactionExecutor {
db: &mut **db,
validator: self,
Expand Down Expand Up @@ -2678,6 +2709,7 @@ impl Backend {
requires: vec![],
provides: vec![],
priority: crate::eth::pool::transactions::TransactionPriority(0),
conditions: None,
})
})
.collect();
Expand Down
10 changes: 10 additions & 0 deletions crates/anvil/src/eth/backend/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ impl TimeManager {
*self.offset.read()
}

/// Returns the timestamp of the last mined block.
pub fn last_timestamp(&self) -> u64 {
*self.last_timestamp.read()
}

/// Adds the given `offset` to the already tracked offset and returns the result
fn add_offset(&self, offset: i128) -> i128 {
let mut current = self.offset.write();
Expand Down Expand Up @@ -136,6 +141,11 @@ impl TimeManager {
let (next_timestamp, _) = self.compute_next_timestamp();
next_timestamp
}

/// Returns the current blockchain-adjusted wall clock time (not the next block's timestamp).
pub fn current_time(&self) -> u64 {
(duration_since_unix_epoch().as_secs() as i128 + self.offset()) as u64
}
}

/// Returns the current duration since unix epoch.
Expand Down
5 changes: 5 additions & 0 deletions crates/anvil/src/eth/pool/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use alloy_primitives::{
Address, TxHash,
map::{HashMap, HashSet},
};
use alloy_rpc_types_eth::erc4337::TransactionConditional;
use anvil_core::eth::transaction::PendingTransaction;
use foundry_primitives::FoundryTxEnvelope;
use parking_lot::RwLock;
Expand Down Expand Up @@ -78,6 +79,8 @@ pub struct PoolTransaction {
pub provides: Vec<TxMarker>,
/// priority of the transaction
pub priority: TransactionPriority,
/// Optional conditions (ERC-7796) that must be met for the transaction to be included.
pub conditions: Option<TransactionConditional>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

smol nit: can we box this, most of the type this will be none

Suggested change
pub conditions: Option<TransactionConditional>,
pub conditions: Option<Box<TransactionConditional>>,

}

// == impl PoolTransaction ==
Expand All @@ -89,6 +92,7 @@ impl PoolTransaction {
requires: vec![],
provides: vec![],
priority: TransactionPriority(0),
conditions: None,
}
}
/// Returns the hash of this transaction
Expand Down Expand Up @@ -129,6 +133,7 @@ impl TryFrom<AnyRpcTransaction> for PoolTransaction {
requires: vec![],
provides: vec![],
priority: TransactionPriority(0),
conditions: None,
})
}
}
Expand Down
Loading
Loading