Repository navigation
Add support for new beacon chain /blobs endpoint - #3826
Conversation
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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
We don't use versionedHashes on the legacy endpoint, it's blob indicies
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
oh good, it's nil here, right we don't use versioned hashes here do we need to provide that?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I agree. Why even pass in versionedHashes if they aren't going to be used in the body of the function?
There was a problem hiding this comment.
please see my reponse to James
| if queryParams != nil { | ||
| beaconUrl.RawQuery = queryParams.Encode() | ||
| } | ||
| req, err := http.NewRequestWithContext(ctx, "GET", beaconUrl.String(), http.NoBody) |
There was a problem hiding this comment.
response might be faster with ssz type instead of json
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
I don't think this is correct? ( or are you using versioned hashes for something else outside of the endpoint query)
There was a problem hiding this comment.
should be required when not using the new endpoint (based on implementation?)
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
I agree. Why even pass in versionedHashes if they aren't going to be used in the body of the function?
Codecov Report❌ Patch coverage is 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 |
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:
UseLegacyEndpointflag to BlobClientConfig to control which endpoint to useGetBlobsBySlot()public method for direct slot-based blob fetchinggetBlobs()method for the new endpoint with versioned hash verificationbeaconRequest()to support query parameters for filteringCreated
blobtoolCLI utility for testing both 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.