Skip to content

Commit

Permalink
feat(rpc): include pending UOs in eth_getUserOperationByHash
Browse files Browse the repository at this point in the history
  • Loading branch information
dancoombs committed Jan 19, 2024
1 parent 5b5890d commit 98f7ae3
Show file tree
Hide file tree
Showing 13 changed files with 440 additions and 77 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions crates/pool/proto/op_pool/op_pool.proto
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ message MempoolOp {
// multiple UserOperations in the mempool, otherwise just one UserOperation is
// permitted
bool account_is_staked = 8;
// The entry point address of this operation
bytes entry_point = 9;
}

// Defines the gRPC endpoints for a UserOperation mempool service
Expand All @@ -125,6 +127,9 @@ service OpPool {
// Get up to `max_ops` from the mempool.
rpc GetOps (GetOpsRequest) returns (GetOpsResponse);

// Get a UserOperation by its hash
rpc GetOpByHash (GetOpByHashRequest) returns (GetOpByHashResponse);

// Removes UserOperations from the mempool
rpc RemoveOps(RemoveOpsRequest) returns (RemoveOpsResponse);

Expand All @@ -133,10 +138,13 @@ service OpPool {

// Clears the bundler mempool and reputation data of paymasters/accounts/factories/aggregators
rpc DebugClearState (DebugClearStateRequest) returns (DebugClearStateResponse);

// Dumps the current UserOperations mempool
rpc DebugDumpMempool (DebugDumpMempoolRequest) returns (DebugDumpMempoolResponse);

// Sets reputation of given addresses.
rpc DebugSetReputation (DebugSetReputationRequest) returns (DebugSetReputationResponse);

// Returns the reputation data of all observed addresses. Returns an array of
// reputation objects, each with the fields described above in
// debug_bundler_setReputation
Expand Down Expand Up @@ -197,6 +205,20 @@ message GetOpsSuccess {
repeated MempoolOp ops = 1;
}

message GetOpByHashRequest {
// The serialized UserOperation hash
bytes hash = 1;
}
message GetOpByHashResponse {
oneof result {
GetOpByHashSuccess success = 1;
MempoolError failure = 2;
}
}
message GetOpByHashSuccess {
MempoolOp op = 1;
}

message GetReputationStatusResponse {
oneof result {
GetReputationStatusSuccess success = 1;
Expand Down
6 changes: 6 additions & 0 deletions crates/pool/src/mempool/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ pub trait Mempool: Send + Sync + 'static {
/// Returns the all operations from the pool up to a max size
fn all_operations(&self, max: usize) -> Vec<Arc<PoolOperation>>;

/// Looks up a user operation by hash, returns None if not found
fn get_user_operation_by_hash(&self, hash: H256) -> Option<Arc<PoolOperation>>;

/// Debug methods
/// Clears the mempool
Expand Down Expand Up @@ -168,6 +171,8 @@ pub enum OperationOrigin {
pub struct PoolOperation {
/// The user operation stored in the pool
pub uo: UserOperation,
/// The entry point address for this operation
pub entry_point: Address,
/// The aggregator address for this operation, if any.
pub aggregator: Option<Address>,
/// The valid time range for this operation.
Expand Down Expand Up @@ -264,6 +269,7 @@ mod tests {
init_code: factory.as_fixed_bytes().into(),
..Default::default()
},
entry_point: Address::random(),
aggregator: Some(aggregator),
valid_time_range: ValidTimeRange::all_time(),
expected_code_hash: H256::random(),
Expand Down
33 changes: 33 additions & 0 deletions crates/pool/src/mempool/uo_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ where
let valid_time_range = sim_result.valid_time_range;
let pool_op = PoolOperation {
uo: op,
entry_point: self.config.entry_point,
aggregator: None,
valid_time_range,
expected_code_hash: sim_result.code_hash,
Expand Down Expand Up @@ -451,6 +452,10 @@ where
self.state.read().pool.best_operations().take(max).collect()
}

fn get_user_operation_by_hash(&self, hash: H256) -> Option<Arc<PoolOperation>> {
self.state.read().pool.get_operation_by_hash(hash)
}

fn clear(&self) {
self.state.write().pool.clear()
}
Expand Down Expand Up @@ -997,6 +1002,34 @@ mod tests {
check_ops(pool.best_operations(1, 0).unwrap(), vec![]);
}

#[tokio::test]
async fn test_get_user_op_by_hash() {
let op = create_op(Address::random(), 0, 0);
let pool = create_pool(vec![op.clone()]);

let hash = pool
.add_operation(OperationOrigin::Local, op.op.clone())
.await
.unwrap();

let pool_op = pool.get_user_operation_by_hash(hash).unwrap();
assert_eq!(pool_op.uo, op.op);
}

#[tokio::test]
async fn test_get_user_op_by_hash_not_found() {
let op = create_op(Address::random(), 0, 0);
let pool = create_pool(vec![op.clone()]);

let _ = pool
.add_operation(OperationOrigin::Local, op.op.clone())
.await
.unwrap();

let pool_op = pool.get_user_operation_by_hash(H256::random());
assert_eq!(pool_op, None);
}

#[derive(Clone, Debug)]
struct OpWithErrors {
op: UserOperation,
Expand Down
30 changes: 30 additions & 0 deletions crates/pool/src/server/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,15 @@ impl PoolServer for LocalPoolHandle {
}
}

async fn get_op_by_hash(&self, hash: H256) -> PoolResult<Option<PoolOperation>> {
let req = ServerRequestKind::GetOpByHash { hash };
let resp = self.send(req).await?;
match resp {
ServerResponse::GetOpByHash { op } => Ok(op),
_ => Err(PoolServerError::UnexpectedResponse),
}
}

async fn remove_ops(&self, entry_point: Address, ops: Vec<H256>) -> PoolResult<()> {
let req = ServerRequestKind::RemoveOps { entry_point, ops };
let resp = self.send(req).await?;
Expand Down Expand Up @@ -326,6 +335,15 @@ where
.collect())
}

fn get_op_by_hash(&self, hash: H256) -> PoolResult<Option<PoolOperation>> {
for mempool in self.mempools.values() {
if let Some(op) = mempool.get_user_operation_by_hash(hash) {
return Ok(Some((*op).clone()));
}
}
Ok(None)
}

fn remove_ops(&self, entry_point: Address, ops: &[H256]) -> PoolResult<()> {
let mempool = self.get_pool(entry_point)?;
mempool.remove_operations(ops);
Expand Down Expand Up @@ -441,6 +459,12 @@ where
Err(e) => Err(e),
}
},
ServerRequestKind::GetOpByHash { hash } => {
match self.get_op_by_hash(hash) {
Ok(op) => Ok(ServerResponse::GetOpByHash { op }),
Err(e) => Err(e),
}
}
ServerRequestKind::RemoveOps { entry_point, ops } => {
match self.remove_ops(entry_point, &ops) {
Ok(_) => Ok(ServerResponse::RemoveOps),
Expand Down Expand Up @@ -535,6 +559,9 @@ enum ServerRequestKind {
max_ops: u64,
shard_index: u64,
},
GetOpByHash {
hash: H256,
},
RemoveOps {
entry_point: Address,
ops: Vec<H256>,
Expand Down Expand Up @@ -576,6 +603,9 @@ enum ServerResponse {
GetOps {
ops: Vec<PoolOperation>,
},
GetOpByHash {
op: Option<PoolOperation>,
},
RemoveOps,
UpdateEntities,
DebugClearState,
Expand Down
5 changes: 5 additions & 0 deletions crates/pool/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ pub trait PoolServer: Send + Sync + 'static {
shard_index: u64,
) -> PoolResult<Vec<PoolOperation>>;

/// Get an operation from the pool by hash
/// Checks each entry point in order until the operation is found
/// Returns None if the operation is not found
async fn get_op_by_hash(&self, hash: H256) -> PoolResult<Option<PoolOperation>>;

/// Remove operations from the pool by hash
async fn remove_ops(&self, entry_point: Address, ops: Vec<H256>) -> PoolResult<()>;

Expand Down
39 changes: 33 additions & 6 deletions crates/pool/src/server/remote/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ use tonic_health::{

use super::protos::{
self, add_op_response, debug_clear_state_response, debug_dump_mempool_response,
debug_dump_reputation_response, debug_set_reputation_response, get_ops_response,
get_reputation_status_response, get_stake_status_response, op_pool_client::OpPoolClient,
remove_ops_response, update_entities_response, AddOpRequest, DebugClearStateRequest,
DebugDumpMempoolRequest, DebugDumpReputationRequest, DebugSetReputationRequest, GetOpsRequest,
GetReputationStatusRequest, GetStakeStatusRequest, RemoveOpsRequest, SubscribeNewHeadsRequest,
SubscribeNewHeadsResponse, UpdateEntitiesRequest,
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,
DebugClearStateRequest, DebugDumpMempoolRequest, DebugDumpReputationRequest,
DebugSetReputationRequest, GetOpsRequest, GetReputationStatusRequest, GetStakeStatusRequest,
RemoveOpsRequest, SubscribeNewHeadsRequest, SubscribeNewHeadsResponse, UpdateEntitiesRequest,
};
use crate::{
mempool::{PoolOperation, Reputation, StakeStatus},
Expand Down Expand Up @@ -188,6 +188,33 @@ impl PoolServer for RemotePoolClient {
}
}

async fn get_op_by_hash(&self, hash: H256) -> PoolResult<Option<PoolOperation>> {
let res = self
.op_pool_client
.clone()
.get_op_by_hash(protos::GetOpByHashRequest {
hash: hash.as_bytes().to_vec(),
})
.await?
.into_inner()
.result;

match res {
Some(get_op_by_hash_response::Result::Success(s)) => {
Ok(s.op.map(PoolOperation::try_from).transpose()?)
}
Some(get_op_by_hash_response::Result::Failure(e)) => match e.error {
Some(_) => Err(e.try_into()?),
None => Err(PoolServerError::Other(anyhow::anyhow!(
"should have received error from op pool"
)))?,
},
None => Err(PoolServerError::Other(anyhow::anyhow!(
"should have received result from op pool"
)))?,
}
}

async fn remove_ops(&self, entry_point: Address, ops: Vec<H256>) -> PoolResult<()> {
let res = self
.op_pool_client
Expand Down
2 changes: 1 addition & 1 deletion crates/pool/src/server/remote/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

mod client;
mod error;
#[allow(non_snake_case, unreachable_pub)]
#[allow(non_snake_case, unreachable_pub, clippy::large_enum_variant)]
mod protos;
mod server;

Expand Down
4 changes: 4 additions & 0 deletions crates/pool/src/server/remote/protos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ impl From<&PoolOperation> for MempoolOp {
fn from(op: &PoolOperation) -> Self {
MempoolOp {
uo: Some(UserOperation::from(&op.uo)),
entry_point: op.entry_point.as_bytes().to_vec(),
aggregator: op.aggregator.map_or(vec![], |a| a.as_bytes().to_vec()),
valid_after: op.valid_time_range.valid_after.seconds_since_epoch(),
valid_until: op.valid_time_range.valid_until.seconds_since_epoch(),
Expand All @@ -256,6 +257,8 @@ impl TryFrom<MempoolOp> for PoolOperation {
fn try_from(op: MempoolOp) -> Result<Self, Self::Error> {
let uo = op.uo.context(MISSING_USER_OP_ERR_STR)?.try_into()?;

let entry_point = from_bytes(&op.entry_point)?;

let aggregator: Option<Address> = if op.aggregator.is_empty() {
None
} else {
Expand All @@ -278,6 +281,7 @@ impl TryFrom<MempoolOp> for PoolOperation {

Ok(PoolOperation {
uo,
entry_point,
aggregator,
valid_time_range,
expected_code_hash,
Expand Down
44 changes: 36 additions & 8 deletions crates/pool/src/server/remote/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,21 @@ use tonic::{transport::Server, Request, Response, Result, Status};

use super::protos::{
add_op_response, debug_clear_state_response, debug_dump_mempool_response,
debug_dump_reputation_response, debug_set_reputation_response, get_ops_response,
get_reputation_status_response, get_stake_status_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,
DebugClearStateRequest, DebugClearStateResponse, DebugClearStateSuccess,
DebugDumpMempoolRequest, DebugDumpMempoolResponse, DebugDumpMempoolSuccess,
DebugDumpReputationRequest, DebugDumpReputationResponse, DebugDumpReputationSuccess,
DebugSetReputationRequest, DebugSetReputationResponse, DebugSetReputationSuccess,
GetOpsRequest, GetOpsResponse, GetOpsSuccess, GetReputationStatusRequest,
GetReputationStatusResponse, GetReputationStatusSuccess, GetStakeStatusRequest,
GetStakeStatusResponse, GetStakeStatusSuccess, GetSupportedEntryPointsRequest,
GetSupportedEntryPointsResponse, MempoolOp, RemoveOpsRequest, RemoveOpsResponse,
RemoveOpsSuccess, SubscribeNewHeadsRequest, SubscribeNewHeadsResponse, UpdateEntitiesRequest,
UpdateEntitiesResponse, UpdateEntitiesSuccess, OP_POOL_FILE_DESCRIPTOR_SET,
GetOpByHashRequest, GetOpByHashResponse, GetOpByHashSuccess, GetOpsRequest, GetOpsResponse,
GetOpsSuccess, GetReputationStatusRequest, GetReputationStatusResponse,
GetReputationStatusSuccess, GetStakeStatusRequest, GetStakeStatusResponse,
GetStakeStatusSuccess, GetSupportedEntryPointsRequest, GetSupportedEntryPointsResponse,
MempoolOp, RemoveOpsRequest, RemoveOpsResponse, RemoveOpsSuccess, SubscribeNewHeadsRequest,
SubscribeNewHeadsResponse, UpdateEntitiesRequest, UpdateEntitiesResponse,
UpdateEntitiesSuccess, OP_POOL_FILE_DESCRIPTOR_SET,
};
use crate::{
mempool::Reputation,
Expand Down Expand Up @@ -181,6 +182,33 @@ impl OpPool for OpPoolImpl {
Ok(Response::new(resp))
}

async fn get_op_by_hash(
&self,
request: Request<GetOpByHashRequest>,
) -> Result<Response<GetOpByHashResponse>> {
let req = request.into_inner();

if req.hash.len() != 32 {
return Err(Status::invalid_argument("Hash must be 32 bytes long"));
}
let hash = H256::from_slice(&req.hash);

let resp = match self.local_pool.get_op_by_hash(hash).await {
Ok(op) => GetOpByHashResponse {
result: Some(get_op_by_hash_response::Result::Success(
GetOpByHashSuccess {
op: op.map(|op| MempoolOp::from(&op)),
},
)),
},
Err(error) => GetOpByHashResponse {
result: Some(get_op_by_hash_response::Result::Failure(error.into())),
},
};

Ok(Response::new(resp))
}

async fn remove_ops(
&self,
request: Request<RemoveOpsRequest>,
Expand Down
5 changes: 5 additions & 0 deletions crates/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,8 @@ serde.workspace = true
strum.workspace = true
url.workspace = true
futures-util.workspace = true

[dev-dependencies]
mockall.workspace = true
rundler-provider = { path = "../provider", features = ["test-utils"]}
rundler-pool = { path = "../pool", features = ["test-utils"] }
Loading

0 comments on commit 98f7ae3

Please sign in to comment.