Skip to content
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ signet-zenith = { version = "0.16.0-rc.12", path = "crates/zenith" }
signet-test-utils = { version = "0.16.0-rc.12", path = "crates/test-utils" }

# trevm
trevm = { version = "0.34.0", features = ["full_env_cfg"] }
trevm = { version = "0.34.1", features = ["full_env_cfg", "asyncdb"] }

# Alloy periphery crates
alloy-core = "1.4"
Expand Down
2 changes: 1 addition & 1 deletion crates/evm/src/sys/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ fn adjust_decimals(amount: U256, decimals: u8, target_decimals: u8) -> U256 {
let divisor = U256::from(10u64).pow(U256::from(divisor_exp));
amount / divisor
} else {
let multiplier_exp = target_decimals.checked_sub(decimals).unwrap_or_default();
let multiplier_exp = target_decimals.saturating_sub(decimals);
let multiplier = U256::from(10u64).pow(U256::from(multiplier_exp));
amount * multiplier
}
Expand Down
22 changes: 11 additions & 11 deletions crates/sim/src/cache/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,11 @@ impl SimItem {
}
}

fn check_tx<S>(&self, source: &S) -> Result<SimItemValidity, Box<dyn std::error::Error>>
async fn check_tx<S>(&self, source: &S) -> Result<SimItemValidity, Box<dyn std::error::Error>>
where
S: StateSource,
{
let item = self.as_tx().expect("SimItem is not a Tx");

let total = U256::from(item.max_fee_per_gas() * item.gas_limit() as u128) + item.value();

source
Expand All @@ -139,10 +138,11 @@ impl SimItem {
// nonce is equal and balance is sufficient
SimItemValidity::Now
})
.await
.map_err(Into::into)
}

fn check_bundle_tx_list<S>(
async fn check_bundle_tx_list<S>(
items: impl Iterator<Item = TxRequirement>,
source: &S,
) -> Result<SimItemValidity, S::Error>
Expand All @@ -160,7 +160,7 @@ impl SimItem {

// Peek to perform the balance check for the first tx
if let Some(first) = items.peek() {
let info = source.account_details(&first.signer)?;
let info = source.account_details(&first.signer).await?;

// check balance for the first tx is sufficient
if first.balance > info.balance {
Expand All @@ -175,7 +175,7 @@ impl SimItem {
let state_nonce = match nonce_cache.get(&requirement.signer) {
Some(cached_nonce) => *cached_nonce,
None => {
let nonce = source.nonce(&requirement.signer)?;
let nonce = source.nonce(&requirement.signer).await?;
nonce_cache.insert(requirement.signer, nonce);
nonce
}
Expand All @@ -198,7 +198,7 @@ impl SimItem {
Ok(SimItemValidity::Now)
}

fn check_bundle<S, S2>(
async fn check_bundle<S, S2>(
&self,
source: &S,
host_source: &S2,
Expand All @@ -209,8 +209,8 @@ impl SimItem {
{
let item = self.as_bundle().expect("SimItem is not a Bundle");

let ru_tx = Self::check_bundle_tx_list(item.tx_reqs(), source)?;
let host_tx = Self::check_bundle_tx_list(item.host_tx_reqs(), host_source)?;
let ru_tx = Self::check_bundle_tx_list(item.tx_reqs(), source).await?;
let host_tx = Self::check_bundle_tx_list(item.host_tx_reqs(), host_source).await?;

// Check both the regular txs and the host txs.
Ok(ru_tx.min(host_tx))
Expand All @@ -220,7 +220,7 @@ impl SimItem {
///
/// This will check that nonces and balances are sufficient for the item to
/// be included on the current state.
pub fn check<S, S2>(
pub async fn check<S, S2>(
&self,
source: &S,
host_source: &S2,
Expand All @@ -230,8 +230,8 @@ impl SimItem {
S2: StateSource,
{
match self {
SimItem::Bundle(_) => self.check_bundle(source, host_source),
SimItem::Tx(_) => self.check_tx(source),
SimItem::Bundle(_) => self.check_bundle(source, host_source).await,
SimItem::Tx(_) => self.check_tx(source).await,
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/sim/src/cache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ mod item;
pub use item::{SimIdentifier, SimItem};

mod state;
pub use state::StateSource;
pub use state::{AcctInfo, StateSource};

mod store;
pub use store::SimCache;
Expand Down
45 changes: 28 additions & 17 deletions crates/sim/src/cache/state.rs
Original file line number Diff line number Diff line change
@@ -1,53 +1,64 @@
use alloy::primitives::{Address, U256};
use core::future::Future;
use trevm::revm::database_interface::async_db::DatabaseAsyncRef;

/// Account information including nonce and balance. This is partially modeled
/// after [`revm::AccountInfo`], but only includes the fields we care about.
///
/// [`revm::AccountInfo`]: trevm::revm::state::AccountInfo
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AcctInfo {
/// The account nonce.
pub nonce: u64,
/// The account balance.
pub balance: U256,
/// Whether the account has deployed code.
pub has_code: bool,
}

/// A source for nonce and balance information. Exists to simplify type bounds
/// in various places.
pub trait StateSource {
/// The error type for state lookups, usually a database error.
type Error: core::error::Error + 'static;
pub trait StateSource: Send + Sync {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this new bound is significant, as it prevents implementation for the signet-storage MDBX revm accessors

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmm - that's unfortunate indeed. I don't see a way round adding these bounds for now, since the crux of the fix here is to use async DBs for the preflight work.

I guess it's something we might need to revisit when we come to using signet-storage here?

/// The error type for state lookups.
type Error: core::error::Error + Send + 'static;

/// Get account details for an address.
fn account_details(&self, address: &Address) -> Result<AcctInfo, Self::Error>;
fn account_details(
&self,
address: &Address,
) -> impl Future<Output = Result<AcctInfo, Self::Error>> + Send;

/// Get the nonce for an address. This should return the NEXT EXPECTED
/// nonce. I.e. `0` for an address that has never sent a transaction, 1 for an address that has sent exactly one transaction, etc.
fn nonce(&self, address: &Address) -> Result<u64, Self::Error> {
self.account_details(address).map(|info| info.nonce)
/// Get the nonce for an address. Returns the NEXT EXPECTED nonce, i.e. `0` for an address that
/// has never sent a transaction, 1 for an address that has sent exactly one transaction, etc.
fn nonce(&self, address: &Address) -> impl Future<Output = Result<u64, Self::Error>> + Send {
async { self.account_details(address).await.map(|info| info.nonce) }
}

/// Get the balance for an address.
fn balance(&self, address: &Address) -> Result<U256, Self::Error> {
self.account_details(address).map(|info| info.balance)
fn balance(&self, address: &Address) -> impl Future<Output = Result<U256, Self::Error>> + Send {
async { self.account_details(address).await.map(|info| info.balance) }
}

/// Run an arbitrary check on the account details for an address.
fn map<T, F: FnOnce(&AcctInfo) -> T>(&self, address: &Address, f: F) -> Result<T, Self::Error> {
self.account_details(address).map(|info| f(&info))
fn map<T: Send, F: FnOnce(&AcctInfo) -> T + Send>(
&self,
address: &Address,
f: F,
) -> impl Future<Output = Result<T, Self::Error>> + Send {
async { self.account_details(address).await.map(|info| f(&info)) }
}
}

impl<Db> StateSource for Db
where
Db: trevm::revm::DatabaseRef<Error: 'static>,
Db: DatabaseAsyncRef + Send + Sync,
Db::Error: Send + 'static,
{
type Error = Db::Error;

fn account_details(&self, address: &Address) -> Result<AcctInfo, Self::Error> {
let info = self.basic_ref(*address)?.unwrap_or_default();

async fn account_details(&self, address: &Address) -> Result<AcctInfo, Self::Error> {
let info = self.basic_async_ref(*address).await?.unwrap_or_default();
let has_code = info.code_hash() != trevm::revm::primitives::KECCAK_EMPTY;

Ok(AcctInfo { nonce: info.nonce, balance: info.balance, has_code })
}
}
109 changes: 36 additions & 73 deletions crates/sim/src/cache/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,73 +64,6 @@ impl SimCache {
vec
}

/// Iter over the best items in the cache, writing only those that pass
/// preflight validity checks (nonce and initial fee) to the buffer.
///
/// The state sources are used to validate the items against the current
/// nonce and balance, to prevent simulating invalid items.
///
/// This will additionally remove items that can _never_ be valid from the
/// cache.
///
/// When an error is encountered, the process stops and the error is
/// returned. At this point, the buffer may be partially written.
pub fn write_best_valid_to<S, S2>(
&self,
buf: &mut [MaybeUninit<(u128, SimItem)>],
source: &S,
host_source: &S2,
) -> Result<usize, Box<dyn std::error::Error>>
where
S: StateSource,
S2: StateSource,
{
let mut cache = self.inner.upgradable_read();
let mut slots = buf.iter_mut();
let start = slots.len();

let mut never = Vec::new();

// Traverse the cache in reverse order (best items first), checking
// each item.
//
// Errors are shortcut by `try_for_each`. Passes are written to the
// buffer, consuming slots. Once no slots are left, the try_for_each
// returns early.
let res = cache
.items
.iter()
.rev()
.map(|(rank, item)| {
item.check(source, host_source).map(|validity| (validity, rank, item))
})
.try_for_each(|result| {
if slots.len() == 0 {
return Ok(());
}
let (validity, rank, item) = result?;

if validity.is_valid_now() {
slots.next().expect("checked by len").write((*rank, item.clone()));
}
if validity.is_never_valid() {
never.push(*rank);
}

Ok(())
})
.map(|_| start - slots.len());

cache.with_upgraded(|cache| {
// Remove never valid items from the cache
never.iter().for_each(|rank| {
cache.remove_and_disallow(*rank);
});
});

res
}

/// Get up to the `n` best items in the cache that pass preflight validity
/// checks (nonce and initial fee). The returned vector may be smaller than
/// `n` if not enough valid items are found.
Expand All @@ -140,7 +73,7 @@ impl SimCache {
///
/// The state sources are used to validate the items against the current
/// nonce and balance, to prevent simulating invalid items.
pub fn read_best_valid<S, S2>(
pub async fn read_best_valid<S, S2>(
&self,
n: usize,
source: &S,
Expand All @@ -150,11 +83,41 @@ impl SimCache {
S: StateSource,
S2: StateSource,
{
let mut vec = Vec::with_capacity(n);
let n = self.write_best_valid_to(vec.spare_capacity_mut(), source, host_source)?;
// SAFETY: We just wrote n items.
unsafe { vec.set_len(n) };
Ok(vec)
// Snapshot the entire cache under a short-lived read lock so that
// filtering out invalid items doesn't reduce the result set below `n`.
let candidates: Vec<(u128, SimItem)> = {
let cache = self.inner.read();
// Traverse the cache in reverse order (best items first).
cache.items.iter().rev().map(|(rank, item)| (*rank, item.clone())).collect()
};

let mut valid = Vec::with_capacity(n);
let mut never = Vec::new();

for (rank, item) in &candidates {
if valid.len() >= n {
break;
}

let validity = item.check(source, host_source).await?;

if validity.is_valid_now() {
valid.push((*rank, item.clone()));
}
if validity.is_never_valid() {
never.push(*rank);
}
Comment thread
dylanlott marked this conversation as resolved.
}

// Remove never-valid items under a write lock.
if !never.is_empty() {
let mut cache = self.inner.write();
for rank in never {
cache.remove_and_disallow(rank);
}
}

Ok(valid)
}

/// Get the number of items in the cache.
Expand Down
Loading