mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 13:58:09 -05:00
Compare commits
5 Commits
validation
...
log-att-st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f87252004 | ||
|
|
b2a9db0826 | ||
|
|
040661bd68 | ||
|
|
d3bd0eaa30 | ||
|
|
577899bfec |
@@ -16,6 +16,7 @@ import (
|
||||
ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v6/time/slots"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// The caller of this function must have a lock on forkchoice.
|
||||
@@ -32,6 +33,7 @@ func (s *Service) getRecentPreState(ctx context.Context, c *ethpb.Checkpoint) st
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
log.Info("Got head state read only")
|
||||
return st
|
||||
}
|
||||
slot, err := slots.EpochStart(c.Epoch)
|
||||
@@ -48,6 +50,7 @@ func (s *Service) getRecentPreState(ctx context.Context, c *ethpb.Checkpoint) st
|
||||
return nil
|
||||
}
|
||||
if cachedState != nil && !cachedState.IsNil() {
|
||||
log.Info("Got cached state from check point")
|
||||
return cachedState
|
||||
}
|
||||
st, err := s.HeadState(ctx)
|
||||
@@ -61,6 +64,7 @@ func (s *Service) getRecentPreState(ctx context.Context, c *ethpb.Checkpoint) st
|
||||
if err := s.checkpointStateCache.AddCheckpointState(c, st); err != nil {
|
||||
log.WithError(err).Error("Could not save checkpoint state to cache")
|
||||
}
|
||||
log.Info("Got head state and processed head state to slot ", slot)
|
||||
return st
|
||||
}
|
||||
|
||||
@@ -82,6 +86,7 @@ func (s *Service) getAttPreState(ctx context.Context, c *ethpb.Checkpoint) (stat
|
||||
return nil, errors.Wrap(err, "could not get cached checkpoint state")
|
||||
}
|
||||
if cachedState != nil && !cachedState.IsNil() {
|
||||
log.Info("Got state (non canonical) check point cache")
|
||||
return cachedState, nil
|
||||
}
|
||||
// Try the next slot cache for the early epoch calls, this should mostly have been covered already
|
||||
@@ -101,6 +106,7 @@ func (s *Service) getAttPreState(ctx context.Context, c *ethpb.Checkpoint) (stat
|
||||
if err := s.checkpointStateCache.AddCheckpointState(c, cachedState); err != nil {
|
||||
return nil, errors.Wrap(err, "could not save checkpoint state to cache")
|
||||
}
|
||||
log.Info("Got state next slot cache and processed to slot ", slot)
|
||||
return cachedState, nil
|
||||
}
|
||||
|
||||
@@ -135,6 +141,9 @@ func (s *Service) getAttPreState(ctx context.Context, c *ethpb.Checkpoint) (stat
|
||||
if err := s.checkpointStateCache.AddCheckpointState(c, baseState); err != nil {
|
||||
return nil, errors.Wrap(err, "could not save checkpoint state to cache")
|
||||
}
|
||||
|
||||
log.Info("Got state state gen by root and processed to slot ", slot)
|
||||
|
||||
return baseState, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package p2p
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"reflect"
|
||||
@@ -106,18 +107,26 @@ func peerScoringParams(colocationWhitelist []*net.IPNet) (*pubsub.PeerScoreParam
|
||||
}
|
||||
|
||||
func (s *Service) topicScoreParams(topic string) (*pubsub.TopicScoreParams, error) {
|
||||
activeValidators, err := s.retrieveActiveValidators()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch {
|
||||
case strings.Contains(topic, GossipBlockMessage):
|
||||
return defaultBlockTopicParams(), nil
|
||||
case strings.Contains(topic, GossipAggregateAndProofMessage):
|
||||
activeValidators, err := s.retrieveActiveValidators()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compute active validator count for topic %s: %w", GossipAggregateAndProofMessage, err)
|
||||
}
|
||||
return defaultAggregateTopicParams(activeValidators), nil
|
||||
case strings.Contains(topic, GossipAttestationMessage):
|
||||
activeValidators, err := s.retrieveActiveValidators()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compute active validator count for topic %s: %w", GossipAttestationMessage, err)
|
||||
}
|
||||
return defaultAggregateSubnetTopicParams(activeValidators), nil
|
||||
case strings.Contains(topic, GossipSyncCommitteeMessage):
|
||||
activeValidators, err := s.retrieveActiveValidators()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compute active validator count for topic %s: %w", GossipSyncCommitteeMessage, err)
|
||||
}
|
||||
return defaultSyncSubnetTopicParams(activeValidators), nil
|
||||
case strings.Contains(topic, GossipContributionAndProofMessage):
|
||||
return defaultSyncContributionTopicParams(), nil
|
||||
@@ -142,6 +151,8 @@ func (s *Service) topicScoreParams(topic string) (*pubsub.TopicScoreParams, erro
|
||||
}
|
||||
|
||||
func (s *Service) retrieveActiveValidators() (uint64, error) {
|
||||
s.activeValidatorCountLock.Lock()
|
||||
defer s.activeValidatorCountLock.Unlock()
|
||||
if s.activeValidatorCount != 0 {
|
||||
return s.activeValidatorCount, nil
|
||||
}
|
||||
|
||||
@@ -65,35 +65,36 @@ var (
|
||||
|
||||
// Service for managing peer to peer (p2p) networking.
|
||||
type Service struct {
|
||||
started bool
|
||||
isPreGenesis bool
|
||||
pingMethod func(ctx context.Context, id peer.ID) error
|
||||
pingMethodLock sync.RWMutex
|
||||
cancel context.CancelFunc
|
||||
cfg *Config
|
||||
peers *peers.Status
|
||||
addrFilter *multiaddr.Filters
|
||||
ipLimiter *leakybucket.Collector
|
||||
privKey *ecdsa.PrivateKey
|
||||
metaData metadata.Metadata
|
||||
pubsub *pubsub.PubSub
|
||||
joinedTopics map[string]*pubsub.Topic
|
||||
joinedTopicsLock sync.RWMutex
|
||||
subnetsLock map[uint64]*sync.RWMutex
|
||||
subnetsLockLock sync.Mutex // Lock access to subnetsLock
|
||||
initializationLock sync.Mutex
|
||||
dv5Listener ListenerRebooter
|
||||
startupErr error
|
||||
ctx context.Context
|
||||
host host.Host
|
||||
genesisTime time.Time
|
||||
genesisValidatorsRoot []byte
|
||||
activeValidatorCount uint64
|
||||
peerDisconnectionTime *cache.Cache
|
||||
custodyInfo *custodyInfo
|
||||
custodyInfoLock sync.RWMutex // Lock access to custodyInfo
|
||||
custodyInfoSet chan struct{}
|
||||
allForkDigests map[[4]byte]struct{}
|
||||
started bool
|
||||
isPreGenesis bool
|
||||
pingMethod func(ctx context.Context, id peer.ID) error
|
||||
pingMethodLock sync.RWMutex
|
||||
cancel context.CancelFunc
|
||||
cfg *Config
|
||||
peers *peers.Status
|
||||
addrFilter *multiaddr.Filters
|
||||
ipLimiter *leakybucket.Collector
|
||||
privKey *ecdsa.PrivateKey
|
||||
metaData metadata.Metadata
|
||||
pubsub *pubsub.PubSub
|
||||
joinedTopics map[string]*pubsub.Topic
|
||||
joinedTopicsLock sync.RWMutex
|
||||
subnetsLock map[uint64]*sync.RWMutex
|
||||
subnetsLockLock sync.Mutex // Lock access to subnetsLock
|
||||
initializationLock sync.Mutex
|
||||
dv5Listener ListenerRebooter
|
||||
startupErr error
|
||||
ctx context.Context
|
||||
host host.Host
|
||||
genesisTime time.Time
|
||||
genesisValidatorsRoot []byte
|
||||
activeValidatorCount uint64
|
||||
activeValidatorCountLock sync.Mutex
|
||||
peerDisconnectionTime *cache.Cache
|
||||
custodyInfo *custodyInfo
|
||||
custodyInfoLock sync.RWMutex // Lock access to custodyInfo
|
||||
custodyInfoSet chan struct{}
|
||||
allForkDigests map[[4]byte]struct{}
|
||||
}
|
||||
|
||||
type custodyInfo struct {
|
||||
|
||||
3
changelog/fernantho_ssz-ql-update-path-processing.md
Normal file
3
changelog/fernantho_ssz-ql-update-path-processing.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## Changed
|
||||
- Introduced Path type for SSZ-QL queries and updated PathElement (removed Length field, kept Index) enforcing that len queries are terminal (at most one per path).
|
||||
- Changed length query syntax from `block.payload.len(transactions)` to `len(block.payload.transactions)`
|
||||
2
changelog/manu-factory.md
Normal file
2
changelog/manu-factory.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Ignored
|
||||
- `BeaconBlockContainerToSignedBeaconBlock`: Add Fulu.
|
||||
2
changelog/manu-go-netroute-2.md
Normal file
2
changelog/manu-go-netroute-2.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Changed
|
||||
- Update `go-netroute` to `v0.4.0`
|
||||
4
changelog/pvl-active-val-count-lock.md
Normal file
4
changelog/pvl-active-val-count-lock.md
Normal file
@@ -0,0 +1,4 @@
|
||||
### Fixed
|
||||
|
||||
- Changed the behavior of topic subscriptions such that only topics that require the active validator count will compute that value.
|
||||
- Added a Mutex to the computation of active validator count during topic subscription to avoid a race condition where multiple goroutines are computing the same work.
|
||||
@@ -640,6 +640,10 @@ func BuildSignedBeaconBlockFromExecutionPayload(blk interfaces.ReadOnlySignedBea
|
||||
// This is particularly useful for using the values from API calls.
|
||||
func BeaconBlockContainerToSignedBeaconBlock(obj *eth.BeaconBlockContainer) (interfaces.ReadOnlySignedBeaconBlock, error) {
|
||||
switch obj.Block.(type) {
|
||||
case *eth.BeaconBlockContainer_BlindedFuluBlock:
|
||||
return NewSignedBeaconBlock(obj.GetBlindedFuluBlock())
|
||||
case *eth.BeaconBlockContainer_FuluBlock:
|
||||
return NewSignedBeaconBlock(obj.GetFuluBlock())
|
||||
case *eth.BeaconBlockContainer_BlindedElectraBlock:
|
||||
return NewSignedBeaconBlock(obj.GetBlindedElectraBlock())
|
||||
case *eth.BeaconBlockContainer_ElectraBlock:
|
||||
|
||||
4
deps.bzl
4
deps.bzl
@@ -2023,8 +2023,8 @@ def prysm_deps():
|
||||
go_repository(
|
||||
name = "com_github_libp2p_go_netroute",
|
||||
importpath = "github.com/libp2p/go-netroute",
|
||||
sum = "h1:nqPCXHmeNmgTJnktosJ/sIef9hvwYCrsLxXmfNks/oc=",
|
||||
version = "v0.3.0",
|
||||
sum = "h1:sZZx9hyANYUx9PZyqcgE/E1GUG3iEtTZHUEvdtXT7/Q=",
|
||||
version = "v0.4.0",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_github_libp2p_go_reuseport",
|
||||
|
||||
@@ -14,13 +14,13 @@ const listBaseIndex = 2
|
||||
// 1. The sszInfo of the root object, to be able to navigate the SSZ structure
|
||||
// 2. The path to the field (e.g., "field_a.field_b[3].field_c")
|
||||
// It walks the path step by step, updating the generalized index at each step.
|
||||
func GetGeneralizedIndexFromPath(info *SszInfo, path []PathElement) (uint64, error) {
|
||||
func GetGeneralizedIndexFromPath(info *SszInfo, path Path) (uint64, error) {
|
||||
if info == nil {
|
||||
return 0, errors.New("SszInfo is nil")
|
||||
}
|
||||
|
||||
// If path is empty, no generalized index can be computed.
|
||||
if len(path) == 0 {
|
||||
if len(path.Elements) == 0 {
|
||||
return 0, errors.New("cannot compute generalized index for an empty path")
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ func GetGeneralizedIndexFromPath(info *SszInfo, path []PathElement) (uint64, err
|
||||
currentIndex := uint64(1)
|
||||
currentInfo := info
|
||||
|
||||
for _, pathElement := range path {
|
||||
for index, pathElement := range path.Elements {
|
||||
element := pathElement
|
||||
|
||||
// Check that we are in a container to access fields
|
||||
@@ -52,8 +52,8 @@ func GetGeneralizedIndexFromPath(info *SszInfo, path []PathElement) (uint64, err
|
||||
currentIndex = currentIndex*nextPowerOfTwo(chunkCount) + fieldPos
|
||||
currentInfo = fieldSsz
|
||||
|
||||
// Check if a path element is a length field
|
||||
if element.Length {
|
||||
// Check for length access: element is the last in the path and requests length
|
||||
if path.Length && index == len(path.Elements)-1 {
|
||||
currentInfo, currentIndex, err = calculateLengthGeneralizedIndex(fieldSsz, element, currentIndex)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("length calculation error: %w", err)
|
||||
|
||||
@@ -65,12 +65,6 @@ func TestGetIndicesFromPath_FixedNestedContainer(t *testing.T) {
|
||||
expectedIndex: 3,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Empty path error",
|
||||
path: "",
|
||||
expectError: true,
|
||||
errorMessage: "empty path",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -217,8 +211,8 @@ func TestGetIndicesFromPath_VariableTestContainer(t *testing.T) {
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "variable_container_list[0].inner_1.len(nested_list_field[3])",
|
||||
path: "variable_container_list[0].inner_1.len(nested_list_field[3])",
|
||||
name: "len(variable_container_list[0].inner_1.nested_list_field[3])",
|
||||
path: "len(variable_container_list[0].inner_1.nested_list_field[3])",
|
||||
expectError: true,
|
||||
errorMessage: "length calculation error: len() is not supported for multi-dimensional arrays",
|
||||
},
|
||||
|
||||
@@ -10,51 +10,77 @@ import (
|
||||
|
||||
// PathElement represents a single element in a path.
|
||||
type PathElement struct {
|
||||
Length bool
|
||||
Name string
|
||||
Name string
|
||||
// [Optional] Index for List/Vector elements
|
||||
Index *uint64
|
||||
}
|
||||
|
||||
var arrayIndexRegex = regexp.MustCompile(`\[\s*([^\]]+)\s*\]`)
|
||||
// Path represents the entire path structure for SSZ-QL queries. It consists of multiple PathElements
|
||||
// and a flag indicating if the path is querying for length.
|
||||
type Path struct {
|
||||
// If true, the path is querying for the length of the final element in Elements field
|
||||
Length bool
|
||||
// Sequence of path elements representing the navigation through the SSZ structure
|
||||
Elements []PathElement
|
||||
}
|
||||
|
||||
// Matches an array index expression like [123] or [ foo ] and captures the inner content without the brackets.
|
||||
var arrayIndexRegex = regexp.MustCompile(`\[(\d+)\]`)
|
||||
|
||||
// Matches an entire string that’s a len(<expr>) call (whitespace flexible), capturing the inner expression and disallowing any trailing characters.
|
||||
var lengthRegex = regexp.MustCompile(`^\s*len\s*\(\s*([^)]+?)\s*\)\s*$`)
|
||||
|
||||
// Valid path characters: letters, digits, dot, slash, square brackets and parentheses only.
|
||||
// Any other character will render the path invalid.
|
||||
var validPathChars = regexp.MustCompile(`^[A-Za-z0-9._\[\]\(\)]*$`)
|
||||
|
||||
// Invalid patterns: a closing bracket followed directly by a letter or underscore
|
||||
var invalidBracketPattern = regexp.MustCompile(`\][^.\[\)]|\).`)
|
||||
|
||||
// ParsePath parses a raw path string into a slice of PathElements.
|
||||
// note: field names are stored in snake case format. rawPath has to be provided in snake case.
|
||||
// 1. Supports dot notation for field access (e.g., "field1.field2").
|
||||
// 2. Supports array indexing using square brackets (e.g., "array_field[0]").
|
||||
// 3. Supports length access using len() notation (e.g., "len(array_field)").
|
||||
// 4. Handles leading dots and validates path format.
|
||||
func ParsePath(rawPath string) ([]PathElement, error) {
|
||||
rawElements := strings.Split(rawPath, ".")
|
||||
func ParsePath(rawPath string) (Path, error) {
|
||||
if err := validateRawPath(rawPath); err != nil {
|
||||
return Path{}, err
|
||||
}
|
||||
|
||||
var rawElements []string
|
||||
var processedPath Path
|
||||
|
||||
matches := lengthRegex.FindStringSubmatch(rawPath)
|
||||
|
||||
// FindStringSubmatch matches a whole string like "len(field_name)" and its inner expression.
|
||||
// For a path element to be a length query, len(matches) should be 2:
|
||||
// 1. Full match: "len(field_name)"
|
||||
// 2. Inner expression: "field_name"
|
||||
if len(matches) == 2 {
|
||||
processedPath.Length = true
|
||||
// If we have found a len() expression, we only want to parse its inner expression.
|
||||
rawElements = strings.Split(matches[1], ".")
|
||||
} else {
|
||||
// Normal path parsing
|
||||
rawElements = strings.Split(rawPath, ".")
|
||||
}
|
||||
|
||||
if rawElements[0] == "" {
|
||||
// Remove leading dot if present
|
||||
rawElements = rawElements[1:]
|
||||
}
|
||||
|
||||
var path []PathElement
|
||||
var pathElements []PathElement
|
||||
for _, elem := range rawElements {
|
||||
if elem == "" {
|
||||
return nil, errors.New("invalid path: consecutive dots or trailing dot")
|
||||
return Path{}, errors.New("invalid path: consecutive dots or trailing dot")
|
||||
}
|
||||
|
||||
// Processing element string
|
||||
processingField := elem
|
||||
var pathElement PathElement
|
||||
|
||||
matches := lengthRegex.FindStringSubmatch(processingField)
|
||||
// FindStringSubmatch matches a whole string like "len(field_name)" and its inner expression.
|
||||
// For a path element to be a length query, len(matches) should be 2:
|
||||
// 1. Full match: "len(field_name)"
|
||||
// 2. Inner expression: "field_name"
|
||||
if len(matches) == 2 {
|
||||
pathElement.Length = true
|
||||
// Extract the inner expression between len( and ) and continue parsing on that
|
||||
processingField = matches[1]
|
||||
}
|
||||
|
||||
// Default name is the full working string (may be updated below if it contains indices)
|
||||
pathElement.Name = processingField
|
||||
|
||||
@@ -63,22 +89,71 @@ func ParsePath(rawPath string) ([]PathElement, error) {
|
||||
pathElement.Name = extractFieldName(processingField)
|
||||
indices, err := extractArrayIndices(processingField)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return Path{}, err
|
||||
}
|
||||
// Although extractArrayIndices supports multiple indices,
|
||||
// only a single index is supported per PathElement, e.g., "transactions[0]" is valid
|
||||
// while "transactions[0][0]" is rejected explicitly.
|
||||
if len(indices) != 1 {
|
||||
return nil, fmt.Errorf("multiple indices not supported in token %s", processingField)
|
||||
return Path{}, fmt.Errorf("multiple indices not supported in token %s", processingField)
|
||||
}
|
||||
pathElement.Index = &indices[0]
|
||||
|
||||
}
|
||||
|
||||
path = append(path, pathElement)
|
||||
pathElements = append(pathElements, pathElement)
|
||||
}
|
||||
|
||||
return path, nil
|
||||
processedPath.Elements = pathElements
|
||||
return processedPath, nil
|
||||
}
|
||||
|
||||
// validateRawPath performs initial validation of the raw path string:
|
||||
// 1. Rejects invalid characters (only letters, digits, '.', '[]', and '()' are allowed).
|
||||
// 2. Validates balanced parentheses
|
||||
// 3. Validates balanced brackets.
|
||||
// 4. Ensures len() calls are only at the start of the path.
|
||||
// 5. Rejects empty len() calls.
|
||||
// 6. Rejects invalid patterns like "][a" or "][_" which indicate malformed paths.
|
||||
func validateRawPath(rawPath string) error {
|
||||
// 1. Reject any path containing invalid characters (this includes spaces).
|
||||
if !validPathChars.MatchString(rawPath) {
|
||||
return fmt.Errorf("invalid character in path: only letters, digits, '.', '[]' and '()' are allowed")
|
||||
}
|
||||
|
||||
// 2. Basic validation for balanced parentheses: wrongly formatted paths like "test))((" are not rejected in this condition but later.
|
||||
if strings.Count(rawPath, "(") != strings.Count(rawPath, ")") {
|
||||
return fmt.Errorf("unmatched parentheses in path: %s", rawPath)
|
||||
}
|
||||
|
||||
// 3. Basic validation for balanced brackets:
|
||||
// wrongly formatted paths like "array][0][" are rejected by checking bracket counts and format.
|
||||
matches := arrayIndexRegex.FindAllStringSubmatch(rawPath, -1)
|
||||
openBracketsCount := strings.Count(rawPath, "[")
|
||||
closeBracketsCount := strings.Count(rawPath, "]")
|
||||
if openBracketsCount != closeBracketsCount {
|
||||
return fmt.Errorf("unmatched brackets in path: %s", rawPath)
|
||||
}
|
||||
if len(matches) != openBracketsCount || len(matches) != closeBracketsCount {
|
||||
return fmt.Errorf("invalid bracket format in path: %s", rawPath)
|
||||
}
|
||||
|
||||
// 4. Reject len() calls not at the start of the path
|
||||
if strings.Index(rawPath, "len(") > 0 {
|
||||
return fmt.Errorf("len() call must be at the start of the path: %s", rawPath)
|
||||
}
|
||||
|
||||
// 5. Reject empty len() calls
|
||||
if strings.Contains(rawPath, "len()") {
|
||||
return fmt.Errorf("len() call must not be empty: %s", rawPath)
|
||||
}
|
||||
|
||||
// 6. Reject invalid patterns like "][a" or "][_" which indicate malformed paths
|
||||
if invalidBracketPattern.MatchString(rawPath) {
|
||||
return fmt.Errorf("invalid path format near brackets in path: %s", rawPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// extractFieldName extracts the field name from a path element name (removes array indices)
|
||||
|
||||
@@ -14,199 +14,285 @@ func TestParsePath(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
expected []query.PathElement
|
||||
expected query.Path
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "simple path",
|
||||
path: "data",
|
||||
expected: query.Path{
|
||||
Length: false,
|
||||
Elements: []query.PathElement{
|
||||
{Name: "data"},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "simple path beginning with dot",
|
||||
path: ".data",
|
||||
expected: query.Path{
|
||||
Length: false,
|
||||
Elements: []query.PathElement{
|
||||
{Name: "data"},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "simple path trailing dot",
|
||||
path: "data.",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "simple path surrounded by dot",
|
||||
path: ".data.",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "simple path beginning with two dots",
|
||||
path: "..data",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "simple nested path",
|
||||
path: "data.target.root",
|
||||
expected: []query.PathElement{
|
||||
{Name: "data"},
|
||||
{Name: "target"},
|
||||
{Name: "root"},
|
||||
expected: query.Path{
|
||||
Length: false,
|
||||
Elements: []query.PathElement{
|
||||
{Name: "data"},
|
||||
{Name: "target"},
|
||||
{Name: "root"},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "simple nested path with leading dot",
|
||||
path: ".data.target.root",
|
||||
expected: []query.PathElement{
|
||||
{Name: "data"},
|
||||
{Name: "target"},
|
||||
{Name: "root"},
|
||||
name: "len with top-level identifier",
|
||||
path: "len(data)",
|
||||
expected: query.Path{
|
||||
Length: true,
|
||||
Elements: []query.PathElement{
|
||||
{Name: "data"},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "simple length path with length field",
|
||||
path: "data.target.len(root)",
|
||||
expected: []query.PathElement{
|
||||
{Name: "data"},
|
||||
{Name: "target"},
|
||||
{Name: "root", Length: true},
|
||||
name: "len with top-level identifier and leading dot",
|
||||
path: "len(.data)",
|
||||
expected: query.Path{
|
||||
Length: true,
|
||||
Elements: []query.PathElement{
|
||||
{Name: "data"},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "len with top-level identifier",
|
||||
path: "len(data)",
|
||||
expected: []query.PathElement{{Name: "data", Length: true}},
|
||||
wantErr: false,
|
||||
name: "len with top-level identifier and trailing dot",
|
||||
path: "len(data.)",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "length with messy whitespace",
|
||||
path: "data.target. \tlen ( root ) ",
|
||||
expected: []query.PathElement{
|
||||
{Name: "data"},
|
||||
{Name: "target"},
|
||||
{Name: "root", Length: true},
|
||||
name: "len with top-level identifier beginning dot",
|
||||
path: ".len(data)",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "len with dotted path inside",
|
||||
path: "len(data.target.root)",
|
||||
expected: query.Path{
|
||||
Length: true,
|
||||
Elements: []query.PathElement{
|
||||
{Name: "data"},
|
||||
{Name: "target"},
|
||||
{Name: "root"},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "len with numeric index inside argument",
|
||||
path: "data.len(a[10])",
|
||||
expected: []query.PathElement{
|
||||
{Name: "data"},
|
||||
{Name: "a", Length: true, Index: u64(10)},
|
||||
name: "simple length path with non-outer length field",
|
||||
path: "data.target.len(root)",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "simple path with `len` used as a field name",
|
||||
path: "data.len",
|
||||
expected: query.Path{
|
||||
Length: false,
|
||||
Elements: []query.PathElement{
|
||||
{Name: "data"},
|
||||
{Name: "len"},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "array index with spaces",
|
||||
path: "arr[ 42 ]",
|
||||
expected: []query.PathElement{{Name: "arr", Index: u64(42)}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "array leading zeros",
|
||||
path: "arr[001]",
|
||||
expected: []query.PathElement{{Name: "arr", Index: u64(1)}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "array max uint64",
|
||||
path: "arr[18446744073709551615]",
|
||||
expected: []query.PathElement{{Name: "arr", Index: u64(18446744073709551615)}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "len with dotted path inside - no input validation - reverts at a later stage",
|
||||
path: "len(data.target.root)",
|
||||
expected: []query.PathElement{{Name: "len(data", Length: false}, {Name: "target", Length: false}, {Name: "root)", Length: false}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "len with dotted path then more - no input validation - reverts at a later stage",
|
||||
path: "len(data.target.root).foo",
|
||||
expected: []query.PathElement{{Name: "len(data", Length: false}, {Name: "target", Length: false}, {Name: "root)", Length: false}, {Name: "foo", Length: false}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "len without closing paren - no input validation - reverts at a later stage",
|
||||
path: "len(root",
|
||||
expected: []query.PathElement{{Name: "len(root"}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "len with extra closing paren - no input validation - reverts at a later stage",
|
||||
path: "len(root))",
|
||||
expected: []query.PathElement{{Name: "len(root))"}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty len argument - no input validation - reverts at a later stage",
|
||||
path: "len()",
|
||||
expected: []query.PathElement{{Name: "len()"}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "len with comma-separated args - no input validation - reverts at a later stage",
|
||||
path: "len(a,b)",
|
||||
expected: []query.PathElement{{Name: "a,b", Length: true}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "len call followed by index (outer) - no input validation - reverts at a later stage",
|
||||
path: "data.len(root)[0]",
|
||||
expected: []query.PathElement{
|
||||
{Name: "data"},
|
||||
{Name: "len(root)", Index: u64(0)},
|
||||
name: "simple path with `len` used as a field name + trailing field",
|
||||
path: "data.len.value",
|
||||
expected: query.Path{
|
||||
Length: false,
|
||||
Elements: []query.PathElement{
|
||||
{Name: "data"},
|
||||
{Name: "len"},
|
||||
{Name: "value"},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "cannot provide consecutive dots in raw path",
|
||||
path: "data..target.root",
|
||||
name: "simple path with `len`",
|
||||
path: "len.len",
|
||||
expected: query.Path{
|
||||
Length: false,
|
||||
Elements: []query.PathElement{
|
||||
{Name: "len"},
|
||||
{Name: "len"},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "simple length path with length field",
|
||||
path: "len.len(root)",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "cannot provide a negative index in array path",
|
||||
path: ".data.target.root[-1]",
|
||||
name: "empty length field",
|
||||
path: "len()",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid index in array path",
|
||||
path: ".data.target.root[a]",
|
||||
name: "length field not terminal",
|
||||
path: "len(data).foo",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "multidimensional array index in path",
|
||||
path: ".data.target.root[0][1]",
|
||||
name: "length field with missing closing paren",
|
||||
path: "len(data",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "leading double dot",
|
||||
path: "..data",
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "trailing dot",
|
||||
path: "data.target.",
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "len with inner bracket non-numeric index",
|
||||
path: "data.len(a[b])",
|
||||
name: "length field with two closing paren",
|
||||
path: "len(data))",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "array empty index",
|
||||
path: "arr[]",
|
||||
name: "len with comma-separated args",
|
||||
path: "len(a,b)",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "array hex index",
|
||||
path: "arr[0x10]",
|
||||
name: "array index path",
|
||||
path: "arr[42]",
|
||||
expected: query.Path{
|
||||
Length: false,
|
||||
Elements: []query.PathElement{
|
||||
{Name: "arr", Index: u64(42)},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "array index path with max uint64",
|
||||
path: "arr[18446744073709551615]",
|
||||
expected: query.Path{
|
||||
Length: false,
|
||||
Elements: []query.PathElement{
|
||||
{Name: "arr", Index: u64(18446744073709551615)},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "array element in wrong nested path",
|
||||
path: "arr[42]foo",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "array missing closing bracket",
|
||||
path: "arr[12",
|
||||
name: "array index in nested path",
|
||||
path: "arr[42].foo",
|
||||
expected: query.Path{
|
||||
Length: false,
|
||||
Elements: []query.PathElement{
|
||||
{Name: "arr", Index: u64(42)},
|
||||
{Name: "foo"},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "array index in deeper nested path",
|
||||
path: "arr[42].foo.bar[10]",
|
||||
expected: query.Path{
|
||||
Length: false,
|
||||
Elements: []query.PathElement{
|
||||
{Name: "arr", Index: u64(42)},
|
||||
{Name: "foo"},
|
||||
{Name: "bar", Index: u64(10)},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "length of array element",
|
||||
path: "len(arr[42])",
|
||||
expected: query.Path{
|
||||
Length: true,
|
||||
Elements: []query.PathElement{
|
||||
{Name: "arr", Index: u64(42)},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "length of array + trailing item",
|
||||
path: "len(arr)[0]",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "array plus sign index",
|
||||
path: "arr[+3]",
|
||||
name: "length of nested path within array element",
|
||||
path: "len(arr[42].foo)",
|
||||
expected: query.Path{
|
||||
Length: true,
|
||||
Elements: []query.PathElement{
|
||||
{Name: "arr", Index: u64(42)},
|
||||
{Name: "foo"},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty spaces in path",
|
||||
path: "data . target",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "array unicode digits",
|
||||
path: "arr[12]",
|
||||
name: "leading dot + empty spaces",
|
||||
path: ". data",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "array overflow uint64",
|
||||
path: "arr[18446744073709551616]",
|
||||
name: "length with leading dot + empty spaces",
|
||||
path: "len(. data)",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "array index then suffix",
|
||||
path: "field[1]suffix",
|
||||
expected: []query.PathElement{{Name: "field", Index: u64(1)}},
|
||||
wantErr: false,
|
||||
name: "Empty path error",
|
||||
path: "",
|
||||
expected: query.Path{},
|
||||
},
|
||||
{
|
||||
name: "length with leading dot + empty spaces",
|
||||
path: "test))((",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "length with leading dot + empty spaces",
|
||||
path: "array][0][",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -220,7 +306,7 @@ func TestParsePath(t *testing.T) {
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(tt.expected), len(parsedPath), "Expected %d path elements, got %d", len(tt.expected), len(parsedPath))
|
||||
require.Equal(t, len(tt.expected.Elements), len(parsedPath.Elements), "Expected %d path elements, got %d", len(tt.expected.Elements), len(parsedPath.Elements))
|
||||
require.DeepEqual(t, tt.expected, parsedPath, "Parsed path does not match expected path")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,19 +7,19 @@ import (
|
||||
|
||||
// CalculateOffsetAndLength calculates the offset and length of a given path within the SSZ object.
|
||||
// By walking the given path, it accumulates the offsets based on SszInfo.
|
||||
func CalculateOffsetAndLength(sszInfo *SszInfo, path []PathElement) (*SszInfo, uint64, uint64, error) {
|
||||
func CalculateOffsetAndLength(sszInfo *SszInfo, path Path) (*SszInfo, uint64, uint64, error) {
|
||||
if sszInfo == nil {
|
||||
return nil, 0, 0, errors.New("sszInfo is nil")
|
||||
}
|
||||
|
||||
if len(path) == 0 {
|
||||
if len(path.Elements) == 0 {
|
||||
return nil, 0, 0, errors.New("path is empty")
|
||||
}
|
||||
|
||||
walk := sszInfo
|
||||
offset := uint64(0)
|
||||
|
||||
for pathIndex, elem := range path {
|
||||
for pathIndex, elem := range path.Elements {
|
||||
containerInfo, err := walk.ContainerInfo()
|
||||
if err != nil {
|
||||
return nil, 0, 0, fmt.Errorf("could not get field infos: %w", err)
|
||||
@@ -56,7 +56,7 @@ func CalculateOffsetAndLength(sszInfo *SszInfo, path []PathElement) (*SszInfo, u
|
||||
// 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 {
|
||||
if pathIndex == len(path.Elements)-1 {
|
||||
return walk, offset, listInfo.elementSizes[index], nil
|
||||
}
|
||||
} else {
|
||||
|
||||
2
go.mod
2
go.mod
@@ -177,7 +177,7 @@ require (
|
||||
github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect
|
||||
github.com/libp2p/go-msgio v0.3.0 // indirect
|
||||
github.com/libp2p/go-nat v0.2.0 // indirect
|
||||
github.com/libp2p/go-netroute v0.3.0 // indirect
|
||||
github.com/libp2p/go-netroute v0.4.0 // indirect
|
||||
github.com/libp2p/go-reuseport v0.4.0 // indirect
|
||||
github.com/libp2p/go-yamux/v4 v4.0.2 // indirect
|
||||
github.com/lunixbochs/vtclean v1.0.0 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -599,8 +599,8 @@ github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0
|
||||
github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM=
|
||||
github.com/libp2p/go-nat v0.2.0 h1:Tyz+bUFAYqGyJ/ppPPymMGbIgNRH+WqC5QrT5fKrrGk=
|
||||
github.com/libp2p/go-nat v0.2.0/go.mod h1:3MJr+GRpRkyT65EpVPBstXLvOlAPzUVlG6Pwg9ohLJk=
|
||||
github.com/libp2p/go-netroute v0.3.0 h1:nqPCXHmeNmgTJnktosJ/sIef9hvwYCrsLxXmfNks/oc=
|
||||
github.com/libp2p/go-netroute v0.3.0/go.mod h1:Nkd5ShYgSMS5MUKy/MU2T57xFoOKvvLR92Lic48LEyA=
|
||||
github.com/libp2p/go-netroute v0.4.0 h1:sZZx9hyANYUx9PZyqcgE/E1GUG3iEtTZHUEvdtXT7/Q=
|
||||
github.com/libp2p/go-netroute v0.4.0/go.mod h1:Nkd5ShYgSMS5MUKy/MU2T57xFoOKvvLR92Lic48LEyA=
|
||||
github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s=
|
||||
github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=
|
||||
github.com/libp2p/go-yamux/v4 v4.0.2 h1:nrLh89LN/LEiqcFiqdKDRHjGstN300C1269K/EX0CPU=
|
||||
|
||||
Reference in New Issue
Block a user