mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 21:38:05 -05:00
Compare commits
25 Commits
subscribe-
...
warmup
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d855816878 | ||
|
|
ab1e0bddc9 | ||
|
|
3e997cf115 | ||
|
|
64ec665890 | ||
|
|
fdb06ea461 | ||
|
|
0486631d73 | ||
|
|
47764696ce | ||
|
|
b2d350b988 | ||
|
|
41e7607092 | ||
|
|
cd429dc253 | ||
|
|
5ced1125f2 | ||
|
|
f67ca6ae5e | ||
|
|
9742333f68 | ||
|
|
c811fadf33 | ||
|
|
55b9448d41 | ||
|
|
10f8d8c26e | ||
|
|
4eab41ea4c | ||
|
|
683608e34a | ||
|
|
fbbf2a1404 | ||
|
|
82f556c50f | ||
|
|
c88aa77ac1 | ||
|
|
0568bec935 | ||
|
|
e463bcd1e1 | ||
|
|
5f8eb69201 | ||
|
|
4b98451649 |
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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: ðpbv1.EventHead{
|
||||
Slot: newHeadSlot,
|
||||
Block: newHeadRoot,
|
||||
State: newHeadStateRoot,
|
||||
EpochTransition: slots.IsEpochStart(newHeadSlot),
|
||||
EpochTransition: epochTransition,
|
||||
PreviousDutyDependentRoot: previousDutyDependentRoot[:],
|
||||
CurrentDutyDependentRoot: currentDutyDependentRoot[:],
|
||||
ExecutionOptimistic: isOptimistic,
|
||||
|
||||
@@ -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{}, ðpb.Checkpoint{}, ðpb.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{}, ðpb.Checkpoint{}, ðpb.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{}, ðpb.Checkpoint{}, ðpb.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{}, ðpb.Checkpoint{}, ðpb.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 := ðpbv1.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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 := ðpb.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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 ðpb.ProposeResponse{BlockRoot: root[:]}, nil
|
||||
}
|
||||
return nil, status.Errorf(codes.Internal, "%s: %v", "handle block failed", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 := ðpb.GenericSignedBeaconBlock{
|
||||
Block: ðpb.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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 := ðpb.Attestation{
|
||||
Data: ðpb.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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, ðpb.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: ðpb.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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
### Added
|
||||
|
||||
- Delegate sszInfo HashTreeRoot to FastSSZ-generated implementations via SSZObject, enabling roots calculation for generated types while avoiding duplicate logic.
|
||||
2
changelog/kasey_att-batch-5ms-deadline.md
Normal file
2
changelog/kasey_att-batch-5ms-deadline.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Fixed
|
||||
- Decreased attestation gossip validation batch deadline to 5ms.
|
||||
2
changelog/kasey_clear-db-rm-genesis.md
Normal file
2
changelog/kasey_clear-db-rm-genesis.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Fixed
|
||||
- Delete the genesis state file when --clear-db / --force-clear-db is specified.
|
||||
2
changelog/manu-advertise-atts.md
Normal file
2
changelog/manu-advertise-atts.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Fixed
|
||||
- Correctly advertise (in ENR and beacon API) attestation subnets when using `--subscribe-all-subnets`.
|
||||
2
changelog/manu-blob-sidecars-beacon-api.md
Normal file
2
changelog/manu-blob-sidecars-beacon-api.md
Normal 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.
|
||||
2
changelog/manu-check-commitment-count.md
Normal file
2
changelog/manu-check-commitment-count.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Fixed
|
||||
- `VerifyDataColumnSidecar`: Check if there is no too many commitments.
|
||||
2
changelog/manu-data-column-retention-period.md
Normal file
2
changelog/manu-data-column-retention-period.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Fixed
|
||||
- `WithDataColumnRetentionEpochs`: Use `dataColumnRetentionEpoch` instead of `blobColumnRetentionEpoch`.
|
||||
2
changelog/manu-gracefully-close-stream.md
Normal file
2
changelog/manu-gracefully-close-stream.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Fixed
|
||||
- `dataColumnSidecarsByRangeRPCHandler`: Gracefully close the stream if no data to return.
|
||||
3
changelog/manu-handle-windows.md
Normal file
3
changelog/manu-handle-windows.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Fixed
|
||||
- `WarmCache`: Return if `extractFileMetadata` fails.
|
||||
- `extractFileMetadata`: Use filepath.Separator instead of `/` for Windows support.
|
||||
2
changelog/manu-has-at-least-one-index.md
Normal file
2
changelog/manu-has-at-least-one-index.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Fixed
|
||||
- `HasAtLeastOneIndex`: Check the index is not too high.
|
||||
2
changelog/manu-random-peer.md
Normal file
2
changelog/manu-random-peer.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Fixed
|
||||
- `randomPeer`: Return if the context is cancelled when waiting for peers.
|
||||
2
changelog/manu-read-columns-from-disk-error.md
Normal file
2
changelog/manu-read-columns-from-disk-error.md
Normal 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.)
|
||||
2
changelog/marcopolo_push-nxynxywxtlpo.md
Normal file
2
changelog/marcopolo_push-nxynxywxtlpo.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Ignored
|
||||
- Fix (unreleased) bug where the preallocated slice for KZG Proofs was 48x bigger than it needed to be.
|
||||
@@ -0,0 +1,3 @@
|
||||
### Fixed
|
||||
|
||||
- reject committee index >= committees_per_slot in unaggregated attestation validation
|
||||
3
changelog/potuz_fix_head_event.md
Normal file
3
changelog/potuz_fix_head_event.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Fixed
|
||||
|
||||
- Mark epoch transition correctly on new head events
|
||||
3
changelog/potuz_invalid_sig.md
Normal file
3
changelog/potuz_invalid_sig.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Fixed
|
||||
|
||||
- Mark the block as invalid if it has an invalid signature.
|
||||
3
changelog/potuz_redundant_check.md
Normal file
3
changelog/potuz_redundant_check.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Ignored
|
||||
|
||||
- Remove redundant check for genesis root at startup.
|
||||
3
changelog/radek_api-individual-failure.md
Normal file
3
changelog/radek_api-individual-failure.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Changed
|
||||
|
||||
- Improve returning individual message errors from Beacon API.
|
||||
3
changelog/radek_read-non-json-error.md
Normal file
3
changelog/radek_read-non-json-error.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Fixed
|
||||
|
||||
- Display error messages from the server verbatim when they are not encoded as `application/json`.
|
||||
7
changelog/syjn99_ssz-ql-fastssz-sizessz.md
Normal file
7
changelog/syjn99_ssz-ql-fastssz-sizessz.md
Normal 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.
|
||||
3
changelog/syjn99_ssz-ql-index-accessing.md
Normal file
3
changelog/syjn99_ssz-ql-index-accessing.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Added
|
||||
|
||||
- SSZ-QL: Access n-th element in `List`/`Vector`.
|
||||
3
changelog/ttsao_fix-sync-committee-subnet-indices.md
Normal file
3
changelog/ttsao_fix-sync-committee-subnet-indices.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Fixed
|
||||
|
||||
- Fix sync committee subscription to use subnet indices instead of committee indices
|
||||
3
changelog/ttsao_handle-relay-502-errors.md
Normal file
3
changelog/ttsao_handle-relay-502-errors.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Changed
|
||||
|
||||
- Gracefully handle submit blind block returning 502 errors.
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 := "├─"
|
||||
|
||||
23
encoding/ssz/query/ssz_object.go
Normal file
23
encoding/ssz/query/ssz_object.go
Normal 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()
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
205
proto/ssz_query/ssz_query.pb.go
generated
205
proto/ssz_query/ssz_query.pb.go
generated
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user