Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions packages/api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,11 @@ export class ApiApp {
RpcErrorCodes.MISSING_AUTH_HEADERS
);
}
result = await this.ethApi.getUserOperationReceipt(params[0], sdAuthHeaders);
result = await this.ethApi.getUserOperationReceipt(
params[0],
sdAuthHeaders,
{ id, jsonrpc }
);
break;
case BundlerRPCMethods.eth_getUserOperationByHash:
if (!sdAuthHeaders || !isSDAuthHeaders(sdAuthHeaders)) {
Expand All @@ -312,7 +316,11 @@ export class ApiApp {
RpcErrorCodes.MISSING_AUTH_HEADERS
);
}
result = await this.ethApi.getUserOperationByHash(params[0], sdAuthHeaders);
result = await this.ethApi.getUserOperationByHash(
params[0],
sdAuthHeaders,
{ id, jsonrpc }
);
break;
case BundlerRPCMethods.web3_clientVersion:
result = this.web3Api.clientVersion();
Expand Down
11 changes: 9 additions & 2 deletions packages/api/src/modules/eth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Eth } from "@skandha/executor/lib/modules/eth";
import {
EstimatedUserOperationGas,
RpcRequestMetadata,
SDAuthHeaders,
UserOperationByHashResponse,
UserOperationReceipt,
Expand Down Expand Up @@ -50,26 +51,32 @@ export class EthAPI {
/**
*
* @param hash user op hash
* @param sdAuthHeaders Silent Data authentication headers
* @param requestMetadata Original RPC request metadata (id and jsonrpc version)
* @returns null in case the UserOperation is not yet included in a block, or a full UserOperation,
* with the addition of entryPoint, blockNumber, blockHash and transactionHash
*/
async getUserOperationByHash(
hash: string,
sdAuthHeaders: SDAuthHeaders,
requestMetadata: RpcRequestMetadata,
): Promise<UserOperationByHashResponse | null> {
return await this.ethModule.getUserOperationByHash(hash, sdAuthHeaders);
return await this.ethModule.getUserOperationByHash(hash, sdAuthHeaders, requestMetadata);
}

/**
*
* @param hash user op hash
* @param sdAuthHeaders Silent Data authentication headers
* @param requestMetadata Original RPC request metadata (id and jsonrpc version)
* @returns a UserOperation receipt
*/
async getUserOperationReceipt(
hash: string,
sdAuthHeaders: SDAuthHeaders,
requestMetadata: RpcRequestMetadata,
): Promise<UserOperationReceipt | null> {
return await this.ethModule.getUserOperationReceipt(hash, sdAuthHeaders);
return await this.ethModule.getUserOperationReceipt(hash, sdAuthHeaders, requestMetadata);
}

/**
Expand Down
9 changes: 7 additions & 2 deletions packages/executor/src/modules/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import RpcError from "@skandha/types/lib/api/errors/rpc-error";
import * as RpcErrorCodes from "@skandha/types/lib/api/errors/rpc-error-codes";
import {
EstimatedUserOperationGas,
RpcRequestMetadata,
SDAuthHeaders,
UserOperationByHashResponse,
UserOperationReceipt,
Expand Down Expand Up @@ -481,6 +482,7 @@ export class Eth {
async getUserOperationByHash(
hash: string,
sdAuthHeaders: SDAuthHeaders,
requestMetadata: RpcRequestMetadata,
): Promise<UserOperationByHashResponse | null> {
// Silent Data Auth is checked in the chain RPC, mempool check not supported
// const entry = await this.mempoolService.getEntryByHash(hash);
Expand All @@ -502,7 +504,7 @@ export class Eth {
// }
// }
const rpcUserOp = await this.entryPointService.getUserOperationByHash(
hash, sdAuthHeaders,
hash, sdAuthHeaders, requestMetadata,
);
if (!rpcUserOp && this.blockscoutApi) {
return await this.blockscoutApi.getUserOperationByHash(hash);
Expand All @@ -513,14 +515,17 @@ export class Eth {
/**
*
* @param hash user op hash
* @param sdAuthHeaders Silent Data authentication headers
* @param requestMetadata Original RPC request metadata (id and jsonrpc version)
* @returns a UserOperation receipt
*/
async getUserOperationReceipt(
hash: string,
sdAuthHeaders: SDAuthHeaders,
requestMetadata: RpcRequestMetadata,
): Promise<UserOperationReceipt | null> {
const rpcUserOp = await this.entryPointService.getUserOperationReceipt(
hash, sdAuthHeaders,
hash, sdAuthHeaders, requestMetadata,
);
if (!rpcUserOp && this.blockscoutApi) {
return await this.blockscoutApi.getUserOperationReceipt(hash);
Expand Down
7 changes: 5 additions & 2 deletions packages/executor/src/services/EntryPointService/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { UserOperation } from "@skandha/types/lib/contracts/UserOperation";
import { IDbController, Logger } from "@skandha/types/lib";
import {
RpcRequestMetadata,
SDAuthHeaders,
UserOperationByHashResponse,
UserOperationReceipt,
Expand Down Expand Up @@ -42,6 +43,7 @@ export class EntryPointService {
async getUserOperationByHash(
userOpHash: string,
sdAuthHeaders: SDAuthHeaders,
requestMetadata: RpcRequestMetadata,
): Promise<UserOperationByHashResponse | null> {
if (!userOpHash) {
throw new RpcError(
Expand All @@ -51,7 +53,7 @@ export class EntryPointService {
}
for (const [_, entryPoint] of Object.entries(this.entryPoints)) {
try {
const res = entryPoint.getUserOperationByHash(userOpHash, sdAuthHeaders);
const res = entryPoint.getUserOperationByHash(userOpHash, sdAuthHeaders, requestMetadata);
if (res) return res;
} catch (err) {
/* empty */
Expand All @@ -63,6 +65,7 @@ export class EntryPointService {
async getUserOperationReceipt(
userOpHash: string,
sdAuthHeaders: SDAuthHeaders,
requestMetadata: RpcRequestMetadata,
): Promise<UserOperationReceipt | null> {
if (!userOpHash) {
throw new RpcError(
Expand All @@ -72,7 +75,7 @@ export class EntryPointService {
}
for (const [_, entryPoint] of Object.entries(this.entryPoints)) {
try {
const res = entryPoint.getUserOperationReceipt(userOpHash, sdAuthHeaders);
const res = entryPoint.getUserOperationReceipt(userOpHash, sdAuthHeaders, requestMetadata);
if (res) return res;
} catch (err) {
/* empty */
Expand Down
95 changes: 68 additions & 27 deletions packages/executor/src/services/EntryPointService/versions/0.0.7.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { IEntryPointSimulations__factory } from "@skandha/types/lib/contracts/EP
import { hexlify, arrayify } from "ethers/lib/utils";
import { Logger } from "@skandha/types/lib";
import {
RpcRequestMetadata,
UserOperationReceipt,
UserOperationByHashResponse,
SDAuthHeaders,
Expand Down Expand Up @@ -59,6 +60,8 @@ import {
toHex,
createPublicClient,
http,
toEventHash,
decodeEventLog,
} from "viem";
import { USER_OPERATION_EVENT_HASH, UserOperationEventAbi } from '@skandha/types/lib/executor/abis'
import { isPrivateEventLog, unwrapPrivateEvent } from '../../../utils/unwrapPrivateEvent'
Expand Down Expand Up @@ -252,32 +255,41 @@ export class EntryPointV7Service implements IEntryPointService {
async getUserOperationEvent(
userOpHash: Hex,
sdAuthHeaders: SDAuthHeaders,
requestMetadata: RpcRequestMetadata,
) {
const {
'x-from-block': headerFromBlock,
...headersToForward
} = sdAuthHeaders

try {
// Validate x-from-block header is present (required for signature verification)
if (!headerFromBlock) {
throw new RpcError(
"Missing x-from-block header. This header is required for signature verification of eth_getLogs requests.",
RpcErrorCodes.INVALID_REQUEST
);
}

const fromBlock = BigInt(headerFromBlock);

const blockNumber = await this.publicClient.getBlockNumber();
let minBlockNumber = blockNumber - BigInt(this.networkConfig.receiptLookupRange);
// underflow check
if (minBlockNumber < 0) {
minBlockNumber = BigInt(0);
const calculatedMin = blockNumber - BigInt(this.networkConfig.receiptLookupRange);
const minBlockNumber = calculatedMin < BigInt(0) ? BigInt(0) : calculatedMin;

// Validate fromBlock is within the allowed range
if (fromBlock > blockNumber) {
throw new RpcError(
`x-from-block (${fromBlock}) cannot be greater than current block number (${blockNumber})`,
RpcErrorCodes.INVALID_REQUEST
);
}

let fromBlock = headerFromBlock
? BigInt(headerFromBlock)
: undefined

// ensure value is within the receipt lookup range
if (
fromBlock === undefined ||
fromBlock > blockNumber ||
fromBlock < minBlockNumber
) {
this.logger.warn(`fromBlock is out of range, using minBlockNumber: ${minBlockNumber}`)
fromBlock = minBlockNumber
if (fromBlock < minBlockNumber) {
throw new RpcError(
`x-from-block (${fromBlock}) is too old. Must be >= ${minBlockNumber} (current block ${blockNumber} - receiptLookupRange ${this.networkConfig.receiptLookupRange})`,
RpcErrorCodes.INVALID_REQUEST
);
}

const clientHeaders = Object.fromEntries(Object
Expand All @@ -293,15 +305,42 @@ export class EntryPointV7Service implements IEntryPointService {
}),
})

const privateLogs = await clientWithHeaders.getLogs({
address: this.address,
event: parseAbiItem([
'event PrivateEvent(address[] allowedViewers, bytes32 indexed eventType, bytes payload)',
]),
fromBlock,
args: {
eventType: USER_OPERATION_EVENT_HASH,
}
// Manually construct eth_getLogs request to control the ID for signature verification
const privateEventAbi = parseAbiItem('event PrivateEvent(address[] allowedViewers, bytes32 indexed eventType, bytes payload)');
const privateEventHash = toEventHash(privateEventAbi);

const getLogsPayload = {
jsonrpc: requestMetadata.jsonrpc,
id: requestMetadata.id,
method: "eth_getLogs",
params: [{
address: this.address,
fromBlock: toHex(fromBlock),
topics: [
privateEventHash,
USER_OPERATION_EVENT_HASH
]
}]
};

// Use transport.request to send the manually constructed payload
const response = await clientWithHeaders.transport.request({
method: 'POST',
body: getLogsPayload
}) as { result?: any[] };

// Parse the logs from the response and decode the PrivateEvent
const privateLogs = (response.result || []).map((log: any) => {
const decoded = decodeEventLog({
abi: [privateEventAbi],
data: log.data,
topics: log.topics
});
return {
...log,
args: decoded.args,
eventName: 'PrivateEvent'
};
});

for (const log of privateLogs) {
Expand Down Expand Up @@ -335,8 +374,9 @@ export class EntryPointV7Service implements IEntryPointService {
async getUserOperationReceipt(
hash: Hex,
sdAuthHeaders: SDAuthHeaders,
requestMetadata: RpcRequestMetadata,
): Promise<UserOperationReceipt | null> {
const event = await this.getUserOperationEvent(hash, sdAuthHeaders);
const event = await this.getUserOperationEvent(hash, sdAuthHeaders, requestMetadata);
if (!event) {
return null;
}
Expand All @@ -358,8 +398,9 @@ export class EntryPointV7Service implements IEntryPointService {
async getUserOperationByHash(
hash: Hex,
sdAuthHeaders: SDAuthHeaders,
requestMetadata: RpcRequestMetadata,
): Promise<UserOperationByHashResponse | null> {
const event = await this.getUserOperationEvent(hash, sdAuthHeaders);
const event = await this.getUserOperationEvent(hash, sdAuthHeaders, requestMetadata);
if (!event) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { UserOperation } from "@skandha/types/lib/contracts/UserOperation";
import { IStakeManager } from "@skandha/types/lib/contracts/EPv7/core/StakeManager";
import { UserOperationEventEvent } from "@skandha/types/lib/contracts/EPv6/EntryPoint";
import {
RpcRequestMetadata,
SDAuthHeaders,
UserOperationByHashResponse,
UserOperationReceipt,
Expand Down Expand Up @@ -39,14 +40,17 @@ export interface IEntryPointService {
getUserOperationEvent(
userOpHash: string,
sdAuthHeaders: SDAuthHeaders,
requestMetadata: RpcRequestMetadata,
): Promise<any>;
getUserOperationReceipt(
hash: string,
sdAuthHeaders: SDAuthHeaders,
requestMetadata: RpcRequestMetadata,
): Promise<UserOperationReceipt | null>;
getUserOperationByHash(
hash: string,
sdAuthHeaders: SDAuthHeaders,
requestMetadata: RpcRequestMetadata,
): Promise<UserOperationByHashResponse | null>;

encodeHandleOps(userOps: UserOperation[], beneficiary: string): Hex;
Expand Down
5 changes: 5 additions & 0 deletions packages/types/src/api/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,8 @@ export function isSDAuthHeaders(
)
)
}

export type RpcRequestMetadata = {
id: number
jsonrpc: string
}