Compare commits

...

7 Commits

Author SHA1 Message Date
Bastin
289dfaa774 add attestation list types 2025-06-23 13:10:01 +02:00
Bastin
de36bfdcd5 add comment 2025-06-20 18:45:34 +02:00
Bastin
723317d4d2 Update beacon-chain/rpc/eth/beacon/handlers_pool.go
Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2025-06-20 16:59:14 +02:00
Bastin
3224a16f81 save 1 line 2025-06-19 10:48:08 +02:00
Bastin
6e05af363f lowercase errors 2025-06-19 10:47:07 +02:00
Bastin
79ff24d5cf changelog 2025-06-18 16:31:48 +02:00
Bastin
8aaceb10ff ssz support for submitAttestationsV2 2025-06-18 16:27:58 +02:00
6 changed files with 612 additions and 79 deletions

View File

@@ -337,3 +337,245 @@ func init() {
dataColumnSizer := &eth.DataColumnSidecarsByRangeRequest{}
dataColumnIdSize = dataColumnSizer.SizeSSZ()
}
// ====================================
// Attestations section
// ====================================
var _ ssz.Marshaler = (*Attestations)(nil)
var _ ssz.Unmarshaler = (*Attestations)(nil)
// Attestations is a list of pre electra Attestation objects.
type Attestations []*eth.Attestation
// UnmarshalSSZ implements ssz.Unmarshaler.
func (a *Attestations) UnmarshalSSZ(buf []byte) error {
// Exit early if the buffer is too small.
if len(buf) < bytesPerLengthOffset {
return nil
}
// Get the size of the offsets.
offsetEnd := binary.LittleEndian.Uint32(buf[:bytesPerLengthOffset])
if offsetEnd%bytesPerLengthOffset != 0 {
return errors.Errorf("expected offsets size to be a multiple of %d but got %d", bytesPerLengthOffset, offsetEnd)
}
count := offsetEnd / bytesPerLengthOffset
if count < 1 {
return nil
}
// TODO is this needed?
maxSize := params.BeaconConfig().MaxAttestations
if uint64(count) > maxSize {
return errors.Errorf("attestations list exceeds max size: %d > %d", count, maxSize)
}
if offsetEnd > uint32(len(buf)) {
return errors.Errorf("offsets value %d larger than buffer %d", offsetEnd, len(buf))
}
valueStart := offsetEnd
// Decode the attestations.
*a = make([]*eth.Attestation, count)
var start uint32
end := uint32(len(buf))
for i := count; i > 0; i-- {
offsetEnd -= bytesPerLengthOffset
start = binary.LittleEndian.Uint32(buf[offsetEnd : offsetEnd+bytesPerLengthOffset])
if start > end {
return errors.Errorf("expected offset[%d] %d to be less than %d", i-1, start, end)
}
if start < valueStart {
return errors.Errorf("offset[%d] %d indexes before value section %d", i-1, start, valueStart)
}
// Decode the identifier.
ident := &eth.Attestation{}
if err := ident.UnmarshalSSZ(buf[start:end]); err != nil {
return err
}
(*a)[i-1] = ident
end = start
}
return nil
}
func (a *Attestations) MarshalSSZ() ([]byte, error) {
var err error
count := len(*a)
// TODO is this needed?
maxSize := params.BeaconConfig().MaxAttestations
if uint64(count) > maxSize {
return nil, errors.Errorf("attestations list exceeds max size: %d > %d", count, maxSize)
}
if count == 0 {
return []byte{}, nil
}
sizes := make([]uint32, count)
valTotal := uint32(0)
for i, elem := range *a {
if elem == nil {
return nil, errors.New("nil item in Attestations list")
}
sizes[i] = uint32(elem.SizeSSZ())
valTotal += sizes[i]
}
offSize := uint32(4 * count)
out := make([]byte, offSize, offSize+valTotal)
for i := range sizes {
binary.LittleEndian.PutUint32(out[i*4:i*4+4], offSize)
offSize += sizes[i]
}
for _, elem := range *a {
out, err = elem.MarshalSSZTo(out)
if err != nil {
return nil, err
}
}
return out, nil
}
// MarshalSSZTo implements ssz.Marshaler.
func (a *Attestations) MarshalSSZTo(dst []byte) ([]byte, error) {
obj, err := a.MarshalSSZ()
if err != nil {
return nil, err
}
return append(dst, obj...), nil
}
func (a *Attestations) SizeSSZ() int {
size := 0
for i := 0; i < len(*a); i++ {
size += 4
size += (*a)[i].SizeSSZ()
}
return size
}
// ====================================
// SingleAttestations section
// ====================================
var _ ssz.Marshaler = (*SingleAttestations)(nil)
var _ ssz.Unmarshaler = (*SingleAttestations)(nil)
// SingleAttestations is a list of post electra SingleAttestation objects.
type SingleAttestations []*eth.SingleAttestation
// UnmarshalSSZ implements ssz.Unmarshaler.
func (a *SingleAttestations) UnmarshalSSZ(buf []byte) error {
// Exit early if the buffer is too small.
if len(buf) < bytesPerLengthOffset {
return nil
}
// Get the size of the offsets.
offsetEnd := binary.LittleEndian.Uint32(buf[:bytesPerLengthOffset])
if offsetEnd%bytesPerLengthOffset != 0 {
return errors.Errorf("expected offsets size to be a multiple of %d but got %d", bytesPerLengthOffset, offsetEnd)
}
count := offsetEnd / bytesPerLengthOffset
if count < 1 {
return nil
}
// TODO is this needed?
maxSize := params.BeaconConfig().MaxAttestations
if uint64(count) > maxSize {
return errors.Errorf("single attestations list exceeds max size: %d > %d", count, maxSize)
}
if offsetEnd > uint32(len(buf)) {
return errors.Errorf("offsets value %d larger than buffer %d", offsetEnd, len(buf))
}
valueStart := offsetEnd
// Decode the attestations.
*a = make([]*eth.SingleAttestation, count)
var start uint32
end := uint32(len(buf))
for i := count; i > 0; i-- {
offsetEnd -= bytesPerLengthOffset
start = binary.LittleEndian.Uint32(buf[offsetEnd : offsetEnd+bytesPerLengthOffset])
if start > end {
return errors.Errorf("expected offset[%d] %d to be less than %d", i-1, start, end)
}
if start < valueStart {
return errors.Errorf("offset[%d] %d indexes before value section %d", i-1, start, valueStart)
}
// Decode the identifier.
ident := &eth.SingleAttestation{}
if err := ident.UnmarshalSSZ(buf[start:end]); err != nil {
return err
}
(*a)[i-1] = ident
end = start
}
return nil
}
func (a *SingleAttestations) MarshalSSZ() ([]byte, error) {
var err error
count := len(*a)
// TODO is this needed?
maxSize := params.BeaconConfig().MaxAttestationsElectra
if uint64(count) > maxSize {
return nil, errors.Errorf("single attestations list exceeds max size: %d > %d", count, maxSize)
}
if count == 0 {
return []byte{}, nil
}
sizes := make([]uint32, count)
valTotal := uint32(0)
for i, elem := range *a {
if elem == nil {
return nil, errors.New("nil item in SingleAttestations list")
}
sizes[i] = uint32(elem.SizeSSZ())
valTotal += sizes[i]
}
offSize := uint32(4 * count)
out := make([]byte, offSize, offSize+valTotal)
for i := range sizes {
binary.LittleEndian.PutUint32(out[i*4:i*4+4], offSize)
offSize += sizes[i]
}
for _, elem := range *a {
out, err = elem.MarshalSSZTo(out)
if err != nil {
return nil, err
}
}
return out, nil
}
// MarshalSSZTo implements ssz.Marshaler.
func (a *SingleAttestations) MarshalSSZTo(dst []byte) ([]byte, error) {
obj, err := a.MarshalSSZ()
if err != nil {
return nil, err
}
return append(dst, obj...), nil
}
func (a *SingleAttestations) SizeSSZ() int {
size := 0
for i := 0; i < len(*a); i++ {
size += 4
size += (*a)[i].SizeSSZ()
}
return size
}

View File

@@ -694,7 +694,7 @@ func (s *Service) beaconEndpoints(
template: "/eth/v2/beacon/pool/attestations",
name: namespace + ".SubmitAttestationsV2",
middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
middleware.ContentTypeHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}),
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
handler: server.SubmitAttestationsV2,

View File

@@ -34,6 +34,7 @@ go_library(
"//beacon-chain/operations/slashings:go_default_library",
"//beacon-chain/operations/voluntaryexits:go_default_library",
"//beacon-chain/p2p:go_default_library",
"//beacon-chain/p2p/types:go_default_library",
"//beacon-chain/rpc/core:go_default_library",
"//beacon-chain/rpc/eth/helpers:go_default_library",
"//beacon-chain/rpc/eth/shared:go_default_library",
@@ -95,6 +96,7 @@ go_test(
"//beacon-chain/operations/synccommittee:go_default_library",
"//beacon-chain/operations/voluntaryexits/mock:go_default_library",
"//beacon-chain/p2p/testing:go_default_library",
"//beacon-chain/p2p/types:go_default_library",
"//beacon-chain/rpc/core:go_default_library",
"//beacon-chain/rpc/eth/shared/testing:go_default_library",
"//beacon-chain/rpc/lookup:go_default_library",

View File

@@ -18,6 +18,7 @@ import (
"github.com/OffchainLabs/prysm/v6/beacon-chain/core/feed/operation"
corehelpers "github.com/OffchainLabs/prysm/v6/beacon-chain/core/helpers"
"github.com/OffchainLabs/prysm/v6/beacon-chain/core/transition"
"github.com/OffchainLabs/prysm/v6/beacon-chain/p2p/types"
"github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/core"
"github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared"
"github.com/OffchainLabs/prysm/v6/config/features"
@@ -183,18 +184,7 @@ func (s *Server) SubmitAttestations(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "beacon.SubmitAttestations")
defer span.End()
var req structs.SubmitAttestationsRequest
err := json.NewDecoder(r.Body).Decode(&req.Data)
switch {
case errors.Is(err, io.EOF):
httputil.HandleError(w, "No data submitted", http.StatusBadRequest)
return
case err != nil:
httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
return
}
attFailures, failedBroadcasts, err := s.handleAttestations(ctx, req.Data)
attFailures, failedBroadcasts, err := s.handleAttestations(ctx, r)
if err != nil {
httputil.HandleError(w, err.Error(), http.StatusBadRequest)
return
@@ -236,24 +226,13 @@ func (s *Server) SubmitAttestationsV2(w http.ResponseWriter, r *http.Request) {
return
}
var req structs.SubmitAttestationsRequest
err = json.NewDecoder(r.Body).Decode(&req.Data)
switch {
case errors.Is(err, io.EOF):
httputil.HandleError(w, "No data submitted", http.StatusBadRequest)
return
case err != nil:
httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
return
}
var attFailures []*server.IndexedVerificationFailure
var failedBroadcasts []string
if v >= version.Electra {
attFailures, failedBroadcasts, err = s.handleAttestationsElectra(ctx, req.Data)
attFailures, failedBroadcasts, err = s.handleAttestationsElectra(ctx, r)
} else {
attFailures, failedBroadcasts, err = s.handleAttestations(ctx, req.Data)
attFailures, failedBroadcasts, err = s.handleAttestations(ctx, r)
}
if err != nil {
httputil.HandleError(w, fmt.Sprintf("Failed to handle attestations: %v", err), http.StatusBadRequest)
@@ -281,40 +260,76 @@ func (s *Server) SubmitAttestationsV2(w http.ResponseWriter, r *http.Request) {
func (s *Server) handleAttestationsElectra(
ctx context.Context,
data json.RawMessage,
r *http.Request,
) (attFailures []*server.IndexedVerificationFailure, failedBroadcasts []string, err error) {
var sourceAttestations []*structs.SingleAttestation
var validAttestations []*eth.SingleAttestation
currentEpoch := slots.ToEpoch(s.TimeFetcher.CurrentSlot())
if currentEpoch < params.BeaconConfig().ElectraForkEpoch {
return nil, nil, errors.Errorf("electra attestations have not been enabled, current epoch %d enabled epoch %d", currentEpoch, params.BeaconConfig().ElectraForkEpoch)
}
if err = json.Unmarshal(data, &sourceAttestations); err != nil {
return nil, nil, errors.Wrap(err, "failed to unmarshal attestation")
}
if len(sourceAttestations) == 0 {
return nil, nil, errors.New("no data submitted")
}
var validAttestations []*eth.SingleAttestation
for i, sourceAtt := range sourceAttestations {
att, err := sourceAtt.ToConsensus()
if httputil.IsRequestSsz(r) {
sszLen := (&eth.SingleAttestation{}).SizeSSZ()
body, err := readRequestBody(r)
if err != nil {
attFailures = append(attFailures, &server.IndexedVerificationFailure{
Index: i,
Message: "Could not convert request attestation to consensus attestation: " + err.Error(),
})
continue
return nil, nil, errors.Wrap(err, "could not read request body")
}
if _, err = bls.SignatureFromBytes(att.Signature); err != nil {
attFailures = append(attFailures, &server.IndexedVerificationFailure{
Index: i,
Message: "Incorrect attestation signature: " + err.Error(),
})
continue
if len(body) < sszLen {
return nil, nil, errors.New("no data submitted")
}
atts := make(types.SingleAttestations, 0)
if err := atts.UnmarshalSSZ(body); err != nil {
return nil, nil, errors.Wrap(err, "could not unmarshal ssz single attestations")
}
for i, att := range atts {
if _, err = bls.SignatureFromBytes(att.Signature); err != nil {
attFailures = append(attFailures, &server.IndexedVerificationFailure{
Index: i,
Message: "Incorrect attestation signature: " + err.Error(),
})
} else {
validAttestations = append(validAttestations, att)
}
}
} else {
var sourceAttestations []*structs.SingleAttestation
var req structs.SubmitAttestationsRequest
err = json.NewDecoder(r.Body).Decode(&req.Data)
switch {
case errors.Is(err, io.EOF):
return nil, nil, errors.New("no data submitted")
case err != nil:
return nil, nil, errors.Wrap(err, "could not decode request body")
}
if err = json.Unmarshal(req.Data, &sourceAttestations); err != nil {
return nil, nil, errors.Wrap(err, "failed to unmarshal attestation")
}
if len(sourceAttestations) == 0 {
return nil, nil, errors.New("no data submitted")
}
for i, sourceAtt := range sourceAttestations {
att, err := sourceAtt.ToConsensus()
if err != nil {
attFailures = append(attFailures, &server.IndexedVerificationFailure{
Index: i,
Message: "Could not convert request attestation to consensus attestation: " + err.Error(),
})
continue
}
if _, err = bls.SignatureFromBytes(att.Signature); err != nil {
attFailures = append(attFailures, &server.IndexedVerificationFailure{
Index: i,
Message: "Incorrect attestation signature: " + err.Error(),
})
continue
}
validAttestations = append(validAttestations, att)
}
validAttestations = append(validAttestations, att)
}
for i, singleAtt := range validAttestations {
@@ -362,39 +377,73 @@ func (s *Server) handleAttestationsElectra(
return attFailures, failedBroadcasts, nil
}
func (s *Server) handleAttestations(ctx context.Context, data json.RawMessage) (attFailures []*server.IndexedVerificationFailure, failedBroadcasts []string, err error) {
var sourceAttestations []*structs.Attestation
func (s *Server) handleAttestations(ctx context.Context, r *http.Request) (attFailures []*server.IndexedVerificationFailure, failedBroadcasts []string, err error) {
var validAttestations []*eth.Attestation
if slots.ToEpoch(s.TimeFetcher.CurrentSlot()) >= params.BeaconConfig().ElectraForkEpoch {
return nil, nil, errors.New("old attestation format, only electra attestations should be sent")
}
if err = json.Unmarshal(data, &sourceAttestations); err != nil {
return nil, nil, errors.Wrap(err, "failed to unmarshal attestation")
}
if len(sourceAttestations) == 0 {
return nil, nil, errors.New("no data submitted")
}
var validAttestations []*eth.Attestation
for i, sourceAtt := range sourceAttestations {
att, err := sourceAtt.ToConsensus()
if httputil.IsRequestSsz(r) {
body, err := readRequestBody(r)
if err != nil {
attFailures = append(attFailures, &server.IndexedVerificationFailure{
Index: i,
Message: "Could not convert request attestation to consensus attestation: " + err.Error(),
})
continue
return nil, nil, errors.Wrap(err, "could not read request body")
}
if _, err = bls.SignatureFromBytes(att.Signature); err != nil {
attFailures = append(attFailures, &server.IndexedVerificationFailure{
Index: i,
Message: "Incorrect attestation signature: " + err.Error(),
})
continue
if len(body) == 0 {
return nil, nil, errors.New("no data submitted")
}
atts := make(types.Attestations, 0)
if err := atts.UnmarshalSSZ(body); err != nil {
return nil, nil, errors.Wrap(err, "could not unmarshal ssz attestations")
}
for i, att := range atts {
if _, err = bls.SignatureFromBytes(att.Signature); err != nil {
attFailures = append(attFailures, &server.IndexedVerificationFailure{
Index: i,
Message: "Incorrect attestation signature: " + err.Error(),
})
} else {
validAttestations = append(validAttestations, att)
}
}
} else {
var sourceAttestations []*structs.Attestation
var req structs.SubmitAttestationsRequest
err = json.NewDecoder(r.Body).Decode(&req.Data)
switch {
case errors.Is(err, io.EOF):
return nil, nil, errors.New("no data submitted")
case err != nil:
return nil, nil, errors.Wrap(err, "could not decode request body")
}
if err = json.Unmarshal(req.Data, &sourceAttestations); err != nil {
return nil, nil, errors.Wrap(err, "failed to unmarshal attestation")
}
if len(sourceAttestations) == 0 {
return nil, nil, errors.New("no data submitted")
}
for i, sourceAtt := range sourceAttestations {
att, err := sourceAtt.ToConsensus()
if err != nil {
attFailures = append(attFailures, &server.IndexedVerificationFailure{
Index: i,
Message: "Could not convert request attestation to consensus attestation: " + err.Error(),
})
continue
}
if _, err = bls.SignatureFromBytes(att.Signature); err != nil {
attFailures = append(attFailures, &server.IndexedVerificationFailure{
Index: i,
Message: "Incorrect attestation signature: " + err.Error(),
})
continue
}
validAttestations = append(validAttestations, att)
}
validAttestations = append(validAttestations, att)
}
for i, att := range validAttestations {

View File

@@ -24,6 +24,7 @@ import (
"github.com/OffchainLabs/prysm/v6/beacon-chain/operations/synccommittee"
"github.com/OffchainLabs/prysm/v6/beacon-chain/operations/voluntaryexits/mock"
p2pMock "github.com/OffchainLabs/prysm/v6/beacon-chain/p2p/testing"
"github.com/OffchainLabs/prysm/v6/beacon-chain/p2p/types"
"github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/core"
state_native "github.com/OffchainLabs/prysm/v6/beacon-chain/state/state-native"
"github.com/OffchainLabs/prysm/v6/config/params"
@@ -611,7 +612,7 @@ func TestSubmitAttestations(t *testing.T) {
e := &httputil.DefaultJsonError{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
assert.Equal(t, http.StatusBadRequest, e.Code)
assert.Equal(t, true, strings.Contains(e.Message, "No data submitted"))
assert.Equal(t, true, strings.Contains(e.Message, "no data submitted"))
})
t.Run("empty", func(t *testing.T) {
var body bytes.Buffer
@@ -676,6 +677,49 @@ func TestSubmitAttestations(t *testing.T) {
assert.Equal(t, primitives.Epoch(0), broadcaster.BroadcastAttestations[0].GetData().Target.Epoch)
assert.Equal(t, 1, s.AttestationsPool.UnaggregatedAttestationCount())
})
t.Run("single SSZ", func(t *testing.T) {
broadcaster := &p2pMock.MockBroadcaster{}
s.Broadcaster = broadcaster
s.AttestationsPool = attestations.NewPool()
var jsonSingleAtt []structs.Attestation
err = json.NewDecoder(strings.NewReader(singleAtt)).Decode(&jsonSingleAtt)
require.NoError(t, err)
singleAttConsensus, err := jsonSingleAtt[0].ToConsensus()
require.NoError(t, err)
var attList types.Attestations
attList = append(attList, singleAttConsensus)
sszPayload, err := attList.MarshalSSZ()
require.NoError(t, err)
var body bytes.Buffer
_, err = body.Write(sszPayload)
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com", &body)
request.Header.Set(api.VersionHeader, version.String(version.Phase0))
request.Header.Set("Content-Type", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.SubmitAttestationsV2(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, true, broadcaster.BroadcastCalled.Load())
assert.Equal(t, 1, broadcaster.NumAttestations())
assert.Equal(t, "0x03", hexutil.Encode(broadcaster.BroadcastAttestations[0].GetAggregationBits()))
assert.Equal(t, "0x8146f4397bfd8fd057ebbcd6a67327bdc7ed5fb650533edcb6377b650dea0b6da64c14ecd60846d5c0a0cd43893d6972092500f82c9d8a955e2b58c5ed3cbe885d84008ace6bd86ba9e23652f58e2ec207cec494c916063257abf285b9b15b15", hexutil.Encode(broadcaster.BroadcastAttestations[0].GetSignature()))
assert.Equal(t, primitives.Slot(0), broadcaster.BroadcastAttestations[0].GetData().Slot)
assert.Equal(t, primitives.CommitteeIndex(0), broadcaster.BroadcastAttestations[0].GetData().CommitteeIndex)
assert.Equal(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", hexutil.Encode(broadcaster.BroadcastAttestations[0].GetData().BeaconBlockRoot))
assert.Equal(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", hexutil.Encode(broadcaster.BroadcastAttestations[0].GetData().Source.Root))
assert.Equal(t, primitives.Epoch(0), broadcaster.BroadcastAttestations[0].GetData().Source.Epoch)
assert.Equal(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", hexutil.Encode(broadcaster.BroadcastAttestations[0].GetData().Target.Root))
assert.Equal(t, primitives.Epoch(0), broadcaster.BroadcastAttestations[0].GetData().Target.Epoch)
assert.Equal(t, 1, s.AttestationsPool.UnaggregatedAttestationCount())
})
t.Run("multiple", func(t *testing.T) {
broadcaster := &p2pMock.MockBroadcaster{}
s.Broadcaster = broadcaster
@@ -695,6 +739,40 @@ func TestSubmitAttestations(t *testing.T) {
assert.Equal(t, 2, broadcaster.NumAttestations())
assert.Equal(t, 2, s.AttestationsPool.UnaggregatedAttestationCount())
})
t.Run("multiple SSZ", func(t *testing.T) {
broadcaster := &p2pMock.MockBroadcaster{}
s.Broadcaster = broadcaster
s.AttestationsPool = attestations.NewPool()
var jsonAttList []structs.Attestation
err = json.NewDecoder(strings.NewReader(multipleAtts)).Decode(&jsonAttList)
require.NoError(t, err)
var attList types.Attestations
for _, att := range jsonAttList {
attConsensus, err := att.ToConsensus()
require.NoError(t, err)
attList = append(attList, attConsensus)
}
sszPayload, err := attList.MarshalSSZ()
require.NoError(t, err)
var body bytes.Buffer
_, err = body.Write(sszPayload)
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com", &body)
request.Header.Set(api.VersionHeader, version.String(version.Phase0))
request.Header.Set("Content-Type", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.SubmitAttestationsV2(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, true, broadcaster.BroadcastCalled.Load())
assert.Equal(t, 2, broadcaster.NumAttestations())
assert.Equal(t, 2, s.AttestationsPool.UnaggregatedAttestationCount())
})
t.Run("phase0 att post electra", func(t *testing.T) {
params.SetupTestConfigCleanup(t)
config := params.BeaconConfig()
@@ -743,7 +821,21 @@ func TestSubmitAttestations(t *testing.T) {
e := &httputil.DefaultJsonError{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
assert.Equal(t, http.StatusBadRequest, e.Code)
assert.Equal(t, true, strings.Contains(e.Message, "No data submitted"))
assert.Equal(t, true, strings.Contains(e.Message, "no data submitted"))
})
t.Run("no body SSZ", func(t *testing.T) {
request := httptest.NewRequest(http.MethodPost, "http://example.com", nil)
request.Header.Set(api.VersionHeader, version.String(version.Phase0))
request.Header.Set("Content-Type", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.SubmitAttestationsV2(writer, request)
assert.Equal(t, http.StatusBadRequest, writer.Code)
e := &httputil.DefaultJsonError{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
assert.Equal(t, http.StatusBadRequest, e.Code)
assert.Equal(t, true, strings.Contains(e.Message, "no data submitted"))
})
t.Run("empty", func(t *testing.T) {
var body bytes.Buffer
@@ -770,6 +862,34 @@ func TestSubmitAttestations(t *testing.T) {
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.SubmitAttestationsV2(writer, request)
assert.Equal(t, http.StatusBadRequest, writer.Code)
e := &server.IndexedVerificationFailureError{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
assert.Equal(t, http.StatusBadRequest, e.Code)
require.Equal(t, 1, len(e.Failures))
assert.Equal(t, true, strings.Contains(e.Failures[0].Message, "Incorrect attestation signature"))
})
t.Run("invalid SSZ", func(t *testing.T) {
var jsonSingleAtt []structs.Attestation
err = json.NewDecoder(strings.NewReader(invalidAtt)).Decode(&jsonSingleAtt)
require.NoError(t, err)
singleAttConsensus, err := jsonSingleAtt[0].ToConsensus()
require.NoError(t, err)
var attList types.Attestations
attList = append(attList, singleAttConsensus)
sszPayload, err := attList.MarshalSSZ()
require.NoError(t, err)
var body bytes.Buffer
_, err = body.Write(sszPayload)
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com", &body)
request.Header.Set(api.VersionHeader, version.String(version.Phase0))
request.Header.Set("Content-Type", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.SubmitAttestationsV2(writer, request)
assert.Equal(t, http.StatusBadRequest, writer.Code)
e := &server.IndexedVerificationFailureError{}
@@ -814,6 +934,48 @@ func TestSubmitAttestations(t *testing.T) {
assert.Equal(t, primitives.Epoch(0), broadcaster.BroadcastAttestations[0].GetData().Target.Epoch)
assert.Equal(t, 1, s.AttestationsPool.UnaggregatedAttestationCount())
})
t.Run("single SSZ", func(t *testing.T) {
broadcaster := &p2pMock.MockBroadcaster{}
s.Broadcaster = broadcaster
s.AttestationsPool = attestations.NewPool()
var jsonSingleAttElectra []structs.SingleAttestation
err = json.NewDecoder(strings.NewReader(singleAttElectra)).Decode(&jsonSingleAttElectra)
require.NoError(t, err)
singleAttElectraConsensus, err := jsonSingleAttElectra[0].ToConsensus()
require.NoError(t, err)
var attList types.SingleAttestations
attList = append(attList, singleAttElectraConsensus)
sszPayload, err := attList.MarshalSSZ()
require.NoError(t, err)
var body bytes.Buffer
_, err = body.Write(sszPayload)
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com", &body)
request.Header.Set(api.VersionHeader, version.String(version.Electra))
request.Header.Set("Content-Type", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.SubmitAttestationsV2(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, true, broadcaster.BroadcastCalled.Load())
assert.Equal(t, 1, broadcaster.NumAttestations())
assert.Equal(t, primitives.ValidatorIndex(1), broadcaster.BroadcastAttestations[0].GetAttestingIndex())
assert.Equal(t, "0x8146f4397bfd8fd057ebbcd6a67327bdc7ed5fb650533edcb6377b650dea0b6da64c14ecd60846d5c0a0cd43893d6972092500f82c9d8a955e2b58c5ed3cbe885d84008ace6bd86ba9e23652f58e2ec207cec494c916063257abf285b9b15b15", hexutil.Encode(broadcaster.BroadcastAttestations[0].GetSignature()))
assert.Equal(t, primitives.Slot(0), broadcaster.BroadcastAttestations[0].GetData().Slot)
assert.Equal(t, primitives.CommitteeIndex(0), broadcaster.BroadcastAttestations[0].GetData().CommitteeIndex)
assert.Equal(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", hexutil.Encode(broadcaster.BroadcastAttestations[0].GetData().BeaconBlockRoot))
assert.Equal(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", hexutil.Encode(broadcaster.BroadcastAttestations[0].GetData().Source.Root))
assert.Equal(t, primitives.Epoch(0), broadcaster.BroadcastAttestations[0].GetData().Source.Epoch)
assert.Equal(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", hexutil.Encode(broadcaster.BroadcastAttestations[0].GetData().Target.Root))
assert.Equal(t, primitives.Epoch(0), broadcaster.BroadcastAttestations[0].GetData().Target.Epoch)
assert.Equal(t, 1, s.AttestationsPool.UnaggregatedAttestationCount())
})
t.Run("multiple", func(t *testing.T) {
broadcaster := &p2pMock.MockBroadcaster{}
s.Broadcaster = broadcaster
@@ -833,6 +995,39 @@ func TestSubmitAttestations(t *testing.T) {
assert.Equal(t, 2, broadcaster.NumAttestations())
assert.Equal(t, 2, s.AttestationsPool.UnaggregatedAttestationCount())
})
t.Run("multiple SSZ", func(t *testing.T) {
broadcaster := &p2pMock.MockBroadcaster{}
s.Broadcaster = broadcaster
s.AttestationsPool = attestations.NewPool()
var jsonAttsList []structs.SingleAttestation
err = json.NewDecoder(strings.NewReader(multipleAttsElectra)).Decode(&jsonAttsList)
require.NoError(t, err)
var attList types.SingleAttestations
for _, att := range jsonAttsList {
singleAttElectraConsensus, err := att.ToConsensus()
require.NoError(t, err)
attList = append(attList, singleAttElectraConsensus)
}
sszPayload, err := attList.MarshalSSZ()
require.NoError(t, err)
var body bytes.Buffer
_, err = body.Write(sszPayload)
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com", &body)
request.Header.Set(api.VersionHeader, version.String(version.Electra))
request.Header.Set("Content-Type", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.SubmitAttestationsV2(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, true, broadcaster.BroadcastCalled.Load())
assert.Equal(t, 2, broadcaster.NumAttestations())
assert.Equal(t, 2, s.AttestationsPool.UnaggregatedAttestationCount())
})
t.Run("no body", func(t *testing.T) {
request := httptest.NewRequest(http.MethodPost, "http://example.com", nil)
request.Header.Set(api.VersionHeader, version.String(version.Electra))
@@ -844,7 +1039,21 @@ func TestSubmitAttestations(t *testing.T) {
e := &httputil.DefaultJsonError{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
assert.Equal(t, http.StatusBadRequest, e.Code)
assert.Equal(t, true, strings.Contains(e.Message, "No data submitted"))
assert.Equal(t, true, strings.Contains(e.Message, "no data submitted"))
})
t.Run("no body SSZ", func(t *testing.T) {
request := httptest.NewRequest(http.MethodPost, "http://example.com", nil)
request.Header.Set(api.VersionHeader, version.String(version.Electra))
request.Header.Set("Content-Type", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.SubmitAttestationsV2(writer, request)
assert.Equal(t, http.StatusBadRequest, writer.Code)
e := &httputil.DefaultJsonError{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
assert.Equal(t, http.StatusBadRequest, e.Code)
assert.Equal(t, true, strings.Contains(e.Message, "no data submitted"))
})
t.Run("empty", func(t *testing.T) {
var body bytes.Buffer
@@ -871,6 +1080,34 @@ func TestSubmitAttestations(t *testing.T) {
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.SubmitAttestationsV2(writer, request)
assert.Equal(t, http.StatusBadRequest, writer.Code)
e := &server.IndexedVerificationFailureError{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
assert.Equal(t, http.StatusBadRequest, e.Code)
require.Equal(t, 1, len(e.Failures))
assert.Equal(t, true, strings.Contains(e.Failures[0].Message, "Incorrect attestation signature"))
})
t.Run("invalid SSZ", func(t *testing.T) {
var jsonInvalidAttElectra []structs.SingleAttestation
err = json.NewDecoder(strings.NewReader(invalidAttElectra)).Decode(&jsonInvalidAttElectra)
require.NoError(t, err)
invalidAttElectraConsensus, err := jsonInvalidAttElectra[0].ToConsensus()
require.NoError(t, err)
var attList types.SingleAttestations
attList = append(attList, invalidAttElectraConsensus)
sszPayload, err := attList.MarshalSSZ()
require.NoError(t, err)
var body bytes.Buffer
_, err = body.Write(sszPayload)
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com", &body)
request.Header.Set(api.VersionHeader, version.String(version.Electra))
request.Header.Set("Content-Type", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.SubmitAttestationsV2(writer, request)
assert.Equal(t, http.StatusBadRequest, writer.Code)
e := &server.IndexedVerificationFailureError{}

View File

@@ -0,0 +1,3 @@
### Added
- Add SSZ support for `submitPoolAttestationsV2` beacon API.