Skip to content

Commit

Permalink
feat(pool): reject UOs below configurable gas limit efficiency
Browse files Browse the repository at this point in the history
  • Loading branch information
dancoombs committed Oct 16, 2024
1 parent 45e6858 commit a65fae2
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 25 deletions.
9 changes: 9 additions & 0 deletions bin/rundler/src/cli/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,14 @@ pub struct PoolArgs {
default_value = "10"
)]
pub drop_min_num_blocks: u64,

#[arg(
long = "pool.gas_limit_efficiency_reject_threshold",
name = "pool.gas_limit_efficiency_reject_threshold",
env = "POOL_GAS_LIMIT_EFFICIENCY_REJECT_THRESHOLD",
default_value = "0.0"
)]
pub gas_limit_efficiency_reject_threshold: f32,
}

impl PoolArgs {
Expand Down Expand Up @@ -226,6 +234,7 @@ impl PoolArgs {
reputation_tracking_enabled: self.reputation_tracking_enabled,
drop_min_num_blocks: self.drop_min_num_blocks,
da_gas_tracking_enabled,
gas_limit_efficiency_reject_threshold: self.gas_limit_efficiency_reject_threshold,
};

let mut pool_configs = vec![];
Expand Down
12 changes: 12 additions & 0 deletions crates/pool/proto/op_pool/op_pool.proto
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,8 @@ message MempoolError {
AssociatedStorageIsAlternateSender associated_storage_is_alternate_sender = 14;
PaymasterBalanceTooLow paymaster_balance_too_low = 15;
OperationDropTooSoon operation_drop_too_soon = 16;
PreOpGasLimitEfficiencyTooLow pre_op_gas_limit_efficiency_too_low = 17;
CallGasLimitEfficiencyTooLow call_gas_limit_efficiency_too_low = 18;
}
}

Expand Down Expand Up @@ -563,6 +565,16 @@ message OperationDropTooSoon {
uint64 must_wait = 3;
}

message PreOpGasLimitEfficiencyTooLow {
float required = 1;
float actual = 2;
}

message CallGasLimitEfficiencyTooLow {
float required = 1;
float actual = 2;
}

// PRECHECK VIOLATIONS
message PrecheckViolationError {
oneof violation {
Expand Down
4 changes: 4 additions & 0 deletions crates/pool/src/mempool/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ pub struct PoolConfig {
pub da_gas_tracking_enabled: bool,
/// The minimum number of blocks a user operation must be in the mempool before it can be dropped
pub drop_min_num_blocks: u64,
/// Reject user operations with gas limit efficiency below this threshold.
/// Gas limit efficiency is defined as the ratio of the gas limit to the gas used.
/// This applies to all the verification, call, and paymaster gas limits.
pub gas_limit_efficiency_reject_threshold: f32,
}

/// Origin of an operation.
Expand Down
97 changes: 87 additions & 10 deletions crates/pool/src/mempool/uo_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@

use std::{collections::HashSet, marker::PhantomData, sync::Arc, time::Instant};

use alloy_primitives::{utils::format_units, Address, B256, U256};
use alloy_primitives::{utils::format_units, Address, Bytes, B256, U256};
use anyhow::Context;
use futures::TryFutureExt;
use itertools::Itertools;
use metrics::{Counter, Gauge, Histogram};
use metrics_derive::Metrics;
use parking_lot::RwLock;
use rundler_provider::{DAGasProvider, EntryPoint, EvmProvider};
use rundler_provider::{DAGasProvider, EntryPoint, EvmProvider, SimulationProvider, StateOverride};
use rundler_sim::{Prechecker, Simulator};
use rundler_types::{
pool::{
Expand Down Expand Up @@ -72,7 +74,8 @@ struct UoPoolState<E> {

impl<UO, EP, P, S, E> UoPool<UO, EP, P, S, E>
where
E: DAGasProvider<UO = UO> + Clone,
UO: From<UserOperationVariant>,
E: DAGasProvider<UO = UO> + SimulationProvider<UO = UO> + Clone,
{
// TODO refactor provider args
#[allow(clippy::too_many_arguments)]
Expand Down Expand Up @@ -152,6 +155,54 @@ where
.increment(count as u64);
self.ep_specific_metrics.removed_entities.increment(1);
}

async fn check_call_gas_limit_efficiency(
&self,
op: UserOperationVariant,
block_hash: B256,
) -> MempoolResult<()> {
// Check call gas limit efficiency only if needed
if self.config.gas_limit_efficiency_reject_threshold > 0.0 {
let gas_price = op.gas_price(0);
let call_gas_limit = op.call_gas_limit();

let ret = self
.entry_point
.simulate_handle_op(
op.into(),
Address::ZERO,
Bytes::new(),
block_hash.into(),
StateOverride::default(),
)
.await;
match ret {
Err(e) => {
tracing::error!("Failed to simulate handle op for gas limit efficiency check, failing open: {:?}", e);
}
Ok(Err(e)) => {
tracing::error!("Failed to simulate handle op for gas limit efficiency check, failing open: {:?}", e);
}
Ok(Ok(execution_res)) => {
let total_gas_used: u128 = (execution_res.paid / U256::from(gas_price))
.try_into()
.context("total gas used should fit in u128")?;

let call_gas_used = total_gas_used - execution_res.pre_op_gas;

let call_gas_efficiency = call_gas_used as f32 / call_gas_limit as f32;
if call_gas_efficiency < self.config.gas_limit_efficiency_reject_threshold {
return Err(MempoolError::CallGasLimitEfficiencyTooLow(
self.config.gas_limit_efficiency_reject_threshold,
call_gas_efficiency,
));
}
}
}
}

Ok(())
}
}

#[async_trait]
Expand All @@ -161,7 +212,7 @@ where
EP: EvmProvider,
P: Prechecker<UO = UO>,
S: Simulator<UO = UO>,
E: EntryPoint + DAGasProvider<UO = UO> + Clone,
E: EntryPoint + DAGasProvider<UO = UO> + SimulationProvider<UO = UO> + Clone,
{
async fn on_chain_update(&self, update: &ChainUpdate) {
let deduped_ops = update.deduped_ops();
Expand Down Expand Up @@ -484,10 +535,13 @@ where
.await?;

// Only let ops with successful simulations through
let sim_result = self
// Run simulation and call gas limit efficiency check in parallel
let sim_fut = self
.simulator
.simulate_validation(versioned_op, block_hash, None)
.await?;
.map_err(Into::into);
let call_gas_check_future = self.check_call_gas_limit_efficiency(op.clone(), block_hash);
let (sim_result, _) = tokio::try_join!(sim_fut, call_gas_check_future)?;

// No aggregators supported for now
if let Some(agg) = &sim_result.aggregator {
Expand All @@ -500,6 +554,15 @@ where
.pool
.check_associated_storage(&sim_result.associated_addresses, &op)?;

// Check pre op gas limit efficiency
let pre_op_gas_efficiency = sim_result.pre_op_gas as f32 / op.pre_op_gas_limit() as f32;
if pre_op_gas_efficiency < self.config.gas_limit_efficiency_reject_threshold {
return Err(MempoolError::PreOpGasLimitEfficiencyTooLow(
self.config.gas_limit_efficiency_reject_threshold,
pre_op_gas_efficiency,
));
}

let valid_time_range = sim_result.valid_time_range;
let pool_op = PoolOperation {
uo: op,
Expand Down Expand Up @@ -1583,7 +1646,10 @@ mod tests {
impl EvmProvider,
impl Prechecker<UO = UserOperation>,
impl Simulator<UO = UserOperation>,
impl EntryPoint + DAGasProvider<UO = UserOperation> + Clone,
impl EntryPoint
+ DAGasProvider<UO = UserOperation>
+ SimulationProvider<UO = UserOperation>
+ Clone,
> {
let entrypoint = MockEntryPointV0_6::new();
create_pool_with_entry_point(ops, entrypoint)
Expand All @@ -1597,7 +1663,10 @@ mod tests {
impl EvmProvider,
impl Prechecker<UO = UserOperation>,
impl Simulator<UO = UserOperation>,
impl EntryPoint + DAGasProvider<UO = UserOperation> + Clone,
impl EntryPoint
+ DAGasProvider<UO = UserOperation>
+ SimulationProvider<UO = UserOperation>
+ Clone,
> {
let entrypoint = Arc::new(entrypoint);
let args = PoolConfig {
Expand All @@ -1620,6 +1689,7 @@ mod tests {
paymaster_cache_length: 100,
reputation_tracking_enabled: true,
drop_min_num_blocks: 10,
gas_limit_efficiency_reject_threshold: 0.0,
};

let mut evm = MockEvmProvider::new();
Expand Down Expand Up @@ -1685,6 +1755,7 @@ mod tests {
},
..EntityInfos::default()
},
pre_op_gas: 1000,
..SimulationResult::default()
})
}
Expand Down Expand Up @@ -1714,7 +1785,10 @@ mod tests {
impl EvmProvider,
impl Prechecker<UO = UserOperation>,
impl Simulator<UO = UserOperation>,
impl EntryPoint + DAGasProvider<UO = UserOperation> + Clone,
impl EntryPoint
+ DAGasProvider<UO = UserOperation>
+ SimulationProvider<UO = UserOperation>
+ Clone,
>,
Vec<UserOperationVariant>,
) {
Expand All @@ -1734,7 +1808,10 @@ mod tests {
impl EvmProvider,
impl Prechecker<UO = UserOperation>,
impl Simulator<UO = UserOperation>,
impl EntryPoint + DAGasProvider<UO = UserOperation> + Clone,
impl EntryPoint
+ DAGasProvider<UO = UserOperation>
+ SimulationProvider<UO = UserOperation>
+ Clone,
>,
Vec<UserOperationVariant>,
) {
Expand Down
47 changes: 32 additions & 15 deletions crates/pool/src/server/remote/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,22 @@ use rundler_types::{
use super::protos::{
mempool_error, precheck_violation_error, simulation_violation_error, validation_revert,
AccessedUndeployedContract, AccessedUnsupportedContractType, AggregatorValidationFailed,
AssociatedStorageDuringDeploy, AssociatedStorageIsAlternateSender, CallGasLimitTooLow,
CallHadValue, CalledBannedEntryPointMethod, CodeHashChanged, DidNotRevert,
DiscardedOnInsertError, Entity, EntityThrottledError, EntityType, EntryPointRevert,
ExistingSenderWithInitCode, FactoryCalledCreate2Twice, FactoryIsNotContract,
InvalidAccountSignature, InvalidPaymasterSignature, InvalidSignature, InvalidStorageAccess,
InvalidTimeRange, MaxFeePerGasTooLow, MaxOperationsReachedError, MaxPriorityFeePerGasTooLow,
MempoolError as ProtoMempoolError, MultipleRolesViolation, NotStaked,
OperationAlreadyKnownError, OperationDropTooSoon, OperationRevert, OutOfGas, PanicRevert,
PaymasterBalanceTooLow, PaymasterDepositTooLow, PaymasterIsNotContract,
PreVerificationGasTooLow, PrecheckViolationError as ProtoPrecheckViolationError,
ReplacementUnderpricedError, SenderAddressUsedAsAlternateEntity, SenderFundsTooLow,
SenderIsNotContractAndNoInitCode, SimulationViolationError as ProtoSimulationViolationError,
TotalGasLimitTooHigh, UnintendedRevert, UnintendedRevertWithMessage, UnknownEntryPointError,
UnknownRevert, UnstakedAggregator, UnstakedPaymasterContext, UnsupportedAggregatorError,
UsedForbiddenOpcode, UsedForbiddenPrecompile, ValidationRevert as ProtoValidationRevert,
AssociatedStorageDuringDeploy, AssociatedStorageIsAlternateSender,
CallGasLimitEfficiencyTooLow, CallGasLimitTooLow, CallHadValue, CalledBannedEntryPointMethod,
CodeHashChanged, DidNotRevert, DiscardedOnInsertError, Entity, EntityThrottledError,
EntityType, EntryPointRevert, ExistingSenderWithInitCode, FactoryCalledCreate2Twice,
FactoryIsNotContract, InvalidAccountSignature, InvalidPaymasterSignature, InvalidSignature,
InvalidStorageAccess, InvalidTimeRange, MaxFeePerGasTooLow, MaxOperationsReachedError,
MaxPriorityFeePerGasTooLow, MempoolError as ProtoMempoolError, MultipleRolesViolation,
NotStaked, OperationAlreadyKnownError, OperationDropTooSoon, OperationRevert, OutOfGas,
PanicRevert, PaymasterBalanceTooLow, PaymasterDepositTooLow, PaymasterIsNotContract,
PreOpGasLimitEfficiencyTooLow, PreVerificationGasTooLow,
PrecheckViolationError as ProtoPrecheckViolationError, ReplacementUnderpricedError,
SenderAddressUsedAsAlternateEntity, SenderFundsTooLow, SenderIsNotContractAndNoInitCode,
SimulationViolationError as ProtoSimulationViolationError, TotalGasLimitTooHigh,
UnintendedRevert, UnintendedRevertWithMessage, UnknownEntryPointError, UnknownRevert,
UnstakedAggregator, UnstakedPaymasterContext, UnsupportedAggregatorError, UsedForbiddenOpcode,
UsedForbiddenPrecompile, ValidationRevert as ProtoValidationRevert,
VerificationGasLimitBufferTooLow, VerificationGasLimitTooHigh, WrongNumberOfPhases,
};

Expand Down Expand Up @@ -126,6 +127,12 @@ impl TryFrom<ProtoMempoolError> for MempoolError {
Some(mempool_error::Error::OperationDropTooSoon(e)) => {
MempoolError::OperationDropTooSoon(e.added_at, e.attempted_at, e.must_wait)
}
Some(mempool_error::Error::PreOpGasLimitEfficiencyTooLow(e)) => {
MempoolError::PreOpGasLimitEfficiencyTooLow(e.required, e.actual)
}
Some(mempool_error::Error::CallGasLimitEfficiencyTooLow(e)) => {
MempoolError::CallGasLimitEfficiencyTooLow(e.required, e.actual)
}
None => bail!("unknown proto mempool error"),
})
}
Expand Down Expand Up @@ -230,6 +237,16 @@ impl From<MempoolError> for ProtoMempoolError {
)),
}
}
MempoolError::PreOpGasLimitEfficiencyTooLow(required, actual) => ProtoMempoolError {
error: Some(mempool_error::Error::PreOpGasLimitEfficiencyTooLow(
PreOpGasLimitEfficiencyTooLow { required, actual },
)),
},
MempoolError::CallGasLimitEfficiencyTooLow(required, actual) => ProtoMempoolError {
error: Some(mempool_error::Error::CallGasLimitEfficiencyTooLow(
CallGasLimitEfficiencyTooLow { required, actual },
)),
},
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions crates/rpc/src/eth/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,12 @@ impl From<MempoolError> for EthRpcError {
Self::EntryPointValidationRejected(format!("unknown entry point: {}", a))
}
MempoolError::OperationDropTooSoon(_, _, _) => Self::InvalidParams(value.to_string()),
MempoolError::PreOpGasLimitEfficiencyTooLow(_, _) => {
Self::InvalidParams(value.to_string())
}
MempoolError::CallGasLimitEfficiencyTooLow(_, _) => {
Self::InvalidParams(value.to_string())
}
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions crates/types/src/pool/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ pub enum MempoolError {
/// The operation drop attempt too soon after being added to the pool
#[error("Operation drop attempt too soon after being added to the pool. Added at {0}, attempted to drop at {1}, must wait {2} blocks.")]
OperationDropTooSoon(u64, u64, u64),
/// Pre-op gas limit efficiency too low
#[error("Pre-op gas limit efficiency too low. Required: {0}, Actual: {1}")]
PreOpGasLimitEfficiencyTooLow(f32, f32),
/// Call gas limit efficiency too low
#[error("Call gas limit efficiency too low. Required: {0}, Actual: {1}")]
CallGasLimitEfficiencyTooLow(f32, f32),
}

/// Precheck violation enumeration
Expand Down
11 changes: 11 additions & 0 deletions crates/types/src/user_operation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ pub trait UserOperation: Debug + Clone + Send + Sync + 'static {
/// Returns the maximum cost, in wei, of this user operation
fn max_gas_cost(&self) -> U256;

/// Returns the gas price for this UO given the base fee
fn gas_price(&self, base_fee: u128) -> u128 {
self.max_fee_per_gas()
.min(base_fee + self.max_priority_fee_per_gas())
}

/*
* Enhanced functions
*/
Expand Down Expand Up @@ -133,6 +139,11 @@ pub trait UserOperation: Debug + Clone + Send + Sync + 'static {
/// executing the user operation.
fn required_pre_execution_buffer(&self) -> u128;

/// Returns the limit of gas that may be used used prior to the execution of the user operation
fn pre_op_gas_limit(&self) -> u128 {
self.pre_verification_gas() + self.total_verification_gas_limit()
}

/// Returns the pre-verification gas
fn pre_verification_gas(&self) -> u128;

Expand Down
2 changes: 2 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ List of command line options for configuring the Pool.
- env: *POOL_REPUTATION_TRACKING_ENABLED*
- `--pool.drop_min_num_blocks`: The minimum number of blocks that a UO must stay in the mempool before it can be requested to be dropped by the user (default: `10`)
- env: *POOL_DROP_MIN_NUM_BLOCKS*
- `--pool.gas_limit_efficiency_reject_threshold`: The ratio of gas used to gas limit under which to reject UOs upon entry to the mempool (default: `0.0` disabled)
- env: *POOL_GAS_LIMIT_EFFICIENCY_REJECT_THRESHOLD*

## Builder Options

Expand Down

0 comments on commit a65fae2

Please sign in to comment.