You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Store last HISTORY_SERVE_WINDOW historical block hashes in the storage of a system contract as part of the block processing logic. Furthermore this TIP has no impact on BLOCKHASH resolution mechanism (and hence its range/costs etc).
Motivation
TVM implicitly assumes the client has the recent block (hashes) at hand. This assumption is not future-proof given the prospect of stateless clients. Including the block hashes in the state will allow bundling these hashes in the witness provided to a stateless client.
Extending the range of blocks which BLOCKHASH can serve (BLOCKHASH_SERVE_WINDOW) would have been a semantics change. Using extending that via this contract storage would allow a soft-transition. Rollups can benefit from the longer history window through directly querying this contract.
A side benefit of this approach could be that it allows building/validating proofs related to last HISTORY_SERVE_WINDOW ancestors directly against the current state.
Specification
Parameter
Value
BLOCKHASH_SERVE_WINDOW
256
HISTORY_SERVE_WINDOW
8191
SYSTEM_ADDRESS
0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE
HISTORY_STORAGE_ADDRESS
0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfF
This TIP specifies for storing last HISTORY_SERVE_WINDOW block hashes in a ring buffer storage of HISTORY_SERVE_WINDOW length. Note that HISTORY_SERVE_WINDOW > BLOCKHASH_SERVE_WINDOW (which remains unchanged).
Block processing
At the start of processing any block where this TIP is active (ie. before processing any transactions), call to HISTORY_STORAGE_ADDRESS as SYSTEM_ADDRESS with the 32-byte input of block.parent.hash, a gas limit of 30_000_000, and 0 value. This will trigger the set() routine of the history contract. This is a system operation and therefore:
the call must execute to completion
the call does not count against the block's gas limit
if no code exists at HISTORY_STORAGE_ADDRESS, the call must fail silently
Note: Alternatively clients can choose to directly write to the storage of the contract but TVM calling the contract remains preferred. Refer to the rationale for more info.
Note that, it will take HISTORY_SERVE_WINDOW blocks after the EIP's activation to completely fill up the ring buffer. The contract will only contain the parent hash of the fork block and no hashes prior to that.
TVM Changes
The BLOCKHASH opcode semantics remains the same as before.
Block hash history contract
The history contract has two operations: get and set.The set operation is invoked only when the caller is equal to the SYSTEM_ADDRESS.
get
It is used from the TVM for looking up block hashes.
Callers provide the block number they are querying in a big-endian encoding.
If calldata is not 32 bytes, revert.
For any request outside the range of [block.number-HISTORY_SERVE_WINDOW, block.number-1], revert.
set
Caller provides block.parent.hash as calldata to the contract.
Set the storage value at block.number-1 % HISTORY_SERVE_WINDOW to be calldata[0:32].
Bytecode
Exact TVM assembly that can be used for the history contract:
// https://github.com/lightclient/sys-asm/blob/f1c13e285b6aeef2b19793995e00861bf0f32c9a/src/execution_hash/main.eas
caller
push20 0xfffffffffffffffffffffffffffffffffffffffe
eq
push1 0x46
jumpi
push1 0x20
calldatasize
sub
push1 0x42
jumpi
push0
calldataload
push1 0x01
number
sub
dup2
gt
push1 0x42
jumpi
push2 0x1fff
dup2
number
sub
gt
push1 0x42
jumpi
push2 0x1fff
swap1
mod
sload
push0
mstore
push1 0x20
push0
return
jumpdest
push0
push0
revert
jumpdest
push0
calldataload
push2 0x1fff
push1 0x01
number
sub
mod
sstore
stop
Deployment
The contract will be deployed as a system contract at the predefined address HISTORY_STORAGE_ADDRESS during the network upgrade that activates this TIP. The deployment will be handled directly by the protocol rather than through a regular contract creation transaction.
Gas costs
The system update at the beginning of the block, i.e. process_block_hash_history (or via system call to the contract with SYSTEM_ADDRESS caller), will not warm the HISTORY_STORAGE_ADDRESS account or its storage slots . As such the first call to the contract will pay for warming up the account and storage slots it accesses.To clarify further any contract call to the HISTORY_STORAGE_ADDRESS will follow normal TVM execution semantics.
Since BLOCKHASH semantics doesn't change, this TIP has no impact on BLOCKHASH mechanism and costs.
Rationale
Very similar ideas were proposed before. This TIP is a simplification, removing two sources of needless complexity:
Having a tree-like structure with multiple layers as opposed to a single list
Writing the TIP in TVM code
Serial unbounded storage of hashes for a deep access to the history
However after weighing pros and cons, we decided to go with just a limited ring buffer to only serve the requisite HISTORY_SERVE_WINDOW.
Second concern was how to best transition the BLOCKHASH resolution logic post fork by:
Either waiting for HISTORY_SERVE_WINDOW blocks for the entire relevant history to persist
Storing of all last HISTORY_SERVE_WINDOW block hashes on the fork block.
We choose to go with the former. It simplifies the logic greatly. It will take roughly a day to bootstrap the contract. Given that this is a new way of accessing history and no contract depends on it, it is deemed a favorable tradeoff.
Inserting the parent block hash
Clients have generally two options for inserting the parent block hash into state:
Performing a system call to HISTORY_STORAGE_ADDRESS and letting that handle the storing in state.
Avoid TVM processing and directly write to the state trie.
The ring buffer data structure is sized to hold 8191 hashes. In other system contracts a prime ring buffer size is chosen in because using a prime as the modulus ensures that no value is overwritten until the entire ring buffer has been saturated and thereafter, each value will be updated once per iteration, regardless of if some slot are missing or the slot time changes. However, in this TIP the block number is the value in the modulo operation and it only ever increases by 1 each iteration. Which means we can be confident that the ring buffer will always remain saturated.
For consistency with other system contracts, we have decided to retain the buffer size of 8191. Given the current mainnet values, 8191 roots provides about a day of coverage. This also gives users plenty of time to make a transaction with a verification against a specific hash and get the transaction included on-chain.
Backwards Compatibility
This TIP introduces backwards incompatible changes to the block validation rule set. But neither of these changes break anything related to current user activity and experience.
Test Cases
TBD
Security Considerations
Having contracts (system or otherwise) with hot update paths (branches) poses a risk of "branch" poisioning attacks where attacker could sprinkle trivial amounts of trx around these hot paths (branches). But it has been deemed that cost of attack would escalate significantly to cause any meaningful slow down of state root updates.
tip: 719
title: Implement EIP-2935:Serve historical block hashes from state
author: [email protected]
status: Draft
type: Standards Track
category: Core
created: 2025-01-20
Summary
As part of the Ethereum Pectra upgrade, EIP-2935:Serve historical block hashes from state is required to be implemented in TRON.
Abstract
Store last
HISTORY_SERVE_WINDOW
historical block hashes in the storage of a system contract as part of the block processing logic. Furthermore this TIP has no impact onBLOCKHASH
resolution mechanism (and hence its range/costs etc).Motivation
TVM implicitly assumes the client has the recent block (hashes) at hand. This assumption is not future-proof given the prospect of stateless clients. Including the block hashes in the state will allow bundling these hashes in the witness provided to a stateless client.
Extending the range of blocks which
BLOCKHASH
can serve (BLOCKHASH_SERVE_WINDOW
) would have been a semantics change. Using extending that via this contract storage would allow a soft-transition. Rollups can benefit from the longer history window through directly querying this contract.A side benefit of this approach could be that it allows building/validating proofs related to last
HISTORY_SERVE_WINDOW
ancestors directly against the current state.Specification
BLOCKHASH_SERVE_WINDOW
256
HISTORY_SERVE_WINDOW
8191
SYSTEM_ADDRESS
0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE
HISTORY_STORAGE_ADDRESS
0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfF
This TIP specifies for storing last
HISTORY_SERVE_WINDOW
block hashes in a ring buffer storage ofHISTORY_SERVE_WINDOW
length. Note thatHISTORY_SERVE_WINDOW
>BLOCKHASH_SERVE_WINDOW
(which remains unchanged).Block processing
At the start of processing any block where this TIP is active (ie. before processing any transactions), call to
HISTORY_STORAGE_ADDRESS
asSYSTEM_ADDRESS
with the 32-byte input ofblock.parent.hash
, a gas limit of30_000_000
, and0
value. This will trigger theset()
routine of the history contract. This is a system operation and therefore:HISTORY_STORAGE_ADDRESS
, the call must fail silentlyNote: Alternatively clients can choose to directly write to the storage of the contract but TVM calling the contract remains preferred. Refer to the rationale for more info.
Note that, it will take
HISTORY_SERVE_WINDOW
blocks after the EIP's activation to completely fill up the ring buffer. The contract will only contain the parent hash of the fork block and no hashes prior to that.TVM Changes
The
BLOCKHASH
opcode semantics remains the same as before.Block hash history contract
The history contract has two operations:
get
andset
.Theset
operation is invoked only when thecaller
is equal to theSYSTEM_ADDRESS
.get
It is used from the TVM for looking up block hashes.
HISTORY_SERVE_WINDOW
, block.number-1], revert.set
block.parent.hash
as calldata to the contract.block.number-1 % HISTORY_SERVE_WINDOW
to becalldata[0:32]
.Bytecode
Exact TVM assembly that can be used for the history contract:
Deployment
The contract will be deployed as a system contract at the predefined address
HISTORY_STORAGE_ADDRESS
during the network upgrade that activates this TIP. The deployment will be handled directly by the protocol rather than through a regular contract creation transaction.Gas costs
The system update at the beginning of the block, i.e.
process_block_hash_history
(or via system call to the contract withSYSTEM_ADDRESS
caller), will not warm theHISTORY_STORAGE_ADDRESS
account or its storage slots . As such the first call to the contract will pay for warming up the account and storage slots it accesses.To clarify further any contract call to theHISTORY_STORAGE_ADDRESS
will follow normal TVM execution semantics.Since
BLOCKHASH
semantics doesn't change, this TIP has no impact onBLOCKHASH
mechanism and costs.Rationale
Very similar ideas were proposed before. This TIP is a simplification, removing two sources of needless complexity:
However after weighing pros and cons, we decided to go with just a limited ring buffer to only serve the requisite
HISTORY_SERVE_WINDOW
.Second concern was how to best transition the BLOCKHASH resolution logic post fork by:
HISTORY_SERVE_WINDOW
blocks for the entire relevant history to persistHISTORY_SERVE_WINDOW
block hashes on the fork block.We choose to go with the former. It simplifies the logic greatly. It will take roughly a day to bootstrap the contract. Given that this is a new way of accessing history and no contract depends on it, it is deemed a favorable tradeoff.
Inserting the parent block hash
Clients have generally two options for inserting the parent block hash into state:
HISTORY_STORAGE_ADDRESS
and letting that handle the storing in state.The latter option is as follows:
Size of ring buffers
The ring buffer data structure is sized to hold 8191 hashes. In other system contracts a prime ring buffer size is chosen in because using a prime as the modulus ensures that no value is overwritten until the entire ring buffer has been saturated and thereafter, each value will be updated once per iteration, regardless of if some slot are missing or the slot time changes. However, in this TIP the block number is the value in the modulo operation and it only ever increases by 1 each iteration. Which means we can be confident that the ring buffer will always remain saturated.
For consistency with other system contracts, we have decided to retain the buffer size of 8191. Given the current mainnet values, 8191 roots provides about a day of coverage. This also gives users plenty of time to make a transaction with a verification against a specific hash and get the transaction included on-chain.
Backwards Compatibility
This TIP introduces backwards incompatible changes to the block validation rule set. But neither of these changes break anything related to current user activity and experience.
Test Cases
TBD
Security Considerations
Having contracts (system or otherwise) with hot update paths (branches) poses a risk of "branch" poisioning attacks where attacker could sprinkle trivial amounts of trx around these hot paths (branches). But it has been deemed that cost of attack would escalate significantly to cause any meaningful slow down of state root updates.
Copyright
Copyright and related rights waived via CC0.
The text was updated successfully, but these errors were encountered: