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

op-node,sources: Add Beacon source option to fetch all sidecars #9151

Merged
merged 4 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion op-e2e/l1_beacon_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ func TestGetVersion(t *testing.T) {
})
require.NoError(t, beaconApi.Start("127.0.0.1:0"))

cl := sources.NewL1BeaconClient(client.NewBasicHTTPClient(beaconApi.BeaconAddr(), l))
beaconCfg := sources.L1BeaconClientConfig{FetchAllSidecars: false}
cl := sources.NewL1BeaconClient(client.NewBasicHTTPClient(beaconApi.BeaconAddr(), l), beaconCfg)

version, err := cl.GetVersion(context.Background())
require.NoError(t, err)
Expand Down
8 changes: 8 additions & 0 deletions op-node/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ var (
Value: false,
EnvVars: prefixEnvVars("L1_BEACON_IGNORE"),
}
BeaconFetchAllSidecars = &cli.BoolFlag{
Name: "l1.beacon.fetch-all-sidecars",
Usage: "If true, all sidecars are fetched and filtered locally. Workaround for buggy Beacon nodes.",
Required: false,
Value: false,
EnvVars: prefixEnvVars("L1_BEACON_FETCH_ALL_SIDECARS"),
}
SyncModeFlag = &cli.GenericFlag{
Name: "syncmode",
Usage: fmt.Sprintf("IN DEVELOPMENT: Options are: %s", openum.EnumString(sync.ModeStrings)),
Expand Down Expand Up @@ -285,6 +292,7 @@ var requiredFlags = []cli.Flag{
var optionalFlags = []cli.Flag{
BeaconAddr,
BeaconCheckIgnore,
BeaconFetchAllSidecars,
SyncModeFlag,
RPCListenAddr,
RPCListenPort,
Expand Down
10 changes: 8 additions & 2 deletions op-node/node/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type L1BeaconEndpointSetup interface {
Setup(ctx context.Context, log log.Logger) (cl client.HTTP, err error)
// ShouldIgnoreBeaconCheck returns true if the Beacon-node version check should not halt startup.
ShouldIgnoreBeaconCheck() bool
ShouldFetchAllSidecars() bool
Check() error
}

Expand Down Expand Up @@ -175,8 +176,9 @@ func (cfg *PreparedL1Endpoint) Check() error {
}

type L1BeaconEndpointConfig struct {
BeaconAddr string // Address of L1 User Beacon-API endpoint to use (beacon namespace required)
BeaconCheckIgnore bool // When false, halt startup if the beacon version endpoint fails
BeaconAddr string // Address of L1 User Beacon-API endpoint to use (beacon namespace required)
BeaconCheckIgnore bool // When false, halt startup if the beacon version endpoint fails
BeaconFetchAllSidecars bool // Whether to fetch all blob sidecars and filter locally
}

var _ L1BeaconEndpointSetup = (*L1BeaconEndpointConfig)(nil)
Expand All @@ -195,3 +197,7 @@ func (cfg *L1BeaconEndpointConfig) Check() error {
func (cfg *L1BeaconEndpointConfig) ShouldIgnoreBeaconCheck() bool {
return cfg.BeaconCheckIgnore
}

func (cfg *L1BeaconEndpointConfig) ShouldFetchAllSidecars() bool {
return cfg.BeaconFetchAllSidecars
}
9 changes: 5 additions & 4 deletions op-node/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/sources"
)

var (
ErrAlreadyClosed = errors.New("node is already closed")
)
var ErrAlreadyClosed = errors.New("node is already closed")

type OpNode struct {
log log.Logger
Expand Down Expand Up @@ -308,7 +306,10 @@ func (n *OpNode) initL1BeaconAPI(ctx context.Context, cfg *Config) error {
if err != nil {
return fmt.Errorf("failed to setup L1 Beacon API client: %w", err)
}
n.beacon = sources.NewL1BeaconClient(httpClient)
beaconCfg := sources.L1BeaconClientConfig{
FetchAllSidecars: cfg.Beacon.ShouldFetchAllSidecars(),
}
n.beacon = sources.NewL1BeaconClient(httpClient, beaconCfg)

// Retry retrieval of the Beacon API version, to be more robust on startup against Beacon API connection issues.
beaconVersion, missingEndpoint, err := retry.Do2[string, bool](ctx, 5, retry.Exponential(), func() (string, bool, error) {
Expand Down
5 changes: 3 additions & 2 deletions op-node/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,9 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) {

func NewBeaconEndpointConfig(ctx *cli.Context) node.L1BeaconEndpointSetup {
return &node.L1BeaconEndpointConfig{
BeaconAddr: ctx.String(flags.BeaconAddr.Name),
BeaconCheckIgnore: ctx.Bool(flags.BeaconCheckIgnore.Name),
BeaconAddr: ctx.String(flags.BeaconAddr.Name),
BeaconCheckIgnore: ctx.Bool(flags.BeaconCheckIgnore.Name),
BeaconFetchAllSidecars: ctx.Bool(flags.BeaconFetchAllSidecars.Name),
}
}

Expand Down
50 changes: 35 additions & 15 deletions op-service/sources/l1_beacon_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,29 @@ const (
sidecarsMethodPrefix = "eth/v1/beacon/blob_sidecars/"
)

type L1BeaconClientConfig struct {
FetchAllSidecars bool
}

type L1BeaconClient struct {
cl client.HTTP
cl client.HTTP
cfg L1BeaconClientConfig

initLock sync.Mutex
timeToSlotFn TimeToSlotFn
}

// NewL1BeaconClient returns a client for making requests to an L1 consensus layer node.
func NewL1BeaconClient(cl client.HTTP) *L1BeaconClient {
return &L1BeaconClient{cl: cl}
func NewL1BeaconClient(cl client.HTTP, cfg L1BeaconClientConfig) *L1BeaconClient {
return &L1BeaconClient{cl: cl, cfg: cfg}
}

func (cl *L1BeaconClient) apiReq(ctx context.Context, dest any, reqPath string, reqQuery url.Values) error {
headers := http.Header{}
headers.Add("Accept", "application/json")
resp, err := cl.cl.Get(ctx, reqPath, reqQuery, headers)
if err != nil {
return fmt.Errorf("%w: http Get failed", err)
return fmt.Errorf("http Get failed: %w", err)
}
if resp.StatusCode != http.StatusOK {
errMsg, _ := io.ReadAll(resp.Body)
Expand All @@ -54,7 +59,7 @@ func (cl *L1BeaconClient) apiReq(ctx context.Context, dest any, reqPath string,
return err
}
if err := resp.Body.Close(); err != nil {
return fmt.Errorf("%w: failed to close response body", err)
return fmt.Errorf("failed to close response body: %w", err)
}
return nil
}
Expand Down Expand Up @@ -102,28 +107,43 @@ func (cl *L1BeaconClient) GetBlobSidecars(ctx context.Context, ref eth.L1BlockRe
}
slotFn, err := cl.GetTimeToSlotFn(ctx)
if err != nil {
return nil, fmt.Errorf("%w: failed to get time to slot function", err)
return nil, fmt.Errorf("failed to get time to slot function: %w", err)
}
slot, err := slotFn(ref.Time)
if err != nil {
return nil, fmt.Errorf("%w: error in converting ref.Time to slot", err)
return nil, fmt.Errorf("error in converting ref.Time to slot: %w", err)
}

reqPath := path.Join(sidecarsMethodPrefix, strconv.FormatUint(slot, 10))
reqQuery := url.Values{}
for i := range hashes {
reqQuery.Add("indices", strconv.FormatUint(hashes[i].Index, 10))
var reqQuery url.Values
if !cl.cfg.FetchAllSidecars {
reqQuery = url.Values{}
for i := range hashes {
reqQuery.Add("indices", strconv.FormatUint(hashes[i].Index, 10))
}
}

var resp eth.APIGetBlobSidecarsResponse
if err := cl.apiReq(ctx, &resp, reqPath, reqQuery); err != nil {
return nil, fmt.Errorf("%w: failed to fetch blob sidecars for slot %v block %v", err, slot, ref)
return nil, fmt.Errorf("failed to fetch blob sidecars for slot %v block %v: %w", slot, ref, err)
}
if len(hashes) != len(resp.Data) {

apiscs := make([]*eth.APIBlobSidecar, 0, len(hashes))
// filter and order by hashes
for _, h := range hashes {
sebastianst marked this conversation as resolved.
Show resolved Hide resolved
for _, apisc := range resp.Data {
if h.Index == uint64(apisc.Index) {
apiscs = append(apiscs, apisc)
}
sebastianst marked this conversation as resolved.
Show resolved Hide resolved
}
}

if len(hashes) != len(apiscs) {
return nil, fmt.Errorf("expected %v sidecars but got %v", len(hashes), len(resp.Data))
}

bscs := make([]*eth.BlobSidecar, 0, len(hashes))
for _, apisc := range resp.Data {
for _, apisc := range apiscs {
bscs = append(bscs, apisc.BlobSidecar())
}

Expand All @@ -137,7 +157,7 @@ func (cl *L1BeaconClient) GetBlobSidecars(ctx context.Context, ref eth.L1BlockRe
func (cl *L1BeaconClient) GetBlobs(ctx context.Context, ref eth.L1BlockRef, hashes []eth.IndexedBlobHash) ([]*eth.Blob, error) {
blobSidecars, err := cl.GetBlobSidecars(ctx, ref, hashes)
if err != nil {
return nil, fmt.Errorf("%w: failed to get blob sidecars for L1BlockRef %s", err, ref)
return nil, fmt.Errorf("failed to get blob sidecars for L1BlockRef %s: %w", ref, err)
}
return blobsFromSidecars(blobSidecars, hashes)
}
Expand All @@ -164,7 +184,7 @@ func blobsFromSidecars(blobSidecars []*eth.BlobSidecar, hashes []eth.IndexedBlob

// confirm blob data is valid by verifying its proof against the commitment
if err := eth.VerifyBlobProof(&sidecar.Blob, kzg4844.Commitment(sidecar.KZGCommitment), kzg4844.Proof(sidecar.KZGProof)); err != nil {
return nil, fmt.Errorf("%w: blob at index %d failed verification", err, i)
return nil, fmt.Errorf("blob at index %d failed verification: %w", i, err)
}
out[i] = &sidecar.Blob
}
Expand Down
Loading