Commit 9603ab0c authored by protolambda's avatar protolambda Committed by GitHub

op-e2e: EIP-4844 DA test & beacon client query string fix (#9076)

* op-e2e: revive original 4844 DA test
Co-authored-by: default avatarRoberto Bayardo <bayardo@alum.mit.edu>

* op-service: fix beacon API HTTP client encoding of query string, avoid malformatting due to path joining

* op-batcher: data-availability-type enum and generic flag

* op-e2e: eip4844 test improvements

* op-batcher,op-service: review fixes on 4844 api fix

* eth: Add tests of Beacon API response structs

* eth,sources,fakebeacon: Update Beacon API BlobSidecar format

* eth: Remove field BlobSidecar.BlockRoot

* eth: Fix BeaconBlockHeader json tag, test APIGetBlobSidecarsResponse

* eth: Simplify Beacon API types and tests

* op-service: fix bytes96 terminal stringer in op-service/eth/types.go

* op-service: fix comment lint

---------
Co-authored-by: default avatarRoberto Bayardo <bayardo@alum.mit.edu>
Co-authored-by: default avatarSebastian Stammler <seb@oplabs.co>
parent b83650df
......@@ -57,9 +57,9 @@ type CLIConfig struct {
BatchType uint
// DataAvailabilityType is one of the values defined in op-batcher/flags/flags.go and dictates
// the data availability type to use for poting batches, e.g. blobs vs calldata.
DataAvailabilityType string
// DataAvailabilityType is one of the values defined in op-batcher/flags/types.go and dictates
// the data availability type to use for posting batches, e.g. blobs vs calldata.
DataAvailabilityType flags.DataAvailabilityType
TxMgrConfig txmgr.CLIConfig
LogConfig oplog.CLIConfig
......@@ -91,11 +91,8 @@ func (c *CLIConfig) Check() error {
if c.BatchType > 1 {
return fmt.Errorf("unknown batch type: %v", c.BatchType)
}
switch c.DataAvailabilityType {
case flags.CalldataType:
case flags.BlobsType:
default:
return fmt.Errorf("unknown data availability type: %v", c.DataAvailabilityType)
if !flags.ValidDataAvailabilityType(c.DataAvailabilityType) {
return fmt.Errorf("unknown data availability type: %q", c.DataAvailabilityType)
}
if err := c.MetricsConfig.Check(); err != nil {
return err
......@@ -128,7 +125,7 @@ func NewConfig(ctx *cli.Context) *CLIConfig {
MaxL1TxSize: ctx.Uint64(flags.MaxL1TxSizeBytesFlag.Name),
Stopped: ctx.Bool(flags.StoppedFlag.Name),
BatchType: ctx.Uint(flags.BatchTypeFlag.Name),
DataAvailabilityType: ctx.String(flags.DataAvailabilityTypeFlag.Name),
DataAvailabilityType: flags.DataAvailabilityType(ctx.String(flags.DataAvailabilityTypeFlag.Name)),
TxMgrConfig: txmgr.ReadCLIConfig(ctx),
LogConfig: oplog.ReadCLIConfig(ctx),
MetricsConfig: opmetrics.ReadCLIConfig(ctx),
......
......@@ -85,7 +85,7 @@ func TestBatcherConfig(t *testing.T) {
{
name: "invalid batch submission policy",
override: func(c *batcher.CLIConfig) { c.DataAvailabilityType = "foo" },
errString: "unknown data availability type: foo",
errString: "unknown data availability type: \"foo\"",
},
}
......
......@@ -8,6 +8,7 @@ import (
"github.com/ethereum-optimism/optimism/op-batcher/compressor"
opservice "github.com/ethereum-optimism/optimism/op-service"
openum "github.com/ethereum-optimism/optimism/op-service/enum"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
......@@ -21,12 +22,6 @@ func prefixEnvVars(name string) []string {
return opservice.PrefixEnvVar(EnvVarPrefix, name)
}
const (
// data availability types
CalldataType = "calldata"
BlobsType = "blobs"
)
var (
// Required flags
L1EthRpcFlag = &cli.StringFlag{
......@@ -88,10 +83,14 @@ var (
Value: 0,
EnvVars: prefixEnvVars("BATCH_TYPE"),
}
DataAvailabilityTypeFlag = &cli.StringFlag{
Name: "data-availability-type",
Usage: "The data availability type to use for submitting batches to the L1, e.g. blobs or calldata.",
Value: CalldataType,
DataAvailabilityTypeFlag = &cli.GenericFlag{
Name: "data-availability-type",
Usage: "The data availability type to use for submitting batches to the L1. Valid options: " +
openum.EnumString(DataAvailabilityTypes),
Value: func() *DataAvailabilityType {
out := CalldataType
return &out
}(),
EnvVars: prefixEnvVars("DATA_AVAILABILITY_TYPE"),
}
// Legacy Flags
......
package flags
import "fmt"
type DataAvailabilityType string
const (
// data availability types
CalldataType DataAvailabilityType = "calldata"
BlobsType DataAvailabilityType = "blobs"
)
var DataAvailabilityTypes = []DataAvailabilityType{
CalldataType,
BlobsType,
}
func (kind DataAvailabilityType) String() string {
return string(kind)
}
func (kind *DataAvailabilityType) Set(value string) error {
if !ValidDataAvailabilityType(DataAvailabilityType(value)) {
return fmt.Errorf("unknown data-availability type: %q", value)
}
*kind = DataAvailabilityType(value)
return nil
}
func (kind *DataAvailabilityType) Clone() any {
cpy := *kind
return &cpy
}
func ValidDataAvailabilityType(value DataAvailabilityType) bool {
for _, k := range DataAvailabilityTypes {
if k == value {
return true
}
}
return false
}
......@@ -71,7 +71,7 @@ func (f *FakeBeacon) Start(addr string) error {
blockID := strings.TrimPrefix(r.URL.Path, "/eth/v1/beacon/blob_sidecars/")
slot, err := strconv.ParseUint(blockID, 10, 64)
if err != nil {
f.log.Error("could not parse block id from request", "url", r.URL.Path)
f.log.Error("could not parse block id from request", "url", r.URL.Path, "err", err)
w.WriteHeader(http.StatusBadRequest)
return
}
......@@ -105,19 +105,23 @@ func (f *FakeBeacon) Start(addr string) error {
var mockBeaconBlockRoot [32]byte
mockBeaconBlockRoot[0] = 42
binary.LittleEndian.PutUint64(mockBeaconBlockRoot[32-8:], slot)
sidecars := make([]*eth.BlobSidecar, len(indices))
sidecars := make([]*eth.APIBlobSidecar, len(indices))
for i, ix := range indices {
if ix >= uint64(len(bundle.Blobs)) {
f.log.Error("blob index from request is out of range", "url", r.URL)
w.WriteHeader(http.StatusBadRequest)
return
}
sidecars[i] = &eth.BlobSidecar{
BlockRoot: mockBeaconBlockRoot,
Slot: eth.Uint64String(slot),
sidecars[i] = &eth.APIBlobSidecar{
Index: eth.Uint64String(i),
KZGCommitment: eth.Bytes48(bundle.Commitments[ix]),
KZGProof: eth.Bytes48(bundle.Proofs[ix]),
SignedBlockHeader: eth.SignedBeaconBlockHeader{
Message: eth.BeaconBlockHeader{
StateRoot: mockBeaconBlockRoot,
Slot: eth.Uint64String(slot),
},
},
}
copy(sidecars[i].Blob[:], bundle.Blobs[ix])
}
......
package op_e2e
import (
"context"
"math/big"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
batcherFlags "github.com/ethereum-optimism/optimism/op-batcher/flags"
"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum-optimism/optimism/op-service/testlog"
)
// TestSystem4844E2E runs the SystemE2E test with 4844 enabled on L1,
// and active on the rollup in the op-batcher and verifier.
func TestSystem4844E2E(t *testing.T) {
InitParallel(t)
cfg := DefaultSystemConfig(t)
cfg.DataAvailabilityType = batcherFlags.BlobsType
genesisActivation := hexutil.Uint64(0)
cfg.DeployConfig.L1CancunTimeOffset = &genesisActivation
cfg.DeployConfig.L2GenesisDeltaTimeOffset = &genesisActivation
cfg.DeployConfig.L2GenesisEcotoneTimeOffset = &genesisActivation
sys, err := cfg.Start(t)
require.Nil(t, err, "Error starting up system")
defer sys.Close()
log := testlog.Logger(t, log.LvlInfo)
log.Info("genesis", "l2", sys.RollupConfig.Genesis.L2, "l1", sys.RollupConfig.Genesis.L1, "l2_time", sys.RollupConfig.Genesis.L2Time)
l1Client := sys.Clients["l1"]
l2Seq := sys.Clients["sequencer"]
l2Verif := sys.Clients["verifier"]
// Transactor Account
ethPrivKey := cfg.Secrets.Alice
// Send Transaction & wait for success
fromAddr := cfg.Secrets.Addresses().Alice
log.Info("alice", "addr", fromAddr)
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
startBalance, err := l2Verif.BalanceAt(ctx, fromAddr, nil)
require.NoError(t, err)
// Send deposit transaction
opts, err := bind.NewKeyedTransactorWithChainID(ethPrivKey, cfg.L1ChainIDBig())
require.NoError(t, err)
mintAmount := big.NewInt(1_000_000_000_000)
opts.Value = mintAmount
SendDepositTx(t, cfg, l1Client, l2Verif, opts, func(l2Opts *DepositTxOpts) {})
// Confirm balance
ctx, cancel = context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
endBalance, err := l2Verif.BalanceAt(ctx, fromAddr, nil)
require.NoError(t, err)
diff := new(big.Int).Sub(endBalance, startBalance)
require.Equal(t, mintAmount, diff, "Did not get expected balance change")
// Submit TX to L2 sequencer node
receipt := SendL2Tx(t, cfg, l2Seq, ethPrivKey, func(opts *TxOpts) {
opts.Value = big.NewInt(1_000_000_000)
opts.Nonce = 1 // Already have deposit
opts.ToAddr = &common.Address{0xff, 0xff}
opts.VerifyOnClients(l2Verif)
})
// Verify blocks match after batch submission on verifiers and sequencers
verifBlock, err := l2Verif.BlockByNumber(context.Background(), receipt.BlockNumber)
require.NoError(t, err)
require.Equal(t, verifBlock.Hash(), receipt.BlockHash, "must be same block")
seqBlock, err := l2Seq.BlockByNumber(context.Background(), receipt.BlockNumber)
require.NoError(t, err)
require.Equal(t, seqBlock.Hash(), receipt.BlockHash, "must be same block")
require.Equal(t, verifBlock.NumberU64(), seqBlock.NumberU64(), "Verifier and sequencer blocks not the same after including a batch tx")
require.Equal(t, verifBlock.ParentHash(), seqBlock.ParentHash(), "Verifier and sequencer blocks parent hashes not the same after including a batch tx")
require.Equal(t, verifBlock.Hash(), seqBlock.Hash(), "Verifier and sequencer blocks not the same after including a batch tx")
rollupRPCClient, err := rpc.DialContext(context.Background(), sys.RollupNodes["sequencer"].HTTPEndpoint())
require.NoError(t, err)
rollupClient := sources.NewRollupClient(client.NewBaseRPCClient(rollupRPCClient))
// basic check that sync status works
seqStatus, err := rollupClient.SyncStatus(context.Background())
require.NoError(t, err)
require.LessOrEqual(t, seqBlock.NumberU64(), seqStatus.UnsafeL2.Number)
// basic check that version endpoint works
seqVersion, err := rollupClient.Version(context.Background())
require.NoError(t, err)
require.NotEqual(t, "", seqVersion)
// quick check that the batch submitter works
require.Eventually(t, func() bool {
// wait for chain to be marked as "safe" (i.e. confirm batch-submission works)
stat, err := rollupClient.SyncStatus(context.Background())
require.NoError(t, err)
return stat.SafeL2.Number > 0
}, time.Second*20, time.Second, "expected L2 to be batch-submitted and labeled as safe")
// check that the L2 tx is still canonical
seqBlock, err = l2Seq.BlockByNumber(context.Background(), receipt.BlockNumber)
require.NoError(t, err)
require.Equal(t, seqBlock.Hash(), receipt.BlockHash, "receipt block must match canonical block at tx inclusion height")
}
......@@ -16,7 +16,6 @@ import (
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-service/dial"
ds "github.com/ipfs/go-datastore"
dsSync "github.com/ipfs/go-datastore/sync"
ic "github.com/libp2p/go-libp2p/core/crypto"
......@@ -59,6 +58,7 @@ import (
l2os "github.com/ethereum-optimism/optimism/op-proposer/proposer"
"github.com/ethereum-optimism/optimism/op-service/cliapp"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum-optimism/optimism/op-service/dial"
"github.com/ethereum-optimism/optimism/op-service/eth"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/sources"
......@@ -156,6 +156,7 @@ func DefaultSystemConfig(t *testing.T) SystemConfig {
NonFinalizedProposals: false,
ExternalL2Shim: config.ExternalL2Shim,
BatcherTargetL1TxSizeBytes: 100_000,
DataAvailabilityType: batcherFlags.CalldataType,
}
}
......@@ -208,6 +209,9 @@ type SystemConfig struct {
// Explicitly disable batcher, for tests that rely on unsafe L2 payloads
DisableBatcher bool
// Configure data-availability type that is used by the batcher.
DataAvailabilityType batcherFlags.DataAvailabilityType
// Target L1 tx size for the batcher transactions
BatcherTargetL1TxSizeBytes uint64
......@@ -781,7 +785,7 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
},
Stopped: sys.Cfg.DisableBatcher, // Batch submitter may be enabled later
BatchType: batchType,
DataAvailabilityType: batcherFlags.CalldataType,
DataAvailabilityType: sys.Cfg.DataAvailabilityType,
}
// Batch Submitter
batcher, err := bss.BatcherServiceFromCLIConfig(context.Background(), "0.0.1", batcherCLIConfig, sys.Cfg.Loggers["batcher"])
......
......@@ -18,7 +18,7 @@ const (
var _ HTTP = (*BasicHTTPClient)(nil)
type HTTP interface {
Get(ctx context.Context, path string, headers http.Header) (*http.Response, error)
Get(ctx context.Context, path string, query url.Values, headers http.Header) (*http.Response, error)
}
type BasicHTTPClient struct {
......@@ -37,12 +37,17 @@ func NewBasicHTTPClient(endpoint string, log log.Logger) *BasicHTTPClient {
}
}
func (cl *BasicHTTPClient) Get(ctx context.Context, p string, headers http.Header) (*http.Response, error) {
u, err := url.JoinPath(cl.endpoint, p)
func (cl *BasicHTTPClient) Get(ctx context.Context, p string, query url.Values, headers http.Header) (*http.Response, error) {
target, err := url.Parse(cl.endpoint)
if err != nil {
return nil, fmt.Errorf("%w: failed to join path", err)
return nil, fmt.Errorf("failed to parse endpoint URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
// If we include the raw query in the path-join, it gets url-encoded,
// and fails to parse as query, and ends up in the url.URL.Path part on the server side.
// We want to avoid that, and insert the query manually. Real footgun in the url package.
target = target.JoinPath(p)
target.RawQuery = query.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, target.String(), nil)
if err != nil {
return nil, fmt.Errorf("%w: failed to construct request", err)
}
......
package eth
type BlobSidecar struct {
BlockRoot Bytes32 `json:"block_root"`
Slot Uint64String `json:"slot"`
Blob Blob `json:"blob"`
Index Uint64String `json:"index"`
......@@ -9,8 +8,41 @@ type BlobSidecar struct {
KZGProof Bytes48 `json:"kzg_proof"`
}
type APIBlobSidecar struct {
Index Uint64String `json:"index"`
Blob Blob `json:"blob"`
KZGCommitment Bytes48 `json:"kzg_commitment"`
KZGProof Bytes48 `json:"kzg_proof"`
SignedBlockHeader SignedBeaconBlockHeader `json:"signed_block_header"`
// The inclusion-proof of the blob-sidecar into the beacon-block is ignored,
// since we verify blobs by their versioned hashes against the execution-layer block instead.
}
func (sc *APIBlobSidecar) BlobSidecar() *BlobSidecar {
return &BlobSidecar{
Slot: sc.SignedBlockHeader.Message.Slot,
Blob: sc.Blob,
Index: sc.Index,
KZGCommitment: sc.KZGCommitment,
KZGProof: sc.KZGProof,
}
}
type SignedBeaconBlockHeader struct {
Message BeaconBlockHeader `json:"message"`
// signature is ignored, since we verify blobs against EL versioned-hashes
}
type BeaconBlockHeader struct {
Slot Uint64String `json:"slot"`
ProposerIndex Uint64String `json:"proposer_index"`
ParentRoot Bytes32 `json:"parent_root"`
StateRoot Bytes32 `json:"state_root"`
BodyRoot Bytes32 `json:"body_root"`
}
type APIGetBlobSidecarsResponse struct {
Data []*BlobSidecar `json:"data"`
Data []*APIBlobSidecar `json:"data"`
}
type ReducedGenesisData struct {
......
package eth_test
import (
"encoding/json"
"os"
"path/filepath"
"reflect"
"testing"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/stretchr/testify/require"
)
type dataJson struct {
Data map[string]any `json:"data"`
}
// TestAPIGenesisResponse tests that json unmarshaling a json response from a
// eth/v1/beacon/genesis beacon node call into a APIGenesisResponse object
// fills all existing fields with the expected values, thereby confirming that
// APIGenesisResponse is compatible with the current beacon node API.
// This also confirms that the [sources.L1BeaconClient] correctly parses
// responses from a real beacon node.
func TestAPIGenesisResponse(t *testing.T) {
require := require.New(t)
var resp eth.APIGenesisResponse
require.Equal(1, reflect.TypeOf(resp.Data).NumField(), "APIGenesisResponse changed, adjust test")
path := filepath.Join("testdata", "eth_v1_beacon_genesis_goerli.json")
jsonStr, err := os.ReadFile(path)
require.NoError(err)
require.NoError(json.Unmarshal(jsonStr, &resp))
require.NotZero(resp.Data.GenesisTime)
jsonMap := &dataJson{Data: make(map[string]any)}
require.NoError(json.Unmarshal(jsonStr, jsonMap))
genesisTime, err := resp.Data.GenesisTime.MarshalText()
require.NoError(err)
require.Equal(jsonMap.Data["genesis_time"].(string), string(genesisTime))
}
// TestAPIConfigResponse tests that json unmarshaling a json response from a
// eth/v1/config/spec beacon node call into a APIConfigResponse object
// fills all existing fields with the expected values, thereby confirming that
// APIGenesisResponse is compatible with the current beacon node API.
// This also confirms that the [sources.L1BeaconClient] correctly parses
// responses from a real beacon node.
func TestAPIConfigResponse(t *testing.T) {
require := require.New(t)
var resp eth.APIConfigResponse
require.Equal(1, reflect.TypeOf(resp.Data).NumField(), "APIConfigResponse changed, adjust test")
path := filepath.Join("testdata", "eth_v1_config_spec_goerli.json")
jsonStr, err := os.ReadFile(path)
require.NoError(err)
require.NoError(json.Unmarshal(jsonStr, &resp))
require.NotZero(resp.Data.SecondsPerSlot)
jsonMap := &dataJson{Data: make(map[string]any)}
require.NoError(json.Unmarshal(jsonStr, jsonMap))
secPerSlot, err := resp.Data.SecondsPerSlot.MarshalText()
require.NoError(err)
require.Equal(jsonMap.Data["SECONDS_PER_SLOT"].(string), string(secPerSlot))
}
// TestAPIGetBlobSidecarsResponse tests that json unmarshaling a json response from a
// eth/v1/beacon/blob_sidecars/<X> beacon node call into a APIGetBlobSidecarsResponse object
// fills all existing fields with the expected values, thereby confirming that
// APIGenesisResponse is compatible with the current beacon node API.
// This also confirms that the [sources.L1BeaconClient] correctly parses
// responses from a real beacon node.
func TestAPIGetBlobSidecarsResponse(t *testing.T) {
require := require.New(t)
path := filepath.Join("testdata", "eth_v1_beacon_blob_sidecars_7422094_goerli.json")
jsonStr, err := os.ReadFile(path)
require.NoError(err)
var resp eth.APIGetBlobSidecarsResponse
require.NoError(json.Unmarshal(jsonStr, &resp))
require.NotEmpty(resp.Data)
require.Equal(5, reflect.TypeOf(*resp.Data[0]).NumField(), "APIBlobSidecar changed, adjust test")
require.Equal(1, reflect.TypeOf(resp.Data[0].SignedBlockHeader).NumField(), "SignedBeaconBlockHeader changed, adjust test")
require.Equal(5, reflect.TypeOf(resp.Data[0].SignedBlockHeader.Message).NumField(), "BeaconBlockHeader changed, adjust test")
require.NotZero(resp.Data[0].Blob)
require.NotZero(resp.Data[1].Index)
require.NotZero(resp.Data[0].KZGCommitment)
require.NotZero(resp.Data[0].KZGProof)
require.NotZero(resp.Data[0].SignedBlockHeader.Message.Slot)
require.NotZero(resp.Data[0].SignedBlockHeader.Message.ParentRoot)
require.NotZero(resp.Data[0].SignedBlockHeader.Message.BodyRoot)
require.NotZero(resp.Data[0].SignedBlockHeader.Message.ProposerIndex)
require.NotZero(resp.Data[0].SignedBlockHeader.Message.StateRoot)
}
{"data":{"genesis_time":"1606824023","genesis_validators_root":"0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95","genesis_fork_version":"0x00000000"}}
\ No newline at end of file
{"data":{"CONFIG_NAME":"mainnet","PRESET_BASE":"mainnet","TERMINAL_TOTAL_DIFFICULTY":"58750000000000000000000","TERMINAL_BLOCK_HASH":"0x0000000000000000000000000000000000000000000000000000000000000000","TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH":"18446744073709551615","SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY":"128","MIN_GENESIS_ACTIVE_VALIDATOR_COUNT":"16384","MIN_GENESIS_TIME":"1606824000","GENESIS_FORK_VERSION":"0x00000000","GENESIS_DELAY":"604800","ALTAIR_FORK_VERSION":"0x01000000","ALTAIR_FORK_EPOCH":"74240","BELLATRIX_FORK_VERSION":"0x02000000","BELLATRIX_FORK_EPOCH":"144896","CAPELLA_FORK_VERSION":"0x03000000","CAPELLA_FORK_EPOCH":"194048","SECONDS_PER_SLOT":"12","SECONDS_PER_ETH1_BLOCK":"14","MIN_VALIDATOR_WITHDRAWABILITY_DELAY":"256","SHARD_COMMITTEE_PERIOD":"256","ETH1_FOLLOW_DISTANCE":"2048","SUBNETS_PER_NODE":"2","INACTIVITY_SCORE_BIAS":"4","INACTIVITY_SCORE_RECOVERY_RATE":"16","EJECTION_BALANCE":"16000000000","MIN_PER_EPOCH_CHURN_LIMIT":"4","CHURN_LIMIT_QUOTIENT":"65536","PROPOSER_SCORE_BOOST":"40","DEPOSIT_CHAIN_ID":"1","DEPOSIT_NETWORK_ID":"1","DEPOSIT_CONTRACT_ADDRESS":"0x00000000219ab540356cbb839cbe05303d7705fa","MAX_COMMITTEES_PER_SLOT":"64","TARGET_COMMITTEE_SIZE":"128","MAX_VALIDATORS_PER_COMMITTEE":"2048","SHUFFLE_ROUND_COUNT":"90","HYSTERESIS_QUOTIENT":"4","HYSTERESIS_DOWNWARD_MULTIPLIER":"1","HYSTERESIS_UPWARD_MULTIPLIER":"5","SAFE_SLOTS_TO_UPDATE_JUSTIFIED":"8","MIN_DEPOSIT_AMOUNT":"1000000000","MAX_EFFECTIVE_BALANCE":"32000000000","EFFECTIVE_BALANCE_INCREMENT":"1000000000","MIN_ATTESTATION_INCLUSION_DELAY":"1","SLOTS_PER_EPOCH":"32","MIN_SEED_LOOKAHEAD":"1","MAX_SEED_LOOKAHEAD":"4","EPOCHS_PER_ETH1_VOTING_PERIOD":"64","SLOTS_PER_HISTORICAL_ROOT":"8192","MIN_EPOCHS_TO_INACTIVITY_PENALTY":"4","EPOCHS_PER_HISTORICAL_VECTOR":"65536","EPOCHS_PER_SLASHINGS_VECTOR":"8192","HISTORICAL_ROOTS_LIMIT":"16777216","VALIDATOR_REGISTRY_LIMIT":"1099511627776","BASE_REWARD_FACTOR":"64","WHISTLEBLOWER_REWARD_QUOTIENT":"512","PROPOSER_REWARD_QUOTIENT":"8","INACTIVITY_PENALTY_QUOTIENT":"67108864","MIN_SLASHING_PENALTY_QUOTIENT":"128","PROPORTIONAL_SLASHING_MULTIPLIER":"1","MAX_PROPOSER_SLASHINGS":"16","MAX_ATTESTER_SLASHINGS":"2","MAX_ATTESTATIONS":"128","MAX_DEPOSITS":"16","MAX_VOLUNTARY_EXITS":"16","INACTIVITY_PENALTY_QUOTIENT_ALTAIR":"50331648","MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR":"64","PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR":"2","SYNC_COMMITTEE_SIZE":"512","EPOCHS_PER_SYNC_COMMITTEE_PERIOD":"256","MIN_SYNC_COMMITTEE_PARTICIPANTS":"1","INACTIVITY_PENALTY_QUOTIENT_BELLATRIX":"16777216","MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX":"32","PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX":"3","MAX_BYTES_PER_TRANSACTION":"1073741824","MAX_TRANSACTIONS_PER_PAYLOAD":"1048576","BYTES_PER_LOGS_BLOOM":"256","MAX_EXTRA_DATA_BYTES":"32","MAX_BLS_TO_EXECUTION_CHANGES":"16","MAX_WITHDRAWALS_PER_PAYLOAD":"16","MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP":"16384","DOMAIN_DEPOSIT":"0x03000000","DOMAIN_SELECTION_PROOF":"0x05000000","SYNC_COMMITTEE_SUBNET_COUNT":"4","DOMAIN_AGGREGATE_AND_PROOF":"0x06000000","TARGET_AGGREGATORS_PER_COMMITTEE":"16","BLS_WITHDRAWAL_PREFIX":"0x00","DOMAIN_SYNC_COMMITTEE":"0x07000000","DOMAIN_BEACON_PROPOSER":"0x00000000","DOMAIN_BEACON_ATTESTER":"0x01000000","DOMAIN_VOLUNTARY_EXIT":"0x04000000","DOMAIN_RANDAO":"0x02000000","DOMAIN_APPLICATION_MASK":"0x00000001","DOMAIN_CONTRIBUTION_AND_PROOF":"0x09000000","TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE":"16","DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF":"0x08000000"}}
\ No newline at end of file
......@@ -26,9 +26,7 @@ const (
InvalidPayloadAttributes ErrorCode = -38003 // Payload attributes are invalid / inconsistent.
)
var (
ErrBedrockScalarPaddingNotEmpty = errors.New("version 0 scalar value has non-empty padding")
)
var ErrBedrockScalarPaddingNotEmpty = errors.New("version 0 scalar value has non-empty padding")
// InputError distinguishes an user-input error from regular rpc errors,
// to help the (Engine) API user divert from accidental input mistakes.
......@@ -76,6 +74,30 @@ func (b Bytes32) TerminalString() string {
return fmt.Sprintf("%x..%x", b[:3], b[29:])
}
type Bytes96 [96]byte
func (b *Bytes96) UnmarshalJSON(text []byte) error {
return hexutil.UnmarshalFixedJSON(reflect.TypeOf(b), text, b[:])
}
func (b *Bytes96) UnmarshalText(text []byte) error {
return hexutil.UnmarshalFixedText("Bytes96", text, b[:])
}
func (b Bytes96) MarshalText() ([]byte, error) {
return hexutil.Bytes(b[:]).MarshalText()
}
func (b Bytes96) String() string {
return hexutil.Encode(b[:])
}
// TerminalString implements log.TerminalStringer, formatting a string for console
// output during logging.
func (b Bytes96) TerminalString() string {
return fmt.Sprintf("%x..%x", b[:3], b[93:])
}
type Bytes256 [256]byte
func (b *Bytes256) UnmarshalJSON(text []byte) error {
......
......@@ -7,9 +7,9 @@ import (
"io"
"net/http"
"net/url"
"path"
"slices"
"strconv"
"strings"
"sync"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
......@@ -36,10 +36,10 @@ func NewL1BeaconClient(cl client.HTTP) *L1BeaconClient {
return &L1BeaconClient{cl: cl}
}
func (cl *L1BeaconClient) apiReq(ctx context.Context, dest any, method string) error {
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, method, headers)
resp, err := cl.cl.Get(ctx, reqPath, reqQuery, headers)
if err != nil {
return fmt.Errorf("%w: http Get failed", err)
}
......@@ -69,12 +69,12 @@ func (cl *L1BeaconClient) GetTimeToSlotFn(ctx context.Context) (TimeToSlotFn, er
}
var genesisResp eth.APIGenesisResponse
if err := cl.apiReq(ctx, &genesisResp, genesisMethod); err != nil {
if err := cl.apiReq(ctx, &genesisResp, genesisMethod, nil); err != nil {
return nil, err
}
var configResp eth.APIConfigResponse
if err := cl.apiReq(ctx, &configResp, specMethod); err != nil {
if err := cl.apiReq(ctx, &configResp, specMethod, nil); err != nil {
return nil, err
}
......@@ -108,25 +108,25 @@ func (cl *L1BeaconClient) GetBlobSidecars(ctx context.Context, ref eth.L1BlockRe
return nil, fmt.Errorf("%w: error in converting ref.Time to slot", err)
}
builder := strings.Builder{}
builder.WriteString(sidecarsMethodPrefix)
builder.WriteString(strconv.FormatUint(slot, 10))
builder.WriteRune('?')
v := url.Values{}
reqPath := path.Join(sidecarsMethodPrefix, strconv.FormatUint(slot, 10))
reqQuery := url.Values{}
for i := range hashes {
v.Add("indices", strconv.FormatUint(hashes[i].Index, 10))
reqQuery.Add("indices", strconv.FormatUint(hashes[i].Index, 10))
}
builder.WriteString(v.Encode())
var resp eth.APIGetBlobSidecarsResponse
if err := cl.apiReq(ctx, &resp, builder.String()); err != nil {
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)
}
if len(hashes) != len(resp.Data) {
return nil, fmt.Errorf("expected %v sidecars but got %v", len(hashes), len(resp.Data))
}
return resp.Data, nil
bscs := make([]*eth.BlobSidecar, 0, len(hashes))
for _, apisc := range resp.Data {
bscs = append(bscs, apisc.BlobSidecar())
}
return bscs, nil
}
// GetBlobs fetches blobs that were confirmed in the specified L1 block with the given indexed
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment