From 397360bf04f81bdbc63af114e3017b9525e1d4b7 Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Sun, 22 Jan 2023 20:35:02 +0100 Subject: [PATCH 01/20] feat: add reputation primitives --- Makefile | 2 +- README.md | 2 +- src/uopool/mod.rs | 32 +++++++++++++++++---- src/uopool/reputation.rs | 52 +++++++++++++++++++++++++++++++++++ src/uopool/services/uopool.rs | 6 ++-- 5 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 src/uopool/reputation.rs diff --git a/Makefile b/Makefile index 789472f8..67061d47 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ run-bundler: cargo run -- --mnemonic-file ${HOME}/.aa-bundler/0x129D197b2a989C6798601A49D89a4AEC822A17a3 --beneficiary 0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990 --gas-factor 600 --min-balance 1 --entry-points 0x0000000000000000000000000000000000000000 --chain-id 5 --helper 0x0000000000000000000000000000000000000000 run-bundler-uopool: - cargo run --bin bundler-uopool -- --entry-points 0x0000000000000000000000000000000000000000 --chain-id 5 + cargo run --bin bundler-uopool -- --entry-points 0x0000000000000000000000000000000000000000 --chain-id 5 --min-stake 1 --min-unstake-delay 0 run-bundler-rpc: cargo run --bin bundler-rpc diff --git a/README.md b/README.md index 90505b04..6abeb609 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ cargo run -- --mnemonic-file ${HOME}/.aa-bundler/0x129D197b2a989C6798601A49D89a4 Run only user operation pool: ```bash -cargo run --bin bundler-uopool -- --entry-points 0x0000000000000000000000000000000000000000 --chain-id 5 +cargo run --bin bundler-uopool -- --entry-points 0x0000000000000000000000000000000000000000 --chain-id 5 --min-stake 1 --min-unstake-delay 0 ``` Run only JSON-RPC API: diff --git a/src/uopool/mod.rs b/src/uopool/mod.rs index d7000b98..b1607924 100644 --- a/src/uopool/mod.rs +++ b/src/uopool/mod.rs @@ -1,9 +1,10 @@ use crate::{ types::user_operation::{UserOperation, UserOperationHash}, uopool::{ - memory::MemoryMempool, server::uopool::uo_pool_server::UoPoolServer, - services::UoPoolService, + memory::MemoryMempool, reputation::Reputation, + server::uopool::uo_pool_server::UoPoolServer, services::UoPoolService, }, + utils::parse_u256, }; use anyhow::Result; use async_trait::async_trait; @@ -19,11 +20,16 @@ use parking_lot::RwLock; use std::{collections::HashMap, fmt::Debug, net::SocketAddr, sync::Arc, time::Duration}; pub mod memory; +pub mod reputation; pub mod server; pub mod services; pub type MempoolId = H256; +const MIN_INCLUSION_RATE_DENOMINATOR: u64 = 10; +const THROTTLING_SLACK: u64 = 10; +const BAN_SLACK: u64 = 50; + pub fn mempool_id(entry_point: Address, chain_id: U256) -> MempoolId { H256::from_slice(keccak256([entry_point.encode(), chain_id.encode()].concat()).as_slice()) } @@ -53,11 +59,16 @@ pub struct UserOperationPool { pub pool: Arc, } -#[derive(Clone, Copy, Educe, Parser)] -#[educe(Debug)] +#[derive(Debug, Parser, PartialEq)] pub struct UoPoolOpts { #[clap(long, default_value = "127.0.0.1:3001")] pub uopool_grpc_listen_address: SocketAddr, + + #[clap(long, value_parser=parse_u256, default_value = "1")] + pub min_stake: U256, + + #[clap(long, default_value = "0")] + pub min_unstake_delay: u64, } pub async fn run(opts: UoPoolOpts, entry_points: Vec
, chain_id: U256) -> Result<()> { @@ -70,7 +81,18 @@ pub async fn run(opts: UoPoolOpts, entry_points: Vec
, chain_id: U256) - mempools.insert(id, Box::::default()); } - let svc = UoPoolServer::new(UoPoolService::new(Arc::new(RwLock::new(mempools)))); + let reputation = Reputation::new( + MIN_INCLUSION_RATE_DENOMINATOR, + THROTTLING_SLACK, + BAN_SLACK, + opts.min_stake, + opts.min_unstake_delay, + ); + + let svc = UoPoolServer::new(UoPoolService::new( + Arc::new(RwLock::new(mempools)), + Arc::new(RwLock::new(reputation)), + )); info!( "UoPool gRPC server starting on {}", diff --git a/src/uopool/reputation.rs b/src/uopool/reputation.rs new file mode 100644 index 00000000..8e2c742e --- /dev/null +++ b/src/uopool/reputation.rs @@ -0,0 +1,52 @@ +use ethers::types::{Address, U256}; +use parking_lot::RwLock; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; + +pub enum ReputationStatus { + OK, + THROTTLED, + BANNED, +} + +pub struct ReputationEntry { + address: Address, + uo_seen: u64, + uo_included: u64, + status: ReputationStatus, +} + +pub struct Reputation { + min_inclusion_denominator: u64, + throttling_slack: u64, + ban_slack: u64, + min_stake: U256, + min_unstake_delay: u64, + + entites: Arc>>, + whitelist: Arc>>, + blacklist: Arc>>, +} + +impl Reputation { + pub fn new( + min_inclusion_denominator: u64, + throttling_slack: u64, + ban_slack: u64, + min_stake: U256, + min_unstake_delay: u64, + ) -> Self { + Self { + min_inclusion_denominator, + throttling_slack, + ban_slack, + min_stake, + min_unstake_delay, + entites: Arc::new(RwLock::new(HashMap::new())), + whitelist: Arc::new(RwLock::new(HashSet::new())), + blacklist: Arc::new(RwLock::new(HashSet::new())), + } + } +} diff --git a/src/uopool/services/uopool.rs b/src/uopool/services/uopool.rs index d4c77679..a077894c 100644 --- a/src/uopool/services/uopool.rs +++ b/src/uopool/services/uopool.rs @@ -5,7 +5,7 @@ use crate::{ uo_pool_server::UoPool, AddRequest, AddResponse, AddResult, AllRequest, AllResponse, RemoveRequest, RemoveResponse, }, - MempoolBox, MempoolId, + MempoolBox, MempoolId, reputation::Reputation, }, }; use async_trait::async_trait; @@ -19,12 +19,14 @@ pub type UoPoolError = ErrorObject<'static>; pub struct UoPoolService { _mempools: Arc>>>>, + _reputation: Arc>, } impl UoPoolService { - pub fn new(mempools: Arc>>>>) -> Self { + pub fn new(mempools: Arc>>>>, reputation: Arc>) -> Self { Self { _mempools: mempools, + _reputation: reputation, } } } From acc86bc17124aef43c3a19fde789178ce45cc530 Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Sun, 22 Jan 2023 20:48:21 +0100 Subject: [PATCH 02/20] chore: make reputation generic --- src/uopool/{memory.rs => memory_mempool.rs} | 0 .../{reputation.rs => memory_reputation.rs} | 39 +++++++++++++++++-- src/uopool/mod.rs | 19 +++++++-- src/uopool/services/uopool.rs | 6 +-- 4 files changed, 54 insertions(+), 10 deletions(-) rename src/uopool/{memory.rs => memory_mempool.rs} (100%) rename src/uopool/{reputation.rs => memory_reputation.rs} (68%) diff --git a/src/uopool/memory.rs b/src/uopool/memory_mempool.rs similarity index 100% rename from src/uopool/memory.rs rename to src/uopool/memory_mempool.rs diff --git a/src/uopool/reputation.rs b/src/uopool/memory_reputation.rs similarity index 68% rename from src/uopool/reputation.rs rename to src/uopool/memory_reputation.rs index 8e2c742e..bbe165a5 100644 --- a/src/uopool/reputation.rs +++ b/src/uopool/memory_reputation.rs @@ -1,3 +1,5 @@ +use async_trait::async_trait; +use educe::Educe; use ethers::types::{Address, U256}; use parking_lot::RwLock; use std::{ @@ -5,12 +7,18 @@ use std::{ sync::Arc, }; +use super::Reputation; + +#[derive(Educe)] +#[educe(Debug)] pub enum ReputationStatus { OK, THROTTLED, BANNED, } +#[derive(Educe)] +#[educe(Debug)] pub struct ReputationEntry { address: Address, uo_seen: u64, @@ -18,7 +26,9 @@ pub struct ReputationEntry { status: ReputationStatus, } -pub struct Reputation { +#[derive(Default, Educe)] +#[educe(Debug)] +pub struct MemoryReputation { min_inclusion_denominator: u64, throttling_slack: u64, ban_slack: u64, @@ -30,8 +40,9 @@ pub struct Reputation { blacklist: Arc>>, } -impl Reputation { - pub fn new( +#[async_trait] +impl Reputation for MemoryReputation { + fn new( min_inclusion_denominator: u64, throttling_slack: u64, ban_slack: u64, @@ -49,4 +60,26 @@ impl Reputation { blacklist: Arc::new(RwLock::new(HashSet::new())), } } + + // hourly cron + + // add whitelist + + // add blacklist + + // init entity + + // increase seen + + // increase included + + // get status + + // check stake + + // crash handle ops + + // debug: set reputation + + // debug: clear reputation } diff --git a/src/uopool/mod.rs b/src/uopool/mod.rs index b1607924..77f8a497 100644 --- a/src/uopool/mod.rs +++ b/src/uopool/mod.rs @@ -1,7 +1,7 @@ use crate::{ types::user_operation::{UserOperation, UserOperationHash}, uopool::{ - memory::MemoryMempool, reputation::Reputation, + memory_mempool::MemoryMempool, memory_reputation::MemoryReputation, server::uopool::uo_pool_server::UoPoolServer, services::UoPoolService, }, utils::parse_u256, @@ -19,8 +19,8 @@ use jsonrpsee::tracing::info; use parking_lot::RwLock; use std::{collections::HashMap, fmt::Debug, net::SocketAddr, sync::Arc, time::Duration}; -pub mod memory; -pub mod reputation; +pub mod memory_mempool; +pub mod memory_reputation; pub mod server; pub mod services; @@ -53,6 +53,17 @@ pub trait Mempool: Debug + Send + Sync + 'static { async fn clear(&mut self) -> anyhow::Result<()>; } +#[async_trait] +pub trait Reputation: Debug + Send + Sync + 'static { + fn new( + min_inclusion_denominator: u64, + throttling_slack: u64, + ban_slack: u64, + min_stake: U256, + min_unstake_delay: u64, + ) -> Self; +} + #[derive(Educe)] #[educe(Debug)] pub struct UserOperationPool { @@ -81,7 +92,7 @@ pub async fn run(opts: UoPoolOpts, entry_points: Vec
, chain_id: U256) - mempools.insert(id, Box::::default()); } - let reputation = Reputation::new( + let reputation = MemoryReputation::new( MIN_INCLUSION_RATE_DENOMINATOR, THROTTLING_SLACK, BAN_SLACK, diff --git a/src/uopool/services/uopool.rs b/src/uopool/services/uopool.rs index a077894c..c39bf6cd 100644 --- a/src/uopool/services/uopool.rs +++ b/src/uopool/services/uopool.rs @@ -5,7 +5,7 @@ use crate::{ uo_pool_server::UoPool, AddRequest, AddResponse, AddResult, AllRequest, AllResponse, RemoveRequest, RemoveResponse, }, - MempoolBox, MempoolId, reputation::Reputation, + MempoolBox, MempoolId, memory_reputation::MemoryReputation, }, }; use async_trait::async_trait; @@ -19,11 +19,11 @@ pub type UoPoolError = ErrorObject<'static>; pub struct UoPoolService { _mempools: Arc>>>>, - _reputation: Arc>, + _reputation: Arc>, } impl UoPoolService { - pub fn new(mempools: Arc>>>>, reputation: Arc>) -> Self { + pub fn new(mempools: Arc>>>>, reputation: Arc>) -> Self { Self { _mempools: mempools, _reputation: reputation, From ace71193a7e8e4b18baabb0d402c72563b1922fb Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Sun, 22 Jan 2023 23:23:29 +0100 Subject: [PATCH 03/20] feat: add update hourly function to reputation --- src/uopool/memory_reputation.rs | 10 +++++++++- src/uopool/mod.rs | 21 +++++++++++---------- src/uopool/services/uopool.rs | 8 ++++++-- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/uopool/memory_reputation.rs b/src/uopool/memory_reputation.rs index bbe165a5..f084b4b6 100644 --- a/src/uopool/memory_reputation.rs +++ b/src/uopool/memory_reputation.rs @@ -61,7 +61,15 @@ impl Reputation for MemoryReputation { } } - // hourly cron + async fn update_hourly(&mut self) -> anyhow::Result<()> { + let mut entities = self.entites.write(); + for (_, entity) in entities.iter_mut() { + entity.uo_seen = entity.uo_seen * 23 / 24; + entity.uo_included = entity.uo_included * 23 / 24; + } + entities.retain(|_, entity| entity.uo_seen > 0 || entity.uo_included > 0); + Ok(()) + } // add whitelist diff --git a/src/uopool/mod.rs b/src/uopool/mod.rs index 77f8a497..0720361c 100644 --- a/src/uopool/mod.rs +++ b/src/uopool/mod.rs @@ -62,6 +62,7 @@ pub trait Reputation: Debug + Send + Sync + 'static { min_stake: U256, min_unstake_delay: u64, ) -> Self; + async fn update_hourly(&mut self) -> anyhow::Result<()>; } #[derive(Educe)] @@ -70,7 +71,7 @@ pub struct UserOperationPool { pub pool: Arc, } -#[derive(Debug, Parser, PartialEq)] +#[derive(Clone, Copy, Debug, Parser, PartialEq)] pub struct UoPoolOpts { #[clap(long, default_value = "127.0.0.1:3001")] pub uopool_grpc_listen_address: SocketAddr, @@ -92,19 +93,19 @@ pub async fn run(opts: UoPoolOpts, entry_points: Vec
, chain_id: U256) - mempools.insert(id, Box::::default()); } - let reputation = MemoryReputation::new( - MIN_INCLUSION_RATE_DENOMINATOR, - THROTTLING_SLACK, - BAN_SLACK, - opts.min_stake, - opts.min_unstake_delay, - ); - let svc = UoPoolServer::new(UoPoolService::new( Arc::new(RwLock::new(mempools)), - Arc::new(RwLock::new(reputation)), + Arc::new(RwLock::new(MemoryReputation::new( + MIN_INCLUSION_RATE_DENOMINATOR, + THROTTLING_SLACK, + BAN_SLACK, + opts.min_stake, + opts.min_unstake_delay, + ))), )); + // TODO: start updating reputation hourly + info!( "UoPool gRPC server starting on {}", opts.uopool_grpc_listen_address diff --git a/src/uopool/services/uopool.rs b/src/uopool/services/uopool.rs index c39bf6cd..ca983580 100644 --- a/src/uopool/services/uopool.rs +++ b/src/uopool/services/uopool.rs @@ -1,11 +1,12 @@ use crate::{ types::user_operation::UserOperation, uopool::{ + memory_reputation::MemoryReputation, server::uopool::{ uo_pool_server::UoPool, AddRequest, AddResponse, AddResult, AllRequest, AllResponse, RemoveRequest, RemoveResponse, }, - MempoolBox, MempoolId, memory_reputation::MemoryReputation, + MempoolBox, MempoolId, }, }; use async_trait::async_trait; @@ -23,7 +24,10 @@ pub struct UoPoolService { } impl UoPoolService { - pub fn new(mempools: Arc>>>>, reputation: Arc>) -> Self { + pub fn new( + mempools: Arc>>>>, + reputation: Arc>, + ) -> Self { Self { _mempools: mempools, _reputation: reputation, From 21255acc7c9d39ec6d083755364bbb37b69f1868 Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Wed, 25 Jan 2023 23:01:32 +0100 Subject: [PATCH 04/20] feat: implement several methods related to reputation --- src/uopool/memory_reputation.rs | 77 ++++++++++++++++++++++++++++----- src/uopool/mod.rs | 13 ++++++ 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/src/uopool/memory_reputation.rs b/src/uopool/memory_reputation.rs index f084b4b6..3cf28a94 100644 --- a/src/uopool/memory_reputation.rs +++ b/src/uopool/memory_reputation.rs @@ -9,7 +9,7 @@ use std::{ use super::Reputation; -#[derive(Educe)] +#[derive(Clone, Copy, Educe)] #[educe(Debug)] pub enum ReputationStatus { OK, @@ -17,7 +17,7 @@ pub enum ReputationStatus { BANNED, } -#[derive(Educe)] +#[derive(Clone, Copy, Educe)] #[educe(Debug)] pub struct ReputationEntry { address: Address, @@ -35,7 +35,7 @@ pub struct MemoryReputation { min_stake: U256, min_unstake_delay: u64, - entites: Arc>>, + entities: Arc>>, whitelist: Arc>>, blacklist: Arc>>, } @@ -55,14 +55,55 @@ impl Reputation for MemoryReputation { ban_slack, min_stake, min_unstake_delay, - entites: Arc::new(RwLock::new(HashMap::new())), + entities: Arc::new(RwLock::new(HashMap::new())), whitelist: Arc::new(RwLock::new(HashSet::new())), blacklist: Arc::new(RwLock::new(HashSet::new())), } } + async fn get(&mut self, address: Address) -> anyhow::Result { + let mut entities = self.entities.write(); + + if let Some(entity) = entities.get(&address) { + return Ok(*entity); + } + + let entity = ReputationEntry { + address, + uo_seen: 0, + uo_included: 0, + status: ReputationStatus::OK, + }; + + entities.insert(address, entity); + + Ok(entity) + } + + async fn increment_seen(&mut self, address: Address) -> anyhow::Result<()> { + let mut entities = self.entities.write(); + + if let Some(entity) = entities.get_mut(&address) { + entity.uo_seen += 1; + return Ok(()); + } + + Err(anyhow::anyhow!("Entity not found")) + } + + async fn increment_included(&mut self, address: Address) -> anyhow::Result<()> { + let mut entities = self.entities.write(); + + if let Some(entity) = entities.get_mut(&address) { + entity.uo_included += 1; + return Ok(()); + } + + Err(anyhow::anyhow!("Entity not found")) + } + async fn update_hourly(&mut self) -> anyhow::Result<()> { - let mut entities = self.entites.write(); + let mut entities = self.entities.write(); for (_, entity) in entities.iter_mut() { entity.uo_seen = entity.uo_seen * 23 / 24; entity.uo_included = entity.uo_included * 23 / 24; @@ -71,15 +112,31 @@ impl Reputation for MemoryReputation { Ok(()) } - // add whitelist + async fn add_whitelist(&mut self, address: Address) -> anyhow::Result<()> { + self.whitelist.write().insert(address); + Ok(()) + } - // add blacklist + async fn remove_whitelist(&mut self, address: Address) -> anyhow::Result { + Ok(self.whitelist.write().remove(&address)) + } - // init entity + async fn is_whitelist(&self, address: Address) -> anyhow::Result { + Ok(self.whitelist.read().contains(&address)) + } + + async fn add_blacklist(&mut self, address: Address) -> anyhow::Result<()> { + self.blacklist.write().insert(address); + Ok(()) + } - // increase seen + async fn remove_blacklist(&mut self, address: Address) -> anyhow::Result { + Ok(self.blacklist.write().remove(&address)) + } - // increase included + async fn is_blacklist(&self, address: Address) -> anyhow::Result { + Ok(self.blacklist.read().contains(&address)) + } // get status diff --git a/src/uopool/mod.rs b/src/uopool/mod.rs index 0720361c..1f404fd9 100644 --- a/src/uopool/mod.rs +++ b/src/uopool/mod.rs @@ -19,6 +19,8 @@ use jsonrpsee::tracing::info; use parking_lot::RwLock; use std::{collections::HashMap, fmt::Debug, net::SocketAddr, sync::Arc, time::Duration}; +use self::memory_reputation::ReputationEntry; + pub mod memory_mempool; pub mod memory_reputation; pub mod server; @@ -62,7 +64,18 @@ pub trait Reputation: Debug + Send + Sync + 'static { min_stake: U256, min_unstake_delay: u64, ) -> Self; + async fn get(&mut self, address: Address) -> anyhow::Result; async fn update_hourly(&mut self) -> anyhow::Result<()>; + async fn increment_seen(&mut self, address: Address) -> anyhow::Result<()>; + async fn increment_included(&mut self, address: Address) -> anyhow::Result<()>; + async fn add_whitelist(&mut self, address: Address) -> anyhow::Result<()>; + async fn remove_whitelist(&mut self, address: Address) -> anyhow::Result; + async fn is_whitelist(&self, address: Address) -> anyhow::Result; + async fn add_blacklist(&mut self, address: Address) -> anyhow::Result<()>; + async fn remove_blacklist(&mut self, address: Address) -> anyhow::Result; + async fn is_blacklist(&self, address: Address) -> anyhow::Result; + + // #[cfg(test)] } #[derive(Educe)] From e6552e349defb6ac677699545f55cfd6b91df38e Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Thu, 26 Jan 2023 23:52:55 +0100 Subject: [PATCH 05/20] feat: add get status method for reputation --- src/uopool/memory_reputation.rs | 26 +++++++++++++++++++++++++- src/uopool/mod.rs | 3 ++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/uopool/memory_reputation.rs b/src/uopool/memory_reputation.rs index 3cf28a94..d28a89ea 100644 --- a/src/uopool/memory_reputation.rs +++ b/src/uopool/memory_reputation.rs @@ -138,7 +138,31 @@ impl Reputation for MemoryReputation { Ok(self.blacklist.read().contains(&address)) } - // get status + async fn get_status(&self, address: Address) -> anyhow::Result { + if self.is_whitelist(address).await? { + return Ok(ReputationStatus::OK); + } + + if self.is_blacklist(address).await? { + return Ok(ReputationStatus::BANNED); + } + + let entities = self.entities.read(); + + match entities.get(&address) { + Some(entity) => { + let min_expected_included = entity.uo_seen / self.min_inclusion_denominator; + if min_expected_included <= entity.uo_included + self.throttling_slack { + Ok(ReputationStatus::OK) + } else if min_expected_included <= entity.uo_included + self.ban_slack { + Ok(ReputationStatus::THROTTLED) + } else { + Ok(ReputationStatus::BANNED) + } + } + _ => Ok(ReputationStatus::OK), + } + } // check stake diff --git a/src/uopool/mod.rs b/src/uopool/mod.rs index 1f404fd9..0962ba9a 100644 --- a/src/uopool/mod.rs +++ b/src/uopool/mod.rs @@ -19,7 +19,7 @@ use jsonrpsee::tracing::info; use parking_lot::RwLock; use std::{collections::HashMap, fmt::Debug, net::SocketAddr, sync::Arc, time::Duration}; -use self::memory_reputation::ReputationEntry; +use self::memory_reputation::{ReputationEntry, ReputationStatus}; pub mod memory_mempool; pub mod memory_reputation; @@ -74,6 +74,7 @@ pub trait Reputation: Debug + Send + Sync + 'static { async fn add_blacklist(&mut self, address: Address) -> anyhow::Result<()>; async fn remove_blacklist(&mut self, address: Address) -> anyhow::Result; async fn is_blacklist(&self, address: Address) -> anyhow::Result; + async fn get_status(&self, address: Address) -> anyhow::Result; // #[cfg(test)] } From 54dc188633d8fb588fac0e91dadc4ddb40eabaad Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Fri, 27 Jan 2023 07:58:57 +0100 Subject: [PATCH 06/20] chore: fix lint --- src/uopool/memory_reputation.rs | 2 ++ src/uopool/services/uopool.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/uopool/memory_reputation.rs b/src/uopool/memory_reputation.rs index d28a89ea..5b9eb9f3 100644 --- a/src/uopool/memory_reputation.rs +++ b/src/uopool/memory_reputation.rs @@ -172,3 +172,5 @@ impl Reputation for MemoryReputation { // debug: clear reputation } + +// tests diff --git a/src/uopool/services/uopool.rs b/src/uopool/services/uopool.rs index ca983580..ef418d10 100644 --- a/src/uopool/services/uopool.rs +++ b/src/uopool/services/uopool.rs @@ -55,6 +55,8 @@ impl UoPool for UoPoolService { // TODO: sanity checks // TODO: simulation + // TODO: make something with reputation + let uo_pool_error = UoPoolError::owned( -32602, "user operation was not added", From 2085c2ace0297f1f543aa5b357a453e23dce7057 Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Fri, 27 Jan 2023 09:27:39 +0100 Subject: [PATCH 07/20] feat: add verify stake method for reputation --- src/uopool/memory_reputation.rs | 133 +++++++++++++++++++++++++------- src/uopool/mod.rs | 39 ++++++---- 2 files changed, 128 insertions(+), 44 deletions(-) diff --git a/src/uopool/memory_reputation.rs b/src/uopool/memory_reputation.rs index 5b9eb9f3..1f0ae312 100644 --- a/src/uopool/memory_reputation.rs +++ b/src/uopool/memory_reputation.rs @@ -1,15 +1,19 @@ use async_trait::async_trait; use educe::Educe; -use ethers::types::{Address, U256}; +use ethers::{ + abi::AbiEncode, + types::{Address, U256}, +}; use parking_lot::RwLock; +use serde_json::json; use std::{ collections::{HashMap, HashSet}, sync::Arc, }; -use super::Reputation; +use super::{Reputation, ReputationError, ENTITY_BANNED_ERROR_CODE, STAKE_TOO_LOW_ERROR_CODE}; -#[derive(Clone, Copy, Educe)] +#[derive(Clone, Copy, Educe, PartialEq, Eq)] #[educe(Debug)] pub enum ReputationStatus { OK, @@ -26,6 +30,14 @@ pub struct ReputationEntry { status: ReputationStatus, } +#[derive(Clone, Copy, Educe)] +#[educe(Debug)] +pub struct StakeInfo { + address: Address, + stake: U256, + unstake_delay: U256, // seconds +} + #[derive(Default, Educe)] #[educe(Debug)] pub struct MemoryReputation { @@ -33,7 +45,7 @@ pub struct MemoryReputation { throttling_slack: u64, ban_slack: u64, min_stake: U256, - min_unstake_delay: u64, + min_unstake_delay: U256, entities: Arc>>, whitelist: Arc>>, @@ -47,7 +59,7 @@ impl Reputation for MemoryReputation { throttling_slack: u64, ban_slack: u64, min_stake: U256, - min_unstake_delay: u64, + min_unstake_delay: U256, ) -> Self { Self { min_inclusion_denominator, @@ -61,29 +73,29 @@ impl Reputation for MemoryReputation { } } - async fn get(&mut self, address: Address) -> anyhow::Result { + async fn get(&mut self, address: &Address) -> anyhow::Result { let mut entities = self.entities.write(); - if let Some(entity) = entities.get(&address) { + if let Some(entity) = entities.get(address) { return Ok(*entity); } let entity = ReputationEntry { - address, + address: *address, uo_seen: 0, uo_included: 0, status: ReputationStatus::OK, }; - entities.insert(address, entity); + entities.insert(*address, entity); Ok(entity) } - async fn increment_seen(&mut self, address: Address) -> anyhow::Result<()> { + async fn increment_seen(&mut self, address: &Address) -> anyhow::Result<()> { let mut entities = self.entities.write(); - if let Some(entity) = entities.get_mut(&address) { + if let Some(entity) = entities.get_mut(address) { entity.uo_seen += 1; return Ok(()); } @@ -91,10 +103,10 @@ impl Reputation for MemoryReputation { Err(anyhow::anyhow!("Entity not found")) } - async fn increment_included(&mut self, address: Address) -> anyhow::Result<()> { + async fn increment_included(&mut self, address: &Address) -> anyhow::Result<()> { let mut entities = self.entities.write(); - if let Some(entity) = entities.get_mut(&address) { + if let Some(entity) = entities.get_mut(address) { entity.uo_included += 1; return Ok(()); } @@ -112,33 +124,33 @@ impl Reputation for MemoryReputation { Ok(()) } - async fn add_whitelist(&mut self, address: Address) -> anyhow::Result<()> { - self.whitelist.write().insert(address); + async fn add_whitelist(&mut self, address: &Address) -> anyhow::Result<()> { + self.whitelist.write().insert(*address); Ok(()) } - async fn remove_whitelist(&mut self, address: Address) -> anyhow::Result { - Ok(self.whitelist.write().remove(&address)) + async fn remove_whitelist(&mut self, address: &Address) -> anyhow::Result { + Ok(self.whitelist.write().remove(address)) } - async fn is_whitelist(&self, address: Address) -> anyhow::Result { - Ok(self.whitelist.read().contains(&address)) + async fn is_whitelist(&self, address: &Address) -> anyhow::Result { + Ok(self.whitelist.read().contains(address)) } - async fn add_blacklist(&mut self, address: Address) -> anyhow::Result<()> { - self.blacklist.write().insert(address); + async fn add_blacklist(&mut self, address: &Address) -> anyhow::Result<()> { + self.blacklist.write().insert(*address); Ok(()) } - async fn remove_blacklist(&mut self, address: Address) -> anyhow::Result { - Ok(self.blacklist.write().remove(&address)) + async fn remove_blacklist(&mut self, address: &Address) -> anyhow::Result { + Ok(self.blacklist.write().remove(address)) } - async fn is_blacklist(&self, address: Address) -> anyhow::Result { - Ok(self.blacklist.read().contains(&address)) + async fn is_blacklist(&self, address: &Address) -> anyhow::Result { + Ok(self.blacklist.read().contains(address)) } - async fn get_status(&self, address: Address) -> anyhow::Result { + async fn get_status(&self, address: &Address) -> anyhow::Result { if self.is_whitelist(address).await? { return Ok(ReputationStatus::OK); } @@ -149,7 +161,7 @@ impl Reputation for MemoryReputation { let entities = self.entities.read(); - match entities.get(&address) { + match entities.get(address) { Some(entity) => { let min_expected_included = entity.uo_seen / self.min_inclusion_denominator; if min_expected_included <= entity.uo_included + self.throttling_slack { @@ -164,9 +176,72 @@ impl Reputation for MemoryReputation { } } - // check stake + async fn update_handle_ops_reverted(&mut self, address: &Address) -> anyhow::Result<()> { + if let Ok(mut entity) = self.get(address).await { + entity.uo_seen = 100; + entity.uo_included = 0; + } + + Ok(()) + } + + async fn verify_stake( + &self, + title: &str, + stake_info: Option, + ) -> anyhow::Result<()> { + if let Some(stake_info) = stake_info { + if self.is_whitelist(&stake_info.address).await? { + return Ok(()); + } + + let entities = self.entities.read(); + + if let Some(entity) = entities.get(&stake_info.address) { + let error = if entity.status == ReputationStatus::BANNED { + ReputationError::owned( + ENTITY_BANNED_ERROR_CODE, + format!("{title} with address {} is banned", stake_info.address), + Some(json!({ + title: stake_info.address.to_string(), + })), + ) + } else if stake_info.stake < self.min_stake { + ReputationError::owned( + STAKE_TOO_LOW_ERROR_CODE, + format!( + "{title} with address {} stake {} is lower than {}", + stake_info.address, stake_info.stake, self.min_stake + ), + Some(json!({ + title: stake_info.address.to_string(), + "minimumStake": AbiEncode::encode_hex(self.min_stake), + "minimumUnstakeDelay": AbiEncode::encode_hex(self.min_unstake_delay), + })), + ) + } else if stake_info.unstake_delay < self.min_unstake_delay { + ReputationError::owned( + STAKE_TOO_LOW_ERROR_CODE, + format!( + "{title} with address {} unstake delay {} is lower than {}", + stake_info.address, stake_info.unstake_delay, self.min_unstake_delay + ), + Some(json!({ + title: stake_info.address.to_string(), + "minimumStake": AbiEncode::encode_hex(self.min_stake), + "minimumUnstakeDelay": AbiEncode::encode_hex(self.min_unstake_delay), + })), + ) + } else { + return Ok(()); + }; + + return Err(anyhow::anyhow!(serde_json::to_string(&error)?)); + } + } - // crash handle ops + Ok(()) + } // debug: set reputation diff --git a/src/uopool/mod.rs b/src/uopool/mod.rs index 0962ba9a..3cc05ccc 100644 --- a/src/uopool/mod.rs +++ b/src/uopool/mod.rs @@ -15,11 +15,11 @@ use ethers::{ types::{Address, H256, U256}, utils::keccak256, }; -use jsonrpsee::tracing::info; +use jsonrpsee::{tracing::info, types::ErrorObject}; use parking_lot::RwLock; use std::{collections::HashMap, fmt::Debug, net::SocketAddr, sync::Arc, time::Duration}; -use self::memory_reputation::{ReputationEntry, ReputationStatus}; +use self::memory_reputation::{ReputationEntry, ReputationStatus, StakeInfo}; pub mod memory_mempool; pub mod memory_reputation; @@ -27,10 +27,13 @@ pub mod server; pub mod services; pub type MempoolId = H256; +pub type ReputationError = ErrorObject<'static>; const MIN_INCLUSION_RATE_DENOMINATOR: u64 = 10; const THROTTLING_SLACK: u64 = 10; const BAN_SLACK: u64 = 50; +const ENTITY_BANNED_ERROR_CODE: i32 = -32504; +const STAKE_TOO_LOW_ERROR_CODE: i32 = -32505; pub fn mempool_id(entry_point: Address, chain_id: U256) -> MempoolId { H256::from_slice(keccak256([entry_point.encode(), chain_id.encode()].concat()).as_slice()) @@ -62,19 +65,25 @@ pub trait Reputation: Debug + Send + Sync + 'static { throttling_slack: u64, ban_slack: u64, min_stake: U256, - min_unstake_delay: u64, + min_unstake_delay: U256, ) -> Self; - async fn get(&mut self, address: Address) -> anyhow::Result; + async fn get(&mut self, address: &Address) -> anyhow::Result; async fn update_hourly(&mut self) -> anyhow::Result<()>; - async fn increment_seen(&mut self, address: Address) -> anyhow::Result<()>; - async fn increment_included(&mut self, address: Address) -> anyhow::Result<()>; - async fn add_whitelist(&mut self, address: Address) -> anyhow::Result<()>; - async fn remove_whitelist(&mut self, address: Address) -> anyhow::Result; - async fn is_whitelist(&self, address: Address) -> anyhow::Result; - async fn add_blacklist(&mut self, address: Address) -> anyhow::Result<()>; - async fn remove_blacklist(&mut self, address: Address) -> anyhow::Result; - async fn is_blacklist(&self, address: Address) -> anyhow::Result; - async fn get_status(&self, address: Address) -> anyhow::Result; + async fn increment_seen(&mut self, address: &Address) -> anyhow::Result<()>; + async fn increment_included(&mut self, address: &Address) -> anyhow::Result<()>; + async fn add_whitelist(&mut self, address: &Address) -> anyhow::Result<()>; + async fn remove_whitelist(&mut self, address: &Address) -> anyhow::Result; + async fn is_whitelist(&self, address: &Address) -> anyhow::Result; + async fn add_blacklist(&mut self, address: &Address) -> anyhow::Result<()>; + async fn remove_blacklist(&mut self, address: &Address) -> anyhow::Result; + async fn is_blacklist(&self, address: &Address) -> anyhow::Result; + async fn get_status(&self, address: &Address) -> anyhow::Result; + async fn update_handle_ops_reverted(&mut self, address: &Address) -> anyhow::Result<()>; + async fn verify_stake( + &self, + title: &str, + stake_info: Option, + ) -> anyhow::Result<()>; // #[cfg(test)] } @@ -93,8 +102,8 @@ pub struct UoPoolOpts { #[clap(long, value_parser=parse_u256, default_value = "1")] pub min_stake: U256, - #[clap(long, default_value = "0")] - pub min_unstake_delay: u64, + #[clap(long, value_parser=parse_u256, default_value = "0")] + pub min_unstake_delay: U256, } pub async fn run(opts: UoPoolOpts, entry_points: Vec
, chain_id: U256) -> Result<()> { From d8e3297547412ad7ac8ac6679eeda401b613f793 Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Fri, 27 Jan 2023 09:44:40 +0100 Subject: [PATCH 08/20] chore: fix lint --- src/uopool/memory_reputation.rs | 6 +----- src/uopool/mod.rs | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/uopool/memory_reputation.rs b/src/uopool/memory_reputation.rs index 1f0ae312..72537131 100644 --- a/src/uopool/memory_reputation.rs +++ b/src/uopool/memory_reputation.rs @@ -185,11 +185,7 @@ impl Reputation for MemoryReputation { Ok(()) } - async fn verify_stake( - &self, - title: &str, - stake_info: Option, - ) -> anyhow::Result<()> { + async fn verify_stake(&self, title: &str, stake_info: Option) -> anyhow::Result<()> { if let Some(stake_info) = stake_info { if self.is_whitelist(&stake_info.address).await? { return Ok(()); diff --git a/src/uopool/mod.rs b/src/uopool/mod.rs index 3cc05ccc..f352e76c 100644 --- a/src/uopool/mod.rs +++ b/src/uopool/mod.rs @@ -79,11 +79,7 @@ pub trait Reputation: Debug + Send + Sync + 'static { async fn is_blacklist(&self, address: &Address) -> anyhow::Result; async fn get_status(&self, address: &Address) -> anyhow::Result; async fn update_handle_ops_reverted(&mut self, address: &Address) -> anyhow::Result<()>; - async fn verify_stake( - &self, - title: &str, - stake_info: Option, - ) -> anyhow::Result<()>; + async fn verify_stake(&self, title: &str, stake_info: Option) -> anyhow::Result<()>; // #[cfg(test)] } From a5ebd126fd10c3c7cd92420af1375f72d0edcda7 Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Sat, 28 Jan 2023 15:24:16 +0100 Subject: [PATCH 09/20] chore: call repeat hourly --- src/uopool/memory_reputation.rs | 3 +-- src/uopool/mod.rs | 25 ++++++++++++++++--------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/uopool/memory_reputation.rs b/src/uopool/memory_reputation.rs index 72537131..ec754ec7 100644 --- a/src/uopool/memory_reputation.rs +++ b/src/uopool/memory_reputation.rs @@ -114,14 +114,13 @@ impl Reputation for MemoryReputation { Err(anyhow::anyhow!("Entity not found")) } - async fn update_hourly(&mut self) -> anyhow::Result<()> { + fn update_hourly(&mut self) { let mut entities = self.entities.write(); for (_, entity) in entities.iter_mut() { entity.uo_seen = entity.uo_seen * 23 / 24; entity.uo_included = entity.uo_included * 23 / 24; } entities.retain(|_, entity| entity.uo_seen > 0 || entity.uo_included > 0); - Ok(()) } async fn add_whitelist(&mut self, address: &Address) -> anyhow::Result<()> { diff --git a/src/uopool/mod.rs b/src/uopool/mod.rs index f352e76c..4cc21ba2 100644 --- a/src/uopool/mod.rs +++ b/src/uopool/mod.rs @@ -68,9 +68,9 @@ pub trait Reputation: Debug + Send + Sync + 'static { min_unstake_delay: U256, ) -> Self; async fn get(&mut self, address: &Address) -> anyhow::Result; - async fn update_hourly(&mut self) -> anyhow::Result<()>; async fn increment_seen(&mut self, address: &Address) -> anyhow::Result<()>; async fn increment_included(&mut self, address: &Address) -> anyhow::Result<()>; + fn update_hourly(&mut self); async fn add_whitelist(&mut self, address: &Address) -> anyhow::Result<()>; async fn remove_whitelist(&mut self, address: &Address) -> anyhow::Result; async fn is_whitelist(&self, address: &Address) -> anyhow::Result; @@ -112,18 +112,25 @@ pub async fn run(opts: UoPoolOpts, entry_points: Vec
, chain_id: U256) - mempools.insert(id, Box::::default()); } + let reputation = Arc::new(RwLock::new(MemoryReputation::new( + MIN_INCLUSION_RATE_DENOMINATOR, + THROTTLING_SLACK, + BAN_SLACK, + opts.min_stake, + opts.min_unstake_delay, + ))); + let svc = UoPoolServer::new(UoPoolService::new( Arc::new(RwLock::new(mempools)), - Arc::new(RwLock::new(MemoryReputation::new( - MIN_INCLUSION_RATE_DENOMINATOR, - THROTTLING_SLACK, - BAN_SLACK, - opts.min_stake, - opts.min_unstake_delay, - ))), + reputation.clone(), )); - // TODO: start updating reputation hourly + tokio::spawn(async move { + loop { + reputation.write().update_hourly(); + tokio::time::sleep(Duration::from_secs(60 * 60)).await; + } + }); info!( "UoPool gRPC server starting on {}", From 4aab56f6e5b68b464005f4a3b254de8481240833 Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Sat, 28 Jan 2023 15:56:19 +0100 Subject: [PATCH 10/20] feat: debug methods for reputation --- src/uopool/memory_reputation.rs | 23 +++++++++++++++++++++-- src/uopool/mod.rs | 11 ++++++++++- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/uopool/memory_reputation.rs b/src/uopool/memory_reputation.rs index ec754ec7..38b198ee 100644 --- a/src/uopool/memory_reputation.rs +++ b/src/uopool/memory_reputation.rs @@ -54,6 +54,8 @@ pub struct MemoryReputation { #[async_trait] impl Reputation for MemoryReputation { + type ReputationEntries = Vec; + fn new( min_inclusion_denominator: u64, throttling_slack: u64, @@ -238,9 +240,26 @@ impl Reputation for MemoryReputation { Ok(()) } - // debug: set reputation + #[cfg(debug_assertions)] + fn set(&mut self, reputation_entries: Self::ReputationEntries) -> Self::ReputationEntries { + let mut entities = self.entities.write(); - // debug: clear reputation + for reputation in reputation_entries { + entities.insert(reputation.address, reputation.clone()); + } + + entities.values().cloned().collect() + } + + #[cfg(debug_assertions)] + fn get_all(&self) -> Self::ReputationEntries { + self.entities.read().values().cloned().collect() + } + + #[cfg(debug_assertions)] + fn clear(&mut self) { + self.entities.write().clear(); + } } // tests diff --git a/src/uopool/mod.rs b/src/uopool/mod.rs index 4cc21ba2..5d2188fe 100644 --- a/src/uopool/mod.rs +++ b/src/uopool/mod.rs @@ -60,6 +60,8 @@ pub trait Mempool: Debug + Send + Sync + 'static { #[async_trait] pub trait Reputation: Debug + Send + Sync + 'static { + type ReputationEntries: IntoIterator; + fn new( min_inclusion_denominator: u64, throttling_slack: u64, @@ -81,7 +83,14 @@ pub trait Reputation: Debug + Send + Sync + 'static { async fn update_handle_ops_reverted(&mut self, address: &Address) -> anyhow::Result<()>; async fn verify_stake(&self, title: &str, stake_info: Option) -> anyhow::Result<()>; - // #[cfg(test)] + #[cfg(debug_assertions)] + fn set(&mut self, reputation_entries: Self::ReputationEntries) -> Self::ReputationEntries; + + #[cfg(debug_assertions)] + fn get_all(&self) -> Self::ReputationEntries; + + #[cfg(debug_assertions)] + fn clear(&mut self); } #[derive(Educe)] From 3f0ec5714005ba9985770be8aa2f5bb6402b36b5 Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Sat, 28 Jan 2023 16:13:19 +0100 Subject: [PATCH 11/20] chore: move two mempool methods to debug only --- src/uopool/memory_mempool.rs | 22 +++++++++++----------- src/uopool/mod.rs | 8 ++++++-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/uopool/memory_mempool.rs b/src/uopool/memory_mempool.rs index 153c74f3..28369886 100644 --- a/src/uopool/memory_mempool.rs +++ b/src/uopool/memory_mempool.rs @@ -50,10 +50,6 @@ impl Mempool for MemoryMempool { } } - async fn get_all(&self) -> anyhow::Result { - Ok(self.user_operations.read().values().cloned().collect()) - } - async fn get_all_by_sender(&self, sender: Address) -> anyhow::Result { let user_operations = self.user_operations.read(); @@ -93,14 +89,18 @@ impl Mempool for MemoryMempool { Ok(()) } - async fn clear(&mut self) -> anyhow::Result<()> { + #[cfg(debug_assertions)] + fn get_all(&self) -> Self::UserOperations { + self.user_operations.read().values().cloned().collect() + } + + #[cfg(debug_assertions)] + fn clear(&mut self) { let mut user_operations = self.user_operations.write(); let mut user_operations_by_sender = self.user_operations_by_sender.write(); user_operations.clear(); user_operations_by_sender.clear(); - - Ok(()) } } @@ -171,7 +171,7 @@ mod tests { ); } - assert_eq!(mempool.get_all().await.unwrap().len(), 7); + assert_eq!(mempool.get_all().len(), 7); assert_eq!( mempool.get_all_by_sender(senders[0]).await.unwrap().len(), 2 @@ -195,7 +195,7 @@ mod tests { anyhow::anyhow!("User operation not found").to_string() ); - assert_eq!(mempool.get_all().await.unwrap().len(), 6); + assert_eq!(mempool.get_all().len(), 6); assert_eq!( mempool.get_all_by_sender(senders[0]).await.unwrap().len(), 2 @@ -205,9 +205,9 @@ mod tests { 2 ); - assert_eq!(mempool.clear().await.unwrap(), ()); + assert_eq!(mempool.clear(), ()); - assert_eq!(mempool.get_all().await.unwrap().len(), 0); + assert_eq!(mempool.get_all().len(), 0); assert_eq!( mempool.get_all_by_sender(senders[0]).await.unwrap().len(), 0 diff --git a/src/uopool/mod.rs b/src/uopool/mod.rs index 5d2188fe..bb848419 100644 --- a/src/uopool/mod.rs +++ b/src/uopool/mod.rs @@ -52,10 +52,14 @@ pub trait Mempool: Debug + Send + Sync + 'static { chain_id: U256, ) -> anyhow::Result; async fn get(&self, user_operation_hash: UserOperationHash) -> anyhow::Result; - async fn get_all(&self) -> anyhow::Result; async fn get_all_by_sender(&self, sender: Address) -> anyhow::Result; async fn remove(&mut self, user_operation_hash: UserOperationHash) -> anyhow::Result<()>; - async fn clear(&mut self) -> anyhow::Result<()>; + + #[cfg(debug_assertions)] + fn get_all(&self) -> Self::UserOperations; + + #[cfg(debug_assertions)] + fn clear(&mut self); } #[async_trait] From c9b6a77fec64c0bd60164e6065665659fbacf06c Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Sat, 28 Jan 2023 16:59:17 +0100 Subject: [PATCH 12/20] feat: add debug api --- bin/bundler-rpc.rs | 13 ++++++++++++- bin/bundler.rs | 12 +++++++++++- src/rpc/debug.rs | 32 ++++++++++++++++++++++++++++++++ src/rpc/debug_api.rs | 18 ++++++++++++++++++ src/rpc/eth_api.rs | 4 ++++ src/rpc/mod.rs | 2 ++ src/types/mod.rs | 1 + src/types/reputation.rs | 28 ++++++++++++++++++++++++++++ src/uopool/memory_reputation.rs | 33 ++++----------------------------- src/uopool/mod.rs | 9 +++++---- 10 files changed, 117 insertions(+), 35 deletions(-) create mode 100644 src/rpc/debug.rs create mode 100644 src/rpc/debug_api.rs create mode 100644 src/types/reputation.rs diff --git a/bin/bundler-rpc.rs b/bin/bundler-rpc.rs index c46b4c57..940ed7ad 100644 --- a/bin/bundler-rpc.rs +++ b/bin/bundler-rpc.rs @@ -4,7 +4,10 @@ use jsonrpsee::{core::server::rpc_module::Methods, server::ServerBuilder, tracin use std::future::pending; use aa_bundler::{ - rpc::{eth::EthApiServerImpl, eth_api::EthApiServer}, + rpc::{ + debug::DebugApiServerImpl, debug_api::DebugApiServer, eth::EthApiServerImpl, + eth_api::EthApiServer, + }, uopool::server::uopool::uo_pool_client::UoPoolClient, }; @@ -34,6 +37,14 @@ async fn main() -> Result<()> { let mut api = Methods::new(); let uopool_grpc_client = UoPoolClient::connect(format!("http://{}", opt.uopool_grpc_listen_address)).await?; + + #[cfg(debug_assertions)] + api.merge( + DebugApiServerImpl { + uopool_grpc_client: uopool_grpc_client.clone(), + } + .into_rpc(), + )?; api.merge( EthApiServerImpl { call_gas_limit: 100_000_000, diff --git a/bin/bundler.rs b/bin/bundler.rs index 11939bbe..4c55d683 100644 --- a/bin/bundler.rs +++ b/bin/bundler.rs @@ -1,7 +1,10 @@ use aa_bundler::{ bundler::Bundler, models::wallet::Wallet, - rpc::{eth::EthApiServerImpl, eth_api::EthApiServer}, + rpc::{ + debug::DebugApiServerImpl, debug_api::DebugApiServer, eth::EthApiServerImpl, + eth_api::EthApiServer, + }, uopool::server::uopool::uo_pool_client::UoPoolClient, utils::{parse_address, parse_u256}, }; @@ -87,6 +90,13 @@ fn main() -> Result<()> { )) .await?; + #[cfg(debug_assertions)] + api.merge( + DebugApiServerImpl { + uopool_grpc_client: uopool_grpc_client.clone(), + } + .into_rpc(), + )?; api.merge( EthApiServerImpl { call_gas_limit: 100_000_000, diff --git a/src/rpc/debug.rs b/src/rpc/debug.rs new file mode 100644 index 00000000..cf397e5e --- /dev/null +++ b/src/rpc/debug.rs @@ -0,0 +1,32 @@ +use super::debug_api::DebugApiServer; +use crate::{ + types::{reputation::ReputationEntry, user_operation::UserOperation}, + uopool::server::uopool::uo_pool_client::UoPoolClient, +}; +use async_trait::async_trait; +use jsonrpsee::core::RpcResult; + +#[cfg(debug_assertions)] +pub struct DebugApiServerImpl { + pub uopool_grpc_client: UoPoolClient, +} + +#[cfg(debug_assertions)] +#[async_trait] +impl DebugApiServer for DebugApiServerImpl { + async fn clear_state(&self) -> RpcResult<()> { + todo!() + } + + async fn dump_mempool(&self) -> RpcResult> { + todo!() + } + + async fn set_reputation(&self, _reputation_entries: Vec) -> RpcResult<()> { + todo!() + } + + async fn dump_reputation(&self) -> RpcResult> { + todo!() + } +} diff --git a/src/rpc/debug_api.rs b/src/rpc/debug_api.rs new file mode 100644 index 00000000..981e5f26 --- /dev/null +++ b/src/rpc/debug_api.rs @@ -0,0 +1,18 @@ +use crate::types::{reputation::ReputationEntry, user_operation::UserOperation}; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; + +#[cfg(debug_assertions)] +#[rpc(server, namespace = "debug_bundler")] +pub trait DebugApi { + #[method(name = "clearState")] + async fn clear_state(&self) -> RpcResult<()>; + + #[method(name = "dumpMempool")] + async fn dump_mempool(&self) -> RpcResult>; + + #[method(name = "setReputation")] + async fn set_reputation(&self, reputation_entries: Vec) -> RpcResult<()>; + + #[method(name = "dumpReputation")] + async fn dump_reputation(&self) -> RpcResult>; +} diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index 9a5d59a2..33d458c3 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -15,20 +15,24 @@ pub struct EstimateUserOperationGasResponse { pub trait EthApi { #[method(name = "chainId")] async fn chain_id(&self) -> RpcResult; + #[method(name = "supportedEntryPoints")] async fn supported_entry_points(&self) -> RpcResult>; + #[method(name = "sendUserOperation")] async fn send_user_operation( &self, user_operation: UserOperation, entry_point: Address, ) -> RpcResult; + #[method(name = "estimateUserOperationGas")] async fn estimate_user_operation_gas( &self, user_operation: UserOperation, entry_point: Address, ) -> RpcResult; + #[method(name = "getUserOperationReceipt")] async fn get_user_operation_receipt( &self, diff --git a/src/rpc/mod.rs b/src/rpc/mod.rs index 4474ceff..d6489a25 100644 --- a/src/rpc/mod.rs +++ b/src/rpc/mod.rs @@ -1,2 +1,4 @@ +pub mod debug; +pub mod debug_api; pub mod eth; pub mod eth_api; diff --git a/src/types/mod.rs b/src/types/mod.rs index b7c34932..cc4efcc8 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1 +1,2 @@ +pub mod reputation; pub mod user_operation; diff --git a/src/types/reputation.rs b/src/types/reputation.rs new file mode 100644 index 00000000..1711915c --- /dev/null +++ b/src/types/reputation.rs @@ -0,0 +1,28 @@ +use educe::Educe; +use ethers::types::{Address, U256}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Copy, Educe, PartialEq, Eq, Serialize, Deserialize)] +#[educe(Debug)] +pub enum ReputationStatus { + OK, + THROTTLED, + BANNED, +} + +#[derive(Clone, Copy, Educe, Serialize, Deserialize)] +#[educe(Debug)] +pub struct ReputationEntry { + pub address: Address, + pub uo_seen: u64, + pub uo_included: u64, + pub status: ReputationStatus, +} + +#[derive(Clone, Copy, Educe, Serialize, Deserialize)] +#[educe(Debug)] +pub struct StakeInfo { + pub address: Address, + pub stake: U256, + pub unstake_delay: U256, // seconds +} diff --git a/src/uopool/memory_reputation.rs b/src/uopool/memory_reputation.rs index 38b198ee..c4d7d7f0 100644 --- a/src/uopool/memory_reputation.rs +++ b/src/uopool/memory_reputation.rs @@ -11,32 +11,9 @@ use std::{ sync::Arc, }; -use super::{Reputation, ReputationError, ENTITY_BANNED_ERROR_CODE, STAKE_TOO_LOW_ERROR_CODE}; - -#[derive(Clone, Copy, Educe, PartialEq, Eq)] -#[educe(Debug)] -pub enum ReputationStatus { - OK, - THROTTLED, - BANNED, -} +use crate::types::reputation::{ReputationEntry, ReputationStatus, StakeInfo}; -#[derive(Clone, Copy, Educe)] -#[educe(Debug)] -pub struct ReputationEntry { - address: Address, - uo_seen: u64, - uo_included: u64, - status: ReputationStatus, -} - -#[derive(Clone, Copy, Educe)] -#[educe(Debug)] -pub struct StakeInfo { - address: Address, - stake: U256, - unstake_delay: U256, // seconds -} +use super::{Reputation, ReputationError, ENTITY_BANNED_ERROR_CODE, STAKE_TOO_LOW_ERROR_CODE}; #[derive(Default, Educe)] #[educe(Debug)] @@ -241,14 +218,12 @@ impl Reputation for MemoryReputation { } #[cfg(debug_assertions)] - fn set(&mut self, reputation_entries: Self::ReputationEntries) -> Self::ReputationEntries { + fn set(&mut self, reputation_entries: Self::ReputationEntries) { let mut entities = self.entities.write(); for reputation in reputation_entries { - entities.insert(reputation.address, reputation.clone()); + entities.insert(reputation.address, reputation); } - - entities.values().cloned().collect() } #[cfg(debug_assertions)] diff --git a/src/uopool/mod.rs b/src/uopool/mod.rs index bb848419..95eb66f7 100644 --- a/src/uopool/mod.rs +++ b/src/uopool/mod.rs @@ -1,5 +1,8 @@ use crate::{ - types::user_operation::{UserOperation, UserOperationHash}, + types::{ + reputation::{ReputationEntry, ReputationStatus, StakeInfo}, + user_operation::{UserOperation, UserOperationHash}, + }, uopool::{ memory_mempool::MemoryMempool, memory_reputation::MemoryReputation, server::uopool::uo_pool_server::UoPoolServer, services::UoPoolService, @@ -19,8 +22,6 @@ use jsonrpsee::{tracing::info, types::ErrorObject}; use parking_lot::RwLock; use std::{collections::HashMap, fmt::Debug, net::SocketAddr, sync::Arc, time::Duration}; -use self::memory_reputation::{ReputationEntry, ReputationStatus, StakeInfo}; - pub mod memory_mempool; pub mod memory_reputation; pub mod server; @@ -88,7 +89,7 @@ pub trait Reputation: Debug + Send + Sync + 'static { async fn verify_stake(&self, title: &str, stake_info: Option) -> anyhow::Result<()>; #[cfg(debug_assertions)] - fn set(&mut self, reputation_entries: Self::ReputationEntries) -> Self::ReputationEntries; + fn set(&mut self, reputation_entries: Self::ReputationEntries); #[cfg(debug_assertions)] fn get_all(&self) -> Self::ReputationEntries; From a37277228d7eab3635cdf721c43342d674f7024f Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Sat, 28 Jan 2023 20:48:22 +0100 Subject: [PATCH 13/20] feat: make reputation in uopoolservice generic, implement clear endpoint --- src/proto/uopool/uopool.proto | 29 +++++++++++++++++++---- src/rpc/debug.rs | 21 +++++++++++++++-- src/uopool/memory_reputation.rs | 20 +++++++--------- src/uopool/mod.rs | 12 ++++++---- src/uopool/services/uopool.rs | 42 +++++++++++++++++++++++---------- 5 files changed, 88 insertions(+), 36 deletions(-) diff --git a/src/proto/uopool/uopool.proto b/src/proto/uopool/uopool.proto index 41f03d81..2fae54da 100644 --- a/src/proto/uopool/uopool.proto +++ b/src/proto/uopool/uopool.proto @@ -30,17 +30,36 @@ enum RemoveResult { message RemoveResponse { RemoveResult result = 1; - string data = 2; } -message AllRequest {} +enum GetAllResult { + GOT_ALL = 0; + NOT_GOT_ALL = 1; +} + +message GetAllRequest {} + +message GetAllResponse { + GetAllResult result = 1; + repeated types.UserOperation uos = 2; +} -message AllResponse { - repeated types.UserOperation uos = 1; +enum ClearResult { + CLEARED = 0; + NOT_CLEARED = 1; +} + +message ClearRequest {} + +message ClearResponse { + ClearResult result = 1; } service UoPool { rpc Add(AddRequest) returns (AddResponse); rpc Remove(RemoveRequest) returns (RemoveResponse); - rpc All(AllRequest) returns (AllResponse); + rpc GetAll(GetAllRequest) returns (GetAllResponse); + + // debug + rpc Clear(ClearRequest) returns (ClearResponse); } diff --git a/src/rpc/debug.rs b/src/rpc/debug.rs index cf397e5e..4c5d637c 100644 --- a/src/rpc/debug.rs +++ b/src/rpc/debug.rs @@ -1,8 +1,9 @@ use super::debug_api::DebugApiServer; use crate::{ types::{reputation::ReputationEntry, user_operation::UserOperation}, - uopool::server::uopool::uo_pool_client::UoPoolClient, + uopool::server::uopool::{uo_pool_client::UoPoolClient, ClearRequest, ClearResult}, }; +use anyhow::format_err; use async_trait::async_trait; use jsonrpsee::core::RpcResult; @@ -15,7 +16,23 @@ pub struct DebugApiServerImpl { #[async_trait] impl DebugApiServer for DebugApiServerImpl { async fn clear_state(&self) -> RpcResult<()> { - todo!() + let mut uopool_grpc_client = self.uopool_grpc_client.clone(); + + let request = tonic::Request::new(ClearRequest {}); + + let response = uopool_grpc_client + .clear(request) + .await + .map_err(|status| format_err!("GRPC error (uopool): {}", status.message()))? + .into_inner(); + + if response.result == ClearResult::Cleared as i32 { + return Ok(()); + } + + Err(jsonrpsee::core::Error::Custom( + "error clearing state".to_string(), + )) } async fn dump_mempool(&self) -> RpcResult> { diff --git a/src/uopool/memory_reputation.rs b/src/uopool/memory_reputation.rs index c4d7d7f0..7026979b 100644 --- a/src/uopool/memory_reputation.rs +++ b/src/uopool/memory_reputation.rs @@ -33,23 +33,19 @@ pub struct MemoryReputation { impl Reputation for MemoryReputation { type ReputationEntries = Vec; - fn new( + fn init( + &mut self, min_inclusion_denominator: u64, throttling_slack: u64, ban_slack: u64, min_stake: U256, min_unstake_delay: U256, - ) -> Self { - Self { - min_inclusion_denominator, - throttling_slack, - ban_slack, - min_stake, - min_unstake_delay, - entities: Arc::new(RwLock::new(HashMap::new())), - whitelist: Arc::new(RwLock::new(HashSet::new())), - blacklist: Arc::new(RwLock::new(HashSet::new())), - } + ) { + self.min_inclusion_denominator = min_inclusion_denominator; + self.throttling_slack = throttling_slack; + self.ban_slack = ban_slack; + self.min_stake = min_stake; + self.min_unstake_delay = min_unstake_delay; } async fn get(&mut self, address: &Address) -> anyhow::Result { diff --git a/src/uopool/mod.rs b/src/uopool/mod.rs index 95eb66f7..27a901c8 100644 --- a/src/uopool/mod.rs +++ b/src/uopool/mod.rs @@ -41,6 +41,7 @@ pub fn mempool_id(entry_point: Address, chain_id: U256) -> MempoolId { } pub type MempoolBox = Box>; +pub type ReputationBox = Box>; #[async_trait] pub trait Mempool: Debug + Send + Sync + 'static { @@ -67,13 +68,14 @@ pub trait Mempool: Debug + Send + Sync + 'static { pub trait Reputation: Debug + Send + Sync + 'static { type ReputationEntries: IntoIterator; - fn new( + fn init( + &mut self, min_inclusion_denominator: u64, throttling_slack: u64, ban_slack: u64, min_stake: U256, min_unstake_delay: U256, - ) -> Self; + ); async fn get(&mut self, address: &Address) -> anyhow::Result; async fn increment_seen(&mut self, address: &Address) -> anyhow::Result<()>; async fn increment_included(&mut self, address: &Address) -> anyhow::Result<()>; @@ -126,13 +128,15 @@ pub async fn run(opts: UoPoolOpts, entry_points: Vec
, chain_id: U256) - mempools.insert(id, Box::::default()); } - let reputation = Arc::new(RwLock::new(MemoryReputation::new( + let reputation: Arc>>> = + Arc::new(RwLock::new(Box::::default())); + reputation.write().init( MIN_INCLUSION_RATE_DENOMINATOR, THROTTLING_SLACK, BAN_SLACK, opts.min_stake, opts.min_unstake_delay, - ))); + ); let svc = UoPoolServer::new(UoPoolService::new( Arc::new(RwLock::new(mempools)), diff --git a/src/uopool/services/uopool.rs b/src/uopool/services/uopool.rs index ef418d10..5628c5d5 100644 --- a/src/uopool/services/uopool.rs +++ b/src/uopool/services/uopool.rs @@ -1,12 +1,12 @@ use crate::{ - types::user_operation::UserOperation, + types::{reputation::ReputationEntry, user_operation::UserOperation}, uopool::{ - memory_reputation::MemoryReputation, server::uopool::{ - uo_pool_server::UoPool, AddRequest, AddResponse, AddResult, AllRequest, AllResponse, - RemoveRequest, RemoveResponse, + uo_pool_server::UoPool, AddRequest, AddResponse, AddResult, ClearRequest, + ClearResponse, ClearResult, GetAllRequest, GetAllResponse, RemoveRequest, + RemoveResponse, }, - MempoolBox, MempoolId, + MempoolBox, MempoolId, ReputationBox, }, }; use async_trait::async_trait; @@ -19,18 +19,18 @@ use tonic::Response; pub type UoPoolError = ErrorObject<'static>; pub struct UoPoolService { - _mempools: Arc>>>>, - _reputation: Arc>, + mempools: Arc>>>>, + reputation: Arc>>>, } impl UoPoolService { pub fn new( mempools: Arc>>>>, - reputation: Arc>, + reputation: Arc>>>, ) -> Self { Self { - _mempools: mempools, - _reputation: reputation, + mempools, + reputation, } } } @@ -82,10 +82,26 @@ impl UoPool for UoPoolService { Err(tonic::Status::unimplemented("todo")) } - async fn all( + async fn get_all( &self, - _request: tonic::Request, - ) -> Result, tonic::Status> { + _request: tonic::Request, + ) -> Result, tonic::Status> { Err(tonic::Status::unimplemented("todo")) } + + #[cfg(debug_assertions)] + async fn clear( + &self, + _request: tonic::Request, + ) -> Result, tonic::Status> { + for mempool in self.mempools.write().values_mut() { + mempool.clear(); + } + + self.reputation.write().clear(); + + Ok(tonic::Response::new(ClearResponse { + result: ClearResult::Cleared as i32, + })) + } } From f46d4f13b1fb1caf70b880cc9fc9540b820d5bf2 Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Sat, 28 Jan 2023 21:32:25 +0100 Subject: [PATCH 14/20] feat: implement debug get all user operations --- src/proto/uopool/uopool.proto | 6 +++-- src/rpc/debug.rs | 28 ++++++++++++++++++++--- src/rpc/debug_api.rs | 3 ++- src/uopool/mod.rs | 1 + src/uopool/services/uopool.rs | 43 +++++++++++++++++++++++++++++++---- 5 files changed, 70 insertions(+), 11 deletions(-) diff --git a/src/proto/uopool/uopool.proto b/src/proto/uopool/uopool.proto index 2fae54da..7540c5ec 100644 --- a/src/proto/uopool/uopool.proto +++ b/src/proto/uopool/uopool.proto @@ -37,7 +37,9 @@ enum GetAllResult { NOT_GOT_ALL = 1; } -message GetAllRequest {} +message GetAllRequest { + types.H160 ep = 1; +} message GetAllResponse { GetAllResult result = 1; @@ -58,8 +60,8 @@ message ClearResponse { service UoPool { rpc Add(AddRequest) returns (AddResponse); rpc Remove(RemoveRequest) returns (RemoveResponse); - rpc GetAll(GetAllRequest) returns (GetAllResponse); // debug + rpc GetAll(GetAllRequest) returns (GetAllResponse); rpc Clear(ClearRequest) returns (ClearResponse); } diff --git a/src/rpc/debug.rs b/src/rpc/debug.rs index 4c5d637c..50befbca 100644 --- a/src/rpc/debug.rs +++ b/src/rpc/debug.rs @@ -1,10 +1,14 @@ use super::debug_api::DebugApiServer; use crate::{ types::{reputation::ReputationEntry, user_operation::UserOperation}, - uopool::server::uopool::{uo_pool_client::UoPoolClient, ClearRequest, ClearResult}, + uopool::server::uopool::{ + uo_pool_client::UoPoolClient, ClearRequest, ClearResult, GetAllRequest, + GetAllResult, + }, }; use anyhow::format_err; use async_trait::async_trait; +use ethers::types::Address; use jsonrpsee::core::RpcResult; #[cfg(debug_assertions)] @@ -35,8 +39,26 @@ impl DebugApiServer for DebugApiServerImpl { )) } - async fn dump_mempool(&self) -> RpcResult> { - todo!() + async fn dump_mempool(&self, entry_point: Address) -> RpcResult> { + let mut uopool_grpc_client = self.uopool_grpc_client.clone(); + + let request = tonic::Request::new(GetAllRequest { + ep: Some(entry_point.into()), + }); + + let response = uopool_grpc_client + .get_all(request) + .await + .map_err(|status| format_err!("GRPC error (uopool): {}", status.message()))? + .into_inner(); + + if response.result == GetAllResult::GotAll as i32 { + return Ok(response.uos.iter().map(|uo| uo.clone().into()).collect()); + } + + Err(jsonrpsee::core::Error::Custom( + "error getting mempool".to_string(), + )) } async fn set_reputation(&self, _reputation_entries: Vec) -> RpcResult<()> { diff --git a/src/rpc/debug_api.rs b/src/rpc/debug_api.rs index 981e5f26..6370ac17 100644 --- a/src/rpc/debug_api.rs +++ b/src/rpc/debug_api.rs @@ -1,4 +1,5 @@ use crate::types::{reputation::ReputationEntry, user_operation::UserOperation}; +use ethers::types::Address; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; #[cfg(debug_assertions)] @@ -8,7 +9,7 @@ pub trait DebugApi { async fn clear_state(&self) -> RpcResult<()>; #[method(name = "dumpMempool")] - async fn dump_mempool(&self) -> RpcResult>; + async fn dump_mempool(&self, entry_point: Address) -> RpcResult>; #[method(name = "setReputation")] async fn set_reputation(&self, reputation_entries: Vec) -> RpcResult<()>; diff --git a/src/uopool/mod.rs b/src/uopool/mod.rs index 27a901c8..4ceecd38 100644 --- a/src/uopool/mod.rs +++ b/src/uopool/mod.rs @@ -141,6 +141,7 @@ pub async fn run(opts: UoPoolOpts, entry_points: Vec
, chain_id: U256) - let svc = UoPoolServer::new(UoPoolService::new( Arc::new(RwLock::new(mempools)), reputation.clone(), + chain_id, )); tokio::spawn(async move { diff --git a/src/uopool/services/uopool.rs b/src/uopool/services/uopool.rs index 5628c5d5..8bfb9336 100644 --- a/src/uopool/services/uopool.rs +++ b/src/uopool/services/uopool.rs @@ -3,13 +3,14 @@ use crate::{ uopool::{ server::uopool::{ uo_pool_server::UoPool, AddRequest, AddResponse, AddResult, ClearRequest, - ClearResponse, ClearResult, GetAllRequest, GetAllResponse, RemoveRequest, + ClearResponse, ClearResult, GetAllRequest, GetAllResponse, GetAllResult, RemoveRequest, RemoveResponse, }, MempoolBox, MempoolId, ReputationBox, }, }; use async_trait::async_trait; +use ethers::types::{Address, U256}; use jsonrpsee::{tracing::info, types::ErrorObject}; use parking_lot::RwLock; use serde_json::json; @@ -19,18 +20,21 @@ use tonic::Response; pub type UoPoolError = ErrorObject<'static>; pub struct UoPoolService { - mempools: Arc>>>>, - reputation: Arc>>>, + pub mempools: Arc>>>>, + pub reputation: Arc>>>, + pub chain_id: U256, } impl UoPoolService { pub fn new( mempools: Arc>>>>, reputation: Arc>>>, + chain_id: U256, ) -> Self { Self { mempools, reputation, + chain_id, } } } @@ -82,11 +86,40 @@ impl UoPool for UoPoolService { Err(tonic::Status::unimplemented("todo")) } + #[cfg(debug_assertions)] async fn get_all( &self, - _request: tonic::Request, + request: tonic::Request, ) -> Result, tonic::Status> { - Err(tonic::Status::unimplemented("todo")) + use crate::uopool::mempool_id; + + let req = request.into_inner(); + let mut res = GetAllResponse::default(); + + if let Some(entry_point) = req.ep { + let entry_point: Address = entry_point + .try_into() + .map_err(|_| tonic::Status::invalid_argument("invalid entry point"))?; + + if let Some(mempool) = self + .mempools + .read() + .get(&mempool_id(entry_point, self.chain_id)) + { + res.result = GetAllResult::GotAll as i32; + res.uos = mempool + .get_all() + .iter() + .map(|uo| uo.clone().into()) + .collect(); + } else { + res.result = GetAllResult::NotGotAll as i32; + } + + return Ok(tonic::Response::new(res)); + } + + Err(tonic::Status::invalid_argument("missing entry point")) } #[cfg(debug_assertions)] From c2ea4325a4c3cce082a13012575c5678fff731c6 Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Sun, 29 Jan 2023 00:11:40 +0100 Subject: [PATCH 15/20] feat: implement debug reputation endpoints --- src/proto/types/types.proto | 13 ++++++++ src/proto/uopool/uopool.proto | 27 ++++++++++++++++ src/rpc/debug.rs | 51 +++++++++++++++++++++++++---- src/rpc/debug_api.rs | 8 +++-- src/uopool/server.rs | 45 ++++++++++++++++++++++++++ src/uopool/services/uopool.rs | 61 ++++++++++++++++++++++++++++------- 6 files changed, 185 insertions(+), 20 deletions(-) diff --git a/src/proto/types/types.proto b/src/proto/types/types.proto index 6d1b2bbd..4b8a0b12 100644 --- a/src/proto/types/types.proto +++ b/src/proto/types/types.proto @@ -34,3 +34,16 @@ message UserOperation { bytes paymaster_and_data = 10; bytes signature = 11; } + +enum ReputationStatus { + OK = 0; + THROTTLED = 1; + BANNED = 2; +} + +message ReputationEntry { + types.H160 address = 1; + uint64 uo_seen = 2; + uint64 uo_included = 3; + ReputationStatus status = 4; +} \ No newline at end of file diff --git a/src/proto/uopool/uopool.proto b/src/proto/uopool/uopool.proto index 7540c5ec..26b3d18b 100644 --- a/src/proto/uopool/uopool.proto +++ b/src/proto/uopool/uopool.proto @@ -57,6 +57,31 @@ message ClearResponse { ClearResult result = 1; } +enum GetAllReputationResult { + GOT_ALL_REPUTATION = 0; + NOT_GOT_ALL_REPUTATION = 1; +} + +message GetAllReputationRequest {} + +message GetAllReputationResponse { + GetAllReputationResult result = 1; + repeated types.ReputationEntry res = 2; +} + +enum SetReputationResult { + SET_REPUTATION = 0; + NOT_SET_REPUTATION = 1; +} + +message SetReputationRequest { + repeated types.ReputationEntry res = 1; +} + +message SetReputationResponse { + SetReputationResult result = 1; +} + service UoPool { rpc Add(AddRequest) returns (AddResponse); rpc Remove(RemoveRequest) returns (RemoveResponse); @@ -64,4 +89,6 @@ service UoPool { // debug rpc GetAll(GetAllRequest) returns (GetAllResponse); rpc Clear(ClearRequest) returns (ClearResponse); + rpc GetAllReputation(GetAllReputationRequest) returns (GetAllReputationResponse); + rpc SetReputation(SetReputationRequest) returns (SetReputationResponse); } diff --git a/src/rpc/debug.rs b/src/rpc/debug.rs index 50befbca..e3b22752 100644 --- a/src/rpc/debug.rs +++ b/src/rpc/debug.rs @@ -2,8 +2,9 @@ use super::debug_api::DebugApiServer; use crate::{ types::{reputation::ReputationEntry, user_operation::UserOperation}, uopool::server::uopool::{ - uo_pool_client::UoPoolClient, ClearRequest, ClearResult, GetAllRequest, - GetAllResult, + uo_pool_client::UoPoolClient, ClearRequest, ClearResult, GetAllReputationRequest, + GetAllReputationResult, GetAllRequest, GetAllResult, SetReputationRequest, + SetReputationResult, }, }; use anyhow::format_err; @@ -61,11 +62,49 @@ impl DebugApiServer for DebugApiServerImpl { )) } - async fn set_reputation(&self, _reputation_entries: Vec) -> RpcResult<()> { - todo!() + async fn set_reputation( + &self, + reputation_entries: Vec, + _entry_point: Address, + ) -> RpcResult<()> { + let mut uopool_grpc_client = self.uopool_grpc_client.clone(); + + let request = tonic::Request::new(SetReputationRequest { + res: reputation_entries.iter().map(|re| (*re).into()).collect(), + }); + + let response = uopool_grpc_client + .set_reputation(request) + .await + .map_err(|status| format_err!("GRPC error (uopool): {}", status.message()))? + .into_inner(); + + if response.result == SetReputationResult::SetReputation as i32 { + return Ok(()); + } + + Err(jsonrpsee::core::Error::Custom( + "error setting reputation".to_string(), + )) } - async fn dump_reputation(&self) -> RpcResult> { - todo!() + async fn dump_reputation(&self, _entry_point: Address) -> RpcResult> { + let mut uopool_grpc_client = self.uopool_grpc_client.clone(); + + let request = tonic::Request::new(GetAllReputationRequest {}); + + let response = uopool_grpc_client + .get_all_reputation(request) + .await + .map_err(|status| format_err!("GRPC error (uopool): {}", status.message()))? + .into_inner(); + + if response.result == GetAllReputationResult::GotAllReputation as i32 { + return Ok(response.res.iter().map(|re| re.clone().into()).collect()); + } + + Err(jsonrpsee::core::Error::Custom( + "error getting reputation".to_string(), + )) } } diff --git a/src/rpc/debug_api.rs b/src/rpc/debug_api.rs index 6370ac17..976c7092 100644 --- a/src/rpc/debug_api.rs +++ b/src/rpc/debug_api.rs @@ -12,8 +12,12 @@ pub trait DebugApi { async fn dump_mempool(&self, entry_point: Address) -> RpcResult>; #[method(name = "setReputation")] - async fn set_reputation(&self, reputation_entries: Vec) -> RpcResult<()>; + async fn set_reputation( + &self, + reputation_entries: Vec, + entry_point: Address, + ) -> RpcResult<()>; #[method(name = "dumpReputation")] - async fn dump_reputation(&self) -> RpcResult>; + async fn dump_reputation(&self, entry_point: Address) -> RpcResult>; } diff --git a/src/uopool/server.rs b/src/uopool/server.rs index bfb30b80..5ba22b90 100644 --- a/src/uopool/server.rs +++ b/src/uopool/server.rs @@ -112,6 +112,51 @@ pub mod types { } } } + + impl From for ReputationEntry { + fn from(reputation_entry: crate::types::reputation::ReputationEntry) -> Self { + Self { + address: Some(reputation_entry.address.into()), + uo_seen: reputation_entry.uo_seen, + uo_included: reputation_entry.uo_included, + status: match reputation_entry.status { + crate::types::reputation::ReputationStatus::OK => ReputationStatus::Ok, + crate::types::reputation::ReputationStatus::THROTTLED => { + ReputationStatus::Throttled + } + crate::types::reputation::ReputationStatus::BANNED => ReputationStatus::Banned, + } as i32, + } + } + } + + impl From for crate::types::reputation::ReputationEntry { + fn from(reputation_entry: ReputationEntry) -> Self { + Self { + address: { + if let Some(address) = reputation_entry.address { + address.into() + } else { + Address::zero() + } + }, + uo_seen: reputation_entry.uo_seen, + uo_included: reputation_entry.uo_included, + status: match reputation_entry.status { + _ if reputation_entry.status == ReputationStatus::Ok as i32 => { + crate::types::reputation::ReputationStatus::OK + } + _ if reputation_entry.status == ReputationStatus::Throttled as i32 => { + crate::types::reputation::ReputationStatus::THROTTLED + } + _ if reputation_entry.status == ReputationStatus::Banned as i32 => { + crate::types::reputation::ReputationStatus::BANNED + } + _ => crate::types::reputation::ReputationStatus::OK, + }, + } + } + } } pub mod uopool { diff --git a/src/uopool/services/uopool.rs b/src/uopool/services/uopool.rs index 8bfb9336..5a75fdb2 100644 --- a/src/uopool/services/uopool.rs +++ b/src/uopool/services/uopool.rs @@ -3,8 +3,9 @@ use crate::{ uopool::{ server::uopool::{ uo_pool_server::UoPool, AddRequest, AddResponse, AddResult, ClearRequest, - ClearResponse, ClearResult, GetAllRequest, GetAllResponse, GetAllResult, RemoveRequest, - RemoveResponse, + ClearResponse, ClearResult, GetAllReputationRequest, GetAllReputationResponse, + GetAllReputationResult, GetAllRequest, GetAllResponse, GetAllResult, RemoveRequest, + RemoveResponse, SetReputationRequest, SetReputationResponse, SetReputationResult, }, MempoolBox, MempoolId, ReputationBox, }, @@ -86,6 +87,22 @@ impl UoPool for UoPoolService { Err(tonic::Status::unimplemented("todo")) } + #[cfg(debug_assertions)] + async fn clear( + &self, + _request: tonic::Request, + ) -> Result, tonic::Status> { + for mempool in self.mempools.write().values_mut() { + mempool.clear(); + } + + self.reputation.write().clear(); + + Ok(tonic::Response::new(ClearResponse { + result: ClearResult::Cleared as i32, + })) + } + #[cfg(debug_assertions)] async fn get_all( &self, @@ -123,18 +140,38 @@ impl UoPool for UoPoolService { } #[cfg(debug_assertions)] - async fn clear( + async fn set_reputation( &self, - _request: tonic::Request, - ) -> Result, tonic::Status> { - for mempool in self.mempools.write().values_mut() { - mempool.clear(); - } + request: tonic::Request, + ) -> Result, tonic::Status> { + let req = request.into_inner(); + let mut res = SetReputationResponse::default(); - self.reputation.write().clear(); + self.reputation + .write() + .set(req.res.iter().map(|re| re.clone().into()).collect()); - Ok(tonic::Response::new(ClearResponse { - result: ClearResult::Cleared as i32, - })) + res.result = SetReputationResult::SetReputation as i32; + + Ok(tonic::Response::new(res)) + } + + #[cfg(debug_assertions)] + async fn get_all_reputation( + &self, + _request: tonic::Request, + ) -> Result, tonic::Status> { + let res = GetAllReputationResponse { + result: GetAllReputationResult::GotAllReputation as i32, + res: self + .reputation + .read() + .get_all() + .iter() + .map(|re| (*re).into()) + .collect(), + }; + + Ok(tonic::Response::new(res)) } } From 539c459b3d57df0d2bcfbb93b6a0a8f000cb073f Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Sun, 29 Jan 2023 18:16:52 +0100 Subject: [PATCH 16/20] feat: reputation different for each entry point --- src/proto/uopool/uopool.proto | 5 ++- src/rpc/debug.rs | 9 +++-- src/uopool/mod.rs | 29 ++++++++++------ src/uopool/services/uopool.rs | 65 ++++++++++++++++++++++++----------- 4 files changed, 72 insertions(+), 36 deletions(-) diff --git a/src/proto/uopool/uopool.proto b/src/proto/uopool/uopool.proto index 26b3d18b..5bf640a5 100644 --- a/src/proto/uopool/uopool.proto +++ b/src/proto/uopool/uopool.proto @@ -62,7 +62,9 @@ enum GetAllReputationResult { NOT_GOT_ALL_REPUTATION = 1; } -message GetAllReputationRequest {} +message GetAllReputationRequest { + types.H160 ep = 1; +} message GetAllReputationResponse { GetAllReputationResult result = 1; @@ -76,6 +78,7 @@ enum SetReputationResult { message SetReputationRequest { repeated types.ReputationEntry res = 1; + types.H160 ep = 2; } message SetReputationResponse { diff --git a/src/rpc/debug.rs b/src/rpc/debug.rs index e3b22752..3b8434d6 100644 --- a/src/rpc/debug.rs +++ b/src/rpc/debug.rs @@ -65,12 +65,13 @@ impl DebugApiServer for DebugApiServerImpl { async fn set_reputation( &self, reputation_entries: Vec, - _entry_point: Address, + entry_point: Address, ) -> RpcResult<()> { let mut uopool_grpc_client = self.uopool_grpc_client.clone(); let request = tonic::Request::new(SetReputationRequest { res: reputation_entries.iter().map(|re| (*re).into()).collect(), + ep: Some(entry_point.into()), }); let response = uopool_grpc_client @@ -88,10 +89,12 @@ impl DebugApiServer for DebugApiServerImpl { )) } - async fn dump_reputation(&self, _entry_point: Address) -> RpcResult> { + async fn dump_reputation(&self, entry_point: Address) -> RpcResult> { let mut uopool_grpc_client = self.uopool_grpc_client.clone(); - let request = tonic::Request::new(GetAllReputationRequest {}); + let request = tonic::Request::new(GetAllReputationRequest { + ep: Some(entry_point.into()), + }); let response = uopool_grpc_client .get_all_reputation(request) diff --git a/src/uopool/mod.rs b/src/uopool/mod.rs index 4ceecd38..e58a67fd 100644 --- a/src/uopool/mod.rs +++ b/src/uopool/mod.rs @@ -123,30 +123,37 @@ pub async fn run(opts: UoPoolOpts, entry_points: Vec
, chain_id: U256) - let mut builder = tonic::transport::Server::builder(); let mut mempools = HashMap::>>::new(); + let mut reputations = HashMap::>>::new(); + for entry_point in entry_points { let id = mempool_id(entry_point, chain_id); mempools.insert(id, Box::::default()); + + reputations.insert(id, Box::::default()); + if let Some(reputation) = reputations.get_mut(&id) { + reputation.init( + MIN_INCLUSION_RATE_DENOMINATOR, + THROTTLING_SLACK, + BAN_SLACK, + opts.min_stake, + opts.min_unstake_delay, + ); + } } - let reputation: Arc>>> = - Arc::new(RwLock::new(Box::::default())); - reputation.write().init( - MIN_INCLUSION_RATE_DENOMINATOR, - THROTTLING_SLACK, - BAN_SLACK, - opts.min_stake, - opts.min_unstake_delay, - ); + let reputations = Arc::new(RwLock::new(reputations)); let svc = UoPoolServer::new(UoPoolService::new( Arc::new(RwLock::new(mempools)), - reputation.clone(), + reputations.clone(), chain_id, )); tokio::spawn(async move { loop { - reputation.write().update_hourly(); + for reputation in reputations.write().values_mut() { + reputation.update_hourly(); + } tokio::time::sleep(Duration::from_secs(60 * 60)).await; } }); diff --git a/src/uopool/services/uopool.rs b/src/uopool/services/uopool.rs index 5a75fdb2..85205367 100644 --- a/src/uopool/services/uopool.rs +++ b/src/uopool/services/uopool.rs @@ -1,6 +1,7 @@ use crate::{ types::{reputation::ReputationEntry, user_operation::UserOperation}, uopool::{ + mempool_id, server::uopool::{ uo_pool_server::UoPool, AddRequest, AddResponse, AddResult, ClearRequest, ClearResponse, ClearResult, GetAllReputationRequest, GetAllReputationResponse, @@ -22,19 +23,19 @@ pub type UoPoolError = ErrorObject<'static>; pub struct UoPoolService { pub mempools: Arc>>>>, - pub reputation: Arc>>>, + pub reputations: Arc>>>>, pub chain_id: U256, } impl UoPoolService { pub fn new( mempools: Arc>>>>, - reputation: Arc>>>, + reputations: Arc>>>>, chain_id: U256, ) -> Self { Self { mempools, - reputation, + reputations, chain_id, } } @@ -96,7 +97,7 @@ impl UoPool for UoPoolService { mempool.clear(); } - self.reputation.write().clear(); + self.reputations.write().clear(); Ok(tonic::Response::new(ClearResponse { result: ClearResult::Cleared as i32, @@ -108,8 +109,6 @@ impl UoPool for UoPoolService { &self, request: tonic::Request, ) -> Result, tonic::Status> { - use crate::uopool::mempool_id; - let req = request.into_inner(); let mut res = GetAllResponse::default(); @@ -147,31 +146,55 @@ impl UoPool for UoPoolService { let req = request.into_inner(); let mut res = SetReputationResponse::default(); - self.reputation - .write() - .set(req.res.iter().map(|re| re.clone().into()).collect()); + if let Some(entry_point) = req.ep { + let entry_point: Address = entry_point + .try_into() + .map_err(|_| tonic::Status::invalid_argument("invalid entry point"))?; - res.result = SetReputationResult::SetReputation as i32; + if let Some(reputation) = self + .reputations + .write() + .get_mut(&mempool_id(entry_point, self.chain_id)) + { + reputation.set(req.res.iter().map(|re| re.clone().into()).collect()); + res.result = SetReputationResult::SetReputation as i32; + } else { + res.result = SetReputationResult::NotSetReputation as i32; + } + + return Ok(tonic::Response::new(res)); + } - Ok(tonic::Response::new(res)) + Err(tonic::Status::invalid_argument("missing entry point")) } #[cfg(debug_assertions)] async fn get_all_reputation( &self, - _request: tonic::Request, + request: tonic::Request, ) -> Result, tonic::Status> { - let res = GetAllReputationResponse { - result: GetAllReputationResult::GotAllReputation as i32, - res: self - .reputation + let req = request.into_inner(); + let mut res = GetAllReputationResponse::default(); + + if let Some(entry_point) = req.ep { + let entry_point: Address = entry_point + .try_into() + .map_err(|_| tonic::Status::invalid_argument("invalid entry point"))?; + + if let Some(reputation) = self + .reputations .read() - .get_all() - .iter() - .map(|re| (*re).into()) - .collect(), + .get(&mempool_id(entry_point, self.chain_id)) + { + res.result = GetAllReputationResult::GotAllReputation as i32; + res.res = reputation.get_all().iter().map(|re| (*re).into()).collect(); + } else { + res.result = GetAllReputationResult::NotGotAllReputation as i32; + } + + return Ok(tonic::Response::new(res)); }; - Ok(tonic::Response::new(res)) + Err(tonic::Status::invalid_argument("missing entry point")) } } From 46733fa7cc183b5f0077de2bcdfdc625983f87e3 Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Mon, 30 Jan 2023 10:37:56 +0100 Subject: [PATCH 17/20] feat: add reputation tests --- src/types/reputation.rs | 90 +++++++++++++++- src/uopool/memory_reputation.rs | 183 +++++++++++++++++++++++++------- src/uopool/mod.rs | 18 ++-- 3 files changed, 237 insertions(+), 54 deletions(-) diff --git a/src/types/reputation.rs b/src/types/reputation.rs index 1711915c..f2d81562 100644 --- a/src/types/reputation.rs +++ b/src/types/reputation.rs @@ -1,6 +1,19 @@ use educe::Educe; -use ethers::types::{Address, U256}; +use ethers::{ + abi::AbiEncode, + types::{Address, U256}, +}; +use jsonrpsee::types::ErrorObject; use serde::{Deserialize, Serialize}; +use serde_json::json; + +pub const MIN_INCLUSION_RATE_DENOMINATOR: u64 = 10; +pub const THROTTLING_SLACK: u64 = 10; +pub const BAN_SLACK: u64 = 50; +const ENTITY_BANNED_ERROR_CODE: i32 = -32504; +const STAKE_TOO_LOW_ERROR_CODE: i32 = -32505; + +pub type ReputationError = ErrorObject<'static>; #[derive(Clone, Copy, Educe, PartialEq, Eq, Serialize, Deserialize)] #[educe(Debug)] @@ -10,7 +23,7 @@ pub enum ReputationStatus { BANNED, } -#[derive(Clone, Copy, Educe, Serialize, Deserialize)] +#[derive(Clone, Copy, Educe, Eq, PartialEq, Serialize, Deserialize)] #[educe(Debug)] pub struct ReputationEntry { pub address: Address, @@ -19,10 +32,81 @@ pub struct ReputationEntry { pub status: ReputationStatus, } -#[derive(Clone, Copy, Educe, Serialize, Deserialize)] +#[derive(Clone, Copy, Educe, Eq, PartialEq, Serialize, Deserialize)] #[educe(Debug)] pub struct StakeInfo { pub address: Address, pub stake: U256, pub unstake_delay: U256, // seconds } + +pub enum BadReputationError { + EntityBanned { + address: Address, + title: String, + }, + StakeTooLow { + address: Address, + title: String, + stake: U256, + min_stake: U256, + min_unstake_delay: U256, + }, + UnstakeDelayTooLow { + address: Address, + title: String, + unstake_delay: U256, + min_stake: U256, + min_unstake_delay: U256, + }, +} + +impl From for ReputationError { + fn from(error: BadReputationError) -> Self { + match error { + BadReputationError::EntityBanned { address, title } => ReputationError::owned( + ENTITY_BANNED_ERROR_CODE, + format!("{title} with address {} is banned", address), + Some(json!({ + title: address.to_string(), + })), + ), + BadReputationError::StakeTooLow { + address, + title, + stake, + min_stake, + min_unstake_delay, + } => ReputationError::owned( + STAKE_TOO_LOW_ERROR_CODE, + format!( + "{title} with address {} stake {} is lower than {}", + address, stake, min_stake + ), + Some(json!({ + title: address.to_string(), + "minimumStake": AbiEncode::encode_hex(min_stake), + "minimumUnstakeDelay": AbiEncode::encode_hex(min_unstake_delay), + })), + ), + BadReputationError::UnstakeDelayTooLow { + address, + title, + unstake_delay, + min_stake, + min_unstake_delay, + } => ReputationError::owned( + STAKE_TOO_LOW_ERROR_CODE, + format!( + "{title} with address {} unstake delay {} is lower than {}", + address, unstake_delay, min_unstake_delay + ), + Some(json!({ + title: address.to_string(), + "minimumStake": AbiEncode::encode_hex(min_stake), + "minimumUnstakeDelay": AbiEncode::encode_hex(min_unstake_delay), + })), + ), + } + } +} diff --git a/src/uopool/memory_reputation.rs b/src/uopool/memory_reputation.rs index 7026979b..6e7b3643 100644 --- a/src/uopool/memory_reputation.rs +++ b/src/uopool/memory_reputation.rs @@ -1,19 +1,17 @@ use async_trait::async_trait; use educe::Educe; -use ethers::{ - abi::AbiEncode, - types::{Address, U256}, -}; +use ethers::types::{Address, U256}; use parking_lot::RwLock; -use serde_json::json; use std::{ collections::{HashMap, HashSet}, sync::Arc, }; -use crate::types::reputation::{ReputationEntry, ReputationStatus, StakeInfo}; +use crate::types::reputation::{ + BadReputationError, ReputationEntry, ReputationError, ReputationStatus, StakeInfo, +}; -use super::{Reputation, ReputationError, ENTITY_BANNED_ERROR_CODE, STAKE_TOO_LOW_ERROR_CODE}; +use super::Reputation; #[derive(Default, Educe)] #[educe(Debug)] @@ -169,44 +167,33 @@ impl Reputation for MemoryReputation { if let Some(entity) = entities.get(&stake_info.address) { let error = if entity.status == ReputationStatus::BANNED { - ReputationError::owned( - ENTITY_BANNED_ERROR_CODE, - format!("{title} with address {} is banned", stake_info.address), - Some(json!({ - title: stake_info.address.to_string(), - })), - ) + BadReputationError::EntityBanned { + address: stake_info.address, + title: title.to_string(), + } } else if stake_info.stake < self.min_stake { - ReputationError::owned( - STAKE_TOO_LOW_ERROR_CODE, - format!( - "{title} with address {} stake {} is lower than {}", - stake_info.address, stake_info.stake, self.min_stake - ), - Some(json!({ - title: stake_info.address.to_string(), - "minimumStake": AbiEncode::encode_hex(self.min_stake), - "minimumUnstakeDelay": AbiEncode::encode_hex(self.min_unstake_delay), - })), - ) + BadReputationError::StakeTooLow { + address: stake_info.address, + title: title.to_string(), + stake: stake_info.stake, + min_stake: self.min_stake, + min_unstake_delay: self.min_unstake_delay, + } } else if stake_info.unstake_delay < self.min_unstake_delay { - ReputationError::owned( - STAKE_TOO_LOW_ERROR_CODE, - format!( - "{title} with address {} unstake delay {} is lower than {}", - stake_info.address, stake_info.unstake_delay, self.min_unstake_delay - ), - Some(json!({ - title: stake_info.address.to_string(), - "minimumStake": AbiEncode::encode_hex(self.min_stake), - "minimumUnstakeDelay": AbiEncode::encode_hex(self.min_unstake_delay), - })), - ) + BadReputationError::UnstakeDelayTooLow { + address: stake_info.address, + title: title.to_string(), + unstake_delay: stake_info.unstake_delay, + min_stake: self.min_stake, + min_unstake_delay: self.min_unstake_delay, + } } else { return Ok(()); }; - return Err(anyhow::anyhow!(serde_json::to_string(&error)?)); + return Err(anyhow::anyhow!(serde_json::to_string( + &ReputationError::from(error) + )?)); } } @@ -233,4 +220,120 @@ impl Reputation for MemoryReputation { } } -// tests +#[cfg(test)] +mod tests { + use crate::uopool::{BAN_SLACK, MIN_INCLUSION_RATE_DENOMINATOR, THROTTLING_SLACK}; + + use super::*; + + #[tokio::test] + async fn memory_reputation() { + let mut reputation = MemoryReputation::default(); + reputation.init( + MIN_INCLUSION_RATE_DENOMINATOR, + THROTTLING_SLACK, + BAN_SLACK, + U256::from(1), + U256::from(0), + ); + + let mut addresses: Vec
= vec![]; + + for _ in 0..5 { + let address = Address::random(); + assert_eq!( + reputation.get(&address).await.unwrap(), + ReputationEntry { + address, + uo_seen: 0, + uo_included: 0, + status: ReputationStatus::OK, + } + ); + addresses.push(address); + } + + assert_eq!(reputation.add_whitelist(&addresses[2]).await.unwrap(), ()); + assert_eq!(reputation.add_blacklist(&addresses[1]).await.unwrap(), ()); + + assert_eq!(reputation.is_whitelist(&addresses[2]).await.unwrap(), true); + assert_eq!(reputation.is_whitelist(&addresses[1]).await.unwrap(), false); + assert_eq!(reputation.is_blacklist(&addresses[1]).await.unwrap(), true); + assert_eq!(reputation.is_blacklist(&addresses[2]).await.unwrap(), false); + + assert_eq!( + reputation.remove_whitelist(&addresses[2]).await.unwrap(), + true + ); + assert_eq!( + reputation.remove_whitelist(&addresses[1]).await.unwrap(), + false + ); + assert_eq!( + reputation.remove_blacklist(&addresses[1]).await.unwrap(), + true + ); + assert_eq!( + reputation.remove_blacklist(&addresses[2]).await.unwrap(), + false + ); + + assert_eq!(reputation.add_whitelist(&addresses[2]).await.unwrap(), ()); + assert_eq!(reputation.add_blacklist(&addresses[1]).await.unwrap(), ()); + + assert_eq!( + reputation.get_status(&addresses[2]).await.unwrap(), + ReputationStatus::OK + ); + assert_eq!( + reputation.get_status(&addresses[1]).await.unwrap(), + ReputationStatus::BANNED + ); + assert_eq!( + reputation.get_status(&addresses[3]).await.unwrap(), + ReputationStatus::OK + ); + + assert_eq!(reputation.increment_seen(&addresses[2]).await.unwrap(), ()); + assert_eq!(reputation.increment_seen(&addresses[2]).await.unwrap(), ()); + assert_eq!(reputation.increment_seen(&addresses[3]).await.unwrap(), ()); + assert_eq!(reputation.increment_seen(&addresses[3]).await.unwrap(), ()); + + assert_eq!( + reputation.increment_included(&addresses[2]).await.unwrap(), + () + ); + assert_eq!( + reputation.increment_included(&addresses[2]).await.unwrap(), + () + ); + assert_eq!( + reputation.increment_included(&addresses[3]).await.unwrap(), + () + ); + + assert_eq!( + reputation + .update_handle_ops_reverted(&addresses[3]) + .await + .unwrap(), + () + ); + + for _ in 0..250 { + assert_eq!(reputation.increment_seen(&addresses[3]).await.unwrap(), ()); + } + assert_eq!( + reputation.get_status(&addresses[3]).await.unwrap(), + ReputationStatus::THROTTLED + ); + + for _ in 0..500 { + assert_eq!(reputation.increment_seen(&addresses[3]).await.unwrap(), ()); + } + assert_eq!( + reputation.get_status(&addresses[3]).await.unwrap(), + ReputationStatus::BANNED + ); + } +} diff --git a/src/uopool/mod.rs b/src/uopool/mod.rs index e58a67fd..b0c4ae3d 100644 --- a/src/uopool/mod.rs +++ b/src/uopool/mod.rs @@ -1,6 +1,9 @@ use crate::{ types::{ - reputation::{ReputationEntry, ReputationStatus, StakeInfo}, + reputation::{ + ReputationEntry, ReputationStatus, StakeInfo, BAN_SLACK, + MIN_INCLUSION_RATE_DENOMINATOR, THROTTLING_SLACK, + }, user_operation::{UserOperation, UserOperationHash}, }, uopool::{ @@ -18,7 +21,7 @@ use ethers::{ types::{Address, H256, U256}, utils::keccak256, }; -use jsonrpsee::{tracing::info, types::ErrorObject}; +use jsonrpsee::tracing::info; use parking_lot::RwLock; use std::{collections::HashMap, fmt::Debug, net::SocketAddr, sync::Arc, time::Duration}; @@ -28,21 +31,14 @@ pub mod server; pub mod services; pub type MempoolId = H256; -pub type ReputationError = ErrorObject<'static>; -const MIN_INCLUSION_RATE_DENOMINATOR: u64 = 10; -const THROTTLING_SLACK: u64 = 10; -const BAN_SLACK: u64 = 50; -const ENTITY_BANNED_ERROR_CODE: i32 = -32504; -const STAKE_TOO_LOW_ERROR_CODE: i32 = -32505; +pub type MempoolBox = Box>; +pub type ReputationBox = Box>; pub fn mempool_id(entry_point: Address, chain_id: U256) -> MempoolId { H256::from_slice(keccak256([entry_point.encode(), chain_id.encode()].concat()).as_slice()) } -pub type MempoolBox = Box>; -pub type ReputationBox = Box>; - #[async_trait] pub trait Mempool: Debug + Send + Sync + 'static { type UserOperations: IntoIterator; From 80fd7e5f7cfcbc66794ed3250eff43b752d376bb Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Mon, 30 Jan 2023 10:42:55 +0100 Subject: [PATCH 18/20] chore: update Makefile --- .github/workflows/ci.yml | 4 ++-- Makefile | 12 +++++------- README.md | 6 ++++++ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3983f5e4..f1335899 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,9 +16,9 @@ jobs: with: toolchain: stable components: rustfmt, clippy - - name: Setup third party dependencies + - name: Setup third-party dependencies run: | - make fetch-thirdparty + make setup-thirdparty - name: Install Geth and solc run: | sudo add-apt-repository ppa:ethereum/ethereum diff --git a/Makefile b/Makefile index 67061d47..90fbf7b3 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,4 @@ build: - make fetch-thirdparty - cd thirdparty/account-abstraction && yarn install && yarn compile && cd ../.. cargo build run-bundler: @@ -15,18 +13,18 @@ run-bundler-rpc: run-create-wallet: cargo run --bin create-wallet -- --output-path ${HOME}/.aa-bundler -fetch-thirdparty: - git submodule update --init - -test: +setup-thirdparty: + git submodule update --init + cd thirdparty/account-abstraction && yarn install && yarn compile && cd ../.. cd thirdparty/bundler && yarn install && yarn preprocess && cd ../.. + +test: cargo test format: cargo fmt --all lint: - cd thirdparty/bundler && yarn install && yarn preprocess && cd ../.. cargo fmt --all -- --check cargo clippy -- -D warnings -A clippy::derive_partial_eq_without_eq -D clippy::unwrap_used diff --git a/README.md b/README.md index 6abeb609..5d1338d0 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,12 @@ For more information: https://hackmd.io/@Vid201/aa-bundler-rust ## How to run? +Set up third-party dependencies (EIP-4337 smart contracts and bundler tests): + +```bash +make setup-thirdparty +``` + Create wallet for bundler: ```bash From 6e1b20edef9adb1241236c16bcbb3774800d18c2 Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Mon, 30 Jan 2023 12:12:44 +0100 Subject: [PATCH 19/20] chore: fix lint --- Makefile | 2 +- src/types/reputation.rs | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 90fbf7b3..833fc631 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ format: lint: cargo fmt --all -- --check - cargo clippy -- -D warnings -A clippy::derive_partial_eq_without_eq -D clippy::unwrap_used + cargo clippy -- -D warnings -A clippy::derive_partial_eq_without_eq -D clippy::unwrap_used -D clippy::uninlined_format_args clean: cd thirdparty/account-abstraction && yarn clean && cd ../.. diff --git a/src/types/reputation.rs b/src/types/reputation.rs index f2d81562..0a94bce7 100644 --- a/src/types/reputation.rs +++ b/src/types/reputation.rs @@ -66,7 +66,7 @@ impl From for ReputationError { match error { BadReputationError::EntityBanned { address, title } => ReputationError::owned( ENTITY_BANNED_ERROR_CODE, - format!("{title} with address {} is banned", address), + format!("{title} with address {address} is banned",), Some(json!({ title: address.to_string(), })), @@ -80,8 +80,7 @@ impl From for ReputationError { } => ReputationError::owned( STAKE_TOO_LOW_ERROR_CODE, format!( - "{title} with address {} stake {} is lower than {}", - address, stake, min_stake + "{title} with address {address} stake {stake} is lower than {min_stake}", ), Some(json!({ title: address.to_string(), @@ -98,8 +97,7 @@ impl From for ReputationError { } => ReputationError::owned( STAKE_TOO_LOW_ERROR_CODE, format!( - "{title} with address {} unstake delay {} is lower than {}", - address, unstake_delay, min_unstake_delay + "{title} with address {address} unstake delay {unstake_delay} is lower than {min_unstake_delay}", ), Some(json!({ title: address.to_string(), From 93d95f47effa17b0e1ebf84df25a0466f97fe79c Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Mon, 30 Jan 2023 21:59:49 +0100 Subject: [PATCH 20/20] chore: fix debug clear --- src/uopool/services/uopool.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/uopool/services/uopool.rs b/src/uopool/services/uopool.rs index 85205367..7ceb99d1 100644 --- a/src/uopool/services/uopool.rs +++ b/src/uopool/services/uopool.rs @@ -97,7 +97,9 @@ impl UoPool for UoPoolService { mempool.clear(); } - self.reputations.write().clear(); + for reputation in self.reputations.write().values_mut() { + reputation.clear(); + } Ok(tonic::Response::new(ClearResponse { result: ClearResult::Cleared as i32,