From b2498ebc32496642430d850e2bce74824037ebd6 Mon Sep 17 00:00:00 2001 From: Sam Calder-Mason Date: Thu, 16 Jul 2026 14:29:20 +1000 Subject: [PATCH 1/2] feat: fetch raw signed execution payload envelopes Adds RawExecutionPayloadEnvelope to the consensus API client and FetchRawExecutionPayloadEnvelope to the Node interface, exposing GET /eth/v1/beacon/execution_payload_envelopes/{block_id} (gloas onwards) in JSON or SSZ depending on the requested content type. Claude-Session: https://claude.ai/code/session_012CnMNMdPisWGmvKhKzj1ii --- pkg/beacon/api/api.go | 12 ++++++++++++ pkg/beacon/beacon.go | 3 +++ pkg/beacon/fetch.go | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/pkg/beacon/api/api.go b/pkg/beacon/api/api.go index f20f956..6f93da5 100644 --- a/pkg/beacon/api/api.go +++ b/pkg/beacon/api/api.go @@ -19,6 +19,7 @@ type ConsensusClient interface { NodePeers(ctx context.Context) (types.Peers, error) NodePeerCount(ctx context.Context) (types.PeerCount, error) RawBlock(ctx context.Context, stateID string, contentType string) ([]byte, error) + RawExecutionPayloadEnvelope(ctx context.Context, blockID string, contentType string) ([]byte, error) RawDebugBeaconState(ctx context.Context, stateID string, contentType string) ([]byte, error) DepositSnapshot(ctx context.Context) (*types.DepositSnapshot, error) NodeIdentity(ctx context.Context) (*types.Identity, error) @@ -223,6 +224,17 @@ func (c *consensusClient) RawBlock(ctx context.Context, stateID string, contentT return data, nil } +// RawExecutionPayloadEnvelope returns the signed execution payload envelope +// for the given block id in the requested format (gloas onwards). +func (c *consensusClient) RawExecutionPayloadEnvelope(ctx context.Context, blockID string, contentType string) ([]byte, error) { + data, err := c.getRaw(ctx, fmt.Sprintf("/eth/v1/beacon/execution_payload_envelopes/%s", blockID), contentType) + if err != nil { + return nil, err + } + + return data, nil +} + // DepositSnapshot returns the deposit snapshot in the requested format. func (c *consensusClient) DepositSnapshot(ctx context.Context) (*types.DepositSnapshot, error) { data, err := c.get(ctx, "/eth/v1/beacon/deposit_snapshot") diff --git a/pkg/beacon/beacon.go b/pkg/beacon/beacon.go index 277f6e6..b797ce1 100644 --- a/pkg/beacon/beacon.go +++ b/pkg/beacon/beacon.go @@ -67,6 +67,9 @@ type Node interface { FetchBlock(ctx context.Context, stateID string) (*spec.VersionedSignedBeaconBlock, error) // FetchRawBlock fetches the raw, unparsed block for the given state id. FetchRawBlock(ctx context.Context, stateID string, contentType string) ([]byte, error) + // FetchRawExecutionPayloadEnvelope fetches the raw, unparsed signed execution + // payload envelope for the given block id (gloas onwards). + FetchRawExecutionPayloadEnvelope(ctx context.Context, blockID string, contentType string) ([]byte, error) // FetchBlockRoot fetches the block root for the given state id. FetchBlockRoot(ctx context.Context, stateID string) (*phase0.Root, error) // FetchBeaconState fetches the beacon state for the given state id. diff --git a/pkg/beacon/fetch.go b/pkg/beacon/fetch.go index 9595448..1dc1824 100644 --- a/pkg/beacon/fetch.go +++ b/pkg/beacon/fetch.go @@ -73,6 +73,10 @@ func (n *node) FetchRawBlock(ctx context.Context, stateID string, contentType st return n.api.RawBlock(ctx, stateID, contentType) } +func (n *node) FetchRawExecutionPayloadEnvelope(ctx context.Context, blockID string, contentType string) ([]byte, error) { + return n.api.RawExecutionPayloadEnvelope(ctx, blockID, contentType) +} + func (n *node) FetchBlockRoot(ctx context.Context, stateID string) (*phase0.Root, error) { return n.getBlockRoot(ctx, stateID) } From 1de3961bc7c995fe091589bd577b9f858b4f8a87 Mon Sep 17 00:00:00 2001 From: Sam Calder-Mason Date: Thu, 16 Jul 2026 14:34:07 +1000 Subject: [PATCH 2/2] feat: typed ErrNotFound for 404 responses from raw endpoints Claude-Session: https://claude.ai/code/session_012CnMNMdPisWGmvKhKzj1ii --- pkg/beacon/api/api.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/beacon/api/api.go b/pkg/beacon/api/api.go index 6f93da5..881478e 100644 --- a/pkg/beacon/api/api.go +++ b/pkg/beacon/api/api.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -13,6 +14,11 @@ import ( "github.com/sirupsen/logrus" ) +// ErrNotFound is returned when the node responds with HTTP 404 for the +// requested resource, e.g. an execution payload envelope that has not been +// revealed or a block that does not exist. +var ErrNotFound = errors.New("not found") + // ConsensusClient is an interface for executing RPC calls to the Ethereum node. type ConsensusClient interface { NodePeer(ctx context.Context, peerID string) (types.Peer, error) @@ -153,6 +159,10 @@ func (c *consensusClient) getRaw(ctx context.Context, path string, contentType s defer rsp.Body.Close() if rsp.StatusCode != http.StatusOK { + if rsp.StatusCode == http.StatusNotFound { + return nil, fmt.Errorf("status code: %d: %w", rsp.StatusCode, ErrNotFound) + } + return nil, fmt.Errorf("status code: %d", rsp.StatusCode) }