Compare commits

...

25 Commits

Author SHA1 Message Date
Manu NALEPA
d855816878 Write changelog 2025-10-20 15:10:47 +02:00
Manu NALEPA
ab1e0bddc9 WarmCache: Return if extractFileMetadata fails. 2025-10-20 15:10:47 +02:00
Manu NALEPA
3e997cf115 extractFileMetadata: Use filepath.Separator instead of "/" for Windows. 2025-10-20 15:10:47 +02:00
terence
64ec665890 Fix sync committee subscription to use subnet indices instead of committee indices (#15885)
Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
2025-10-17 19:03:53 +00:00
kasey
fdb06ea461 clear genesis state file when --(force-)clear-db is specified (#15883)
Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com>
2025-10-17 14:03:15 +00:00
Manu NALEPA
0486631d73 Improve error message when the byte count read from disk when reading a data column sidecar is lower than expected. (Mostly, because the file is truncated.) (#15881)
* `VerifiedRODataColumnError`: Don't reuse Blob error.

* `VerifiedRODataColumnFromDisk`: Use a specific error when the count of read bytes is lower than expected.

* Add changelog.
2025-10-16 21:49:11 +00:00
Manu NALEPA
47764696ce randomPeer: Return if the context is cancelled when waiting for peers. (#15876)
* `randomPeer`: Return if the context is cancelled when waiting for peers.

* `randomPeer`: Refactor to reduce indentation.
2025-10-16 21:13:11 +00:00
Manu NALEPA
b2d350b988 Correctly advertise (in ENR and metadata) attestation subnets when using --subscribe-all-subnets. (#15880) 2025-10-16 21:12:00 +00:00
kasey
41e7607092 Decrease att batch deadline to 5ms for faster net prop (#15882)
Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com>
2025-10-16 17:30:59 +00:00
Jun Song
cd429dc253 SSZ-QL: Access n-th element in List/Vector. (#15767)
* Add basic parsing feature for accessing by index

* Add more tests for 2d byte vector

* Add List case for access indexing

* Handle 2D bytes List example

* Fix misleading cases for CalculateOffsetAndLength

* Use elementSizes[index] if it is the last path element

* Add variable_container_list field for mocking attester_slashings in BeaconBlockBody

* Remove redundant protobuf message

* Better documentation

* Changelog

* Fix `expectedSize` of `VariableTestContainer`: as we added `variable_container_list` here

* Apply reviews from Radek
2025-10-15 16:11:12 +00:00
phrwlk
5ced1125f2 fix: reject out-of-range attestation committee index (#15855)
* reject committee index >= committees_per_slot in unaggregated attestation validation

* Create phrwlk_fix-attestation-committee-index-bound.md

* add a unit test

* fix test

* fixing test

---------

Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
Co-authored-by: james-prysm <james@prysmaticlabs.com>
2025-10-15 16:02:08 +00:00
Potuz
f67ca6ae5e Fix epoch transition on head event (#15871)
h/t to the NuConstruct team for reporting this. The event feed
incorrectly sends epoch transition flag on head events when the first
slot of the epoch is missing (or reorgs across epoch transition).

Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
2025-10-15 15:13:49 +00:00
Manu NALEPA
9742333f68 WithDataColumnRetentionEpochs: Use dataColumnRetentionEpoch instead of blobColumnRetentionEpoch. (#15872) 2025-10-15 14:44:49 +00:00
Manu NALEPA
c811fadf33 VerifyDataColumnSidecar: Check if there is no too many commitments. (#15859)
* `VerifyDataColumnSidecar`: Check if there is no too many commitments.

* `TestVerifyDataColumnSidecar`: Refactor using test cases.

* Add changelog.
2025-10-15 12:18:04 +00:00
Manu NALEPA
55b9448d41 dataColumnSidecarsByRangeRPCHandler: Gracefully close the stream if no data to return. (#15866)
* `TestDataColumnSidecarsByRangeRPCHandler`: Remove commented code.

* Remove double import

* `dataColumnSidecarsByRangeRPCHandler`: Gracefully close the stream if no data to return.

* Tests: Change `require` to `assert` in goroutines in tests.

https://pkg.go.dev/github.com/stretchr/testify/require#hdr-Assertions

* Add changelog.
2025-10-15 12:16:05 +00:00
Manu NALEPA
10f8d8c26e Fix /eth/v1/beacon/blob_sidecars/ beacon API if the fulu fork epoch is set to the far future epoch. (#15867)
* Fix `/eth/v1/beacon/blob_sidecars/` beacon API is the fulu fork epoch is set to the far future epoch.

* Fix Terence's comment.

* adding a test

---------

Co-authored-by: james-prysm <james@prysmaticlabs.com>
2025-10-14 21:38:12 +00:00
Jun Song
4eab41ea4c SSZ-QL: use fastssz-generated SizeSSZ method & clarify Size method (#15864)
* Add SizeSSZ as a member of SSZObject

* Temporarily rename dereferencePointer function

* Fix analyzeType: use reflect.Value for analyzing

* Fix PopulateVariableLengthInfo: change function signature & reset pointer

* Remove Container arm for Size function as it'll be handled in the previous branch

* Remove OffsetBytes function in listInfo

* Refactor and document codes

* Remove misleading "fixedSize" concept & Add Uint8...64 SSZTypes

* Add size testing

* Move TestSSZObject_Batch and rename it as TestHashTreeRoot

* Changelog :)

* Rename endOffset to fixedOffset

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2025-10-14 17:33:52 +00:00
Radosław Kapka
683608e34a Improve returning individual message errors from Beacon API (#15835)
* Improve returning individual message errors from Beacon API

* changelog <3

* fix test

* add debug logs

* batch broadcast errors

* use logrus fields

* capitalize log messages
2025-10-14 15:22:00 +00:00
Manu NALEPA
fbbf2a1404 HasAtLeastOneIndex: Check the index is not too high. (#15865) 2025-10-14 14:39:38 +00:00
Potuz
82f556c50f Remove redundant check (#15844)
* Remove redundant check

* changelog

* fix gazelle
2025-10-14 12:39:19 +00:00
Radosław Kapka
c88aa77ac1 Display non-JSON error messages (#15860)
* Display non-JSON error messages

* changelog <3
2025-10-14 12:08:21 +00:00
fernantho
0568bec935 SSZ-QL: use FastSSZ-generated HashTreeRoot through SSZObject in sszInfo (#15805)
* stored CL object to enable the usage Fastssz's HashTreeRoot(). added basic test

* refactorization - using interfaces instead of storing original object

* added tests covering ssz custom types

* renamed hash_tree_root to ssz_interface as it contains MarshalSSZ and UnmarshalSSZ functions

* run gazelle

* renamed test and improved comments

* refactored test and extend to marshalSSZ and UnmarshalSSZ

* added changelog

* updated comment

* Changed SSZIface name to SSZObject. Removed MarshalSSZ and UnmarshalSSZ function signatures from interface as they are not used still. Refactored tests.

* renamed file ssz_interface.go to ssz_object.go. merge test from ssz_interface_test.go into query_test.go.
reordered source SSZObject field from sszInfo struct

* sticked SSZObject interface to HashTreeRoot() function, the only one needed so far

* run gazelle :)

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2025-10-13 21:39:15 +00:00
Potuz
e463bcd1e1 Mark block as invalid in gossip if it fails signature check (#15847)
* Mark block as invalid in gossip if it fails signature check

* Add tests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-13 20:29:27 +00:00
terence
5f8eb69201 Add proper handling for submit blind block 502 error (#15848)
* Add proper handling for builder relay 502 BadGateway errors

* James feedback

* Change wording
2025-10-13 18:36:06 +00:00
Marco Munizaga
4b98451649 fix allocation size of proofs in ComputeCellsAndProofsFromStructured (#15809)
* fix allocation size of proofs in ComputeCellsAndProofsFromStructured

the preallocated slice for KZG Proofs was 48x bigger than it needed to
be.

* changelog

---------

Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com>
Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
2025-10-13 17:16:02 +00:00
100 changed files with 1977 additions and 557 deletions

View File

@@ -284,7 +284,7 @@ func (c *Client) SubmitChangeBLStoExecution(ctx context.Context, request []*stru
if resp.StatusCode != http.StatusOK {
decoder := json.NewDecoder(resp.Body)
decoder.DisallowUnknownFields()
errorJson := &server.IndexedVerificationFailureError{}
errorJson := &server.IndexedErrorContainer{}
if err := decoder.Decode(errorJson); err != nil {
return errors.Wrapf(err, "failed to decode error JSON for %s", resp.Request.URL)
}

View File

@@ -726,6 +726,12 @@ func unexpectedStatusErr(response *http.Response, expected int) error {
return errors.Wrap(jsonErr, "unable to read response body")
}
return errors.Wrap(ErrNotOK, errMessage.Message)
case http.StatusBadGateway:
log.WithError(ErrBadGateway).Debug(msg)
if jsonErr := json.Unmarshal(bodyBytes, &errMessage); jsonErr != nil {
return errors.Wrap(jsonErr, "unable to read response body")
}
return errors.Wrap(ErrBadGateway, errMessage.Message)
default:
log.WithError(ErrNotOK).Debug(msg)
return errors.Wrap(ErrNotOK, fmt.Sprintf("unsupported error code: %d", response.StatusCode))

View File

@@ -12,7 +12,6 @@ import (
"github.com/OffchainLabs/prysm/v6/api"
"github.com/OffchainLabs/prysm/v6/api/server/structs"
"github.com/OffchainLabs/prysm/v6/testing/util"
"github.com/OffchainLabs/prysm/v6/config/params"
"github.com/OffchainLabs/prysm/v6/consensus-types/blocks"
"github.com/OffchainLabs/prysm/v6/consensus-types/interfaces"
@@ -22,6 +21,7 @@ import (
eth "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v6/testing/assert"
"github.com/OffchainLabs/prysm/v6/testing/require"
"github.com/OffchainLabs/prysm/v6/testing/util"
"github.com/prysmaticlabs/go-bitfield"
log "github.com/sirupsen/logrus"
)

View File

@@ -21,3 +21,4 @@ var ErrUnsupportedMediaType = errors.Wrap(ErrNotOK, "The media type in \"Content
// ErrNotAcceptable specifically means that a '406 - Not Acceptable' was received from the API.
var ErrNotAcceptable = errors.Wrap(ErrNotOK, "The accept header value is not acceptable")
var ErrBadGateway = errors.Wrap(ErrNotOK, "recv 502 BadGateway response from API")

View File

@@ -6,6 +6,11 @@ import (
"strings"
)
var (
ErrIndexedValidationFail = "One or more messages failed validation"
ErrIndexedBroadcastFail = "One or more messages failed broadcast"
)
// DecodeError represents an error resulting from trying to decode an HTTP request.
// It tracks the full field name for which decoding failed.
type DecodeError struct {
@@ -29,19 +34,38 @@ func (e *DecodeError) Error() string {
return fmt.Sprintf("could not decode %s: %s", strings.Join(e.path, "."), e.err.Error())
}
// IndexedVerificationFailureError wraps a collection of verification failures.
type IndexedVerificationFailureError struct {
Message string `json:"message"`
Code int `json:"code"`
Failures []*IndexedVerificationFailure `json:"failures"`
// IndexedErrorContainer wraps a collection of indexed errors.
type IndexedErrorContainer struct {
Message string `json:"message"`
Code int `json:"code"`
Failures []*IndexedError `json:"failures"`
}
func (e *IndexedVerificationFailureError) StatusCode() int {
func (e *IndexedErrorContainer) StatusCode() int {
return e.Code
}
// IndexedVerificationFailure represents an issue when verifying a single indexed object e.g. an item in an array.
type IndexedVerificationFailure struct {
// IndexedError represents an issue when processing a single indexed object e.g. an item in an array.
type IndexedError struct {
Index int `json:"index"`
Message string `json:"message"`
}
// BroadcastFailedError represents an error scenario where broadcasting a published message failed.
type BroadcastFailedError struct {
msg string
err error
}
// NewBroadcastFailedError creates a new instance of BroadcastFailedError.
func NewBroadcastFailedError(msg string, err error) *BroadcastFailedError {
return &BroadcastFailedError{
msg: msg,
err: err,
}
}
// Error returns the underlying error message.
func (e *BroadcastFailedError) Error() string {
return fmt.Sprintf("could not broadcast %s: %s", e.msg, e.err.Error())
}

View File

@@ -346,13 +346,24 @@ func (s *Service) notifyNewHeadEvent(
if err != nil {
return errors.Wrap(err, "could not check if node is optimistically synced")
}
parentRoot, err := s.ParentRoot([32]byte(newHeadRoot))
if err != nil {
return errors.Wrap(err, "could not obtain parent root in forkchoice")
}
parentSlot, err := s.RecentBlockSlot(parentRoot)
if err != nil {
return errors.Wrap(err, "could not obtain parent slot in forkchoice")
}
epochTransition := slots.ToEpoch(newHeadSlot) > slots.ToEpoch(parentSlot)
s.cfg.StateNotifier.StateFeed().Send(&feed.Event{
Type: statefeed.NewHead,
Data: &ethpbv1.EventHead{
Slot: newHeadSlot,
Block: newHeadRoot,
State: newHeadStateRoot,
EpochTransition: slots.IsEpochStart(newHeadSlot),
EpochTransition: epochTransition,
PreviousDutyDependentRoot: previousDutyDependentRoot[:],
CurrentDutyDependentRoot: currentDutyDependentRoot[:],
ExecutionOptimistic: isOptimistic,

View File

@@ -162,6 +162,9 @@ func Test_notifyNewHeadEvent(t *testing.T) {
require.NoError(t, srv.cfg.ForkChoiceStore.InsertNode(t.Context(), st, blk))
newHeadStateRoot := [32]byte{2}
newHeadRoot := [32]byte{3}
st, blk, err = prepareForkchoiceState(t.Context(), 1, newHeadRoot, [32]byte{}, [32]byte{}, &ethpb.Checkpoint{}, &ethpb.Checkpoint{})
require.NoError(t, err)
require.NoError(t, srv.cfg.ForkChoiceStore.InsertNode(t.Context(), st, blk))
require.NoError(t, srv.notifyNewHeadEvent(t.Context(), 1, bState, newHeadStateRoot[:], newHeadRoot[:]))
events := notifier.ReceivedEvents()
require.Equal(t, 1, len(events))
@@ -196,6 +199,9 @@ func Test_notifyNewHeadEvent(t *testing.T) {
newHeadStateRoot := [32]byte{2}
newHeadRoot := [32]byte{3}
st, blk, err = prepareForkchoiceState(t.Context(), 0, newHeadRoot, [32]byte{}, [32]byte{}, &ethpb.Checkpoint{}, &ethpb.Checkpoint{})
require.NoError(t, err)
require.NoError(t, srv.cfg.ForkChoiceStore.InsertNode(t.Context(), st, blk))
err = srv.notifyNewHeadEvent(t.Context(), epoch2Start, bState, newHeadStateRoot[:], newHeadRoot[:])
require.NoError(t, err)
events := notifier.ReceivedEvents()
@@ -213,6 +219,37 @@ func Test_notifyNewHeadEvent(t *testing.T) {
}
require.DeepSSZEqual(t, wanted, eventHead)
})
t.Run("epoch transition", func(t *testing.T) {
bState, _ := util.DeterministicGenesisState(t, 10)
srv := testServiceWithDB(t)
srv.SetGenesisTime(time.Now())
notifier := srv.cfg.StateNotifier.(*mock.MockStateNotifier)
srv.originBlockRoot = [32]byte{1}
st, blk, err := prepareForkchoiceState(t.Context(), 0, [32]byte{}, [32]byte{}, [32]byte{}, &ethpb.Checkpoint{}, &ethpb.Checkpoint{})
require.NoError(t, err)
require.NoError(t, srv.cfg.ForkChoiceStore.InsertNode(t.Context(), st, blk))
newHeadStateRoot := [32]byte{2}
newHeadRoot := [32]byte{3}
st, blk, err = prepareForkchoiceState(t.Context(), 32, newHeadRoot, [32]byte{}, [32]byte{}, &ethpb.Checkpoint{}, &ethpb.Checkpoint{})
require.NoError(t, err)
require.NoError(t, srv.cfg.ForkChoiceStore.InsertNode(t.Context(), st, blk))
newHeadSlot := params.BeaconConfig().SlotsPerEpoch
require.NoError(t, srv.notifyNewHeadEvent(t.Context(), newHeadSlot, bState, newHeadStateRoot[:], newHeadRoot[:]))
events := notifier.ReceivedEvents()
require.Equal(t, 1, len(events))
eventHead, ok := events[0].Data.(*ethpbv1.EventHead)
require.Equal(t, true, ok)
wanted := &ethpbv1.EventHead{
Slot: newHeadSlot,
Block: newHeadRoot[:],
State: newHeadStateRoot[:],
EpochTransition: true,
PreviousDutyDependentRoot: params.BeaconConfig().ZeroHash[:],
CurrentDutyDependentRoot: srv.originBlockRoot[:],
}
require.DeepSSZEqual(t, wanted, eventHead)
})
}
func TestRetrieveHead_ReadOnly(t *testing.T) {

View File

@@ -3302,7 +3302,6 @@ func Test_postBlockProcess_EventSending(t *testing.T) {
}
}
func setupLightClientTestRequirements(ctx context.Context, t *testing.T, s *Service, v int, options ...util.LightClientOption) (*util.TestLightClient, *postBlockProcessConfig) {
var l *util.TestLightClient
switch v {

View File

@@ -6,3 +6,4 @@ var errNilSignedWithdrawalMessage = errors.New("nil SignedBLSToExecutionChange m
var errNilWithdrawalMessage = errors.New("nil BLSToExecutionChange message")
var errInvalidBLSPrefix = errors.New("withdrawal credential prefix is not a BLS prefix")
var errInvalidWithdrawalCredentials = errors.New("withdrawal credentials do not match")
var ErrInvalidSignature = errors.New("invalid signature")

View File

@@ -114,9 +114,12 @@ func VerifyBlockSignatureUsingCurrentFork(beaconState state.ReadOnlyBeaconState,
}
proposerPubKey := proposer.PublicKey
sig := blk.Signature()
return signing.VerifyBlockSigningRoot(proposerPubKey, sig[:], domain, func() ([32]byte, error) {
if err := signing.VerifyBlockSigningRoot(proposerPubKey, sig[:], domain, func() ([32]byte, error) {
return blkRoot, nil
})
}); err != nil {
return ErrInvalidSignature
}
return nil
}
// BlockSignatureBatch retrieves the block signature batch from the provided block and its corresponding state.

View File

@@ -89,3 +89,36 @@ func TestVerifyBlockSignatureUsingCurrentFork(t *testing.T) {
require.NoError(t, err)
assert.NoError(t, blocks.VerifyBlockSignatureUsingCurrentFork(bState, wsb, blkRoot))
}
func TestVerifyBlockSignatureUsingCurrentFork_InvalidSignature(t *testing.T) {
params.SetupTestConfigCleanup(t)
bCfg := params.BeaconConfig()
bCfg.AltairForkEpoch = 100
bCfg.ForkVersionSchedule[bytesutil.ToBytes4(bCfg.AltairForkVersion)] = 100
params.OverrideBeaconConfig(bCfg)
bState, keys := util.DeterministicGenesisState(t, 100)
altairBlk := util.NewBeaconBlockAltair()
altairBlk.Block.ProposerIndex = 0
altairBlk.Block.Slot = params.BeaconConfig().SlotsPerEpoch * 100
blkRoot, err := altairBlk.Block.HashTreeRoot()
assert.NoError(t, err)
// Sign with wrong key (proposer index 0, but using key 1)
fData := &ethpb.Fork{
Epoch: 100,
CurrentVersion: params.BeaconConfig().AltairForkVersion,
PreviousVersion: params.BeaconConfig().GenesisForkVersion,
}
domain, err := signing.Domain(fData, 100, params.BeaconConfig().DomainBeaconProposer, bState.GenesisValidatorsRoot())
assert.NoError(t, err)
rt, err := signing.ComputeSigningRoot(altairBlk.Block, domain)
assert.NoError(t, err)
wrongSig := keys[1].Sign(rt[:]).Marshal()
altairBlk.Signature = wrongSig
wsb, err := consensusblocks.NewSignedBeaconBlock(altairBlk)
require.NoError(t, err)
err = blocks.VerifyBlockSignatureUsingCurrentFork(bState, wsb, blkRoot)
require.ErrorIs(t, err, blocks.ErrInvalidSignature, "Expected ErrInvalidSignature for invalid signature")
}

View File

@@ -43,6 +43,13 @@ func VerifyDataColumnSidecar(sidecar blocks.RODataColumn) error {
return ErrNoKzgCommitments
}
// A sidecar with more commitments than the max blob count for this block is invalid.
slot := sidecar.Slot()
maxBlobsPerBlock := params.BeaconConfig().MaxBlobsPerBlock(slot)
if len(sidecar.KzgCommitments) > maxBlobsPerBlock {
return ErrTooManyCommitments
}
// The column length must be equal to the number of commitments/proofs.
if len(sidecar.Column) != len(sidecar.KzgCommitments) || len(sidecar.Column) != len(sidecar.KzgProofs) {
return ErrMismatchLength

View File

@@ -18,38 +18,46 @@ import (
)
func TestVerifyDataColumnSidecar(t *testing.T) {
t.Run("index too large", func(t *testing.T) {
roSidecar := createTestSidecar(t, 1_000_000, nil, nil, nil)
err := peerdas.VerifyDataColumnSidecar(roSidecar)
require.ErrorIs(t, err, peerdas.ErrIndexTooLarge)
})
testCases := []struct {
name string
index uint64
blobCount int
commitmentCount int
proofCount int
maxBlobsPerBlock uint64
expectedError error
}{
{name: "index too large", index: 1_000_000, expectedError: peerdas.ErrIndexTooLarge},
{name: "no commitments", expectedError: peerdas.ErrNoKzgCommitments},
{name: "too many commitments", blobCount: 10, commitmentCount: 10, proofCount: 10, maxBlobsPerBlock: 2, expectedError: peerdas.ErrTooManyCommitments},
{name: "commitments size mismatch", commitmentCount: 1, maxBlobsPerBlock: 1, expectedError: peerdas.ErrMismatchLength},
{name: "proofs size mismatch", blobCount: 1, commitmentCount: 1, maxBlobsPerBlock: 1, expectedError: peerdas.ErrMismatchLength},
{name: "nominal", blobCount: 1, commitmentCount: 1, proofCount: 1, maxBlobsPerBlock: 1, expectedError: nil},
}
t.Run("no commitments", func(t *testing.T) {
roSidecar := createTestSidecar(t, 0, nil, nil, nil)
err := peerdas.VerifyDataColumnSidecar(roSidecar)
require.ErrorIs(t, err, peerdas.ErrNoKzgCommitments)
})
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig()
cfg.FuluForkEpoch = 0
cfg.BlobSchedule = []params.BlobScheduleEntry{{Epoch: 0, MaxBlobsPerBlock: tc.maxBlobsPerBlock}}
params.OverrideBeaconConfig(cfg)
t.Run("KZG commitments size mismatch", func(t *testing.T) {
kzgCommitments := make([][]byte, 1)
roSidecar := createTestSidecar(t, 0, nil, kzgCommitments, nil)
err := peerdas.VerifyDataColumnSidecar(roSidecar)
require.ErrorIs(t, err, peerdas.ErrMismatchLength)
})
column := make([][]byte, tc.blobCount)
kzgCommitments := make([][]byte, tc.commitmentCount)
kzgProof := make([][]byte, tc.proofCount)
t.Run("KZG proofs size mismatch", func(t *testing.T) {
column, kzgCommitments := make([][]byte, 1), make([][]byte, 1)
roSidecar := createTestSidecar(t, 0, column, kzgCommitments, nil)
err := peerdas.VerifyDataColumnSidecar(roSidecar)
require.ErrorIs(t, err, peerdas.ErrMismatchLength)
})
roSidecar := createTestSidecar(t, tc.index, column, kzgCommitments, kzgProof)
err := peerdas.VerifyDataColumnSidecar(roSidecar)
t.Run("nominal", func(t *testing.T) {
column, kzgCommitments, kzgProofs := make([][]byte, 1), make([][]byte, 1), make([][]byte, 1)
roSidecar := createTestSidecar(t, 0, column, kzgCommitments, kzgProofs)
err := peerdas.VerifyDataColumnSidecar(roSidecar)
require.NoError(t, err)
})
if tc.expectedError != nil {
require.ErrorIs(t, err, tc.expectedError)
return
}
require.NoError(t, err)
})
}
}
func TestVerifyDataColumnSidecarKZGProofs(t *testing.T) {

View File

@@ -257,7 +257,7 @@ func ComputeCellsAndProofsFromStructured(blobsAndProofs []*pb.BlobAndProofV2) ([
return nil, errors.Wrap(err, "compute cells")
}
kzgProofs := make([]kzg.Proof, 0, numberOfColumns*kzg.BytesPerProof)
kzgProofs := make([]kzg.Proof, 0, numberOfColumns)
for _, kzgProofBytes := range blobAndProof.KzgProofs {
if len(kzgProofBytes) != kzg.BytesPerProof {
return nil, errors.New("wrong KZG proof size - should never happen")

View File

@@ -441,6 +441,7 @@ func TestComputeCellsAndProofsFromStructured(t *testing.T) {
for i := range blobCount {
require.Equal(t, len(expectedCellsAndProofs[i].Cells), len(actualCellsAndProofs[i].Cells))
require.Equal(t, len(expectedCellsAndProofs[i].Proofs), len(actualCellsAndProofs[i].Proofs))
require.Equal(t, len(expectedCellsAndProofs[i].Proofs), cap(actualCellsAndProofs[i].Proofs))
// Compare cells
for j, expectedCell := range expectedCellsAndProofs[i].Cells {

View File

@@ -200,6 +200,7 @@ func (dcs *DataColumnStorage) WarmCache() {
fileMetadata, err := extractFileMetadata(path)
if err != nil {
log.WithError(err).Error("Error encountered while extracting file metadata")
return nil
}
// Open the data column filesystem file.
@@ -988,8 +989,7 @@ func filePath(root [fieldparams.RootLength]byte, epoch primitives.Epoch) string
// extractFileMetadata extracts the metadata from a file path.
// If the path is not a leaf, it returns nil.
func extractFileMetadata(path string) (*fileMetadata, error) {
// Is this Windows friendly?
parts := strings.Split(path, "/")
parts := strings.Split(path, string(filepath.Separator))
if len(parts) != 3 {
return nil, errors.Errorf("unexpected file %s", path)
}
@@ -1032,5 +1032,5 @@ func extractFileMetadata(path string) (*fileMetadata, error) {
// period computes the period of a given epoch.
func period(epoch primitives.Epoch) uint64 {
return uint64(epoch / params.BeaconConfig().MinEpochsForBlobsSidecarsRequest)
return uint64(epoch / params.BeaconConfig().MinEpochsForDataColumnSidecarsRequest)
}

View File

@@ -35,8 +35,9 @@ func (s DataColumnStorageSummary) HasIndex(index uint64) bool {
// HasAtLeastOneIndex returns true if at least one of the DataColumnSidecars at the given indices is available in the filesystem.
func (s DataColumnStorageSummary) HasAtLeastOneIndex(indices []uint64) bool {
size := uint64(len(s.mask))
for _, index := range indices {
if s.mask[index] {
if index < size && s.mask[index] {
return true
}
}

View File

@@ -25,11 +25,11 @@ func TestHasIndex(t *testing.T) {
func TestHasAtLeastOneIndex(t *testing.T) {
summary := NewDataColumnStorageSummary(0, [fieldparams.NumberOfColumns]bool{false, true})
hasAtLeastOneIndex := summary.HasAtLeastOneIndex([]uint64{3, 1, 2})
require.Equal(t, true, hasAtLeastOneIndex)
actual := summary.HasAtLeastOneIndex([]uint64{3, 1, fieldparams.NumberOfColumns, 2})
require.Equal(t, true, actual)
hasAtLeastOneIndex = summary.HasAtLeastOneIndex([]uint64{3, 4, 2})
require.Equal(t, false, hasAtLeastOneIndex)
actual = summary.HasAtLeastOneIndex([]uint64{3, 4, fieldparams.NumberOfColumns, 2})
require.Equal(t, false, actual)
}
func TestCount(t *testing.T) {

View File

@@ -126,7 +126,7 @@ func NewWarmedEphemeralDataColumnStorageUsingFs(t testing.TB, fs afero.Fs, opts
func NewEphemeralDataColumnStorageUsingFs(t testing.TB, fs afero.Fs, opts ...DataColumnStorageOption) *DataColumnStorage {
opts = append(opts,
WithDataColumnRetentionEpochs(params.BeaconConfig().MinEpochsForBlobsSidecarsRequest),
WithDataColumnRetentionEpochs(params.BeaconConfig().MinEpochsForDataColumnSidecarsRequest),
WithDataColumnFs(fs),
)

View File

@@ -58,7 +58,6 @@ go_library(
"//config/params:go_default_library",
"//consensus-types/primitives:go_default_library",
"//container/slice:go_default_library",
"//encoding/bytesutil:go_default_library",
"//genesis:go_default_library",
"//monitoring/prometheus:go_default_library",
"//monitoring/tracing:go_default_library",

View File

@@ -2,11 +2,13 @@ package node
import (
"context"
"os"
"github.com/OffchainLabs/prysm/v6/beacon-chain/db/filesystem"
"github.com/OffchainLabs/prysm/v6/beacon-chain/db/kv"
"github.com/OffchainLabs/prysm/v6/beacon-chain/db/slasherkv"
"github.com/OffchainLabs/prysm/v6/cmd"
"github.com/OffchainLabs/prysm/v6/genesis"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
)
@@ -36,6 +38,22 @@ func (c *dbClearer) clearKV(ctx context.Context, db *kv.Store) (*kv.Store, error
return kv.NewKVStore(ctx, db.DatabasePath())
}
func (c *dbClearer) clearGenesis(dir string) error {
if !c.shouldProceed() {
return nil
}
gfile, err := genesis.FindStateFile(dir)
if err != nil {
return nil
}
if err := os.Remove(gfile.FilePath()); err != nil {
return errors.Wrapf(err, "genesis state file not removed: %s", gfile.FilePath())
}
return nil
}
func (c *dbClearer) clearBlobs(bs *filesystem.BlobStorage) error {
if !c.shouldProceed() {
return nil

View File

@@ -60,7 +60,6 @@ import (
"github.com/OffchainLabs/prysm/v6/config/params"
"github.com/OffchainLabs/prysm/v6/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v6/container/slice"
"github.com/OffchainLabs/prysm/v6/encoding/bytesutil"
"github.com/OffchainLabs/prysm/v6/genesis"
"github.com/OffchainLabs/prysm/v6/monitoring/prometheus"
"github.com/OffchainLabs/prysm/v6/runtime"
@@ -178,6 +177,9 @@ func New(cliCtx *cli.Context, cancel context.CancelFunc, opts ...Option) (*Beaco
}
beacon.db = kvdb
if err := dbClearer.clearGenesis(dataDir); err != nil {
return nil, errors.Wrap(err, "could not clear genesis state")
}
providers := append(beacon.GenesisProviders, kv.NewLegacyGenesisProvider(kvdb))
if err := genesis.Initialize(ctx, dataDir, providers...); err != nil {
return nil, errors.Wrap(err, "could not initialize genesis state")
@@ -598,22 +600,7 @@ func (b *BeaconNode) startStateGen(ctx context.Context, bfs coverage.AvailableBl
return err
}
r := bytesutil.ToBytes32(cp.Root)
// Consider edge case where finalized root are zeros instead of genesis root hash.
if r == params.BeaconConfig().ZeroHash {
genesisBlock, err := b.db.GenesisBlock(ctx)
if err != nil {
return err
}
if genesisBlock != nil && !genesisBlock.IsNil() {
r, err = genesisBlock.Block().HashTreeRoot()
if err != nil {
return err
}
}
}
b.finalizedStateAtStartUp, err = sg.StateByRoot(ctx, r)
b.finalizedStateAtStartUp, err = sg.StateByRoot(ctx, [32]byte(cp.Root))
if err != nil {
return err
}

View File

@@ -514,17 +514,26 @@ func initializePersistentSubnets(id enode.ID, epoch primitives.Epoch) error {
//
// return [compute_subscribed_subnet(node_id, epoch, index) for index in range(SUBNETS_PER_NODE)]
func computeSubscribedSubnets(nodeID enode.ID, epoch primitives.Epoch) ([]uint64, error) {
subnetsPerNode := params.BeaconConfig().SubnetsPerNode
subs := make([]uint64, 0, subnetsPerNode)
beaconConfig := params.BeaconConfig()
for i := uint64(0); i < subnetsPerNode; i++ {
if flags.Get().SubscribeToAllSubnets {
subnets := make([]uint64, 0, beaconConfig.AttestationSubnetCount)
for i := range beaconConfig.AttestationSubnetCount {
subnets = append(subnets, i)
}
return subnets, nil
}
subnets := make([]uint64, 0, beaconConfig.SubnetsPerNode)
for i := range beaconConfig.SubnetsPerNode {
sub, err := computeSubscribedSubnet(nodeID, epoch, i)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "compute subscribed subnet")
}
subs = append(subs, sub)
subnets = append(subnets, sub)
}
return subs, nil
return subnets, nil
}
// Spec pseudocode definition:

View File

@@ -514,17 +514,39 @@ func TestDataColumnSubnets(t *testing.T) {
func TestSubnetComputation(t *testing.T) {
db, err := enode.OpenDB("")
assert.NoError(t, err)
require.NoError(t, err)
defer db.Close()
priv, _, err := crypto.GenerateSecp256k1Key(rand.Reader)
assert.NoError(t, err)
convertedKey, err := ecdsaprysm.ConvertFromInterfacePrivKey(priv)
assert.NoError(t, err)
localNode := enode.NewLocalNode(db, convertedKey)
retrievedSubnets, err := computeSubscribedSubnets(localNode.ID(), 1000)
assert.NoError(t, err)
assert.Equal(t, retrievedSubnets[0]+1, retrievedSubnets[1])
priv, _, err := crypto.GenerateSecp256k1Key(rand.Reader)
require.NoError(t, err)
convertedKey, err := ecdsaprysm.ConvertFromInterfacePrivKey(priv)
require.NoError(t, err)
localNode := enode.NewLocalNode(db, convertedKey)
beaconConfig := params.BeaconConfig()
t.Run("standard", func(t *testing.T) {
retrievedSubnets, err := computeSubscribedSubnets(localNode.ID(), 1000)
require.NoError(t, err)
require.Equal(t, beaconConfig.SubnetsPerNode, uint64(len(retrievedSubnets)))
require.Equal(t, retrievedSubnets[0]+1, retrievedSubnets[1])
})
t.Run("subscribed to all", func(t *testing.T) {
gFlags := new(flags.GlobalFlags)
gFlags.SubscribeToAllSubnets = true
flags.Init(gFlags)
defer flags.Init(new(flags.GlobalFlags))
retrievedSubnets, err := computeSubscribedSubnets(localNode.ID(), 1000)
require.NoError(t, err)
require.Equal(t, beaconConfig.AttestationSubnetCount, uint64(len(retrievedSubnets)))
for i := range beaconConfig.AttestationSubnetCount {
require.Equal(t, i, retrievedSubnets[i])
}
})
}
func TestInitializePersistentSubnets(t *testing.T) {

View File

@@ -12,6 +12,7 @@ go_library(
importpath = "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/core",
visibility = ["//visibility:public"],
deps = [
"//api/server:go_default_library",
"//beacon-chain/blockchain:go_default_library",
"//beacon-chain/cache:go_default_library",
"//beacon-chain/core/altair:go_default_library",

View File

@@ -7,6 +7,7 @@ import (
"sort"
"time"
"github.com/OffchainLabs/prysm/v6/api/server"
"github.com/OffchainLabs/prysm/v6/beacon-chain/cache"
"github.com/OffchainLabs/prysm/v6/beacon-chain/core/altair"
"github.com/OffchainLabs/prysm/v6/beacon-chain/core/epoch/precompute"
@@ -36,24 +37,6 @@ import (
var errOptimisticMode = errors.New("the node is currently optimistic and cannot serve validators")
// AggregateBroadcastFailedError represents an error scenario where
// broadcasting an aggregate selection proof failed.
type AggregateBroadcastFailedError struct {
err error
}
// NewAggregateBroadcastFailedError creates a new error instance.
func NewAggregateBroadcastFailedError(err error) AggregateBroadcastFailedError {
return AggregateBroadcastFailedError{
err: err,
}
}
// Error returns the underlying error message.
func (e *AggregateBroadcastFailedError) Error() string {
return fmt.Sprintf("could not broadcast signed aggregated attestation: %s", e.err.Error())
}
// ComputeValidatorPerformance reports the validator's latest balance along with other important metrics on
// rewards and penalties throughout its lifecycle in the beacon chain.
func (s *Service) ComputeValidatorPerformance(
@@ -360,7 +343,8 @@ func (s *Service) SubmitSignedContributionAndProof(
// Wait for p2p broadcast to complete and return the first error (if any)
err := errs.Wait()
if err != nil {
return &RpcError{Err: err, Reason: Internal}
log.WithError(err).Debug("Could not broadcast signed contribution and proof")
return &RpcError{Err: server.NewBroadcastFailedError("SignedContributionAndProof", err), Reason: Internal}
}
s.OperationNotifier.OperationFeed().Send(&feed.Event{
@@ -411,7 +395,8 @@ func (s *Service) SubmitSignedAggregateSelectionProof(
}
if err := s.Broadcaster.Broadcast(ctx, agg); err != nil {
return &RpcError{Err: &AggregateBroadcastFailedError{err: err}, Reason: Internal}
log.WithError(err).Debug("Could not broadcast signed aggregate att and proof")
return &RpcError{Err: server.NewBroadcastFailedError("SignedAggregateAttAndProof", err), Reason: Internal}
}
if logrus.GetLevel() >= logrus.DebugLevel {

View File

@@ -6,8 +6,6 @@ import (
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
"github.com/OffchainLabs/prysm/v6/api"
@@ -31,6 +29,7 @@ import (
"github.com/OffchainLabs/prysm/v6/runtime/version"
"github.com/OffchainLabs/prysm/v6/time/slots"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
const broadcastBLSChangesRateLimit = 128
@@ -200,22 +199,23 @@ func (s *Server) SubmitAttestations(w http.ResponseWriter, r *http.Request) {
return
}
if len(failedBroadcasts) > 0 {
httputil.HandleError(
w,
fmt.Sprintf("Attestations at index %s could not be broadcasted", strings.Join(failedBroadcasts, ", ")),
http.StatusInternalServerError,
)
return
}
if len(attFailures) > 0 {
failuresErr := &server.IndexedVerificationFailureError{
failuresErr := &server.IndexedErrorContainer{
Code: http.StatusBadRequest,
Message: "One or more attestations failed validation",
Message: server.ErrIndexedValidationFail,
Failures: attFailures,
}
httputil.WriteError(w, failuresErr)
return
}
if len(failedBroadcasts) > 0 {
failuresErr := &server.IndexedErrorContainer{
Code: http.StatusInternalServerError,
Message: server.ErrIndexedBroadcastFail,
Failures: failedBroadcasts,
}
httputil.WriteError(w, failuresErr)
return
}
}
@@ -247,8 +247,8 @@ func (s *Server) SubmitAttestationsV2(w http.ResponseWriter, r *http.Request) {
return
}
var attFailures []*server.IndexedVerificationFailure
var failedBroadcasts []string
var attFailures []*server.IndexedError
var failedBroadcasts []*server.IndexedError
if v >= version.Electra {
attFailures, failedBroadcasts, err = s.handleAttestationsElectra(ctx, req.Data)
@@ -260,29 +260,30 @@ func (s *Server) SubmitAttestationsV2(w http.ResponseWriter, r *http.Request) {
return
}
if len(failedBroadcasts) > 0 {
httputil.HandleError(
w,
fmt.Sprintf("Attestations at index %s could not be broadcasted", strings.Join(failedBroadcasts, ", ")),
http.StatusInternalServerError,
)
return
}
if len(attFailures) > 0 {
failuresErr := &server.IndexedVerificationFailureError{
failuresErr := &server.IndexedErrorContainer{
Code: http.StatusBadRequest,
Message: "One or more attestations failed validation",
Message: server.ErrIndexedValidationFail,
Failures: attFailures,
}
httputil.WriteError(w, failuresErr)
return
}
if len(failedBroadcasts) > 0 {
failuresErr := &server.IndexedErrorContainer{
Code: http.StatusInternalServerError,
Message: server.ErrIndexedBroadcastFail,
Failures: failedBroadcasts,
}
httputil.WriteError(w, failuresErr)
return
}
}
func (s *Server) handleAttestationsElectra(
ctx context.Context,
data json.RawMessage,
) (attFailures []*server.IndexedVerificationFailure, failedBroadcasts []string, err error) {
) (attFailures []*server.IndexedError, failedBroadcasts []*server.IndexedError, err error) {
var sourceAttestations []*structs.SingleAttestation
currentEpoch := slots.ToEpoch(s.TimeFetcher.CurrentSlot())
if currentEpoch < params.BeaconConfig().ElectraForkEpoch {
@@ -301,14 +302,14 @@ func (s *Server) handleAttestationsElectra(
for i, sourceAtt := range sourceAttestations {
att, err := sourceAtt.ToConsensus()
if err != nil {
attFailures = append(attFailures, &server.IndexedVerificationFailure{
attFailures = append(attFailures, &server.IndexedError{
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{
attFailures = append(attFailures, &server.IndexedError{
Index: i,
Message: "Incorrect attestation signature: " + err.Error(),
})
@@ -317,6 +318,13 @@ func (s *Server) handleAttestationsElectra(
validAttestations = append(validAttestations, att)
}
// We store the error for the first failed broadcast and use it in the log message in case
// there are broadcast issues. Having a single log at the end instead of logging
// for every failed broadcast prevents log noise in case there are many failures.
// Even though we only retain the first error, there is a very good chance that all
// broadcasts fail for the same reason, so this should be sufficient in most cases.
var broadcastErr error
for i, singleAtt := range validAttestations {
s.OperationNotifier.OperationFeed().Send(&feed.Event{
Type: operation.SingleAttReceived,
@@ -338,31 +346,45 @@ func (s *Server) handleAttestationsElectra(
wantedEpoch := slots.ToEpoch(att.Data.Slot)
vals, err := s.HeadFetcher.HeadValidatorsIndices(ctx, wantedEpoch)
if err != nil {
failedBroadcasts = append(failedBroadcasts, strconv.Itoa(i))
continue
return nil, nil, errors.Wrap(err, "could not get head validator indices")
}
subnet := corehelpers.ComputeSubnetFromCommitteeAndSlot(uint64(len(vals)), att.GetCommitteeIndex(), att.Data.Slot)
if err = s.Broadcaster.BroadcastAttestation(ctx, subnet, singleAtt); err != nil {
log.WithError(err).Errorf("could not broadcast attestation at index %d", i)
failedBroadcasts = append(failedBroadcasts, strconv.Itoa(i))
failedBroadcasts = append(failedBroadcasts, &server.IndexedError{
Index: i,
Message: server.NewBroadcastFailedError("SingleAttestation", err).Error(),
})
if broadcastErr == nil {
broadcastErr = err
}
continue
}
if features.Get().EnableExperimentalAttestationPool {
if err = s.AttestationCache.Add(att); err != nil {
log.WithError(err).Error("could not save attestation")
log.WithError(err).Error("Could not save attestation")
}
} else {
if err = s.AttestationsPool.SaveUnaggregatedAttestation(att); err != nil {
log.WithError(err).Error("could not save attestation")
log.WithError(err).Error("Could not save attestation")
}
}
}
if len(failedBroadcasts) > 0 {
log.WithFields(logrus.Fields{
"failedCount": len(failedBroadcasts),
"totalCount": len(validAttestations),
}).WithError(broadcastErr).Error("Some attestations failed to be broadcast")
}
return attFailures, failedBroadcasts, nil
}
func (s *Server) handleAttestations(ctx context.Context, data json.RawMessage) (attFailures []*server.IndexedVerificationFailure, failedBroadcasts []string, err error) {
func (s *Server) handleAttestations(
ctx context.Context,
data json.RawMessage,
) (attFailures []*server.IndexedError, failedBroadcasts []*server.IndexedError, err error) {
var sourceAttestations []*structs.Attestation
if slots.ToEpoch(s.TimeFetcher.CurrentSlot()) >= params.BeaconConfig().ElectraForkEpoch {
@@ -381,14 +403,14 @@ func (s *Server) handleAttestations(ctx context.Context, data json.RawMessage) (
for i, sourceAtt := range sourceAttestations {
att, err := sourceAtt.ToConsensus()
if err != nil {
attFailures = append(attFailures, &server.IndexedVerificationFailure{
attFailures = append(attFailures, &server.IndexedError{
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{
attFailures = append(attFailures, &server.IndexedError{
Index: i,
Message: "Incorrect attestation signature: " + err.Error(),
})
@@ -397,6 +419,13 @@ func (s *Server) handleAttestations(ctx context.Context, data json.RawMessage) (
validAttestations = append(validAttestations, att)
}
// We store the error for the first failed broadcast and use it in the log message in case
// there are broadcast issues. Having a single log at the end instead of logging
// for every failed broadcast prevents log noise in case there are many failures.
// Even though we only retain the first error, there is a very good chance that all
// broadcasts fail for the same reason, so this should be sufficient in most cases.
var broadcastErr error
for i, att := range validAttestations {
// Broadcast the unaggregated attestation on a feed to notify other services in the beacon node
// of a received unaggregated attestation.
@@ -413,32 +442,43 @@ func (s *Server) handleAttestations(ctx context.Context, data json.RawMessage) (
wantedEpoch := slots.ToEpoch(att.Data.Slot)
vals, err := s.HeadFetcher.HeadValidatorsIndices(ctx, wantedEpoch)
if err != nil {
failedBroadcasts = append(failedBroadcasts, strconv.Itoa(i))
continue
return nil, nil, errors.Wrap(err, "could not get head validator indices")
}
subnet := corehelpers.ComputeSubnetFromCommitteeAndSlot(uint64(len(vals)), att.Data.CommitteeIndex, att.Data.Slot)
if err = s.Broadcaster.BroadcastAttestation(ctx, subnet, att); err != nil {
log.WithError(err).Errorf("could not broadcast attestation at index %d", i)
failedBroadcasts = append(failedBroadcasts, strconv.Itoa(i))
failedBroadcasts = append(failedBroadcasts, &server.IndexedError{
Index: i,
Message: server.NewBroadcastFailedError("Attestation", err).Error(),
})
if broadcastErr == nil {
broadcastErr = err
}
continue
}
if features.Get().EnableExperimentalAttestationPool {
if err = s.AttestationCache.Add(att); err != nil {
log.WithError(err).Error("could not save attestation")
log.WithError(err).Error("Could not save attestation")
}
} else if att.IsAggregated() {
if err = s.AttestationsPool.SaveAggregatedAttestation(att); err != nil {
log.WithError(err).Error("could not save aggregated attestation")
log.WithError(err).Error("Could not save aggregated attestation")
}
} else {
if err = s.AttestationsPool.SaveUnaggregatedAttestation(att); err != nil {
log.WithError(err).Error("could not save unaggregated attestation")
log.WithError(err).Error("Could not save unaggregated attestation")
}
}
}
if len(failedBroadcasts) > 0 {
log.WithFields(logrus.Fields{
"failedCount": len(failedBroadcasts),
"totalCount": len(validAttestations),
}).WithError(broadcastErr).Error("Some attestations failed to be broadcast")
}
return attFailures, failedBroadcasts, nil
}
@@ -541,11 +581,11 @@ func (s *Server) SubmitSyncCommitteeSignatures(w http.ResponseWriter, r *http.Re
}
var validMessages []*eth.SyncCommitteeMessage
var msgFailures []*server.IndexedVerificationFailure
var msgFailures []*server.IndexedError
for i, sourceMsg := range req.Data {
msg, err := sourceMsg.ToConsensus()
if err != nil {
msgFailures = append(msgFailures, &server.IndexedVerificationFailure{
msgFailures = append(msgFailures, &server.IndexedError{
Index: i,
Message: "Could not convert request message to consensus message: " + err.Error(),
})
@@ -562,7 +602,7 @@ func (s *Server) SubmitSyncCommitteeSignatures(w http.ResponseWriter, r *http.Re
}
if len(msgFailures) > 0 {
failuresErr := &server.IndexedVerificationFailureError{
failuresErr := &server.IndexedErrorContainer{
Code: http.StatusBadRequest,
Message: "One or more messages failed validation",
Failures: msgFailures,
@@ -581,7 +621,7 @@ func (s *Server) SubmitBLSToExecutionChanges(w http.ResponseWriter, r *http.Requ
httputil.HandleError(w, fmt.Sprintf("Could not get head state: %v", err), http.StatusInternalServerError)
return
}
var failures []*server.IndexedVerificationFailure
var failures []*server.IndexedError
var toBroadcast []*eth.SignedBLSToExecutionChange
var req []*structs.SignedBLSToExecutionChange
@@ -602,7 +642,7 @@ func (s *Server) SubmitBLSToExecutionChanges(w http.ResponseWriter, r *http.Requ
for i, change := range req {
sbls, err := change.ToConsensus()
if err != nil {
failures = append(failures, &server.IndexedVerificationFailure{
failures = append(failures, &server.IndexedError{
Index: i,
Message: "Unable to decode SignedBLSToExecutionChange: " + err.Error(),
})
@@ -610,14 +650,14 @@ func (s *Server) SubmitBLSToExecutionChanges(w http.ResponseWriter, r *http.Requ
}
_, err = blocks.ValidateBLSToExecutionChange(st, sbls)
if err != nil {
failures = append(failures, &server.IndexedVerificationFailure{
failures = append(failures, &server.IndexedError{
Index: i,
Message: "Could not validate SignedBLSToExecutionChange: " + err.Error(),
})
continue
}
if err := blocks.VerifyBLSChangeSignature(st, sbls); err != nil {
failures = append(failures, &server.IndexedVerificationFailure{
failures = append(failures, &server.IndexedError{
Index: i,
Message: "Could not validate signature: " + err.Error(),
})
@@ -636,9 +676,9 @@ func (s *Server) SubmitBLSToExecutionChanges(w http.ResponseWriter, r *http.Requ
}
go s.broadcastBLSChanges(context.Background(), toBroadcast)
if len(failures) > 0 {
failuresErr := &server.IndexedVerificationFailureError{
failuresErr := &server.IndexedErrorContainer{
Code: http.StatusBadRequest,
Message: "One or more BLSToExecutionChange failed validation",
Message: server.ErrIndexedValidationFail,
Failures: failures,
}
httputil.WriteError(w, failuresErr)
@@ -655,18 +695,18 @@ func (s *Server) broadcastBLSBatch(ctx context.Context, ptr *[]*eth.SignedBLSToE
}
st, err := s.ChainInfoFetcher.HeadStateReadOnly(ctx)
if err != nil {
log.WithError(err).Error("could not get head state")
log.WithError(err).Error("Could not get head state")
return
}
for _, ch := range (*ptr)[:limit] {
if ch != nil {
_, err := blocks.ValidateBLSToExecutionChange(st, ch)
if err != nil {
log.WithError(err).Error("could not validate BLS to execution change")
log.WithError(err).Error("Could not validate BLS to execution change")
continue
}
if err := s.Broadcaster.Broadcast(ctx, ch); err != nil {
log.WithError(err).Error("could not broadcast BLS to execution changes.")
log.WithError(err).Error("Could not broadcast BLS to execution changes.")
}
}
}

View File

@@ -638,7 +638,7 @@ func TestSubmitAttestations(t *testing.T) {
s.SubmitAttestations(writer, request)
assert.Equal(t, http.StatusBadRequest, writer.Code)
e := &server.IndexedVerificationFailureError{}
e := &server.IndexedErrorContainer{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
assert.Equal(t, http.StatusBadRequest, e.Code)
require.Equal(t, 1, len(e.Failures))
@@ -772,7 +772,7 @@ func TestSubmitAttestations(t *testing.T) {
s.SubmitAttestationsV2(writer, request)
assert.Equal(t, http.StatusBadRequest, writer.Code)
e := &server.IndexedVerificationFailureError{}
e := &server.IndexedErrorContainer{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
assert.Equal(t, http.StatusBadRequest, e.Code)
require.Equal(t, 1, len(e.Failures))
@@ -873,7 +873,7 @@ func TestSubmitAttestations(t *testing.T) {
s.SubmitAttestationsV2(writer, request)
assert.Equal(t, http.StatusBadRequest, writer.Code)
e := &server.IndexedVerificationFailureError{}
e := &server.IndexedErrorContainer{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
assert.Equal(t, http.StatusBadRequest, e.Code)
require.Equal(t, 1, len(e.Failures))
@@ -1538,7 +1538,7 @@ func TestSubmitSignedBLSToExecutionChanges_Failures(t *testing.T) {
s.SubmitBLSToExecutionChanges(writer, request)
assert.Equal(t, http.StatusBadRequest, writer.Code)
time.Sleep(10 * time.Millisecond) // Delay to allow the routine to start
require.StringContains(t, "One or more BLSToExecutionChange failed validation", writer.Body.String())
require.StringContains(t, "One or more messages failed validation", writer.Body.String())
assert.Equal(t, true, broadcaster.BroadcastCalled.Load())
assert.Equal(t, numValidators, len(broadcaster.BroadcastMessages)+1)

View File

@@ -12,6 +12,7 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//api:go_default_library",
"//api/server:go_default_library",
"//api/server/structs:go_default_library",
"//beacon-chain/blockchain:go_default_library",
"//beacon-chain/builder:go_default_library",

View File

@@ -14,6 +14,7 @@ import (
"time"
"github.com/OffchainLabs/prysm/v6/api"
"github.com/OffchainLabs/prysm/v6/api/server"
"github.com/OffchainLabs/prysm/v6/api/server/structs"
"github.com/OffchainLabs/prysm/v6/beacon-chain/builder"
"github.com/OffchainLabs/prysm/v6/beacon-chain/cache"
@@ -268,22 +269,61 @@ func (s *Server) SubmitContributionAndProofs(w http.ResponseWriter, r *http.Requ
return
}
for _, item := range reqData {
var failures []*server.IndexedError
var failedBroadcasts []*server.IndexedError
for i, item := range reqData {
var contribution structs.SignedContributionAndProof
if err := json.Unmarshal(item, &contribution); err != nil {
httputil.HandleError(w, "Could not decode item: "+err.Error(), http.StatusBadRequest)
return
failures = append(failures, &server.IndexedError{
Index: i,
Message: "Could not unmarshal message: " + err.Error(),
})
continue
}
consensusItem, err := contribution.ToConsensus()
if err != nil {
httputil.HandleError(w, "Could not convert contribution to consensus format: "+err.Error(), http.StatusBadRequest)
return
failures = append(failures, &server.IndexedError{
Index: i,
Message: "Could not convert request contribution to consensus contribution: " + err.Error(),
})
continue
}
if rpcError := s.CoreService.SubmitSignedContributionAndProof(ctx, consensusItem); rpcError != nil {
httputil.HandleError(w, rpcError.Err.Error(), core.ErrorReasonToHTTP(rpcError.Reason))
return
rpcError := s.CoreService.SubmitSignedContributionAndProof(ctx, consensusItem)
if rpcError != nil {
var broadcastFailedErr *server.BroadcastFailedError
if errors.As(rpcError.Err, &broadcastFailedErr) {
failedBroadcasts = append(failedBroadcasts, &server.IndexedError{
Index: i,
Message: rpcError.Err.Error(),
})
continue
} else {
httputil.HandleError(w, rpcError.Err.Error(), core.ErrorReasonToHTTP(rpcError.Reason))
return
}
}
}
if len(failures) > 0 {
failuresErr := &server.IndexedErrorContainer{
Code: http.StatusBadRequest,
Message: server.ErrIndexedValidationFail,
Failures: failures,
}
httputil.WriteError(w, failuresErr)
return
}
if len(failedBroadcasts) > 0 {
failuresErr := &server.IndexedErrorContainer{
Code: http.StatusInternalServerError,
Message: server.ErrIndexedBroadcastFail,
Failures: failedBroadcasts,
}
httputil.WriteError(w, failuresErr)
return
}
}
// Deprecated: use SubmitAggregateAndProofsV2 instead
@@ -322,8 +362,8 @@ func (s *Server) SubmitAggregateAndProofs(w http.ResponseWriter, r *http.Request
}
rpcError := s.CoreService.SubmitSignedAggregateSelectionProof(ctx, consensusItem)
if rpcError != nil {
var aggregateBroadcastFailedError *core.AggregateBroadcastFailedError
ok := errors.As(rpcError.Err, &aggregateBroadcastFailedError)
var broadcastFailedErr *server.BroadcastFailedError
ok := errors.As(rpcError.Err, &broadcastFailedErr)
if ok {
broadcastFailed = true
} else {
@@ -368,49 +408,83 @@ func (s *Server) SubmitAggregateAndProofsV2(w http.ResponseWriter, r *http.Reque
return
}
broadcastFailed := false
var failures []*server.IndexedError
var failedBroadcasts []*server.IndexedError
var rpcError *core.RpcError
for _, raw := range reqData {
for i, raw := range reqData {
if v >= version.Electra {
var signedAggregate structs.SignedAggregateAttestationAndProofElectra
err = json.Unmarshal(raw, &signedAggregate)
if err != nil {
httputil.HandleError(w, "Failed to parse aggregate attestation and proof: "+err.Error(), http.StatusBadRequest)
return
failures = append(failures, &server.IndexedError{
Index: i,
Message: "Could not parse message: " + err.Error(),
})
continue
}
consensusItem, err := signedAggregate.ToConsensus()
if err != nil {
httputil.HandleError(w, "Could not convert request aggregate to consensus aggregate: "+err.Error(), http.StatusBadRequest)
return
failures = append(failures, &server.IndexedError{
Index: i,
Message: "Could not convert request aggregate to consensus aggregate: " + err.Error(),
})
continue
}
rpcError = s.CoreService.SubmitSignedAggregateSelectionProof(ctx, consensusItem)
} else {
var signedAggregate structs.SignedAggregateAttestationAndProof
err = json.Unmarshal(raw, &signedAggregate)
if err != nil {
httputil.HandleError(w, "Failed to parse aggregate attestation and proof: "+err.Error(), http.StatusBadRequest)
return
failures = append(failures, &server.IndexedError{
Index: i,
Message: "Could not parse message: " + err.Error(),
})
continue
}
consensusItem, err := signedAggregate.ToConsensus()
if err != nil {
httputil.HandleError(w, "Could not convert request aggregate to consensus aggregate: "+err.Error(), http.StatusBadRequest)
return
failures = append(failures, &server.IndexedError{
Index: i,
Message: "Could not convert request aggregate to consensus aggregate: " + err.Error(),
})
continue
}
rpcError = s.CoreService.SubmitSignedAggregateSelectionProof(ctx, consensusItem)
}
if rpcError != nil {
var aggregateBroadcastFailedError *core.AggregateBroadcastFailedError
if errors.As(rpcError.Err, &aggregateBroadcastFailedError) {
broadcastFailed = true
var broadcastFailedErr *server.BroadcastFailedError
if errors.As(rpcError.Err, &broadcastFailedErr) {
failedBroadcasts = append(failedBroadcasts, &server.IndexedError{
Index: i,
Message: rpcError.Err.Error(),
})
continue
} else {
httputil.HandleError(w, rpcError.Err.Error(), core.ErrorReasonToHTTP(rpcError.Reason))
return
}
}
}
if broadcastFailed {
httputil.HandleError(w, "Could not broadcast one or more signed aggregated attestations", http.StatusInternalServerError)
if len(failures) > 0 {
failuresErr := &server.IndexedErrorContainer{
Code: http.StatusBadRequest,
Message: server.ErrIndexedValidationFail,
Failures: failures,
}
httputil.WriteError(w, failuresErr)
return
}
if len(failedBroadcasts) > 0 {
failuresErr := &server.IndexedErrorContainer{
Code: http.StatusInternalServerError,
Message: server.ErrIndexedBroadcastFail,
Failures: failedBroadcasts,
}
httputil.WriteError(w, failuresErr)
return
}
}
@@ -523,7 +597,18 @@ func (s *Server) SubmitSyncCommitteeSubscription(w http.ResponseWriter, r *http.
epochDuration := time.Duration(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot)) * time.Second
totalDuration := epochDuration * time.Duration(epochsToWatch)
cache.SyncSubnetIDs.AddSyncCommitteeSubnets(pubkey48[:], startEpoch, sub.SyncCommitteeIndices, totalDuration)
subcommitteeSize := params.BeaconConfig().SyncCommitteeSize / params.BeaconConfig().SyncCommitteeSubnetCount
seen := make(map[uint64]bool)
var subnetIndices []uint64
for _, idx := range sub.SyncCommitteeIndices {
subnetIdx := idx / subcommitteeSize
if !seen[subnetIdx] {
seen[subnetIdx] = true
subnetIndices = append(subnetIndices, subnetIdx)
}
}
cache.SyncSubnetIDs.AddSyncCommitteeSubnets(pubkey48[:], startEpoch, subnetIndices, totalDuration)
}
}

View File

@@ -1049,9 +1049,8 @@ func TestSubmitSyncCommitteeSubscription(t *testing.T) {
s.SubmitSyncCommitteeSubscription(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
subnets, _, _, _ := cache.SyncSubnetIDs.GetSyncCommitteeSubnets(pubkeys[1], 0)
require.Equal(t, 2, len(subnets))
require.Equal(t, 1, len(subnets))
assert.Equal(t, uint64(0), subnets[0])
assert.Equal(t, uint64(2), subnets[1])
})
t.Run("multiple", func(t *testing.T) {
cache.SyncSubnetIDs.EmptyAllCaches()
@@ -1070,7 +1069,7 @@ func TestSubmitSyncCommitteeSubscription(t *testing.T) {
assert.Equal(t, uint64(0), subnets[0])
subnets, _, _, _ = cache.SyncSubnetIDs.GetSyncCommitteeSubnets(pubkeys[1], 0)
require.Equal(t, 1, len(subnets))
assert.Equal(t, uint64(2), subnets[0])
assert.Equal(t, uint64(0), subnets[0])
})
t.Run("no body", func(t *testing.T) {
request := httptest.NewRequest(http.MethodPost, "http://example.com", nil)

View File

@@ -3,6 +3,7 @@ package lookup
import (
"context"
"fmt"
"math"
"strconv"
"github.com/OffchainLabs/prysm/v6/beacon-chain/blockchain"
@@ -283,9 +284,13 @@ func (p *BeaconDbBlocker) Blobs(ctx context.Context, id string, opts ...options.
return make([]*blocks.VerifiedROBlob, 0), nil
}
fuluForkSlot, err := slots.EpochStart(params.BeaconConfig().FuluForkEpoch)
if err != nil {
return nil, &core.RpcError{Err: errors.Wrap(err, "could not calculate Fulu start slot"), Reason: core.Internal}
// Compute the first Fulu slot.
fuluForkSlot := primitives.Slot(math.MaxUint64)
if fuluForkEpoch := params.BeaconConfig().FuluForkEpoch; fuluForkEpoch != primitives.Epoch(math.MaxUint64) {
fuluForkSlot, err = slots.EpochStart(fuluForkEpoch)
if err != nil {
return nil, &core.RpcError{Err: errors.Wrap(err, "could not calculate Fulu start slot"), Reason: core.Internal}
}
}
// Convert versioned hashes to indices if provided

View File

@@ -587,6 +587,51 @@ func TestGetBlob(t *testing.T) {
require.Equal(t, http.StatusBadRequest, core.ErrorReasonToHTTP(rpcErr.Reason))
require.StringContains(t, "not supported before", rpcErr.Err.Error())
})
t.Run("fulu fork epoch not set (MaxUint64)", func(t *testing.T) {
// Setup with Deneb fork enabled but Fulu fork epoch set to MaxUint64 (not set/far future)
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.DenebForkEpoch = 1
cfg.FuluForkEpoch = primitives.Epoch(math.MaxUint64) // Not set / far future
params.OverrideBeaconConfig(cfg)
// Create and save Deneb block and blob sidecars
denebSlot := util.SlotAtEpoch(t, cfg.DenebForkEpoch)
_, tempBlobStorage := filesystem.NewEphemeralBlobStorageAndFs(t)
denebBlockWithBlobs, denebBlobSidecars := util.GenerateTestDenebBlockWithSidecar(t, [fieldparams.RootLength]byte{}, denebSlot, 2, util.WithDenebSlot(denebSlot))
denebBlockRoot := denebBlockWithBlobs.Root()
verifiedDenebBlobs := verification.FakeVerifySliceForTest(t, denebBlobSidecars)
for i := range verifiedDenebBlobs {
err := tempBlobStorage.Save(verifiedDenebBlobs[i])
require.NoError(t, err)
}
err := db.SaveBlock(t.Context(), denebBlockWithBlobs)
require.NoError(t, err)
blocker := &BeaconDbBlocker{
GenesisTimeFetcher: &testutil.MockGenesisTimeFetcher{
Genesis: time.Now(),
},
BeaconDB: db,
BlobStorage: tempBlobStorage,
}
// Should successfully retrieve blobs even when FuluForkEpoch is not set
retrievedBlobs, rpcErr := blocker.Blobs(ctx, hexutil.Encode(denebBlockRoot[:]))
require.IsNil(t, rpcErr)
require.Equal(t, 2, len(retrievedBlobs))
// Verify blob content matches
for i, retrievedBlob := range retrievedBlobs {
require.NotNil(t, retrievedBlob.BlobSidecar)
require.DeepEqual(t, denebBlobSidecars[i].Blob, retrievedBlob.Blob)
require.DeepEqual(t, denebBlobSidecars[i].KzgCommitment, retrievedBlob.KzgCommitment)
}
})
}
func TestBlobs_CommitmentOrdering(t *testing.T) {

View File

@@ -316,6 +316,10 @@ func (vs *Server) ProposeBeaconBlock(ctx context.Context, req *ethpb.GenericSign
blobSidecars, dataColumnSidecars, err = vs.handleUnblindedBlock(rob, req)
}
if err != nil {
if errors.Is(err, builderapi.ErrBadGateway) && block.IsBlinded() {
log.WithError(err).Info("Optimistically proposed block - builder relay temporarily unavailable, block may arrive over P2P")
return &ethpb.ProposeResponse{BlockRoot: root[:]}, nil
}
return nil, status.Errorf(codes.Internal, "%s: %v", "handle block failed", err)
}

View File

@@ -6,6 +6,7 @@ import (
"testing"
"time"
builderapi "github.com/OffchainLabs/prysm/v6/api/client/builder"
"github.com/OffchainLabs/prysm/v6/beacon-chain/blockchain/kzg"
mock "github.com/OffchainLabs/prysm/v6/beacon-chain/blockchain/testing"
"github.com/OffchainLabs/prysm/v6/beacon-chain/builder"
@@ -3634,4 +3635,52 @@ func TestServer_ProposeBeaconBlock_PostFuluBlindedBlock(t *testing.T) {
require.NotNil(t, res)
require.NotEmpty(t, res.BlockRoot)
})
t.Run("blinded block - 502 error handling", func(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.FuluForkEpoch = 10
params.OverrideBeaconConfig(cfg)
mockBuilder := &builderTest.MockBuilderService{
HasConfigured: true,
Cfg: &builderTest.Config{BeaconDB: db},
PayloadDeneb: &enginev1.ExecutionPayloadDeneb{},
ErrSubmitBlindedBlock: builderapi.ErrBadGateway,
}
c := &mock.ChainService{State: beaconState, Root: parentRoot[:]}
proposerServer := &Server{
ChainStartFetcher: &mockExecution.Chain{},
Eth1InfoFetcher: &mockExecution.Chain{},
Eth1BlockFetcher: &mockExecution.Chain{},
BlockReceiver: c,
BlobReceiver: c,
HeadFetcher: c,
BlockNotifier: c.BlockNotifier(),
OperationNotifier: c.OperationNotifier(),
StateGen: stategen.New(db, doublylinkedtree.New()),
TimeFetcher: c,
SyncChecker: &mockSync.Sync{IsSyncing: false},
BeaconDB: db,
BlockBuilder: mockBuilder,
P2P: &mockp2p.MockBroadcaster{},
}
blindedBlock := util.NewBlindedBeaconBlockDeneb()
blindedBlock.Message.Slot = 160 // This puts us at epoch 5 (160/32 = 5)
blindedBlock.Message.ProposerIndex = 0
blindedBlock.Message.ParentRoot = parentRoot[:]
blindedBlock.Message.StateRoot = make([]byte, 32)
req := &ethpb.GenericSignedBeaconBlock{
Block: &ethpb.GenericSignedBeaconBlock_BlindedDeneb{BlindedDeneb: blindedBlock},
}
// Should handle 502 error gracefully and continue with original blinded block
res, err := proposerServer.ProposeBeaconBlock(ctx, req)
require.NoError(t, err)
require.NotNil(t, res)
require.NotEmpty(t, res.BlockRoot)
})
}

View File

@@ -14,7 +14,7 @@ import (
"github.com/pkg/errors"
)
const signatureVerificationInterval = 50 * time.Millisecond
const signatureVerificationInterval = 5 * time.Millisecond
type signatureVerifier struct {
set *bls.SignatureBatch

View File

@@ -1122,19 +1122,21 @@ func randomPeer(
}
}
slices.Sort(nonRateLimitedPeers)
if len(nonRateLimitedPeers) == 0 {
log.WithFields(logrus.Fields{
"peerCount": peerCount,
"delay": waitPeriod,
}).Debug("Waiting for a peer with enough bandwidth for data column sidecars")
time.Sleep(waitPeriod)
continue
if len(nonRateLimitedPeers) > 0 {
slices.Sort(nonRateLimitedPeers)
randomIndex := randomSource.Intn(len(nonRateLimitedPeers))
return nonRateLimitedPeers[randomIndex], nil
}
randomIndex := randomSource.Intn(len(nonRateLimitedPeers))
return nonRateLimitedPeers[randomIndex], nil
log.WithFields(logrus.Fields{
"peerCount": peerCount,
"delay": waitPeriod,
}).Debug("Waiting for a peer with enough bandwidth for data column sidecars")
select {
case <-time.After(waitPeriod):
case <-ctx.Done():
}
}
return "", ctx.Err()

View File

@@ -45,6 +45,7 @@ func TestFetchDataColumnSidecars(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.FuluForkEpoch = 0
cfg.BlobSchedule = []params.BlobScheduleEntry{{Epoch: 0, MaxBlobsPerBlock: 10}}
params.OverrideBeaconConfig(cfg)
// Start the trusted setup.
@@ -760,6 +761,12 @@ func TestVerifyDataColumnSidecarsByPeer(t *testing.T) {
err := kzg.Start()
require.NoError(t, err)
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig()
cfg.FuluForkEpoch = 0
cfg.BlobSchedule = []params.BlobScheduleEntry{{Epoch: 0, MaxBlobsPerBlock: 2}}
params.OverrideBeaconConfig(cfg)
t.Run("nominal", func(t *testing.T) {
const (
start, stop = 0, 15

View File

@@ -683,6 +683,7 @@ func TestFetchOriginColumns(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.FuluForkEpoch = 0
cfg.BlobSchedule = []params.BlobScheduleEntry{{Epoch: 0, MaxBlobsPerBlock: 10}}
params.OverrideBeaconConfig(cfg)
const (

View File

@@ -70,6 +70,7 @@ func (s *Service) dataColumnSidecarsByRangeRPCHandler(ctx context.Context, msg i
log.Trace("Serving data column sidecars by range")
if rangeParameters == nil {
closeStream(stream, log)
return nil
}

View File

@@ -23,16 +23,15 @@ import (
fieldparams "github.com/OffchainLabs/prysm/v6/config/fieldparams"
"github.com/OffchainLabs/prysm/v6/config/params"
"github.com/OffchainLabs/prysm/v6/consensus-types/blocks"
consensusblocks "github.com/OffchainLabs/prysm/v6/consensus-types/blocks"
"github.com/OffchainLabs/prysm/v6/consensus-types/primitives"
pb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v6/testing/assert"
"github.com/OffchainLabs/prysm/v6/testing/util"
)
func TestDataColumnSidecarsByRangeRPCHandler(t *testing.T) {
params.SetupTestConfigCleanup(t)
beaconConfig := params.BeaconConfig()
//beaconConfig.FuluForkEpoch = beaconConfig.ElectraForkEpoch + 100
beaconConfig.FuluForkEpoch = 0
params.OverrideBeaconConfig(beaconConfig)
params.BeaconConfig().InitializeForkSchedule()
@@ -47,6 +46,7 @@ func TestDataColumnSidecarsByRangeRPCHandler(t *testing.T) {
ctxMap, err := ContextByteVersionsForValRoot(params.BeaconConfig().GenesisValidatorsRoot)
require.NoError(t, err)
t.Run("invalid request", func(t *testing.T) {
slot := primitives.Slot(400)
mockNower.SetSlot(t, clock, slot)
@@ -72,8 +72,8 @@ func TestDataColumnSidecarsByRangeRPCHandler(t *testing.T) {
remoteP2P.BHost.SetStreamHandler(protocolID, func(stream network.Stream) {
defer wg.Done()
code, _, err := readStatusCodeNoDeadline(stream, localP2P.Encoding())
require.NoError(t, err)
require.Equal(t, responseCodeInvalidRequest, code)
assert.NoError(t, err)
assert.Equal(t, responseCodeInvalidRequest, code)
})
localP2P.Connect(remoteP2P)
@@ -94,6 +94,48 @@ func TestDataColumnSidecarsByRangeRPCHandler(t *testing.T) {
}
})
t.Run("in the future", func(t *testing.T) {
slot := primitives.Slot(400)
mockNower.SetSlot(t, clock, slot)
localP2P, remoteP2P := p2ptest.NewTestP2P(t), p2ptest.NewTestP2P(t)
protocolID := protocol.ID(fmt.Sprintf("%s/ssz_snappy", p2p.RPCDataColumnSidecarsByRangeTopicV1))
service := &Service{
cfg: &config{
p2p: localP2P,
chain: &chainMock.ChainService{
Slot: &slot,
},
clock: clock,
},
rateLimiter: newRateLimiter(localP2P),
}
var wg sync.WaitGroup
wg.Add(1)
remoteP2P.BHost.SetStreamHandler(protocolID, func(stream network.Stream) {
defer wg.Done()
_, err := readChunkedDataColumnSidecar(stream, remoteP2P, ctxMap)
assert.Equal(t, true, errors.Is(err, io.EOF))
})
localP2P.Connect(remoteP2P)
stream, err := localP2P.BHost.NewStream(ctx, remoteP2P.BHost.ID(), protocolID)
require.NoError(t, err)
msg := &pb.DataColumnSidecarsByRangeRequest{
StartSlot: slot + 1,
Count: 50,
Columns: []uint64{1, 2, 3, 4, 6, 7, 8, 9, 10},
}
err = service.dataColumnSidecarsByRangeRPCHandler(ctx, msg, stream)
require.NoError(t, err)
})
t.Run("nominal", func(t *testing.T) {
slot := primitives.Slot(400)
@@ -133,12 +175,12 @@ func TestDataColumnSidecarsByRangeRPCHandler(t *testing.T) {
signedBeaconBlockPb.Block.ParentRoot = roots[i-1][:]
}
signedBeaconBlock, err := consensusblocks.NewSignedBeaconBlock(signedBeaconBlockPb)
signedBeaconBlock, err := blocks.NewSignedBeaconBlock(signedBeaconBlockPb)
require.NoError(t, err)
// There is a discrepancy between the root of the beacon block and the rodata column root,
// but for the sake of this test, we actually don't care.
roblock, err := consensusblocks.NewROBlockWithRoot(signedBeaconBlock, roots[i])
roblock, err := blocks.NewROBlockWithRoot(signedBeaconBlock, roots[i])
require.NoError(t, err)
roBlocks = append(roBlocks, roblock)
@@ -178,28 +220,28 @@ func TestDataColumnSidecarsByRangeRPCHandler(t *testing.T) {
break
}
require.NoError(t, err)
assert.NoError(t, err)
sidecars = append(sidecars, sidecar)
}
require.Equal(t, 8, len(sidecars))
require.Equal(t, root0, sidecars[0].BlockRoot())
require.Equal(t, root0, sidecars[1].BlockRoot())
require.Equal(t, root0, sidecars[2].BlockRoot())
require.Equal(t, root3, sidecars[3].BlockRoot())
require.Equal(t, root3, sidecars[4].BlockRoot())
require.Equal(t, root5, sidecars[5].BlockRoot())
require.Equal(t, root5, sidecars[6].BlockRoot())
require.Equal(t, root5, sidecars[7].BlockRoot())
assert.Equal(t, 8, len(sidecars))
assert.Equal(t, root0, sidecars[0].BlockRoot())
assert.Equal(t, root0, sidecars[1].BlockRoot())
assert.Equal(t, root0, sidecars[2].BlockRoot())
assert.Equal(t, root3, sidecars[3].BlockRoot())
assert.Equal(t, root3, sidecars[4].BlockRoot())
assert.Equal(t, root5, sidecars[5].BlockRoot())
assert.Equal(t, root5, sidecars[6].BlockRoot())
assert.Equal(t, root5, sidecars[7].BlockRoot())
require.Equal(t, uint64(1), sidecars[0].Index)
require.Equal(t, uint64(2), sidecars[1].Index)
require.Equal(t, uint64(3), sidecars[2].Index)
require.Equal(t, uint64(4), sidecars[3].Index)
require.Equal(t, uint64(6), sidecars[4].Index)
require.Equal(t, uint64(7), sidecars[5].Index)
require.Equal(t, uint64(8), sidecars[6].Index)
require.Equal(t, uint64(9), sidecars[7].Index)
assert.Equal(t, uint64(1), sidecars[0].Index)
assert.Equal(t, uint64(2), sidecars[1].Index)
assert.Equal(t, uint64(3), sidecars[2].Index)
assert.Equal(t, uint64(4), sidecars[3].Index)
assert.Equal(t, uint64(6), sidecars[4].Index)
assert.Equal(t, uint64(7), sidecars[5].Index)
assert.Equal(t, uint64(8), sidecars[6].Index)
assert.Equal(t, uint64(9), sidecars[7].Index)
})
localP2P.Connect(remoteP2P)
@@ -215,7 +257,6 @@ func TestDataColumnSidecarsByRangeRPCHandler(t *testing.T) {
err = service.dataColumnSidecarsByRangeRPCHandler(ctx, msg, stream)
require.NoError(t, err)
})
}
func TestValidateDataColumnsByRange(t *testing.T) {

View File

@@ -716,10 +716,6 @@ func (s *Service) samplingSize() (uint64, error) {
}
func (s *Service) persistentAndAggregatorSubnetIndices(currentSlot primitives.Slot) map[uint64]bool {
if flags.Get().SubscribeToAllSubnets {
return mapFromCount(params.BeaconConfig().AttestationSubnetCount)
}
persistentSubnetIndices := persistentSubnetIndices()
aggregatorSubnetIndices := aggregatorSubnetIndices(currentSlot)

View File

@@ -94,9 +94,11 @@ func TestVerifyIndexInCommittee_ExistsInBeaconCommittee(t *testing.T) {
assert.ErrorContains(t, wanted, err)
assert.Equal(t, pubsub.ValidationReject, result)
att.Data.CommitteeIndex = 10000
// Test the edge case where committee index equals count (should be rejected)
// With 64 validators and minimal config, count = 2, so valid indices are 0 and 1
att.Data.CommitteeIndex = 2
_, _, result, err = service.validateCommitteeIndexAndCount(ctx, att, s)
require.ErrorContains(t, "committee index 10000 > 2", err)
require.ErrorContains(t, "committee index 2 >= 2", err)
assert.Equal(t, pubsub.ValidationReject, result)
}

View File

@@ -278,8 +278,8 @@ func (s *Service) validateCommitteeIndexAndCount(
} else {
ci = a.GetCommitteeIndex()
}
if uint64(ci) > count {
return 0, 0, pubsub.ValidationReject, fmt.Errorf("committee index %d > %d", ci, count)
if uint64(ci) >= count {
return 0, 0, pubsub.ValidationReject, fmt.Errorf("committee index %d >= %d", ci, count)
}
return ci, valCount, pubsub.ValidationAccept, nil
}

View File

@@ -611,3 +611,41 @@ func TestService_setSeenUnaggregatedAtt(t *testing.T) {
})
})
}
func Test_validateCommitteeIndexAndCount_Boundary(t *testing.T) {
ctx := t.Context()
// Create a minimal state with a known number of validators.
validators := uint64(64)
bs, _ := util.DeterministicGenesisState(t, validators)
require.NoError(t, bs.SetSlot(1))
s := &Service{}
// Build a minimal Phase0 attestation (unaggregated path).
att := &ethpb.Attestation{
Data: &ethpb.AttestationData{
Slot: 1,
CommitteeIndex: 0,
},
}
// First call to obtain the active validator count used to derive committees per slot.
_, valCount, res, err := s.validateCommitteeIndexAndCount(ctx, att, bs)
require.NoError(t, err)
require.Equal(t, pubsub.ValidationAccept, res)
count := helpers.SlotCommitteeCount(valCount)
// committee_index == count - 1 should be accepted.
att.Data.CommitteeIndex = primitives.CommitteeIndex(count - 1)
_, _, res, err = s.validateCommitteeIndexAndCount(ctx, att, bs)
require.NoError(t, err)
require.Equal(t, pubsub.ValidationAccept, res)
// committee_index == count should be rejected (out of range).
att.Data.CommitteeIndex = primitives.CommitteeIndex(count)
_, _, res, err = s.validateCommitteeIndexAndCount(ctx, att, bs)
require.ErrorContains(t, "committee index", err)
require.Equal(t, pubsub.ValidationReject, res)
}

View File

@@ -294,6 +294,9 @@ func (s *Service) validatePhase0Block(ctx context.Context, blk interfaces.ReadOn
}
if err := blocks.VerifyBlockSignatureUsingCurrentFork(parentState, blk, blockRoot); err != nil {
if errors.Is(err, blocks.ErrInvalidSignature) {
s.setBadBlock(ctx, blockRoot)
}
return nil, err
}
// In the event the block is more than an epoch ahead from its

View File

@@ -103,11 +103,84 @@ func TestValidateBeaconBlockPubSub_InvalidSignature(t *testing.T) {
},
}
res, err := r.validateBeaconBlockPubSub(ctx, "", m)
require.ErrorIs(t, err, signing.ErrSigFailedToVerify)
require.ErrorContains(t, "invalid signature", err)
result := res == pubsub.ValidationReject
assert.Equal(t, true, result)
}
func TestValidateBeaconBlockPubSub_InvalidSignature_MarksBlockAsBad(t *testing.T) {
db := dbtest.SetupDB(t)
p := p2ptest.NewTestP2P(t)
ctx := t.Context()
beaconState, privKeys := util.DeterministicGenesisState(t, 100)
parentBlock := util.NewBeaconBlock()
util.SaveBlock(t, ctx, db, parentBlock)
bRoot, err := parentBlock.Block.HashTreeRoot()
require.NoError(t, err)
require.NoError(t, db.SaveState(ctx, beaconState, bRoot))
require.NoError(t, db.SaveStateSummary(ctx, &ethpb.StateSummary{Root: bRoot[:]}))
copied := beaconState.Copy()
require.NoError(t, copied.SetSlot(1))
proposerIdx, err := helpers.BeaconProposerIndex(ctx, copied)
require.NoError(t, err)
msg := util.NewBeaconBlock()
msg.Block.ParentRoot = bRoot[:]
msg.Block.Slot = 1
msg.Block.ProposerIndex = proposerIdx
badPrivKeyIdx := proposerIdx + 1 // We generate a valid signature from a wrong private key which fails to verify
msg.Signature, err = signing.ComputeDomainAndSign(beaconState, 0, msg.Block, params.BeaconConfig().DomainBeaconProposer, privKeys[badPrivKeyIdx])
require.NoError(t, err)
stateGen := stategen.New(db, doublylinkedtree.New())
chainService := &mock.ChainService{Genesis: time.Unix(time.Now().Unix()-int64(params.BeaconConfig().SecondsPerSlot), 0),
FinalizedCheckPoint: &ethpb.Checkpoint{
Epoch: 0,
Root: make([]byte, 32),
},
DB: db,
}
r := &Service{
cfg: &config{
beaconDB: db,
p2p: p,
initialSync: &mockSync.Sync{IsSyncing: false},
chain: chainService,
clock: startup.NewClock(chainService.Genesis, chainService.ValidatorsRoot),
blockNotifier: chainService.BlockNotifier(),
stateGen: stateGen,
},
seenBlockCache: lruwrpr.New(10),
badBlockCache: lruwrpr.New(10),
}
blockRoot, err := msg.Block.HashTreeRoot()
require.NoError(t, err)
// Verify block is not marked as bad initially
assert.Equal(t, false, r.hasBadBlock(blockRoot), "block should not be marked as bad initially")
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
digest, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, digest)
m := &pubsub.Message{
Message: &pubsubpb.Message{
Data: buf.Bytes(),
Topic: &topic,
},
}
res, err := r.validateBeaconBlockPubSub(ctx, "", m)
require.ErrorContains(t, "invalid signature", err)
result := res == pubsub.ValidationReject
assert.Equal(t, true, result)
// Verify block is now marked as bad after invalid signature
assert.Equal(t, true, r.hasBadBlock(blockRoot), "block should be marked as bad after invalid signature")
}
func TestValidateBeaconBlockPubSub_BlockAlreadyPresentInDB(t *testing.T) {
db := dbtest.SetupDB(t)
ctx := t.Context()
@@ -976,7 +1049,7 @@ func TestValidateBeaconBlockPubSub_InvalidParentBlock(t *testing.T) {
},
}
res, err := r.validateBeaconBlockPubSub(ctx, "", m)
require.ErrorContains(t, "could not unmarshal bytes into signature", err)
require.ErrorContains(t, "invalid signature", err)
assert.Equal(t, res, pubsub.ValidationReject, "block with invalid signature should be rejected")
require.NoError(t, copied.SetSlot(2))

View File

@@ -58,7 +58,6 @@ func TestValid(t *testing.T) {
t.Run("one invalid column", func(t *testing.T) {
columns := GenerateTestDataColumns(t, [fieldparams.RootLength]byte{}, 1, 1)
columns[0].KzgCommitments = [][]byte{}
verifier := initializer.NewDataColumnsVerifier(columns, GossipDataColumnSidecarRequirements)
err := verifier.ValidFields()
@@ -67,6 +66,14 @@ func TestValid(t *testing.T) {
})
t.Run("nominal", func(t *testing.T) {
const maxBlobsPerBlock = 2
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig()
cfg.FuluForkEpoch = 0
cfg.BlobSchedule = []params.BlobScheduleEntry{{Epoch: 0, MaxBlobsPerBlock: maxBlobsPerBlock}}
params.OverrideBeaconConfig(cfg)
columns := GenerateTestDataColumns(t, [fieldparams.RootLength]byte{}, 1, 1)
verifier := initializer.NewDataColumnsVerifier(columns, GossipDataColumnSidecarRequirements)

View File

@@ -71,9 +71,15 @@ var (
errBatchBlockRootMismatch = errors.Join(ErrBlobInvalid, errors.New("sidecar block header root does not match signed block"))
)
// errVerificationImplementationFault indicates that a code path yielding VerifiedROBlobs has an implementation
// error, leading it to call VerifiedROBlobError with a nil error.
var errVerificationImplementationFault = errors.New("could not verify blob data or create a valid VerifiedROBlob")
var (
// errBlobVerificationImplementationFault indicates that a code path yielding VerifiedROBlobs has an implementation
// error, leading it to call VerifiedROBlobError with a nil error.
errBlobVerificationImplementationFault = errors.New("could not verify blob data or create a valid VerifiedROBlob")
// errDataColumnVerificationImplementationFault indicates that a code path yielding VerifiedRODataColumns has an implementation
// error, leading it to call VerifiedRODataColumnError with a nil error.
errDataColumnVerificationImplementationFault = errors.New("could not verify blob data or create a valid VerifiedROBlob")
)
// VerificationMultiError is a custom error that can be used to access individual verification failures.
type VerificationMultiError struct {
@@ -111,7 +117,7 @@ func newVerificationMultiError(r *results, err error) VerificationMultiError {
// create a value of that type in order to generate an error return value.
func VerifiedROBlobError(err error) (blocks.VerifiedROBlob, error) {
if err == nil {
return blocks.VerifiedROBlob{}, errVerificationImplementationFault
return blocks.VerifiedROBlob{}, errBlobVerificationImplementationFault
}
return blocks.VerifiedROBlob{}, err
}
@@ -120,7 +126,7 @@ func VerifiedROBlobError(err error) (blocks.VerifiedROBlob, error) {
// create a value of that type in order to generate an error return value.
func VerifiedRODataColumnError(err error) (blocks.VerifiedRODataColumn, error) {
if err == nil {
return blocks.VerifiedRODataColumn{}, errVerificationImplementationFault
return blocks.VerifiedRODataColumn{}, errDataColumnVerificationImplementationFault
}
return blocks.VerifiedRODataColumn{}, err
}

View File

@@ -4,6 +4,7 @@ import (
fieldparams "github.com/OffchainLabs/prysm/v6/config/fieldparams"
"github.com/OffchainLabs/prysm/v6/consensus-types/blocks"
ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
"github.com/pkg/errors"
"github.com/spf13/afero"
)
@@ -25,7 +26,8 @@ func VerifiedROBlobFromDisk(fs afero.Fs, root [32]byte, path string) (blocks.Ver
return blocks.NewVerifiedROBlob(ro), nil
}
// VerifiedRODataColumnFromDisk created a verified read-only data column sidecar from disk.
// VerifiedRODataColumnFromDisk creates a verified read-only data column sidecar from disk.
// The file cursor must be positioned at the start of the data column sidecar SSZ data.
func VerifiedRODataColumnFromDisk(file afero.File, root [fieldparams.RootLength]byte, sszEncodedDataColumnSidecarSize uint32) (blocks.VerifiedRODataColumn, error) {
// Read the ssz encoded data column sidecar from the file
sszEncodedDataColumnSidecar := make([]byte, sszEncodedDataColumnSidecarSize)
@@ -34,7 +36,7 @@ func VerifiedRODataColumnFromDisk(file afero.File, root [fieldparams.RootLength]
return VerifiedRODataColumnError(err)
}
if uint32(count) != sszEncodedDataColumnSidecarSize {
return VerifiedRODataColumnError(err)
return VerifiedRODataColumnError(errors.Errorf("read %d bytes while expecting %d", count, sszEncodedDataColumnSidecarSize))
}
// Unmarshal the SSZ encoded data column sidecar.

View File

@@ -0,0 +1,3 @@
### Added
- Delegate sszInfo HashTreeRoot to FastSSZ-generated implementations via SSZObject, enabling roots calculation for generated types while avoiding duplicate logic.

View File

@@ -0,0 +1,2 @@
### Fixed
- Decreased attestation gossip validation batch deadline to 5ms.

View File

@@ -0,0 +1,2 @@
### Fixed
- Delete the genesis state file when --clear-db / --force-clear-db is specified.

View File

@@ -0,0 +1,2 @@
### Fixed
- Correctly advertise (in ENR and beacon API) attestation subnets when using `--subscribe-all-subnets`.

View File

@@ -0,0 +1,2 @@
### Fixed
- Fix `/eth/v1/beacon/blob_sidecars/` beacon API is the fulu fork epoch is set to the far future epoch.

View File

@@ -0,0 +1,2 @@
### Fixed
- `VerifyDataColumnSidecar`: Check if there is no too many commitments.

View File

@@ -0,0 +1,2 @@
### Fixed
- `WithDataColumnRetentionEpochs`: Use `dataColumnRetentionEpoch` instead of `blobColumnRetentionEpoch`.

View File

@@ -0,0 +1,2 @@
### Fixed
- `dataColumnSidecarsByRangeRPCHandler`: Gracefully close the stream if no data to return.

View File

@@ -0,0 +1,3 @@
### Fixed
- `WarmCache`: Return if `extractFileMetadata` fails.
- `extractFileMetadata`: Use filepath.Separator instead of `/` for Windows support.

View File

@@ -0,0 +1,2 @@
### Fixed
- `HasAtLeastOneIndex`: Check the index is not too high.

View File

@@ -0,0 +1,2 @@
### Fixed
- `randomPeer`: Return if the context is cancelled when waiting for peers.

View File

@@ -0,0 +1,2 @@
### Fixed
- Improve error message when the byte count read from disk when reading a data column sidecars is lower than expected. (Mostly, because the file is truncated.)

View File

@@ -0,0 +1,2 @@
### Ignored
- Fix (unreleased) bug where the preallocated slice for KZG Proofs was 48x bigger than it needed to be.

View File

@@ -0,0 +1,3 @@
### Fixed
- reject committee index >= committees_per_slot in unaggregated attestation validation

View File

@@ -0,0 +1,3 @@
### Fixed
- Mark epoch transition correctly on new head events

View File

@@ -0,0 +1,3 @@
### Fixed
- Mark the block as invalid if it has an invalid signature.

View File

@@ -0,0 +1,3 @@
### Ignored
- Remove redundant check for genesis root at startup.

View File

@@ -0,0 +1,3 @@
### Changed
- Improve returning individual message errors from Beacon API.

View File

@@ -0,0 +1,3 @@
### Fixed
- Display error messages from the server verbatim when they are not encoded as `application/json`.

View File

@@ -0,0 +1,7 @@
### Added
- SSZ-QL: Use `fastssz`'s `SizeSSZ` method for calculating the size of `Container` type.
### Changed
- SSZ-QL: Clarify `Size` method with more sophisticated `SSZType`s.

View File

@@ -0,0 +1,3 @@
### Added
- SSZ-QL: Access n-th element in `List`/`Vector`.

View File

@@ -0,0 +1,3 @@
### Fixed
- Fix sync committee subscription to use subnet indices instead of committee indices

View File

@@ -0,0 +1,3 @@
### Changed
- Gracefully handle submit blind block returning 502 errors.

View File

@@ -68,8 +68,13 @@ func BeaconNodeOptions(c *cli.Context) ([]node.Option, error) {
filesystem.WithLayout(c.String(BlobStorageLayout.Name)), // This is validated in the Action func for BlobStorageLayout.
)
dataColumnRetentionEpoch, err := dataColumnRetentionEpoch(c)
if err != nil {
return nil, errors.Wrap(err, "data column retention epoch")
}
dataColumnStorageOption := node.WithDataColumnStorageOptions(
filesystem.WithDataColumnRetentionEpochs(blobRetentionEpoch),
filesystem.WithDataColumnRetentionEpochs(dataColumnRetentionEpoch),
filesystem.WithDataColumnBasePath(dataColumnStoragePath(c)),
)
@@ -116,6 +121,26 @@ func blobRetentionEpoch(cliCtx *cli.Context) (primitives.Epoch, error) {
return re, nil
}
// dataColumnRetentionEpoch returns the spec default MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUEST
// or a user-specified flag overriding this value. If a user-specified override is
// smaller than the spec default, an error will be returned.
func dataColumnRetentionEpoch(cliCtx *cli.Context) (primitives.Epoch, error) {
defaultValue := params.BeaconConfig().MinEpochsForDataColumnSidecarsRequest
if !cliCtx.IsSet(BlobRetentionEpochFlag.Name) {
return defaultValue, nil
}
// We use on purpose the same retention flag for both blobs and data columns.
customValue := primitives.Epoch(cliCtx.Uint64(BlobRetentionEpochFlag.Name))
// Validate the epoch value against the spec default.
if customValue < defaultValue {
return defaultValue, errors.Wrapf(errInvalidBlobRetentionEpochs, "%s=%d, spec=%d", BlobRetentionEpochFlag.Name, customValue, defaultValue)
}
return customValue, nil
}
func init() {
BlobStorageLayout.Action = validateLayoutFlag
}

View File

@@ -61,6 +61,45 @@ func TestConfigureBlobRetentionEpoch(t *testing.T) {
_, err = blobRetentionEpoch(cliCtx)
require.ErrorIs(t, err, errInvalidBlobRetentionEpochs)
}
func TestConfigureDataColumnRetentionEpoch(t *testing.T) {
specValue := params.BeaconConfig().MinEpochsForDataColumnSidecarsRequest
app := cli.App{}
set := flag.NewFlagSet("test", 0)
cliCtx := cli.NewContext(&app, set, nil)
// Test case: Specification value
expected := specValue
actual, err := dataColumnRetentionEpoch(cliCtx)
require.NoError(t, err)
require.Equal(t, expected, actual)
// Manually define the flag in the set, so the following code can use set.Set
set.Uint64(BlobRetentionEpochFlag.Name, 0, "")
// Test case: Input epoch is greater than or equal to specification value.
expected = specValue + 1
err = set.Set(BlobRetentionEpochFlag.Name, fmt.Sprintf("%d", expected))
require.NoError(t, err)
actual, err = dataColumnRetentionEpoch(cliCtx)
require.NoError(t, err)
require.Equal(t, primitives.Epoch(expected), actual)
// Test case: Input epoch is less than specification value.
expected = specValue - 1
err = set.Set(BlobRetentionEpochFlag.Name, fmt.Sprintf("%d", expected))
require.NoError(t, err)
actual, err = dataColumnRetentionEpoch(cliCtx)
require.ErrorIs(t, err, errInvalidBlobRetentionEpochs)
require.Equal(t, specValue, actual)
}
func TestDataColumnStoragePath_FlagSpecified(t *testing.T) {
app := cli.App{}
set := flag.NewFlagSet("test", 0)

View File

@@ -219,8 +219,8 @@ func TestCallWithdrawalEndpoint_Errors(t *testing.T) {
if r.Method == http.MethodPost && r.RequestURI == "/eth/v1/beacon/pool/bls_to_execution_changes" {
w.WriteHeader(400)
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(&server.IndexedVerificationFailureError{
Failures: []*server.IndexedVerificationFailure{
err = json.NewEncoder(w).Encode(&server.IndexedErrorContainer{
Failures: []*server.IndexedError{
{Index: 0, Message: "Could not validate SignedBLSToExecutionChange"},
},
})

View File

@@ -11,6 +11,7 @@ go_library(
"path.go",
"query.go",
"ssz_info.go",
"ssz_object.go",
"ssz_type.go",
"tag_parser.go",
"vector.go",
@@ -23,7 +24,6 @@ go_library(
go_test(
name = "go_default_test",
srcs = [
"analyzer_test.go",
"path_test.go",
"query_test.go",
"tag_parser_test.go",

View File

@@ -10,18 +10,18 @@ import (
const offsetBytes = 4
// AnalyzeObject analyzes given object and returns its SSZ information.
func AnalyzeObject(obj any) (*sszInfo, error) {
value := dereferencePointer(obj)
func AnalyzeObject(obj SSZObject) (*sszInfo, error) {
value := reflect.ValueOf(obj)
info, err := analyzeType(value.Type(), nil)
info, err := analyzeType(value, nil)
if err != nil {
return nil, fmt.Errorf("could not analyze type %s: %w", value.Type().Name(), err)
}
// Populate variable-length information using the actual value.
err = PopulateVariableLengthInfo(info, value.Interface())
err = PopulateVariableLengthInfo(info, value)
if err != nil {
return nil, fmt.Errorf("could not populate variable length info: %w", err)
return nil, fmt.Errorf("could not populate variable length info for type %s: %w", value.Type().Name(), err)
}
return info, nil
@@ -30,13 +30,13 @@ func AnalyzeObject(obj any) (*sszInfo, error) {
// PopulateVariableLengthInfo populates runtime information for SSZ fields of variable-sized types.
// This function updates the sszInfo structure with actual lengths and offsets that can only
// be determined at runtime for variable-sized items like Lists and variable-sized Container fields.
func PopulateVariableLengthInfo(sszInfo *sszInfo, value any) error {
func PopulateVariableLengthInfo(sszInfo *sszInfo, value reflect.Value) error {
if sszInfo == nil {
return errors.New("sszInfo is nil")
}
if value == nil {
return errors.New("value is nil")
if !value.IsValid() {
return errors.New("value is invalid")
}
// Short circuit: If the type is fixed-sized, we don't need to fill in the info.
@@ -56,18 +56,18 @@ func PopulateVariableLengthInfo(sszInfo *sszInfo, value any) error {
return errors.New("listInfo is nil")
}
val := reflect.ValueOf(value)
if val.Kind() != reflect.Slice {
return fmt.Errorf("expected slice for List type, got %v", val.Kind())
if value.Kind() != reflect.Slice {
return fmt.Errorf("expected slice for List type, got %v", value.Kind())
}
length := val.Len()
length := value.Len()
if listInfo.element.isVariable {
listInfo.elementSizes = make([]uint64, 0, length)
// Populate nested variable-sized type element lengths recursively.
for i := range length {
if err := PopulateVariableLengthInfo(listInfo.element, val.Index(i).Interface()); err != nil {
if err := PopulateVariableLengthInfo(listInfo.element, value.Index(i)); err != nil {
return fmt.Errorf("could not populate nested list element at index %d: %w", i, err)
}
listInfo.elementSizes = append(listInfo.elementSizes, listInfo.element.Size())
@@ -91,8 +91,7 @@ func PopulateVariableLengthInfo(sszInfo *sszInfo, value any) error {
return errors.New("bitlistInfo is nil")
}
val := reflect.ValueOf(value)
if err := bitlistInfo.SetLengthFromBytes(val.Bytes()); err != nil {
if err := bitlistInfo.SetLengthFromBytes(value.Bytes()); err != nil {
return fmt.Errorf("could not set bitlist length from bytes: %w", err)
}
@@ -105,11 +104,21 @@ func PopulateVariableLengthInfo(sszInfo *sszInfo, value any) error {
return fmt.Errorf("could not get container info: %w", err)
}
if containerInfo == nil {
return errors.New("containerInfo is nil")
}
// Dereference first in case value is a pointer.
derefValue := dereferencePointer(value)
if derefValue.Kind() != reflect.Struct {
return fmt.Errorf("expected struct for Container type, got %v", derefValue.Kind())
}
// Start with the fixed size of this Container.
currentOffset := sszInfo.FixedSize()
// Reset the pointer to the new value.
sszInfo.source = castToSSZObject(derefValue)
// Start with the end offset of this Container.
currentOffset := containerInfo.fixedOffset
for _, fieldName := range containerInfo.order {
fieldInfo := containerInfo.fields[fieldName]
@@ -125,13 +134,15 @@ func PopulateVariableLengthInfo(sszInfo *sszInfo, value any) error {
// Recursively populate variable-sized fields.
fieldValue := derefValue.FieldByName(fieldInfo.goFieldName)
if err := PopulateVariableLengthInfo(childSszInfo, fieldValue.Interface()); err != nil {
if err := PopulateVariableLengthInfo(childSszInfo, fieldValue); err != nil {
return fmt.Errorf("could not populate from value for field %s: %w", fieldName, err)
}
// Each variable-sized element needs an offset entry.
if childSszInfo.sszType == List {
currentOffset += childSszInfo.listInfo.OffsetBytes()
if listInfo, err := childSszInfo.ListInfo(); err == nil && listInfo != nil {
if listInfo.element.isVariable {
currentOffset += listInfo.Length() * offsetBytes
}
}
// Set the actual offset for variable-sized fields.
@@ -146,66 +157,64 @@ func PopulateVariableLengthInfo(sszInfo *sszInfo, value any) error {
}
}
// analyzeType is an entry point that inspects a reflect.Type and computes its SSZ layout information.
func analyzeType(typ reflect.Type, tag *reflect.StructTag) (*sszInfo, error) {
switch typ.Kind() {
// analyzeType is an entry point that inspects a reflect.Value and computes its SSZ layout information.
func analyzeType(value reflect.Value, tag *reflect.StructTag) (*sszInfo, error) {
switch value.Kind() {
// Basic types (e.g., uintN where N is 8, 16, 32, 64)
// NOTE: uint128 and uint256 are represented as []byte in Go,
// so we handle them as slices. See `analyzeHomogeneousColType`.
case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Bool:
return analyzeBasicType(typ)
return analyzeBasicType(value)
case reflect.Slice:
return analyzeHomogeneousColType(typ, tag)
return analyzeHomogeneousColType(value, tag)
case reflect.Struct:
return analyzeContainerType(typ)
return analyzeContainerType(value)
case reflect.Ptr:
// Dereference pointer types.
return analyzeType(typ.Elem(), tag)
case reflect.Pointer:
derefValue := dereferencePointer(value)
return analyzeType(derefValue, tag)
default:
return nil, fmt.Errorf("unsupported type %v for SSZ calculation", typ.Kind())
return nil, fmt.Errorf("unsupported type %v for SSZ calculation", value.Kind())
}
}
// analyzeBasicType analyzes SSZ basic types (uintN, bool) and returns its info.
func analyzeBasicType(typ reflect.Type) (*sszInfo, error) {
func analyzeBasicType(value reflect.Value) (*sszInfo, error) {
var sszType SSZType
switch value.Kind() {
case reflect.Uint64:
sszType = Uint64
case reflect.Uint32:
sszType = Uint32
case reflect.Uint16:
sszType = Uint16
case reflect.Uint8:
sszType = Uint8
case reflect.Bool:
sszType = Boolean
default:
return nil, fmt.Errorf("unsupported basic type %v for SSZ calculation", value.Kind())
}
sszInfo := &sszInfo{
typ: typ,
sszType: sszType,
typ: value.Type(),
// Every basic type is fixed-size and not variable.
isVariable: false,
}
switch typ.Kind() {
case reflect.Uint64:
sszInfo.sszType = UintN
sszInfo.fixedSize = 8
case reflect.Uint32:
sszInfo.sszType = UintN
sszInfo.fixedSize = 4
case reflect.Uint16:
sszInfo.sszType = UintN
sszInfo.fixedSize = 2
case reflect.Uint8:
sszInfo.sszType = UintN
sszInfo.fixedSize = 1
case reflect.Bool:
sszInfo.sszType = Boolean
sszInfo.fixedSize = 1
default:
return nil, fmt.Errorf("unsupported basic type %v for SSZ calculation", typ.Kind())
}
return sszInfo, nil
}
// analyzeHomogeneousColType analyzes homogeneous collection types (e.g., List, Vector, Bitlist, Bitvector) and returns its SSZ info.
func analyzeHomogeneousColType(typ reflect.Type, tag *reflect.StructTag) (*sszInfo, error) {
if typ.Kind() != reflect.Slice {
return nil, fmt.Errorf("can only analyze slice types, got %v", typ.Kind())
func analyzeHomogeneousColType(value reflect.Value, tag *reflect.StructTag) (*sszInfo, error) {
if value.Kind() != reflect.Slice {
return nil, fmt.Errorf("can only analyze slice types, got %v", value.Kind())
}
// Parse the first dimension from the tag and get remaining tag for element
@@ -217,8 +226,12 @@ func analyzeHomogeneousColType(typ reflect.Type, tag *reflect.StructTag) (*sszIn
return nil, errors.New("ssz tag is required for slice types")
}
// NOTE: Elem() won't panic because value is guaranteed to be a slice here.
elementType := value.Type().Elem()
// Analyze element type with remaining dimensions
elementInfo, err := analyzeType(typ.Elem(), remainingTag)
// Note that it is enough to analyze by a zero value,
// as the actual value with variable-sized type will be populated later.
elementInfo, err := analyzeType(reflect.New(elementType), remainingTag)
if err != nil {
return nil, fmt.Errorf("could not analyze element type for homogeneous collection: %w", err)
}
@@ -230,7 +243,7 @@ func analyzeHomogeneousColType(typ reflect.Type, tag *reflect.StructTag) (*sszIn
return nil, fmt.Errorf("could not get list limit: %w", err)
}
return analyzeListType(typ, elementInfo, limit, sszDimension.isBitfield)
return analyzeListType(value, elementInfo, limit, sszDimension.isBitfield)
}
// 2. Handle Vector/Bitvector type
@@ -240,7 +253,7 @@ func analyzeHomogeneousColType(typ reflect.Type, tag *reflect.StructTag) (*sszIn
return nil, fmt.Errorf("could not get vector length: %w", err)
}
return analyzeVectorType(typ, elementInfo, length, sszDimension.isBitfield)
return analyzeVectorType(value, elementInfo, length, sszDimension.isBitfield)
}
// Parsing ssz tag doesn't provide enough information to determine the collection type,
@@ -249,13 +262,12 @@ func analyzeHomogeneousColType(typ reflect.Type, tag *reflect.StructTag) (*sszIn
}
// analyzeListType analyzes SSZ List/Bitlist type and returns its SSZ info.
func analyzeListType(typ reflect.Type, elementInfo *sszInfo, limit uint64, isBitfield bool) (*sszInfo, error) {
func analyzeListType(value reflect.Value, elementInfo *sszInfo, limit uint64, isBitfield bool) (*sszInfo, error) {
if isBitfield {
return &sszInfo{
sszType: Bitlist,
typ: typ,
typ: value.Type(),
fixedSize: offsetBytes,
isVariable: true,
bitlistInfo: &bitlistInfo{
@@ -270,9 +282,8 @@ func analyzeListType(typ reflect.Type, elementInfo *sszInfo, limit uint64, isBit
return &sszInfo{
sszType: List,
typ: typ,
typ: value.Type(),
fixedSize: offsetBytes,
isVariable: true,
listInfo: &listInfo{
@@ -283,14 +294,12 @@ func analyzeListType(typ reflect.Type, elementInfo *sszInfo, limit uint64, isBit
}
// analyzeVectorType analyzes SSZ Vector/Bitvector type and returns its SSZ info.
func analyzeVectorType(typ reflect.Type, elementInfo *sszInfo, length uint64, isBitfield bool) (*sszInfo, error) {
func analyzeVectorType(value reflect.Value, elementInfo *sszInfo, length uint64, isBitfield bool) (*sszInfo, error) {
if isBitfield {
return &sszInfo{
sszType: Bitvector,
typ: typ,
typ: value.Type(),
// Size in bytes
fixedSize: length,
isVariable: false,
bitvectorInfo: &bitvectorInfo{
@@ -311,9 +320,8 @@ func analyzeVectorType(typ reflect.Type, elementInfo *sszInfo, length uint64, is
return &sszInfo{
sszType: Vector,
typ: typ,
typ: value.Type(),
fixedSize: length * elementInfo.Size(),
isVariable: false,
vectorInfo: &vectorInfo{
@@ -324,44 +332,36 @@ func analyzeVectorType(typ reflect.Type, elementInfo *sszInfo, length uint64, is
}
// analyzeContainerType analyzes SSZ Container type and returns its SSZ info.
func analyzeContainerType(typ reflect.Type) (*sszInfo, error) {
if typ.Kind() != reflect.Struct {
return nil, fmt.Errorf("can only analyze struct types, got %v", typ.Kind())
func analyzeContainerType(value reflect.Value) (*sszInfo, error) {
if value.Kind() != reflect.Struct {
return nil, fmt.Errorf("can only analyze struct types, got %v", value.Kind())
}
containerTyp := value.Type()
fields := make(map[string]*fieldInfo)
order := make([]string, 0, typ.NumField())
order := make([]string, 0)
sszInfo := &sszInfo{
sszType: Container,
typ: typ,
}
isVariable := false
var currentOffset uint64
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
for i := 0; i < value.NumField(); i++ {
structFieldInfo := containerTyp.Field(i)
// Protobuf-generated structs contain private fields we must skip.
// e.g., state, sizeCache, unknownFields, etc.
if !field.IsExported() {
if !structFieldInfo.IsExported() {
continue
}
// The JSON tag contains the field name in the first part.
// e.g., "attesting_indices,omitempty" -> "attesting_indices".
jsonTag := field.Tag.Get("json")
if jsonTag == "" {
return nil, fmt.Errorf("field %s has no JSON tag", field.Name)
}
// NOTE: `fieldName` is a string with `snake_case` format (following consensus specs).
fieldName := strings.Split(jsonTag, ",")[0]
if fieldName == "" {
return nil, fmt.Errorf("field %s has an empty JSON tag", field.Name)
tag := structFieldInfo.Tag
goFieldName := structFieldInfo.Name
fieldName, err := parseFieldNameFromTag(tag)
if err != nil {
return nil, fmt.Errorf("could not parse field name from tag for field %s: %w", goFieldName, err)
}
// Analyze each field so that we can complete full SSZ information.
info, err := analyzeType(field.Type, &field.Tag)
info, err := analyzeType(value.Field(i), &tag)
if err != nil {
return nil, fmt.Errorf("could not analyze type for field %s: %w", fieldName, err)
}
@@ -370,7 +370,7 @@ func analyzeContainerType(typ reflect.Type) (*sszInfo, error) {
fields[fieldName] = &fieldInfo{
sszInfo: info,
offset: currentOffset,
goFieldName: field.Name,
goFieldName: goFieldName,
}
// Persist order
order = append(order, fieldName)
@@ -379,34 +379,87 @@ func analyzeContainerType(typ reflect.Type) (*sszInfo, error) {
if info.isVariable {
// If one of the fields is variable-sized,
// the entire struct is considered variable-sized.
sszInfo.isVariable = true
isVariable = true
currentOffset += offsetBytes
} else {
currentOffset += info.fixedSize
currentOffset += info.Size()
}
}
sszInfo.fixedSize = currentOffset
sszInfo.containerInfo = &containerInfo{
fields: fields,
order: order,
}
return &sszInfo{
sszType: Container,
typ: containerTyp,
source: castToSSZObject(value),
return sszInfo, nil
isVariable: isVariable,
containerInfo: &containerInfo{
fields: fields,
order: order,
fixedOffset: currentOffset,
},
}, nil
}
// dereferencePointer dereferences a pointer to get the underlying value using reflection.
func dereferencePointer(obj any) reflect.Value {
value := reflect.ValueOf(obj)
if value.Kind() == reflect.Ptr {
func dereferencePointer(value reflect.Value) reflect.Value {
derefValue := value
if value.IsValid() && value.Kind() == reflect.Pointer {
if value.IsNil() {
// If we encounter a nil pointer before the end of the path, we can still proceed
// by analyzing the type, not the value.
value = reflect.New(value.Type().Elem()).Elem()
// Create a zero value if the pointer is nil.
derefValue = reflect.New(value.Type().Elem()).Elem()
} else {
value = value.Elem()
derefValue = value.Elem()
}
}
return value
return derefValue
}
// castToSSZObject attempts to cast a reflect.Value to the SSZObject interface.
// If failed, it returns nil.
func castToSSZObject(value reflect.Value) SSZObject {
if !value.IsValid() {
return nil
}
// SSZObject is only implemented by struct types.
if value.Kind() != reflect.Struct {
return nil
}
// To cast to SSZObject, we need the addressable value.
if !value.CanAddr() {
return nil
}
if sszObj, ok := value.Addr().Interface().(SSZObject); ok {
return sszObj
}
return nil
}
// parseFieldNameFromTag extracts the field name (`snake_case` format)
// from a struct tag by looking for the json tag.
// The JSON tag contains the field name in the first part.
// e.g., "attesting_indices,omitempty" -> "attesting_indices".
func parseFieldNameFromTag(tag reflect.StructTag) (string, error) {
jsonTag := tag.Get("json")
if jsonTag == "" {
return "", errors.New("no JSON tag found")
}
substrings := strings.Split(jsonTag, ",")
if len(substrings) == 0 {
return "", errors.New("invalid JSON tag format")
}
fieldName := strings.TrimSpace(substrings[0])
if fieldName == "" {
return "", errors.New("empty field name")
}
return fieldName, nil
}

View File

@@ -1,17 +0,0 @@
package query_test
import (
"testing"
"github.com/OffchainLabs/prysm/v6/encoding/ssz/query"
sszquerypb "github.com/OffchainLabs/prysm/v6/proto/ssz_query"
"github.com/OffchainLabs/prysm/v6/testing/require"
)
func TestAnalyzeSSZInfo(t *testing.T) {
info, err := query.AnalyzeObject(&sszquerypb.FixedTestContainer{})
require.NoError(t, err)
require.NotNil(t, info, "Expected non-nil SSZ info")
require.Equal(t, uint64(565), info.FixedSize())
}

View File

@@ -13,3 +13,12 @@ func (v *bitvectorInfo) Length() uint64 {
return v.length
}
func (v *bitvectorInfo) Size() uint64 {
if v == nil {
return 0
}
// Size in bytes.
return v.length / 8
}

View File

@@ -3,9 +3,11 @@ package query
// containerInfo has
// 1. fields: a field map that maps a field's JSON name to its sszInfo for nested Containers
// 2. order: a list of field names in the order they should be serialized
// 3. fixedOffset: the total size of the fixed part of the container
type containerInfo struct {
fields map[string]*fieldInfo
order []string
fields map[string]*fieldInfo
order []string
fixedOffset uint64
}
type fieldInfo struct {

View File

@@ -71,17 +71,3 @@ func (l *listInfo) Size() uint64 {
}
return totalSize
}
// OffsetBytes returns the total number of offset bytes used for the list elements.
// Each variable-sized element uses 4 bytes to store its offset.
func (l *listInfo) OffsetBytes() uint64 {
if l == nil {
return 0
}
if !l.element.isVariable {
return 0
}
return offsetBytes * l.length
}

View File

@@ -2,12 +2,16 @@ package query
import (
"errors"
"fmt"
"strconv"
"strings"
)
// PathElement represents a single element in a path.
type PathElement struct {
Name string
// [Optional] Index for List/Vector elements
Index *uint64
}
func ParsePath(rawPath string) ([]PathElement, error) {
@@ -24,7 +28,34 @@ func ParsePath(rawPath string) ([]PathElement, error) {
var path []PathElement
for _, elem := range rawElements {
path = append(path, PathElement{Name: elem})
if elem == "" {
return nil, errors.New("invalid path: consecutive dots or trailing dot")
}
fieldName := elem
var index *uint64
// Check for index notation, e.g., "field[0]"
if strings.Contains(elem, "[") {
parts := strings.SplitN(elem, "[", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("invalid index notation in path element %s", elem)
}
fieldName = parts[0]
indexPart := strings.TrimSuffix(parts[1], "]")
if indexPart == "" {
return nil, errors.New("index cannot be empty")
}
indexValue, err := strconv.ParseUint(indexPart, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid index in path element %s: %w", elem, err)
}
index = &indexValue
}
path = append(path, PathElement{Name: fieldName, Index: index})
}
return path, nil

View File

@@ -19,7 +19,7 @@ func CalculateOffsetAndLength(sszInfo *sszInfo, path []PathElement) (*sszInfo, u
walk := sszInfo
offset := uint64(0)
for _, elem := range path {
for pathIndex, elem := range path {
containerInfo, err := walk.ContainerInfo()
if err != nil {
return nil, 0, 0, fmt.Errorf("could not get field infos: %w", err)
@@ -32,6 +32,51 @@ func CalculateOffsetAndLength(sszInfo *sszInfo, path []PathElement) (*sszInfo, u
offset += fieldInfo.offset
walk = fieldInfo.sszInfo
// Check for accessing List/Vector elements by index
if elem.Index != nil {
switch walk.sszType {
case List:
index := *elem.Index
listInfo := walk.listInfo
if index >= listInfo.length {
return nil, 0, 0, fmt.Errorf("index %d out of bounds for field %s with size %d", index, elem.Name, listInfo.length)
}
walk = listInfo.element
if walk.isVariable {
// Cumulative sum of sizes of previous elements to get the offset.
for i := range index {
offset += listInfo.elementSizes[i]
}
// NOTE: When populating recursively, the shared element template is updated for each
// list item, causing it to retain the size information of the last processed element.
// This wouldn't be an issue if this is in the middle of the path, as the walk would be updated
// to the next field's sszInfo, which would have the correct size information.
// However, if this is the last element in the path, we need to ensure we return the correct size
// for the indexed element. Hence, we return the size from elementSizes.
if pathIndex == len(path)-1 {
return walk, offset, listInfo.elementSizes[index], nil
}
} else {
offset += index * listInfo.element.Size()
}
case Vector:
index := *elem.Index
vectorInfo := walk.vectorInfo
if index >= vectorInfo.length {
return nil, 0, 0, fmt.Errorf("index %d out of bounds for field %s with size %d", index, elem.Name, vectorInfo.length)
}
offset += index * vectorInfo.element.Size()
walk = vectorInfo.element
default:
return nil, 0, 0, fmt.Errorf("field %s of type %s does not support index access", elem.Name, walk.sszType)
}
}
}
return walk, offset, walk.Size(), nil

View File

@@ -11,6 +11,34 @@ import (
"github.com/prysmaticlabs/go-bitfield"
)
func TestSize(t *testing.T) {
tests := []struct {
name string
obj query.SSZObject
expectedSize uint64
}{
{
name: "FixedTestContainer",
obj: &sszquerypb.FixedTestContainer{},
expectedSize: 565,
},
{
name: "VariableTestContainer",
obj: &sszquerypb.VariableTestContainer{},
expectedSize: 132,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
info, err := query.AnalyzeObject(tt.obj)
require.NoError(t, err)
require.NotNil(t, info)
require.Equal(t, tt.expectedSize, info.Size())
})
}
}
func TestCalculateOffsetAndLength(t *testing.T) {
type testCase struct {
name string
@@ -74,6 +102,19 @@ func TestCalculateOffsetAndLength(t *testing.T) {
expectedOffset: 85,
expectedLength: 192, // 24 * 8 bytes
},
// Accessing an element in the vector
{
name: "vector field (0th element)",
path: ".vector_field[0]",
expectedOffset: 85,
expectedLength: 8,
},
{
name: "vector field (10th element)",
path: ".vector_field[10]",
expectedOffset: 165,
expectedLength: 8,
},
// 2D bytes field
{
name: "two_dimension_bytes_field",
@@ -81,6 +122,13 @@ func TestCalculateOffsetAndLength(t *testing.T) {
expectedOffset: 277,
expectedLength: 160, // 5 * 32 bytes
},
// Accessing an element in the 2D bytes field
{
name: "two_dimension_bytes_field (1st element)",
path: ".two_dimension_bytes_field[1]",
expectedOffset: 309,
expectedLength: 32,
},
// Bitvector fields
{
name: "bitvector64_field",
@@ -133,26 +181,53 @@ func TestCalculateOffsetAndLength(t *testing.T) {
{
name: "field_list_uint64",
path: ".field_list_uint64",
expectedOffset: 112, // First part of variable-sized type.
expectedOffset: 116, // First part of variable-sized type.
expectedLength: 40, // 5 elements * uint64 (8 bytes each)
},
// Accessing an element in the list
{
name: "field_list_uint64 (2nd element)",
path: ".field_list_uint64[2]",
expectedOffset: 132,
expectedLength: 8,
},
{
name: "field_list_container",
path: ".field_list_container",
expectedOffset: 152, // Second part of variable-sized type.
expectedOffset: 156, // Second part of variable-sized type.
expectedLength: 120, // 3 elements * FixedNestedContainer (40 bytes each)
},
// Accessing an element in the list of containers
{
name: "field_list_container (1st element)",
path: ".field_list_container[1]",
expectedOffset: 196,
expectedLength: 40,
},
{
name: "field_list_bytes32",
path: ".field_list_bytes32",
expectedOffset: 272,
expectedOffset: 276,
expectedLength: 96, // 3 elements * 32 bytes each
},
// Accessing an element in the list of bytes32
{
name: "field_list_bytes32 (0th element)",
path: ".field_list_bytes32[0]",
expectedOffset: 276,
expectedLength: 32,
},
{
name: "field_list_bytes32 (2nd element)",
path: ".field_list_bytes32[2]",
expectedOffset: 340,
expectedLength: 32,
},
// Nested paths
{
name: "nested",
path: ".nested",
expectedOffset: 368,
expectedOffset: 372,
// Calculated with:
// - Value1: 8 bytes
// - field_list_uint64 offset: 4 bytes
@@ -166,40 +241,85 @@ func TestCalculateOffsetAndLength(t *testing.T) {
{
name: "nested.value1",
path: ".nested.value1",
expectedOffset: 368,
expectedOffset: 372,
expectedLength: 8,
},
{
name: "nested.field_list_uint64",
path: ".nested.field_list_uint64",
expectedOffset: 384,
expectedOffset: 388,
expectedLength: 40,
},
{
name: "nested.field_list_uint64 (3rd element)",
path: ".nested.field_list_uint64[3]",
expectedOffset: 412,
expectedLength: 8,
},
{
name: "nested.nested_list_field",
path: ".nested.nested_list_field",
expectedOffset: 436,
expectedOffset: 440,
expectedLength: 99,
},
// Accessing an element in the nested list of bytes
{
name: "nested.nested_list_field (1st element)",
path: ".nested.nested_list_field[1]",
expectedOffset: 472,
expectedLength: 33,
},
{
name: "nested.nested_list_field (2nd element)",
path: ".nested.nested_list_field[2]",
expectedOffset: 505,
expectedLength: 34,
},
// Variable list of variable-sized containers
{
name: "variable_container_list",
path: ".variable_container_list",
expectedOffset: 547,
expectedLength: 604,
},
// Bitlist field
{
name: "bitlist_field",
path: ".bitlist_field",
expectedOffset: 535,
expectedOffset: 1151,
expectedLength: 33, // 32 bytes + 1 byte for length delimiter
},
// 2D bytes field
{
name: "nested_list_field",
path: ".nested_list_field",
expectedOffset: 580,
expectedOffset: 1196,
expectedLength: 99,
},
// Accessing an element in the list of nested bytes
{
name: "nested_list_field (0th element)",
path: ".nested_list_field[0]",
expectedOffset: 1196,
expectedLength: 32,
},
{
name: "nested_list_field (1st element)",
path: ".nested_list_field[1]",
expectedOffset: 1228,
expectedLength: 33,
},
{
name: "nested_list_field (2nd element)",
path: ".nested_list_field[2]",
expectedOffset: 1261,
expectedLength: 34,
},
// Fixed trailing field
{
name: "trailing_field",
path: ".trailing_field",
expectedOffset: 56, // After leading_field + 6 offset pointers
expectedOffset: 60, // After leading_field + 7 offset pointers
expectedLength: 56,
},
}
@@ -224,6 +344,56 @@ func TestCalculateOffsetAndLength(t *testing.T) {
})
}
func TestHashTreeRoot(t *testing.T) {
tests := []struct {
name string
obj query.SSZObject
}{
{
name: "FixedNestedContainer",
obj: &sszquerypb.FixedNestedContainer{
Value1: 42,
Value2: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
},
},
{
name: "FixedTestContainer",
obj: createFixedTestContainer(),
},
{
name: "VariableNestedContainer",
obj: &sszquerypb.VariableNestedContainer{
Value1: 84,
FieldListUint64: []uint64{1, 2, 3, 4, 5},
NestedListField: [][]byte{
{0x0a, 0x0b, 0x0c},
{0x1a, 0x1b, 0x1c, 0x1d},
},
},
},
{
name: "VariableTestContainer",
obj: createVariableTestContainer(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Analyze the object to get its sszInfo
info, err := query.AnalyzeObject(tt.obj)
require.NoError(t, err)
require.NotNil(t, info, "Expected non-nil SSZ info")
// Call HashTreeRoot on the sszInfo and compare results
hashTreeRoot, err := info.HashTreeRoot()
require.NoError(t, err, "HashTreeRoot should not return an error")
expectedHashTreeRoot, err := tt.obj.HashTreeRoot()
require.NoError(t, err, "HashTreeRoot on original object should not return an error")
require.Equal(t, expectedHashTreeRoot, hashTreeRoot, "HashTreeRoot from sszInfo should match original object's HashTreeRoot")
})
}
}
func TestRoundTripSszInfo(t *testing.T) {
specs := []testutil.TestSpec{
getFixedTestContainerSpec(),
@@ -302,7 +472,7 @@ func getFixedTestContainerSpec() testutil.TestSpec {
return testutil.TestSpec{
Name: "FixedTestContainer",
Type: sszquerypb.FixedTestContainer{},
Type: &sszquerypb.FixedTestContainer{},
Instance: testContainer,
PathTests: []testutil.PathTest{
// Basic types
@@ -341,11 +511,27 @@ func getFixedTestContainerSpec() testutil.TestSpec {
Path: ".vector_field",
Expected: testContainer.VectorField,
},
{
Path: ".vector_field[0]",
Expected: testContainer.VectorField[0],
},
{
Path: ".vector_field[10]",
Expected: testContainer.VectorField[10],
},
// 2D bytes field
{
Path: ".two_dimension_bytes_field",
Expected: testContainer.TwoDimensionBytesField,
},
{
Path: ".two_dimension_bytes_field[0]",
Expected: testContainer.TwoDimensionBytesField[0],
},
{
Path: ".two_dimension_bytes_field[1]",
Expected: testContainer.TwoDimensionBytesField[1],
},
// Bitvector fields
{
Path: ".bitvector64_field",
@@ -403,6 +589,28 @@ func createVariableTestContainer() *sszquerypb.VariableTestContainer {
}
}
// Two VariableOuterContainer elements, each with two VariableInnerContainer elements
variableContainerList := make([]*sszquerypb.VariableOuterContainer, 2)
for i := range variableContainerList {
// Inner1: 8 + 4 + 4 + (8*3) + (4*3) + 99 = 151 bytes
inner1 := &sszquerypb.VariableNestedContainer{
Value1: 42,
FieldListUint64: []uint64{uint64(i), uint64(i + 1), uint64(i + 2)},
NestedListField: nestedListField,
}
// Inner2: 8 + 4 + 4 + (8*2) + (4*3) + 99 = 143 bytes
inner2 := &sszquerypb.VariableNestedContainer{
Value1: 84,
FieldListUint64: []uint64{uint64(i + 3), uint64(i + 4)},
NestedListField: nestedListField,
}
// (4*2) + 151 + 143 = 302 bytes per VariableOuterContainer
variableContainerList[i] = &sszquerypb.VariableOuterContainer{
Inner_1: inner1,
Inner_2: inner2,
}
}
return &sszquerypb.VariableTestContainer{
// Fixed leading field
LeadingField: leadingField,
@@ -423,6 +631,9 @@ func createVariableTestContainer() *sszquerypb.VariableTestContainer {
NestedListField: nestedListField,
},
// Variable list of variable-sized containers
VariableContainerList: variableContainerList,
// Bitlist field
BitlistField: bitlistField,
@@ -439,7 +650,7 @@ func getVariableTestContainerSpec() testutil.TestSpec {
return testutil.TestSpec{
Name: "VariableTestContainer",
Type: sszquerypb.VariableTestContainer{},
Type: &sszquerypb.VariableTestContainer{},
Instance: testContainer,
PathTests: []testutil.PathTest{
// Fixed leading field
@@ -452,11 +663,24 @@ func getVariableTestContainerSpec() testutil.TestSpec {
Path: ".field_list_uint64",
Expected: testContainer.FieldListUint64,
},
{
Path: ".field_list_uint64[2]",
Expected: testContainer.FieldListUint64[2],
},
// Variable-size list of (fixed-size) containers
{
Path: ".field_list_container",
Expected: testContainer.FieldListContainer,
},
// Accessing an element in the list of containers
{
Path: ".field_list_container[0]",
Expected: testContainer.FieldListContainer[0],
},
{
Path: ".field_list_container[1]",
Expected: testContainer.FieldListContainer[1],
},
// Variable-size list of bytes32
{
Path: ".field_list_bytes32",
@@ -475,10 +699,55 @@ func getVariableTestContainerSpec() testutil.TestSpec {
Path: ".nested.field_list_uint64",
Expected: testContainer.Nested.FieldListUint64,
},
{
Path: ".nested.field_list_uint64[3]",
Expected: testContainer.Nested.FieldListUint64[3],
},
{
Path: ".nested.nested_list_field",
Expected: testContainer.Nested.NestedListField,
},
{
Path: ".nested.nested_list_field[0]",
Expected: testContainer.Nested.NestedListField[0],
},
{
Path: ".nested.nested_list_field[1]",
Expected: testContainer.Nested.NestedListField[1],
},
{
Path: ".nested.nested_list_field[2]",
Expected: testContainer.Nested.NestedListField[2],
},
// Variable list of variable-sized containers
{
Path: ".variable_container_list",
Expected: testContainer.VariableContainerList,
},
{
Path: ".variable_container_list[0]",
Expected: testContainer.VariableContainerList[0],
},
{
Path: ".variable_container_list[0].inner_1.field_list_uint64[1]",
Expected: testContainer.VariableContainerList[0].Inner_1.FieldListUint64[1],
},
{
Path: ".variable_container_list[0].inner_2.field_list_uint64[1]",
Expected: testContainer.VariableContainerList[0].Inner_2.FieldListUint64[1],
},
{
Path: ".variable_container_list[1]",
Expected: testContainer.VariableContainerList[1],
},
{
Path: ".variable_container_list[1].inner_1.field_list_uint64[1]",
Expected: testContainer.VariableContainerList[1].Inner_1.FieldListUint64[1],
},
{
Path: ".variable_container_list[1].inner_2.field_list_uint64[1]",
Expected: testContainer.VariableContainerList[1].Inner_2.FieldListUint64[1],
},
// Bitlist field
{
Path: ".bitlist_field",
@@ -489,6 +758,18 @@ func getVariableTestContainerSpec() testutil.TestSpec {
Path: ".nested_list_field",
Expected: testContainer.NestedListField,
},
{
Path: ".nested_list_field[0]",
Expected: testContainer.NestedListField[0],
},
{
Path: ".nested_list_field[1]",
Expected: testContainer.NestedListField[1],
},
{
Path: ".nested_list_field[2]",
Expected: testContainer.NestedListField[2],
},
// Fixed trailing field
{
Path: ".trailing_field",

View File

@@ -13,11 +13,11 @@ type sszInfo struct {
sszType SSZType
// Type in Go. Need this for unmarshaling.
typ reflect.Type
// Original object being analyzed
source SSZObject
// isVariable is true if the struct contains any variable-size fields.
isVariable bool
// fixedSize is the total size of the struct's fixed part.
fixedSize uint64
// For Container types.
containerInfo *containerInfo
@@ -35,46 +35,38 @@ type sszInfo struct {
bitvectorInfo *bitvectorInfo
}
func (info *sszInfo) FixedSize() uint64 {
if info == nil {
return 0
}
return info.fixedSize
}
func (info *sszInfo) Size() uint64 {
if info == nil {
return 0
}
// Easy case: if the type is not variable, we can return the fixed size.
if !info.isVariable {
return info.fixedSize
}
switch info.sszType {
case Uint8:
return 1
case Uint16:
return 2
case Uint32:
return 4
case Uint64:
return 8
case Boolean:
return 1
case Container:
// Using existing API if the pointer is available.
if info.source != nil {
return uint64(info.source.SizeSSZ())
}
return 0
case Vector:
return info.vectorInfo.Size()
case List:
return info.listInfo.Size()
case Bitvector:
return info.bitvectorInfo.Size()
case Bitlist:
return info.bitlistInfo.Size()
case Container:
size := info.fixedSize
for _, fieldInfo := range info.containerInfo.fields {
if !fieldInfo.sszInfo.isVariable {
continue
}
// Include offset bytes inside nested lists.
if fieldInfo.sszInfo.sszType == List {
size += fieldInfo.sszInfo.listInfo.OffsetBytes()
}
size += fieldInfo.sszInfo.Size()
}
return size
default:
return 0
}
@@ -191,7 +183,7 @@ func printRecursive(info *sszInfo, builder *strings.Builder, prefix string) {
switch info.sszType {
case Container:
builder.WriteString(fmt.Sprintf("%s (%s / fixed size: %d, total size: %d)\n", info, sizeDesc, info.FixedSize(), info.Size()))
builder.WriteString(fmt.Sprintf("%s (%s / size: %d)\n", info, sizeDesc, info.Size()))
for i, key := range info.containerInfo.order {
connector := "├─"

View File

@@ -0,0 +1,23 @@
package query
import "errors"
type SSZObject interface {
HashTreeRoot() ([32]byte, error)
SizeSSZ() int
}
// HashTreeRoot calls the HashTreeRoot method on the stored interface if it implements SSZObject.
// Returns the 32-byte hash tree root or an error if the interface doesn't support hashing.
func (info *sszInfo) HashTreeRoot() ([32]byte, error) {
if info == nil {
return [32]byte{}, errors.New("sszInfo is nil")
}
if info.source == nil {
return [32]byte{}, errors.New("sszInfo.source is nil")
}
// Check if the value implements the Hashable interface
return info.source.HashTreeRoot()
}

View File

@@ -9,8 +9,10 @@ type SSZType int
// SSZ type constants.
const (
// Basic types
UintN SSZType = iota
Byte
Uint8 SSZType = iota
Uint16
Uint32
Uint64
Boolean
// Composite types
@@ -27,10 +29,14 @@ const (
func (t SSZType) String() string {
switch t {
case UintN:
return "UintN"
case Byte:
return "Byte"
case Uint8:
return "Uint8"
case Uint16:
return "Uint16"
case Uint32:
return "Uint32"
case Uint64:
return "Uint64"
case Boolean:
return "Boolean"
case Container:

View File

@@ -1,6 +1,7 @@
package testutil
import (
"reflect"
"testing"
"github.com/OffchainLabs/prysm/v6/encoding/ssz/query"
@@ -14,7 +15,7 @@ func RunStructTest(t *testing.T, spec TestSpec) {
require.NoError(t, err)
testInstance := spec.Instance
err = query.PopulateVariableLengthInfo(info, testInstance)
err = query.PopulateVariableLengthInfo(info, reflect.ValueOf(testInstance))
require.NoError(t, err)
marshaller, ok := testInstance.(ssz.Marshaler)

View File

@@ -1,5 +1,7 @@
package testutil
import "github.com/OffchainLabs/prysm/v6/encoding/ssz/query"
type PathTest struct {
Path string
Expected any
@@ -7,7 +9,7 @@ type PathTest struct {
type TestSpec struct {
Name string
Type any
Instance any
Type query.SSZObject
Instance query.SSZObject
PathTests []PathTest
}

View File

@@ -25,3 +25,15 @@ func (v *vectorInfo) Element() (*sszInfo, error) {
return v.element, nil
}
func (v *vectorInfo) Size() uint64 {
if v == nil {
return 0
}
if v.element == nil {
return 0
}
return v.length * v.element.Size()
}

View File

@@ -23,7 +23,7 @@ func Initialize(ctx context.Context, dir string, providers ...Provider) error {
setPkgVar(emb, true)
return nil
}
gd, err := findGenesisFile(dir)
gd, err := FindStateFile(dir)
if err == nil {
setPkgVar(gd, true)
return nil
@@ -65,7 +65,8 @@ func newGenesisData(st state.BeaconState, dir string) (GenesisData, error) {
}, nil
}
func findGenesisFile(dir string) (GenesisData, error) {
// FindStateFile searches for a valid genesis state file in the specified directory.
func FindStateFile(dir string) (GenesisData, error) {
if dir == "" {
return GenesisData{}, ErrFilePathUnset
}

View File

@@ -100,7 +100,8 @@ type GenesisData struct {
initialized bool
}
func (d GenesisData) filePath() string {
// FilePath returns the full path to the genesis state file.
func (d GenesisData) FilePath() string {
parts := [3]string{}
parts[genesisPart] = "genesis"
parts[timePart] = strconv.FormatInt(d.Time.Unix(), 10)
@@ -115,7 +116,7 @@ func persist(d GenesisData) error {
if d.FileDir == "" {
return ErrFilePathUnset
}
fpath := d.filePath()
fpath := d.FilePath()
sb, err := d.State.MarshalSSZ()
if err != nil {
return errors.Wrap(err, "marshal ssz")
@@ -144,7 +145,7 @@ func loadState() (state.BeaconState, error) {
stateMu.Lock()
defer stateMu.Unlock()
s, err := stateFromFile(data.filePath())
s, err := stateFromFile(data.FilePath())
if err != nil {
return nil, errors.Wrapf(err, "InitializeFromProtoUnsafePhase0")
}

View File

@@ -251,23 +251,76 @@ func (x *VariableNestedContainer) GetNestedListField() [][]byte {
return nil
}
type VariableOuterContainer struct {
state protoimpl.MessageState `protogen:"open.v1"`
Inner_1 *VariableNestedContainer `protobuf:"bytes,1,opt,name=inner_1,json=inner1,proto3" json:"inner_1,omitempty"`
Inner_2 *VariableNestedContainer `protobuf:"bytes,2,opt,name=inner_2,json=inner2,proto3" json:"inner_2,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *VariableOuterContainer) Reset() {
*x = VariableOuterContainer{}
mi := &file_proto_ssz_query_ssz_query_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *VariableOuterContainer) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*VariableOuterContainer) ProtoMessage() {}
func (x *VariableOuterContainer) ProtoReflect() protoreflect.Message {
mi := &file_proto_ssz_query_ssz_query_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use VariableOuterContainer.ProtoReflect.Descriptor instead.
func (*VariableOuterContainer) Descriptor() ([]byte, []int) {
return file_proto_ssz_query_ssz_query_proto_rawDescGZIP(), []int{3}
}
func (x *VariableOuterContainer) GetInner_1() *VariableNestedContainer {
if x != nil {
return x.Inner_1
}
return nil
}
func (x *VariableOuterContainer) GetInner_2() *VariableNestedContainer {
if x != nil {
return x.Inner_2
}
return nil
}
type VariableTestContainer struct {
state protoimpl.MessageState `protogen:"open.v1"`
LeadingField []byte `protobuf:"bytes,1,opt,name=leading_field,json=leadingField,proto3" json:"leading_field,omitempty" ssz-size:"32"`
FieldListUint64 []uint64 `protobuf:"varint,2,rep,packed,name=field_list_uint64,json=fieldListUint64,proto3" json:"field_list_uint64,omitempty" ssz-max:"2048"`
FieldListContainer []*FixedNestedContainer `protobuf:"bytes,3,rep,name=field_list_container,json=fieldListContainer,proto3" json:"field_list_container,omitempty" ssz-max:"128"`
FieldListBytes32 [][]byte `protobuf:"bytes,4,rep,name=field_list_bytes32,json=fieldListBytes32,proto3" json:"field_list_bytes32,omitempty" ssz-max:"100" ssz-size:"?,32"`
Nested *VariableNestedContainer `protobuf:"bytes,5,opt,name=nested,proto3" json:"nested,omitempty"`
BitlistField github_com_prysmaticlabs_go_bitfield.Bitlist `protobuf:"bytes,6,opt,name=bitlist_field,json=bitlistField,proto3" json:"bitlist_field,omitempty" cast-type:"github.com/prysmaticlabs/go-bitfield.Bitlist" ssz-max:"2048"`
NestedListField [][]byte `protobuf:"bytes,7,rep,name=nested_list_field,json=nestedListField,proto3" json:"nested_list_field,omitempty" ssz-max:"100,50" ssz-size:"?,?"`
TrailingField []byte `protobuf:"bytes,8,opt,name=trailing_field,json=trailingField,proto3" json:"trailing_field,omitempty" ssz-size:"56"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
LeadingField []byte `protobuf:"bytes,1,opt,name=leading_field,json=leadingField,proto3" json:"leading_field,omitempty" ssz-size:"32"`
FieldListUint64 []uint64 `protobuf:"varint,2,rep,packed,name=field_list_uint64,json=fieldListUint64,proto3" json:"field_list_uint64,omitempty" ssz-max:"2048"`
FieldListContainer []*FixedNestedContainer `protobuf:"bytes,3,rep,name=field_list_container,json=fieldListContainer,proto3" json:"field_list_container,omitempty" ssz-max:"128"`
FieldListBytes32 [][]byte `protobuf:"bytes,4,rep,name=field_list_bytes32,json=fieldListBytes32,proto3" json:"field_list_bytes32,omitempty" ssz-max:"100" ssz-size:"?,32"`
Nested *VariableNestedContainer `protobuf:"bytes,5,opt,name=nested,proto3" json:"nested,omitempty"`
VariableContainerList []*VariableOuterContainer `protobuf:"bytes,6,rep,name=variable_container_list,json=variableContainerList,proto3" json:"variable_container_list,omitempty" ssz-max:"10"`
BitlistField github_com_prysmaticlabs_go_bitfield.Bitlist `protobuf:"bytes,7,opt,name=bitlist_field,json=bitlistField,proto3" json:"bitlist_field,omitempty" cast-type:"github.com/prysmaticlabs/go-bitfield.Bitlist" ssz-max:"2048"`
NestedListField [][]byte `protobuf:"bytes,8,rep,name=nested_list_field,json=nestedListField,proto3" json:"nested_list_field,omitempty" ssz-max:"100,50" ssz-size:"?,?"`
TrailingField []byte `protobuf:"bytes,9,opt,name=trailing_field,json=trailingField,proto3" json:"trailing_field,omitempty" ssz-size:"56"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *VariableTestContainer) Reset() {
*x = VariableTestContainer{}
mi := &file_proto_ssz_query_ssz_query_proto_msgTypes[3]
mi := &file_proto_ssz_query_ssz_query_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -279,7 +332,7 @@ func (x *VariableTestContainer) String() string {
func (*VariableTestContainer) ProtoMessage() {}
func (x *VariableTestContainer) ProtoReflect() protoreflect.Message {
mi := &file_proto_ssz_query_ssz_query_proto_msgTypes[3]
mi := &file_proto_ssz_query_ssz_query_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -292,7 +345,7 @@ func (x *VariableTestContainer) ProtoReflect() protoreflect.Message {
// Deprecated: Use VariableTestContainer.ProtoReflect.Descriptor instead.
func (*VariableTestContainer) Descriptor() ([]byte, []int) {
return file_proto_ssz_query_ssz_query_proto_rawDescGZIP(), []int{3}
return file_proto_ssz_query_ssz_query_proto_rawDescGZIP(), []int{4}
}
func (x *VariableTestContainer) GetLeadingField() []byte {
@@ -330,6 +383,13 @@ func (x *VariableTestContainer) GetNested() *VariableNestedContainer {
return nil
}
func (x *VariableTestContainer) GetVariableContainerList() []*VariableOuterContainer {
if x != nil {
return x.VariableContainerList
}
return nil
}
func (x *VariableTestContainer) GetBitlistField() github_com_prysmaticlabs_go_bitfield.Bitlist {
if x != nil {
return x.BitlistField
@@ -411,45 +471,60 @@ var file_proto_ssz_query_ssz_query_proto_rawDesc = []byte{
0x64, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x03,
0x28, 0x0c, 0x42, 0x11, 0x8a, 0xb5, 0x18, 0x03, 0x3f, 0x2c, 0x3f, 0x92, 0xb5, 0x18, 0x06, 0x31,
0x30, 0x30, 0x2c, 0x35, 0x30, 0x52, 0x0f, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x4c, 0x69, 0x73,
0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x22, 0x9e, 0x04, 0x0a, 0x15, 0x56, 0x61, 0x72, 0x69, 0x61,
0x62, 0x6c, 0x65, 0x54, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72,
0x12, 0x2b, 0x0a, 0x0d, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x69, 0x65, 0x6c,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52,
0x0c, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x34, 0x0a,
0x11, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x75, 0x69, 0x6e, 0x74,
0x36, 0x34, 0x18, 0x02, 0x20, 0x03, 0x28, 0x04, 0x42, 0x08, 0x92, 0xb5, 0x18, 0x04, 0x32, 0x30,
0x34, 0x38, 0x52, 0x0f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x69, 0x6e,
0x74, 0x36, 0x34, 0x12, 0x5a, 0x0a, 0x14, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6c, 0x69, 0x73,
0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x03, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x1f, 0x2e, 0x73, 0x73, 0x7a, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x46, 0x69,
0x78, 0x65, 0x64, 0x4e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e,
0x65, 0x72, 0x42, 0x07, 0x92, 0xb5, 0x18, 0x03, 0x31, 0x32, 0x38, 0x52, 0x12, 0x66, 0x69, 0x65,
0x6c, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12,
0x3d, 0x0a, 0x12, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x62, 0x79,
0x74, 0x65, 0x73, 0x33, 0x32, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x42, 0x0f, 0x8a, 0xb5, 0x18,
0x04, 0x3f, 0x2c, 0x33, 0x32, 0x92, 0xb5, 0x18, 0x03, 0x31, 0x30, 0x30, 0x52, 0x10, 0x66, 0x69,
0x65, 0x6c, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x79, 0x74, 0x65, 0x73, 0x33, 0x32, 0x12, 0x3a,
0x0a, 0x06, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22,
0x2e, 0x73, 0x73, 0x7a, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61,
0x62, 0x6c, 0x65, 0x4e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e,
0x65, 0x72, 0x52, 0x06, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x12, 0x5d, 0x0a, 0x0d, 0x62, 0x69,
0x74, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28,
0x0c, 0x42, 0x38, 0x82, 0xb5, 0x18, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f,
0x67, 0x6f, 0x2d, 0x62, 0x69, 0x74, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x42, 0x69, 0x74, 0x6c,
0x69, 0x73, 0x74, 0x92, 0xb5, 0x18, 0x04, 0x32, 0x30, 0x34, 0x38, 0x52, 0x0c, 0x62, 0x69, 0x74,
0x6c, 0x69, 0x73, 0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x3d, 0x0a, 0x11, 0x6e, 0x65, 0x73,
0x74, 0x65, 0x64, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x07,
0x20, 0x03, 0x28, 0x0c, 0x42, 0x11, 0x8a, 0xb5, 0x18, 0x03, 0x3f, 0x2c, 0x3f, 0x92, 0xb5, 0x18,
0x06, 0x31, 0x30, 0x30, 0x2c, 0x35, 0x30, 0x52, 0x0f, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x4c,
0x69, 0x73, 0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x2d, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x69,
0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c,
0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x35, 0x36, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69,
0x6e, 0x67, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4c, 0x61,
0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x36, 0x2f, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2f, 0x73, 0x73, 0x7a, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x22, 0x92, 0x01, 0x0a, 0x16, 0x56, 0x61, 0x72, 0x69, 0x61,
0x62, 0x6c, 0x65, 0x4f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65,
0x72, 0x12, 0x3b, 0x0a, 0x07, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x5f, 0x31, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x73, 0x7a, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x56,
0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6e,
0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x06, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x31, 0x12, 0x3b,
0x0a, 0x07, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x5f, 0x32, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x22, 0x2e, 0x73, 0x73, 0x7a, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x56, 0x61, 0x72, 0x69,
0x61, 0x62, 0x6c, 0x65, 0x4e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69,
0x6e, 0x65, 0x72, 0x52, 0x06, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x32, 0x22, 0x81, 0x05, 0x0a, 0x15,
0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74,
0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x0d, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67,
0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5,
0x18, 0x02, 0x33, 0x32, 0x52, 0x0c, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x46, 0x69, 0x65,
0x6c, 0x64, 0x12, 0x34, 0x0a, 0x11, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6c, 0x69, 0x73, 0x74,
0x5f, 0x75, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x18, 0x02, 0x20, 0x03, 0x28, 0x04, 0x42, 0x08, 0x92,
0xb5, 0x18, 0x04, 0x32, 0x30, 0x34, 0x38, 0x52, 0x0f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4c, 0x69,
0x73, 0x74, 0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x12, 0x5a, 0x0a, 0x14, 0x66, 0x69, 0x65, 0x6c,
0x64, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72,
0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x73, 0x73, 0x7a, 0x5f, 0x71, 0x75, 0x65,
0x72, 0x79, 0x2e, 0x46, 0x69, 0x78, 0x65, 0x64, 0x4e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6f,
0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x42, 0x07, 0x92, 0xb5, 0x18, 0x03, 0x31, 0x32, 0x38,
0x52, 0x12, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61,
0x69, 0x6e, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x12, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6c, 0x69,
0x73, 0x74, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x33, 0x32, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c,
0x42, 0x0f, 0x8a, 0xb5, 0x18, 0x04, 0x3f, 0x2c, 0x33, 0x32, 0x92, 0xb5, 0x18, 0x03, 0x31, 0x30,
0x30, 0x52, 0x10, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x79, 0x74, 0x65,
0x73, 0x33, 0x32, 0x12, 0x3a, 0x0a, 0x06, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x73, 0x7a, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e,
0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6f,
0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x06, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x12,
0x61, 0x0a, 0x17, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74,
0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x21, 0x2e, 0x73, 0x73, 0x7a, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x56, 0x61, 0x72,
0x69, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69,
0x6e, 0x65, 0x72, 0x42, 0x06, 0x92, 0xb5, 0x18, 0x02, 0x31, 0x30, 0x52, 0x15, 0x76, 0x61, 0x72,
0x69, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4c, 0x69,
0x73, 0x74, 0x12, 0x5d, 0x0a, 0x0d, 0x62, 0x69, 0x74, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x66, 0x69,
0x65, 0x6c, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x38, 0x82, 0xb5, 0x18, 0x2c, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61,
0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x67, 0x6f, 0x2d, 0x62, 0x69, 0x74, 0x66, 0x69,
0x65, 0x6c, 0x64, 0x2e, 0x42, 0x69, 0x74, 0x6c, 0x69, 0x73, 0x74, 0x92, 0xb5, 0x18, 0x04, 0x32,
0x30, 0x34, 0x38, 0x52, 0x0c, 0x62, 0x69, 0x74, 0x6c, 0x69, 0x73, 0x74, 0x46, 0x69, 0x65, 0x6c,
0x64, 0x12, 0x3d, 0x0a, 0x11, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x6c, 0x69, 0x73, 0x74,
0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0c, 0x42, 0x11, 0x8a, 0xb5,
0x18, 0x03, 0x3f, 0x2c, 0x3f, 0x92, 0xb5, 0x18, 0x06, 0x31, 0x30, 0x30, 0x2c, 0x35, 0x30, 0x52,
0x0f, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x69, 0x65, 0x6c, 0x64,
0x12, 0x2d, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x69, 0x65,
0x6c, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x35, 0x36,
0x52, 0x0d, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x42,
0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x66,
0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d,
0x2f, 0x76, 0x36, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x73, 0x7a, 0x5f, 0x71, 0x75,
0x65, 0x72, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -464,22 +539,26 @@ func file_proto_ssz_query_ssz_query_proto_rawDescGZIP() []byte {
return file_proto_ssz_query_ssz_query_proto_rawDescData
}
var file_proto_ssz_query_ssz_query_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_proto_ssz_query_ssz_query_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_proto_ssz_query_ssz_query_proto_goTypes = []any{
(*FixedNestedContainer)(nil), // 0: ssz_query.FixedNestedContainer
(*FixedTestContainer)(nil), // 1: ssz_query.FixedTestContainer
(*VariableNestedContainer)(nil), // 2: ssz_query.VariableNestedContainer
(*VariableTestContainer)(nil), // 3: ssz_query.VariableTestContainer
(*VariableOuterContainer)(nil), // 3: ssz_query.VariableOuterContainer
(*VariableTestContainer)(nil), // 4: ssz_query.VariableTestContainer
}
var file_proto_ssz_query_ssz_query_proto_depIdxs = []int32{
0, // 0: ssz_query.FixedTestContainer.nested:type_name -> ssz_query.FixedNestedContainer
0, // 1: ssz_query.VariableTestContainer.field_list_container:type_name -> ssz_query.FixedNestedContainer
2, // 2: ssz_query.VariableTestContainer.nested:type_name -> ssz_query.VariableNestedContainer
3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
2, // 1: ssz_query.VariableOuterContainer.inner_1:type_name -> ssz_query.VariableNestedContainer
2, // 2: ssz_query.VariableOuterContainer.inner_2:type_name -> ssz_query.VariableNestedContainer
0, // 3: ssz_query.VariableTestContainer.field_list_container:type_name -> ssz_query.FixedNestedContainer
2, // 4: ssz_query.VariableTestContainer.nested:type_name -> ssz_query.VariableNestedContainer
3, // 5: ssz_query.VariableTestContainer.variable_container_list:type_name -> ssz_query.VariableOuterContainer
6, // [6:6] is the sub-list for method output_type
6, // [6:6] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
}
func init() { file_proto_ssz_query_ssz_query_proto_init() }
@@ -493,7 +572,7 @@ func file_proto_ssz_query_ssz_query_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proto_ssz_query_ssz_query_proto_rawDesc,
NumEnums: 0,
NumMessages: 4,
NumMessages: 5,
NumExtensions: 0,
NumServices: 0,
},

View File

@@ -71,6 +71,13 @@ message VariableNestedContainer {
];
}
// Mock of AttesterSlashingElectra
message VariableOuterContainer {
VariableNestedContainer inner_1 = 1;
VariableNestedContainer inner_2 = 2;
}
// VariableTestContainer - comprehensive variable-size container for SSZ query testing
// Tests: Variable-size lists, offsets in variable containers, mixed fixed/variable fields
message VariableTestContainer {
@@ -93,8 +100,12 @@ message VariableTestContainer {
// Variable nested container - test nested container access within variable container
VariableNestedContainer nested = 5;
// List of variable-sized containers
// e.g., BeaconBlockBody.attester_slashings
repeated VariableOuterContainer variable_container_list = 6 [ (ethereum.eth.ext.ssz_max) = "10" ]; // Test: List[VariableOuterContainer, 10]
// Bitlist type - test bitlist serialization
bytes bitlist_field = 6 [
bytes bitlist_field = 7 [
(ethereum.eth.ext.ssz_max) = "2048",
(ethereum.eth.ext.cast_type) =
"github.com/prysmaticlabs/go-bitfield.Bitlist"
@@ -102,12 +113,12 @@ message VariableTestContainer {
// 2D bytes list - test list of bytelists.
// e.g., ExecutionPayload.transactions
repeated bytes nested_list_field = 7 [
repeated bytes nested_list_field = 8 [
(ethereum.eth.ext.ssz_size) = "?,?",
(ethereum.eth.ext.ssz_max) = "100,50"
];
// Fixed-size trailing field - test fixed field after variable fields
// Verifies correct offset calculation after variable-size fields
bytes trailing_field = 8 [ (ethereum.eth.ext.ssz_size) = "56" ]; // Test: fixed 56-byte field at end, offset: 32 + 4 + 4 + 4 + 4 + 4 + 4 = 56
bytes trailing_field = 9 [ (ethereum.eth.ext.ssz_size) = "56" ]; // Test: fixed 56-byte field at end, offset: 32 + 4 + 4 + 4 + 4 + 4 + 4 + 4 = 60
}

View File

@@ -509,6 +509,134 @@ func (v *VariableNestedContainer) HashTreeRootWith(hh *ssz.Hasher) (err error) {
return
}
// MarshalSSZ ssz marshals the VariableOuterContainer object
func (v *VariableOuterContainer) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(v)
}
// MarshalSSZTo ssz marshals the VariableOuterContainer object to a target array
func (v *VariableOuterContainer) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
offset := int(8)
// Offset (0) 'Inner_1'
dst = ssz.WriteOffset(dst, offset)
if v.Inner_1 == nil {
v.Inner_1 = new(VariableNestedContainer)
}
offset += v.Inner_1.SizeSSZ()
// Offset (1) 'Inner_2'
dst = ssz.WriteOffset(dst, offset)
if v.Inner_2 == nil {
v.Inner_2 = new(VariableNestedContainer)
}
offset += v.Inner_2.SizeSSZ()
// Field (0) 'Inner_1'
if dst, err = v.Inner_1.MarshalSSZTo(dst); err != nil {
return
}
// Field (1) 'Inner_2'
if dst, err = v.Inner_2.MarshalSSZTo(dst); err != nil {
return
}
return
}
// UnmarshalSSZ ssz unmarshals the VariableOuterContainer object
func (v *VariableOuterContainer) UnmarshalSSZ(buf []byte) error {
var err error
size := uint64(len(buf))
if size < 8 {
return ssz.ErrSize
}
tail := buf
var o0, o1 uint64
// Offset (0) 'Inner_1'
if o0 = ssz.ReadOffset(buf[0:4]); o0 > size {
return ssz.ErrOffset
}
if o0 != 8 {
return ssz.ErrInvalidVariableOffset
}
// Offset (1) 'Inner_2'
if o1 = ssz.ReadOffset(buf[4:8]); o1 > size || o0 > o1 {
return ssz.ErrOffset
}
// Field (0) 'Inner_1'
{
buf = tail[o0:o1]
if v.Inner_1 == nil {
v.Inner_1 = new(VariableNestedContainer)
}
if err = v.Inner_1.UnmarshalSSZ(buf); err != nil {
return err
}
}
// Field (1) 'Inner_2'
{
buf = tail[o1:]
if v.Inner_2 == nil {
v.Inner_2 = new(VariableNestedContainer)
}
if err = v.Inner_2.UnmarshalSSZ(buf); err != nil {
return err
}
}
return err
}
// SizeSSZ returns the ssz encoded size in bytes for the VariableOuterContainer object
func (v *VariableOuterContainer) SizeSSZ() (size int) {
size = 8
// Field (0) 'Inner_1'
if v.Inner_1 == nil {
v.Inner_1 = new(VariableNestedContainer)
}
size += v.Inner_1.SizeSSZ()
// Field (1) 'Inner_2'
if v.Inner_2 == nil {
v.Inner_2 = new(VariableNestedContainer)
}
size += v.Inner_2.SizeSSZ()
return
}
// HashTreeRoot ssz hashes the VariableOuterContainer object
func (v *VariableOuterContainer) HashTreeRoot() ([32]byte, error) {
return ssz.HashWithDefaultHasher(v)
}
// HashTreeRootWith ssz hashes the VariableOuterContainer object with a hasher
func (v *VariableOuterContainer) HashTreeRootWith(hh *ssz.Hasher) (err error) {
indx := hh.Index()
// Field (0) 'Inner_1'
if err = v.Inner_1.HashTreeRootWith(hh); err != nil {
return
}
// Field (1) 'Inner_2'
if err = v.Inner_2.HashTreeRootWith(hh); err != nil {
return
}
hh.Merkleize(indx)
return
}
// MarshalSSZ ssz marshals the VariableTestContainer object
func (v *VariableTestContainer) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(v)
@@ -517,7 +645,7 @@ func (v *VariableTestContainer) MarshalSSZ() ([]byte, error) {
// MarshalSSZTo ssz marshals the VariableTestContainer object to a target array
func (v *VariableTestContainer) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
offset := int(112)
offset := int(116)
// Field (0) 'LeadingField'
if size := len(v.LeadingField); size != 32 {
@@ -545,18 +673,25 @@ func (v *VariableTestContainer) MarshalSSZTo(buf []byte) (dst []byte, err error)
}
offset += v.Nested.SizeSSZ()
// Offset (5) 'BitlistField'
// Offset (5) 'VariableContainerList'
dst = ssz.WriteOffset(dst, offset)
for ii := 0; ii < len(v.VariableContainerList); ii++ {
offset += 4
offset += v.VariableContainerList[ii].SizeSSZ()
}
// Offset (6) 'BitlistField'
dst = ssz.WriteOffset(dst, offset)
offset += len(v.BitlistField)
// Offset (6) 'NestedListField'
// Offset (7) 'NestedListField'
dst = ssz.WriteOffset(dst, offset)
for ii := 0; ii < len(v.NestedListField); ii++ {
offset += 4
offset += len(v.NestedListField[ii])
}
// Field (7) 'TrailingField'
// Field (8) 'TrailingField'
if size := len(v.TrailingField); size != 56 {
err = ssz.ErrBytesLengthFn("--.TrailingField", size, 56)
return
@@ -601,14 +736,32 @@ func (v *VariableTestContainer) MarshalSSZTo(buf []byte) (dst []byte, err error)
return
}
// Field (5) 'BitlistField'
// Field (5) 'VariableContainerList'
if size := len(v.VariableContainerList); size > 10 {
err = ssz.ErrListTooBigFn("--.VariableContainerList", size, 10)
return
}
{
offset = 4 * len(v.VariableContainerList)
for ii := 0; ii < len(v.VariableContainerList); ii++ {
dst = ssz.WriteOffset(dst, offset)
offset += v.VariableContainerList[ii].SizeSSZ()
}
}
for ii := 0; ii < len(v.VariableContainerList); ii++ {
if dst, err = v.VariableContainerList[ii].MarshalSSZTo(dst); err != nil {
return
}
}
// Field (6) 'BitlistField'
if size := len(v.BitlistField); size > 2048 {
err = ssz.ErrBytesLengthFn("--.BitlistField", size, 2048)
return
}
dst = append(dst, v.BitlistField...)
// Field (6) 'NestedListField'
// Field (7) 'NestedListField'
if size := len(v.NestedListField); size > 100 {
err = ssz.ErrListTooBigFn("--.NestedListField", size, 100)
return
@@ -635,12 +788,12 @@ func (v *VariableTestContainer) MarshalSSZTo(buf []byte) (dst []byte, err error)
func (v *VariableTestContainer) UnmarshalSSZ(buf []byte) error {
var err error
size := uint64(len(buf))
if size < 112 {
if size < 116 {
return ssz.ErrSize
}
tail := buf
var o1, o2, o3, o4, o5, o6 uint64
var o1, o2, o3, o4, o5, o6, o7 uint64
// Field (0) 'LeadingField'
if cap(v.LeadingField) == 0 {
@@ -653,7 +806,7 @@ func (v *VariableTestContainer) UnmarshalSSZ(buf []byte) error {
return ssz.ErrOffset
}
if o1 != 112 {
if o1 != 116 {
return ssz.ErrInvalidVariableOffset
}
@@ -672,21 +825,26 @@ func (v *VariableTestContainer) UnmarshalSSZ(buf []byte) error {
return ssz.ErrOffset
}
// Offset (5) 'BitlistField'
// Offset (5) 'VariableContainerList'
if o5 = ssz.ReadOffset(buf[48:52]); o5 > size || o4 > o5 {
return ssz.ErrOffset
}
// Offset (6) 'NestedListField'
// Offset (6) 'BitlistField'
if o6 = ssz.ReadOffset(buf[52:56]); o6 > size || o5 > o6 {
return ssz.ErrOffset
}
// Field (7) 'TrailingField'
if cap(v.TrailingField) == 0 {
v.TrailingField = make([]byte, 0, len(buf[56:112]))
// Offset (7) 'NestedListField'
if o7 = ssz.ReadOffset(buf[56:60]); o7 > size || o6 > o7 {
return ssz.ErrOffset
}
v.TrailingField = append(v.TrailingField, buf[56:112]...)
// Field (8) 'TrailingField'
if cap(v.TrailingField) == 0 {
v.TrailingField = make([]byte, 0, len(buf[60:116]))
}
v.TrailingField = append(v.TrailingField, buf[60:116]...)
// Field (1) 'FieldListUint64'
{
@@ -746,9 +904,31 @@ func (v *VariableTestContainer) UnmarshalSSZ(buf []byte) error {
}
}
// Field (5) 'BitlistField'
// Field (5) 'VariableContainerList'
{
buf = tail[o5:o6]
num, err := ssz.DecodeDynamicLength(buf, 10)
if err != nil {
return err
}
v.VariableContainerList = make([]*VariableOuterContainer, num)
err = ssz.UnmarshalDynamic(buf, num, func(indx int, buf []byte) (err error) {
if v.VariableContainerList[indx] == nil {
v.VariableContainerList[indx] = new(VariableOuterContainer)
}
if err = v.VariableContainerList[indx].UnmarshalSSZ(buf); err != nil {
return err
}
return nil
})
if err != nil {
return err
}
}
// Field (6) 'BitlistField'
{
buf = tail[o6:o7]
if err = ssz.ValidateBitlist(buf, 2048); err != nil {
return err
}
@@ -758,9 +938,9 @@ func (v *VariableTestContainer) UnmarshalSSZ(buf []byte) error {
v.BitlistField = append(v.BitlistField, buf...)
}
// Field (6) 'NestedListField'
// Field (7) 'NestedListField'
{
buf = tail[o6:]
buf = tail[o7:]
num, err := ssz.DecodeDynamicLength(buf, 100)
if err != nil {
return err
@@ -785,7 +965,7 @@ func (v *VariableTestContainer) UnmarshalSSZ(buf []byte) error {
// SizeSSZ returns the ssz encoded size in bytes for the VariableTestContainer object
func (v *VariableTestContainer) SizeSSZ() (size int) {
size = 112
size = 116
// Field (1) 'FieldListUint64'
size += len(v.FieldListUint64) * 8
@@ -802,10 +982,16 @@ func (v *VariableTestContainer) SizeSSZ() (size int) {
}
size += v.Nested.SizeSSZ()
// Field (5) 'BitlistField'
// Field (5) 'VariableContainerList'
for ii := 0; ii < len(v.VariableContainerList); ii++ {
size += 4
size += v.VariableContainerList[ii].SizeSSZ()
}
// Field (6) 'BitlistField'
size += len(v.BitlistField)
// Field (6) 'NestedListField'
// Field (7) 'NestedListField'
for ii := 0; ii < len(v.NestedListField); ii++ {
size += 4
size += len(v.NestedListField[ii])
@@ -886,14 +1072,30 @@ func (v *VariableTestContainer) HashTreeRootWith(hh *ssz.Hasher) (err error) {
return
}
// Field (5) 'BitlistField'
// Field (5) 'VariableContainerList'
{
subIndx := hh.Index()
num := uint64(len(v.VariableContainerList))
if num > 10 {
err = ssz.ErrIncorrectListSize
return
}
for _, elem := range v.VariableContainerList {
if err = elem.HashTreeRootWith(hh); err != nil {
return
}
}
hh.MerkleizeWithMixin(subIndx, num, 10)
}
// Field (6) 'BitlistField'
if len(v.BitlistField) == 0 {
err = ssz.ErrEmptyBitlist
return
}
hh.PutBitlist(v.BitlistField, 2048)
// Field (6) 'NestedListField'
// Field (7) 'NestedListField'
{
subIndx := hh.Index()
num := uint64(len(v.NestedListField))
@@ -916,7 +1118,7 @@ func (v *VariableTestContainer) HashTreeRootWith(hh *ssz.Hasher) (err error) {
hh.MerkleizeWithMixin(subIndx, num, 100)
}
// Field (7) 'TrailingField'
// Field (8) 'TrailingField'
if size := len(v.TrailingField); size != 56 {
err = ssz.ErrBytesLengthFn("--.TrailingField", size, 56)
return

View File

@@ -202,10 +202,10 @@ func TestBeaconApiValidatorClient_ProposeBeaconBlockError_ThenPass(t *testing.T)
func TestBeaconApiValidatorClient_ProposeBeaconBlockAllTypes(t *testing.T) {
tests := []struct {
name string
block *ethpb.GenericSignedBeaconBlock
expectedPath string
wantErr bool
name string
block *ethpb.GenericSignedBeaconBlock
expectedPath string
wantErr bool
errorMessage string
}{
{
@@ -374,7 +374,7 @@ func TestBeaconApiValidatorClient_ProposeBeaconBlockHTTPErrors(t *testing.T) {
gomock.Any(),
gomock.Any(),
).Return(nil, nil, tt.sszError).Times(1)
if tt.expectJSON {
// When SSZ fails, it falls back to JSON
jsonRestHandler.EXPECT().Post(

View File

@@ -121,7 +121,7 @@ func (m *MockJsonRestHandler) PostSSZ(ctx context.Context, endpoint string, head
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(http.Header)
ret2, _ := ret[2].(error)
return ret0,ret1,ret2
return ret0, ret1, ret2
}
// Post indicates an expected call of Post.

View File

@@ -135,7 +135,7 @@ func (c *BeaconApiRestHandler) GetSSZ(ctx context.Context, endpoint string) ([]b
decoder := json.NewDecoder(bytes.NewBuffer(body))
errorJson := &httputil.DefaultJsonError{}
if err = decoder.Decode(errorJson); err != nil {
return nil, nil, errors.Wrapf(err, "failed to decode response body into error json for %s", httpResp.Request.URL)
return nil, nil, fmt.Errorf("HTTP request for %s unsuccessful (%d: %s)", httpResp.Request.URL, httpResp.StatusCode, string(body))
}
return nil, nil, errorJson
}
@@ -241,7 +241,7 @@ func (c *BeaconApiRestHandler) PostSSZ(
decoder := json.NewDecoder(bytes.NewBuffer(body))
errorJson := &httputil.DefaultJsonError{}
if err = decoder.Decode(errorJson); err != nil {
return nil, nil, errors.Wrapf(err, "failed to decode response body into error json for %s", httpResp.Request.URL)
return nil, nil, fmt.Errorf("HTTP request for %s unsuccessful (%d: %s)", httpResp.Request.URL, httpResp.StatusCode, string(body))
}
return nil, nil, errorJson
}

View File

@@ -344,4 +344,18 @@ func Test_decodeResp(t *testing.T) {
err = decodeResp(r, nil)
assert.ErrorContains(t, "failed to decode response body into error json", err)
})
t.Run("500 not JSON", func(t *testing.T) {
body := bytes.Buffer{}
_, err := body.WriteString("foo")
require.NoError(t, err)
r := &http.Response{
Status: "500",
StatusCode: http.StatusInternalServerError,
Body: io.NopCloser(&body),
Header: map[string][]string{"Content-Type": {"text/plain"}},
Request: &http.Request{},
}
err = decodeResp(r, nil)
assert.ErrorContains(t, "HTTP request unsuccessful (500: foo)", err)
})
}