-
Notifications
You must be signed in to change notification settings - Fork 5.8k
Add EIP: Introspection opcodes #9028
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
base: master
Are you sure you want to change the base?
Conversation
File
|
Co-authored-by: Andrew B Coathup <[email protected]>
The commit 1676c94 (as a parent of 02c9517) contains errors. |
|
||
## Specification | ||
|
||
- `TXROOT`: This opcode returns the transaction trie root of all transactions in the current block, excluding the transaction that is currently executing (which could introduce a circular dependency between the merkle tree proof and the transactions hash and thus the merkle trie root). The tx trie is already calculated for the block header. This EIP just enforces that the trie is constructed incrementally per transaction and exposes the root to the EVM. |
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.
I think I am missing something here. What does TXROOT
calculate? It is clearly not the block.transactionsRoot
, at least that is what I understand from this motivation.
If we have a block with txs A,B,C, then if I am executing B then TXROOT
should return the root without B (?) in it. How is this calculated? Should I put A at key 0, C at key 2? Or should I only include A? Since I have to recalculate the root, the 2
gas price also is too low (this would likely be ok if it is indeed the transactions root as calculated for the header). In the current spec it seems that for each transaction, the TXROOT
will return a different root. Is this correct? How should this be calculated (at which keys should the txs be placed and which txs should be included)? I am also currently not understanding why we would not want the current tx in this root. I do not see how this can create a circular dependency.
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.
Sorry it seems that I should have explained better! I already implemented it in the execution spec here which may help understand: ethereum/execution-specs#1345
So if a block contains 3 transactions A, B, and C, the transactions trie root would be as followed in each transaction:
- A: empty
- B: root([A])
- C: root([A, B])
With this we can find out the following for example:
- We can calculate the L1 state before each transaction, e.g. in transaction B we can calculate the L1 state using prev_block.state_root and re-executing A
- We know in each transaction what its position in the block is. In C we know there are 2 previous transactions so we are at the 3rd tx in the block.
I believe the gas cost of the opcode can be so low because the full transaction trie root needs to be calculated for the block header anyway, and so this EIP only changes so that it now incrementally builds this merkle tree. This is more expensive, but I would say still very small when the number of elements in the trie is small (which is the case for the transactions trie). The opcode would not trigger the calculation of the root (which would always be done), it simply exposes it. It is a valid point though that the opcode could trigger the calculation. Not sure if this is better or not.
The circular dependency would that the tx hash changes based on the tx input. If that input is a merkle proof showing that some tx is in the root, then putting this data in the tx changes the tx hash, which changes the tx trie root, which then would require a different merkle proof to check for inclusion!
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.
I think there is some confusion here for the terms used and I am currently not sure if we are talking about the transactions root or the state root. Just to verify here: it seems you are talking about the state root and not the tx root? The transactions trie root is the root of the MPT where you put all the serialized transactions in (in the corresponding order). It seems that here, if I call TXROOT, I get the state root after executing the previous txs (?). If this is not the case and it is actually the root of the previous transactions (so if we are in tx C then we thus apply the serialized A to key 0 and B to key 1) then we cannot know directly that C is the third (key 2) transaction. Because in order to prove this on-chain, we would need a proof that it is indeed at key 2, which means we a MPT proof which would thus contain both the serialized txs (so we can reconstruct the MPT in EVM and then see we are indeed at key 2) or in some cases hashes of the relevant MPT nodes.
Do not interpret this as hostile - I am mainly figuring out the motivation and end goal of this EIP. It seems that you want to supply the state root as it was right before executing the current transaction. Is this correct? If this is the case, how would one use this in practice? If you want to prove things (either that a storage slot changed or a value) you need to upload the MPT proof to the tx (likely as calldata) to verify it in the EVM. This would thus alter the tx (because you alter calldata) and you would thus have to re-sign the tx. This is thus only practical for the builder to do. Is this correct or am I missing something?
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.
Oh good questions! Appreciate the feedback and points for clarifications, so don't worry. :)
So the root exposed is the transactions root, but yes I'm more interested in the actual state root so I see the confusion!
Ideally the latest state root at any point during EVM execution would be available. However, the state root calculation is very expensive and is already a bottleneck calculating it once per block (which is where the delayed state root calculation EIP comes in to help mitigate this). So this EIP obviously does not want to make things even worse by having the state root calculating multiple times during EVM execution (and then of course making it impossible to delay the state root calculation).
Instead this EIP only exposes the previous transactions in the current block to the EVM. This doesn't require expensive expensive state root calculations, and this can still be used to get the latest state by going from the pre block state and executing the previous transactions on top of that state to get to the state before the current transaction started executing.
If this is not the case and it is actually the root of the previous transactions (so if we are in tx C then we thus apply the serialized A to key 0 and B to key 1) then we cannot know directly that C is the third (key 2) transaction. Because in order to prove this on-chain, we would need a proof that it is indeed at key 2, which means we a MPT proof which would thus contain both the serialized txs (so we can reconstruct the MPT in EVM and then see we are indeed at key 2) or in some cases hashes of the relevant MPT nodes.
Yes that is one way to do it. Another way would be to use non-inclusion proof to minimize the data needed. If the check is done through a proving system offchain these things don't matter that much.
It seems that you want to supply the state root as it was right before executing the current transaction. Is this correct?
I want to make it possible to know the latest state in any way that is efficient.
If this is the case, how would one use this in practice?
In practice I would use it for ULTRA TX for synchronous composability with the L1 where I need to know the latest L1 state to be able to generate a proof that everything has executed correctly on top of the latest state.
If you want to prove things (either that a storage slot changed or a value) you need to upload the MPT proof to the tx (likely as calldata) to verify it in the EVM. This would thus alter the tx (because you alter calldata) and you would thus have to re-sign the tx. This is thus only practical for the builder to do. Is this correct or am I missing something?
For my purposes no MPT data will be onchain, only a proof of correct execution and with the TXROOT as input to the proof. But yes, it would mostly be the builder (or sophisticated searchers) making use of this (which is already an assumption for my purposes).
--
Maybe one way to make this EIP more flexible is to step away from the merkle tree format so that the transaction data can be queried without merkle proofs and so there are fewer dependencies. For example, one could also expose the array of previous transactions directly through something like TX(index) that return the tx hash at the specified index. Maybe could be useful for other use cases though not really necessary for my own.
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.
Some questions 😄 👍
|
||
## Abstract | ||
|
||
This EIP proposes to add an opcode that enables introspection of the chain state at arbitrary points within a block in the EVM. Currently, the EVM only has access to the state of previous blocks. No block data is currently exposed to the EVM for the block it's executing in. |
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.
I get the idea but this statement is not correct, for instance TIMESTAMP
and NUMBER
are accessible.
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.
Oh right yes, I guess I mean any data about a block that is not already known at the very start of the block.
|
||
### Gas Price | ||
|
||
The precompiles are priced to match similar opcodes in the `W_base` set. |
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.
Is this intended to be a precompile or opcode?
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.
Sorry first made it a precompile but then though hmmm this does seem better as just an opcode because it's so simple and general. But open for any feedback on this!
|
||
The new opcode aims to enhance introspection capabilities within the EVM, enabling the calculation of the latest chain state offchain at any point in an Ethereum block. This is important to allow general and efficient synchronous composability with L1. Otherwise, to ensure having the latest L1 state, the state would have to be read on L1 and passed in as a separate input. This is expensive and there may be limitations on who can read the state without something like [EIP-2330](./eip-2330). | ||
|
||
This proposal allows computing the latest state from the state root of the previous block and the transactions that are in the current block. This data can then be passed into any system requiring the latest chain state where the partial block can be re-executed to compute the latest state before the start of the current transaction in a provable way. The state can be computed at arbitrary points inside the transaction as well by enforcing things inside the current transaction, this requires no additional EVM functionality. |
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.
A very long time ago, the transaction receipts included an intermediate state root. This has changed since Byzantium to the status (0 or 1) of the transaction: https://eips.ethereum.org/EIPS/eip-658. Although the intermediate state root was not accessible in the EVM, would such thing help here if we would reintroduce it?
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.
Oh interesting, I didn't know that!
Unfortunately this would not help for the cases I am trying to solve. Which is exposing this kind of data to the EVM so that it can be used in proofs that can be verified onchain.
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.
from comments and reading of EIP it seems that it is still in process of being spec-ed in correct and useful manner, so will hold off review of the PR till that completes
This EIP proposes calculating the transactions trie root while the block is being built, and adding an opcode that exposes the current transactions trie root to the EVM through an opcode.
Execution spec implementation: ethereum/execution-specs#1345