# EPF6 - Week 14 Updates ## tl;dr - Merged one PR([#15704](https://github.com/OffchainLabs/prysm/pull/15704)) for handling `Bitlist` and `Bitvector`. - Opened one PR([#15725](https://github.com/OffchainLabs/prysm/pull/15725)) for handling `List` over `List`. - Now SSZ-QL engine parses any random `BeaconState`/`BeaconBlock`! 🙌 ## Details ### `Bitlist` and `Bitvector` {%preview https://github.com/OffchainLabs/prysm/pull/15704 %} `Bitlist` and `Bitvector` are different with `List` and `Vector` in terms of the encoding/decoding methodology. `List[bool, N]` isn't same as `Bitlist[N]`, as we can encode much better with bits. You might refer to the `consensus-specs` [here](https://github.com/ethereum/consensus-specs/blob/master/ssz/simple-serialize.md#bitvectorn). Fortunately, Prysm relies on [`go-bitfield`](https://github.com/OffchainLabs/go-bitfield/tree/master) library for **all** `Bitlist`/`Bitvector` SSZ types in Go. I can determine whether `[]byte` is a `Bitlist`/`Bitvector` or not by checking the Go struct tag like: ```go if castType := tag.Get(castTypeTag); strings.Contains(castType, "go-bitfield") { isBitfield = true } ``` Also, I can safely leverage on one useful method from `go-bitfield` to get the bit length of raw bytes: ```go // SetLengthFromBytes determines the actual bitlist length from SSZ-encoded bytes. func (l *bitlistInfo) SetLengthFromBytes(rawBytes []byte) error { // Wrap rawBytes in a Bitlist to use existing methods. bl := bitfield.Bitlist(rawBytes) return l.SetLength(bl.Len()) } ``` ### `List[List[...], ...]` {%preview https://github.com/OffchainLabs/prysm/pull/15725 %} When I tried to parse `BeaconBlock`, the test failed because of this path: `.body.execution_payload`. `ExecutionPayload` has a field named `transactions` which contains `Transaction` as a `List`. Since `Transaction` is also a `List` of byte, the size for each transaction can be vary. My current design for `listInfo` only holds the general information of the element: ```go // listInfo holds information about a SSZ List type. // // length is initialized with zero, // and can be set using SetLength while populating the actual SSZ List. type listInfo struct { // limit is the maximum number of elements in the list. limit uint64 // element is the SSZ info of the list's element type. element *sszInfo // length is the actual number of elements at runtime (0 if not set). length uint64 } ``` So the best way to calculate the size (in byte) is just multiplying `element.Size()` with the `length`. This works well for trivial cases like `List[uint64, 5]` but it becomes much complex if the element is a variable-sized type as well. So I added one field under `listInfo`: ```go= // elementSize caches the each element's byte size for variable-sized type elements elementSize []uint64 ``` and `elementSize` is appended **after** recursively populating the `sszInfo` of each element. ```go if listInfo.element.isVariable { listInfo.elementSize = 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 { return fmt.Errorf("could not populate nested list element at index %d: %w", i, err) } listInfo.elementSize = append(listInfo.elementSize, listInfo.element.Size()) } } ``` One other thing to consider is about offset bytes. As you might know, `List` type is a variable-sized type which means it always results 4-byte of offset for indicating where the actual data was encoded. Thus, if `transations` contains three transactions, we should consider 12 (4*3) bytes more as well as the actual size of each transaction. This led me to add one more method under `listInfo`: ```go // 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 } ``` `OffsetBytes()` is used for 1) calculating the size of `Container` (which has `List` field) and 2) populating the length with an actual value. ### Test Results! {%preview https://hackmd.io/@junsong/Bkl7aa2oxl %} You might check this out, which includes the result of parsing `BeaconState` and `BeaconBlock`. For this EPF project, I planned to support two APIs: - `/prysm/v1/beacon/states/{state_id}/query` - `/prysm/v1/beacon/blocks/{block_id}/query` So it is enough to test with those two objects. I yet have one remaining task for getting one element using index(e.g., `validators[3]`), but this doesn't look that hard.