---
# System prepended metadata

title: EPF6 - Week 14 Updates
tags: [EPF6, Weekly Updates]

---

# 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.