diff --git a/crates/pool/src/mempool/mod.rs b/crates/pool/src/mempool/mod.rs index 3ab046e75..f547f0397 100644 --- a/crates/pool/src/mempool/mod.rs +++ b/crates/pool/src/mempool/mod.rs @@ -18,13 +18,13 @@ mod entity_tracker; mod pool; mod reputation; -pub(crate) use reputation::{HourlyMovingAverageReputation, ReputationParams}; +pub(crate) use reputation::{AddressReputation, ReputationParams}; pub use reputation::{Reputation, ReputationStatus}; -use rundler_provider::ProviderResult; mod size; mod paymaster; +pub(crate) use paymaster::{PaymasterConfig, PaymasterTracker}; mod uo_pool; use std::{ @@ -66,9 +66,6 @@ pub trait Mempool: Send + Sync + 'static { /// Updates the reputation of an entity. fn update_entity(&self, entity_update: EntityUpdate); - /// Returns current paymaster balance - async fn paymaster_balance(&self, paymaster: Address) -> ProviderResult; - /// Returns the best operations from the pool. /// /// Returns the best operations from the pool based on their gas bids up to diff --git a/crates/pool/src/mempool/paymaster.rs b/crates/pool/src/mempool/paymaster.rs index 6a825a70f..620bb6824 100644 --- a/crates/pool/src/mempool/paymaster.rs +++ b/crates/pool/src/mempool/paymaster.rs @@ -17,14 +17,206 @@ use std::collections::HashMap; use anyhow::Context; use ethers::{abi::Address, types::U256}; -use rundler_types::UserOperationId; +use parking_lot::RwLock; +use rundler_provider::{EntryPoint, PaymasterHelper}; +use rundler_types::{UserOperation, UserOperationId}; -use super::{error::MempoolResult, PaymasterMetadata}; -use crate::{chain::MinedOp, MempoolError, PoolOperation}; +use super::{error::MempoolResult, PaymasterMetadata, StakeInfo}; +use crate::{ + chain::{BalanceUpdate, MinedOp}, + MempoolError, PoolOperation, StakeStatus, +}; + +/// Keeps track of current and pending paymaster balances +#[derive(Debug)] +pub(crate) struct PaymasterTracker { + paymaster_helper: PH, + entry_point: E, + state: RwLock, + config: PaymasterConfig, +} + +#[derive(Debug)] +pub(crate) struct PaymasterConfig { + min_stake_value: u128, + min_unstake_delay: u32, + tracker_enabled: bool, +} + +impl PaymasterConfig { + pub(crate) fn new( + min_stake_value: u128, + min_unstake_delay: u32, + tracker_enabled: bool, + ) -> Self { + Self { + min_stake_value, + min_unstake_delay, + tracker_enabled, + } + } +} + +impl PaymasterTracker +where + PH: PaymasterHelper, + E: EntryPoint, +{ + pub(crate) fn new(paymaster_helper: PH, entry_point: E, config: PaymasterConfig) -> Self { + Self { + paymaster_helper, + entry_point, + state: RwLock::new(PaymasterTrackerInner::new(config.tracker_enabled)), + config, + } + } + + pub(crate) async fn get_stake_status(&self, address: Address) -> MempoolResult { + let deposit_info = self.paymaster_helper.get_deposit_info(address).await?; + + let is_staked = deposit_info.stake.ge(&self.config.min_stake_value) + && deposit_info + .unstake_delay_sec + .ge(&self.config.min_unstake_delay); + + let stake_status = StakeStatus { + stake_info: StakeInfo { + stake: deposit_info.stake, + unstake_delay_sec: deposit_info.unstake_delay_sec, + }, + is_staked, + }; + + Ok(stake_status) + } + + pub(crate) fn update_paymaster_balances_after_update<'a>( + &self, + entity_balance_updates: impl Iterator, + unmined_entity_balance_updates: impl Iterator, + ) { + for balance_update in entity_balance_updates { + self.state.write().update_paymaster_balance_from_event( + balance_update.address, + balance_update.amount, + balance_update.is_addition, + ) + } + + for unmined_balance_update in unmined_entity_balance_updates { + self.state.write().update_paymaster_balance_from_event( + unmined_balance_update.address, + unmined_balance_update.amount, + !unmined_balance_update.is_addition, + ) + } + } + + pub(crate) async fn paymaster_balance( + &self, + paymaster: Address, + ) -> MempoolResult { + if self.state.read().paymaster_exists(paymaster) { + let meta = self + .state + .read() + .paymaster_metadata(paymaster) + .context("Paymaster balance should not be empty if address exists in pool")?; + + return Ok(meta); + } + + let balance = self + .entry_point + .balance_of(paymaster, None) + .await + .context("Paymaster balance should not be empty if address exists in pool")?; + + let paymaster_meta = PaymasterMetadata { + address: paymaster, + pending_balance: balance, + confirmed_balance: balance, + }; + + // Save paymaster balance after first lookup + self.state + .write() + .add_new_paymaster(paymaster, balance, 0.into()); + + Ok(paymaster_meta) + } + + pub(crate) async fn check_operation_cost(&self, op: &UserOperation) -> MempoolResult<()> { + if let Some(paymaster) = op.paymaster() { + let balance = self.paymaster_balance(paymaster).await?; + self.state.read().check_operation_cost(op, &balance)? + } + + Ok(()) + } + + pub(crate) fn clear(&self) { + self.state.write().clear(); + } + + pub(crate) fn dump_paymaster_metadata(&self) -> Vec { + self.state.read().dump_paymaster_metadata() + } + + pub(crate) fn set_tracking(&self, tracking_enabled: bool) { + self.state.write().set_tracking(tracking_enabled); + } + + pub(crate) async fn reset_confimed_balances(&self) -> MempoolResult<()> { + let paymaster_addresses = self.paymaster_addresses(); + + let balances = self + .paymaster_helper + .get_balances(paymaster_addresses.clone()) + .await?; + + self.state + .write() + .set_confimed_balances(&paymaster_addresses, &balances); + + Ok(()) + } + + pub(crate) fn update_paymaster_balance_from_mined_op(&self, mined_op: &MinedOp) { + self.state + .write() + .update_paymaster_balance_from_mined_op(mined_op); + } + pub(crate) fn remove_operation(&self, id: &UserOperationId) { + self.state.write().remove_operation(id); + } + + pub(crate) fn paymaster_addresses(&self) -> Vec
{ + self.state.read().paymaster_addresses() + } + + pub(crate) fn unmine_actual_cost(&self, paymaster: &Address, actual_cost: U256) { + self.state + .write() + .unmine_actual_cost(paymaster, actual_cost); + } + + pub(crate) async fn add_or_update_balance(&self, po: &PoolOperation) -> MempoolResult<()> { + if let Some(paymaster) = po.uo.paymaster() { + let paymaster_metadata = self.paymaster_balance(paymaster).await?; + return self + .state + .write() + .add_or_update_balance(po, &paymaster_metadata); + } + + Ok(()) + } +} /// Keeps track of current and pending paymaster balances #[derive(Debug, Clone, PartialEq, Eq, Default)] -pub(crate) struct PaymasterTracker { +struct PaymasterTrackerInner { /// map for userop based on id user_op_fees: HashMap, /// map for paymaster balance status @@ -33,28 +225,56 @@ pub(crate) struct PaymasterTracker { tracker_enabled: bool, } -impl PaymasterTracker { - pub(crate) fn new(tracker_enabled: bool) -> Self { +impl PaymasterTrackerInner { + fn new(tracker_enabled: bool) -> Self { Self { tracker_enabled, ..Default::default() } } - pub(crate) fn paymaster_exists(&self, paymaster: Address) -> bool { + fn paymaster_exists(&self, paymaster: Address) -> bool { self.paymaster_balances.contains_key(&paymaster) } - pub(crate) fn set_paymaster_tracker(&mut self, tracking_enabled: bool) { + fn set_tracking(&mut self, tracking_enabled: bool) { self.tracker_enabled = tracking_enabled; } - pub(crate) fn clear(&mut self) { + fn check_operation_cost( + &self, + op: &UserOperation, + paymaster_metadata: &PaymasterMetadata, + ) -> MempoolResult<()> { + let max_op_cost = op.max_gas_cost(); + + if let Some(prev) = self.user_op_fees.get(&op.id()) { + let reset_balance = paymaster_metadata + .pending_balance + .saturating_add(prev.max_op_cost); + + if reset_balance.lt(&max_op_cost) { + return Err(MempoolError::PaymasterBalanceTooLow( + max_op_cost, + reset_balance, + )); + } + } else if paymaster_metadata.pending_balance.lt(&max_op_cost) { + return Err(MempoolError::PaymasterBalanceTooLow( + max_op_cost, + paymaster_metadata.pending_balance, + )); + } + + Ok(()) + } + + fn clear(&mut self) { self.user_op_fees.clear(); self.paymaster_balances.clear(); } - pub(crate) fn set_confimed_balances(&mut self, addresses: &[Address], balances: &[U256]) { + fn set_confimed_balances(&mut self, addresses: &[Address], balances: &[U256]) { for (i, address) in addresses.iter().enumerate() { if let Some(paymaster_balance) = self.paymaster_balances.get_mut(address) { paymaster_balance.confirmed = balances[i]; @@ -63,7 +283,7 @@ impl PaymasterTracker { } //TODO track if paymaster has become stale and can be removed from the pool - pub(crate) fn update_paymaster_balance_from_mined_op(&mut self, mined_op: &MinedOp) { + fn update_paymaster_balance_from_mined_op(&mut self, mined_op: &MinedOp) { let id = mined_op.id(); if let Some(op_fee) = self.user_op_fees.get(&id) { @@ -80,7 +300,7 @@ impl PaymasterTracker { } } - pub(crate) fn remove_operation(&mut self, id: &UserOperationId) { + fn remove_operation(&mut self, id: &UserOperationId) { if let Some(op_fee) = self.user_op_fees.get(id) { if let Some(paymaster_balance) = self.paymaster_balances.get_mut(&op_fee.paymaster) { paymaster_balance.pending = @@ -91,13 +311,13 @@ impl PaymasterTracker { } } - pub(crate) fn paymaster_addresses(&self) -> Vec
{ + fn paymaster_addresses(&self) -> Vec
{ let keys: Vec
= self.paymaster_balances.keys().cloned().collect(); keys } - pub(crate) fn update_paymaster_balance_from_event( + fn update_paymaster_balance_from_event( &mut self, paymaster: Address, amount: U256, @@ -112,7 +332,7 @@ impl PaymasterTracker { } } - pub(crate) fn paymaster_metadata(&self, paymaster: Address) -> Option { + fn paymaster_metadata(&self, paymaster: Address) -> Option { if let Some(paymaster_balance) = self.paymaster_balances.get(&paymaster) { return Some(PaymasterMetadata { pending_balance: paymaster_balance.pending_balance(), @@ -124,7 +344,7 @@ impl PaymasterTracker { None } - pub(crate) fn dump_paymaster_metadata(&self) -> Vec { + fn dump_paymaster_metadata(&self) -> Vec { self.paymaster_balances .iter() .map(|(address, balance)| PaymasterMetadata { @@ -135,13 +355,13 @@ impl PaymasterTracker { .collect() } - pub(crate) fn unmine_actual_cost(&mut self, paymaster: &Address, actual_cost: U256) { + fn unmine_actual_cost(&mut self, paymaster: &Address, actual_cost: U256) { if let Some(paymaster_balance) = self.paymaster_balances.get_mut(paymaster) { paymaster_balance.confirmed = paymaster_balance.confirmed.saturating_add(actual_cost); } } - pub(crate) fn add_or_update_balance( + fn add_or_update_balance( &mut self, po: &PoolOperation, paymaster_metadata: &PaymasterMetadata, @@ -225,9 +445,10 @@ impl PaymasterTracker { self.decrement_previous_paymaster_balance(&prev_paymaster, prev_max_op_cost)?; } - self.paymaster_balances.insert( + self.add_new_paymaster( paymaster_metadata.address, - PaymasterBalance::new(paymaster_metadata.confirmed_balance, max_op_cost), + paymaster_metadata.confirmed_balance, + max_op_cost, ); } @@ -250,12 +471,25 @@ impl PaymasterTracker { { paymaster_balance.pending = paymaster_balance.pending.saturating_add(max_op_cost); } else { - self.paymaster_balances.insert( + self.add_new_paymaster( paymaster_metadata.address, - PaymasterBalance::new(paymaster_metadata.confirmed_balance, max_op_cost), + paymaster_metadata.confirmed_balance, + max_op_cost, ); } } + + fn add_new_paymaster( + &mut self, + address: Address, + confirmed_balance: U256, + inital_pending_balance: U256, + ) { + self.paymaster_balances.insert( + address, + PaymasterBalance::new(confirmed_balance, inital_pending_balance), + ); + } } #[derive(Debug, Clone, PartialEq, Eq, Default)] @@ -292,14 +526,14 @@ impl PaymasterBalance { #[cfg(test)] mod tests { use ethers::types::{Address, H256, U256}; + use rundler_provider::{MockEntryPoint, MockPaymasterHelper}; use rundler_sim::EntityInfos; - use rundler_types::{UserOperation, UserOperationId, ValidTimeRange}; + use rundler_types::{DepositInfo, UserOperation, UserOperationId, ValidTimeRange}; + use super::PaymasterConfig; use crate::{ - mempool::{ - paymaster::{PaymasterBalance, PaymasterTracker, UserOpFees}, - PaymasterMetadata, - }, + chain::BalanceUpdate, + mempool::{paymaster::PaymasterTracker, PaymasterMetadata}, PoolOperation, }; @@ -318,125 +552,126 @@ mod tests { } } - #[test] - fn new_uo_unused_paymaster() { - let mut paymaster_tracker = PaymasterTracker::new(true); + #[tokio::test] + async fn new_uo_unused_paymaster() { + let paymaster_tracker = new_paymaster_tracker(); let paymaster = Address::random(); let sender = Address::random(); - let paymaster_balance = U256::from(100000000); - let confirmed_balance = U256::from(100000000); let uo = UserOperation { sender, call_gas_limit: 10.into(), pre_verification_gas: 10.into(), + paymaster_and_data: paymaster.as_bytes().to_vec().into(), verification_gas_limit: 10.into(), max_fee_per_gas: 1.into(), ..Default::default() }; let uo_max_cost = uo.clone().max_gas_cost(); - let paymaster_meta = PaymasterMetadata { - address: paymaster, - pending_balance: paymaster_balance, - confirmed_balance, - }; let po = demo_pool_op(uo); - let res = paymaster_tracker.add_or_update_balance(&po, &paymaster_meta); + let res = paymaster_tracker.add_or_update_balance(&po).await; assert!(res.is_ok()); + let balance = paymaster_tracker + .paymaster_balance(paymaster) + .await + .unwrap(); + + assert_eq!(balance.confirmed_balance, 1000.into(),); + assert_eq!( - paymaster_tracker - .paymaster_balances - .get(&paymaster) - .unwrap() - .confirmed, - paymaster_balance, - ); - assert_eq!( - paymaster_tracker - .paymaster_balances - .get(&paymaster) - .unwrap() - .pending, - uo_max_cost, + balance.pending_balance, + balance.confirmed_balance.saturating_sub(uo_max_cost), ); } - #[test] - fn new_uo_not_enough_balance() { - let mut paymaster_tracker = PaymasterTracker::new(true); + #[tokio::test] + async fn new_uo_not_enough_balance() { + let paymaster_tracker = new_paymaster_tracker(); let paymaster = Address::random(); let sender = Address::random(); let paymaster_balance = U256::from(5); let confirmed_balance = U256::from(5); + + paymaster_tracker.add_new_paymaster(paymaster, confirmed_balance, paymaster_balance); + let uo = UserOperation { sender, call_gas_limit: 10.into(), + paymaster_and_data: paymaster.as_bytes().to_vec().into(), pre_verification_gas: 10.into(), verification_gas_limit: 10.into(), max_fee_per_gas: 1.into(), ..Default::default() }; - let paymaster_meta = PaymasterMetadata { - address: paymaster, - pending_balance: paymaster_balance, - confirmed_balance, - }; - let po = demo_pool_op(uo); - let res = paymaster_tracker.add_or_update_balance(&po, &paymaster_meta); + let res = paymaster_tracker.add_or_update_balance(&po).await; + assert!(res.is_err()); } - #[test] - fn test_update_balance() { - let mut paymaster_tracker = PaymasterTracker::new(true); + #[tokio::test] + async fn test_update_balance() { + let paymaster_tracker = new_paymaster_tracker(); let paymaster = Address::random(); let pending_op_cost = U256::from(100); let confirmed_balance = U256::from(1000); - paymaster_tracker.paymaster_balances.insert( - paymaster, - PaymasterBalance { - pending: pending_op_cost, - confirmed: confirmed_balance, - }, - ); + paymaster_tracker.add_new_paymaster(paymaster, confirmed_balance, pending_op_cost); + + let deposit = BalanceUpdate { + address: paymaster, + entrypoint: Address::random(), + amount: 100.into(), + is_addition: true, + }; // deposit - paymaster_tracker.update_paymaster_balance_from_event(paymaster, 100.into(), true); + paymaster_tracker + .update_paymaster_balances_after_update(vec![&deposit].into_iter(), vec![].into_iter()); let balance = paymaster_tracker - .paymaster_balances - .get(&paymaster) + .paymaster_balance(paymaster) + .await .unwrap(); - assert_eq!(balance.confirmed, 1100.into()); + assert_eq!(balance.confirmed_balance, 1100.into()); + + let withdrawal = BalanceUpdate { + address: paymaster, + entrypoint: Address::random(), + amount: 50.into(), + is_addition: false, + }; // withdrawal - paymaster_tracker.update_paymaster_balance_from_event(paymaster, 50.into(), false); + paymaster_tracker.update_paymaster_balances_after_update( + vec![&withdrawal].into_iter(), + vec![].into_iter(), + ); let balance = paymaster_tracker - .paymaster_balances - .get(&paymaster) + .paymaster_balance(paymaster) + .await .unwrap(); - assert_eq!(balance.confirmed, 1050.into()); + assert_eq!(balance.confirmed_balance, 1050.into()); } - #[test] - fn new_uo_not_enough_balance_tracking_disabled() { - let mut paymaster_tracker = PaymasterTracker::new(false); + #[tokio::test] + async fn new_uo_not_enough_balance_tracking_disabled() { + let paymaster_tracker = new_paymaster_tracker(); + paymaster_tracker.set_tracking(false); let paymaster = Address::random(); let sender = Address::random(); - let paymaster_balance = U256::from(5); + let pending_op_cost = U256::from(5); let confirmed_balance = U256::from(5); let uo = UserOperation { sender, @@ -447,85 +682,82 @@ mod tests { ..Default::default() }; - let paymaster_meta = PaymasterMetadata { - address: paymaster, - pending_balance: paymaster_balance, - confirmed_balance, - }; - let po = demo_pool_op(uo); - let res = paymaster_tracker.add_or_update_balance(&po, &paymaster_meta); + paymaster_tracker.add_new_paymaster(paymaster, confirmed_balance, pending_op_cost); + + let res = paymaster_tracker.add_or_update_balance(&po).await; assert!(res.is_ok()); - assert_eq!( - paymaster_tracker - .paymaster_balances - .get(&paymaster) - .unwrap() - .confirmed, - 5.into(), - ); - assert_eq!( - paymaster_tracker - .paymaster_balances - .get(&paymaster) - .unwrap() - .pending, - 30.into(), - ); } - #[test] - fn new_uo_not_enough_balance_existing_paymaster() { - let mut paymaster_tracker = PaymasterTracker::new(true); + #[tokio::test] + async fn new_uo_not_enough_balance_existing_paymaster() { + let paymaster_tracker = new_paymaster_tracker(); let paymaster = Address::random(); let sender = Address::random(); let paymaster_balance = U256::from(100); let pending_paymaster_balance = U256::from(10); - paymaster_tracker.paymaster_balances.insert( + paymaster_tracker.add_new_paymaster( paymaster, - PaymasterBalance { - pending: pending_paymaster_balance, - confirmed: paymaster_balance, - }, + paymaster_balance, + pending_paymaster_balance, ); let uo = UserOperation { sender, call_gas_limit: 100.into(), + paymaster_and_data: paymaster.as_bytes().to_vec().into(), pre_verification_gas: 100.into(), verification_gas_limit: 100.into(), max_fee_per_gas: 1.into(), ..Default::default() }; - let paymaster_meta = PaymasterMetadata { - address: paymaster, - pending_balance: paymaster_balance, - confirmed_balance: paymaster_balance, - }; - let po = demo_pool_op(uo); - let res = paymaster_tracker.add_or_update_balance(&po, &paymaster_meta); + let res = paymaster_tracker.add_or_update_balance(&po).await; assert!(res.is_err()); } - #[test] - fn new_uo_existing_paymaster_valid_balance() { - let mut paymaster_tracker = PaymasterTracker::new(true); + #[tokio::test] + async fn test_reset_balances() { + let paymaster_tracker = new_paymaster_tracker(); + + let paymaster_0 = Address::random(); + let paymaster_0_confimed = 1000.into(); + + paymaster_tracker.add_new_paymaster(paymaster_0, paymaster_0_confimed, 0.into()); + + let balance_0 = paymaster_tracker + .paymaster_balance(paymaster_0) + .await + .unwrap(); + + assert_eq!(balance_0.confirmed_balance, 1000.into()); + + let _ = paymaster_tracker.reset_confimed_balances().await; + + let balance_0 = paymaster_tracker + .paymaster_balance(paymaster_0) + .await + .unwrap(); + + assert_eq!(balance_0.confirmed_balance, 50.into()); + } + + #[tokio::test] + async fn new_uo_existing_paymaster_valid_balance() { + let paymaster_tracker = new_paymaster_tracker(); let paymaster = Address::random(); let paymaster_balance = U256::from(100000000); let pending_paymaster_balance = U256::from(10); - paymaster_tracker.paymaster_balances.insert( + paymaster_tracker.add_new_paymaster( paymaster, - PaymasterBalance { - pending: pending_paymaster_balance, - confirmed: paymaster_balance, - }, + paymaster_balance, + pending_paymaster_balance, ); let sender = Address::random(); @@ -534,51 +766,35 @@ mod tests { call_gas_limit: 10.into(), pre_verification_gas: 10.into(), verification_gas_limit: 10.into(), + paymaster_and_data: paymaster.as_bytes().to_vec().into(), max_fee_per_gas: 1.into(), ..Default::default() }; let uo_max_cost = uo.clone().max_gas_cost(); - let paymaster_meta = PaymasterMetadata { - address: paymaster, - pending_balance: paymaster_balance, - confirmed_balance: paymaster_balance, - }; - let po = demo_pool_op(uo); - let res = paymaster_tracker.add_or_update_balance(&po, &paymaster_meta); + let res = paymaster_tracker.add_or_update_balance(&po).await; assert!(res.is_ok()); + + let remaining = paymaster_tracker + .paymaster_balance(paymaster) + .await + .unwrap(); + + assert_eq!(remaining.confirmed_balance, paymaster_balance); assert_eq!( - paymaster_tracker - .paymaster_balances - .get(&paymaster) - .unwrap() - .confirmed, - paymaster_balance, - ); - assert_eq!( - paymaster_tracker - .paymaster_balances - .get(&paymaster) - .unwrap() - .pending, - pending_paymaster_balance.saturating_add(uo_max_cost), - ); - assert_eq!( - paymaster_tracker - .paymaster_balances - .get(&paymaster) - .unwrap() - .pending_balance(), - paymaster_balance.saturating_sub(uo_max_cost.saturating_add(pending_paymaster_balance)), + remaining.pending_balance, + paymaster_balance + .saturating_sub(pending_paymaster_balance) + .saturating_sub(uo_max_cost), ); } - #[test] - fn replacement_uo_new_paymaster() { - let mut paymaster_tracker = PaymasterTracker::new(true); + #[tokio::test] + async fn replacement_uo_new_paymaster() { + let paymaster_tracker = new_paymaster_tracker(); let paymaster_0 = Address::random(); let paymaster_1 = Address::random(); @@ -590,6 +806,7 @@ mod tests { sender, call_gas_limit: 10.into(), pre_verification_gas: 10.into(), + paymaster_and_data: paymaster_0.as_bytes().to_vec().into(), verification_gas_limit: 10.into(), max_fee_per_gas: 1.into(), ..Default::default() @@ -597,30 +814,27 @@ mod tests { let mut uo_1 = uo.clone(); uo_1.max_fee_per_gas = 2.into(); + uo_1.paymaster_and_data = paymaster_1.as_bytes().to_vec().into(); let max_op_cost_0 = uo.max_gas_cost(); let max_op_cost_1 = uo_1.max_gas_cost(); - let paymaster_meta_0 = PaymasterMetadata { - address: paymaster_0, - pending_balance: paymaster_balance_0, - confirmed_balance: paymaster_balance_0, - }; - - let paymaster_meta_1 = PaymasterMetadata { - address: paymaster_1, - pending_balance: paymaster_balance_1, - confirmed_balance: paymaster_balance_1, - }; + paymaster_tracker.add_new_paymaster(paymaster_0, paymaster_balance_0, 0.into()); + paymaster_tracker.add_new_paymaster(paymaster_1, paymaster_balance_1, 0.into()); let po_0 = demo_pool_op(uo); + // Update first paymaster balance with first uo paymaster_tracker - .add_or_update_balance(&po_0, &paymaster_meta_0) + .add_or_update_balance(&po_0) + .await .unwrap(); assert_eq!( - paymaster_tracker.paymaster_metadata(paymaster_0).unwrap(), + paymaster_tracker + .paymaster_balance(paymaster_0) + .await + .unwrap(), PaymasterMetadata { address: paymaster_0, confirmed_balance: paymaster_balance_0, @@ -631,12 +845,16 @@ mod tests { let po_1 = demo_pool_op(uo_1); // send same uo with updated fees and new paymaster paymaster_tracker - .add_or_update_balance(&po_1, &paymaster_meta_1) + .add_or_update_balance(&po_1) + .await .unwrap(); // check previous paymaster goes back to normal balance assert_eq!( - paymaster_tracker.paymaster_metadata(paymaster_0).unwrap(), + paymaster_tracker + .paymaster_balance(paymaster_0) + .await + .unwrap(), PaymasterMetadata { address: paymaster_0, confirmed_balance: paymaster_balance_0, @@ -646,7 +864,10 @@ mod tests { // check that new paymaster has been updated correctly assert_eq!( - paymaster_tracker.paymaster_metadata(paymaster_1).unwrap(), + paymaster_tracker + .paymaster_balance(paymaster_1) + .await + .unwrap(), PaymasterMetadata { address: paymaster_1, confirmed_balance: paymaster_balance_1, @@ -655,9 +876,9 @@ mod tests { ); } - #[test] - fn replacement_uo_same_paymaster() { - let mut paymaster_tracker = PaymasterTracker::new(true); + #[tokio::test] + async fn replacement_uo_same_paymaster() { + let paymaster_tracker = new_paymaster_tracker(); let sender = Address::random(); let paymaster = Address::random(); let paymaster_balance = U256::from(100000000); @@ -666,22 +887,19 @@ mod tests { let existing_id = UserOperationId { sender, nonce }; - paymaster_tracker.paymaster_balances.insert( + // add paymaster + paymaster_tracker.add_new_paymaster( paymaster, - PaymasterBalance { - pending: pending_paymaster_balance, - confirmed: paymaster_balance, - }, + paymaster_balance, + pending_paymaster_balance, ); - // existing fee - paymaster_tracker.user_op_fees.insert( - existing_id, - UserOpFees { - paymaster, - max_op_cost: 30.into(), - }, - ); + let meta = paymaster_tracker + .paymaster_balance(paymaster) + .await + .unwrap(); + + paymaster_tracker.add_new_user_op(&existing_id, &meta, 30.into()); // replacement_uo let uo = UserOperation { @@ -690,45 +908,96 @@ mod tests { call_gas_limit: 100.into(), pre_verification_gas: 100.into(), verification_gas_limit: 100.into(), + paymaster_and_data: paymaster.as_bytes().to_vec().into(), max_fee_per_gas: 1.into(), ..Default::default() }; - let paymaster_meta = PaymasterMetadata { - address: paymaster, - pending_balance: paymaster_balance, - confirmed_balance: paymaster_balance, - }; - let max_op_cost = uo.clone().max_gas_cost(); let po = demo_pool_op(uo); - let res = paymaster_tracker.add_or_update_balance(&po, &paymaster_meta); + let res = paymaster_tracker.add_or_update_balance(&po).await; assert!(res.is_ok()); assert_eq!( paymaster_tracker - .paymaster_balances - .get(&paymaster) + .paymaster_balance(paymaster) + .await .unwrap() - .confirmed, + .confirmed_balance, paymaster_balance, ); assert_eq!( paymaster_tracker - .paymaster_balances - .get(&paymaster) - .unwrap() - .pending, - max_op_cost, - ); - assert_eq!( - paymaster_tracker - .paymaster_balances - .get(&paymaster) + .paymaster_balance(paymaster) + .await .unwrap() - .pending_balance(), - paymaster_balance.saturating_sub(max_op_cost), + .pending_balance, + paymaster_balance + .saturating_sub(pending_paymaster_balance) + .saturating_sub(max_op_cost), ); } + + #[tokio::test] + async fn test_stake_status_staked() { + let tracker = new_paymaster_tracker(); + + let status = tracker.get_stake_status(Address::random()).await.unwrap(); + + assert!(status.is_staked); + } + + fn new_paymaster_tracker() -> PaymasterTracker { + let mut helper = MockPaymasterHelper::new(); + let mut entrypoint = MockEntryPoint::new(); + + helper.expect_get_deposit_info().returning(|_| { + Ok(DepositInfo { + deposit: 1000, + staked: true, + stake: 10000, + unstake_delay_sec: 100, + withdraw_time: 10, + }) + }); + + helper + .expect_get_balances() + .returning(|_| Ok(vec![50.into()])); + + entrypoint + .expect_balance_of() + .returning(|_, _| Ok(U256::from(1000))); + + let config = PaymasterConfig::new(1001, 99, true); + + PaymasterTracker::new(helper, entrypoint, config) + } + + impl PaymasterTracker { + fn add_new_user_op( + &self, + id: &UserOperationId, + paymaster_metadata: &PaymasterMetadata, + max_op_cost: U256, + ) { + self.state + .write() + .add_new_user_op(id, paymaster_metadata, max_op_cost) + } + + fn add_new_paymaster( + &self, + address: Address, + confirmed_balance: U256, + inital_pending_balance: U256, + ) { + self.state.write().add_new_paymaster( + address, + confirmed_balance, + inital_pending_balance, + ); + } + } } diff --git a/crates/pool/src/mempool/pool.rs b/crates/pool/src/mempool/pool.rs index cb9ad98f4..856a949d5 100644 --- a/crates/pool/src/mempool/pool.rs +++ b/crates/pool/src/mempool/pool.rs @@ -29,11 +29,10 @@ use tracing::info; use super::{ entity_tracker::EntityCounter, error::{MempoolError, MempoolResult}, - paymaster::PaymasterTracker, size::SizeTracker, - PaymasterMetadata, PoolConfig, PoolOperation, + PoolConfig, PoolOperation, }; -use crate::chain::{BalanceUpdate, MinedOp}; +use crate::chain::MinedOp; #[derive(Debug, Clone)] pub(crate) struct PoolInnerConfig { @@ -43,7 +42,6 @@ pub(crate) struct PoolInnerConfig { min_replacement_fee_increase_percentage: u64, throttled_entity_mempool_count: u64, throttled_entity_live_blocks: u64, - paymaster_tracking_enabled: bool, } impl From for PoolInnerConfig { @@ -55,7 +53,6 @@ impl From for PoolInnerConfig { min_replacement_fee_increase_percentage: config.min_replacement_fee_increase_percentage, throttled_entity_mempool_count: config.throttled_entity_mempool_count, throttled_entity_live_blocks: config.throttled_entity_live_blocks, - paymaster_tracking_enabled: config.paymaster_tracking_enabled, } } } @@ -82,8 +79,6 @@ pub(crate) struct PoolInner { count_by_address: HashMap, /// Submission ID counter submission_id: u64, - /// A field that keeps track of paymaster balances across the mempool - paymaster_balances: PaymasterTracker, /// keeps track of the size of the pool in bytes pool_size: SizeTracker, /// keeps track of the size of the removed cache in bytes @@ -93,7 +88,6 @@ pub(crate) struct PoolInner { impl PoolInner { pub(crate) fn new(config: PoolInnerConfig) -> Self { Self { - paymaster_balances: PaymasterTracker::new(config.paymaster_tracking_enabled), config, by_hash: HashMap::new(), by_id: HashMap::new(), @@ -140,29 +134,12 @@ impl PoolInner { } } - pub(crate) fn add_operation( - &mut self, - op: PoolOperation, - paymaster_meta: Option, - ) -> MempoolResult { - let ret = self.add_operation_internal(Arc::new(op), None, paymaster_meta); + pub(crate) fn add_operation(&mut self, op: PoolOperation) -> MempoolResult { + let ret = self.add_operation_internal(Arc::new(op), None); self.update_metrics(); ret } - pub(crate) fn paymaster_addresses(&self) -> Vec
{ - self.paymaster_balances.paymaster_addresses() - } - - pub(crate) fn set_confirmed_paymaster_balances( - &mut self, - addresses: &[Address], - balances: &[U256], - ) { - self.paymaster_balances - .set_confimed_balances(addresses, balances); - } - pub(crate) fn best_operations(&self) -> impl Iterator> { self.best.clone().into_iter().map(|v| v.po) } @@ -261,9 +238,6 @@ impl PoolInner { .uo() .op_hash(mined_op.entry_point, self.config.chain_id); - self.paymaster_balances - .update_paymaster_balance_from_mined_op(mined_op); - let ret = self.remove_operation_internal(hash, Some(block_number)); self.update_metrics(); @@ -276,7 +250,7 @@ impl PoolInner { self.mined_hashes_with_block_numbers .remove(&(block_number, hash)); - if let Err(error) = self.put_back_unmined_operation(op.clone(), mined_op) { + if let Err(error) = self.put_back_unmined_operation(op.clone()) { info!("Could not put back unmined operation: {error}"); }; self.update_metrics(); @@ -349,60 +323,16 @@ impl PoolInner { self.update_metrics(); } - pub(crate) fn paymaster_metadata(&self, paymaster: Address) -> Option { - self.paymaster_balances.paymaster_metadata(paymaster) - } - - pub(crate) fn dump_paymaster_metadata(&self) -> Vec { - self.paymaster_balances.dump_paymaster_metadata() - } - - pub(crate) fn paymaster_exists(&self, paymaster: Address) -> bool { - self.paymaster_balances.paymaster_exists(paymaster) - } - - pub(crate) fn update_paymaster_balances_after_update<'a>( - &mut self, - entity_balance_updates: impl Iterator, - unmined_entity_balance_updates: impl Iterator, - ) { - for balance_update in entity_balance_updates { - self.paymaster_balances.update_paymaster_balance_from_event( - balance_update.address, - balance_update.amount, - balance_update.is_addition, - ) - } - - for unmined_balance_update in unmined_entity_balance_updates { - self.paymaster_balances.update_paymaster_balance_from_event( - unmined_balance_update.address, - unmined_balance_update.amount, - !unmined_balance_update.is_addition, - ) - } - } - - pub(crate) fn clear(&mut self, clear_mempool: bool, clear_paymaster: bool) { - if clear_mempool { - self.by_hash.clear(); - self.by_id.clear(); - self.best.clear(); - self.mined_at_block_number_by_hash.clear(); - self.mined_hashes_with_block_numbers.clear(); - self.count_by_address.clear(); - self.pool_size = SizeTracker::default(); - self.cache_size = SizeTracker::default(); - self.update_metrics(); - } - - if clear_paymaster { - self.paymaster_balances.clear(); - } - } - - pub(crate) fn set_tracking(&mut self, paymaster: bool) { - self.paymaster_balances.set_paymaster_tracker(paymaster); + pub(crate) fn clear(&mut self) { + self.by_hash.clear(); + self.by_id.clear(); + self.best.clear(); + self.mined_at_block_number_by_hash.clear(); + self.mined_hashes_with_block_numbers.clear(); + self.count_by_address.clear(); + self.pool_size = SizeTracker::default(); + self.cache_size = SizeTracker::default(); + self.update_metrics(); } fn enforce_size(&mut self) -> anyhow::Result> { @@ -425,27 +355,14 @@ impl PoolInner { Ok(removed) } - fn put_back_unmined_operation( - &mut self, - op: OrderedPoolOperation, - mined_op: &MinedOp, - ) -> MempoolResult { - let mut paymaster_meta = None; - if let Some(paymaster) = op.uo().paymaster() { - self.paymaster_balances - .unmine_actual_cost(&paymaster, mined_op.actual_gas_cost); - - paymaster_meta = self.paymaster_metadata(paymaster); - } - - self.add_operation_internal(op.po, Some(op.submission_id), paymaster_meta) + fn put_back_unmined_operation(&mut self, op: OrderedPoolOperation) -> MempoolResult { + self.add_operation_internal(op.po, Some(op.submission_id)) } fn add_operation_internal( &mut self, op: Arc, submission_id: Option, - paymaster_meta: Option, ) -> MempoolResult { // Check if operation already known or replacing an existing operation // if replacing, remove the existing operation @@ -453,12 +370,6 @@ impl PoolInner { self.remove_operation_by_hash(hash); } - // check or update paymaster balance - if let Some(paymaster_meta) = paymaster_meta { - self.paymaster_balances - .add_or_update_balance(&op, &paymaster_meta)?; - } - let pool_op = OrderedPoolOperation { po: op, submission_id: submission_id.unwrap_or_else(|| self.next_submission_id()), @@ -502,7 +413,6 @@ impl PoolInner { let id = &op.po.uo.id(); self.by_id.remove(id); self.best.remove(&op); - self.paymaster_balances.remove_operation(id); if let Some(block_number) = block_number { self.cache_size += op.mem_size(); @@ -627,7 +537,7 @@ mod tests { fn add_single_op() { let mut pool = PoolInner::new(conf()); let op = create_op(Address::random(), 0, 1); - let hash = pool.add_operation(op.clone(), None).unwrap(); + let hash = pool.add_operation(op.clone()).unwrap(); check_map_entry(pool.by_hash.get(&hash), Some(&op)); check_map_entry(pool.by_id.get(&op.uo.id()), Some(&op)); @@ -645,7 +555,7 @@ mod tests { let mut hashes = vec![]; for op in ops.iter() { - hashes.push(pool.add_operation(op.clone(), None).unwrap()); + hashes.push(pool.add_operation(op.clone()).unwrap()); } for (hash, op) in hashes.iter().zip(&ops) { @@ -671,7 +581,7 @@ mod tests { let mut hashes = vec![]; for op in ops.iter() { - hashes.push(pool.add_operation(op.clone(), None).unwrap()); + hashes.push(pool.add_operation(op.clone()).unwrap()); } // best should be sorted by gas, then by submission id @@ -692,7 +602,7 @@ mod tests { let mut hashes = vec![]; for op in ops.iter() { - hashes.push(pool.add_operation(op.clone(), None).unwrap()); + hashes.push(pool.add_operation(op.clone()).unwrap()); } assert!(pool.remove_operation_by_hash(hashes[0]).is_some()); @@ -723,7 +633,7 @@ mod tests { ]; for mut op in ops.into_iter() { op.aggregator = Some(account); - pool.add_operation(op.clone(), None).unwrap(); + pool.add_operation(op.clone()).unwrap(); } assert_eq!(pool.by_hash.len(), 3); @@ -743,7 +653,7 @@ mod tests { let hash = op.uo.op_hash(pool.config.entry_point, pool.config.chain_id); - pool.add_operation(op, None).unwrap(); + pool.add_operation(op).unwrap(); let mined_op = MinedOp { paymaster: None, @@ -774,8 +684,8 @@ mod tests { .uo .op_hash(pool.config.entry_point, pool.config.chain_id); - pool.add_operation(op, None).unwrap(); - pool.add_operation(op_2, None).unwrap(); + pool.add_operation(op).unwrap(); + pool.add_operation(op_2).unwrap(); let mined_op = MinedOp { paymaster: None, @@ -808,7 +718,7 @@ mod tests { address: agg, is_staked: false, }); - pool.add_operation(op.clone(), None).unwrap(); + pool.add_operation(op.clone()).unwrap(); } assert_eq!(pool.by_hash.len(), 3); @@ -833,7 +743,7 @@ mod tests { address: op.uo.paymaster().unwrap(), is_staked: false, }); - pool.add_operation(op.clone(), None).unwrap(); + pool.add_operation(op.clone()).unwrap(); } assert_eq!(pool.by_hash.len(), 3); @@ -873,7 +783,7 @@ mod tests { for i in 0..count { let mut op = op.clone(); op.uo.nonce = i.into(); - hashes.push(pool.add_operation(op, None).unwrap()); + hashes.push(pool.add_operation(op).unwrap()); } assert_eq!(pool.address_count(&sender), 5); @@ -897,12 +807,12 @@ mod tests { let mut pool = PoolInner::new(args.clone()); for i in 0..20 { let op = create_op(Address::random(), i, i + 1); - pool.add_operation(op, None).unwrap(); + pool.add_operation(op).unwrap(); } // on greater gas, new op should win let op = create_op(Address::random(), args.max_size_of_pool_bytes, 2); - let result = pool.add_operation(op, None); + let result = pool.add_operation(op); assert!(result.is_ok(), "{:?}", result.err()); } @@ -912,15 +822,15 @@ mod tests { let mut pool = PoolInner::new(args.clone()); for i in 0..20 { let op = create_op(Address::random(), i, i + 1); - pool.add_operation(op, None).unwrap(); + pool.add_operation(op).unwrap(); } let op = create_op(Address::random(), 4, 1); - assert!(pool.add_operation(op, None).is_err()); + assert!(pool.add_operation(op).is_err()); // on equal gas, worst should remain because it came first let op = create_op(Address::random(), 4, 2); - let result = pool.add_operation(op, None); + let result = pool.add_operation(op); assert!(result.is_ok(), "{:?}", result.err()); } @@ -930,11 +840,11 @@ mod tests { let sender = Address::random(); let mut po1 = create_op(sender, 0, 100); po1.uo.max_priority_fee_per_gas = 100.into(); - let _ = pool.add_operation(po1.clone(), None).unwrap(); + let _ = pool.add_operation(po1.clone()).unwrap(); let mut po2 = create_op(sender, 0, 101); po2.uo.max_priority_fee_per_gas = 101.into(); - let res = pool.add_operation(po2, None); + let res = pool.add_operation(po2); assert!(res.is_err()); match res.err().unwrap() { MempoolError::ReplacementUnderpriced(a, b) => { @@ -967,7 +877,7 @@ mod tests { address: po1.uo.paymaster().unwrap(), is_staked: false, }); - let _ = pool.add_operation(po1, None).unwrap(); + let _ = pool.add_operation(po1).unwrap(); assert_eq!(pool.address_count(&paymaster1), 1); let paymaster2 = Address::random(); @@ -978,7 +888,7 @@ mod tests { address: po2.uo.paymaster().unwrap(), is_staked: false, }); - let _ = pool.add_operation(po2.clone(), None).unwrap(); + let _ = pool.add_operation(po2.clone()).unwrap(); assert_eq!(pool.address_count(&sender), 1); assert_eq!(pool.address_count(&paymaster1), 0); @@ -999,9 +909,9 @@ mod tests { let sender = Address::random(); let mut po1 = create_op(sender, 0, 10); po1.uo.max_priority_fee_per_gas = 10.into(); - let _ = pool.add_operation(po1.clone(), None).unwrap(); + let _ = pool.add_operation(po1.clone()).unwrap(); - let res = pool.add_operation(po1, None); + let res = pool.add_operation(po1); assert!(res.is_err()); match res.err().unwrap() { MempoolError::OperationAlreadyKnown => (), @@ -1016,7 +926,7 @@ mod tests { let sender = Address::random(); let mut po1 = create_op(sender, 0, 10); po1.valid_time_range.valid_until = Timestamp::from(1); - let _ = pool.add_operation(po1.clone(), None).unwrap(); + let _ = pool.add_operation(po1.clone()).unwrap(); let res = pool.remove_expired(Timestamp::from(2)); assert_eq!(res.len(), 1); @@ -1031,15 +941,14 @@ mod tests { let mut po1 = create_op(Address::random(), 0, 10); po1.valid_time_range.valid_until = 5.into(); - let _ = pool.add_operation(po1.clone(), None).unwrap(); + let _ = pool.add_operation(po1.clone()).unwrap(); let mut po2 = create_op(Address::random(), 0, 10); po2.valid_time_range.valid_until = 10.into(); - let _ = pool.add_operation(po2.clone(), None).unwrap(); - + let _ = pool.add_operation(po2.clone()).unwrap(); let mut po3 = create_op(Address::random(), 0, 10); po3.valid_time_range.valid_until = 9.into(); - let _ = pool.add_operation(po3.clone(), None).unwrap(); + let _ = pool.add_operation(po3.clone()).unwrap(); let res = pool.remove_expired(10.into()); assert_eq!(res.len(), 2); @@ -1055,7 +964,6 @@ mod tests { max_size_of_pool_bytes: 20 * mem_size_of_ordered_pool_op(), throttled_entity_mempool_count: 4, throttled_entity_live_blocks: 10, - paymaster_tracking_enabled: true, } } diff --git a/crates/pool/src/mempool/reputation.rs b/crates/pool/src/mempool/reputation.rs index 9521c18c0..87e79ab60 100644 --- a/crates/pool/src/mempool/reputation.rs +++ b/crates/pool/src/mempool/reputation.rs @@ -17,8 +17,6 @@ use std::{ }; use ethers::types::Address; -#[cfg(test)] -use mockall::automock; use parking_lot::RwLock; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use tokio::time::interval; @@ -73,136 +71,6 @@ pub struct Reputation { pub ops_included: u64, } -/// Reputation manager trait -/// -/// Interior mutability pattern used as ReputationManagers may -/// need to be thread-safe. -#[cfg_attr(test, automock)] -pub(crate) trait ReputationManager: Send + Sync + 'static { - /// Called by mempool before returning operations to bundler - fn status(&self, address: Address) -> ReputationStatus; - - /// Called by mempool when an operation that requires stake is added to the - /// pool - fn add_seen(&self, address: Address); - - /// Called by mempool when an unstaked entity causes the invalidation of a bundle - fn handle_urep_030_penalty(&self, address: Address); - - /// Called by mempool when a staked entity causes the invalidation of a bundle - fn handle_srep_050_penalty(&self, address: Address); - - /// Called by the mempool when an operation that requires stake is removed - /// from the pool - fn add_included(&self, address: Address); - - /// Called by the mempool when a previously mined operation that requires - /// stake is returned to the pool. - fn remove_included(&self, address: Address); - - /// Called by debug API - fn dump_reputation(&self) -> Vec; - - /// Called by debug API - fn set_reputation(&self, address: Address, ops_seen: u64, ops_included: u64); - - /// Get the ops allowed for an unstaked entity - fn get_ops_allowed(&self, address: Address) -> u64; - - /// Clear all reputation values - fn clear(&self); - - /// Sets whether reputation tracking can block user operations - fn set_tracking(&self, tracking_enabled: bool); -} - -#[derive(Debug)] -pub(crate) struct HourlyMovingAverageReputation { - reputation: RwLock, -} - -impl HourlyMovingAverageReputation { - pub(crate) fn new( - params: ReputationParams, - blocklist: Option>, - allowlist: Option>, - ) -> Self { - let rep = AddressReputation::new(params) - .with_blocklist(blocklist.unwrap_or_default()) - .with_allowlist(allowlist.unwrap_or_default()); - - Self { - reputation: RwLock::new(rep), - } - } - - // run the reputation hourly update job - pub(crate) async fn run(&self) { - let mut tick = interval(Duration::from_secs(60 * 60)); - loop { - tick.tick().await; - self.reputation.write().hourly_update(); - } - } -} - -impl ReputationManager for HourlyMovingAverageReputation { - fn status(&self, address: Address) -> ReputationStatus { - self.reputation.read().status(address) - } - - fn add_seen(&self, address: Address) { - self.reputation.write().add_seen(address); - } - - fn handle_urep_030_penalty(&self, address: Address) { - self.reputation.write().handle_urep_030_penalty(address); - } - - fn handle_srep_050_penalty(&self, address: Address) { - self.reputation.write().handle_srep_050_penalty(address); - } - - fn add_included(&self, address: Address) { - self.reputation.write().add_included(address); - } - - fn remove_included(&self, address: Address) { - self.reputation.write().remove_included(address); - } - - fn dump_reputation(&self) -> Vec { - let reputation = self.reputation.read(); - reputation - .counts - .iter() - .map(|(address, count)| Reputation { - address: *address, - ops_seen: count.ops_seen, - ops_included: count.ops_included, - }) - .collect() - } - - fn set_reputation(&self, address: Address, ops_seen: u64, ops_included: u64) { - self.reputation - .write() - .set_reputation(address, ops_seen, ops_included) - } - - fn get_ops_allowed(&self, address: Address) -> u64 { - self.reputation.read().get_ops_allowed(address) - } - - fn clear(&self) { - self.reputation.write().clear() - } - - fn set_tracking(&self, tracking_enabled: bool) { - self.reputation.write().set_tracking(tracking_enabled) - } -} - #[derive(Debug, Clone, Copy)] pub(crate) struct ReputationParams { bundle_invalidation_ops_seen_staked_penalty: u64, @@ -213,6 +81,8 @@ pub(crate) struct ReputationParams { throttling_slack: u64, ban_slack: u64, tracking_enabled: bool, + decay_interval_secs: u64, + decay_factor: u64, } impl Default for ReputationParams { @@ -226,6 +96,8 @@ impl Default for ReputationParams { throttling_slack: 10, ban_slack: 50, tracking_enabled: true, + decay_interval_secs: 3600, + decay_factor: 24, } } } @@ -250,10 +122,95 @@ impl ReputationParams { ..Self::default() } } + + #[cfg(test)] + pub(crate) fn test_parameters(ban_slack: u64, throttling_slack: u64) -> Self { + Self { + ban_slack, + throttling_slack, + ..Self::default() + } + } +} + +pub(crate) struct AddressReputation { + state: RwLock, +} + +impl AddressReputation { + pub(crate) fn new( + params: ReputationParams, + blocklist: HashSet
, + allowlist: HashSet
, + ) -> AddressReputation { + Self { + state: RwLock::new( + AddressReputationInner::new(params) + .with_blocklist(blocklist) + .with_allowlist(allowlist), + ), + } + } + + pub(crate) async fn run(&self) { + let mut tick = interval(Duration::from_secs( + self.state.read().params.decay_interval_secs, + )); + loop { + tick.tick().await; + self.state.write().update(); + } + } + + pub(crate) fn status(&self, address: Address) -> ReputationStatus { + self.state.read().status(address) + } + + pub(crate) fn add_seen(&self, address: Address) { + self.state.write().add_seen(address); + } + + pub(crate) fn handle_urep_030_penalty(&self, address: Address) { + self.state.write().handle_urep_030_penalty(address); + } + + pub(crate) fn handle_srep_050_penalty(&self, address: Address) { + self.state.write().handle_srep_050_penalty(address); + } + + pub(crate) fn dump_reputation(&self) -> Vec { + self.state.read().dump_reputation() + } + + pub(crate) fn add_included(&self, address: Address) { + self.state.write().add_included(address); + } + + pub(crate) fn remove_included(&self, address: Address) { + self.state.write().remove_included(address); + } + + pub(crate) fn set_reputation(&self, address: Address, ops_seen: u64, ops_included: u64) { + self.state + .write() + .set_reputation(address, ops_seen, ops_included); + } + + pub(crate) fn get_ops_allowed(&self, address: Address) -> u64 { + self.state.read().get_ops_allowed(address) + } + + pub(crate) fn clear(&self) { + self.state.write().clear(); + } + + pub(crate) fn set_tracking(&self, tracking_enabled: bool) { + self.state.write().set_tracking(tracking_enabled); + } } #[derive(Debug)] -struct AddressReputation { +struct AddressReputationInner { // Addresses that are always banned blocklist: HashSet
, // Addresses that are always exempt from throttling and banning @@ -262,9 +219,9 @@ struct AddressReputation { params: ReputationParams, } -impl AddressReputation { - fn new(params: ReputationParams) -> Self { - Self { +impl AddressReputationInner { + fn new(params: ReputationParams) -> AddressReputationInner { + AddressReputationInner { blocklist: HashSet::new(), allowlist: HashSet::new(), counts: HashMap::new(), @@ -272,12 +229,12 @@ impl AddressReputation { } } - fn with_blocklist(self, blocklist: HashSet
) -> Self { - Self { blocklist, ..self } + fn with_blocklist(self, blocklist: HashSet
) -> AddressReputationInner { + AddressReputationInner { blocklist, ..self } } - fn with_allowlist(self, allowlist: HashSet
) -> Self { - Self { allowlist, ..self } + fn with_allowlist(self, allowlist: HashSet
) -> AddressReputationInner { + AddressReputationInner { allowlist, ..self } } fn status(&self, address: Address) -> ReputationStatus { @@ -322,6 +279,17 @@ impl AddressReputation { count.ops_seen = self.params.bundle_invalidation_ops_seen_staked_penalty; } + fn dump_reputation(&self) -> Vec { + self.counts + .iter() + .map(|(address, count)| Reputation { + address: *address, + ops_seen: count.ops_seen, + ops_included: count.ops_included, + }) + .collect() + } + fn add_included(&mut self, address: Address) { let count = self.counts.entry(address).or_default(); count.ops_included += 1; @@ -355,10 +323,10 @@ impl AddressReputation { self.params.same_unstaked_entity_mempool_count + inclusion_based_count } - fn hourly_update(&mut self) { + fn update(&mut self) { for count in self.counts.values_mut() { - count.ops_seen -= count.ops_seen / 24; - count.ops_included -= count.ops_included / 24; + count.ops_seen -= count.ops_seen / self.params.decay_factor; + count.ops_included -= count.ops_included / self.params.decay_factor; } self.counts .retain(|_, count| count.ops_seen > 0 || count.ops_included > 0); @@ -388,7 +356,7 @@ mod tests { #[test] fn seen_included() { let addr = Address::random(); - let mut reputation = AddressReputation::new(ReputationParams::bundler_default()); + let mut reputation = AddressReputationInner::new(ReputationParams::bundler_default()); for _ in 0..1000 { reputation.add_seen(addr); @@ -402,7 +370,7 @@ mod tests { #[test] fn set_rep() { let addr = Address::random(); - let mut reputation = AddressReputation::new(ReputationParams::bundler_default()); + let mut reputation = AddressReputationInner::new(ReputationParams::bundler_default()); reputation.set_reputation(addr, 1000, 1000); let counts = reputation.counts.get(&addr).unwrap(); @@ -413,7 +381,7 @@ mod tests { #[test] fn reputation_ok() { let addr = Address::random(); - let mut reputation = AddressReputation::new(ReputationParams::bundler_default()); + let mut reputation = AddressReputationInner::new(ReputationParams::bundler_default()); reputation.add_seen(addr); assert_eq!(reputation.status(addr), ReputationStatus::Ok); } @@ -422,7 +390,7 @@ mod tests { fn reputation_throttled() { let addr = Address::random(); let params = ReputationParams::bundler_default(); - let mut reputation = AddressReputation::new(params); + let mut reputation = AddressReputationInner::new(params); let ops_seen = 1000; let ops_included = @@ -435,7 +403,7 @@ mod tests { fn reputation_throttled_edge() { let addr = Address::random(); let params = ReputationParams::bundler_default(); - let mut reputation = AddressReputation::new(params); + let mut reputation = AddressReputationInner::new(params); let ops_seen = 1000; let ops_included = @@ -448,7 +416,7 @@ mod tests { fn reputation_banned() { let addr = Address::random(); let params = ReputationParams::bundler_default(); - let mut reputation = AddressReputation::new(params); + let mut reputation = AddressReputationInner::new(params); let ops_seen = 1000; let ops_included = ops_seen / params.min_inclusion_rate_denominator - params.ban_slack - 1; @@ -460,7 +428,7 @@ mod tests { fn reputation_banned_tracking_disabled() { let addr = Address::random(); let params = ReputationParams::new(false); - let mut reputation = AddressReputation::new(params); + let mut reputation = AddressReputationInner::new(params); let ops_seen = 1000; let ops_included = ops_seen / params.min_inclusion_rate_denominator - params.ban_slack - 1; @@ -471,23 +439,29 @@ mod tests { #[test] fn hourly_update() { let addr = Address::random(); - let mut reputation = AddressReputation::new(ReputationParams::bundler_default()); + let mut reputation = AddressReputationInner::new(ReputationParams::bundler_default()); for _ in 0..1000 { reputation.add_seen(addr); reputation.add_included(addr); } - reputation.hourly_update(); + reputation.update(); let counts = reputation.counts.get(&addr).unwrap(); - assert_eq!(counts.ops_seen, 1000 - 1000 / 24); - assert_eq!(counts.ops_included, 1000 - 1000 / 24); + assert_eq!( + counts.ops_seen, + 1000 - 1000 / reputation.params.decay_factor + ); + assert_eq!( + counts.ops_included, + 1000 - 1000 / reputation.params.decay_factor + ); } #[test] fn test_blocklist() { let addr = Address::random(); - let reputation = AddressReputation::new(ReputationParams::bundler_default()) + let reputation = AddressReputationInner::new(ReputationParams::bundler_default()) .with_blocklist(HashSet::from([addr])); assert_eq!(reputation.status(addr), ReputationStatus::Banned); @@ -497,7 +471,7 @@ mod tests { #[test] fn test_allowlist() { let addr = Address::random(); - let mut reputation = AddressReputation::new(ReputationParams::bundler_default()) + let mut reputation = AddressReputationInner::new(ReputationParams::bundler_default()) .with_allowlist(HashSet::from([addr])); reputation.set_reputation(addr, 1000000, 0); @@ -508,8 +482,7 @@ mod tests { #[test] fn manager_seen_included() { - let manager = - HourlyMovingAverageReputation::new(ReputationParams::bundler_default(), None, None); + let mut manager = AddressReputationInner::new(ReputationParams::bundler_default()); let addrs = [Address::random(), Address::random(), Address::random()]; for _ in 0..10 { @@ -522,8 +495,7 @@ mod tests { for addr in &addrs { assert_eq!(manager.status(*addr), ReputationStatus::Ok); - let rep = manager.reputation.read(); - let counts = rep.counts.get(addr).unwrap(); + let counts = manager.counts.get(addr).unwrap(); assert_eq!(counts.ops_seen, 10); assert_eq!(counts.ops_included, 10); } @@ -531,8 +503,7 @@ mod tests { #[test] fn manager_set_dump_reputation() { - let manager = - HourlyMovingAverageReputation::new(ReputationParams::bundler_default(), None, None); + let mut manager = AddressReputationInner::new(ReputationParams::bundler_default()); let addrs = [Address::random(), Address::random(), Address::random()]; for addr in &addrs { diff --git a/crates/pool/src/mempool/uo_pool.rs b/crates/pool/src/mempool/uo_pool.rs index 9954d043e..ba7c92592 100644 --- a/crates/pool/src/mempool/uo_pool.rs +++ b/crates/pool/src/mempool/uo_pool.rs @@ -19,7 +19,7 @@ use ethers::{ }; use itertools::Itertools; use parking_lot::RwLock; -use rundler_provider::{EntryPoint, PaymasterHelper, ProviderResult}; +use rundler_provider::{EntryPoint, PaymasterHelper}; use rundler_sim::{Prechecker, Simulator}; use rundler_types::{Entity, EntityUpdate, EntityUpdateType, UserOperation}; use rundler_utils::emit::WithEntryPoint; @@ -29,13 +29,15 @@ use tracing::info; use super::{ error::{MempoolError, MempoolResult}, + paymaster::PaymasterTracker, pool::PoolInner, - reputation::{Reputation, ReputationManager, ReputationStatus}, - Mempool, OperationOrigin, PaymasterMetadata, PoolConfig, PoolOperation, StakeInfo, StakeStatus, + reputation::{AddressReputation, Reputation, ReputationStatus}, + Mempool, OperationOrigin, PaymasterMetadata, PoolConfig, PoolOperation, }; use crate::{ chain::ChainUpdate, emit::{EntityReputation, EntityStatus, EntitySummary, OpPoolEvent, OpRemovalReason}, + StakeStatus, }; /// User Operation Mempool @@ -43,21 +45,14 @@ use crate::{ /// Wrapper around a pool object that implements thread-safety /// via a RwLock. Safe to call from multiple threads. Methods /// block on write locks. -pub(crate) struct UoPool< - R: ReputationManager, - P: Prechecker, - S: Simulator, - E: EntryPoint, - PH: PaymasterHelper, -> { +pub(crate) struct UoPool { config: PoolConfig, - reputation: Arc, state: RwLock, + paymaster: PaymasterTracker, + reputation: Arc, event_sender: broadcast::Sender>, prechecker: P, simulator: S, - entry_point: E, - paymaster_helper: PH, } struct UoPoolState { @@ -66,9 +61,8 @@ struct UoPoolState { block_number: u64, } -impl UoPool +impl UoPool where - R: ReputationManager, P: Prechecker, S: Simulator, E: EntryPoint, @@ -76,26 +70,24 @@ where { pub(crate) fn new( config: PoolConfig, - reputation: Arc, event_sender: broadcast::Sender>, prechecker: P, simulator: S, - entry_point: E, - paymaster_helper: PH, + paymaster: PaymasterTracker, + reputation: Arc, ) -> Self { Self { - config: config.clone(), - reputation, state: RwLock::new(UoPoolState { - pool: PoolInner::new(config.into()), + pool: PoolInner::new(config.clone().into()), throttled_ops: HashSet::new(), block_number: 0, }), + reputation, + paymaster, event_sender, prechecker, simulator, - entry_point, - paymaster_helper, + config, } } @@ -140,9 +132,8 @@ where } #[async_trait] -impl Mempool for UoPool +impl Mempool for UoPool where - R: ReputationManager, P: Prechecker, S: Simulator, E: EntryPoint, @@ -177,8 +168,7 @@ where let _ = self.reset_confirmed_paymaster_balances().await; } - let mut state = self.state.write(); - state.pool.update_paymaster_balances_after_update( + self.paymaster.update_paymaster_balances_after_update( entity_balance_updates, unmined_entity_balance_updates, ); @@ -187,13 +177,19 @@ where if op.entry_point != self.config.entry_point { continue; } + self.paymaster.update_paymaster_balance_from_mined_op(op); // Remove throttled ops that were included in the block - state.throttled_ops.remove(&op.hash); + self.state.write().throttled_ops.remove(&op.hash); - if let Some(op) = state.pool.mine_operation(op, update.latest_block_number) { + if let Some(pool_op) = self + .state + .write() + .pool + .mine_operation(op, update.latest_block_number) + { // Only account for an entity once - for entity_addr in op.entities().map(|e| e.address).unique() { + for entity_addr in pool_op.entities().map(|e| e.address).unique() { self.reputation.add_included(entity_addr); } mined_op_count += 1; @@ -204,12 +200,20 @@ where continue; } - if let Some(op) = state.pool.unmine_operation(op) { - // Only account for an entity once - for entity_addr in op.entities().map(|e| e.address).unique() { + if let Some(paymaster) = op.paymaster { + self.paymaster + .unmine_actual_cost(&paymaster, op.actual_gas_cost); + } + + let pool_op = self.state.write().pool.unmine_operation(op); + + if let Some(po) = pool_op { + for entity_addr in po.entities().map(|e| e.address).unique() { self.reputation.remove_included(entity_addr); } + unmined_op_count += 1; + let _ = self.paymaster.add_or_update_balance(&po).await; } } if mined_op_count > 0 { @@ -234,9 +238,11 @@ where ); UoPoolMetrics::increment_unmined_operations(unmined_op_count, self.config.entry_point); + let mut state = self.state.write(); state .pool .forget_mined_operations_before_block(update.earliest_remembered_block_number); + // Remove throttled ops that are too old let mut to_remove = HashSet::new(); for hash in state.throttled_ops.iter() { @@ -251,6 +257,7 @@ where } } } + for (hash, added_at_block) in to_remove { state.pool.remove_operation_by_hash(hash); state.throttled_ops.remove(&hash); @@ -303,24 +310,16 @@ where } fn set_tracking(&self, paymaster: bool, reputation: bool) { - self.state.write().pool.set_tracking(paymaster); + self.paymaster.set_tracking(paymaster); self.reputation.set_tracking(reputation); } async fn reset_confirmed_paymaster_balances(&self) -> MempoolResult<()> { - let paymaster_addresses = self.state.read().pool.paymaster_addresses(); - - let balances = self - .paymaster_helper - .get_balances(paymaster_addresses.clone()) - .await?; - - self.state - .write() - .pool - .set_confirmed_paymaster_balances(&paymaster_addresses, &balances); + self.paymaster.reset_confimed_balances().await + } - Ok(()) + async fn get_stake_status(&self, address: Address) -> MempoolResult { + self.paymaster.get_stake_status(address).await } async fn add_operation( @@ -372,17 +371,10 @@ where self.state.read().pool.check_multiple_roles_violation(&op)?; // check if paymaster is present and exists in pool - // Note: this is super gross but due the fact that we do not want to make - // http calls when we hold the readwrite lock its a work around - let mut paymaster_metadata = None; - if let Some(address) = op.paymaster() { - let meta = self - .paymaster_balance(address) - .await - .map_err(|e| MempoolError::Other(e.into()))?; - - paymaster_metadata = Some(meta); - } + // this is optimistic and could potentially lead to + // multiple user operations call this before they are + // added to the pool and can lead to an overdraft + self.paymaster.check_operation_cost(&op).await?; // Prechecks self.prechecker.check(&op).await?; @@ -449,15 +441,18 @@ where // Add op to pool let hash = { let mut state = self.state.write(); - let hash = state - .pool - .add_operation(pool_op.clone(), paymaster_metadata)?; + let hash = state.pool.add_operation(pool_op.clone())?; + if throttled { state.throttled_ops.insert(hash); } hash }; + // Add op cost to pending paymaster balance + // once the operation has been added to the pool + self.paymaster.add_or_update_balance(&pool_op).await?; + // Update reputation if replacement.is_none() { pool_op.entities().unique().for_each(|e| { @@ -493,7 +488,8 @@ where { let mut state = self.state.write(); for hash in hashes { - if state.pool.remove_operation_by_hash(*hash).is_some() { + if let Some(op) = state.pool.remove_operation_by_hash(*hash) { + self.paymaster.remove_operation(&op.uo.id()); count += 1; removed_hashes.push(*hash); } @@ -525,28 +521,6 @@ where } } - async fn paymaster_balance(&self, paymaster: Address) -> ProviderResult { - if self.state.read().pool.paymaster_exists(paymaster) { - let meta = self - .state - .read() - .pool - .paymaster_metadata(paymaster) - .expect("Paymaster balance should not be empty if address exists in pool"); - return Ok(meta); - } - - let balance = self.entry_point.balance_of(paymaster, None).await?; - - let paymaster_meta = PaymasterMetadata { - address: paymaster, - pending_balance: balance, - confirmed_balance: balance, - }; - - Ok(paymaster_meta) - } - fn best_operations( &self, max: usize, @@ -586,10 +560,13 @@ where } fn clear_state(&self, clear_mempool: bool, clear_paymaster: bool, clear_reputation: bool) { - self.state - .write() - .pool - .clear(clear_mempool, clear_paymaster); + if clear_mempool { + self.state.write().pool.clear(); + } + + if clear_paymaster { + self.paymaster.clear(); + } if clear_reputation { self.reputation.clear() @@ -601,34 +578,13 @@ where } fn dump_paymaster_balances(&self) -> Vec { - self.state.read().pool.dump_paymaster_metadata() + self.paymaster.dump_paymaster_metadata() } fn get_reputation_status(&self, address: Address) -> ReputationStatus { self.reputation.status(address) } - async fn get_stake_status(&self, address: Address) -> MempoolResult { - let deposit_info = self.paymaster_helper.get_deposit_info(address).await?; - - let is_staked = deposit_info - .stake - .ge(&self.config.sim_settings.min_stake_value) - && deposit_info - .unstake_delay_sec - .ge(&self.config.sim_settings.min_unstake_delay); - - let stake_status = StakeStatus { - stake_info: StakeInfo { - stake: deposit_info.stake, - unstake_delay_sec: deposit_info.unstake_delay_sec, - }, - is_staked, - }; - - Ok(stake_status) - } - fn set_reputation(&self, address: Address, ops_seen: u64, ops_included: u64) { self.reputation .set_reputation(address, ops_seen, ops_included) @@ -681,7 +637,10 @@ mod tests { use rundler_types::{DepositInfo, EntityType, GasFees, ValidTimeRange}; use super::*; - use crate::chain::{BalanceUpdate, MinedOp}; + use crate::{ + chain::{BalanceUpdate, MinedOp}, + mempool::{PaymasterConfig, ReputationParams}, + }; const THROTTLE_SLACK: u64 = 5; const BAN_SLACK: u64 = 10; @@ -790,8 +749,7 @@ mod tests { check_ops(pool.best_operations(3, 0).unwrap(), uos[1..].to_vec()); - let paymaster_balance = pool.paymaster_balance(paymaster).await.unwrap(); - + let paymaster_balance = pool.paymaster.paymaster_balance(paymaster).await.unwrap(); assert_eq!(paymaster_balance.confirmed_balance, 1110.into()); } @@ -814,12 +772,7 @@ mod tests { } let (pool, uos) = create_pool_insert_ops(ops).await; - let metadata = pool - .state - .read() - .pool - .paymaster_metadata(paymaster) - .unwrap(); + let metadata = pool.paymaster.paymaster_balance(paymaster).await.unwrap(); assert_eq!(metadata.pending_balance, 850.into()); check_ops(pool.best_operations(3, 0).unwrap(), uos.clone()); @@ -861,13 +814,7 @@ mod tests { uos.clone()[1..].to_vec(), ); - let metadata = pool - .state - .read() - .pool - .paymaster_metadata(paymaster) - .unwrap(); - + let metadata = pool.paymaster.paymaster_balance(paymaster).await.unwrap(); assert_eq!(metadata.pending_balance, 1000.into()); pool.on_chain_update(&ChainUpdate { @@ -896,13 +843,8 @@ mod tests { }) .await; - let metadata = pool - .state - .read() - .pool - .paymaster_metadata(paymaster) - .unwrap(); - assert_eq!(metadata.pending_balance, 860.into()); + let metadata = pool.paymaster.paymaster_balance(paymaster).await.unwrap(); + assert_eq!(metadata.pending_balance, 850.into()); check_ops(pool.best_operations(3, 0).unwrap(), uos); } @@ -996,8 +938,12 @@ mod tests { } let uos = ops.iter().map(|op| op.op.clone()).collect::>(); let pool = create_pool(ops); + + let ops_seen = 100; + let ops_included = ops_seen / 10 - THROTTLE_SLACK - 1; + // Past throttle slack - pool.set_reputation(address, 1 + THROTTLE_SLACK, 0); + pool.set_reputation(address, ops_seen, ops_included); // Ops 0 through 3 should be included for uo in uos.iter().take(4) { @@ -1075,7 +1021,10 @@ mod tests { let uo = op.op.clone(); let pool = create_pool(vec![op]); // Past ban slack - pool.set_reputation(address, 1 + BAN_SLACK, 0); + + let ops_seen = 1000; + let ops_included = ops_seen / 10 - BAN_SLACK - 1; + pool.set_reputation(address, ops_seen, ops_included); // First op should be banned let ret = pool.add_operation(OperationOrigin::Local, uo.clone()).await; @@ -1191,18 +1140,6 @@ mod tests { check_ops(pool.best_operations(1, 0).unwrap(), vec![op.op]); } - #[tokio::test] - async fn test_stake_status_staked() { - let mut pool = create_pool(vec![]); - - pool.config.sim_settings.min_stake_value = 9999; - pool.config.sim_settings.min_unstake_delay = 99; - - let status = pool.get_stake_status(Address::random()).await.unwrap(); - - assert!(status.is_staked); - } - #[tokio::test] async fn test_stake_status_not_staked() { let mut pool = create_pool(vec![]); @@ -1242,12 +1179,7 @@ mod tests { check_ops(pool.best_operations(1, 0).unwrap(), vec![replacement]); - let paymaster_balance = pool - .state - .read() - .pool - .paymaster_metadata(paymaster) - .unwrap(); + let paymaster_balance = pool.paymaster.paymaster_balance(paymaster).await.unwrap(); assert_eq!(paymaster_balance.pending_balance, U256::from(900)); let rep = pool.dump_reputation(); assert_eq!(rep.len(), 1); @@ -1340,28 +1272,29 @@ mod tests { fn create_pool( ops: Vec, - ) -> UoPool< - impl ReputationManager, - impl Prechecker, - impl Simulator, - impl EntryPoint, - impl PaymasterHelper, - > { - let reputation = Arc::new(MockReputationManager::new(THROTTLE_SLACK, BAN_SLACK)); + ) -> UoPool { + let args = PoolConfig { + entry_point: Address::random(), + chain_id: 1, + min_replacement_fee_increase_percentage: 10, + max_size_of_pool_bytes: 10000, + blocklist: None, + allowlist: None, + precheck_settings: PrecheckSettings::default(), + sim_settings: SimulationSettings::default(), + mempool_channel_configs: HashMap::new(), + num_shards: 1, + same_sender_mempool_count: 4, + throttled_entity_mempool_count: 4, + throttled_entity_live_blocks: 10, + paymaster_tracking_enabled: true, + reputation_tracking_enabled: true, + }; + let mut simulator = MockSimulator::new(); let mut prechecker = MockPrechecker::new(); let mut entrypoint = MockEntryPoint::new(); let mut paymaster_helper = MockPaymasterHelper::new(); - prechecker.expect_update_fees().returning(|| { - Ok(( - GasFees { - max_fee_per_gas: 0.into(), - max_priority_fee_per_gas: 0.into(), - }, - 0.into(), - )) - }); - paymaster_helper.expect_get_deposit_info().returning(|_| { Ok(DepositInfo { deposit: 1000, @@ -1375,6 +1308,31 @@ mod tests { entrypoint .expect_balance_of() .returning(|_, _| Ok(U256::from(1000))); + let paymaster = PaymasterTracker::new( + paymaster_helper, + entrypoint, + PaymasterConfig::new( + args.sim_settings.min_stake_value, + args.sim_settings.min_unstake_delay, + args.paymaster_tracking_enabled, + ), + ); + + let reputation = Arc::new(AddressReputation::new( + ReputationParams::test_parameters(BAN_SLACK, THROTTLE_SLACK), + args.blocklist.clone().unwrap_or_default(), + args.allowlist.clone().unwrap_or_default(), + )); + + prechecker.expect_update_fees().returning(|| { + Ok(( + GasFees { + max_fee_per_gas: 0.into(), + max_priority_fee_per_gas: 0.into(), + }, + 0.into(), + )) + }); for op in ops { prechecker.expect_check().returning(move |_| { @@ -1410,46 +1368,22 @@ mod tests { }); } - let args = PoolConfig { - entry_point: Address::random(), - chain_id: 1, - min_replacement_fee_increase_percentage: 10, - max_size_of_pool_bytes: 10000, - blocklist: None, - allowlist: None, - precheck_settings: PrecheckSettings::default(), - sim_settings: SimulationSettings::default(), - mempool_channel_configs: HashMap::new(), - num_shards: 1, - same_sender_mempool_count: 4, - throttled_entity_mempool_count: 4, - throttled_entity_live_blocks: 10, - paymaster_tracking_enabled: true, - reputation_tracking_enabled: true, - }; let (event_sender, _) = broadcast::channel(4); UoPool::new( args, - reputation, event_sender, prechecker, simulator, - entrypoint, - paymaster_helper, + paymaster, + reputation, ) } async fn create_pool_insert_ops( ops: Vec, ) -> ( - UoPool< - impl ReputationManager, - impl Prechecker, - impl Simulator, - impl EntryPoint, - impl PaymasterHelper, - >, + UoPool, Vec, ) { let uos = ops.iter().map(|op| op.op.clone()).collect::>(); @@ -1515,116 +1449,4 @@ mod tests { assert_eq!(actual.uo, expected); } } - - #[derive(Default, Clone)] - struct MockReputationManager { - bundle_invalidation_ops_seen_staked_penalty: u64, - bundle_invalidation_ops_seen_unstaked_penalty: u64, - same_unstaked_entity_mempool_count: u64, - inclusion_rate_factor: u64, - throttling_slack: u64, - ban_slack: u64, - counts: Arc>, - } - - #[derive(Default)] - struct Counts { - seen: HashMap, - included: HashMap, - tracking_enabled: bool, - } - - impl MockReputationManager { - fn new(throttling_slack: u64, ban_slack: u64) -> Self { - Self { - throttling_slack, - ban_slack, - ..Self::default() - } - } - } - - impl ReputationManager for MockReputationManager { - fn status(&self, address: Address) -> ReputationStatus { - let counts = self.counts.read(); - - let seen = *counts.seen.get(&address).unwrap_or(&0); - let included = *counts.included.get(&address).unwrap_or(&0); - let diff = seen.saturating_sub(included); - if diff > self.ban_slack { - ReputationStatus::Banned - } else if diff > self.throttling_slack { - ReputationStatus::Throttled - } else { - ReputationStatus::Ok - } - } - - fn add_seen(&self, address: Address) { - *self.counts.write().seen.entry(address).or_default() += 1; - } - - fn handle_srep_050_penalty(&self, address: Address) { - *self.counts.write().seen.entry(address).or_default() = - self.bundle_invalidation_ops_seen_staked_penalty; - } - - fn handle_urep_030_penalty(&self, address: Address) { - *self.counts.write().seen.entry(address).or_default() += - self.bundle_invalidation_ops_seen_unstaked_penalty; - } - - fn add_included(&self, address: Address) { - *self.counts.write().included.entry(address).or_default() += 1; - } - - fn remove_included(&self, address: Address) { - let mut counts = self.counts.write(); - let included = counts.included.entry(address).or_default(); - *included = included.saturating_sub(1); - } - - fn dump_reputation(&self) -> Vec { - self.counts - .read() - .seen - .iter() - .map(|(address, ops_seen)| Reputation { - address: *address, - ops_seen: *ops_seen, - ops_included: *self.counts.read().included.get(address).unwrap_or(&0), - }) - .collect() - } - - fn set_reputation(&self, address: Address, ops_seen: u64, ops_included: u64) { - let mut counts = self.counts.write(); - counts.seen.insert(address, ops_seen); - counts.included.insert(address, ops_included); - } - - fn get_ops_allowed(&self, address: Address) -> u64 { - let counts = self.counts.read(); - let seen = *counts.seen.get(&address).unwrap_or(&0); - let included = *counts.included.get(&address).unwrap_or(&0); - let inclusion_based_count = if seen == 0 { - // make sure we aren't dividing by 0 - 0 - } else { - included * self.inclusion_rate_factor / seen + std::cmp::min(included, 10_000) - }; - - // return ops allowed, as defined by UREP-020 - self.same_unstaked_entity_mempool_count + inclusion_based_count - } - - fn clear(&self) { - self.counts.write().seen.clear(); - self.counts.write().included.clear(); - } - - fn set_tracking(&self, tracking_enabled: bool) { - self.counts.write().tracking_enabled = tracking_enabled; - } - } } diff --git a/crates/pool/src/task.rs b/crates/pool/src/task.rs index f66ba210a..5231d09f3 100644 --- a/crates/pool/src/task.rs +++ b/crates/pool/src/task.rs @@ -31,11 +31,11 @@ use rundler_utils::{emit::WithEntryPoint, eth, handle}; use tokio::{sync::broadcast, try_join}; use tokio_util::sync::CancellationToken; -use super::mempool::{HourlyMovingAverageReputation, PoolConfig, ReputationParams}; +use super::mempool::PoolConfig; use crate::{ chain::{self, Chain}, emit::OpPoolEvent, - mempool::UoPool, + mempool::{AddressReputation, PaymasterConfig, PaymasterTracker, ReputationParams, UoPool}, server::{spawn_remote_mempool_server, LocalPoolBuilder}, }; @@ -165,24 +165,8 @@ impl PoolTask { event_sender: broadcast::Sender>, provider: Arc

, ) -> anyhow::Result< - UoPool< - HourlyMovingAverageReputation, - impl Prechecker, - impl Simulator, - impl EntryPoint, - impl PaymasterHelper, - >, + UoPool, > { - // Reputation manager - let reputation = Arc::new(HourlyMovingAverageReputation::new( - ReputationParams::new(pool_config.reputation_tracking_enabled), - pool_config.blocklist.clone(), - pool_config.allowlist.clone(), - )); - // Start reputation manager - let reputation_runner = Arc::clone(&reputation); - tokio::spawn(async move { reputation_runner.run().await }); - let i_entry_point = IEntryPoint::new(pool_config.entry_point, Arc::clone(&provider)); let paymaster_helper = PaymasterHelperContract::new(pool_config.entry_point, Arc::clone(&provider)); @@ -203,14 +187,35 @@ impl PoolTask { pool_config.mempool_channel_configs.clone(), ); - Ok(UoPool::new( + let reputation = Arc::new(AddressReputation::new( + ReputationParams::new(pool_config.reputation_tracking_enabled), + pool_config.blocklist.clone().unwrap_or_default(), + pool_config.allowlist.clone().unwrap_or_default(), + )); + + // Start reputation manager + let reputation_runner = Arc::clone(&reputation); + tokio::spawn(async move { reputation_runner.run().await }); + + let paymaster = PaymasterTracker::new( + paymaster_helper, + i_entry_point, + PaymasterConfig::new( + pool_config.sim_settings.min_stake_value, + pool_config.sim_settings.min_unstake_delay, + pool_config.paymaster_tracking_enabled, + ), + ); + + let uo_pool = UoPool::new( pool_config.clone(), - Arc::clone(&reputation), event_sender, prechecker, simulator, - i_entry_point, - paymaster_helper, - )) + paymaster, + reputation, + ); + + Ok(uo_pool) } }