# SSZ-QL: Proof-of-Concept
> [!Important]
> This document is based on this version: https://github.com/syjn99/prysm/tree/eac60374f13dcdcf7d35febdfefb5f912ae886de. Some links might be updated while I'm working on it continuously.
---
{%preview https://github.com/syjn99/prysm/pull/1 %}
> [!Note]
> Following up the [Option Discovery Phase](https://hackmd.io/@junsong/Byr3_lfPxl), this document will contain a part of implementation details. You can also see the diff in this [PR](https://github.com/syjn99/prysm/pull/1).
## Introduction
There was some discussion on my last write-up, and we all synced with each other that ["Option 2. Marshal everything, and treat as string of bytes"](https://hackmd.io/@junsong/Byr3_lfPxl#Option-2-Marshal-everything-and-treat-as-string-of-bytes--Byte-Parsing-Engine) is better than the Option 1. One thing I worried about the Option 2 was the **overhead** in marshalling the full `BeaconState` (almost) every time. (You might wonder how `BeaconState` is fetched in Prysm, then you can check the [very first section](https://hackmd.io/@junsong/Byr3_lfPxl#Background-Fetching-BeaconState) of my last write-up.)
In this point, Radek provided a concrete time ([~120ms](https://discord.com/channels/476244492043812875/1387734369527136297/1399399388345925785)), and we think that it is not that bad. As Bastin [mentioned](https://discord.com/channels/476244492043812875/1387734369527136297/1399400934517244096), a significant proportion of API call would be about the head state, so I [thought](https://discord.com/channels/476244492043812875/1387734369527136297/1399418641698525316) it was quite early to discuss caching mechanism due to the low cache hit-rate.
So it's time to show the **feasibility** of this approach. Let's start with the actual test of round trip. You can see the full diff [here](https://github.com/syjn99/prysm/pull/1).
## Annotated process with a concrete example: Get `.data.target.root` from `IndexedAttestationElectra`
```go
// Start with a pointer to empty object and calculate SSZ info of `IndexedAttestationElectra`.
info, err := sszquery.PreCalculateSSZInfo(ðpb.IndexedAttestationElectra{})
require.NoError(t, err, "PreCalculateSSZInfo should not return an error")
```
First, we need to *precalculate* the target SSZ type. `PreCalculateSSZInfo` analyzes the received value **recursively** and returns `sszInfo` object that has all information needed.
```go!
// sszInfo holds the pre-calculated SSZ data for a struct type.
// TODO: maybe we should another field for which type? (e.g., "Container", "List", etc.)
type sszInfo struct {
// 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 structs, additional information is stored:
//
// fieldOffsets maps a field's JSON name to its offset within the struct's fixed part.
fieldOffsets map[string]uint64
// goFieldNames maps a field's JSON name to its Go struct field name (e.g., "attesting_indices" -> "AttestingIndices").
// TODO: do we need this?
goFieldNames map[string]string
// fieldInfos maps a field's JSON name to its SSZ info (for nested structs).
fieldInfos map[string]*sszInfo
}
```
`sszInfo` is not yet finalized (b/c it's PoC) but contains enough data to calculate our example path.
```
Struct (fixedSize: 228, isVariable: true)
├─ attesting_indices (offset: 0) Struct (fixedSize: 4, isVariable: true)
├─ data (offset: 4) Struct (fixedSize: 128, isVariable: false)
│ ├─ beacon_block_root (offset: 16) Struct (fixedSize: 32, isVariable: false)
│ ├─ committee_index (offset: 8) Struct (fixedSize: 8, isVariable: false)
│ ├─ slot (offset: 0) Struct (fixedSize: 8, isVariable: false)
│ ├─ source (offset: 48) Struct (fixedSize: 40, isVariable: false)
│ │ ├─ epoch (offset: 0) Struct (fixedSize: 8, isVariable: false)
│ │ └─ root (offset: 8) Struct (fixedSize: 32, isVariable: false)
│ └─ target (offset: 88) Struct (fixedSize: 40, isVariable: false)
│ ├─ epoch (offset: 0) Struct (fixedSize: 8, isVariable: false)
│ └─ root (offset: 8) Struct (fixedSize: 32, isVariable: false)
└─ signature (offset: 132) Struct (fixedSize: 96, isVariable: false)
```
Printing the `info` looks like above. As `PreCalculateSSZInfo` runs in recursive manner, you can also see `Checkpoint` container is analyzed as well.
I'd like to note we *might* cache the result for the SSZ types that are often used (e.g. `BeaconState`).
```go
// Calculate the offset and length for the path ".data.target.root".
path, err := sszquery.ParsePath(".data.target.root")
require.NoError(t, err, "ParsePath should not return an error")
```
Given `sszInfo`, we can move on to parse the path. Currently, I defined `PathElement` only to include the (field) name, and parsing the accessor for array element is out of the scope of this PoC.
```go
offset, length, err := sszquery.CalculateOffsetAndLength(info, path)
require.NoError(t, err, "CalculateOffsetAndLength should not return an error")
```
`CalculateOffsetAndLength` returns **1) the offset and 2) length to read in bytes**. As we treat `BeaconState` as serialized, those are enough. `CalculateOffsetAndLength` doesn't support the various sized type yet, but it seems it is a low-hanging fruit if we include that information in `sszInfo`.
```go
// Marshal the real object to SSZ.
realObj := ðpb.IndexedAttestationElectra{
// ...
}
realObjMarshaled, err := realObj.MarshalSSZ()
require.NoError(t, err)
// With the offset and length, extract the target value from the marshaled data.
targetValue := realObjMarshaled[offset : offset+length]
assert.DeepEqual(t, expectedTargetRoot, targetValue, "Extracted target value should match expected")
```
Alright, we now have all. Extract only needed part from bytes slice, and then unmarshal it if it is needed. In PoC, I just compared two byte slices (`assert.DeepEqual`), but it is trivial to unmarshal a bytes slice into wanted struct.
## Limitation & Future Works
- The analyzer heavily relies on the metadata of the protoc-generated struct like `json`, `ssz-size`, and `ssz-max`. Indeed, this relies on `fastssz` library which Prysm uses it for generating the structs based on `*.proto` file. You can check out the encoding logic in `fastssz` repository [here](https://github.com/ferranbt/fastssz/blob/f0b0ed3a7f4cfae1c169cbdc66533a2665e215e8/sszgen/generator/generator.go#L852).
- `sszInfo` can be discussed more. I think it must include more details to deal with the various sizeed types like `List`. Also if it's homogenous collection (e.g. `List`, `Vector`), it SHOULD contain the `sszInfo` of the element.
- Can this be compatible with merkle proof generation?