From 21797d59513d532928ef2b221b8fd1539d87639b Mon Sep 17 00:00:00 2001 From: Dan Coombs Date: Wed, 21 Feb 2024 10:32:27 -0500 Subject: [PATCH] feat: add debug rpc method to dump paymaster balances from pool (#609) --- crates/pool/proto/op_pool/op_pool.proto | 21 +++ crates/pool/src/mempool/mod.rs | 7 +- crates/pool/src/mempool/paymaster.rs | 11 ++ crates/pool/src/mempool/pool.rs | 4 + crates/pool/src/mempool/uo_pool.rs | 4 + crates/pool/src/server/local.rs | 128 +++++++++++++------ crates/pool/src/server/mod.rs | 34 +++-- crates/pool/src/server/remote/client.rs | 45 +++++-- crates/pool/src/server/remote/protos.rs | 27 +++- crates/pool/src/server/remote/server.rs | 51 ++++++-- crates/rpc/src/admin.rs | 22 ++-- crates/rpc/src/debug.rs | 34 ++++- crates/rpc/src/types.rs | 18 ++- docs/architecture/rpc.md | 163 ++++++++++++++++++++++-- 14 files changed, 465 insertions(+), 104 deletions(-) diff --git a/crates/pool/proto/op_pool/op_pool.proto b/crates/pool/proto/op_pool/op_pool.proto index d6f4fdea1..58e9c5952 100644 --- a/crates/pool/proto/op_pool/op_pool.proto +++ b/crates/pool/proto/op_pool/op_pool.proto @@ -150,6 +150,9 @@ service OpPool { // debug_bundler_setReputation rpc DebugDumpReputation(DebugDumpReputationRequest) returns (DebugDumpReputationResponse); + // Dumps the paymaster balances + rpc DebugDumpPaymasterBalances(DebugDumpPaymasterBalancesRequest) returns (DebugDumpPaymasterBalancesResponse); + // Get reputation status of address rpc GetReputationStatus(GetReputationStatusRequest) returns (GetReputationStatusResponse); @@ -347,6 +350,24 @@ message DebugDumpReputationSuccess { repeated Reputation reputations = 1; } +message DebugDumpPaymasterBalancesRequest { + bytes entry_point = 1; +} +message DebugDumpPaymasterBalancesResponse { + oneof result { + DebugDumpPaymasterBalancesSuccess success = 1; + MempoolError failure = 2; + } +} +message DebugDumpPaymasterBalancesSuccess { + repeated PaymasterBalance balances = 1; +} +message PaymasterBalance { + bytes address = 1; + bytes pending_balance = 2; + bytes confirmed_balance = 3; +} + message SubscribeNewHeadsRequest {} message SubscribeNewHeadsResponse { // The new chain head diff --git a/crates/pool/src/mempool/mod.rs b/crates/pool/src/mempool/mod.rs index d48d5cd42..3ab046e75 100644 --- a/crates/pool/src/mempool/mod.rs +++ b/crates/pool/src/mempool/mod.rs @@ -97,6 +97,9 @@ pub trait Mempool: Send + Sync + 'static { /// Dumps the mempool's reputation tracking fn dump_reputation(&self) -> Vec; + /// Dumps the mempool's paymaster balance cache + fn dump_paymaster_balances(&self) -> Vec; + /// Dumps the mempool's reputation tracking fn get_reputation_status(&self, address: Address) -> ReputationStatus; @@ -106,7 +109,7 @@ pub trait Mempool: Send + Sync + 'static { /// Get stake status for address async fn get_stake_status(&self, address: Address) -> MempoolResult; - /// Reset paymater state + /// Reset paymaster state async fn reset_confirmed_paymaster_balances(&self) -> MempoolResult<()>; /// Turns on and off tracking errors @@ -163,7 +166,7 @@ pub struct StakeStatus { #[derive(Debug, Clone, Copy)] pub struct StakeInfo { - /// Stake ammount + /// Stake amount pub stake: u128, /// Unstake delay in seconds pub unstake_delay_sec: u32, diff --git a/crates/pool/src/mempool/paymaster.rs b/crates/pool/src/mempool/paymaster.rs index e0f12f3bb..6a825a70f 100644 --- a/crates/pool/src/mempool/paymaster.rs +++ b/crates/pool/src/mempool/paymaster.rs @@ -124,6 +124,17 @@ impl PaymasterTracker { None } + pub(crate) fn dump_paymaster_metadata(&self) -> Vec { + self.paymaster_balances + .iter() + .map(|(address, balance)| PaymasterMetadata { + pending_balance: balance.pending_balance(), + confirmed_balance: balance.confirmed, + address: *address, + }) + .collect() + } + pub(crate) fn unmine_actual_cost(&mut self, paymaster: &Address, actual_cost: U256) { if let Some(paymaster_balance) = self.paymaster_balances.get_mut(paymaster) { paymaster_balance.confirmed = paymaster_balance.confirmed.saturating_add(actual_cost); diff --git a/crates/pool/src/mempool/pool.rs b/crates/pool/src/mempool/pool.rs index b7a54ce21..cb9ad98f4 100644 --- a/crates/pool/src/mempool/pool.rs +++ b/crates/pool/src/mempool/pool.rs @@ -353,6 +353,10 @@ impl PoolInner { self.paymaster_balances.paymaster_metadata(paymaster) } + pub(crate) fn dump_paymaster_metadata(&self) -> Vec { + self.paymaster_balances.dump_paymaster_metadata() + } + pub(crate) fn paymaster_exists(&self, paymaster: Address) -> bool { self.paymaster_balances.paymaster_exists(paymaster) } diff --git a/crates/pool/src/mempool/uo_pool.rs b/crates/pool/src/mempool/uo_pool.rs index a7a81f269..9954d043e 100644 --- a/crates/pool/src/mempool/uo_pool.rs +++ b/crates/pool/src/mempool/uo_pool.rs @@ -600,6 +600,10 @@ where self.reputation.dump_reputation() } + fn dump_paymaster_balances(&self) -> Vec { + self.state.read().pool.dump_paymaster_metadata() + } + fn get_reputation_status(&self, address: Address) -> ReputationStatus { self.reputation.status(address) } diff --git a/crates/pool/src/server/local.rs b/crates/pool/src/server/local.rs index a73c934c2..7abc931a7 100644 --- a/crates/pool/src/server/local.rs +++ b/crates/pool/src/server/local.rs @@ -11,7 +11,7 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -use std::{collections::HashMap, pin::Pin, sync::Arc}; +use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc}; use async_stream::stream; use async_trait::async_trait; @@ -29,7 +29,9 @@ use tracing::error; use super::{PoolResult, PoolServerError}; use crate::{ chain::ChainUpdate, - mempool::{Mempool, MempoolError, OperationOrigin, PoolOperation, StakeStatus}, + mempool::{ + Mempool, MempoolError, OperationOrigin, PaymasterMetadata, PoolOperation, StakeStatus, + }, server::{NewHead, PoolServer, Reputation}, ReputationStatus, }; @@ -254,6 +256,18 @@ impl PoolServer for LocalPoolHandle { } } + async fn debug_dump_paymaster_balances( + &self, + entry_point: Address, + ) -> PoolResult> { + let req = ServerRequestKind::DebugDumpPaymasterBalances { entry_point }; + let resp = self.send(req).await?; + match resp { + ServerResponse::DebugDumpPaymasterBalances { balances } => Ok(balances), + _ => Err(PoolServerError::UnexpectedResponse), + } + } + async fn get_stake_status( &self, entry_point: Address, @@ -438,6 +452,14 @@ where Ok(mempool.dump_reputation()) } + fn debug_dump_paymaster_balances( + &self, + entry_point: Address, + ) -> PoolResult> { + let mempool = self.get_pool(entry_point)?; + Ok(mempool.dump_paymaster_balances()) + } + fn get_reputation_status( &self, entry_point: Address, @@ -447,6 +469,28 @@ where Ok(mempool.get_reputation_status(address)) } + fn get_pool_and_spawn( + &self, + entry_point: Address, + response: oneshot::Sender>, + f: F, + ) where + F: FnOnce(Arc, oneshot::Sender>) -> Fut, + Fut: Future + Send + 'static, + { + match self.get_pool(entry_point) { + Ok(mempool) => { + let mempool = Arc::clone(mempool); + tokio::spawn(f(mempool, response)); + } + Err(e) => { + if let Err(e) = response.send(Err(e)) { + tracing::error!("Failed to send response: {:?}", e); + } + } + } + } + async fn run(&mut self, shutdown_token: CancellationToken) -> anyhow::Result<()> { loop { tokio::select! { @@ -473,29 +517,43 @@ where } Some(req) = self.req_receiver.recv() => { let resp = match req.request { + // Async methods + // Responses are sent in the spawned task + ServerRequestKind::AddOp { entry_point, op, origin } => { + let fut = |mempool: Arc, response: oneshot::Sender>| async move { + let resp = match mempool.add_operation(origin, op).await { + Ok(hash) => Ok(ServerResponse::AddOp { hash }), + Err(e) => Err(e.into()), + }; + if let Err(e) = response.send(resp) { + tracing::error!("Failed to send response: {:?}", e); + } + }; + + self.get_pool_and_spawn(entry_point, req.response, fut); + continue; + }, + ServerRequestKind::GetStakeStatus { entry_point, address }=> { + let fut = |mempool: Arc, response: oneshot::Sender>| async move { + let resp = match mempool.get_stake_status(address).await { + Ok(status) => Ok(ServerResponse::GetStakeStatus { status }), + Err(e) => Err(e.into()), + }; + if let Err(e) = response.send(resp) { + tracing::error!("Failed to send response: {:?}", e); + } + }; + self.get_pool_and_spawn(entry_point, req.response, fut); + continue; + }, + + // Sync methods + // Responses are sent in the main loop below ServerRequestKind::GetSupportedEntryPoints => { Ok(ServerResponse::GetSupportedEntryPoints { entry_points: self.mempools.keys().copied().collect() }) }, - ServerRequestKind::AddOp { entry_point, op, origin } => { - match self.get_pool(entry_point) { - Ok(mempool) => { - let mempool = Arc::clone(mempool); - tokio::spawn(async move { - let resp = match mempool.add_operation(origin, op).await { - Ok(hash) => Ok(ServerResponse::AddOp { hash }), - Err(e) => Err(e.into()), - }; - if let Err(e) = req.response.send(resp) { - tracing::error!("Failed to send response: {:?}", e); - } - }); - continue; - }, - Err(e) => Err(e), - } - }, ServerRequestKind::GetOps { entry_point, max_ops, shard_index } => { match self.get_ops(entry_point, max_ops, shard_index) { Ok(ops) => Ok(ServerResponse::GetOps { ops }), @@ -550,27 +608,15 @@ where Err(e) => Err(e), } }, - ServerRequestKind::GetReputationStatus{ entry_point, address } => { - match self.get_reputation_status(entry_point, address) { - Ok(status) => Ok(ServerResponse::GetReputationStatus { status }), + ServerRequestKind::DebugDumpPaymasterBalances { entry_point } => { + match self.debug_dump_paymaster_balances(entry_point) { + Ok(balances) => Ok(ServerResponse::DebugDumpPaymasterBalances { balances }), Err(e) => Err(e), } }, - ServerRequestKind::GetStakeStatus { entry_point, address }=> { - match self.get_pool(entry_point) { - Ok(mempool) => { - let mempool = Arc::clone(mempool); - tokio::spawn(async move { - let resp = match mempool.get_stake_status(address).await { - Ok(status) => Ok(ServerResponse::GetStakeStatus { status }), - Err(e) => Err(e.into()), - }; - if let Err(e) = req.response.send(resp) { - tracing::error!("Failed to send response: {:?}", e); - } - }); - continue; - }, + ServerRequestKind::GetReputationStatus{ entry_point, address } => { + match self.get_reputation_status(entry_point, address) { + Ok(status) => Ok(ServerResponse::GetReputationStatus { status }), Err(e) => Err(e), } }, @@ -639,6 +685,9 @@ enum ServerRequestKind { DebugDumpReputation { entry_point: Address, }, + DebugDumpPaymasterBalances { + entry_point: Address, + }, GetReputationStatus { entry_point: Address, address: Address, @@ -675,6 +724,9 @@ enum ServerResponse { DebugDumpReputation { reputations: Vec, }, + DebugDumpPaymasterBalances { + balances: Vec, + }, GetReputationStatus { status: ReputationStatus, }, diff --git a/crates/pool/src/server/mod.rs b/crates/pool/src/server/mod.rs index 5913c38b0..41757d050 100644 --- a/crates/pool/src/server/mod.rs +++ b/crates/pool/src/server/mod.rs @@ -29,7 +29,7 @@ pub use remote::RemotePoolClient; use rundler_types::{EntityUpdate, UserOperation}; use crate::{ - mempool::{PoolOperation, Reputation, StakeStatus}, + mempool::{PaymasterMetadata, PoolOperation, Reputation, StakeStatus}, ReputationStatus, }; @@ -90,6 +90,20 @@ pub trait PoolServer: Send + Sync + 'static { /// has processed all operations up to that head. async fn subscribe_new_heads(&self) -> PoolResult + Send>>>; + /// Get reputation status given entrypoint and address + async fn get_reputation_status( + &self, + entry_point: Address, + address: Address, + ) -> PoolResult; + + /// Get stake status given entrypoint and address + async fn get_stake_status( + &self, + entry_point: Address, + address: Address, + ) -> PoolResult; + /// Clear the pool state, used for debug methods async fn debug_clear_state( &self, @@ -108,22 +122,14 @@ pub trait PoolServer: Send + Sync + 'static { reputations: Vec, ) -> PoolResult<()>; - /// Get reputation status given entrypoint and address - async fn get_reputation_status( - &self, - entry_point: Address, - address: Address, - ) -> PoolResult; + /// Dump reputations for entities, used for debug methods + async fn debug_dump_reputation(&self, entry_point: Address) -> PoolResult>; - /// Get stake status given entrypoint and address - async fn get_stake_status( + /// Dump paymaster balances, used for debug methods + async fn debug_dump_paymaster_balances( &self, entry_point: Address, - address: Address, - ) -> PoolResult; - - /// Dump reputations for entities, used for debug methods - async fn debug_dump_reputation(&self, entry_point: Address) -> PoolResult>; + ) -> PoolResult>; /// Controls whether or not the certain tracking data structures are used to block user operations async fn admin_set_tracking( diff --git a/crates/pool/src/server/remote/client.rs b/crates/pool/src/server/remote/client.rs index 2b79479c3..6d481313c 100644 --- a/crates/pool/src/server/remote/client.rs +++ b/crates/pool/src/server/remote/client.rs @@ -34,16 +34,17 @@ use tonic_health::{ use super::protos::{ self, add_op_response, admin_set_tracking_response, debug_clear_state_response, - debug_dump_mempool_response, debug_dump_reputation_response, debug_set_reputation_response, - get_op_by_hash_response, get_ops_response, get_reputation_status_response, - get_stake_status_response, op_pool_client::OpPoolClient, remove_ops_response, - update_entities_response, AddOpRequest, AdminSetTrackingRequest, DebugClearStateRequest, - DebugDumpMempoolRequest, DebugDumpReputationRequest, DebugSetReputationRequest, GetOpsRequest, - GetReputationStatusRequest, GetStakeStatusRequest, RemoveOpsRequest, SubscribeNewHeadsRequest, - SubscribeNewHeadsResponse, UpdateEntitiesRequest, + debug_dump_mempool_response, debug_dump_paymaster_balances_response, + debug_dump_reputation_response, debug_set_reputation_response, get_op_by_hash_response, + get_ops_response, get_reputation_status_response, get_stake_status_response, + op_pool_client::OpPoolClient, remove_ops_response, update_entities_response, AddOpRequest, + AdminSetTrackingRequest, DebugClearStateRequest, DebugDumpMempoolRequest, + DebugDumpPaymasterBalancesRequest, DebugDumpReputationRequest, DebugSetReputationRequest, + GetOpsRequest, GetReputationStatusRequest, GetStakeStatusRequest, RemoveOpsRequest, + SubscribeNewHeadsRequest, SubscribeNewHeadsResponse, UpdateEntitiesRequest, }; use crate::{ - mempool::{PoolOperation, Reputation, StakeStatus}, + mempool::{PaymasterMetadata, PoolOperation, Reputation, StakeStatus}, server::{error::PoolServerError, NewHead, PoolResult, PoolServer}, ReputationStatus, }; @@ -397,6 +398,34 @@ impl PoolServer for RemotePoolClient { } } + async fn debug_dump_paymaster_balances( + &self, + entry_point: Address, + ) -> PoolResult> { + let res = self + .op_pool_client + .clone() + .debug_dump_paymaster_balances(DebugDumpPaymasterBalancesRequest { + entry_point: entry_point.as_bytes().to_vec(), + }) + .await? + .into_inner() + .result; + + match res { + Some(debug_dump_paymaster_balances_response::Result::Success(s)) => s + .balances + .into_iter() + .map(PaymasterMetadata::try_from) + .map(|res| res.map_err(PoolServerError::from)) + .collect(), + Some(debug_dump_paymaster_balances_response::Result::Failure(f)) => Err(f.try_into()?), + None => Err(PoolServerError::Other(anyhow::anyhow!( + "should have received result from op pool" + )))?, + } + } + async fn get_reputation_status( &self, entry_point: Address, diff --git a/crates/pool/src/server/remote/protos.rs b/crates/pool/src/server/remote/protos.rs index 0a2bc8486..a19ad9f73 100644 --- a/crates/pool/src/server/remote/protos.rs +++ b/crates/pool/src/server/remote/protos.rs @@ -22,8 +22,9 @@ use rundler_types::{ use crate::{ mempool::{ - PoolOperation, Reputation as PoolReputation, ReputationStatus as PoolReputationStatus, - StakeInfo as RundlerStakeInfo, StakeStatus as RundlerStakeStatus, + PaymasterMetadata as PoolPaymasterMetadata, PoolOperation, Reputation as PoolReputation, + ReputationStatus as PoolReputationStatus, StakeInfo as RundlerStakeInfo, + StakeStatus as RundlerStakeStatus, }, server::NewHead as PoolNewHead, }; @@ -328,3 +329,25 @@ impl From for NewHead { } } } + +impl TryFrom for PoolPaymasterMetadata { + type Error = ConversionError; + + fn try_from(paymaster_balance: PaymasterBalance) -> Result { + Ok(Self { + address: from_bytes(&paymaster_balance.address)?, + confirmed_balance: from_bytes(&paymaster_balance.confirmed_balance)?, + pending_balance: from_bytes(&paymaster_balance.pending_balance)?, + }) + } +} + +impl From for PaymasterBalance { + fn from(paymaster_metadata: PoolPaymasterMetadata) -> Self { + Self { + address: paymaster_metadata.address.as_bytes().to_vec(), + confirmed_balance: to_le_bytes(paymaster_metadata.confirmed_balance), + pending_balance: to_le_bytes(paymaster_metadata.pending_balance), + } + } +} diff --git a/crates/pool/src/server/remote/server.rs b/crates/pool/src/server/remote/server.rs index 19d195a56..87c69867b 100644 --- a/crates/pool/src/server/remote/server.rs +++ b/crates/pool/src/server/remote/server.rs @@ -31,23 +31,25 @@ use tonic::{transport::Server, Request, Response, Result, Status}; use super::protos::{ add_op_response, admin_set_tracking_response, debug_clear_state_response, - debug_dump_mempool_response, debug_dump_reputation_response, debug_set_reputation_response, - get_op_by_hash_response, get_ops_response, get_reputation_status_response, - get_stake_status_response, + debug_dump_mempool_response, debug_dump_paymaster_balances_response, + debug_dump_reputation_response, debug_set_reputation_response, get_op_by_hash_response, + get_ops_response, get_reputation_status_response, get_stake_status_response, op_pool_server::{OpPool, OpPoolServer}, remove_ops_response, update_entities_response, AddOpRequest, AddOpResponse, AddOpSuccess, AdminSetTrackingRequest, AdminSetTrackingResponse, AdminSetTrackingSuccess, DebugClearStateRequest, DebugClearStateResponse, DebugClearStateSuccess, DebugDumpMempoolRequest, DebugDumpMempoolResponse, DebugDumpMempoolSuccess, - DebugDumpReputationRequest, DebugDumpReputationResponse, DebugDumpReputationSuccess, - DebugSetReputationRequest, DebugSetReputationResponse, DebugSetReputationSuccess, - GetOpByHashRequest, GetOpByHashResponse, GetOpByHashSuccess, GetOpsRequest, GetOpsResponse, - GetOpsSuccess, GetReputationStatusRequest, GetReputationStatusResponse, - GetReputationStatusSuccess, GetStakeStatusRequest, GetStakeStatusResponse, - GetStakeStatusSuccess, GetSupportedEntryPointsRequest, GetSupportedEntryPointsResponse, - MempoolOp, RemoveOpsRequest, RemoveOpsResponse, RemoveOpsSuccess, ReputationStatus, - SubscribeNewHeadsRequest, SubscribeNewHeadsResponse, UpdateEntitiesRequest, - UpdateEntitiesResponse, UpdateEntitiesSuccess, OP_POOL_FILE_DESCRIPTOR_SET, + DebugDumpPaymasterBalancesRequest, DebugDumpPaymasterBalancesResponse, + DebugDumpPaymasterBalancesSuccess, DebugDumpReputationRequest, DebugDumpReputationResponse, + DebugDumpReputationSuccess, DebugSetReputationRequest, DebugSetReputationResponse, + DebugSetReputationSuccess, GetOpByHashRequest, GetOpByHashResponse, GetOpByHashSuccess, + GetOpsRequest, GetOpsResponse, GetOpsSuccess, GetReputationStatusRequest, + GetReputationStatusResponse, GetReputationStatusSuccess, GetStakeStatusRequest, + GetStakeStatusResponse, GetStakeStatusSuccess, GetSupportedEntryPointsRequest, + GetSupportedEntryPointsResponse, MempoolOp, RemoveOpsRequest, RemoveOpsResponse, + RemoveOpsSuccess, ReputationStatus, SubscribeNewHeadsRequest, SubscribeNewHeadsResponse, + UpdateEntitiesRequest, UpdateEntitiesResponse, UpdateEntitiesSuccess, + OP_POOL_FILE_DESCRIPTOR_SET, }; use crate::{ mempool::Reputation, @@ -457,6 +459,31 @@ impl OpPool for OpPoolImpl { Ok(Response::new(resp)) } + async fn debug_dump_paymaster_balances( + &self, + request: Request, + ) -> Result> { + let req = request.into_inner(); + let ep = self.get_entry_point(&req.entry_point)?; + + let resp = match self.local_pool.debug_dump_paymaster_balances(ep).await { + Ok(balances) => DebugDumpPaymasterBalancesResponse { + result: Some(debug_dump_paymaster_balances_response::Result::Success( + DebugDumpPaymasterBalancesSuccess { + balances: balances.into_iter().map(Into::into).collect(), + }, + )), + }, + Err(error) => DebugDumpPaymasterBalancesResponse { + result: Some(debug_dump_paymaster_balances_response::Result::Failure( + error.into(), + )), + }, + }; + + Ok(Response::new(resp)) + } + type SubscribeNewHeadsStream = UnboundedReceiverStream>; async fn subscribe_new_heads( diff --git a/crates/rpc/src/admin.rs b/crates/rpc/src/admin.rs index eb9fd793d..958ceafc1 100644 --- a/crates/rpc/src/admin.rs +++ b/crates/rpc/src/admin.rs @@ -26,13 +26,13 @@ use crate::{ pub trait AdminApi { /// Clears the state of the mempool field if name is true #[method(name = "clearState")] - async fn admin_clear_state(&self, clear_params: RpcAdminClearState) -> RpcResult; + async fn clear_state(&self, clear_params: RpcAdminClearState) -> RpcResult; - /// Clears paymaster tracker state. + /// Sets the tracking state for the paymaster and reputation pool modules #[method(name = "setTracking")] - async fn admin_set_tracking( + async fn set_tracking( &self, - entrypoint: Address, + entry_point: Address, tracking_info: RpcAdminSetTracking, ) -> RpcResult; } @@ -52,13 +52,13 @@ impl

AdminApiServer for AdminApi

where P: PoolServer, { - async fn admin_clear_state(&self, clear_params: RpcAdminClearState) -> RpcResult { + async fn clear_state(&self, clear_params: RpcAdminClearState) -> RpcResult { let _ = self .pool .debug_clear_state( - clear_params.clear_mempool, - clear_params.clear_paymaster, - clear_params.clear_reputation, + clear_params.clear_mempool.unwrap_or(false), + clear_params.clear_paymaster.unwrap_or(false), + clear_params.clear_reputation.unwrap_or(false), ) .await .map_err(|e| rpc_err(INTERNAL_ERROR_CODE, e.to_string()))?; @@ -66,15 +66,15 @@ where Ok("ok".to_string()) } - async fn admin_set_tracking( + async fn set_tracking( &self, - entrypoint: Address, + entry_point: Address, tracking_params: RpcAdminSetTracking, ) -> RpcResult { let _ = self .pool .admin_set_tracking( - entrypoint, + entry_point, tracking_params.paymaster_tracking, tracking_params.reputation_tracking, ) diff --git a/crates/rpc/src/debug.rs b/crates/rpc/src/debug.rs index efb36fdc6..554c4711c 100644 --- a/crates/rpc/src/debug.rs +++ b/crates/rpc/src/debug.rs @@ -21,7 +21,8 @@ use rundler_pool::PoolServer; use crate::{ error::rpc_err, types::{ - RpcReputationInput, RpcReputationOutput, RpcStakeInfo, RpcStakeStatus, RpcUserOperation, + RpcDebugPaymasterBalance, RpcReputationInput, RpcReputationOutput, RpcStakeInfo, + RpcStakeStatus, RpcUserOperation, }, }; @@ -72,6 +73,13 @@ pub trait DebugApi { entry_point: Address, address: Address, ) -> RpcResult; + + /// Dumps the paymaster balance cache + #[method(name = "bundler_dumpPaymasterBalances")] + async fn bundler_dump_paymaster_balances( + &self, + entry_point: Address, + ) -> RpcResult>; } pub(crate) struct DebugApi { @@ -231,4 +239,28 @@ where }, }) } + + async fn bundler_dump_paymaster_balances( + &self, + entry_point: Address, + ) -> RpcResult> { + let result = self + .pool + .debug_dump_paymaster_balances(entry_point) + .await + .map_err(|e| rpc_err(INTERNAL_ERROR_CODE, e.to_string()))?; + + let mut results = Vec::new(); + for b in result { + let balance = RpcDebugPaymasterBalance { + address: b.address, + pending_balance: b.pending_balance, + confirmed_balance: b.confirmed_balance, + }; + + results.push(balance); + } + + Ok(results) + } } diff --git a/crates/rpc/src/types.rs b/crates/rpc/src/types.rs index 4acc44d50..30c182858 100644 --- a/crates/rpc/src/types.rs +++ b/crates/rpc/src/types.rs @@ -241,9 +241,21 @@ pub struct RpcAdminSetTracking { #[serde(rename_all = "camelCase")] pub struct RpcAdminClearState { /// Field to set whether to clear entire mempool - pub clear_mempool: bool, + pub clear_mempool: Option, /// Field to set whether to clear paymaster state - pub clear_paymaster: bool, + pub clear_paymaster: Option, /// Field to set whether to clear reputation state - pub clear_reputation: bool, + pub clear_reputation: Option, +} + +/// Paymaster balance +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RpcDebugPaymasterBalance { + /// Paymaster address + pub address: Address, + /// Paymaster balance including pending UOs in pool + pub pending_balance: U256, + /// Paymaster confirmed balance onchain + pub confirmed_balance: U256, } diff --git a/docs/architecture/rpc.md b/docs/architecture/rpc.md index 3a8ec679e..795ec14dd 100644 --- a/docs/architecture/rpc.md +++ b/docs/architecture/rpc.md @@ -1,10 +1,11 @@ # RPC Task -The `RPC` task is the main interface into the Rundler. It consists of 3 namespaces: +The `RPC` task is the main interface into the Rundler. It consists of 4 namespaces: -- **eth** -- **debug** -- **rundler** +- [**eth**](#eth_-namespace) +- [**debug**](#debug_-namespace) +- [**rundler**](#rundler_-namespace) +- [**admin**](#admin_-namespace) Each of which can be enabled/disabled via configuration. @@ -29,18 +30,80 @@ Methods defined by the [ERC-4337 spec](https://github.com/eth-infinitism/account Method defined by the [ERC-4337 spec](https://github.com/eth-infinitism/account-abstraction/blob/develop/erc/ERCS/erc-4337.md#rpc-methods-debug-namespace). Used only for debugging/testing and should be disabled on production APIs. -| Method | Supported | -| ------ | :-----------: | -| `debug_clearState` | ✅ | -| `debug_dumpMempool` | ✅ | -| `debug_sendBundleNow` | ✅ | -| `debug_setBundlingMode` | ✅ | -| `debug_setReputation` | ✅ | -| `debug_dumpReputation` | ✅ | +| Method | Supported | Non-Standard | +| ------ | :-----------: | :--: | +| `debug_bundler_clearState` | ✅ | +| `debug_bundler_dumpMempool` | ✅ | +| `debug_bundler_sendBundleNow` | ✅ | +| `debug_bundler_setBundlingMode` | ✅ | +| `debug_bundler_setReputation` | ✅ | +| `debug_bundler_dumpReputation` | ✅ | +| `debug_bundler_addUserOps` | 🚧 | | +| [`debug_bundler_clearMempool`](#debug_bundler_clearMempool) | ✅ | ✅ +| [`debug_bundler_dumpPaymasterBalances`](#debug_bundler_dumpPaymasterBalances) | ✅ | ✅ + +#### `debug_bundler_clearMempool` + +This method is used by the ERC-4337 `bundler-spec-tests` but is not (yet) part of the standard. + +This method triggers a the mempool to drop all pending user operations, but keeps the rest of its state. In contrast to `debug_bundler_clearState` which drops all state. + +##### Parameters + +- Entry point address + +``` +# Request +{ + "jsonrpc": "2.0", + "id": 1, + "method": "debug_bundler_clearMempool", + "params": ["0x...."] // entry point address +} + +# Response +{ + "jsonrpc": "2.0", + "id": 1, + "result": "ok" +} +``` + +#### `debug_bundler_dumpPaymasterBalances` + +Dump the paymaster balances from the paymaster tracker in the mempool for a given entry point. + +##### Parameters + +- Entry point address + +``` +# Request +{ + "jsonrpc": "2.0", + "id": 1, + "method": "debug_bundler_clearMempool", + "params": ["0x...."] // entry point address +} + +# Response +{ + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + address: address // paymaster address + pendingBalance: uint256 // paymaster balance including pending UOs in pool + confirmedBalance: uint256 // paymaster confirmed balance onchain + }, + { ... }, ... + ] +} +``` ### `rundler_` Namespace -Rundler specific methods that are not specified by the ERC-4337 spec. +Rundler specific methods that are not specified by the ERC-4337 spec. This namespace may be opened publicly. | Method | Supported | | ------ | :-----------: | @@ -52,6 +115,80 @@ This method returns the minimum `maxPriorityFeePerGas` that the bundler will acc Users of this method should typically increase their priority fee values by a buffer value in order to handle price fluctuations. +### `admin_` Namespace + +Administration methods specific to Rundler. This namespace should not be open to the public. + +| Method | +| ------ | +| [`admin_clearState`](#admin_clearState) | +| [`admin_setTracking`](#admin_settracking) | + +#### `admin_clearState` + +Clears the state of various Rundler components associated with an entry point address. + +##### Parameters + +- Entry point address +- Admin clear state object + +``` +# Request +{ + "jsonrpc": "2.0", + "id": 1, + "method": "admin_clearState", + "params": [ + "0x....", // entry point address + { + clearMempool: bool, // optional, clears the UOs from the pool + clearPaymaster: bool, // optional, clears the paymaster balances + clearReputation: bool // optional, clears the reputation manager + } + ] +} + +# Response +{ + "jsonrpc": "2.0", + "id": 1, + "result": "ok" +} +``` + +#### `admin_setTracking` + +Turns various mempool features on/off. + +##### Parameters + +- Entry point address +- Admin set tracking object + +``` +# Request +{ + "jsonrpc": "2.0", + "id": 1, + "method": "admin_clearState", + "params": [ + "0x....", // entry point address + { + paymasterTracking: bool, // required, enables paymaster balance tracking/enforcement + reputationTracking: bool, // required, enables reputation tracking/enforcement + } + ] +} + +# Response +{ + "jsonrpc": "2.0", + "id": 1, + "result": "ok" +} +``` + ### Health Check The health check endpoint can be used by infrastructure to ensure that Rundler is up and running.