Files
prysm/encoding/ssz/query/query.go
Jun Song 5a897dfa6b SSZ-QL: Add endpoints (BeaconState/BeaconBlock) (#15888)
* Move ssz_query objects into testing folder (ensuring test objects only used in test environment)

* Add containers for response

* Export sszInfo

* Add QueryBeaconState/Block

* Add comments and few refactor

* Fix merge conflict issues

* Return 500 when calculate offset fails

* Add test for QueryBeaconState

* Add test for QueryBeaconBlock

* Changelog :)

* Rename `QuerySSZRequest` to `SSZQueryRequest`

* Fix middleware hooks for RPC to accept JSON from client and return SSZ

* Convert to `SSZObject` directly from proto

* Move marshalling/calculating hash tree root part after `CalculateOffsetAndLength`

* Make nogo happy

* Add informing comment for using proto unsafe conversion

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2025-10-20 16:24:06 +00:00

84 lines
2.6 KiB
Go

package query
import (
"errors"
"fmt"
)
// 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) {
if sszInfo == nil {
return nil, 0, 0, errors.New("sszInfo is nil")
}
if len(path) == 0 {
return nil, 0, 0, errors.New("path is empty")
}
walk := sszInfo
offset := uint64(0)
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)
}
fieldInfo, exists := containerInfo.fields[elem.Name]
if !exists {
return nil, 0, 0, fmt.Errorf("field %s not found in containerInfo", elem.Name)
}
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
}