Skip to content

Commit

Permalink
AA-398: Standardize 'preVerificationGas' verification & create 'debug…
Browse files Browse the repository at this point in the history
…_bundler_setConfiguration' (#228)

* AA-398: (WIP) Standardize 'preVerificationGas' verification

* Enable configuring PVG calculation overrides


* Implement 'debug_bundler_setConfiguration'

* Separate 'fixedGasOverhead' and 'transactionGasStipend' chain config params

* Remove deprecated 'GetUserOpHashes' and 'BundlerHelper' solidity code
  • Loading branch information
forshtat authored Oct 20, 2024
1 parent 3b22c35 commit 5dd266b
Show file tree
Hide file tree
Showing 30 changed files with 343 additions and 227 deletions.
20 changes: 0 additions & 20 deletions packages/bundler/contracts/BundlerHelper.sol

This file was deleted.

5 changes: 3 additions & 2 deletions packages/bundler/localconfig/bundler.config.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"chainId": 1337,
"gasFactor": "1",
"port": "3000",
"privateApiPort": "3001",
Expand All @@ -8,8 +9,8 @@
"minBalance": "1",
"mnemonic": "./localconfig/mnemonic.txt",
"maxBundleGas": 5e6,
"minStake": "1" ,
"minUnstakeDelay": 0 ,
"minStake": "1",
"minUnstakeDelay": 0,
"autoBundleInterval": 3,
"autoBundleMempoolSize": 10,
"rip7560": false,
Expand Down
5 changes: 3 additions & 2 deletions packages/bundler/localconfig/bundler.rip7560.config.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"chainId": 1337,
"gasFactor": "1",
"port": "3000",
"privateApiPort": "3001",
Expand All @@ -8,8 +9,8 @@
"minBalance": "1",
"mnemonic": "./localconfig/mnemonic.txt",
"maxBundleGas": 30e6,
"minStake": "1" ,
"minUnstakeDelay": 0 ,
"minStake": "1",
"minUnstakeDelay": 0,
"autoBundleInterval": 3,
"autoBundleMempoolSize": 10,
"rip7560": true,
Expand Down
39 changes: 38 additions & 1 deletion packages/bundler/src/BundlerConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import ow from 'ow'
// RIP-7560 EntyPoint address
const MIN_UNSTAKE_DELAY = 86400
const MIN_STAKE_VALUE = 1e18.toString()

export interface BundlerConfig {
chainId: number
beneficiary: string
entryPoint: string
gasFactor: string
Expand All @@ -27,10 +29,21 @@ export interface BundlerConfig {
rip7560: boolean
rip7560Mode: string
gethDevMode: boolean

// Config overrides for PreVerificationGas calculation
fixedGasOverhead?: number
perUserOpGasOverhead?: number
perUserOpWordGasOverhead?: number
zeroByteGasCost?: number
nonZeroByteGasCost?: number
expectedBundleSize?: number
estimationSignatureSize?: number
estimationPaymasterDataSize?: number
}

// TODO: implement merging config (args -> config.js -> default) and runtime shape validation
export const BundlerConfigShape = {
chainId: ow.number,
beneficiary: ow.string,
entryPoint: ow.string,
gasFactor: ow.string,
Expand All @@ -52,7 +65,31 @@ export const BundlerConfigShape = {
autoBundleMempoolSize: ow.number,
rip7560: ow.boolean,
rip7560Mode: ow.string.oneOf(['PULL', 'PUSH']),
gethDevMode: ow.boolean
gethDevMode: ow.boolean,

// Config overrides for PreVerificationGas calculation
fixedGasOverhead: ow.optional.number,
perUserOpGasOverhead: ow.optional.number,
perUserOpWordGasOverhead: ow.optional.number,
zeroByteGasCost: ow.optional.number,
nonZeroByteGasCost: ow.optional.number,
expectedBundleSize: ow.optional.number,
estimationSignatureSize: ow.optional.number,
estimationPaymasterDataSize: ow.optional.number
}

/**
* Only parameters in this object can be provided by a 'debug_bundler_setConfiguration' API.
*/
export const DebugBundlerConfigShape = {
fixedGasOverhead: ow.optional.number,
perUserOpGasOverhead: ow.optional.number,
perUserOpWordGasOverhead: ow.optional.number,
zeroByteGasCost: ow.optional.number,
nonZeroByteGasCost: ow.optional.number,
expectedBundleSize: ow.optional.number,
estimationSignatureSize: ow.optional.number,
estimationPaymasterDataSize: ow.optional.number
}

// TODO: consider if we want any default fields at all
Expand Down
9 changes: 8 additions & 1 deletion packages/bundler/src/BundlerServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ export class BundlerServer {
} = reqItem
debug('>>', { jsonrpc, id, method, params })
try {
const result = deepHexlify(await this.handleMethod(method, params))
const handleResult = await this.handleMethod(method, params)
const result = deepHexlify(handleResult)
debug('sent', method, '-', result)
debug('<<', { jsonrpc, id, result })
return {
Expand Down Expand Up @@ -330,6 +331,12 @@ export class BundlerServer {
case 'debug_bundler_getStakeStatus':
result = await this.debugHandler.getStakeStatus(params[0], params[1])
break
case 'debug_bundler_setConfiguration': {
const pvgc = await this.debugHandler._setConfiguration(params[0])
this.methodHandler.preVerificationGasCalculator = pvgc
}
result = {}
break
default:
throw new RpcError(`Method ${method} is not supported`, -32601)
}
Expand Down
16 changes: 13 additions & 3 deletions packages/bundler/src/DebugMethodHandler.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import ow from 'ow'

import { StakeInfo } from '@account-abstraction/utils'
import { PreVerificationGasCalculator } from '@account-abstraction/sdk'

import { BundlerConfig, DebugBundlerConfigShape } from './BundlerConfig'
import { EventsManager } from './modules/EventsManager'
import { ExecutionManager } from './modules/ExecutionManager'
import { ReputationDump, ReputationManager } from './modules/ReputationManager'
import { MempoolManager } from './modules/MempoolManager'
import { ReputationDump, ReputationManager } from './modules/ReputationManager'
import { SendBundleReturn } from './modules/BundleManager'
import { EventsManager } from './modules/EventsManager'
import { StakeInfo } from '@account-abstraction/utils'

export class DebugMethodHandler {
constructor (
Expand Down Expand Up @@ -76,4 +81,9 @@ export class DebugMethodHandler {
}> {
return await this.repManager.getStakeStatus(address, entryPoint)
}

async _setConfiguration (config: Partial<BundlerConfig>): Promise<PreVerificationGasCalculator> {
ow.object.exactShape(DebugBundlerConfigShape)
return await this.execManager._setConfiguration(config)
}
}
43 changes: 27 additions & 16 deletions packages/bundler/src/MethodHandlerERC4337.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
import debug from 'debug'
import { BigNumber, BigNumberish, Signer } from 'ethers'
import { JsonRpcProvider, Log } from '@ethersproject/providers'
import { EventFragment } from '@ethersproject/abi'

import { MainnetConfig, PreVerificationGasCalculator } from '@account-abstraction/sdk'

import { BundlerConfig } from './BundlerConfig'
import {
AddressZero,
IEntryPoint,
PackedUserOperation,
RpcError,
UserOperation,
UserOperationEventEvent,
ValidationErrors,
requireAddressAndFields,
packUserOp,
PackedUserOperation,
unpackUserOp,
simulationRpcParams,
decodeSimulateHandleOpResult,
AddressZero,
decodeRevertReason,
decodeSimulateHandleOpResult,
deepHexlify,
erc4337RuntimeVersion,
mergeValidationDataValues,
UserOperationEventEvent, IEntryPoint, requireCond, deepHexlify, tostr, erc4337RuntimeVersion
, UserOperation
packUserOp,
requireAddressAndFields,
requireCond,
simulationRpcParams,
tostr,
unpackUserOp
} from '@account-abstraction/utils'
import { BundlerConfig } from './BundlerConfig'

import { ExecutionManager } from './modules/ExecutionManager'
import { StateOverride, UserOperationByHashResponse, UserOperationReceipt } from './RpcTypes'
import { calcPreVerificationGas } from '@account-abstraction/sdk'
import { EventFragment } from '@ethersproject/abi'

export const HEX_REGEX = /^0x[a-fA-F\d]*$/i

Expand Down Expand Up @@ -58,7 +66,8 @@ export class MethodHandlerERC4337 {
readonly provider: JsonRpcProvider,
readonly signer: Signer,
readonly config: BundlerConfig,
readonly entryPoint: IEntryPoint
readonly entryPoint: IEntryPoint,
public preVerificationGasCalculator: PreVerificationGasCalculator
) {
}

Expand Down Expand Up @@ -143,16 +152,18 @@ export class MethodHandlerERC4337 {
} = returnInfo

// todo: use simulateHandleOp for this too...
const callGasLimit = await this.provider.estimateGas({
let callGasLimit = await this.provider.estimateGas({
from: this.entryPoint.address,
to: userOp.sender,
data: userOp.callData
}).then(b => b.toNumber()).catch(err => {
const message = err.message.match(/reason="(.*?)"/)?.at(1) ?? 'execution reverted'
throw new RpcError(message, ValidationErrors.UserOperationReverted)
})
// Results from 'estimateGas' assume making a standalone transaction and paying 21'000 gas extra for it
callGasLimit -= MainnetConfig.transactionGasStipend

const preVerificationGas = calcPreVerificationGas(userOp)
const preVerificationGas = this.preVerificationGasCalculator.estimatePreVerificationGas(userOp)
const verificationGasLimit = BigNumber.from(preOpGas).toNumber()
return {
preVerificationGas,
Expand All @@ -166,7 +177,7 @@ export class MethodHandlerERC4337 {
async sendUserOperation (userOp: UserOperation, entryPointInput: string): Promise<string> {
await this._validateParameters(userOp, entryPointInput)

console.log(`UserOperation: Sender=${userOp.sender} Nonce=${tostr(userOp.nonce)} EntryPoint=${entryPointInput} Paymaster=${userOp.paymaster ?? ''}`)
debug(`UserOperation: Sender=${userOp.sender} Nonce=${tostr(userOp.nonce)} EntryPoint=${entryPointInput} Paymaster=${userOp.paymaster ?? ''}`)
await this.execManager.sendUserOperation(userOp, entryPointInput, false)
return await this.entryPoint.getUserOpHash(packUserOp(userOp))
}
Expand Down
10 changes: 3 additions & 7 deletions packages/bundler/src/modules/BundleManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ import {
ValidationErrors,
mergeStorageMap,
packUserOp,
runContractScript
getUserOpHash
} from '@account-abstraction/utils'

import { EventsManager } from './EventsManager'
import { GetUserOpHashes__factory } from '../types'
import { IBundleManager } from './IBundleManager'
import { MempoolEntry } from './MempoolEntry'
import { MempoolManager } from './MempoolManager'
Expand Down Expand Up @@ -373,11 +372,8 @@ export class BundleManager implements IBundleManager {

// helper function to get hashes of all UserOps
async getUserOpHashes (userOps: UserOperation[]): Promise<string[]> {
const { userOpHashes } = await runContractScript(this.entryPoint.provider,
new GetUserOpHashes__factory(),
[this.entryPoint.address, userOps.map(packUserOp)])

return userOpHashes
const network = await this.entryPoint.provider.getNetwork()
return userOps.map(it => getUserOpHash(it, this.entryPoint.address, network.chainId))
}

async getPaymasterBalance (paymaster: string): Promise<BigNumber> {
Expand Down
17 changes: 15 additions & 2 deletions packages/bundler/src/modules/ExecutionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import { ReputationManager } from './ReputationManager'
import { IBundleManager } from './IBundleManager'
import {
EmptyValidateUserOpResult,
IValidationManager
IValidationManager, ValidationManager
} from '@account-abstraction/validation-manager'
import { DepositManager } from './DepositManager'
import { BigNumberish, Signer } from 'ethers'
import { BundlerConfig } from '../BundlerConfig'
import { PreVerificationGasCalculator } from '@account-abstraction/sdk'

const debug = Debug('aa.exec')

Expand All @@ -30,7 +32,7 @@ export class ExecutionManager {
constructor (private readonly reputationManager: ReputationManager,
private readonly mempoolManager: MempoolManager,
private readonly bundleManager: IBundleManager,
private readonly validationManager: IValidationManager,
private validationManager: IValidationManager,
private readonly depositManager: DepositManager,
private readonly signer: Signer,
private readonly rip7560: boolean,
Expand Down Expand Up @@ -143,4 +145,15 @@ export class ExecutionManager {
): Promise<[OperationBase[], StorageMap]> {
return await this.bundleManager.createBundle(minBaseFee, maxBundleGas, maxBundleSize)
}

async _setConfiguration (configOverrides: Partial<BundlerConfig>): Promise<PreVerificationGasCalculator> {
const { configuration, entryPoint, unsafe } = this.validationManager._getDebugConfiguration()
const pvgc = new PreVerificationGasCalculator(Object.assign({}, configuration, configOverrides))
this.validationManager = new ValidationManager(
entryPoint,
unsafe,
pvgc
)
return pvgc
}
}
9 changes: 6 additions & 3 deletions packages/bundler/src/modules/initServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,25 @@ import { BundleManagerRIP7560 } from './BundleManagerRIP7560'
import { IBundleManager } from './IBundleManager'
import { DepositManager } from './DepositManager'
import { IRip7560StakeManager__factory } from '@account-abstraction/utils/dist/src/types'
import { PreVerificationGasCalculator, ChainConfigs } from '@account-abstraction/sdk'

/**
* initialize server modules.
* returns the ExecutionManager and EventsManager (for handling events, to update reputation)
* @param config
* @param signer
*/
export function initServer (config: BundlerConfig, signer: Signer): [ExecutionManager, EventsManager, ReputationManager, MempoolManager] {
export function initServer (config: BundlerConfig, signer: Signer): [ExecutionManager, EventsManager, ReputationManager, MempoolManager, PreVerificationGasCalculator] {
const entryPoint = IEntryPoint__factory.connect(config.entryPoint, signer)
const reputationManager = new ReputationManager(getNetworkProvider(config.network), BundlerReputationParams, parseEther(config.minStake), config.minUnstakeDelay)
const mempoolManager = new MempoolManager(reputationManager)
const eventsManager = new EventsManager(entryPoint, mempoolManager, reputationManager)
const mergedPvgcConfig = Object.assign({}, ChainConfigs[config.chainId] ?? {}, config)
const preVerificationGasCalculator = new PreVerificationGasCalculator(mergedPvgcConfig)
let validationManager: IValidationManager
let bundleManager: IBundleManager
if (!config.rip7560) {
validationManager = new ValidationManager(entryPoint, config.unsafe)
validationManager = new ValidationManager(entryPoint, config.unsafe, preVerificationGasCalculator)
bundleManager = new BundleManager(entryPoint, entryPoint.provider as JsonRpcProvider, signer, eventsManager, mempoolManager, validationManager, reputationManager,
config.beneficiary, parseEther(config.minBalance), config.maxBundleGas, config.conditionalRpc)
} else {
Expand All @@ -52,5 +55,5 @@ export function initServer (config: BundlerConfig, signer: Signer): [ExecutionMa
if (config.rip7560 && config.rip7560Mode === 'PUSH') {
executionManager.setAutoBundler(config.autoBundleInterval, config.autoBundleMempoolSize)
}
return [executionManager, eventsManager, reputationManager, mempoolManager]
return [executionManager, eventsManager, reputationManager, mempoolManager, preVerificationGasCalculator]
}
11 changes: 9 additions & 2 deletions packages/bundler/src/runBundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,21 @@ export async function runBundler (argv: string[], overrideExit = true): Promise<
execManagerConfig.autoBundleInterval = 0
}

const [execManager, eventsManager, reputationManager, mempoolManager] = initServer(execManagerConfig, wallet)
const [
execManager,
eventsManager,
reputationManager,
mempoolManager,
preVerificationGasCalculator
] = initServer(execManagerConfig, wallet)
const methodHandler = new MethodHandlerERC4337(
execManager,
provider,
wallet,
config,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
entryPoint!
entryPoint!,
preVerificationGasCalculator
)
const methodHandlerRip7560 = new MethodHandlerRIP7560(
execManager,
Expand Down
5 changes: 1 addition & 4 deletions packages/bundler/src/runner/runop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,7 @@ class Runner {
entryPointAddress: this.entryPointAddress,
factoryAddress: accountDeployer,
owner: this.accountOwner,
index: this.index,
overheads: {
// perUserOp: 100000
}
index: this.index
})
return this
}
Expand Down
Loading

0 comments on commit 5dd266b

Please sign in to comment.