Introduction
Why is a bundler needed? What is it exactly doing to a UserOperation? We continue our EIP-4337 journey. This time by clearing out what a UserOperation typically goes through in a bundler.
After publishing the previous post detailing UserOperation CallData, a few folks reached out asking Exactly why is a BUNDLER needed if I can just relay the UserOp to the entrypoint from an EOA.
Let's break down the checks a bundler performs upon receiving a UserOp through its RPC URL.
Would like to thank the Ethereum Foundation and ERC4337 community for their support. JiffyScan and these articles wouldn't have existed without their aid.
Overview
The job of a bundler is to accept signed calldata representing a UserOperation and submit it to the chain (triggering entrypoint.handleOps()
), potentially along with other independent UserOperations.
We break down this process in this post into 2 phases:
- Pre-Bundling Phase: Validating a UserOperation before adding it to the pool of UserOperations ready to create a bundle
- Bundling Phase: Submitting a subset from the UserOperations pending to be added to the chain
Note: We've used Eth-infinitism's bundler as a reference for this post.
Pre-Bundling Phase
When a UserOperation is received by the bundler's RPC endpoint, it runs a few checks before adding it to the set of valid UserOperations to be ready to be submitted to the chain. The checks are carried out in the following stages:
Steps while adding UserOp to the MempoolValidate Input Parameters
Bundler checks for the presence & validity of all the fields of a UserOp at an individual level. These are as follows:
- The entrypoint parameter in the RPC call is not null and is supported by the bundler
- UserOperation is not null and all the fields have hexadecimal values:
- Sender
- nonce
- initCode
- callData
- paymasterAndData
- signature (if required)
- UserOperations has all the gas parameters and the values are valid hexadecimal values:
- preVerificationGas
- verificationGasLimit
- callGasLimit
- maxFeePerGas
- maxPriorityFeePerGas
- The paymasterAndData field is either
0x
or the length is greater than or equal to42
(to store at least an address) - The initCode field is either
0x
or the length is greater than or equal to42
(to store at least an address) - Ensure
preVerificationGas
is the minimum required by the bundler to process the request.
You may refer to the code to validate input parameters here. For some reason, steps 1-3 are performed once before redundantly here in the v1.6 of the Eth-infinitism's bundler implementation.
Simulate UserOp and Validate the trace
The bundler next checks the changes the operation would've on the state of the blockchain and that it meets the restrictions suggested by the standard.
This is achieved by using debug_tracecall
offered by certain node providers, the dominant being by geth. It allows you to simulate a transaction on the latest state of the blockchain and collect any information related to addresses, opcodes involved, memory accessed, value transferred, etc. that one is interested in.
A tracer is a javascript program passed in the debug_tracecall API call specifying the information you want to collect and receive in the response. You can see the script used in the current implementation here.
The steps in this stage are as follows:
- Simulate the UserOp and generate the validation result and trace output:
- entrypoint's
simulateValidation()
method is invoked for this. - Any return value apart from
REVERT
is invalid. - Additional logic checks whether the data returned is a genuine result or an error and handles it accordingly
- entrypoint's
- Parse Trace Result to ensure no security compromises are made and the bundler can not be cheated out of being reimbursed for gas:
- There's at least one call from the entrypoint
- No illegal calls are made to the entrypoint from an external contract
- A
CALL
is not made without specifying a value to an external contract - A banned opcode is not used
- There's only one
CREATE2
call from the factory address - No entity apart from Factory uses a
CREATE2
opcode - An unstaked entity is not accessing forbidden storage slots of external contracts/accounts
- All the referenced contracts have code deployed unless it's the sender of the UserOp
- Perform validity of additional meta parameters:
- Both UserOp and Paymaster Signature are valid
validAfter
is in the pastvalidUntil
is eithernull
or in the future- the
validUntil
is sufficiently in the future to not expire while being added to the chain
These complete the bulk of the checks needed to accept a UserOp.
Perform Mempool level checks
After initial validation, the bundler proceeds to add UserOperation to the valid pool which can be submitted to the chain next time it's ready to do so.
The checks performed at this stage depend on whether a previous UserOperation with the same nonce
and sender
exists already or not:
- Found a pending UserOp from the same
sender
with the samenonce
value:- It's a straightforward check that the new operation gives sufficient incentive to replace the previous UserOp. In the reference implementation, the old UserOp is replaced if the
maxPriorityFeePerGas
&maxFeePerGas
values are 1.1 times more in the new UserOp.
- It's a straightforward check that the new operation gives sufficient incentive to replace the previous UserOp. In the reference implementation, the old UserOp is replaced if the
- No Existing UserOp with the same
sender
&nonce
value found:- The reputation Status of the account, paymaster, factory & aggregator are checked first.
- The entities shouldn't be banned, throttled, or exceed the maximum allowed UserOps from an unstaked entity.
- The
sender
address shouldn't be present as thefactory
orpaymaster
for another UserOp in the Mempool - The
factory
orpaymaster
addresses shouldn't be present assender
for another UserOp in the Mempool
Bundling Phase
Depending on the bundler, certain conditions will trigger the bundler to package the pending UserOps from the pool in a bundle and add them to the chain. The trigger condition could be as simple as submitting every UserOp to the chain the moment it is added to the mempool to wait for a certain period or cumulative gasFee
reward from UserOps.
Due to the delay, UserOps could become invalid, thus being dropped or never being added to a bundle.
In this section, we break down what happens when a bundler decides to create a new bundle from the pool of pending UserOps.
Steps while adding UserOp to the MempoolCreate Bundle Checks
The process to select UserOperations into the next bundle is as follows:
- Sort the pending UserOps in decreasing order of incentive. In the reference implementation, UserOperations are sorted based on the
maxPriorityFeePerGas
value. - Each UserOperation in the sorted list is iterated over. While iterating, the following checks are made:
- If the
paymaster
orfactory
is banned, the UserOperation is dropped from the mempool - If the
paymaster
orfactory
has been throttled to limit the number of UserOperations from them, the UserOperation is skipped for the current bundle - If there's already a UserOperation from the same
sender
added to the bundle in the previous iterations, the UserOperation is skipped for the current bundle. - The UserOperation is again validated by simulating it. If the simulation fails now, the UserOperation is dropped from the pool
- If the UserOperation accesses the storage of a
sender
from the UserOperations already included from previous iterations, it is skipped - If the cumulative gas from all the UserOps accepted so far is less than the maximum Gas the bundle can have, skips the current UserOperation and stops iterating over the remaining ones.
- Skips the UserOperation if the
paymaster
balance is not sufficient to sponsor all the UserOperations if the current is also added to the bundle
- If the
The UserOps selected after the iteration process ends are added to a bundle and sent as a transaction to the entrypoint to be added to the chain and later removed from the mempool.
What's Next
Try running a bundler yourself. You can try the Eth-Infinitism Bundler to get started.
Remember you can use the JiffyScan interface to share your UserOps with your friends and community.
Also, if you need real-time data on 4337, do check out our leading API.
We will be releasing more deep dives, walkthroughs, and quickstart tutorials for 4337 regularly in the coming weeks. Follow me on Twitter or JiffyScan to stay updated.