Skip to content
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

HIP-1056: Block Streams #1056

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Draft

HIP-1056: Block Streams #1056

wants to merge 14 commits into from

Conversation

rbair23
Copy link
Member

@rbair23 rbair23 commented Oct 1, 2024

Abstract

This HIP introduces a new output data format for consensus nodes, called Block Streams, that replaces the existing
event streams, record streams, state files, and signature files with one single stream.
Each block within the block stream is a self-contained entity, including every event and all transactions that were
part of the block, the state changes as a result of those transactions, and a
network signature (using a BLS threshold signature scheme we call TSS) that proves the block was signed by a
threshold of the network.

By including state changes, downstream services can seamlessly maintain and verify state alongside consensus nodes. This
enhancement fosters greater transparency and trust within the Hedera network. Any downstream service can independently
rebuild and verify the state of consensus nodes at the time of the block, verified by the TSS network signature. Using
this state, they can provide additional services such as state proofs, state snapshots, and more.

With the event information within blocks, downstream services can be reconstructed and the hashgraph algorithm replayed,
permitting anyone to verify the correct execution of the consensus algorithm and removes the need for the extra event
stream. In doing so Hedera users gain comprehensive visibility into network activities through an easily consumable
format that can be delivered with low latency.

A key design criteria is for the block stream to be easily consumed by any programming language, and with minimal
complexity or dependencies. For example, state data can be utilized for basic queries without having to reconstruct a
merkle tree.

Block streams are an upgrade to the existing RecordStream V6. Block streams restructure and aggregate the multiple
record types in record streams, including EventStream, RecordStream, and Hedera state data to produce a single
unified stream of items.

The key enhancements offered by block streams include:

  • Unified Data Stream: Block stream consolidates event streams, record streams, sidecars, and signature files into a
    single cohesive data stream.
  • State Change Data: Each block will include state change data for the given round of consensus.
  • Verifiable Proof: Each block will be independently verifiable, containing a full proof of transactions, network
    consensus and state changes.
  • Comprehensive Protobuf specification By defining blocks in protobuf, the inputs, outputs, and state changes of
    consensus nodes are clearly specified, paving the way for future implementations and greater client diversity.

With the adoption of block streams, data output by Hedera consensus nodes will be consolidated into a single, verifiable
chronicle of the network, thereby strengthening the integrity and transparency of the Hedera ecosystem.

@rbair23 rbair23 requested a review from mgarbs as a code owner October 1, 2024 20:21
Copy link

netlify bot commented Oct 1, 2024

Deploy Preview for hedera-hips ready!

Name Link
🔨 Latest commit 470b928
🔍 Latest deploy log https://app.netlify.com/sites/hedera-hips/deploys/67399d53bb06120008b79d1a
😎 Deploy Preview https://deploy-preview-1056--hedera-hips.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@rbair23 rbair23 changed the title Block Streams HIP-1056: Block Streams Oct 1, 2024
@rbair23 rbair23 marked this pull request as draft October 1, 2024 20:25
HIP/hip-1056.md Outdated Show resolved Hide resolved
HIP/hip-1056.md Outdated Show resolved Hide resolved
HIP/hip-1056.md Outdated Show resolved Hide resolved
HIP/hip-1056.md Outdated Show resolved Hide resolved
retain on disk, without breaking the cryptographic integrity of the block stream. Different block node operators in
different legal jurisdictions can make different decisions about what data to retain. Or, block node operators may
subset the stream to retain minimal data and minimize storage costs.
- **Errata Handling**: Errata refers to software bugs where a node executed the transaction correctly but did not record
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternative option would be the given block node to be temporarily excluded from the network of block nodes and it's block stream invalidated. Then, upon fixing the bug, the block node operator could again join the network, sync the state and continue it's normal work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Errata are needed when a bug on the consensus node produces incorrect output. This section refers to the necessity to change the recorded block chain. I believe this has been necessary a small number of times since genesis on mainnet.

Block Nodes are not a network; each node is completely independent. Those details will be specified in a different HIP.

```

```protobuf
message CryptoTransferOutput {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The crypto create might result in auto account creation. Shouldn't we also add a record for it for those cases?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The account creation will be present in StateChanges, no additional output is needed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to call out parent child considerations more explicitly. Might be done but noting here as a reminder to come back in case there's room for improvement

HIP/hip-1056.md Outdated Show resolved Hide resolved
@Nana-EC Nana-EC self-assigned this Oct 15, 2024
HIP/hip-1056.md Outdated Show resolved Hide resolved
Copy link
Member

@jsync-swirlds jsync-swirlds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few typos and wording suggestions.

HIP/hip-1056.md Outdated Show resolved Hide resolved
HIP/hip-1056.md Outdated Show resolved Hide resolved
HIP/hip-1056.md Show resolved Hide resolved
HIP/hip-1056.md Outdated Show resolved Hide resolved
HIP/hip-1056.md Outdated Show resolved Hide resolved
HIP/hip-1056.md Outdated Show resolved Hide resolved
Copy link
Member

@poulok poulok left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got to Design for Verifiability. Will finish the rest when I can.

HIP/hip-1056.md Outdated

With the record stream design nodes had to independently upload record streams and their signatures files.
These were stored in varied public cloud buckets and anyone in the world could download the files
e.g. Mirror Nodes do this today to support queries.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatting nit

Suggested change
e.g. Mirror Nodes do this today to support queries.
(e.g. Mirror Nodes do this today to support queries.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

HIP/hip-1056.md Outdated
- **Signature Files**: Each node currently produces a v6 record stream with a corresponding signature file for
verification. Mirror nodes must download a strong minority (1/3 of consensus weight) worth of these signature files,
along with the record file, to verify v6 records. This frequent access to cloud storage (GCS/S3) is costly. To reduce
this expense and complexity, blocks include a TSS signature from the majority weight of consensus nodes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Today the plan is to make the threshold a majority, but it could change in the future. There are various levels of guarantees that can be made by thresholds of 1/3, >1/2, and >2/3. I suggest:

Suggested change
this expense and complexity, blocks include a TSS signature from the majority weight of consensus nodes.
this expense and complexity, blocks include a TSS signature from a threshold (minimum of 1/3) weight of consensus nodes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is important for the diagram to match the verbage, it should include the other stream files that are being consolidated: sidecar files and signature files

HIP/hip-1056.md Outdated
simplify node software by allowing nodes to reconnect with a block node, instead of another consensus node. This
is a significant and important enhancement to reconnect that is needed for permissionless nodes.
- **Query Endpoints:** As Block Nodes or other consumers of the Block Stream can maintain a live state in sync with the
consensus nodes, they can answer any queries against state. They can also produce cryptograph state proofs to prove
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
consensus nodes, they can answer any queries against state. They can also produce cryptograph state proofs to prove
consensus nodes, they can answer any queries against state. They can also produce cryptographic state proofs to prove

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

HIP/hip-1056.md Outdated
Comment on lines 174 to 176
```
NOTE: Update: Add missing Round header after Block Header to image
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be removed since the diagram has the Round Header

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The label for the Input Block Items Merkle Tree is labeled "Output"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TTS Signature -> TSS Signature

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments:

  1. Where is the Round Header included?
  2. It might be worth stating explicitly that the order in which the items appear in the block merkle tree are not the same as the order in which they are streamed. This is implied in the description that says the items are organized into items known pre-execution and those known after, but better to state it explicitly IMO.
  3. Maybe this comes later, but if not, I think it's worth explaining why the hash of the state merkle tree from the beginning of the block is included in the tree rather than the hash that results from all the stuff included in that block.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made the requested changes and inclusions

HIP/hip-1056.md Outdated
Each block within the block stream is a self-contained entity, including every event and all transactions that were
part of the block, the state changes as a result of those transactions, and a
_network signature_ (using a BLS threshold signature scheme we call TSS) that proves the block was signed by a
threshold of the network.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
threshold of the network.
subset of nodes whose stake accounts for more than 1/3 of the network's consensus weight.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adopted

HIP/hip-1056.md Outdated
Comment on lines 194 to 197
The block stream has a separate `block stream merkle tree` which is a merkle tree of all the items in the block stream
over the entire history of the stream. Of course, older blocks can be represented by their hash rather than the actual
full contents, so it is possible to have a very large block stream merkle tree *in concept* that is still very efficient
to use.
Copy link
Contributor

@tinker-michaelj tinker-michaelj Oct 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The block stream has a separate `block stream merkle tree` which is a merkle tree of all the items in the block stream
over the entire history of the stream. Of course, older blocks can be represented by their hash rather than the actual
full contents, so it is possible to have a very large block stream merkle tree *in concept* that is still very efficient
to use.
At each block boundary, the state merkle tree is a depth two *subtree* of a `block merkle tree`, which is a binary merkle tree whose four subtrees at depth two also include the block's input items, the block's output items, and the merkle tree of the previous block. (Of course hash computation for the block `N` merkle tree reuses the hash already computed for the block `N-1` merkle tree.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adopted

HIP/hip-1056.md Outdated
Block streams are built out of Items. Those items can be delivered in a continuous stream containing one or more blocks.
Or they can be delivered as a single block using the `Block` message type container. If a single block is delivered as
a file it expected to be either a raw protobuf `Block` message with file name of `<BLOCK_NUMBER>.blk` e.g.
`0000000000000000001.blk` . With total of 19 digits to allow for 2^63 blocks. Or a compressed file e.g.
Copy link
Contributor

@derektriley derektriley Oct 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently Block file names are 36-digit strings padded with leading zeroes. FileBlockItemWriter

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jsync-swirlds does this match with latest considerations or are these separate node (CN & BN) considerations?

@derektriley
Copy link
Contributor

Should this HIP at all mention/reference the behaviors around consensus node to block node communication? Will this information be covered in the block node HIP?

HIP/hip-1056.md Outdated
Comment on lines 203 to 204
Record files are streams in a blockchain, with the hash of previous file included in the next record file, ensuring
immutability. Additionally, each consensus node on the Hedera mainnet signs a hash of the record files as they are
Copy link
Contributor

@tinker-michaelj tinker-michaelj Oct 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Record files are streams in a blockchain, with the hash of previous file included in the next record file, ensuring
immutability. Additionally, each consensus node on the Hedera mainnet signs a hash of the record files as they are
Record file contents are summarized in a chain of hashes, with each file's contents containing the hash of the previous file; so that the hash of the current file depends on the contents of the entire record stream. Additionally, each consensus node on the Hedera mainnet signs the hash of each record file as it is

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...containing the hash of the previous file...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Copy link
Member

@poulok poulok left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got further, but still not done.

can authenticate transactional data without the cost and complexity of downloading multiple signature files for
verification.
3. As a verifier for a network, I want a single, self-contained stream of data with aggregated signature signed by a
threshold of stake weights, so that I can cost-effectively confirm that the stream represents the consensus output of
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should really decide what terms we are going to use. Some places use stake weights and others simply use weight. Either is fine as long as it is used consistently. Inconsistent use will contribute to confusion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to stake weight

HIP/hip-1056.md Outdated
The Block Stream will be defined by the continuous transmission of Block information defined as follows.
- Each `Block` represents all inputs and outputs of the consensus network for zero or more whole `Rounds`. The maximum
number of rounds per block will be configurable and start at 1 round per block. It could be increased later if
necessary for performance reasons or if round become very frequent and small.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
necessary for performance reasons or if round become very frequent and small.
necessary for performance reasons or if rounds become very frequent and small.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

number of rounds per block will be configurable and start at 1 round per block. It could be increased later if
necessary for performance reasons or if round become very frequent and small.
- A block *may* be zero rounds because the last block prior to a network "freeze" must communicate the state hash
(via a block proof) of the state immediately prior to network shutdown. This is only possible with an extra empty
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by the (via a block proof) part here. In normal cases, the state hash is part of the block merkle tree and the block proof has an aggregated signature of the block merkle tree root (although now I realize that this has not been explicitly state in the HIP thus far, and it probably should be). Is this section saying that in a block with no rounds, the proof is simply on the hash of the state root and not a merkle tree root hash that also includes previous block root hash and null padding?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A block with no rounds is the same as any other block except for the lack of any content between block header and block proof.
The bullet point is describing a specific case where a block with no rounds may
be required, and describing how that "empty" block resolves a sequence problem.

The wording is intended to explain that, because block proof contains the state hash at the beginning of the block (end of the prior block), the state hash at end-of-block is communicated in the block stream as a field in the block proof for the following block.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the state hash at end-of-block is communicated in the block stream as a field in the block proof

Aha! I had not gotten to the BlockProof proto yet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think no change is needed here

HIP/hip-1056.md Outdated
- A `Round` is a sequence of zero or more `Events` that have reached consensus at the same time. Round length in real
world time depends on internet latency and event creation rate. In Hedera mainnet today it is around 1 round per
second. In the future this could be shorter to lower latency, but would never make sense to be less than ping time.
- An `Event` is a sequence of zero or more `EventTransactions` submitted by a single node.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A round is more than just its events, and an event is more than just its transactions. Perhaps it is simplest to say "contains" instead of "is" so we don't have to explain the metadata that comes with each.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

HIP/hip-1056.md Outdated
These concepts are core to the Hashgraph consensus mechanism and have direct impacts on the structure of data in the
streams.

Block streams are built out of Items. Those items can be delivered in a continuous stream containing one or more blocks.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand. If a block is made up of items, how can those items be streamed such that they contain one or more blocks?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stream contains one or more blocks, which are made up of many items (often thousands).
Agree that this wording is a little awkward at expressing that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

HIP/hip-1056.md Outdated

Block streams are built out of Items. Those items can be delivered in a continuous stream containing one or more blocks.
Or they can be delivered as a single block using the `Block` message type container. If a single block is delivered as
a file it expected to be either a raw protobuf `Block` message with file name of `<BLOCK_NUMBER>.blk` e.g.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
a file it expected to be either a raw protobuf `Block` message with file name of `<BLOCK_NUMBER>.blk` e.g.
a file it must be either a raw protobuf `Block` message with file name of `<BLOCK_NUMBER>.blk` e.g.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

HIP/hip-1056.md Outdated
Block streams are built out of Items. Those items can be delivered in a continuous stream containing one or more blocks.
Or they can be delivered as a single block using the `Block` message type container. If a single block is delivered as
a file it expected to be either a raw protobuf `Block` message with file name of `<BLOCK_NUMBER>.blk` e.g.
`0000000000000000001.blk` . With total of 19 digits to allow for 2^63 blocks. Or a compressed file e.g.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

incomplete sentences here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

HIP/hip-1056.md Outdated
}
```

The order of block items in a block stream is important in some cases it designates useful positions in the stream that
Copy link
Member

@poulok poulok Oct 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The order of block items in a block stream is important in some cases it designates useful positions in the stream that
The order of block items in a block stream is important and in some cases it designates useful positions in the stream that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

HIP/hip-1056.md Outdated
first transaction. Block items for one block can also be packaged in a `Block` message so they can be stored in a
protobuf file and served as a single entity if desired.

![Block Stream Items](../assets/hip-blocks/block-stream-items.svg)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some cases where multiple changes made to a singleton state type over the course of a round will be consolidated into a single change set for that singleton type for the round instead. How do those fit into the stream and block merkle tree? Do they need to be placed somewhere specific both in the stream and in the tree?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question.
@jsync-swirlds or @tinker-michaelj any thoughts here?

HIP/hip-1056.md Outdated
Comment on lines 412 to 414
#### BlockHeader

### BlockHeader
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate Blockheader section titles

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

De-duplicated

@jsync-swirlds
Copy link
Member

Should this HIP at all mention/reference the behaviors around consensus node to block node communication? Will this information be covered in the block node HIP?

This is currently documented in the block node codebase, and should be included, to the extent appropriate, in the forthcoming "Block Node" HIP. That HIP will cover all of the Block Node APIs, not just the publishBlockStream API used by consensus nodes.

Copy link
Member

@poulok poulok left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slow progress :-)

HIP/hip-1056.md Outdated
Comment on lines 480 to 481
🚨 **Open Detail:** How do we define the `first_transaction_consensus_time` of a block with no transactions? Can it be
first event consensus time if no transactions? what about empty block with no events?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some details and edge cases:

  1. For "normal" rounds that contain events and transactions, the round timestamp is equal to the timestamp of the last transaction in the round
  2. For empty rounds (no events), the round timestamp is equal to the timestamp of the last transaction in the previous round plus 1,000 nanos, rounded up to the nearest 1,000 nanos. Is there is no previous round, then the round timestamp is the median of the judge created times.

To me, #1 makes no sense at all. An event's consensus timestamp is equal to the timestamp of the FIRST transaction it contains. If the round follow the same rules, the round's timestamp would be equal to the timestamp of the first event and transaction it contains. As far as I'm aware, there is no reason we cannot change the round timestamp to work this way if we want. If we did that, we could just put the round timestamp in this protobuf instead of the first transaction timestamp and call it a day.

If we can't do that or don't want to do that, then we have some other options:

  1. Keep the transaction timestamp but make it nullable. If there are no transactions in the round, it is null.
  2. Keep the timestamp and make it always populated (with the one exception of an empty block sent after the freeze round), but for rounds with no transactions, it will be the round timestamp
  3. Add a second field for the round timestamp which will always be populated (with the one exception of an empty block sent after the freeze round).

None of these sound good because there are exceptions and edge cases that need to be explained.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting points.
@jasperpotts what are your thoughts here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also thanks for the clarity @poulok. Noted, those details for clarity

#### EventHeader

The event header depicts the beginning of an event. All items after this and before the next `EventHeader` or
`BlockProof` are part of this event or results of execution of this event’s contents. It is designed so that it is easy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't a third "event end" marker would be the non-transactional state changes?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no mechanism for a reader to differentiate non-transactional state changes from transactional state changes. To a reader of the Block Stream, those are just additional state changes in the event.

Is it important to differentiate those in some way? We might be missing another header if that's the case...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, that makes sense. I suppose the only part that is not correct is that if there are non-transaction state changes (i.e. like platform state changes or the sum of all changes to the transaction receipt queue), those are not a result of the last event's execution or contents. They are a consequence of the round as a whole. But I may be nit-picking wording here where not necessary. Feel free to close this comment if you think the distinction is not important.

HIP/hip-1056.md Outdated

The event header depicts the beginning of an event. All items after this and before the next `EventHeader` or
`BlockProof` are part of this event or results of execution of this event’s contents. It is designed so that it is easy
to reconstruct `GossipEvent`’s as needed for hashgraph reconstruction from the contents in the block stream. To make
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
to reconstruct `GossipEvent`s as needed for hashgraph reconstruction from the contents in the block stream. To make
to reconstruct `GossipEvent`s as needed for hashgraph reconstruction from the contents in the block stream. To make

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

HIP/hip-1056.md Outdated
of user and system transactions, and matches the structure of Events within the hashgraph algorithm. A state signature
system transaction is a non-user transaction internally created to carry out a state change in conformance with network
hashgraph logic. This transaction does not count towards network TPS and can only be issued internally via the consensus
platform.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's future proof this a bit

Suggested change
platform.
node software.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

The block stream supports complete reconstruction of the consensus event stream in a simple and secure manner. Any
entity wishing to reconstruct the event stream may begin with the `EventHeader` and append each following
`EventTransaction` *in order* as it appears in the Block Stream. The event ends when the next `EventHeader` or
`Block Proof` is encountered in the stream.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or non-transactional state changes?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this related to your previous question.
Following a non-transaction state change should still be an EventHeader or a Block Proof

Signed-off-by: Nana Essilfie-Conduah <[email protected]>
Signed-off-by: Nana Essilfie-Conduah <[email protected]>
HIP/hip-1056.md Outdated
Comment on lines 331 to 392
message BlockItem {
oneof item {
/**
* An header for the block, marking the start of a new block.
*/
com.hedera.hapi.block.stream.output.BlockHeader block_header = 1;

/**
* An header emitted at the start of a new network "event".
*/
com.hedera.hapi.block.stream.input.EventHeader event_header = 2;

/**
* An header emitted at the start of a new consensus "round".
* <p>
* This item SHALL contain the properties relevant to a single
* consensus round.
*/
com.hedera.hapi.block.stream.input.RoundHeader round_header = 3;

/**
* A single transaction.
*/
com.hedera.hapi.platform.event.EventTransaction event_transaction = 4;

/**
* The result of running a transaction.
*/
com.hedera.hapi.block.stream.output.TransactionResult transaction_result = 5;

/**
* A transaction output.
*/
com.hedera.hapi.block.stream.output.TransactionOutput transaction_output = 6;

/**
* A set of state changes.
*/
com.hedera.hapi.block.stream.output.StateChanges state_changes = 7;

/**
* Verification data for an item filtered from the stream.<br/>
* This is a hash for a merkle tree node where the contents of that
* part of the merkle tree have been removed from this stream.
*/
FilteredItemHash filtered_item_hash = 8;

/**
* A signed block proof.<br/>
* The signed merkle proof for this block. This will validate
* a "virtual" merkle tree containing the previous block "virtual"
* root, an "input" subtree, an "output" subtree, and
* a "state changes" subtree.
*/
BlockProof block_proof = 9;

/**
* A record file and associated data.
*/
RecordFileItem record_file = 10;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "fixed" indent is using the wrong settings:

  1. indentation should be 4 spaces, not 2
  2. comments should line up on *, so every line after the first should be indented one extra space.

Example

        /**
         * A signed block proof.<br/>
         * The signed merkle proof for this block. This will validate
         * a "virtual" merkle tree containing the previous block "virtual"
         * root, an "input" subtree, an "output" subtree, and
         * a "state changes" subtree.
         */
        BlockProof block_proof = 9;

        /**
         * A record file and associated data.
         */
        RecordFileItem record_file = 10;
    }
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got up to BlockHeader with the above. Will circle back to complete

HIP/hip-1056.md Outdated
Comment on lines 1933 to 1947
- [ ] Q: Are the current block sizes acceptable to not need to adopt an entity state changes splitting strategy?
- [ ] Q: Is it clear which block contains a block proof and state hash regarding that block?
- Currently a block will contain it's own proof. The state hash a block contains however is the hash at the begining of the block/end
of the previous block. The state hash representing the changes to state made by a given block are found in the next block.
State proofs for block N thus require the state following block N, and the block proof from block N+1.
- [ ] Q: There are some cases where multiple changes made to a singleton state type over the course of a round will be consolidated into a single change set for that singleton type for the round instead. How do those fit into the stream and block merkle tree? Do they need to be placed somewhere specific both in the stream and in the tree?
- [ ] Q: Should TransferList be included in the TransactionResult object or not. It seems closer to an input and output.
- Couldn't a MN that parses the block stream take changes to the transferList in the TransactionResult?
There are considerations for emergent transfer logic that isn’t in the transaction body which may prevent this.
- [ ] Q: Is the current algorithm for Block hash calculation accurate based on latest services explorations?
- [ ] Q: Should we include a string error message in the block stream to give improved DexExp when troubleshooting transaction failures?
- [ ] Q: Are redaction and errata and how FilteredBlockItem can be used to achieve them adequately described?
- [ ] Q: Can we change EthereumOutput to EVMOutput? it seems more accurate to cover EVM transactions
- An argument against this is that Outputs match to the transaction input. Since there's a ContractCall, ContractCreate and
EThereumTransaction it would be inconsistent.
Copy link
Member

@jsync-swirlds jsync-swirlds Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- [ ] Q: Are the current block sizes acceptable to not need to adopt an entity state changes splitting strategy?
- [ ] Q: Is it clear which block contains a block proof and state hash regarding that block?
- Currently a block will contain it's own proof. The state hash a block contains however is the hash at the begining of the block/end
of the previous block. The state hash representing the changes to state made by a given block are found in the next block.
State proofs for block N thus require the state following block N, and the block proof from block N+1.
- [ ] Q: There are some cases where multiple changes made to a singleton state type over the course of a round will be consolidated into a single change set for that singleton type for the round instead. How do those fit into the stream and block merkle tree? Do they need to be placed somewhere specific both in the stream and in the tree?
- [ ] Q: Should TransferList be included in the TransactionResult object or not. It seems closer to an input and output.
- Couldn't a MN that parses the block stream take changes to the transferList in the TransactionResult?
There are considerations for emergent transfer logic that isn’t in the transaction body which may prevent this.
- [ ] Q: Is the current algorithm for Block hash calculation accurate based on latest services explorations?
- [ ] Q: Should we include a string error message in the block stream to give improved DexExp when troubleshooting transaction failures?
- [ ] Q: Are redaction and errata and how FilteredBlockItem can be used to achieve them adequately described?
- [ ] Q: Can we change EthereumOutput to EVMOutput? it seems more accurate to cover EVM transactions
- An argument against this is that Outputs match to the transaction input. Since there's a ContractCall, ContractCreate and
EThereumTransaction it would be inconsistent.
- [ ] Q: Is the current compression approach sufficient to maintain manageable
block size and avoid adopting an entity state changes splitting strategy?
- [ ] Q: Are there additional protocol buffer changes needed to reduce the
stream overhead for Events, Transactions, or other frequent entities.
- [ ] Q: Is it clear which block contains a block proof and state hash regarding
that block?
- A block will contain it's own proof. The state hash a block contains
however is the hash at the begining of the block/end of the previous block.
The state hash representing the changes to state made by a given block are
found in the next block. State proofs after block N thus require the state
at the end of block N, and the block proof from block N+1.
- [ ] Q: There are some cases where multiple changes made to a singleton state
type over the course of a round will be consolidated into a single change
set for that singleton type for the round instead. How do those fit into
the stream and block merkle tree? Do they need to be placed somewhere
specific both in the stream and in the tree?
- Actually, it's possible these must be reported multiple times instead of
holding them to a single later update. The reason being that out-of-order
state changes, or skipped state changes could produce a different total
order of updates, and change the state hash.<br/> This needs to be verified
with the services, platform, and platform-data experts.
- [ ] Q: Should TransferList be included in the TransactionResult object or not.
It contains redundant data, but makes mirror node processing much simpler.
- Could a MN that parses the block stream reconstruct the transfer list from
state changes?<br/> There are considerations for emergent transfer logic
that isn’t in the transaction body which may prevent this.
- [ ] Q: Is the current algorithm for Block hash calculation accurate based on
latest services explorations?
- [ ] Q: Should we include a string error message in the block stream to give
improved DexExp when troubleshooting transaction failures?
- Should we, alternatively, offer a detail error _number_, defined on a
per-result-code basis, and published as a set of enumerations? This
numeric option would greatly reduce extra data in the block stream and
avoid unclear or "bit-rotted" error strings.
- [ ] Q: Are redaction and errata and how FilteredBlockItem can be used to
achieve them adequately described?
- [ ] Q: Can we change EthereumOutput to EVMOutput? it seems more accurate to
cover EVM transactions
- An argument against this is that Outputs match to the transaction input.
Since there's a ContractCall, ContractCreate and EthereumTransaction it
would be inconsistent.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adopted.
@jsync-swirlds I also added the following. Does that sound correct?

  • Q: Is it clear which block contains a block proof and state hash regarding
    that block?
    ...
    Verified state proofs for changes in block N thus require the state hash in block N+1.

HIP/hip-1056.md Outdated
Comment on lines 1924 to 1927
In the past the exchange_rate details were in the transaction record. However, this information doesn't change often and is based on
the value of a system file. Changes to this file are externalized to the MN and thus the current exchange rate value is always available
via a MN (or future BN). Thus the exchange rate is readily available from the MN when using the transaction timestamp as input.
This allowed us to removing it and reduce unnecessary data from the block stream.
Copy link
Member

@jsync-swirlds jsync-swirlds Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
In the past the exchange_rate details were in the transaction record. However, this information doesn't change often and is based on
the value of a system file. Changes to this file are externalized to the MN and thus the current exchange rate value is always available
via a MN (or future BN). Thus the exchange rate is readily available from the MN when using the transaction timestamp as input.
This allowed us to removing it and reduce unnecessary data from the block stream.
In the past the exchange_rate details were in the transaction record.
However, this information doesn't change often and is based on the value
of a system file. Changes to this file are also externalized in the
Block Stream, thus the current exchange rate value is available in the state
changes and we can remove it from the `TransactionResult` to reduce unnecessary
data in the block stream.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the change to got from 120 chars in width to 80 right? I'm gonna have to do it for the whole doc at some point.
Personally I prefer the 120 length. For a doc like this 80 is gonna make it really long 😅

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I see change removing the MN inclusion.
Is there a reason we don't want to note the data availability in the MN?
We can definitely update it to highlight it's availability in the stream, and BN and MN states

* <p>
* These fees SHALL be present in the full transfer list for the transaction.
*/
repeated proto.AssessedCustomFee assessed_custom_fees = 1;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HIP-991 will enable custom fees for topics, and most likely this field will be used to externalize the custom fees charged for a consensus submit message transaction. Should we take the opportunity and rename CryptoTransferOutput to be generic?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is probably more appropriate, as part of HIP-991, to add assessed_custom_fees to SubmitMessageOutput and include that message in TransactionOutput (to be generated when a consensus submit charges custom fees).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Xin is saying that since now AssessedCustomFee will appear in at least two transaction types and probably more in the future, instead of adding it to each different output we should consider pulling it up to the TransactionResult since it's more common. This is important to consider now, not in HIP-991 since it's a breaking change later.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The design thus far has been to add outputs for a given transaction to a transaction-specific output (for the few transactions that have outputs).

Changing that, at least in this case, would make it much harder to filter the block stream to, for example, only show the activity for consensus service transactions, as you would also need to read all of a "generic" set of outputs and tie them to specific transactions (so the filter would need to be stateful).

It's certainly an option, but I would suggest that maximizing the options for stateless filters in the Block Stream is worth the minor duplication of concepts (no data would be duplicated, of course).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once an output starts appearing on more than one transaction type and if we think it might appear in more we should consider moving it to TransactionResult. This is already the case for fields in the TransactionResult and we'd be following the same model. TokenTransferList appears in the result but it only applies to TokenReject, TokenAirdrop, and CryptoTransfer. TokenAssociation in the result only applies to TokenCreate and CryptoTransfer. There is strong precedent to consider AssessedCustomFee to be the same.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not how I view it, but it's entirely possible my view is not correct.

As I view these items, TransactionResult is mostly items that are global to all transactions, as well as a few items that are currently expected by Mirror Node in the record stream but would be difficult to move into individual transaction results.
Ideally, in my opinion, we would move those "extra" items into individual transaction results, or state changes, and TransactionResult would be reduced to just the properly "global" items like status, consensus timestamps, congestion multiplier, and transaction fee; perhaps paid staking rewards (not sure if that's universal or specific) as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are valid considerations.

Personally I wish TransactionResult was not needed or at the least was exactly as @jsync-swirlds noted and didn't include transfer_list, token_transfer_lists or automatic_token_associations which are present because they are results from transactions that differ from their usual associated transaction type.

@steven-sheehy and @xin-hedera are right that there's a precedence here to add assessed_custom_fees.
My concern is what else does that open the door and how do we ensure consistency and ability to manage future pivots.
However, if we rely on future move of items in a specific output type to the general TransactionResult we might require multiple deprecations of those fields to avoid duplication. For instance if Block Stream were already in use we'd now be considering how to stop using CryptoTransferOutput.assessed_custom_fees and how to start using TransactionResult.assessed_custom_fees. This will certainly happen again for other fields as HAPI continues to mature.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jsync-swirlds how does the inclusion of assessed_custom_fees impact filtering functionality in a way different from say token_transfer_lists or automatic_token_associations?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@steven-sheehy what's the downside if assessed_custom_fees appeared in both CryptoTransferOutput and TokenAirdropOutput? Is it just the duplication the protobuf and the parsing consideration to look in more than one object for the same value depending on transaction type? Is this better as it ensure transaction types are more self contained?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Token transfer list and automatic associations are both present elsewhere; their inclusion here is duplicative, and actually very hard to remove if a redaction were to become necessary. Both of the mentioned current fields are already a problem for redaction and filtering. Basically we can redact whole messages and preserve the block proof, but if we change a single message (say, to remove custom fees related to a redacted message), the block proof will not validate, so we could remove the message that related to a custom fee (perhaps an HCS submit), but could not remove the related custom fees from this message, which limits how thoroughly we could comply with a redaction request.
For general filtering it's possible to filter out the state change, transaction, and outputs that duplicate the data here; but this data would likely remain in the stream unless this output message was also filtered (which would require a complex filter, and associated higher charge).
Part of the reason for being very careful to minimize fields in this message is to reduce cases where redaction is limited and filtering is more complex.

HIP/hip-1056.md Outdated Show resolved Hide resolved
HIP/hip-1056.md Outdated
Or they can be delivered as a single block using the `Block` message type container. If a single block is delivered as
a file it must be either a raw protobuf `Block` message with file name of `<BLOCK_NUMBER>.blk` e.g.
`0000000000000000001.blk`. The file name contains a total of 19 digits to allow for 2^63 blocks. The file format may
also be a compressed file e.g. `0000000000000000001.blk.gz` or `0000000000000000001.blk.ztsd`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The common extension for zstd is .zst.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is merely an example, and probably shouldn't be in this HIP at all, given the storage of files (or use of files at all) is an implementation detail of a Block Node that should not actually be exposed in the API.

HIP/hip-1056.md Outdated
Comment on lines 279 to 280
`0000000000000000001.blk`. The file name contains a total of 19 digits to allow for 2^63 blocks. The file format may
also be a compressed file e.g. `0000000000000000001.blk.gz` or `0000000000000000001.blk.ztsd`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is ambiguous. The HIP presents three possible names for the block file in the bucket: 0000000000000000001.blk, 0000000000000000001.blk.gz, or 0000000000000000001.blk.zst. Since the filename is the API for how mirror nodes will consume it from the bucket, we have to explicitly pick one of these. Consensus nodes will only upload one of these three formats and if mirror nodes pick the wrong format then it won't work or it has to waste effort and try all 3 names.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file name is not an API. We'll work out the temporary storage of files in buckets independent of the Block Stream HIP, as the HIP should neither mandate nor envision file-in-bucket storage as a long-term solution.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just got out of a meeting where we discussed phase 1 being block streams in the bucket as the canonical source. So the file name will very much be an API if that's the case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say we need to reconsider that, as the block stream is not a file, definitionally.
If we want a separate HIP, that offers temporary public access, then that might make sense, but making a temporary transitional access part of the permanent public API is not what I would consider well designed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe there's a needed separation of the API the HIP is proposing and the API the team requires to achieve this goal in the interim to meet a schedule.
MNs consuming buckets is a project phasing optimization to get this out soon but is not an intention of this HIPs specification nor of long term usage.
For instance a non Hedera run Mirror Node would consider this HIP functional and implemented when they can read from the block stream and should hopefully never have to pull from the cloud bucket.
However, for the project I do believe we must commit to one file format so MNs early on can commit to one format.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, @steven-sheehy's suggestion is fair.
Assuming the suggestion is CN will really have 2 output formats for Block Items ( (i) block streams and ii) BlockFile for special cases) and we should specify both for clarity.
@jasperpotts what do you think?

HIP/hip-1056.md Outdated
Comment on lines 277 to 280
Or they can be delivered as a single block using the `Block` message type container. If a single block is delivered as
a file it must be either a raw protobuf `Block` message with file name of `<BLOCK_NUMBER>.blk` e.g.
`0000000000000000001.blk`. The file name contains a total of 19 digits to allow for 2^63 blocks. The file format may
also be a compressed file e.g. `0000000000000000001.blk.gz` or `0000000000000000001.blk.ztsd`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to the filename, this HIP needs to define the path in the bucket where this file will reside. I suggest /blocks/0000000000000000001.blk.zst but open to alternatives.

Copy link
Member

@jsync-swirlds jsync-swirlds Nov 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be defined in the HIP, as we should not be storing these files in public buckets long-term.
The Block Nodes will provide the blocks via an API call, as a bytestream, based on block number; not as files.
Alternatively, for clients like mirror node, block stream data should (mostly) be obtained from the stream API, not as whole blocks.

HIP/hip-1056.md Outdated
Comment on lines 277 to 279
Or they can be delivered as a single block using the `Block` message type container. If a single block is delivered as
a file it must be either a raw protobuf `Block` message with file name of `<BLOCK_NUMBER>.blk` e.g.
`0000000000000000001.blk`. The file name contains a total of 19 digits to allow for 2^63 blocks. The file format may
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhere we need to define which buckets the data will reside. It should hopefully just be in the same buckets as the current record streams just in different paths.

Copy link
Member

@jsync-swirlds jsync-swirlds Nov 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Different lifecycle, different access (never public, or only temporarily public), and therefore different buckets.
Also, it's not, in my opinion, properly part of the HIP; it's an internal decision for any entity that wishes to run a private block node to create a (private) archival bucket.

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the bottom left diagram under Root of Input Merkle Tree the one describing input block items.

I guess the most bottom left Output Block Items Merkle Tree should be renamed to Input Block Items Merkle Tree.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I understand you.
Yes, the added label beneath the input items subtree should be corrected:
Output Block Items Merkle Tree
Input Block Items Merkle Tree

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've fixed this

…en-sheehy, xin-hedera, tinker-michaelj, derektriley and IvanKavaldzhiev

Signed-off-by: Nana Essilfie-Conduah <[email protected]>
* This SHALL be the timestamp assigned by the hashgraph consensus
* algorithm to the first transaction of this block.
*/
proto.Timestamp first_transaction_consensus_time = 5;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasperpotts & @jsync-swirlds for a block with an empty round there will be no transactions so should we rename this property to be more general?
We'd then note that normally it's the first transaction consensus timestamp and in an empty round case it's equal to the last transaction that reached consensus (in the previous round) plus 1,000 nanos, rounded up to the nearest 1,000 nanos (thanks @poulok for the summary).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a bit tricky, in my view.

  • It's hard to see what the name should be if we change it.
  • Ideally such empty rounds will be vanishingly rare (I, for one, certainly hope that network usage rises to the point that we never actually see one).
  • Also, this time could be seen as a "virtual" first transaction time for an empty round (essentially lock-step incrementing consensus time 1 microsecond at a time until the network has real transactions that set the next consensus time).

Not sure the best choice. We could rename to round_start_consensus_time, but I'm not sure that's accurate either.

HIP/hip-1056.md Outdated Show resolved Hide resolved
HIP/hip-1056.md Show resolved Hide resolved
HIP/hip-1056.md Outdated

With the record stream design nodes had to independently upload record streams and their signatures files.
These were stored in varied public cloud buckets and anyone in the world could download the files
e.g. Mirror Nodes do this today to support queries.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

HIP/hip-1056.md Outdated
- **Signature Files**: Each node currently produces a v6 record stream with a corresponding signature file for
verification. Mirror nodes must download a strong minority (1/3 of consensus weight) worth of these signature files,
along with the record file, to verify v6 records. This frequent access to cloud storage (GCS/S3) is costly. To reduce
this expense and complexity, blocks include a TSS signature from the majority weight of consensus nodes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

* <p>
* These fees SHALL be present in the full transfer list for the transaction.
*/
repeated proto.AssessedCustomFee assessed_custom_fees = 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@steven-sheehy what's the downside if assessed_custom_fees appeared in both CryptoTransferOutput and TokenAirdropOutput? Is it just the duplication the protobuf and the parsing consideration to look in more than one object for the same value depending on transaction type? Is this better as it ensure transaction types are more self contained?

HIP/hip-1056.md Outdated Show resolved Hide resolved
HIP/hip-1056.md Outdated
Comment on lines 279 to 280
`0000000000000000001.blk`. The file name contains a total of 19 digits to allow for 2^63 blocks. The file format may
also be a compressed file e.g. `0000000000000000001.blk.gz` or `0000000000000000001.blk.ztsd`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe there's a needed separation of the API the HIP is proposing and the API the team requires to achieve this goal in the interim to meet a schedule.
MNs consuming buckets is a project phasing optimization to get this out soon but is not an intention of this HIPs specification nor of long term usage.
For instance a non Hedera run Mirror Node would consider this HIP functional and implemented when they can read from the block stream and should hopefully never have to pull from the cloud bucket.
However, for the project I do believe we must commit to one file format so MNs early on can commit to one format.

HIP/hip-1056.md Outdated
Comment on lines 279 to 280
`0000000000000000001.blk`. The file name contains a total of 19 digits to allow for 2^63 blocks. The file format may
also be a compressed file e.g. `0000000000000000001.blk.gz` or `0000000000000000001.blk.ztsd`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, @steven-sheehy's suggestion is fair.
Assuming the suggestion is CN will really have 2 output formats for Block Items ( (i) block streams and ii) BlockFile for special cases) and we should specify both for clarity.
@jasperpotts what do you think?

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've fixed this

@ted-yeh-vc
Copy link

ted-yeh-vc commented Nov 20, 2024

Has there been any research/discussion into using/implementing this as a substream https://thegraph.com/blog/subgraphs-substreams-firehose-explained/? Not saying that firehose/substreams are "the" standard, but it would allow integration into TheGraph/Subgraphs, which is (I think) decently popular.

@iron4548
Copy link

iron4548 commented Nov 20, 2024

These diagrams needs a white background for readability when using the dark theme.

image

image

@iron4548
Copy link

There's a typo in the HIP:

"For emptu rounds with no eents"

@jsync-swirlds
Copy link
Member

Has there been any research/discussion into using/implementing this as a substream https://thegraph.com/blog/subgraphs-substreams-firehose-explained/? Not saying that firehose/substreams are "the" standard, but it would allow integration into TheGraph/Subgraphs, which is (I think) decently popular.

There has been some limited thought in this area; the Block Stream cannot initially be delivered in this format and still be verifiable and self-contained, but it might be possible in the future.
It would be an interesting area for a community project to explore as a value-added service, in fact. The effort would be to read the block stream (which is already in minimum composable units delivered as early as possible) and repackage that stream as a firehose and/or in substreams (possibly with filtering and other services).

Copy link
Member

@jsync-swirlds jsync-swirlds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few possible improvements.

Comment on lines +196 to +197
Merkle Item Order vs Block Item Order: The order in which items appear in the block merkle tree are not the same
order in which are streamed.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't entirely accurate. The left-to-right order of merkle leaves must be the order in which those items are encountered in the stream. The only change is that some items are in the "input" subtree and some are in the "output" subtree, but within those trees the order will need to be the same as stream-order.

Comment on lines +203 to +204
changes of the block and hash them, waiting for this process would result in the block creation time being held up.
Quick block times is a goal of the block stream and thus a tradeoff is made to ensure fast block times.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor wording adjustment:

Suggested change
changes of the block and hash them, waiting for this process would result in the block creation time being held up.
Quick block times is a goal of the block stream and thus a tradeoff is made to ensure fast block times.
changes of the block and hash them, waiting for this process would result in the block delivery time being held up.
rapid block delivery is a goal of the block stream and thus a tradeoff is made to ensure minimum latency.

Comment on lines +221 to +229
To make each block within a block stream independently verifiable, the use of an aggregated signature is leveraged.
Aggregated signatures allow the verification of multiple node signatures to be processed using a single public key.
Therefore, a user can verify that a block was signed by a sufficient stake weight within the network by verifying a single
aggregated signature. TSS-BLS signatures were selected because of their ability to support partial share signatures
assigned on a weighted number of shares per node with a threshold value required for verification. Additionally,
adopting TSS-BLS allows the network to utilize a single semi-permanent public ledger id to verify the signature. This
offers a valuable system optimization as no matter how much the network node membership changes, the same ledger id can
be used to verify the aggregated signature. TSS-BLS signatures are also reasonably efficient when used within smart
contracts. A state proof based on this signature can be verified by a smart contract on another ledger for little cost.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small changes for readability

Suggested change
To make each block within a block stream independently verifiable, the use of an aggregated signature is leveraged.
Aggregated signatures allow the verification of multiple node signatures to be processed using a single public key.
Therefore, a user can verify that a block was signed by a sufficient stake weight within the network by verifying a single
aggregated signature. TSS-BLS signatures were selected because of their ability to support partial share signatures
assigned on a weighted number of shares per node with a threshold value required for verification. Additionally,
adopting TSS-BLS allows the network to utilize a single semi-permanent public ledger id to verify the signature. This
offers a valuable system optimization as no matter how much the network node membership changes, the same ledger id can
be used to verify the aggregated signature. TSS-BLS signatures are also reasonably efficient when used within smart
contracts. A state proof based on this signature can be verified by a smart contract on another ledger for little cost.
To make each block within a block stream independently verifiable, we make use of
an aggregated signature.
Aggregated signatures allow the verification of a single signature that can
only be produced by cryptographically aggregating signatures from nodes
representing a sufficient stake weight of the network. Therefore, any entity can
verify that a block was signed by a sufficient stake weight within the network
with a single signature verification, significantly reducing complexity.
TSS-BLS signatures are selected for this purpose because of the ability to
support partial share signatures assigned as a weighted number of shares per
node with a threshold value required for verification.
Additionally, adopting TSS-BLS allows the network to provide a single semi-
permanent public ledger id, which is the public key needed to verify the network
signature. This offers a valuable system optimization as no matter how much the
network node membership changes, the same ledger id can be used to verify
the aggregated signature. TSS-BLS signatures are also reasonably efficient
when used within smart contracts. A state proof based on this signature can be
verified by a smart contract on another ledger for little cost when compared
to many other alternatives.

Comment on lines +234 to +241
Notably the block stream will enable 3 types of proof
1. Block Proof - This proof proves the blockchain and illustrats that given a starting block and it proof, a given
set of transactions inpput and outputs as well as resulting state changes had a final merkle root hash that was agreed
upon my the majority stake weight of the network.
2. Block Item Proof - This proof proves that a given object was present in a Block. This is just as powerful as noting
a value was in state.
3. State Proof - This proof proves a given state value at a given block number was agreed upon my the majority stake
weight of the network.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling corrections

Suggested change
Notably the block stream will enable 3 types of proof
1. Block Proof - This proof proves the blockchain and illustrats that given a starting block and it proof, a given
set of transactions inpput and outputs as well as resulting state changes had a final merkle root hash that was agreed
upon my the majority stake weight of the network.
2. Block Item Proof - This proof proves that a given object was present in a Block. This is just as powerful as noting
a value was in state.
3. State Proof - This proof proves a given state value at a given block number was agreed upon my the majority stake
weight of the network.
Notably the block stream will enable 3 types of proof
1. Block Proof - This proof proves the blockchain and illustrates that given a
starting block and its proof, a given set of transaction inputs and outputs
as well as resulting state changes and a final merkle root hash was agreed
upon my the majority stake weight of the network.
2. Block Item Proof - This proof proves that a given object was present in a
Block. This is just as powerful as proving a value was in state.
3. State Proof - This proof proves a given state value at a given block number
was agreed upon by a majority stake weight of the network.

Comment on lines +245 to +254
| I submitted a transaction in block-number `x`. I want to prove that. | Block Item Proof (`x`) |
| I submitted a transaction in block-number `x`. I want to prove my balance after that transaction. | Block Item Proof (`x`) |
| I submitted a transaction in block-number `x`. I didn’t make any transaction until block-number y; y >> x. I want to prove my balance as of block-number y. | State Proof (`y`) |
| I sent an HCS message to a topic in block-number `x`. I want to prove that my message was included in the topic. | Block Item Proof (`x`) |
| I sent an HCS message to a topic in block-number `x`. Nobody sent any messages to that topic until block-number y; y >> x. I want to prove that my message was included in the topic | Block Item Proof (`x`) |
| I sent an HCS message to a topic in block-number x. Many people sent many messages to that topic until block-number y; y >> x. I want to prove that my original message at block-number x was included in the topic. | Block Item Proof (`x`) |
| I executed a Smart Contract call in block-number x. I want to prove that *all* actions triggered by that smart contract (e.g. other contracts it called, HTS or Account transactions it generated) took place | Block Item Proof(s) (`x`) |
| I submitted (or signed) a scheduled transaction in block-number x. My scheduled-transaction executed as in block-number y; y >> x. I want to prove that my transaction executed at y. | Block Item Proof (`x`) / State Proof (`y`) |
| I submitted (or signed) a scheduled transaction in block-number x. My scheduled-transaction expired because it didn’t get sufficient signatures. I want to prove that my scheduled transaction expired at y. | Block Item Proof (`x`) / State Proof (`y`) |
| I did a transaction in block-number x. I didn’t make any transaction until block-number y; y >> x. My entity (account, contract, file, token, topic, or schedule) expired at y. I want to prove that my entity expired at y. | Block Item Proof (`x`) |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling corrections, wording adjustments, a couple proof type corrections, and two added scenarios.

Suggested change
| I submitted a transaction in block-number `x`. I want to prove that. | Block Item Proof (`x`) |
| I submitted a transaction in block-number `x`. I want to prove my balance after that transaction. | Block Item Proof (`x`) |
| I submitted a transaction in block-number `x`. I didn’t make any transaction until block-number y; y >> x. I want to prove my balance as of block-number y. | State Proof (`y`) |
| I sent an HCS message to a topic in block-number `x`. I want to prove that my message was included in the topic. | Block Item Proof (`x`) |
| I sent an HCS message to a topic in block-number `x`. Nobody sent any messages to that topic until block-number y; y >> x. I want to prove that my message was included in the topic | Block Item Proof (`x`) |
| I sent an HCS message to a topic in block-number x. Many people sent many messages to that topic until block-number y; y >> x. I want to prove that my original message at block-number x was included in the topic. | Block Item Proof (`x`) |
| I executed a Smart Contract call in block-number x. I want to prove that *all* actions triggered by that smart contract (e.g. other contracts it called, HTS or Account transactions it generated) took place | Block Item Proof(s) (`x`) |
| I submitted (or signed) a scheduled transaction in block-number x. My scheduled-transaction executed as in block-number y; y >> x. I want to prove that my transaction executed at y. | Block Item Proof (`x`) / State Proof (`y`) |
| I submitted (or signed) a scheduled transaction in block-number x. My scheduled-transaction expired because it didn’t get sufficient signatures. I want to prove that my scheduled transaction expired at y. | Block Item Proof (`x`) / State Proof (`y`) |
| I did a transaction in block-number x. I didn’t make any transaction until block-number y; y >> x. My entity (account, contract, file, token, topic, or schedule) expired at y. I want to prove that my entity expired at y. | Block Item Proof (`x`) |
| I submitted a transaction in block-number `x`. I want to prove that transaction. | Block Item Proof (`x`) |
| I submitted a transaction in block-number `x`. I want to prove my balance after that transaction. | Block Item Proof (`x`) |
| I submitted multiple transactions in block-number `x`. I want to prove my balance after _each_ transaction. | Block Item Proof (`x`), one per transaction |
| I submitted a transaction in block-number `x`. I didn’t submit any further transactions until after block-number y; y >> x. I want to prove my balance as of block-number y. | State Proof (`y`) |
| I sent an HCS message to a topic in block-number `x`. I want to prove that my message was written to the topic. | Block Item Proof (`x`) |
| I sent an HCS message to a topic in block-number `x`. Nobody sent any messages to that topic until block-number y; y >> x. I want to prove that my message was written to the topic | Block Item Proof (`x`) |
| I sent an HCS message to a topic in block-number x. Many people sent many messages to that topic through block-number y; y >> x. I want to prove that my original message at block-number x was written to the topic. | Block Item Proof (`x`) |
| I executed a Smart Contract call in block-number x. I want to prove that *all* actions triggered by that smart contract (e.g. other contracts it called, HTS or Account transactions it generated) took place | Block Item Proof(s) (`x`) |
| I submitted (or signed) a scheduled transaction in block-number x. My scheduled-transaction executed in block-number y; y >> x. I want to prove that my transaction was submitted/signed at x _and_ executed at y. | Block Item Proof (`x`) / Block Item Proof (`y`) |
| I submitted (or signed) a scheduled transaction in block-number x. My scheduled-transaction expired because it didn’t get sufficient signatures. I want to prove that my scheduled transaction was signed/submitted at x _and_ expired at y. | Block Item Proof (`x`) / Block Item Proof (`y`) |
| I submitted a transaction in block-number x. I didn’t submit any further transactions until block-number y; y >> x. My entity (account, contract, file, token, topic, or schedule) expired at y. I want to prove that my entity expired at y. | Block Item Proof (`y`) |
| I do not know the last transaction affecting an entity (Account, Smart Contract, File, Token, etc...). I want to prove the state of that entity as of block-number x. | State Proof (`x`) |

All items in the block other than the final `BlockProof` and `FilteredItemHash` are either Input or Output items. This
separation is important as they are hashed into two different sub-merkle-trees in the final proof. We did not want to
carry in each block item data representing which tree it was hashed in as that would waste a lot of bytes. So which tree
is defined by the protobuf package that message type is in. The reason they are split into input and output item sets is
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
is defined by the protobuf package that message type is in. The reason they are split into input and output item sets is
is defined _both_ by the protobuf package that message type is in and by a
convention for the _field number_ assigned to the item in this message.
The reason they are split into input and output item sets is

this opens the door for using the block stream format to describe the data passed from consensus to transaction
processing.

The division into block items also enables filtering of data based on data type. For example a downstream services could
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The division into block items also enables filtering of data based on data type. For example a downstream services could
The division into block items also enables filtering of data based on data type or
fine-grained content filters. For example a downstream services could

* This SHALL be the timestamp assigned by the hashgraph consensus
* algorithm to the first transaction of this block.
*/
proto.Timestamp first_transaction_consensus_time = 5;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a bit tricky, in my view.

  • It's hard to see what the name should be if we change it.
  • Ideally such empty rounds will be vanishingly rare (I, for one, certainly hope that network usage rises to the point that we never actually see one).
  • Also, this time could be seen as a "virtual" first transaction time for an empty round (essentially lock-step incrementing consensus time 1 microsecond at a time until the network has real transactions that set the next consensus time).

Not sure the best choice. We could rename to round_start_consensus_time, but I'm not sure that's accurate either.

Comment on lines +497 to +500
/**
* Version of the active address book of the network at the time relating to the network public key
*/
proto.SemanticVersion address_book_version = 7;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is removed from the latest updates.

Comment on lines +515 to +521
<aside>
Timestamp on empty round: Currently, for rounds with events and transactions, the round timestamp is equal to the
timestamp of the last transaction in the round. For emptu rounds with no eents, the round timestamp is equal to the
timestamp of the last transaction in the previous round plus 1,000 nanos, rounded up to the nearest 1,000 nanos.
If there is no previous round, then the round timestamp is the median of the judge created times.
This logic disticts the value of the 1BlockHeader` item `first_transaction_consensus_time` property.
</aside>
Copy link
Member

@jsync-swirlds jsync-swirlds Nov 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor update

Suggested change
<aside>
Timestamp on empty round: Currently, for rounds with events and transactions, the round timestamp is equal to the
timestamp of the last transaction in the round. For emptu rounds with no eents, the round timestamp is equal to the
timestamp of the last transaction in the previous round plus 1,000 nanos, rounded up to the nearest 1,000 nanos.
If there is no previous round, then the round timestamp is the median of the judge created times.
This logic disticts the value of the 1BlockHeader` item `first_transaction_consensus_time` property.
</aside>
<blockquote><aside>
Timestamp on empty round: Currently, for rounds with events and transactions,
the round timestamp is equal to the timestamp of the last transaction in the
round. For empty rounds with no events, the round timestamp is equal to the
timestamp of the last transaction in the previous round plus 1,000 nanoseconds,
rounded up to the nearest 1,000 nanoseconds.
If there is no previous round, then the round timestamp is the median of the
judge created times. This logic derives the value of the `BlockHeader` item
`first_transaction_consensus_time` property.
</aside></blockquote>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.