Skip to content
Merged
Show file tree
Hide file tree
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
16 changes: 11 additions & 5 deletions op-node/config/beacon.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,17 @@ type L1BeaconEndpointSetup interface {
// ShouldIgnoreBeaconCheck returns true if the Beacon-node version check should not halt startup.
ShouldIgnoreBeaconCheck() bool
ShouldFetchAllSidecars() bool
ShouldSkipBlobVerification() bool
Check() error
}

type L1BeaconEndpointConfig struct {
BeaconAddr string // Address of L1 User Beacon-API endpoint to use (beacon namespace required)
BeaconHeader string // Optional HTTP header for all requests to L1 Beacon
BeaconFallbackAddrs []string // Addresses of L1 Beacon-API fallback endpoints (only for blob sidecars retrieval)
BeaconCheckIgnore bool // When false, halt startup if the beacon version endpoint fails
BeaconFetchAllSidecars bool // Whether to fetch all blob sidecars and filter locally
BeaconAddr string // Address of L1 User Beacon-API endpoint to use (beacon namespace required)
BeaconHeader string // Optional HTTP header for all requests to L1 Beacon
BeaconFallbackAddrs []string // Addresses of L1 Beacon-API fallback endpoints (only for blob sidecars retrieval)
BeaconCheckIgnore bool // When false, halt startup if the beacon version endpoint fails
BeaconFetchAllSidecars bool // Whether to fetch all blob sidecars and filter locally
BeaconSkipBlobVerification bool // Whether to skip the verification of the kzg_proof for each blob returned by the Beacon node
}

var _ L1BeaconEndpointSetup = (*L1BeaconEndpointConfig)(nil)
Expand Down Expand Up @@ -66,6 +68,10 @@ func (cfg *L1BeaconEndpointConfig) ShouldFetchAllSidecars() bool {
return cfg.BeaconFetchAllSidecars
}

func (cfg *L1BeaconEndpointConfig) ShouldSkipBlobVerification() bool {
return cfg.BeaconSkipBlobVerification
}

func parseHTTPHeader(headerStr string) (http.Header, error) {
h := make(http.Header, 1)
s := strings.SplitN(headerStr, ": ", 2)
Expand Down
9 changes: 9 additions & 0 deletions op-node/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ var (
EnvVars: prefixEnvVars("L1_BEACON_FETCH_ALL_SIDECARS"),
Category: L1RPCCategory,
}
BeaconSkipBlobVerification = &cli.BoolFlag{
Name: "l1.beacon.skip-blob-verification",
Usage: "If true, skips the verification of the kzg_proof for each blob returned by the Beacon node. Not recommended unless the provided beacon endpoints are trusted.",
Required: false,
Value: false,
EnvVars: prefixEnvVars("L1_BEACON_SKIP_BLOB_VERIFICATION"),
Category: L1RPCCategory,
}
SyncModeFlag = &cli.GenericFlag{
Name: "syncmode",
Usage: fmt.Sprintf("Blockchain sync mode (options: %s)", openum.EnumString(sync.ModeStrings)),
Expand Down Expand Up @@ -434,6 +442,7 @@ var optionalFlags = []cli.Flag{
BeaconFallbackAddrs,
BeaconCheckIgnore,
BeaconFetchAllSidecars,
BeaconSkipBlobVerification,
SyncModeFlag,
FetchWithdrawalRootFromState,
L1TrustRPC,
Expand Down
3 changes: 2 additions & 1 deletion op-node/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,8 @@ func (n *OpNode) initL1BeaconAPI(ctx context.Context, cfg *config.Config) error
return fmt.Errorf("failed to setup L1 Beacon API client: %w", err)
}
beaconCfg := sources.L1BeaconClientConfig{
FetchAllSidecars: cfg.Beacon.ShouldFetchAllSidecars(),
FetchAllSidecars: cfg.Beacon.ShouldFetchAllSidecars(),
SkipBlobVerification: cfg.Beacon.ShouldSkipBlobVerification(),
}
n.beacon = sources.NewL1BeaconClient(beaconClient, beaconCfg, fallbacks...)

Expand Down
11 changes: 6 additions & 5 deletions op-node/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,12 @@ func NewSupervisorEndpointConfig(ctx *cli.Context) *interop.Config {

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

Expand Down
15 changes: 9 additions & 6 deletions op-service/sources/l1_beacon_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ const (
)

type L1BeaconClientConfig struct {
FetchAllSidecars bool
FetchAllSidecars bool
SkipBlobVerification bool
}

// L1BeaconClient is a high level golang client for the Beacon API.
Expand Down Expand Up @@ -271,14 +272,14 @@ func (cl *L1BeaconClient) GetBlobs(ctx context.Context, ref eth.L1BlockRef, hash
if err != nil {
return nil, fmt.Errorf("failed to get blob sidecars for L1BlockRef %s: %w", ref, err)
}
blobs, err := blobsFromSidecars(blobSidecars, hashes)
blobs, err := blobsFromSidecars(blobSidecars, hashes, cl.cfg.SkipBlobVerification)
if err != nil {
return nil, fmt.Errorf("failed to get blobs from sidecars for L1BlockRef %s: %w", ref, err)
}
return blobs, nil
}

func blobsFromSidecars(blobSidecars []*eth.BlobSidecar, hashes []eth.IndexedBlobHash) ([]*eth.Blob, error) {
func blobsFromSidecars(blobSidecars []*eth.BlobSidecar, hashes []eth.IndexedBlobHash, skipBlobVerification bool) ([]*eth.Blob, error) {
if len(blobSidecars) != len(hashes) {
return nil, fmt.Errorf("number of hashes and blobSidecars mismatch, %d != %d", len(hashes), len(blobSidecars))
}
Expand All @@ -296,9 +297,11 @@ func blobsFromSidecars(blobSidecars []*eth.BlobSidecar, hashes []eth.IndexedBlob
return nil, fmt.Errorf("expected hash %s for blob at index %d but got %s", ih.Hash, ih.Index, hash)
}

// 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("blob at index %d failed verification: %w", i, err)
if !skipBlobVerification {
// 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("blob at index %d failed verification: %w", i, err)
}
}
out[i] = &sidecar.Blob
}
Expand Down
84 changes: 77 additions & 7 deletions op-service/sources/l1_beacon_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import (
"strconv"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto/kzg4844"

client_mocks "github.com/ethereum-optimism/optimism/op-service/client/mocks"
Expand Down Expand Up @@ -61,17 +63,17 @@ func TestBlobsFromSidecars(t *testing.T) {

// put the sidecars in scrambled order to confirm error
sidecars := []*eth.BlobSidecar{sidecar2, sidecar0, sidecar1}
_, err := blobsFromSidecars(sidecars, hashes)
_, err := blobsFromSidecars(sidecars, hashes, false)
require.Error(t, err)

// too few sidecars should error
sidecars = []*eth.BlobSidecar{sidecar0, sidecar1}
_, err = blobsFromSidecars(sidecars, hashes)
_, err = blobsFromSidecars(sidecars, hashes, false)
require.Error(t, err)

// correct order should work
sidecars = []*eth.BlobSidecar{sidecar0, sidecar1, sidecar2}
blobs, err := blobsFromSidecars(sidecars, hashes)
blobs, err := blobsFromSidecars(sidecars, hashes, false)
require.NoError(t, err)
// confirm order by checking first blob byte against expected index
for i := range blobs {
Expand All @@ -82,27 +84,65 @@ func TestBlobsFromSidecars(t *testing.T) {
badProof := *sidecar0
badProof.KZGProof[11]++
sidecars[1] = &badProof
_, err = blobsFromSidecars(sidecars, hashes)
_, err = blobsFromSidecars(sidecars, hashes, false)
require.Error(t, err)

// mangle a commitment to make sure it's detected
badCommitment := *sidecar0
badCommitment.KZGCommitment[13]++
sidecars[1] = &badCommitment
_, err = blobsFromSidecars(sidecars, hashes)
_, err = blobsFromSidecars(sidecars, hashes, false)
require.Error(t, err)

// mangle a hash to make sure it's detected
sidecars[1] = sidecar0
hashes[2].Hash[17]++
_, err = blobsFromSidecars(sidecars, hashes)
_, err = blobsFromSidecars(sidecars, hashes, false)
require.Error(t, err)

}

func KZGProofFromHex(s string) (kzg4844.Proof, error) {
var out kzg4844.Proof // underlying size is 48 bytes
b, err := hexutil.Decode(s)
if err != nil {
return out, err
}
if len(b) != 48 {
return out, fmt.Errorf("want 48 bytes, got %d", len(b))
}
copy(out[:], b)
return out, nil
}

var badProof, _ = KZGProofFromHex("0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")

func TestBlobsFromSidecars_SkipBlobVerification(t *testing.T) {
indices := []uint64{5, 7, 2}
index0, sidecar0 := makeTestBlobSidecar(indices[0])
index1, sidecar1 := makeTestBlobSidecar(indices[1])
index2, sidecar2 := makeTestBlobSidecar(indices[2])
hashes := []eth.IndexedBlobHash{index0, index1, index2}

sidecars := []*eth.BlobSidecar{sidecar0, sidecar1, sidecar2}

// Set proof to a bad / stubbed value
sidecars[1].KZGProof = eth.Bytes48(badProof)

// Check that verification succeeds when skipBlobVerification is true
_, err := blobsFromSidecars(sidecars, hashes, true)
require.NoError(t, err)

// Check that verification fails when skipBlobVerification is false
_, err = blobsFromSidecars(sidecars, hashes, false)
require.Error(t, err)

}

func TestBlobsFromSidecars_EmptySidecarList(t *testing.T) {
hashes := []eth.IndexedBlobHash{}
sidecars := []*eth.BlobSidecar{}
blobs, err := blobsFromSidecars(sidecars, hashes)
blobs, err := blobsFromSidecars(sidecars, hashes, false)
require.NoError(t, err)
require.Empty(t, blobs, "blobs should be empty when no sidecars are provided")
}
Expand Down Expand Up @@ -186,7 +226,37 @@ func TestBeaconClientFallback(t *testing.T) {
resp, err = c.GetBlobSidecars(ctx, eth.L1BlockRef{Time: 14}, hashes)
require.Equal(t, sidecars, resp)
require.NoError(t, err)
}

func TestBeaconClientSkipBlobVerification(t *testing.T) {
indices := []uint64{5, 7, 2}
index0, sidecar0 := makeTestBlobSidecar(indices[0])
index1, sidecar1 := makeTestBlobSidecar(indices[1])
index2, sidecar2 := makeTestBlobSidecar(indices[2])

hashes := []eth.IndexedBlobHash{index0, index1, index2}
sidecars := []*eth.BlobSidecar{sidecar0, sidecar1, sidecar2}

// invalidate proof
sidecar1.KZGProof = eth.Bytes48(badProof)
apiSidecars := toAPISideCars(sidecars)

ctx := context.Background()
p := mocks.NewBeaconClient(t)

p.EXPECT().BeaconGenesis(ctx).Return(eth.APIGenesisResponse{Data: eth.ReducedGenesisData{GenesisTime: 10}}, nil)
p.EXPECT().ConfigSpec(ctx).Return(eth.APIConfigResponse{Data: eth.ReducedConfigData{SecondsPerSlot: 2}}, nil)
clientWithValidation := NewL1BeaconClient(p, L1BeaconClientConfig{SkipBlobVerification: false})
p.EXPECT().BeaconBlobSideCars(ctx, false, uint64(1), hashes).Return(eth.APIGetBlobSidecarsResponse{Data: apiSidecars}, nil)
_, err := clientWithValidation.GetBlobs(ctx, eth.L1BlockRef{Time: 12}, hashes)
assert.Error(t, err)

p.EXPECT().BeaconGenesis(ctx).Return(eth.APIGenesisResponse{Data: eth.ReducedGenesisData{GenesisTime: 10}}, nil)
p.EXPECT().ConfigSpec(ctx).Return(eth.APIConfigResponse{Data: eth.ReducedConfigData{SecondsPerSlot: 2}}, nil)
clientWithoutValidation := NewL1BeaconClient(p, L1BeaconClientConfig{SkipBlobVerification: true})
p.EXPECT().BeaconBlobSideCars(ctx, false, uint64(1), hashes).Return(eth.APIGetBlobSidecarsResponse{Data: apiSidecars}, nil)
_, err = clientWithoutValidation.GetBlobs(ctx, eth.L1BlockRef{Time: 12}, hashes)
assert.NoError(t, err)
}

func TestBeaconHTTPClient(t *testing.T) {
Expand Down