mirror of
https://github.com/luxfi/bundler.git
synced 2026-07-27 02:41:10 +00:00
AA-472: Support native tracer (#230)
* support native tracer --tracerRpcUrl - provide a geth node that supports the native "bundlerCollectorTracer" native tracer main provider must support "prestateTracer" --------- Co-authored-by: Alex Forshtat <forshtat1@gmail.com>
This commit is contained in:
co-authored by
Alex Forshtat
parent
f464796938
commit
8bc5028caa
@@ -16,6 +16,7 @@ export interface BundlerConfig {
|
||||
port: string
|
||||
privateApiPort: string
|
||||
unsafe: boolean
|
||||
tracerRpcUrl?: string
|
||||
debugRpc?: boolean
|
||||
conditionalRpc: boolean
|
||||
|
||||
@@ -53,6 +54,7 @@ export const BundlerConfigShape = {
|
||||
port: ow.string,
|
||||
privateApiPort: ow.string,
|
||||
unsafe: ow.boolean,
|
||||
tracerRpcUrl: ow.optional.string,
|
||||
debugRpc: ow.optional.boolean,
|
||||
conditionalRpc: ow.boolean,
|
||||
|
||||
|
||||
@@ -38,7 +38,8 @@ export function initServer (config: BundlerConfig, signer: Signer): [ExecutionMa
|
||||
let validationManager: IValidationManager
|
||||
let bundleManager: IBundleManager
|
||||
if (!config.rip7560) {
|
||||
validationManager = new ValidationManager(entryPoint, config.unsafe, preVerificationGasCalculator)
|
||||
const tracerProvider = config.tracerRpcUrl == null ? undefined : getNetworkProvider(config.tracerRpcUrl)
|
||||
validationManager = new ValidationManager(entryPoint, config.unsafe, preVerificationGasCalculator, tracerProvider)
|
||||
bundleManager = new BundleManager(entryPoint, entryPoint.provider as JsonRpcProvider, signer, eventsManager, mempoolManager, validationManager, reputationManager,
|
||||
config.beneficiary, parseEther(config.minBalance), config.maxBundleGas, config.conditionalRpc)
|
||||
} else {
|
||||
|
||||
@@ -15,7 +15,7 @@ import { MethodHandlerERC4337 } from './MethodHandlerERC4337'
|
||||
|
||||
import { initServer } from './modules/initServer'
|
||||
import { DebugMethodHandler } from './DebugMethodHandler'
|
||||
import { supportsDebugTraceCall } from '@account-abstraction/validation-manager'
|
||||
import { supportsDebugTraceCall, supportsNativeTracer } from '@account-abstraction/validation-manager'
|
||||
import { resolveConfiguration } from './Config'
|
||||
import { bundlerConfigDefault } from './BundlerConfig'
|
||||
import { parseEther } from 'ethers/lib/utils'
|
||||
@@ -80,7 +80,8 @@ export async function runBundler (argv: string[], overrideExit = true): Promise<
|
||||
.option('--privateApiPort <number>', `server listening port for block builder (default: ${bundlerConfigDefault.privateApiPort})`)
|
||||
.option('--config <string>', 'path to config file', CONFIG_FILE_NAME)
|
||||
.option('--auto', 'automatic bundling (bypass config.autoBundleMempoolSize)', false)
|
||||
.option('--unsafe', 'UNSAFE mode: no storage or opcode checks (safe mode requires geth)')
|
||||
.option('--unsafe', 'UNSAFE mode: no storage or opcode checks (safe mode requires debug_traceCall)')
|
||||
.option('--tracerRpcUrl <string>', 'run native tracer on this provider, and prestateTracer native tracer on network provider. requires unsafe=false')
|
||||
.option('--debugRpc', 'enable debug rpc methods (auto-enabled for test node')
|
||||
.option('--conditionalRpc', 'Use eth_sendRawTransactionConditional RPC)')
|
||||
.option('--show-stack-traces', 'Show stack traces.')
|
||||
@@ -130,10 +131,31 @@ export async function runBundler (argv: string[], overrideExit = true): Promise<
|
||||
console.error('FATAL: --conditionalRpc requires a node that support eth_sendRawTransactionConditional')
|
||||
process.exit(1)
|
||||
}
|
||||
if (!config.unsafe && !await supportsDebugTraceCall(provider as any, config.rip7560)) {
|
||||
const requiredApi = config.rip7560 ? 'eth_traceRip7560Validation' : 'debug_traceCall'
|
||||
console.error(`FATAL: full validation requires a node with ${requiredApi}. for local UNSAFE mode: use --unsafe`)
|
||||
process.exit(1)
|
||||
if (config.unsafe) {
|
||||
if (config.tracerRpcUrl != null) {
|
||||
console.error('FATAL: --unsafe and --tracerRpcUrl are mutually exclusive')
|
||||
process.exit(1)
|
||||
}
|
||||
} else {
|
||||
if (config.tracerRpcUrl != null) {
|
||||
// validate standard tracer supports "prestateTracer":
|
||||
if (!await supportsNativeTracer(provider, 'prestateTracer')) {
|
||||
console.error('FATAL: --tracerRpcUrl requires the network provider to support prestateTracer')
|
||||
process.exit(1)
|
||||
}
|
||||
const tracerProvider = new ethers.providers.JsonRpcProvider(config.tracerRpcUrl)
|
||||
if (!await supportsNativeTracer(tracerProvider)) {
|
||||
console.error('FATAL: --tracerRpcUrl requires a provider to support bundlerCollectorTracer')
|
||||
process.exit(1)
|
||||
}
|
||||
} else {
|
||||
// check standard javascript tracer:
|
||||
if (!await supportsDebugTraceCall(provider as any, config.rip7560)) {
|
||||
const requiredApi = config.rip7560 ? 'eth_traceRip7560Validation' : 'debug_traceCall'
|
||||
console.error(`FATAL: full validation requires a node with ${requiredApi}. for local UNSAFE mode: use --unsafe`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.rip7560) {
|
||||
|
||||
@@ -217,7 +217,13 @@ export function sum (...args: BigNumberish[]): BigNumber {
|
||||
*/
|
||||
export function getUserOpMaxCost (userOp: OperationBase): BigNumber {
|
||||
const preVerificationGas: BigNumberish = (userOp as UserOperation).preVerificationGas
|
||||
return sum(preVerificationGas ?? 0, userOp.verificationGasLimit, userOp.callGasLimit, userOp.paymasterVerificationGasLimit ?? 0, userOp.paymasterPostOpGasLimit ?? 0).mul(userOp.maxFeePerGas)
|
||||
return sum(
|
||||
preVerificationGas ?? 0,
|
||||
userOp.verificationGasLimit,
|
||||
userOp.callGasLimit,
|
||||
userOp.paymasterVerificationGasLimit ?? 0,
|
||||
userOp.paymasterPostOpGasLimit ?? 0
|
||||
).mul(userOp.maxFeePerGas)
|
||||
}
|
||||
|
||||
export function getPackedNonce (userOp: OperationBase): BigNumber {
|
||||
|
||||
@@ -8,6 +8,10 @@ import { OperationRIP7560, RpcError } from '@account-abstraction/utils'
|
||||
|
||||
const debug = Debug('aa.tracer')
|
||||
|
||||
// the name of the native tracer.
|
||||
// equivalent to the javascript "bundlerCollectorTracer".
|
||||
export const bundlerNativeTracerName = 'bundlerCollectorTracer'
|
||||
|
||||
/**
|
||||
* a function returning a LogTracer.
|
||||
* the function's body must be "{ return {...} }"
|
||||
@@ -17,13 +21,50 @@ const debug = Debug('aa.tracer')
|
||||
*/
|
||||
type LogTracerFunc = () => LogTracer
|
||||
|
||||
/**
|
||||
* trace a transaction using the geth debug_traceCall method.
|
||||
* @param provider the network node to trace on
|
||||
* @param tx the transaction to trace
|
||||
* @param options the trace options
|
||||
* @param nativeTracerProvider if set, submit only preStateTracer to the network provider, and use this (second) provider with native tracer.
|
||||
* if null, then use javascript tracer on the first provider.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export async function debug_traceCall (provider: JsonRpcProvider, tx: Deferrable<TransactionRequest>, options: TraceOptions): Promise<TraceResult | any> {
|
||||
export async function debug_traceCall (provider: JsonRpcProvider, tx: Deferrable<TransactionRequest>, options: TraceOptions, nativeTracerProvider?: JsonRpcProvider): Promise<TraceResult | any> {
|
||||
const tx1 = await resolveProperties(tx)
|
||||
const traceOptions = tracer2string(options)
|
||||
if (nativeTracerProvider != null) {
|
||||
// there is a nativeTracerProvider: use it for the native tracer, but first we need preStateTracer from the main provider:
|
||||
const preState: { [addr: string]: any } = await provider.send('debug_traceCall', [tx1, 'latest', { ...traceOptions, tracer: 'prestateTracer' }])
|
||||
|
||||
// fix prestate to be valid "state overrides"
|
||||
// - convert nonce's to hex
|
||||
// - rename storage to state
|
||||
for (const key in preState) {
|
||||
if (preState[key]?.nonce != null) {
|
||||
preState[key].nonce = '0x' + (preState[key].nonce.toString(16) as string)
|
||||
}
|
||||
if (preState[key]?.storage != null) {
|
||||
// rpc expects "state" instead...
|
||||
preState[key].state = preState[key].storage
|
||||
delete preState[key].storage
|
||||
}
|
||||
}
|
||||
|
||||
const ret = await nativeTracerProvider.send('debug_traceCall', [tx1, 'latest', {
|
||||
tracer: bundlerNativeTracerName,
|
||||
stateOverrides: preState
|
||||
}])
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
const ret = await provider.send('debug_traceCall', [tx1, 'latest', traceOptions]).catch(e => {
|
||||
debug('ex=', e.error)
|
||||
debug('tracer=', traceOptions.tracer?.toString().split('\n').map((line, index) => `${index + 1}: ${line}`).join('\n'))
|
||||
if (debug.enabled) {
|
||||
debug('ex=', e.error)
|
||||
debug('tracer=', traceOptions.tracer?.toString().split('\n').map((line, index) => `${index + 1}: ${line}`).join('\n'))
|
||||
}
|
||||
throw e
|
||||
})
|
||||
// return applyTracer(ret, options)
|
||||
|
||||
@@ -44,11 +44,20 @@ const VALID_UNTIL_FUTURE_SECONDS = 30
|
||||
const HEX_REGEX = /^0x[a-fA-F\d]*$/i
|
||||
const entryPointSimulations = IEntryPointSimulations__factory.createInterface()
|
||||
|
||||
/**
|
||||
* ValidationManager is responsible for validating UserOperations.
|
||||
* @param entryPoint - the entryPoint contract
|
||||
* @param unsafe - if true, skip tracer for validation rules (validate only through eth_call)
|
||||
* @param preVerificationGasCalculator - helper to calculate the correct 'preVerificationGas' for the current network conditions
|
||||
* @param providerForTracer - if provided, use it for native bundlerCollectorTracer, and use main provider with "preStateTracer"
|
||||
* (relevant only if unsafe=false)
|
||||
*/
|
||||
export class ValidationManager implements IValidationManager {
|
||||
constructor (
|
||||
readonly entryPoint: IEntryPoint,
|
||||
readonly unsafe: boolean,
|
||||
readonly preVerificationGasCalculator: PreVerificationGasCalculator
|
||||
readonly preVerificationGasCalculator: PreVerificationGasCalculator,
|
||||
readonly providerForTracer?: JsonRpcProvider
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -144,7 +153,7 @@ export class ValidationManager implements IValidationManager {
|
||||
code: EntryPointSimulationsJson.deployedBytecode
|
||||
}
|
||||
}
|
||||
})
|
||||
}, this.providerForTracer)
|
||||
|
||||
const lastResult = tracerResult.calls.slice(-1)[0]
|
||||
const data = (lastResult as ExitInfo).data
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
import { JsonRpcProvider } from '@ethersproject/providers'
|
||||
|
||||
import { AddressZero, IEntryPoint__factory, OperationRIP7560, UserOperation } from '@account-abstraction/utils'
|
||||
import { PreVerificationGasCalculator } from '@account-abstraction/sdk'
|
||||
|
||||
import { bundlerNativeTracerName, debug_traceCall, eth_traceRip7560Validation } from './GethTracer'
|
||||
import { bundlerCollectorTracer } from './BundlerCollectorTracer'
|
||||
import { debug_traceCall, eth_traceRip7560Validation } from './GethTracer'
|
||||
import { ValidateUserOpResult } from './IValidationManager'
|
||||
import { ValidationManager } from './ValidationManager'
|
||||
import { PreVerificationGasCalculator } from '@account-abstraction/sdk'
|
||||
|
||||
export * from './ValidationManager'
|
||||
export * from './ValidationManagerRIP7560'
|
||||
export * from './IValidationManager'
|
||||
|
||||
export async function supportsNativeTracer (provider: JsonRpcProvider, nativeTracer = bundlerNativeTracerName): Promise<boolean> {
|
||||
try {
|
||||
await provider.send('debug_traceCall', [{}, 'latest', { tracer: nativeTracer }])
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function supportsDebugTraceCall (provider: JsonRpcProvider, rip7560: boolean): Promise<boolean> {
|
||||
const p = provider.send as any
|
||||
if (p._clientVersion == null) {
|
||||
|
||||
Reference in New Issue
Block a user