Files
prysm/encoding/ssz/query/ssz_info.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

215 lines
5.0 KiB
Go

package query
import (
"errors"
"fmt"
"reflect"
"strings"
)
// SszInfo holds the all necessary data for analyzing SSZ data types.
type SszInfo struct {
// Type of the SSZ structure (Basic, Container, List, etc.).
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
// For Container types.
containerInfo *containerInfo
// For List types.
listInfo *listInfo
// For Vector types.
vectorInfo *vectorInfo
// For Bitlist types.
bitlistInfo *bitlistInfo
// For Bitvector types.
bitvectorInfo *bitvectorInfo
}
func (info *SszInfo) Size() uint64 {
if info == nil {
return 0
}
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()
default:
return 0
}
}
func (info *SszInfo) ContainerInfo() (*containerInfo, error) {
if info == nil {
return nil, errors.New("SszInfo is nil")
}
if info.sszType != Container {
return nil, fmt.Errorf("SszInfo is not a Container type, got %s", info.sszType)
}
if info.containerInfo == nil {
return nil, errors.New("SszInfo.containerInfo is nil")
}
return info.containerInfo, nil
}
func (info *SszInfo) ListInfo() (*listInfo, error) {
if info == nil {
return nil, errors.New("SszInfo is nil")
}
if info.sszType != List {
return nil, fmt.Errorf("SszInfo is not a List type, got %s", info.sszType)
}
return info.listInfo, nil
}
func (info *SszInfo) VectorInfo() (*vectorInfo, error) {
if info == nil {
return nil, errors.New("SszInfo is nil")
}
if info.sszType != Vector {
return nil, fmt.Errorf("SszInfo is not a Vector type, got %s", info.sszType)
}
return info.vectorInfo, nil
}
func (info *SszInfo) BitlistInfo() (*bitlistInfo, error) {
if info == nil {
return nil, errors.New("SszInfo is nil")
}
if info.sszType != Bitlist {
return nil, fmt.Errorf("SszInfo is not a Bitlist type, got %s", info.sszType)
}
return info.bitlistInfo, nil
}
func (info *SszInfo) BitvectorInfo() (*bitvectorInfo, error) {
if info == nil {
return nil, errors.New("SszInfo is nil")
}
if info.sszType != Bitvector {
return nil, fmt.Errorf("SszInfo is not a Bitvector type, got %s", info.sszType)
}
return info.bitvectorInfo, nil
}
// String implements the Stringer interface for SszInfo.
// This follows the notation used in the consensus specs.
func (info *SszInfo) String() string {
if info == nil {
return "<nil>"
}
switch info.sszType {
case List:
return fmt.Sprintf("List[%s, %d]", info.listInfo.element, info.listInfo.limit)
case Vector:
if info.vectorInfo.element.String() == "uint8" {
// Handle byte vectors as BytesN
// See Aliases section in SSZ spec:
// https://github.com/ethereum/consensus-specs/blob/master/ssz/simple-serialize.md#aliases
return fmt.Sprintf("Bytes%d", info.vectorInfo.length)
}
return fmt.Sprintf("Vector[%s, %d]", info.vectorInfo.element, info.vectorInfo.length)
case Bitlist:
return fmt.Sprintf("Bitlist[%d]", info.bitlistInfo.limit)
case Bitvector:
return fmt.Sprintf("Bitvector[%d]", info.bitvectorInfo.length)
default:
return info.typ.Name()
}
}
// Print returns a string representation of the SszInfo, which is useful for debugging.
func (info *SszInfo) Print() string {
if info == nil {
return "<nil>"
}
var builder strings.Builder
printRecursive(info, &builder, "")
return builder.String()
}
func printRecursive(info *SszInfo, builder *strings.Builder, prefix string) {
var sizeDesc string
if info.isVariable {
sizeDesc = "Variable-size"
} else {
sizeDesc = "Fixed-size"
}
switch info.sszType {
case Container:
builder.WriteString(fmt.Sprintf("%s (%s / size: %d)\n", info, sizeDesc, info.Size()))
for i, key := range info.containerInfo.order {
connector := "├─"
nextPrefix := prefix + "│ "
if i == len(info.containerInfo.order)-1 {
connector = "└─"
nextPrefix = prefix + " "
}
builder.WriteString(fmt.Sprintf("%s%s %s (offset: %d) ", prefix, connector, key, info.containerInfo.fields[key].offset))
if nestedInfo := info.containerInfo.fields[key].sszInfo; nestedInfo != nil {
printRecursive(nestedInfo, builder, nextPrefix)
} else {
builder.WriteString("\n")
}
}
case List:
builder.WriteString(fmt.Sprintf("%s (%s / length: %d, size: %d)\n", info, sizeDesc, info.listInfo.length, info.Size()))
case Bitlist:
builder.WriteString(fmt.Sprintf("%s (%s / length (bit): %d, size: %d)\n", info, sizeDesc, info.bitlistInfo.length, info.Size()))
default:
builder.WriteString(fmt.Sprintf("%s (%s / size: %d)\n", info, sizeDesc, info.Size()))
}
}