diff --git a/common/types.go b/common/types.go index 0cb139be..7b6ca2f1 100644 --- a/common/types.go +++ b/common/types.go @@ -9,6 +9,7 @@ import ( "strings" builderApiV1 "github.com/attestantio/go-builder-client/api/v1" + "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/attestantio/go-eth2-client/spec/capella" "github.com/attestantio/go-eth2-client/spec/deneb" @@ -18,8 +19,11 @@ import ( ) var ( - ErrUnknownNetwork = errors.New("unknown network") - ErrEmptyPayload = errors.New("empty payload") + ErrUnknownNetwork = errors.New("unknown network") + ErrEmptyPayload = errors.New("empty payload") + ErrEmptyPayloadHeader = errors.New("empty payload header") + ErrEmptyPayloadMessage = errors.New("empty payload message") + ErrVersionNotSupported = errors.New("version is not supported") EthNetworkHolesky = "holesky" EthNetworkSepolia = "sepolia" @@ -416,269 +420,305 @@ type BlockSubmissionInfo struct { ExcessBlobGas uint64 } -/* -SubmitBlockRequestV2Optimistic is the v2 request from the builder to submit -a block. The message must be SSZ encoded. The first three fields are at most -944 bytes, which fit into a single 1500 MTU ethernet packet. The -`UnmarshalSSZHeaderOnly` function just parses the first three fields, -which is sufficient data to set the bid of the builder. The `Transactions` -and `Withdrawals` fields are required to construct the full SignedBeaconBlock -and are parsed asynchronously. - -Header only layout: -[000-236) = Message (236 bytes) -[236-240) = offset1 ( 4 bytes) -[240-336) = Signature ( 96 bytes) -[336-340) = offset2 ( 4 bytes) -[340-344) = offset3 ( 4 bytes) -[344-944) = EPH (600 bytes) -*/ -type SubmitBlockRequestV2Optimistic struct { - Message *builderApiV1.BidTrace - ExecutionPayloadHeader *capella.ExecutionPayloadHeader - Signature phase0.BLSSignature `ssz-size:"96"` - Transactions []bellatrix.Transaction `ssz-max:"1048576,1073741824" ssz-size:"?,?"` - Withdrawals []*capella.Withdrawal `ssz-max:"16"` +type HeaderSubmissionInfo struct { + BidTrace *builderApiV1.BidTrace + Signature phase0.BLSSignature + Timestamp uint64 + PrevRandao phase0.Hash32 + TransactionsRoot phase0.Root + WithdrawalsRoot phase0.Root } -// MarshalSSZ ssz marshals the SubmitBlockRequestV2Optimistic object -func (s *SubmitBlockRequestV2Optimistic) MarshalSSZ() ([]byte, error) { - return ssz.MarshalSSZ(s) +// VersionedSubmitHeaderOptimistic is a versioned signed header to construct the builder bid. +type VersionedSubmitHeaderOptimistic struct { + Version spec.DataVersion + Deneb *DenebSubmitHeaderOptimistic } -// UnmarshalSSZ ssz unmarshals the SubmitBlockRequestV2Optimistic object -func (s *SubmitBlockRequestV2Optimistic) UnmarshalSSZ(buf []byte) error { - var err error - size := uint64(len(buf)) - if size < 344 { - return ssz.ErrSize +func (h *VersionedSubmitHeaderOptimistic) MarshalSSZ() ([]byte, error) { + switch h.Version { //nolint:exhaustive + case spec.DataVersionDeneb: + return h.Deneb.MarshalSSZ() + default: + return nil, fmt.Errorf("%w: %s", ErrVersionNotSupported, h.Version) } +} - tail := buf - var o1, o3, o4 uint64 - - // Field (0) 'Message' - if s.Message == nil { - s.Message = new(builderApiV1.BidTrace) - } - if err = s.Message.UnmarshalSSZ(buf[0:236]); err != nil { - return err +func (h *VersionedSubmitHeaderOptimistic) UnmarshalSSZ(data []byte) error { + var err error + denebHeader := &DenebSubmitHeaderOptimistic{} + if err = denebHeader.UnmarshalSSZ(data); err == nil { + h.Version = spec.DataVersionDeneb + h.Deneb = denebHeader + return nil } + return err +} - // Offset (1) 'ExecutionPayloadHeader' - if o1 = ssz.ReadOffset(buf[236:240]); o1 > size { - return ssz.ErrOffset +func (h *VersionedSubmitHeaderOptimistic) MarshalJSON() ([]byte, error) { + switch h.Version { //nolint:exhaustive + case spec.DataVersionDeneb: + return json.Marshal(h.Deneb) + default: + return nil, fmt.Errorf("%w: %s", ErrVersionNotSupported, h.Version) } +} - if o1 < 344 { - return ssz.ErrInvalidVariableOffset +func (h *VersionedSubmitHeaderOptimistic) UnmarshalJSON(data []byte) error { + var err error + denebHeader := &DenebSubmitHeaderOptimistic{} + if err = json.Unmarshal(data, denebHeader); err == nil { + h.Version = spec.DataVersionDeneb + h.Deneb = denebHeader + return nil } + return err +} - // Field (2) 'Signature' - copy(s.Signature[:], buf[240:336]) +func (h *VersionedSubmitHeaderOptimistic) BidTrace() (*builderApiV1.BidTrace, error) { + switch h.Version { //nolint:exhaustive + case spec.DataVersionDeneb: + if h.Deneb == nil { + return nil, ErrEmptyPayload + } + if h.Deneb.Message == nil { + return nil, ErrEmptyPayloadMessage + } + return h.Deneb.Message, nil + default: + return nil, fmt.Errorf("%w: %s", ErrVersionNotSupported, h.Version) + } +} - // Offset (3) 'Transactions' - if o3 = ssz.ReadOffset(buf[336:340]); o3 > size || o1 > o3 { - return ssz.ErrOffset +func (h *VersionedSubmitHeaderOptimistic) ExecutionPayloadBlockHash() (phase0.Hash32, error) { + switch h.Version { //nolint:exhaustive + case spec.DataVersionDeneb: + if h.Deneb == nil { + return phase0.Hash32{}, ErrEmptyPayload + } + if h.Deneb.ExecutionPayloadHeader == nil { + return phase0.Hash32{}, ErrEmptyPayloadHeader + } + return h.Deneb.ExecutionPayloadHeader.BlockHash, nil + default: + return phase0.Hash32{}, fmt.Errorf("%w: %s", ErrVersionNotSupported, h.Version) } +} - // Offset (4) 'Withdrawals' - if o4 = ssz.ReadOffset(buf[340:344]); o4 > size || o3 > o4 { - return ssz.ErrOffset +func (h *VersionedSubmitHeaderOptimistic) Signature() (phase0.BLSSignature, error) { + switch h.Version { //nolint:exhaustive + case spec.DataVersionDeneb: + if h.Deneb == nil { + return phase0.BLSSignature{}, ErrEmptyPayload + } + return h.Deneb.Signature, nil + default: + return phase0.BLSSignature{}, fmt.Errorf("%w: %s", ErrVersionNotSupported, h.Version) } +} - // Field (1) 'ExecutionPayloadHeader' - { - buf = tail[o1:o3] - if s.ExecutionPayloadHeader == nil { - s.ExecutionPayloadHeader = new(capella.ExecutionPayloadHeader) +func (h *VersionedSubmitHeaderOptimistic) Timestamp() (uint64, error) { + switch h.Version { //nolint:exhaustive + case spec.DataVersionDeneb: + if h.Deneb == nil { + return 0, ErrEmptyPayload } - if err = s.ExecutionPayloadHeader.UnmarshalSSZ(buf); err != nil { - return err + if h.Deneb.ExecutionPayloadHeader == nil { + return 0, ErrEmptyPayloadHeader } + return h.Deneb.ExecutionPayloadHeader.Timestamp, nil + default: + return 0, fmt.Errorf("%w: %s", ErrVersionNotSupported, h.Version) } +} - // Field (3) 'Transactions' - { - buf = tail[o3:o4] - num, err := ssz.DecodeDynamicLength(buf, 1073741824) - if err != nil { - return err +func (h *VersionedSubmitHeaderOptimistic) PrevRandao() (phase0.Hash32, error) { + switch h.Version { //nolint:exhaustive + case spec.DataVersionDeneb: + if h.Deneb == nil { + return phase0.Hash32{}, ErrEmptyPayload } - s.Transactions = make([]bellatrix.Transaction, num) - err = ssz.UnmarshalDynamic(buf, num, func(indx int, buf []byte) (err error) { - if len(buf) > 1073741824 { - return ssz.ErrBytesLength - } - if cap(s.Transactions[indx]) == 0 { - s.Transactions[indx] = bellatrix.Transaction(make([]byte, 0, len(buf))) - } - s.Transactions[indx] = append(s.Transactions[indx], buf...) - return nil - }) - if err != nil { - return err + if h.Deneb.ExecutionPayloadHeader == nil { + return phase0.Hash32{}, ErrEmptyPayloadHeader } + return h.Deneb.ExecutionPayloadHeader.PrevRandao, nil + default: + return phase0.Hash32{}, fmt.Errorf("%w: %s", ErrVersionNotSupported, h.Version) } +} - // Field (4) 'Withdrawals' - { - buf = tail[o4:] - num, err := ssz.DivideInt2(len(buf), 44, 16) - if err != nil { - return err +func (h *VersionedSubmitHeaderOptimistic) TransactionsRoot() (phase0.Root, error) { + switch h.Version { //nolint:exhaustive + case spec.DataVersionDeneb: + if h.Deneb == nil { + return phase0.Root{}, ErrEmptyPayload } - s.Withdrawals = make([]*capella.Withdrawal, num) - for ii := 0; ii < num; ii++ { - if s.Withdrawals[ii] == nil { - s.Withdrawals[ii] = new(capella.Withdrawal) - } - if err = s.Withdrawals[ii].UnmarshalSSZ(buf[ii*44 : (ii+1)*44]); err != nil { - return err - } + if h.Deneb.ExecutionPayloadHeader == nil { + return phase0.Root{}, ErrEmptyPayloadHeader } + return h.Deneb.ExecutionPayloadHeader.TransactionsRoot, nil + default: + return phase0.Root{}, fmt.Errorf("%w: %s", ErrVersionNotSupported, h.Version) } - return err } -// UnmarshalSSZHeaderOnly ssz unmarshals the first 3 fields of the SubmitBlockRequestV2Optimistic object -func (s *SubmitBlockRequestV2Optimistic) UnmarshalSSZHeaderOnly(buf []byte) error { - var err error - size := uint64(len(buf)) - if size < 344 { - return ssz.ErrSize +func (h *VersionedSubmitHeaderOptimistic) WithdrawalsRoot() (phase0.Root, error) { + switch h.Version { //nolint:exhaustive + case spec.DataVersionDeneb: + if h.Deneb == nil { + return phase0.Root{}, ErrEmptyPayload + } + if h.Deneb.ExecutionPayloadHeader == nil { + return phase0.Root{}, ErrEmptyPayloadHeader + } + return h.Deneb.ExecutionPayloadHeader.WithdrawalsRoot, nil + default: + return phase0.Root{}, fmt.Errorf("%w: %s", ErrVersionNotSupported, h.Version) } +} - tail := buf - var o1, o3 uint64 +/* +DenebSubmitHeaderOptimistic is request from the builder to submit a Deneb header. At minimum +without blobs, it is 956 bytes. With the current maximum of 6 blobs this adds another 288 +bytes for a total of 1244 bytes. + +Layout: +[000-236) = Message (236 bytes) +[236-240) = offset1 ( 4 bytes) ExecutionPayloadHeader +[240-244) = offset2 ( 4 bytes) BlobKZGCommitments +[244-340) = Signature ( 96 bytes) +[340-956) = EPH (616 bytes) +[956-?) = len(KZGCommitments) * 48 ( variable) +*/ +type DenebSubmitHeaderOptimistic struct { + Message *builderApiV1.BidTrace `json:"message"` + ExecutionPayloadHeader *deneb.ExecutionPayloadHeader `json:"header"` + BlobKZGCommitments []deneb.KZGCommitment `json:"blob_kzg_commitments" ssz-max:"4096" ssz-size:"?,48"` + Signature phase0.BLSSignature `json:"signature" ssz-size:"96"` +} + +// MarshalSSZ ssz marshals the DenebSubmitHeaderOptimistic object +func (d *DenebSubmitHeaderOptimistic) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(d) +} + +// MarshalSSZTo ssz marshals the DenebSubmitHeaderOptimistic object to a target array +func (d *DenebSubmitHeaderOptimistic) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(340) // Field (0) 'Message' - if s.Message == nil { - s.Message = new(builderApiV1.BidTrace) + if d.Message == nil { + d.Message = new(builderApiV1.BidTrace) } - if err = s.Message.UnmarshalSSZ(buf[0:236]); err != nil { - return err + if dst, err = d.Message.MarshalSSZTo(dst); err != nil { + return nil, err } // Offset (1) 'ExecutionPayloadHeader' - if o1 = ssz.ReadOffset(buf[236:240]); o1 > size { - return ssz.ErrOffset + dst = ssz.WriteOffset(dst, offset) + if d.ExecutionPayloadHeader == nil { + d.ExecutionPayloadHeader = new(deneb.ExecutionPayloadHeader) } + offset += d.ExecutionPayloadHeader.SizeSSZ() - if o1 < 344 { - return ssz.ErrInvalidVariableOffset - } + // Offset (2) 'BlobKZGCommitments' + dst = ssz.WriteOffset(dst, offset) - // Field (2) 'Signature' - copy(s.Signature[:], buf[240:336]) + // Field (3) 'Signature' + dst = append(dst, d.Signature[:]...) - // Offset (3) 'Transactions' - if o3 = ssz.ReadOffset(buf[336:340]); o3 > size || o1 > o3 { - return ssz.ErrOffset + // Field (1) 'ExecutionPayloadHeader' + if dst, err = d.ExecutionPayloadHeader.MarshalSSZTo(dst); err != nil { + return nil, err } - // Field (1) 'ExecutionPayloadHeader' - { - buf = tail[o1:o3] - if s.ExecutionPayloadHeader == nil { - s.ExecutionPayloadHeader = new(capella.ExecutionPayloadHeader) - } - if err = s.ExecutionPayloadHeader.UnmarshalSSZ(buf); err != nil { - return err - } + // Field (2) 'BlobKZGCommitments' + if size := len(d.BlobKZGCommitments); size > 4096 { + err = ssz.ErrListTooBigFn("DenebSubmitHeaderOptimistic.BlobKZGCommitments", size, 4096) + return nil, err } - return err + for ii := 0; ii < len(d.BlobKZGCommitments); ii++ { + dst = append(dst, d.BlobKZGCommitments[ii][:]...) + } + + return dst, err } -// MarshalSSZTo ssz marshals the SubmitBlockRequestV2Optimistic object to a target array -func (s *SubmitBlockRequestV2Optimistic) MarshalSSZTo(buf []byte) (dst []byte, err error) { - dst = buf - offset := int(344) +// UnmarshalSSZ ssz unmarshals the DenebSubmitHeaderOptimistic object +func (d *DenebSubmitHeaderOptimistic) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 340 { + return ssz.ErrSize + } + + tail := buf + var o1, o2 uint64 // Field (0) 'Message' - if s.Message == nil { - s.Message = new(builderApiV1.BidTrace) + if d.Message == nil { + d.Message = new(builderApiV1.BidTrace) } - if dst, err = s.Message.MarshalSSZTo(dst); err != nil { - return nil, err + if err = d.Message.UnmarshalSSZ(buf[0:236]); err != nil { + return err } // Offset (1) 'ExecutionPayloadHeader' - dst = ssz.WriteOffset(dst, offset) - if s.ExecutionPayloadHeader == nil { - s.ExecutionPayloadHeader = new(capella.ExecutionPayloadHeader) + if o1 = ssz.ReadOffset(buf[236:240]); o1 > size { + return ssz.ErrOffset } - offset += s.ExecutionPayloadHeader.SizeSSZ() - // Field (2) 'Signature' - dst = append(dst, s.Signature[:]...) + if o1 < 340 { + return ssz.ErrInvalidVariableOffset + } - // Offset (3) 'Transactions' - dst = ssz.WriteOffset(dst, offset) - for ii := 0; ii < len(s.Transactions); ii++ { - offset += 4 - offset += len(s.Transactions[ii]) + // Offset (2) 'BlobKZGCommitments' + if o2 = ssz.ReadOffset(buf[240:244]); o2 > size || o1 > o2 { + return ssz.ErrOffset } - // Offset (4) 'Withdrawals' - dst = ssz.WriteOffset(dst, offset) + // Field (3) 'Signature' + copy(d.Signature[:], buf[244:340]) // Field (1) 'ExecutionPayloadHeader' - if dst, err = s.ExecutionPayloadHeader.MarshalSSZTo(dst); err != nil { - return nil, err - } - - // Field (3) 'Transactions' - if size := len(s.Transactions); size > 1073741824 { - err = ssz.ErrListTooBigFn("SubmitBlockRequestV2Optimistic.Transactions", size, 1073741824) - return nil, err - } { - offset = 4 * len(s.Transactions) - for ii := 0; ii < len(s.Transactions); ii++ { - dst = ssz.WriteOffset(dst, offset) - offset += len(s.Transactions[ii]) + buf = tail[o1:o2] + if d.ExecutionPayloadHeader == nil { + d.ExecutionPayloadHeader = new(deneb.ExecutionPayloadHeader) } - } - for ii := 0; ii < len(s.Transactions); ii++ { - if size := len(s.Transactions[ii]); size > 1073741824 { - err = ssz.ErrBytesLengthFn("SubmitBlockRequestV2Optimistic.Transactions[ii]", size, 1073741824) - return nil, err + if err = d.ExecutionPayloadHeader.UnmarshalSSZ(buf); err != nil { + return err } - dst = append(dst, s.Transactions[ii]...) } - // Field (4) 'Withdrawals' - if size := len(s.Withdrawals); size > 16 { - err = ssz.ErrListTooBigFn("SubmitBlockRequestV2Optimistic.Withdrawals", size, 16) - return nil, err - } - for ii := 0; ii < len(s.Withdrawals); ii++ { - if dst, err = s.Withdrawals[ii].MarshalSSZTo(dst); err != nil { - return nil, err + // Field (2) 'BlobKZGCommitments' + { + buf = tail[o2:] + num, err := ssz.DivideInt2(len(buf), 48, 4096) + if err != nil { + return err + } + d.BlobKZGCommitments = make([]deneb.KZGCommitment, num) + for ii := 0; ii < num; ii++ { + copy(d.BlobKZGCommitments[ii][:], buf[ii*48:(ii+1)*48]) } } - return dst, nil + return err } -// SizeSSZ returns the ssz encoded size in bytes for the SubmitBlockRequestV2Optimistic object -func (s *SubmitBlockRequestV2Optimistic) SizeSSZ() (size int) { - size = 344 +// SizeSSZ returns the ssz encoded size in bytes for the DenebSubmitHeaderOptimistic object +func (d *DenebSubmitHeaderOptimistic) SizeSSZ() (size int) { + size = 340 // Field (1) 'ExecutionPayloadHeader' - if s.ExecutionPayloadHeader == nil { - s.ExecutionPayloadHeader = new(capella.ExecutionPayloadHeader) - } - size += s.ExecutionPayloadHeader.SizeSSZ() - - // Field (3) 'Transactions' - for ii := 0; ii < len(s.Transactions); ii++ { - size += 4 - size += len(s.Transactions[ii]) + if d.ExecutionPayloadHeader == nil { + d.ExecutionPayloadHeader = new(deneb.ExecutionPayloadHeader) } + size += d.ExecutionPayloadHeader.SizeSSZ() - // Field (4) 'Withdrawals' - size += len(s.Withdrawals) * 44 + // Field (2) 'BlobKZGCommitments' + size += len(d.BlobKZGCommitments) * 48 return } diff --git a/common/types_spec.go b/common/types_spec.go index 1e3ae491..d64f2048 100644 --- a/common/types_spec.go +++ b/common/types_spec.go @@ -102,6 +102,43 @@ func BuildGetPayloadResponse(payload *VersionedSubmitBlockRequest) (*builderApi. return nil, ErrEmptyPayload } +func BuildGetHeaderResponseOptimistic(payload *VersionedSubmitHeaderOptimistic, sk *bls.SecretKey, pubkey *phase0.BLSPubKey, domain phase0.Domain) (*builderSpec.VersionedSignedBuilderBid, error) { + if payload == nil { + return nil, ErrMissingRequest + } + + if sk == nil { + return nil, ErrMissingSecretKey + } + + switch payload.Version { + case spec.DataVersionDeneb: + builderBid := builderApiDeneb.BuilderBid{ + Header: payload.Deneb.ExecutionPayloadHeader, + BlobKZGCommitments: payload.Deneb.BlobKZGCommitments, + Value: payload.Deneb.Message.Value, + Pubkey: *pubkey, + } + + sig, err := ssz.SignMessage(&builderBid, domain, sk) + if err != nil { + return nil, err + } + + return &builderSpec.VersionedSignedBuilderBid{ + Version: spec.DataVersionDeneb, + Deneb: &builderApiDeneb.SignedBuilderBid{ + Message: &builderBid, + Signature: sig, + }, + }, nil + case spec.DataVersionUnknown, spec.DataVersionPhase0, spec.DataVersionAltair, spec.DataVersionBellatrix, spec.DataVersionCapella: + return nil, ErrInvalidVersion + default: + return nil, ErrEmptyPayload + } +} + func BuilderBlockRequestToSignedBuilderBid(payload *VersionedSubmitBlockRequest, header *builderApi.VersionedExecutionPayloadHeader, sk *bls.SecretKey, pubkey *phase0.BLSPubKey, domain phase0.Domain) (*builderSpec.VersionedSignedBuilderBid, error) { value, err := payload.Value() if err != nil { diff --git a/common/types_test.go b/common/types_test.go index a0def3ea..8b8b0a3c 100644 --- a/common/types_test.go +++ b/common/types_test.go @@ -5,15 +5,14 @@ import ( builderApiV1 "github.com/attestantio/go-builder-client/api/v1" "github.com/attestantio/go-eth2-client/spec" - "github.com/attestantio/go-eth2-client/spec/bellatrix" - "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/deneb" "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/flashbots/go-boost-utils/utils" "github.com/holiman/uint256" "github.com/stretchr/testify/require" ) -func makeTestSubmitBlockRequestV2Optimistic(t *testing.T) *SubmitBlockRequestV2Optimistic { +func makeTestVersionedSubmitHeaderOptimistic(t *testing.T) *VersionedSubmitHeaderOptimistic { t.Helper() testParentHash, err := utils.HexToHash("0xec51bd499a3fa0270f1446fbf05ff0b61157cfe4ec719bb4c3e834e339ee9c5c") require.NoError(t, err) @@ -35,51 +34,40 @@ func makeTestSubmitBlockRequestV2Optimistic(t *testing.T) *SubmitBlockRequestV2O err = testValue.SetFromDecimal("100") require.NoError(t, err) - return &SubmitBlockRequestV2Optimistic{ - Message: &builderApiV1.BidTrace{ - Slot: 31, - ParentHash: testParentHash, - BlockHash: testBlockHash, - BuilderPubkey: testBuilderPubkey, - ProposerPubkey: testProposerPubkey, - ProposerFeeRecipient: testAddress, - GasLimit: 30_000_000, - GasUsed: 15_000_000, - Value: testValue, - }, - ExecutionPayloadHeader: &capella.ExecutionPayloadHeader{ - ParentHash: testParentHash, - FeeRecipient: testAddress, - StateRoot: [32]byte(testBlockHash), - ReceiptsRoot: [32]byte(testBlockHash), - LogsBloom: [256]byte{0xaa, 0xbb, 0xcc}, - PrevRandao: [32]byte(testRandao), - BlockNumber: 30, - GasLimit: 30_000_000, - GasUsed: 15_000_000, - Timestamp: 168318215, - ExtraData: make([]byte, 32), - BaseFeePerGas: [32]byte{0xaa, 0xbb}, - BlockHash: testBlockHash, - TransactionsRoot: phase0.Root(testRoot), - WithdrawalsRoot: phase0.Root(testRoot), - }, - Signature: testSignature, - Transactions: []bellatrix.Transaction{ - []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09}, - []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19}, - []byte{0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29}, - []byte{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39}, - []byte{0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49}, - []byte{0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59}, - }, - Withdrawals: []*capella.Withdrawal{ - { - Index: capella.WithdrawalIndex(120), - ValidatorIndex: phase0.ValidatorIndex(121), - Address: testAddress, - Amount: phase0.Gwei(102412521125125), + return &VersionedSubmitHeaderOptimistic{ + Version: spec.DataVersionDeneb, + Deneb: &DenebSubmitHeaderOptimistic{ + Message: &builderApiV1.BidTrace{ + Slot: 31, + ParentHash: testParentHash, + BlockHash: testBlockHash, + BuilderPubkey: testBuilderPubkey, + ProposerPubkey: testProposerPubkey, + ProposerFeeRecipient: testAddress, + GasLimit: 30_000_000, + GasUsed: 15_000_000, + Value: testValue, + }, + ExecutionPayloadHeader: &deneb.ExecutionPayloadHeader{ + ParentHash: testParentHash, + FeeRecipient: testAddress, + StateRoot: [32]byte(testBlockHash), + ReceiptsRoot: [32]byte(testBlockHash), + LogsBloom: [256]byte{0xaa, 0xbb, 0xcc}, + PrevRandao: [32]byte(testRandao), + BlockNumber: 30, + GasLimit: 30_000_000, + GasUsed: 15_000_000, + Timestamp: 168318215, + ExtraData: make([]byte, 32), + BaseFeePerGas: uint256.NewInt(100), + BlockHash: testBlockHash, + TransactionsRoot: phase0.Root(testRoot), + WithdrawalsRoot: phase0.Root(testRoot), + BlobGasUsed: 15_000_000, + ExcessBlobGas: 30_000_000, }, + Signature: testSignature, }, } } @@ -90,45 +78,45 @@ func TestDataVersion(t *testing.T) { require.Equal(t, ForkVersionStringDeneb, spec.DataVersionDeneb.String()) } -func compareV2RequestEquality(t *testing.T, src, targ *SubmitBlockRequestV2Optimistic) { +func compareV2RequestEquality(t *testing.T, src, targ *VersionedSubmitHeaderOptimistic) { t.Helper() - require.Equal(t, src.Message.String(), targ.Message.String()) - require.Equal(t, src.ExecutionPayloadHeader.String(), targ.ExecutionPayloadHeader.String()) - require.Equal(t, src.Signature, targ.Signature) - for i := 0; i < len(src.Transactions); i++ { - require.Equal(t, src.Transactions[i], targ.Transactions[i]) - } - for i := 0; i < len(src.Withdrawals); i++ { - require.Equal(t, src.Withdrawals[i].String(), targ.Withdrawals[i].String()) - } + srcBidTrace, err := src.BidTrace() + require.NoError(t, err) + targBidTrace, err := targ.BidTrace() + require.NoError(t, err) + require.Equal(t, srcBidTrace, targBidTrace) + srcBlockHash, err := src.ExecutionPayloadBlockHash() + require.NoError(t, err) + targBlockHash, err := targ.ExecutionPayloadBlockHash() + require.NoError(t, err) + require.Equal(t, srcBlockHash, targBlockHash) + srcSignature, err := src.Signature() + require.NoError(t, err) + targSignature, err := targ.Signature() + require.NoError(t, err) + require.Equal(t, srcSignature, targSignature) } -func TestSubmitBlockRequestV2Optimistic(t *testing.T) { - obj := makeTestSubmitBlockRequestV2Optimistic(t) +func TestSubmitBlockHeaderV2Optimistic(t *testing.T) { + obj := makeTestVersionedSubmitHeaderOptimistic(t) // Encode the object. sszObj, err := obj.MarshalSSZ() require.NoError(t, err) - require.Len(t, sszObj, obj.SizeSSZ()) + require.Len(t, sszObj, 956) - // Unmarshal the full object. - unmarshal := new(SubmitBlockRequestV2Optimistic) + // Unmarshal the header. + unmarshal := new(VersionedSubmitHeaderOptimistic) err = unmarshal.UnmarshalSSZ(sszObj) require.NoError(t, err) compareV2RequestEquality(t, obj, unmarshal) - // Clear out non-header data. - obj.Transactions = []bellatrix.Transaction{} - obj.Withdrawals = []*capella.Withdrawal{} - - // Unmarshal just the header. - unmarshalHeader := new(SubmitBlockRequestV2Optimistic) - err = unmarshalHeader.UnmarshalSSZHeaderOnly(sszObj) + // Add KZG data. + obj.Deneb.BlobKZGCommitments = make([]deneb.KZGCommitment, 1) + sszObj, err = obj.MarshalSSZ() require.NoError(t, err) - compareV2RequestEquality(t, obj, unmarshalHeader) - - // Make sure size is correct (must have 32 bytes of ExtraData). - require.Equal(t, 944, unmarshalHeader.SizeSSZ()) + // Make sure size is correct (must have 48 extra bytes from KZG commitments). + require.Len(t, sszObj, 1004) } diff --git a/common/utils.go b/common/utils.go index 8f48c45c..b7b9fbf6 100644 --- a/common/utils.go +++ b/common/utils.go @@ -256,6 +256,41 @@ func GetBlockSubmissionInfo(submission *VersionedSubmitBlockRequest) (*BlockSubm }, nil } +func GetHeaderSubmissionInfo(submission *VersionedSubmitHeaderOptimistic) (*HeaderSubmissionInfo, error) { + bidTrace, err := submission.BidTrace() + if err != nil { + return nil, err + } + signature, err := submission.Signature() + if err != nil { + return nil, err + } + timestamp, err := submission.Timestamp() + if err != nil { + return nil, err + } + prevRandao, err := submission.PrevRandao() + if err != nil { + return nil, err + } + transactionsRoot, err := submission.TransactionsRoot() + if err != nil { + return nil, err + } + withdrawalsRoot, err := submission.WithdrawalsRoot() + if err != nil { + return nil, err + } + return &HeaderSubmissionInfo{ + BidTrace: bidTrace, + Signature: signature, + Timestamp: timestamp, + PrevRandao: prevRandao, + TransactionsRoot: transactionsRoot, + WithdrawalsRoot: withdrawalsRoot, + }, nil +} + func GetBlockSubmissionExecutionPayload(submission *VersionedSubmitBlockRequest) (*builderApi.VersionedSubmitBlindedBlockResponse, error) { switch submission.Version { case spec.DataVersionCapella: diff --git a/common/utils_test.go b/common/utils_test.go index 9565a1da..1a5b75dc 100644 --- a/common/utils_test.go +++ b/common/utils_test.go @@ -14,6 +14,7 @@ import ( "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/deneb" "github.com/ethereum/go-ethereum/common" boostTypes "github.com/flashbots/go-boost-utils/types" "github.com/stretchr/testify/require" @@ -114,7 +115,7 @@ func TestGetBlockSubmissionInfo(t *testing.T) { err string }{ { - name: "valid builderApiCapella", + name: "valid capella", payload: &VersionedSubmitBlockRequest{ VersionedSubmitBlockRequest: builderSpec.VersionedSubmitBlockRequest{ Version: spec.DataVersionCapella, @@ -192,3 +193,76 @@ func TestGetBlockSubmissionInfo(t *testing.T) { }) } } + +func TestGetHeaderSubmissionInfo(t *testing.T) { + cases := []struct { + name string + payload *VersionedSubmitHeaderOptimistic + expected *HeaderSubmissionInfo + err string + }{ + { + name: "valid deneb", + payload: &VersionedSubmitHeaderOptimistic{ + Version: spec.DataVersionDeneb, + Deneb: &DenebSubmitHeaderOptimistic{ + Message: &builderApiV1.BidTrace{}, + ExecutionPayloadHeader: &deneb.ExecutionPayloadHeader{}, + }, + }, + expected: &HeaderSubmissionInfo{ + BidTrace: &builderApiV1.BidTrace{}, + }, + }, + { + name: "unsupported version", + payload: &VersionedSubmitHeaderOptimistic{ + Version: spec.DataVersionCapella, + }, + expected: nil, + err: "version is not supported: capella", + }, + { + name: "missing data", + payload: &VersionedSubmitHeaderOptimistic{ + Version: spec.DataVersionDeneb, + }, + expected: nil, + err: "empty payload", + }, + { + name: "missing message", + payload: &VersionedSubmitHeaderOptimistic{ + Version: spec.DataVersionDeneb, + Deneb: &DenebSubmitHeaderOptimistic{ + ExecutionPayloadHeader: &deneb.ExecutionPayloadHeader{}, + }, + }, + expected: nil, + err: "empty payload message", + }, + { + name: "missing execution payload", + payload: &VersionedSubmitHeaderOptimistic{ + Version: spec.DataVersionDeneb, + Deneb: &DenebSubmitHeaderOptimistic{ + Message: &builderApiV1.BidTrace{}, + }, + }, + expected: nil, + err: "empty payload header", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + submission, err := GetHeaderSubmissionInfo(tc.payload) + require.Equal(t, tc.expected, submission) + if tc.err == "" { + require.NoError(t, err) + } else { + require.Equal(t, tc.err, err.Error()) + } + }) + } +}