-
Notifications
You must be signed in to change notification settings - Fork 390
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
add eth_simulateV1
#484
base: main
Are you sure you want to change the base?
add eth_simulateV1
#484
Changes from 19 commits
31fcbe5
8b020ce
aaab3f8
8ab6539
61fb231
45313dd
d9411e0
06d27bd
b4ce098
0043e12
3095c08
c7ec336
c20052c
7aee5b3
8996847
e76cb57
dde3124
db721c4
aef37eb
85ad1a4
21c6dfa
e27cf9a
b597a7f
1ccfd37
f297b4f
e3332ea
e913466
8141886
2ee9fc2
42d1249
55f657c
0256a4b
de3e3f5
d7b4616
3324ce9
e56d320
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
# Multicall | ||
This document contains some extra information that couldn't be fit to the specification document directly. | ||
|
||
## Default block values | ||
Unlike `eth_call`, `eth_simulateV1`'s calls are conducted inside blocks. We don't require user to define all the fields of the blocks so here are the defaults that are assumed for blocks parameters: | ||
|
||
| parameter name | default value | | ||
-----------------|----------------------- | ||
| prevRandao | `0x0000000000000000000000000000000000000000000000000000000000000000` | | ||
| feeRecipient | `0x0000000000000000000000000000000000000000` | | ||
| mixHash | `0x0000000000000000000000000000000000000000000000000000000000000000` | | ||
| nonce | `0x0` | | ||
| extraData | `0x0000000000000000000000000000000000000000000000000000000000000000` | | ||
| difficulty | The same as the base block defined as the second parameter in the call | | ||
| gasLimit | The same as the base block defined as the second parameter in the call | | ||
| hash | Calculated normally, except for phantom blocks, see below Phantom block section | | ||
| parentHash | Previous blocks hash (the real hash, or phantom blocks hash) | | ||
| timestamp | The timestamp of previous block + 1 | | ||
| baseFeePerGas | Calculated on what it should be according to Ethereum's spec. Note: baseFeePerGas is not adjusted in the phantom blocks. | | ||
| sha3Uncles | Empty trie root | | ||
| withdrawals | Empty array | | ||
| uncles | Empty array | | ||
| blobBaseFee | Calculated on what it should be according to EIP-4844 spec. Note: blobBaseFee is not adjusted in the phantom blocks. | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not a block property, we have |
||
| number | Previous block number + 1 | | ||
| logsBloom | Calculated normally. ETH logs are not part of the calculation | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are ETH logs? For clarity it would be nice to link that these are explain further down 😄 |
||
| receiptsRoot | Calculated normally | | ||
| transactionsRoot | Calculated normally | | ||
| size | Calculated normally | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| withdrawalsRoot | Calculated normally | | ||
| gasUsed | Calculated normally | | ||
| stateRoot | Calculated normally | | ||
|
||
An interesting note here is that we decide timestamp as `previous block timestamp + 1`, while `previous block timestamp + 12` could also be an assumed default. The reasoning to use `+1` is that it's the minimum amount we have to increase the timestamp to keep them valid. While `+12` is what Mainnet uses, there are other chains that use some other values, and we didn't want to complicate the specification to consider all networks. | ||
|
||
### Phantom blocks | ||
The multicall allows you to define on what block number your calls or transactions are being executed on. E.g, consider following call: | ||
```json | ||
{ | ||
"jsonrpc": "2.0", | ||
"id": 1, | ||
"method": "eth_simulateV1", | ||
"params": [ | ||
{ | ||
"blockStateCalls": [ | ||
{ | ||
"blockOverrides": { | ||
"number": "0x64" | ||
}, | ||
}, | ||
{ | ||
"blockOverrides": { | ||
"number": "0xc8" | ||
}, | ||
} | ||
] | ||
}, | ||
"0xa" | ||
] | ||
} | ||
``` | ||
|
||
Here we want our calls to be executed in blocks 100 (`0x64`) and in 200 (`0xc8`). The block numbers can be anything as long as they are increasing and higher than the block we are building from 10 (`0xa`). Now we end up in a situation where there exists block ranges 11-99 and 101-199 that are not defined anywhere. These blocks are called "phantom blocks". What happens if you try to request block hash of any of such blocks in the EVM? How can we calculate the block hash of future blocks when we don't know the block hash of the previous block? | ||
|
||
Our solution to this problem is to define block hash of a phantom block to be: | ||
|
||
``` | ||
keccak(rlp([hash_of_previous_non_phantom_block, phantom_block_number])) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these numbers left-padded? I think adding this would be clear (there are a lot of EIPs where the spec says to either left pad them or not) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assumed that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I actually assumed it was not left-padded, so this should definitely be clarified :) |
||
``` | ||
|
||
So for example in our example, you could get block hash of block 142 as follows: | ||
``` | ||
keccak(rlp([hash of block 12, 142])) | ||
``` | ||
|
||
The phantom blocks other properties are set to their default properties as defined by the multicall specification. We came to this definition by wanting phantom block hashes to be unique if things prior to the phantom block changes, so if tooling is storing block hashes somewhere, they should remain unique if things change in the simulation. | ||
|
||
One other approach to this problem would be to really calculate the real block hashes for all the phantom blocks, but this would make generating blocks far in future really expensive, as to generate 100 phantom blocks, you would need to calculate 100 block hashes that all depend on each other. And in most cases, no one really cares about these blocks. | ||
|
||
Base fee per gas is not adjusted in the phantom blocks, their base fee remains constant. | ||
|
||
## Default values for transactions | ||
As multicall is an extension to `eth_call` we want to enable the nice user experience that the user does not need to provide all required values for a transaction. We are assuming following defaults if the variable is not provided by the user: | ||
| parameter name | description | | ||
-----------------|----------------------- | ||
| type | `0x2` | | ||
| nonce | Take the correct nonce for the account prior multicall and increment by one for each transaction by the account | | ||
| to | `null` | | ||
| from | `0x0000000000000000000000000000000000000000` | | ||
| gas limit | (blockGasLimit - SumOfGasLimitOfTransactionsWithDefinedGasLimit) / NumberOfTransactionsWithoutKnownGasLimit | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, this is smart! I had to play with this for a while to see if I could find an exception but obviously not, if you ensure the sum of all txs equals the block gas limit it works (ensure this is rounded down to really make sure there is no exception, so I would use However there is an edge case: we can get a negative gas limit, and we can also get a gas limit which cannot cover the base gas costs of 21000 (or, if you include the calldata costs or the contract creation costs this is more). This edge case can lead to invalid blocks. (Since the txs are invalid) The other case is the negative one, lets say we have 30M gas limit and we have 3 txs, one has gas limit 30M, the other has gas limit 30M as well (if we execute both they only use 100k gas), but then the 3rd one now has a negative gas limit of 30M which is obviously not possible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah right, I see we throw if this happens. |
||
| value | `0x0` | | ||
| input | no data | | ||
| gasPrice | `0x0` | | ||
| maxPriorityFeePerGas | `0x0` | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we throw for the fields set if these are not available for the tx type? (The 4 types below here, so There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (they can be set and it is fine, but might be confusing for the end user) |
||
| maxFeePerGas | `0x0` | | ||
| accessList | empty array | | ||
| blobVersionedHashes | empty array | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blob txs cannot be simulated it seems per the current spec since it cannot be overridden. |
||
|
||
## Overriding default values | ||
The default values of blocks and transactions can be overriden. For Transactions we allow overriding of variables `type`, `nonce`, `to`, `from`, `gas limit`, `value`, `input`, `gasPrice`, `maxPriorityFeePerGas`, `maxFeePerGas`, `accessList`, and for blocks we allow modifications of `number`, `time`, `gasLimit`, `feeRecipient`, `prevRandao`, `baseFeePerGas` and `blobBaseFee`: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if I have a specific use case that I want a block hash of a previous block be of some kind? Then I should be able to override What about |
||
```json | ||
"blockOverrides": { | ||
"number": "0x14", | ||
"time": "0xc8", | ||
"gasLimit": "0x2e631", | ||
"feeRecipient": "0xc100000000000000000000000000000000000000", | ||
"prevRandao": "0x0000000000000000000000000000000000000000000000000000000000001234", | ||
"baseFeePerGas": "0x14", | ||
"blobBaseFee": "0x15" | ||
}, | ||
``` | ||
All the other fields are computed automatically (eg, `stateRoot` and `gasUsed`) or kept as their default values (eg. `uncles` or `withdrawals`). When overriding `number` and `time` variables for blocks, we automatically check that the block numbers and time fields are strictly increasing (we don't allow decreasing, or duplicated block numbers or times). If the block number is increased more than `1` compared to the previous block, phantom blocks are created to fill the gaps. | ||
|
||
An interesting note here is that an user can specify block numbers and times of some blocks, but not for others. When block numbers of times are left unspecified, the default values will be used. After the blocks have been constructed, and default values are calculated, the blocks are checked that their block numbers and times are still valid. | ||
|
||
## ETH transfer logs | ||
When `traceTransfers` setting is enabled on `eth_simulateV1` The multical will return logs for ethereum transfers along with the normal logs sent by contracts. The ETH transfers are identical to ERC20 transfers, except the "sending contract" is address `0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee`. | ||
|
||
For example, here's a query that will simply send ether from one address to another (with a state override that gives us the ETH initially): | ||
```json | ||
{ | ||
"jsonrpc": "2.0", | ||
"id": 1, | ||
"method": "eth_simulateV1", | ||
"params": [ | ||
{ | ||
"blockStateCalls": [ | ||
{ | ||
"stateOverrides": { | ||
"0xc000000000000000000000000000000000000000": { | ||
"balance": "0x7d0" | ||
} | ||
}, | ||
"calls": [ | ||
{ | ||
"from": "0xc000000000000000000000000000000000000000", | ||
"to": "0xc100000000000000000000000000000000000000", | ||
"value": "0x3e8" | ||
} | ||
] | ||
} | ||
], | ||
"traceTransfers": true | ||
}, | ||
"latest" | ||
] | ||
} | ||
``` | ||
|
||
The output of this query is: | ||
```json | ||
{ | ||
"jsonrpc": "2.0", | ||
"id": 1, | ||
"result": [ | ||
{ | ||
"number": "0x4", | ||
"hash": "0x859c932c5cf0dabf8d12eb2518e063966ac1a25e2fc49f1f02574a37f358d0b5", | ||
"timestamp": "0x1f", | ||
"gasLimit": "0x4c4b40", | ||
"gasUsed": "0x5208", | ||
"feeRecipient": "0x0000000000000000000000000000000000000000", | ||
"baseFeePerGas": "0x2310a91d", | ||
"prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", | ||
"calls": [ | ||
{ | ||
"returnData": "0x", | ||
"logs": [ | ||
{ | ||
"address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", | ||
"topics": [ | ||
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", | ||
"0x000000000000000000000000c000000000000000000000000000000000000000", | ||
"0x000000000000000000000000c100000000000000000000000000000000000000" | ||
], | ||
"data": "0x00000000000000000000000000000000000000000000000000000000000003e8", | ||
"blockNumber": "0x4", | ||
"transactionHash": "0xa4d41019e71335f8567e17746b708ddda8b975a9a61f909bd3df55f4866cc913", | ||
"transactionIndex": "0x0", | ||
"blockHash": "0x859c932c5cf0dabf8d12eb2518e063966ac1a25e2fc49f1f02574a37f358d0b5", | ||
"logIndex": "0x0", | ||
"removed": false | ||
} | ||
], | ||
"gasUsed": "0x5208", | ||
"status": "0x1" | ||
} | ||
] | ||
} | ||
] | ||
} | ||
``` | ||
|
||
Here the interesting part is: | ||
```json | ||
"address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", | ||
"topics": [ | ||
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", | ||
"0x000000000000000000000000c000000000000000000000000000000000000000", | ||
"0x000000000000000000000000c100000000000000000000000000000000000000" | ||
], | ||
"data": "0x00000000000000000000000000000000000000000000000000000000000003e8", | ||
``` | ||
In the observed event, the sender address is denoted as the `0xee...` address. The first topic (`0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef`) aligns with the event signature `Transfer(address,address,uint256)`, while the second topic (`0x000000000000000000000000c000000000000000000000000000000000000000`) corresponds to the sending address, and the third topic (`0x000000000000000000000000c100000000000000000000000000000000000000`) represents the receiving address. The quantity of ETH transacted is stored in the data field. | ||
|
||
The ETH logs will contain following types of ETH transfers: | ||
- Transfering ETH from EOA | ||
- Transfering ETH via contract | ||
- Selfdestructing contract sending ETH | ||
|
||
But not following ones: | ||
- Gas fees | ||
- Multicalls eth balance override | ||
|
||
ETH logs are not part of the calculation for logs bloom filter. Also, similar to normal logs, if the transaction sends ETH but the execution reverts, no log gets issued. | ||
|
||
## Validation | ||
The multicall has a feature to enable or disable validation with setting `Validation`, by default, the validation is off, and the multicall mimics `eth_call` with reduced number of checks. Validation enabled mode is intended to give as close as possible simulation of real EVM block creation, except there's no checks for transaction signatures and we also allow one to send a direct transaction from a contract. | ||
|
||
## Failures | ||
It is possible that user defines a transaction that cannot be included in the Ethereum block as it breaks the rules of EVM. For example, if transactions nonce is too high or low, baseFeePerGas is too low etc. In these situations the execution of multicall ends and an error is returned. | ||
|
||
## Version number | ||
The method name for multicall `eth_simulateV1` the intention is that after release of multicall, if new features are wanted the `eth_simulateV1` is kept as it is, and instead `eth_simulateV2` is published with the new wanted features. | ||
|
||
## Clients can set their own limits | ||
Clients may introduce their own limits to prevent DOS attacks using the method. We have thought of three such standard limits | ||
- How many blocks can be defined in `BlockStateCalls`. The suggested default for this is 256 blocks | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would also argue that there is a max number, because if one allows to create only one block, then one can still request block very high number and this would impose a lot of phantom block hashings. |
||
- A global gas limit (similar to the same limit for `eth_call`). The multicall cannot exceed the global gas limit over its lifespan | ||
- The clients can set their own limit on how big the input JSON payload can be. A suggested default for this is 30mb |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: do we throw here on post-merge blocks if the uncles are non-empty? There can be no uncles post-merge and the uncle hash is therefore also stubbed to the empty trie hash.