Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions EIPS/eip-7814.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
eip: 7814
title: Introspection opcodes
description: Introspection opcodes that expose the current block context to the EVM
author: Brecht Devos (@Brechtpd)
discussions-to: https://ethereum-magicians.org/t/eip-7814-introspection-precompiles/21872
status: Draft
type: Standards Track
category: Core
created: 2024-11-09
---

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

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Author

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.


## Motivation

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.

Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Author

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.


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

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Author

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!

Copy link
Copy Markdown
Member

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?

@Brechtpd Brechtpd Aug 1, 2025

Copy link
Copy Markdown
Author

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.


### Gas Cost

The gas cost for `TXROOT` is a fixed fee of `2`.

## Rationale

Simple and efficient access to the latest state of a chain is critical for composability. For synchronous composability, we need to be able to immediately prove all offchain work inside the same block. This makes it impossible to delay the proving to a later block where more information about the current block is available.
Comment thread
Brechtpd marked this conversation as resolved.

Onchain functionality to verify arbitrary state like [EIP-2330](./eip-2330) is great, but still expensive because it still requires onchain logic. This EIP makes it possible to move all verfication logic offchain without any limitations or edge cases (we need to be able to make sure ALL state is as expected, which is easy if you can simply re-execute).

It also supersedes [EIP-7793](./eip-7793.md) which only exposes the current transaction index for use in the context of encrypted mempools.

After [EIP-7793](./eip-7825.md) the transaction size will be limited. This EIP makes it possible to chain transactions that are block position dependent (i.e. multiple transactions that need to be top of block after each other).

### Gas Price

The precompiles are priced to match similar opcodes in the `W_base` set.

Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Author

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!


## Backwards Compatibility

Further discussion required.

## Test Cases

N/A

## Security Considerations

Needs discussion. <!-- TODO -->

This EIP might impact MEV.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).