Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request for a New RPC Endpoint eth_callMany #4471

Closed
hrthaowang opened this issue Jun 16, 2022 · 1 comment
Closed

Request for a New RPC Endpoint eth_callMany #4471

hrthaowang opened this issue Jun 16, 2022 · 1 comment

Comments

@hrthaowang
Copy link
Contributor

hrthaowang commented Jun 16, 2022

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.

  1. 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)

  2. The method supports simulating multiple transactions with sequential dependencies. The transactions could be separated into different blocks.

  3. 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.

  1. Bundles - a list of Bundle
  • Bundle
    • transactions: A list of unsigned transaction call objects following the same format as eth_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]

  1. Object - Simulation Context
  • blockNumber : Quantity|Tag - Block Number or the string latest or pending.

    • 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,
}
  1. Object - State Overrides
    The state override set is an optional address-to-state mapping, which follows the eth_call standard.
    Example:
{
	"0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3": {
		"balance": "0xde0b6b3a7640000"
	}
}
  1. 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.

@mandrigin
Copy link
Collaborator

@hrthaowang closing the issue since the PR was merged, feel free to reopen

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants