-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
Rationale
We want to request for a new rpc endpoint eth_callMany
, which provides a flexible interface for users to simulate arbitrary number of transactions at an arbitrary blockchain index. It's extremely useful for getting the intermediate evm state of a blockchain. For regular eth_call
, we could only get the end state after all of the transactions in that block. We want an api that's similar with eth_callBundle
but enables the simulations of transactions in the middle of a block. Here is the specification for this API.
Specification
eth_callMany
Executes a list of bundles (a bundle if a collection of transactions), without creating transactions on the blockchain. The eth_callMany
method can be used similarly as eth_call
besides three major distinctions.
-
The method supports simulating transactions at a intermediate state of a block (e.g. inheriting the block state after executing a portion of transactions in that block)
-
The method supports simulating multiple transactions with sequential dependencies. The transactions could be separated into different blocks.
-
The method supports overwrites for block headers (e.g. coinbase, difficulties, blockHash, and etc.)
Parameters
The method takes 3 parameters: a list of bundles where each bundle has a list of unsigned transactions objects and an optional setting for the block header, a simulation context which includes the block identifier, the transaction index, and the standard state overrides, and an optional timeout.
Bundles
- a list of Bundle
- Bundle
transactions
: A list of unsigned transaction call objects following the same format aseth_call
.blockOverride
: An optional overwrite of the block header
Field | Type | Bytes | Optional | Description |
---|---|---|---|---|
BlockNumber | Object | NA | Yes | Block number. Defaults to the block number of the first simulated block. Each following block increments its block number by 1 |
BlockHash | Dict[blockNumber, Hash] | NA | Yes | A dictionary that maps blockNumber to a user-defined hash. It could be queried from the solidity opcode BLOCKHASH |
Coinbase | Address | 20 | Yes | The address of miner. Defaults to the original miner of the first simulated block. |
Timestamp | Object | 8 | Yes | The timestamp of the current block. Defaults to the timestamp of the first simulated block. Each following block increments its timestamp by 1. |
Difficulty | Object | 4 | Yes | The difficulty of the current block. Defaults to the difficulty of the first simulated block. |
GasLimit | Object | 4 | Yes | The gas limit of the current block. Defaults to the difficulty of the first simulated block. |
BaseFee | Object | 32 | Yes | The base fee of the current block. Defaults to the base fee of the first simulated block. |
[{ "transactions": [txn1, txn2, txn3], "blockOverride": {
"blockHash": {
10458679: "0x0000000000000000000000000000000000000000000000000000000000000000"
}}
}, bundle2]
Object
- Simulation Context
-
blockNumber :
Quantity|Tag
- Block Number or the stringlatest
orpending
.- The block number is mandatory and defines the context (state) against which the specified transaction should be executed. It is not possible to execute calls against reorged blocks.
-
transactionIndex :
Number
- The transaction index of the simulated transactions- The transaction index is optional. The default value of the transaction index is -1, which would place the simulated transactions after the entire block.
Example:
{
'blockNumber': "latest",
"transactionIndex": 0,
}
Object
- State Overrides
The state override set is an optional address-to-state mapping, which follows theeth_call
standard.
Example:
{
"0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3": {
"balance": "0xde0b6b3a7640000"
}
}
Number
- Timeoff
The upper limit running time for the simulations in milliseconds. Defaults to 5000
milliseconds.
Return Values
The method returns a list of list of Binary
containing either the return value of the executed contract call or the reverted error code.
Simple Example
{
"id" : 1,
"jsonrpc": "2.0",
"result": [[{"value": "0x0000000000000000000000000000000000000000000000000000000000000001"}, {"error": "execution reverted"}]]
}
Example Usage
For some AMM pools, there might be multiple swap transactions for a pariticular pool in one block. Taking Uniswap V2 as an example, it's impossible to get the intermediate reserves of the pool using getReserves()
function call. This is a toy example of how users might be able to use the new rpc call to get the intermediate states of a smart contract. The second transaction is the demonstration of how users might change the block header.
reserve_tx = {
"to": "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc", // address of WETH/USDC Uniswap V2 Pool
"gas": "0x13880",
"maxPriorityFeePerGas": "0x59682f00",
"maxFeePerGas": "0x28ade5e73c",
"data": "0x0902f1ac", // function selector for getReserves()
}
blockNum_tx = {
"to": "0x0000000000000000000000000000000000000000",
"gas": "0x13880",
"maxPriorityFeePerGas": "0x59682f00",
"maxFeePerGas": "0x28ade5e73c",
"data": "0x7f6c6f10" // function selector for getBlockNum() from the EVMContext contract
}
params = [
[{"transactions": [reserve_tx, blockNum_tx], "blockOverride":{"blockNumber": "0xe39dd0"}}],
{"blockNumber": "0xe39ddf","transactionIndex": 234},
{"0x0000000000000000000000000000000000000000": {"code": EVMContext.deployedBytecode}
]
// EVMContext is a contract that can query the block context
$ curl --data '{"method":"eth_callMany","params":params,"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8545
{
"id" : 1,
"jsonrpc": "2.0",
"result": [[{"value": "0x000000000000000000000000000000000000000000000000000048fc0e88a9d600000000000000000000000000000000000000000000092227c18803e8d401a500000000000000000000000000000000000000000000000000000000629e6eed"}, {"value": "0x0000000000000000000000000000000000000000000000000000000000e39dd0"}]]
}
The result of the first call is the intermediate reserves of the WETH/USDC Uniswap V2 Pool before the 234th transaction. The result of the second call is the overwritten block number from the block override.
debug_traceCallMany
The specification for debug_traceCallMany
is quite similar with eth_callMany
and we follow the go-ethereum's standard for the trace config.
Implementation
We have a proof-of-concept implementation at https://github.com/hrthaowang/erigon/tree/callIntraBundleAlpha. We'd love to hear some feedbacks on this specification from the Erigon's team and we are happy to upstream it.