Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
381 changes: 381 additions & 0 deletions proposals/0307-add-block-footer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,381 @@
---
simd: '0307'
title: Add Block Footer
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I like the idea of a generic "block marker" (as introduced below). So why call this SIMD "block footer"?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In particular, we already know that we have a purpose for a marker somewhere in a middle slice for optimistic handover.

authors:
- jherrera-jump (Firedancer)
category: Standard
type: Core
status: Review
created: 2025-06-17
feature: <pubkey>
development:
- Anza - TBD
---

## Summary

Add a block footer to Solana blocks and expose footer fields in the
`getBlock` rpc endpoint.

## Motivation

For the purposes of historical monitoring, development, and auditing, it is
important to know exactly who produced a block and when it was produced.
Currently, this information can be partially inferred from Gossip and from vote
timestamps. Unfortunately there are some problems with the current approach:

- The information from gossip is ephemeral. Currently a peer needs to record
and persist it. This may cause synchronization issues when matching client
updates in gossip with the correct slot.
- Gossip lacks important information that may useful for monitoring (e.g.
scheduler used, mods, configuration settings, etc).
- Vote timestamps have a granularity of 1-second, so they cannot be used to
estimate block duration.
- Vote timestamps will be removed with Alpenglow.
Copy link
Copy Markdown
Contributor

@rogerANZA rogerANZA Sep 9, 2025

Choose a reason for hiding this comment

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

Is this a Tower SIMD, an Alpenglow SIMD, or somewhat both? If it was an Alpenglow SIMD, then I'd have a lot of comments...

In this block:

  • Gossip will be gradually removed with Alpenglow. We should not rely on it anymore.
  • We do have a proposal how to have a clock (to replace vote timestamps) in Alpenglow, and the two proposals should be aligned.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It is supposed to be a small SIMD to address an immediate need of the chain, which is how to put block producer information in the block, to support publicly verifiable analysis, competition and differentiation among validator clients, validators, and mod builders. Since we have an area to self report the validator user agent, we also allow self reporting timestamp, which is also useful for analysis; and would continue to be useful separate of any consensus level clock.

It's useful to lightly consider the SIMD in the context of Alpenglow, and if there would be some kind of breaking incompatibility, but I would really protest trying to tie them closely together. It would be an unnecessary coupling of a simple and somewhat urgent need to a longer term re-architecture.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Okay, thanks for the quick clarification. My other comments take the Alpenglow POV, nothing is breaking.


This SIMD solves these issues by including relevant information in a static
block footer.

## New Terminology

No new terms, but the following definitions are given for clarity:

- Client - The software run by leaders to interface with a solana cluster.
(e.g. `agave` or `frankendancer`)
- Block Producer - The client that produced a given block
- Scheduler - The system responsible for processing incoming transactions and
ordering them for block construction.
- Forward Error Correction set (FEC set) - A collection of shreds. At a high
level, this is a construct that leverages Reed-Solomon encoding to overcome
the problem of data loss from packet drops.
- Shreds - A fixed chunk of encoded raw block data.
- Entry Batch - An array of entries.
- Entry - An array of transactions.
- Block Marker - A chunk of structured non-transaction data that can be placed
before, after, or in-between entry batches in a block.
Copy link
Copy Markdown
Contributor

@rogerANZA rogerANZA Sep 9, 2025

Choose a reason for hiding this comment

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

Terminology:

  • Client is such a generic term that I am often asked what I mean. What about software client?
  • With Alpenglow we try to say "slice" instead of "FEC Set".


## Detailed Design

### Data Layout

Solana blocks are organized in abstraction layers not entirely unlike the
arrangement of a typical network packet (e.g. MAC -> IP -> TCP -> HTTP). At the
highest layer a block consists of some number (~100+) FEC sets. A single FEC set
contains a handful of shreds (~32). Once sufficient shreds are available the raw
block data is reconstructed and reinterpreted as an array of entry batches.
Entry batches are aligned with shred boundaries (i.e. they will start/stop at a
shred boundary).

This SIMD introduces the idea of a block marker. This is a piece of data that
would take the place of an entry batch in an incoming shred stream. Entry batch
data starts with an 8 byte value that represents the number of entries in the
batch. This number cannot be zero. By including 8 zero bytes at the beginning of
the block marker header, a replay parser can differentiate it from a regular
entry batch. A block marker chunk has the following versioned header:

```

Block Marker Header Layout
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| block_marker_flag (64 bits of 0) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| version=1 (16 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| variant (8 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| length (16 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| --payload-- |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Note that all data fields below are packed together without any alignment or
padding constraints.

```

`block_marker_flag: u64`: will always be zero. The first 8 bytes of an entry
batch are always a positive number (the number of entries in the batch), so this
flag allows parsers to differentiate the block marker from a normal entry batch.

- `version: u16` is a positive integer which changes anytime a change is made to
the block marker header. The initial version will be 1.

- `variant: u8` is an integer which identifies the structure of the block marker
payload. For example, the block footer will be identified by `variant=0`. New
metadata may be added without changing the footer by adding a new variant which
corresponds to a differently specified payload.

- `length: u16` is the length of the block marker payload in bytes (i.e. not
including the `block_marker_flag`, `version`, `variant`, and `length` fields).
Though not necessary, this will make it easier for block parsers to ignore
certain variants.

This header would precede any block marker variant inserted into the block data.
Although the block marker data length can be inferred from the version/variant,
an explicit length field is included for non-client parsers which discard
non-transactional data from the block. This ensures that we don't create a
dependency on these parsers when pushing out new/updated block markers. It also
greatly simplifies the implementation of hardware parsers, especially
considering that some variants contain variable length fields.

This SIMD also proposes the following block marker variant with an additional
constraint: it must occur once after the last entry batch in a block. The block
footer is meant to contain general block and producer metadata, along with any
metrics that must be computed after the block has been produced.

```

Block Marker Variant -- Footer
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| block_marker_flag (64 bits of 0) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| version=1 (16 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| variant=0 (8 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| length (16 bits) |
Copy link
Copy Markdown
Contributor

@ksn6 ksn6 Aug 9, 2025

Choose a reason for hiding this comment

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

Could we remove length here and use version and variant for block parsing purposes?

EDIT: apologies, caught the below two issues after approving 🙈

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm partial to keeping it in, since parsers (e.g. hardware parsers, non-client software parsers) that only care about transaction data would otherwise have to keep up with a constantly changing spec. Is there a reason you want to take it out?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cool - makes sense. In order for this to work, short of a major upgrade, this seems to require us to commit to the first few fields always being:

     Block Marker Variant -- Footer
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| block_marker_flag      (64 bits of 0) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| version=1                   (16 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| variant=1                    (8 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| length                      (16 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

which seems fine.

This is probably good design for all BlockMarkers, come to think of it.

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| footer_version=1 (16 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| block_producer_time_nanos (64 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| block_user_agent_len (8 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| block_user_agent (0-255 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Note that all data fields below are packed together without any alignment or
padding constraints.

```

- `footer_version: u16` is a positive integer which changes anytime a change is
made to the footer. The initial version will be 1.

- `block_producer_time_nanos: u64` is a nanosecond UNIX timestamp representing
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In a follow up SIMD for alpenglow specifics we plan to leverage this timestamp to fill in the clock sysvar - essentially a replacement for the current timestamp oracle derived from TowerBFT votes.

In order to accomplish this we would require that all valid blocks contain a footer (as specified in this SIMD) with the additional requirement that the producer timestamp is sensible (at a minimum, this value must be greater than the parent, and some requirement of how much this can increase from block to block. Full details TBD).

If these requirements are not met we would reject the block. Would FD be amenable to enforcing such a requirement down the line? Any concerns about using the Block Footer in such a manner?

An alternative would be to use a different BlockMarker variant or some other shred/transaction based approach.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Would FD be amenable to enforcing such a requirement down the line?

Yea afaik we're on board. This is briefly mentioned in the Alternatives Considered section

derive timestamp footer field from consensus and enforce user agent format
  - This can and probably should be implemented as a future SIMD.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes, that clock SIMD is almost ready BTW. I would probably remove descriptions from here, and put the clock stuff in the clock SIMD. Let's make this one a bit more vanilla and just describe the general framework without applications.

the time when the block producer started constructing the block. "started
constructing" is the point at which, from the perspective of the leader, all of
the consensus checks required for assuming leadership have "just passed". For
example, in Agave's pre-alpenglow implementations, this would be in
replay/maybe_start_leader. In a post-Alpenglow implementation, this would be
after receiving the proper vote/skip certificate for the previous slot.

- `block_user_agent_len: u8` the length of the `block_user_agent` string in
bytes.

- `block_user_agent: String` is a variable length utf-8 encoded string that
provides identifying information about the block producer.

Any other fields that are deemed necessary in the future may be added in one of
two ways.

- amend the `footer_version` and add the field the footer
- create a new block marker variant and add the field to its payload

### Footer Field Specification

Footer fields will be unilaterally populated by their respective block producer
without any enforced constraint on their contents. This SIMD includes the
following fields in the footer

- `block_producer_time_nanos`: u64
- `block_user_agent_len`: u8
- `block_user_agent`: String

Because it is desirable to maintain cluster-wide diagnostics this SIMD provides
a suggested format for the `block_user_agent` string which includes basic
information about the block producer. This should be a UTF-8 encoded variable
length string (up to 255 bytes long). It is not necessarily null-terminated. It
is expected that all producers use the format specified here, though this will
not be enforced. Clients are encouraged to at the very least use a valid utf-8
string and include extraneous data in a way that coheres with the specification.
The format is loosely based on HTTP `user-agent` header format specification:

```
<product>/<product-version> <comment>
```

The first entry will always be the software client.

```
client/client_version <client_details>
```

Options for `client` currently include:

- `agave`
- `frankendancer`
- `firedancer`

`client_version` should be consistent with the information stored on-chain (in
`ConfigProgram`). Software forks (e.g. `jito-agave`) should put one of
the 3 base clients and can specify details about the fork in the comment.

The comment should be in parentheses and contain a semicolon separated
list of flags. A flag has an unrestricted format, but should represent a
feature that is contained and enabled in the client it describes.

e.g.

```
agave/v2.2.15 (jito; double0; some-mod/v1.2.3)
```

Sometimes there may be software that coexists or runs alongside a validator
client. For example, current client development aims to make the transaction
scheduler modular, which would allow the transaction scheduler to be developed
independently from the client codebase. Validator clients that use
complementary software like this should add additional
`<product>/<product-version> <comment>` entries in the user agent string.

For example:

```
agave/v3.0.0 (paladin; double0) greedy-scheduler/v3 (mode:perf; another-flag)
```

### RPC Protocol Changes

The `getBlock` RPC response will be extended to, optionally, include all footer
fields. The request will be extended with the `footer` parameter, which lets
the client signal that they want the footer fields in the response. By default,
footer fields will be included in the response.

Sample Request Payload

```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlock",
"params": [
378967388,
{
"encoding": "json",
"maxSupportedTransactionVersion": 0,
"transactionDetails": "full",
"rewards": false,
"footer": true
}
]
}
```

Sample Response Payload

<!-- markdownlint-disable MD013 -->
```json
{
"jsonrpc": "2.0",
"result": {
"blockHeight": 428,
"blockTime": null,
"blockhash": "3Eq21vXNB5s86c62bVuUfTeaMif1N2kUqRPBmGRJhyTA",
"parentSlot": 429,
"previousBlockhash": "mfcyqEXB3DnHXki6KjjmZck6YjmZLvpAByy2fj4nh6B",
"footer": {
"blockProducerTimeNanos": 1750176982899968023,
"blockUserAgent": "agave/v3.0.0 (double0) greedy-scheduler/v3 (mode:perf; another-flag)",
},
"transactions": [
{
"meta": {
"err": null,
"fee": 5000,
"innerInstructions": [],
"logMessages": [],
"postBalances": [499998932500, 26858640, 1, 1, 1],
"postTokenBalances": [],
"preBalances": [499998937500, 26858640, 1, 1, 1],
"preTokenBalances": [],
"rewards": null,
"status": {
"Ok": null
}
},
"transaction": {
"message": {
"accountKeys": [
"3UVYmECPPMZSCqWKfENfuoTv51fTDTWicX9xmBD2euKe",
"AjozzgE83A3x1sHNUR64hfH7zaEBWeMaFuAN9kQgujrc",
"SysvarS1otHashes111111111111111111111111111",
"SysvarC1ock11111111111111111111111111111111",
"Vote111111111111111111111111111111111111111"
],
"footer": {
"numReadonlySignedAccounts": 0,
"numReadonlyUnsignedAccounts": 3,
"numRequiredSignatures": 1
},
"instructions": [
{
"accounts": [1, 2, 3, 0],
"data": "37u9WtQpcm6ULa3WRQHmj49EPs4if7o9f1jSRVZpm2dvihR9C8jY4NqEwXUbLwx15HBSNcP1",
"programIdIndex": 4
}
],
"recentBlockhash": "mfcyqEXB3DnHXki6KjjmZck6YjmZLvpAByy2fj4nh6B"
},
"signatures": [
"2nBhEBYYvfaAe16UMNqRHre4YNSskvuYgx3M6E4JP1oDYvZEJHvoPzyUidNgNX5r9sTyN1J9UxtbCXy2rqYcuyuv"
]
}
}
]
},
"id": 1
}
```
<!-- markdownlint-restore -->

### Mandating the footer

This proposal makes the block footer. Blocks that don't include a valid footer
in the block payload must be flagged as dead blocks and skipped by the other
nodes in the cluster.

## Alternatives Considered

- Do nothing
- We can't estimate block time / duration with sufficient granularity. We
won't be able to estimate at all when votes are changed in alpenglow.
- We will continue to have an incomplete, ephemeral record of who produced
blocks.
- derive timestamp footer field from consensus and enforce user agent format
- This can and probably should be implemented as a future SIMD. Meanwhile,
these fields are still useful since
1. most of the cluster is expected to
be honest, so monitoring tools may still use them for cluster-wide
analytics and
2. block producers still use these fields to self-monitor
their performance.
- Send block producer information via gossip instead
- The information is short-lived and depends on physical network availability
- Update this information in an on-chain account instead (e.g. ConfigProgram)
- Same issue as above, the information is short-lived.

## Impact

This change will enable more reliable monitoring and benchmarking for operators
and for the community. Clients and indexers will need to extend both in-memory
and long-term block storage to be aware of the new columns added to the block
footer. The client rpc engine will need to change to support the new fields.

## Security Considerations

- The footer fields are untrusted and purely informational. Tools that expose
these fields to external users should clearly communicate their untrusted
nature.

## Drawbacks

- No expected drawbacks beyond minimal resource overhead.

## Backwards Compatibility

- RPC requests for old slots should properly document and return a suitable
default value (e.g. None).
- Clients that don't implement this SIMD will reject new blocks because they
will fail to parse the new footer.
- Because this footer is mandatory, leaders that produce blocks without a
footer will skip.
Loading