Skip to content

Commit

Permalink
AA-344 AA-401: Enable sending an invalid transaction to the block bui…
Browse files Browse the repository at this point in the history
…lder (#225)

* AA-344 AA-401: Enable sending an invalid transaction to the block builder

* Fix things
  • Loading branch information
forshtat authored Sep 11, 2024
1 parent 09b1141 commit d6ca206
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 16 deletions.
15 changes: 13 additions & 2 deletions packages/bundler/src/BundlerServer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import bodyParser from 'body-parser'
import cors from 'cors'
import express, { Express, Response, Request, RequestHandler } from 'express'
import { Provider } from '@ethersproject/providers'
import { JsonRpcProvider, Provider } from '@ethersproject/providers'
import { Signer, utils } from 'ethers'
import { parseEther } from 'ethers/lib/utils'
import { Server } from 'http'
Expand Down Expand Up @@ -247,9 +247,20 @@ export class BundlerServer {
throw new RpcError(`Method ${method} is not supported`, -32601)
}
if (params[0].sender != null) {
result = await this.methodHandlerRip7560.sendRIP7560Transaction(params[0])
result = await this.methodHandlerRip7560.sendRIP7560Transaction(params[0], false)
}
break
case 'debug_bundler_sendTransactionSkipValidation':
if (!this.config.rip7560) {
throw new RpcError(`Method ${method} is not supported`, -32601)
}
if (params[0].sender != null) {
result = await this.methodHandlerRip7560.sendRIP7560Transaction(params[0], true)
}
break
case 'eth_getRip7560TransactionDebugInfo':
result = await (this.provider as JsonRpcProvider).send('eth_getRip7560TransactionDebugInfo', [params[0]])
break
case 'eth_getTransactionReceipt':
if (!this.config.rip7560) {
throw new RpcError(`Method ${method} is not supported`, -32601)
Expand Down
2 changes: 1 addition & 1 deletion packages/bundler/src/MethodHandlerERC4337.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export class MethodHandlerERC4337 {
await this._validateParameters(userOp, entryPointInput)

console.log(`UserOperation: Sender=${userOp.sender} Nonce=${tostr(userOp.nonce)} EntryPoint=${entryPointInput} Paymaster=${userOp.paymaster ?? ''}`)
await this.execManager.sendUserOperation(userOp, entryPointInput)
await this.execManager.sendUserOperation(userOp, entryPointInput, false)
return await this.entryPoint.getUserOpHash(packUserOp(userOp))
}

Expand Down
4 changes: 2 additions & 2 deletions packages/bundler/src/MethodHandlerRIP7560.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ export class MethodHandlerRIP7560 {
readonly provider: JsonRpcProvider
) {}

async sendRIP7560Transaction (transaction: OperationRIP7560): Promise<string> {
async sendRIP7560Transaction (transaction: OperationRIP7560, skipValidation: boolean): Promise<string> {
await this._validateParameters(transaction)
console.log(`RIP7560Transaction: Sender=${transaction.sender} Nonce=${getPackedNonce(transaction).toHexString()} Paymaster=${transaction.paymaster ?? ''}`)
await this.execManager.sendUserOperation(transaction, '')
await this.execManager.sendUserOperation(transaction, '', skipValidation)
return getRIP7560TransactionHash(transaction)
}

Expand Down
16 changes: 12 additions & 4 deletions packages/bundler/src/modules/BundleManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { JsonRpcProvider } from '@ethersproject/providers'
import { Mutex } from 'async-mutex'
import { isAddress } from 'ethers/lib/utils'

import { IValidationManager, ValidateUserOpResult } from '@account-abstraction/validation-manager'
import {
EmptyValidateUserOpResult,
IValidationManager,
ValidateUserOpResult
} from '@account-abstraction/validation-manager'

import {
AddressZero,
Expand Down Expand Up @@ -260,10 +264,14 @@ export class BundleManager implements IBundleManager {
// allow only a single UserOp per sender per bundle
continue
}
let validationResult: ValidateUserOpResult
let validationResult: ValidateUserOpResult = EmptyValidateUserOpResult
try {
// re-validate UserOp. no need to check stake, since it cannot be reduced between first and 2nd validation
validationResult = await this.validationManager.validateUserOp(entry.userOp, entry.referencedContracts, false)
if (!entry.skipValidation) {
// re-validate UserOp. no need to check stake, since it cannot be reduced between first and 2nd validation
validationResult = await this.validationManager.validateUserOp(entry.userOp, entry.referencedContracts, false)
} else {
console.warn('Skipping second validation for an injected debug operation, id=', entry.userOpHash)
}
} catch (e: any) {
this._handleSecondValidationException(e, paymaster, entry)
continue
Expand Down
17 changes: 13 additions & 4 deletions packages/bundler/src/modules/ExecutionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import { SendBundleReturn } from './BundleManager'
import { MempoolManager } from './MempoolManager'
import { ReputationManager } from './ReputationManager'
import { IBundleManager } from './IBundleManager'
import { IValidationManager } from '@account-abstraction/validation-manager'
import {
EmptyValidateUserOpResult,
IValidationManager
} from '@account-abstraction/validation-manager'
import { DepositManager } from './DepositManager'
import { BigNumberish, Signer } from 'ethers'

Expand Down Expand Up @@ -40,15 +43,21 @@ export class ExecutionManager {
* send a user operation through the bundler.
* @param userOp the UserOp to send.
* @param entryPointInput the entryPoint passed through the RPC request.
* @param skipValidation if set to true we will not perform tracing and ERC-7562 rules compliance validation
*/
async sendUserOperation (userOp: OperationBase, entryPointInput: string): Promise<void> {
async sendUserOperation (userOp: OperationBase, entryPointInput: string, skipValidation: boolean): Promise<void> {
await this.mutex.runExclusive(async () => {
debug('sendUserOperation')
this.validationManager.validateInputParameters(userOp, entryPointInput)
const validationResult = await this.validationManager.validateUserOp(userOp, undefined)
let validationResult = EmptyValidateUserOpResult
if (!skipValidation) {
validationResult = await this.validationManager.validateUserOp(userOp, undefined)
}
const userOpHash = await this.validationManager.getOperationHash(userOp)
await this.depositManager.checkPaymasterDeposit(userOp)
this.mempoolManager.addUserOp(userOp,
this.mempoolManager.addUserOp(
skipValidation,
userOp,
userOpHash,
validationResult.returnInfo.prefund ?? 0,
validationResult.referencedContracts,
Expand Down
1 change: 1 addition & 0 deletions packages/bundler/src/modules/MempoolEntry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export class MempoolEntry {
readonly userOpHash: string,
readonly prefund: BigNumberish,
readonly referencedContracts: ReferencedCodeHashes,
readonly skipValidation: boolean,
readonly aggregator?: string
) {
this.userOpMaxGas = BigNumber
Expand Down
8 changes: 6 additions & 2 deletions packages/bundler/src/modules/MempoolManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export class MempoolManager {
// replace existing, if any (and if new gas is higher)
// reverts if unable to add UserOp to mempool (too many UserOps with this sender)
addUserOp (
skipValidation: boolean,
userOp: OperationBase,
userOpHash: string,
prefund: BigNumberish,
Expand All @@ -75,6 +76,7 @@ export class MempoolManager {
userOpHash,
prefund,
referencedContracts,
skipValidation,
aggregatorInfo?.addr
)
const packedNonce = getPackedNonce(entry.userOp)
Expand All @@ -86,8 +88,10 @@ export class MempoolManager {
this.mempool[index] = entry
} else {
debug('add userOp', userOp.sender, packedNonce)
this.checkReputation(senderInfo, paymasterInfo, factoryInfo, aggregatorInfo)
this.checkMultipleRolesViolation(userOp)
if (!skipValidation) {
this.checkReputation(senderInfo, paymasterInfo, factoryInfo, aggregatorInfo)
this.checkMultipleRolesViolation(userOp)
}
this.incrementEntryCount(userOp.sender)
if (userOp.paymaster != null) {
this.incrementEntryCount(userOp.paymaster)
Expand Down
22 changes: 21 additions & 1 deletion packages/validation-manager/src/IValidationManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { OperationBase, ReferencedCodeHashes, StakeInfo, StorageMap } from '@account-abstraction/utils'
import { BigNumberish } from 'ethers'
import { BigNumber, BigNumberish } from 'ethers'

/**
* result from successful validation
Expand All @@ -24,6 +24,26 @@ export interface ValidateUserOpResult extends ValidationResult {
storageMap: StorageMap
}

export const EmptyValidateUserOpResult: ValidateUserOpResult = {
returnInfo: {
preOpGas: BigNumber.from(0),
prefund: BigNumber.from(0),
sigFailed: false,
validAfter: 0,
validUntil: 0
},
senderInfo: {
addr: '',
stake: '0',
unstakeDelaySec: 0
},
referencedContracts: {
addresses: [],
hash: ''
},
storageMap: {}
}

export interface IValidationManager {

validateInputParameters: (operation: OperationBase, entryPointInput?: string, requireSignature?: boolean, requireGasParams?: boolean) => void
Expand Down

0 comments on commit d6ca206

Please sign in to comment.