Skip to content

Conversation

Tristan-Wilson
Copy link
Member

@Tristan-Wilson Tristan-Wilson commented Oct 14, 2025

This change adds support for the new beacon chain endpoint /eth/v1/beacon/blobs/{block_id} introduced in Fusaka while maintaining backward compatibility with the legacy endpoint
/eth/v1/beacon/blob_sidecars/{slot}. block_id can be a slot so Nitro just uses slot.

The new endpoint supports server-side filtering by versioned hash via query parameters. Since the Arbitrum sequencer inbox message contains the versioned hashes of the blobs that were posted, we can include those in the query.

Key changes:

  • Added UseLegacyEndpoint flag to BlobClientConfig to control which endpoint to use
  • Created new GetBlobsBySlot() public method for direct slot-based blob fetching
  • Implemented getBlobs() method for the new endpoint with versioned hash verification
  • Updated beaconRequest() to support query parameters for filtering
  • Added KZG commitment verification when versioned hashes are provided

Created blobtool CLI utility for testing both endpoints:

 # Fetch specific blob using new endpoint (default)
 blobtool fetch --beacon-url=<url> --slot=<slot> --versioned-hashes=<hash>

 # Fetch using legacy endpoint (requires versioned hashes)
 blobtool fetch --beacon-url=<url> --slot=<slot> --versioned-hashes=<hash> --use-legacy-endpoint

 # Compare both endpoints side-by-side
 blobtool fetch --beacon-url=<url> --slot=<slot> --versioned-hashes=<hash> --compare-endpoints

The new endpoint is used by default, with automatic fallback behavior maintained through the existing secondary beacon URL mechanism.

Spec reference: https://github.com/ethereum/beacon-APIs/blob/master/apis/beacon/blobs/blobs.yaml

Testing

To test the new fetching method I added a new tool called blobtool. It's intended to be used for testing beacon endpoints, and it can use either the new or old method for fetching blobs. This is even more important after Fusaka where it is even more difficult to configure beacon nodes to have and keep all blobs. In future we may want to add some features like looking up blobs by block number, which is something operators often want to do.

$ target/bin/blobtool fetch --beacon-url=https://sepolia --slot 8726798                              

Fetching all blobs for slot 8726798 using new blobs endpoint...
Successfully fetched 4 blobs in 550.156533ms
Blob 0: versioned_hash=0x01ac7e2c7fff656a02bb7d8e9934798ab3d1df8fbf20acff5aec87b183fd99cf, size=131072 bytes
Blob 1: versioned_hash=0x01808fa4fb38311c3a2a7c8a37d95af5a18eb3ccfe279d7e02f5df7d839b4a3a, size=131072 bytes
Blob 2: versioned_hash=0x01e0b7530de207b244ce87448a633d7bb077339236d4a4e82985b944553913dc, size=131072 bytes
Blob 3: versioned_hash=0x012873697f6a3baa3ed5b0e53784a2b389fee4c341253a5ef69a85bd720d2aad, size=131072 bytes

$ target/bin/blobtool fetch --beacon-url=https://sepolia --slot 8726798 --versioned-hashes=0x01ac7e2c7fff656a02bb7d8e9934798ab3d1df8fbf20acff5aec87b183fd99cf,0x01808fa4fb38311c3a2a7c8a37d95af5a18eb3ccfe279d7e02f5df7d839b4a3a,0x01e0b7530de207b244ce87448a633d7bb077339236d4a4e82985b944553913dc                               
Fetching 3 blobs for slot 8726798 using new blobs endpoint...
Successfully fetched 3 blobs in 3.075861523s
Blob 0: versioned_hash=0x01ac7e2c7fff656a02bb7d8e9934798ab3d1df8fbf20acff5aec87b183fd99cf (computed=0x01ac7e2c7fff656a02bb7d8e9934798ab3d1df8fbf20acff5aec87b183fd99cf), size=131072 bytes
Blob 1: versioned_hash=0x01808fa4fb38311c3a2a7c8a37d95af5a18eb3ccfe279d7e02f5df7d839b4a3a (computed=0x01808fa4fb38311c3a2a7c8a37d95af5a18eb3ccfe279d7e02f5df7d839b4a3a), size=131072 bytes
Blob 2: versioned_hash=0x01e0b7530de207b244ce87448a633d7bb077339236d4a4e82985b944553913dc (computed=0x01e0b7530de207b244ce87448a633d7bb077339236d4a4e82985b944553913dc), size=131072 bytes

$ target/bin/blobtool fetch --beacon-url=https://sepolia --slot 8726798 --versioned-hashes=0x01ac7e2c7fff656a02bb7d8e9934798ab3d1df8fbf20acff5aec87b183fd99cf,0x01808fa4fb38311c3a2a7c8a37d95af5a18eb3ccfe279d7e02f5df7d839b4a3a,0x01e0b7530de207b244ce87448a633d7bb077339236d4a4e82985b944553913dc --use-legacy-endpoint                             
Fetching 3 blobs for slot 8726798 using legacy blob_sidecars endpoint...
Successfully fetched 3 blobs in 3.254657814s
Blob 0: versioned_hash=0x01ac7e2c7fff656a02bb7d8e9934798ab3d1df8fbf20acff5aec87b183fd99cf (computed=0x01ac7e2c7fff656a02bb7d8e9934798ab3d1df8fbf20acff5aec87b183fd99cf), size=131072 bytes
Blob 1: versioned_hash=0x01808fa4fb38311c3a2a7c8a37d95af5a18eb3ccfe279d7e02f5df7d839b4a3a (computed=0x01808fa4fb38311c3a2a7c8a37d95af5a18eb3ccfe279d7e02f5df7d839b4a3a), size=131072 bytes
Blob 2: versioned_hash=0x01e0b7530de207b244ce87448a633d7bb077339236d4a4e82985b944553913dc (computed=0x01e0b7530de207b244ce87448a633d7bb077339236d4a4e82985b944553913dc), size=131072 bytes

$ blobtool fetch --beacon-url=https://sepolia --versioned-hashes=0x01ac7e2c7fff656a02bb7d8e9934798ab3d1df8fbf20acff5aec87b183fd99cf,0x01808fa4fb38311c3a2a7c8a37d95af5a18eb3ccfe279d7e02f5df7d839b4a3a,0x01e0b7530de207b244ce87448a633d7bb077339236d4a4e82985b944553913dc --compare-endpoints
Comparing legacy blob_sidecars and new blobs endpoints...

Fetching with legacy blob_sidecars endpoint...
✓ Legacy endpoint: fetched 3 blobs in 2.242901587s

Fetching with new blobs endpoint...
✓ New endpoint: fetched 3 blobs in 498.302806ms

Comparing blob data...
  Blob 0: ✓ identical (0x01ac7e2c7fff656a02bb7d8e9934798ab3d1df8fbf20acff5aec87b183fd99cf)
  Blob 1: ✓ identical (0x01808fa4fb38311c3a2a7c8a37d95af5a18eb3ccfe279d7e02f5df7d839b4a3a)
  Blob 2: ✓ identical (0x01e0b7530de207b244ce87448a633d7bb077339236d4a4e82985b944553913dc)

Performance comparison:
  Legacy endpoint: 2.242901587s
  New endpoint:    498.302806ms
  New endpoint is 77.8% faster

This change adds support for the new beacon chain endpoint
`/eth/v1/beacon/blobs/{block_id}` introduced in Fusaka while maintaining
backward compatibility with the legacy endpoint
`/eth/v1/beacon/blob_sidecars/{slot}`. block_id can be a slot so Nitro
just uses slot.

The new endpoint supports server-side filtering by versioned hash via
query parameters. Since the Arbitrum sequencer inbox message contains
the versioned hashes of the blobs that were posted, we can include those
in the query.

Key changes:
- Added `UseLegacyEndpoint` flag to BlobClientConfig to control which
  endpoint to use
- Created new `GetBlobsBySlot()` public method for direct slot-based
  blob fetching
- Implemented `getBlobs()` method for the new endpoint with versioned
  hash verification
- Updated `beaconRequest()` to support query parameters for filtering
- Added KZG commitment verification when versioned hashes are provided

Created `blobtool` CLI utility for testing both endpoints:
```
 # Fetch specific blob using new endpoint (default)
 blobtool fetch --beacon-url=<url> --slot=<slot> --versioned-hashes=<hash>

 # Fetch using legacy endpoint (requires versioned hashes)
 blobtool fetch --beacon-url=<url> --slot=<slot> --versioned-hashes=<hash> --use-legacy-endpoint

 # Compare both endpoints side-by-side
 blobtool fetch --beacon-url=<url> --slot=<slot> --versioned-hashes=<hash> --compare-endpoints
```

The new endpoint is used by default, with automatic fallback behavior
maintained through the existing secondary beacon URL mechanism.

Spec reference: https://github.com/ethereum/beacon-APIs/blob/master/apis/beacon/blobs/blobs.yaml
queryParams.Add("versioned_hashes", hash.Hex())
}

response, err := beaconRequest[[]hexutil.Bytes](b, ctx, fmt.Sprintf("/eth/v1/beacon/blobs/%d", slot), queryParams)

Choose a reason for hiding this comment

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

for others looking at this pr,
confirmed this was intentional, if versioned hashes are not provided all will be returned, this was intentional to add the query params

Copy link
Member Author

Choose a reason for hiding this comment

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

yep, when posting 4844 enabled batches the sequencer inbox message contains the versioned hashes as calldata. So the new API is actually really nice because we can only fetch what we need, in the order we want it, and also we can verify the blobs retrieved against the versioned hash.

func beaconRequest[T interface{}](b *BlobClient, ctx context.Context, beaconPath string) (T, error) {
// Unfortunately, methods on a struct can't be generic.

func beaconRequest[T interface{}](b *BlobClient, ctx context.Context, beaconPath string, queryParams url.Values) (T, error) {

Choose a reason for hiding this comment

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

Prysm should learn from this request setup ( we could also use some generics here)

var blobs []kzg4844.Blob
var err error
if b.useLegacyEndpoint {
blobs, err = b.blobSidecars(ctx, slot, versionedHashes)

Choose a reason for hiding this comment

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

We don't use versionedHashes on the legacy endpoint, it's blob indicies

Copy link
Member Author

Choose a reason for hiding this comment

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

This is a quirk of how our blob fetching works for Nitro for the legacy endpoint since does client side filtering to filter out non related blobs.


func (b *BlobClient) blobSidecars(ctx context.Context, slot uint64, versionedHashes []common.Hash) ([]kzg4844.Blob, error) {
rawData, err := beaconRequest[json.RawMessage](b, ctx, fmt.Sprintf("/eth/v1/beacon/blob_sidecars/%d", slot))
rawData, err := beaconRequest[json.RawMessage](b, ctx, fmt.Sprintf("/eth/v1/beacon/blob_sidecars/%d", slot), nil)

Choose a reason for hiding this comment

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

oh good, it's nil here, right we don't use versioned hashes here do we need to provide that?

Copy link
Member Author

@Tristan-Wilson Tristan-Wilson Oct 14, 2025

Choose a reason for hiding this comment

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

Exactly, the versioned hashes are just used for client side filtering for the old endpoint, since we don't know what index a particular blob is at we had to do it entirely client side.

Copy link
Member

Choose a reason for hiding this comment

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

I agree. Why even pass in versionedHashes if they aren't going to be used in the body of the function?

Copy link
Member Author

Choose a reason for hiding this comment

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

please see my reponse to James

if queryParams != nil {
beaconUrl.RawQuery = queryParams.Encode()
}
req, err := http.NewRequestWithContext(ctx, "GET", beaconUrl.String(), http.NoBody)

Choose a reason for hiding this comment

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

response might be faster with ssz type instead of json

Copy link
Member Author

Choose a reason for hiding this comment

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

It works now so we can leave this for later.

}

if config.UseLegacyEndpoint && len(versionedHashes) == 0 {
return fmt.Errorf("--versioned-hashes is required when using --use-legacy-endpoint")

Choose a reason for hiding this comment

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

I don't think this is correct? ( or are you using versioned hashes for something else outside of the endpoint query)

Choose a reason for hiding this comment

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

should be required when not using the new endpoint (based on implementation?)

Copy link
Member Author

Choose a reason for hiding this comment

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

This is a quirk of how our blob fetching works for Nitro for the legacy endpoint since does client side filtering to filter out non related blobs.


func parseFetchConfig(args []string) (*FetchConfig, error) {
f := flag.NewFlagSet("blobtool fetch", flag.ContinueOnError)
f.String("beacon-url", "", "Beacon Chain RPC URL")
Copy link
Member

Choose a reason for hiding this comment

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

Can you document how much of the URL is expected? Or, maybe provide a default, or an example?
I take it that we don't include the "/blobs" part of the URL, but do we stop at v1 or just before eth?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's just the base url, so none of the /eth/v1/beacon stuff. I don't know what to include as a default.


func (b *BlobClient) blobSidecars(ctx context.Context, slot uint64, versionedHashes []common.Hash) ([]kzg4844.Blob, error) {
rawData, err := beaconRequest[json.RawMessage](b, ctx, fmt.Sprintf("/eth/v1/beacon/blob_sidecars/%d", slot))
rawData, err := beaconRequest[json.RawMessage](b, ctx, fmt.Sprintf("/eth/v1/beacon/blob_sidecars/%d", slot), nil)
Copy link
Member

Choose a reason for hiding this comment

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

I agree. Why even pass in versionedHashes if they aren't going to be used in the body of the function?

@eljobe eljobe assigned Tristan-Wilson and unassigned eljobe Oct 14, 2025
Copy link
Member

@joshuacolvin0 joshuacolvin0 left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link

codecov bot commented Oct 14, 2025

Codecov Report

❌ Patch coverage is 0% with 60 lines in your changes missing coverage. Please review.
✅ Project coverage is 22.73%. Comparing base (d58fe46) to head (4537ec9).
⚠️ Report is 6 commits behind head on v3.7.0-backports.

Additional details and impacted files
@@                 Coverage Diff                  @@
##           v3.7.0-backports    #3826      +/-   ##
====================================================
+ Coverage             22.68%   22.73%   +0.04%     
====================================================
  Files                   383      383              
  Lines                 58209    58253      +44     
====================================================
+ Hits                  13206    13241      +35     
- Misses                42963    42981      +18     
+ Partials               2040     2031       -9     

@joshuacolvin0 joshuacolvin0 merged commit 91ebb83 into v3.7.0-backports Oct 14, 2025
15 checks passed
@joshuacolvin0 joshuacolvin0 deleted the v3.7.0-backports-support-beacon-blob-api branch October 14, 2025 18:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants