What happens to a UserOp within a Bundler?

What happens to a UserOp within a Bundler?

·

7 min read

Introduction

Following our previous post on UserOperation CallData, several readers asked, "Why do we need a bundler when I can just relay the UserOp directly to the entrypoint from an Externally Owned Account (EOA)?" To answer this, let's break down the checks a bundler performs upon receiving a UserOperation through its RPC URL.

Special thanks to the Ethereum Foundation and ERC4337 community for their support. JiffyScan and these articles wouldn't be possible without their help.

Overview

A bundler accepts signed calldata representing a UserOperation and submits it to the blockchain (triggering entrypoint.handleOps()), possibly alongside other independent UserOperations. This process can be divided into two phases:

  1. Pre-Bundling Phase: Validating a UserOperation before adding it to the pool of operations ready to create a bundle.

  2. Bundling Phase: Submitting a selected subset of UserOperations to be added to the blockchain.

Note: We’ve used Eth-infinitism’s bundler as a reference for this post.

Pre-Bundling Phase

When a UserOperation is received by a bundler’s RPC endpoint, several checks are performed before it can be added to the set of valid UserOperations that are ready to be submitted to the blockchain. These checks are conducted in the following stages:

  1. Validate Input Parameters: Ensure all required fields in the UserOperation are valid.

  2. Simulate UserOperation and Validate the Trace: Check the potential impact of the operation on the blockchain’s state and confirm it meets the necessary restrictions.

  3. Perform Mempool Level Checks: Verify the operation's readiness for inclusion in a bundle.

Pre-Bundling Phase

When a UserOperation is received by the bundler's RPC endpoint, it undergoes several checks before being added to the set of valid UserOperations ready for submission to the blockchain. These checks occur in the following stages:

1. Validate Input Parameters

The bundler verifies the presence and validity of all the fields of a UserOperation at an individual level. Key checks include:

  • Ensuring the entrypoint parameter in the RPC call is not null and is supported by the bundler.

  • Verifying the UserOperation is not null and that all fields have valid hexadecimal values, such as:

    • Sender

    • nonce

    • initCode

    • callData

    • paymasterAndData

    • signature (if required)

  • Checking that all gas parameters are present and have valid hexadecimal values, including:

    • preVerificationGas

    • verificationGasLimit

    • callGasLimit

    • maxFeePerGas

    • maxPriorityFeePerGas

  • Ensuring the paymasterAndData field is either 0x or has a length of at least 42 bytes (to store at least an address).

  • Ensuring the initCode field is either 0x or has a length of at least 42 bytes.

  • Confirming that preVerificationGas meets the minimum required by the bundler to process the request.

You can refer to the code for validating input parameters [here](insert link). It's worth noting that steps 1-3 are redundantly performed in the v1.6 of Eth-infinitism's bundler implementation.

2. Simulate UserOperation and Validate the Trace

Next, the bundler checks the potential impact of the operation on the blockchain's state and ensures it adheres to the standard's restrictions.

This is done using debug_traceCall provided by certain node providers, with geth being the most prominent. It allows you to simulate a transaction on the latest blockchain state and gather information about the addresses involved, opcodes used, memory accessed, value transferred, etc., according to your needs.

Note: A tracer is a JavaScript program passed in the debug_traceCall API call, specifying the information you want to collect. You can see the script used in the current implementation [here](insert link).

Steps in this stage include:

  • Simulating the UserOperation and generating the validation result and trace output by invoking the entrypoint's simulateValidation() method.

  • Ensuring the return value is not REVERT as it would be invalid.

  • Performing additional checks to determine if the returned data is a genuine result or an error and handling it accordingly.

The bundler then parses the trace results to ensure no security breaches, ensuring:

  • There is at least one call from the entrypoint.

  • No illegal calls are made to the entrypoint from external contracts.

  • Calls without specifying a value to external contracts are not made.

  • No banned opcodes are used.

  • Only the factory address makes a CREATE2 call.

  • No entity other than the factory uses the CREATE2 opcode.

  • Unstaked entities do not access forbidden storage slots of external contracts/accounts.

  • All referenced contracts have code deployed unless it is the sender of the UserOperation.

Finally, the validity of additional meta-parameters is checked, ensuring:

  • Both the UserOperation and Paymaster signatures are valid.

  • validAfter is in the past.

  • validUntil is either null or in the future.

  • validUntil is sufficiently far in the future to not expire while being added to the chain.

These checks complete the bulk of the validations needed to accept a UserOperation.

3. Mempool Level Checks

After initial validation, the bundler adds the UserOperation to the valid pool for potential submission to the blockchain during the next round.

The checks at this stage depend on whether a previous UserOperation with the same nonce and sender already exists:

  • If a pending UserOperation from the same sender with the same nonce value is found, the bundler checks if the new operation has sufficient incentive to replace the previous one. In the reference implementation, the old UserOperation is replaced if the maxPriorityFeePerGas and maxFeePerGas values are 1.1 times higher in the new UserOperation.

  • If no existing UserOperation with the same sender and nonce value is found, the reputation status of the account, paymaster, factory, and aggregator is checked first. The entities should not be banned, throttled, or exceed the maximum allowed UserOperations from an unstaked entity.

  • The sender address should not be the factory or paymaster for another UserOperation in the Mempool.

  • The factory or paymaster addresses should not be the sender for another UserOperation in the Mempool.

Bundling UserOperations in Mempool

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.

Checks while Creating a Bundle

The process to select UserOperations into the next bundle is as follows:

  1. Sort the pending UserOps in decreasing order of incentive. In the reference implementation, UserOperations are sorted based on the maxPriorityFeePerGas value.

  2. Each UserOperation in the sorted list is iterated over. While iterating, the following checks are made:

  • If the paymaster or factory is banned, the UserOperation is dropped from the mempool

  • If the paymaster or factory 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

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.

Next Steps

Try running a bundler yourself. You can try the Eth-Infinitism Bundler to get started.

Remember you can use the JiffyLabs 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 us on Twitter to stay updated.