HIP-415: Introduction Of Blocks
Author | Daniel Ivanov, Ivan Kavaldzhiev, Steven Sheehy |
---|---|
Working Group | Danno Ferrin, Richard Bair, Mitchell Martin |
Discussions-To | https://github.com/hashgraph/hedera-improvement-proposal/discussions/434 |
Status | Final ⓘ |
Needs Council Approval | Yes ⓘ |
Review period ends ⓘ | Tue, 17 May 2022 07:00:00 +0000 |
Type | Standards Track ⓘ |
Category | Core ⓘ |
Created | 2022-03-28 |
Updated | 2023-02-01 |
Requires | 435 |
Release | v0.26.0 |
Table of Contents
Abstract
Specifies how to introduce and formalize the concepts of Blocks in Hedera Hashgraph so that it can be used as a foundation on which further interoperability with existing DLT networks and infrastructure can be built.
Motivation
The concept of blocks is a vital part of the existing Ethereum infrastructure and, as such, the introduction of a standard mapping between existing transaction ordering in Hedera and the notion of blocks can be considered a foundational step towards greater interoperability with EVM based tooling, explorers, exchanges and wallet providers.
Rationale
Hedera must have a single consistent answer to what transactions belong to a block and the identifying hash and number for that block. This is required for two reasons:
- The Smart Contracts Service to have the context of a
number
and thehash
of the current block while running the EVM bytecode. - Mirror Nodes to have the information in order to implement standard JSON RPC endpoints and be able to answer queries such as:
- Getting current block number
- Getting block by hash/number
- Getting a list of transactions for a block by number/hash
- Getting logs based on block number filtering
Design Goal #1 Minimize Changes
The Block concept should fit naturally into the existing processes, mechanisms and state updates. It must keep the same responsibilities between consensus, services and mirror nodes.
Design Goal #2 Lightweight
The Block concept must not add a lot of complexity and performance overhead to the processing of transactions. It must have minimal impact on the TPS of the network.
Based on the described design goals above, the outlined specification defines that block properties are to be computed and populated at different points in time and by different components of the network, based on their responsibility.
Specification
Definitions
block
→Record file
containing allRecord Stream Objects
for a given time frame. Block times are to be at leasthedera.recordStream.logPeriod
seconds. The genesis block and therefore number (blockNumber=0
) is considered the stream start date with the first RCD file exported fromservices
nodes.block number
→ consecutive number of theRecord file
that is being incremented by1
for every newRecord file
. For already existing networks, this value will be initially bootstrapped throughMirror Nodes
and after that maintained by services nodes.block hash
→32 byte
prefix (out of48 bytes
) of the running hash of the lastRecord Stream Object
from the previousRecord File
block timestamp
→ Instant of consensus timestamp of the firsttransaction
/Record Stream Object
in theRecord file
.
Platform
Adapt TimestampStreamFileWriter
to include blockNumber
in the Record File
. Introduce a new field
firstConsensusTimeInCurrentFile
to be used as a marker when to start a new Record file. Use the field
lastConsensusTimestamp
to keep track of the last-seen consensus timestamp that was processed. In this way, we can
ensure that we have at least 1000ns
difference between the last processed transaction before a new file is created.
The unit of time to be used for those 2 properties is nanos
. In this way if we have a parent transaction
with at
least one child precompile transaction
they all will be included in the same block
/Record file
. Otherwise, we
might have a corner case where a parent transaction
is included in one block, and its child precompile transaction
falls into the next block since it will increase the consensus timestamp
with 1ns
. Therefore, the algorithm for
checking whether to start a new file would be the following:
A new Record Stream Object
enters addObject(T object)
in TimestampStreamFileWriter
. It has a consensus timestamp
T
. We create a new file only if both (1) and (2) conditions are met:
T - lastConsensusTime > 1000ns
T - firstConsensusTimeInCurrentFile > 2s
or
if lastConsensusTime
or firstConsensusTimeInCurrentFile
is null
Services
Services are to update the processing logic of transactions so that it supports logic for determining new record file
periods, incrementing block
number and keeping block
relevant data. The proposed solution specifies a long
field
to be
used for the block number counter, incremented every hedera.recordStream.logPeriod
seconds. Using a signed 32-bit int
would result in the block number rolling over in 140 years (if current 2-second length is kept). Sub-second block
lengths would exhaust that number well within the operational lifetime of typical networks. A signed 64-bit integer
provides a much longer timeframe.
Pseudo-code of the record streaming algorithm
Properties in State:
- blockHashes `map(number -> keccak256(RunningHash))` - stores the hashes of the last 256 blocks
- blockNumber `long` - stores the current block number
- blockTimestamp - `Instant` - stores the timestamp of the block
handleTransaction() {
...
bool `newBlock` = shouldCreateNewBlock() {
if (`currentTS` - `lastConsensusTime` > `1000ns` && `currentTS` - `blockTimestamp` > `hedera.recordStream.logPeriod`) return `true`; else `false`
}
if (`newBlock`) {
`blockHashes[blockNumber] = keccak256(runningHash)` // `runningHash` is stored in `RecordStreaming`. It is a running hash of the last processed RSO
delete `blockHashes[blockNumber - 256]`
`blockNumber++`
`blockTimestamp = currentTS`
}
processTransaction(tx)
...
}
Migration:
`blockNumber = bootstrapLastBlockNumber`, where `bootstrapLastBlockNumber` is a system property added on startup.
`blockTimestamp = bootstrapLastBlockTimestamp`, where `bootstrapLastBlockTimestamp` is a system property added on startup.
number
, timestamp
and hash
(map of the 256 most recent blocks) must be available during transaction execution
since the following opcodes are to be supported as per the EVM specification:
BLOCKHASH
→ Accepts the blockNUMBER
for which to return thehash
. Valid range is the last256
blocks (not including the current one)NUMBER
→ Returns the current block numberTIMESTAMP
→ Returns the unix timestamp of the current block
Record File
Once HIP-435 is implemented, record files are to be migrated to protobufs. New long blockNumber
property is to be
added in the record stream.
It is required for services
to propagate this property to mirror nodes
since there are partial mirror nodes
,
that don’t keep the full history from the first record file
. Due to that, they are unable to calculate the
block number
, thus all other block properties
. Block hash
and timestamp
will not be included in the record files,
since block hash
is the 32 byte
prefix (out of 48 bytes
) of the running hash of the last Record Stream
Object
from the previous Record File
and timestamp
is 1st Record Stream Object
’s consensus timestamp
.
With the introduction of the new version of Record Stream Object
s, the respective libraries for state proofs must be updated:
- https://github.com/hashgraph/hedera-mirror-node/tree/main/hedera-mirror-rest/check-state-proof
- https://github.com/hashgraph/hedera-state-proof-verifier-go
Mirror Nodes
Based on the updates specified above, the record stream objects will pass all the necessary information for mirror nodes
to build Record files
/blocks
and store enough data about them to be able to answer all block related queries outlined above.
To do this they will need to read and store the number
specified in the Record file
. For old record files, they will
be derived through a migration process as specified below.
Migration
Record files prior to the introduction of the block properties will not expose block information. Mirror
nodes that have a full testnet
and mainnet
history will maintain the most recent consensus timestamp
to block number mapping. The values will be retrieved from mirror nodes that have a full history
from stream start. Additionally, a hedera.mirror.importer.startBlockNumber
property will be added to be able
to customize the block number for new mirror nodes just starting up.
A repeatable database migration would be added to correct historical record files for partial mirror nodes. It would work as follows:
- Skip if not
testnet
ormainnet
- Look up the block in the database associated with the latest hardcoded timestamp for the environment.
- If the row’s block number matches then there’s nothing to do.
- If the row’s block number does not match, calculate the offset and update every row with that offset.
Later, after the block number is included in the record file, the mirror node will dynamically detect the presence of the first record file with block information and re-run the same migration to adjust historical values.
List Blocks REST API
A new list block REST API will be added to support the ability to search for block information. Note it only contains aggregate information about the blocks. It’s expected that users use the returned information like timestamps to query the transaction or contract results REST APIs for details about the transactions within the block.
{
"blocks": [{
"count": 4,
"gas_limit": 150000000,
"gas_used": 50000000,
"hapi_version": "0.24.0",
"hash": "0xa4ef824cd63a325586bfe1a66396424cd33499f895db2ce2292996e2fc5667a69d83a48f3883f2acab0edfb6bfeb23c4",
"logs_bloom": "0x549358c4c2e573e02410ef7b5a5ffa5f36dd7398",
"name": "2022-04-07T16_59_23.159846673Z.rcd",
"number": 19533336,
"previous_hash": "0x4fbcefec4d07c60364ac42286d5dd989bc09c57acc7370b46fa8860de4b8721e63a5ed46addf1564e4f8cd7b956a5afa",
"size": 8489,
"timestamp": {
"from": "1649350763.159846673",
"to": "1649350763.382130000"
}
}],
"links": {
"next": null
}
}
Note: gas_limit
won’t be available in initial implementations.
Query parameters:
block.number
- The block number. Used as the primary key in pagination. Supportseq
,gt
,gte
,lt
,lte
operators.limit
- The maximum number of results to return. Defaults to 25 and max 100 allowed.order
- Sort by the block number. Value ofasc
ordesc
with a default ofdesc
.timestamp
- The consensus timestamp of the last transaction in the block. Supportseq
,gt
,gte
,lt
,lte
operators.
Get Block REST API
The new mirror node /api/v1/blocks/{hashOrNumber}
REST API will return the block information associated with a specific
block. The path parameter will accept either a block hash or block number to uniquely identify a specific block. The block
hash will accept a 64 or 96 hex-encoded string with a 0x
prefix. The JSON response will be the same as
/api/v1/blocks
but without the pagination structure:
{
"count": 4,
"gas_limit": 150000000,
"gas_used": 50000000,
"hapi_version": "0.24.0",
"hash": "0xa4ef824cd63a325586bfe1a66396424cd33499f895db2ce2292996e2fc5667a69d83a48f3883f2acab0edfb6bfeb23c4",
"logs_bloom": "0x549358c4c2e573e02410ef7b5a5ffa5f36dd7398",
"name": "2022-04-07T16_59_23.159846673Z.rcd",
"number": 19533336,
"previous_hash": "0x4fbcefec4d07c60364ac42286d5dd989bc09c57acc7370b46fa8860de4b8721e63a5ed46addf1564e4f8cd7b956a5afa",
"size": 8489,
"timestamp": {
"from": "1649350763.159846673".
"to": "1649350763.382130000"
}
}
Query parameters: None
Note: gas_limit
won’t be available in initial implementations.
List Contract results REST API
A new /api/v1/contracts/results
mirror node REST API will be added to be able to search for contract results
across addresses and blocks. By default, this API will only show transactions that originate from an EOA
(externally owned accounts). Its JSON response will be identical to /api/v1/contracts/{id}/results
. The
contract ID/address will not be supported as a filter option as it is assumed users will use
/api/v1/contracts/{id}/results
if that functionality is desired. It will support the following query parameters:
block.number
- The block number in base 10 or hex format w/0x
prefix. Onlyeq
operator is supported.block.hash
- The 32 or 48 byte block hash. With or without optional0x
prefix. Onlyeq
operator is supported.internal
- Whether internal/child transactions should be returned. Defaults tofalse
.limit
- The maximum number of results to return. Defaults to 25 and max 100 allowed.order
- Sort by timestamp. Value ofasc
ordesc
with a default ofdesc
.timestamp
- The consensus timestamp of the transaction. Supportseq
,gt
,gte
,lt
,lte
operators. Primary key for pagination.transaction.index
- The position of the transaction within a block. Requires eitherblock.number
orblock.hash
to be present. Onlyeq
operator is supported.
So that it has parity with the new API, the existing /api/v1/contracts/{id}/results
will also be updated to add
the block.number
, block.hash
, internal
, and transaction.index
query parameters.
Block Properties
The following table specifies all block
properties and at which point they will be computed. Mirror nodes must hex
encode all properties prior to exposing them through their APIs. This table defines the properties that must be returned
through APIs from Mirror Nodes.
Property | Computed By | Description |
---|---|---|
number | Services | Stored in services state (only the current number) and exported in the record stream. It is the consecutive number of the record file that is being incremented by 1 for every new record file. The number will be initially set in services through the bootstrapping process. It is exposed to the 1) EVM during Transaction Execution through the NUMBER opcode; 2) as a new property in the record file and ingested by mirror nodes. |
timestamp | Services | Stored in services state (only the last block timestamp) and computed by services. It is the consensusTimestamp of the first transaction in the record file. It is exposed to the 1) EVM during Transaction Execution through the TIMESTAMP opcode; 2) implicitly exported in the record file through the TS of the first transaction in the record file. |
hash | Services | Stored in services (last 256 blocks). It is the 32 byte prefix of the runningHash of the previous record file. That is the running hash of the last record stream object from the previous record file. It is exposed to the 1) EVM during Transaction Execution through the BLOCKHASH opcode; 2) In the record file as the End Object Running Hash |
baseFeePerGas | Relay | Always zero, since there is no EIP-1559 style floating block capacity fees in Hedera. |
difficulty | Relay | Hardcoded to hex encoded 0. |
extraData | Relay | Hardcoded to 0x. |
gasLimit | Mirror Node | Computed by Mirror Node(s). The gas throttle limit per second multiplied by the target block time. |
gasUsed | Mirror Node | Computed by Mirror Node(s). The sum of the gasUsed value for all ContractCall and ContractCreate transactions within the block. |
logsBloom | Mirror Node | Computed by Mirror Node(s). It is the bloom filter for the logs within the block. |
miner | Relay | Hardcoded to the 0x0000000000000000000000000000000000000000 address. |
mixHash | Relay | Hardcoded to 0x. |
nonce | Relay | Hardcoded to hex-encoded 0. |
parentHash | Mirror Node | The hash of the previous block. |
receiptsRoot | Relay | Hardcoded to hex-encoded 0. |
sha3Uncles | Relay | Hardcoded to the SHA3 computation of empty array (0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347 ). |
size | Mirror Node | Computed by Mirror Node(s). The size of the record file. |
stateRoot | Relay | Hardcoded to hex-encoded 0. |
totalDifficulty | Relay | Hardcoded to hex-encoded 0. |
transactions | Mirror Node | Computed by Mirror Node(s) by ingesting the record stream and aggregating RecordStreamObjects of type ContractCall and ContractCreate within the block. |
transactionsRoot | Mirror Node | The same value as block hash . |
uncles | Relay | Hardcoded to empty array [] . |
Backwards Compatibility
The following breaking changes will be introduced with the implementation of the HIP:
BLOCKHASH
will no longer return an emptyhash
but the actualhash
of the block as per the EVM specification.NUMBER
will no longer return thetimestamp
of theblock
, but rather the properblock
number as specified in the HIP.
Security Implications
- The specification does not have any security implications.
How to Teach this
- Respective documentation will be added.
Reference Implementation
Initial POC in services
:
Rejected Ideas
Two iterations have been conducted prior to settling on this approach.
- Rounds as blocks - the first iteration was proposing that each block is the
consensus round
. It became clear that this approach is not suitable as it implied that multiple blocks are to be issued per second. That would add additional load to infrastructure providers when clients are iterating and going through blocks to query for data.- Events - the second iteration suggested that
consensus events
are to be defined asblocks
. The proposal had a much higher cognitive load (1 block = 1 record file is easier to grasp) and it required more changes to theplatform
in order to be implemented. To add on top of that, the same drawback as thehigh frequency
blocks was present as well.
- Events - the second iteration suggested that
Open Issues
- Feeding the current values of
contracts.consensusThrottleMaxGasLimit
andhedera.recordStream.logPeriod
Global Dynamic Properties to Mirror Nodes so that they can compute thegasLimit
value. The values / gas throttles are available in the network file0.0.121
but only if they are overriden. In practice Mirror Nodes can update the value for gas/sec (consensusThrottleMaxGasLimit) and block times (log period) however, they do not have a way to feed the current/initial values. Calculating thegasLimit
:gasLimit = contracts.consensusThrottleMaxGasLimit * hedera.recordStream.logPeriod
References
Copyright/license
This document is licensed under the Apache License, Version 2.0 – see LICENSE or (https://www.apache.org/licenses/LICENSE-2.0)
Citation
Please cite this document as: