# EPF6 - Week 15 Updates ## tl;dr - Merged one PR([#15725](https://github.com/OffchainLabs/prysm/pull/15725)) for handling `List` over `List`. - Opened one PR([#15767](https://github.com/OffchainLabs/prysm/pull/15767)) that parses an index for accessing an element in `List`/`Vector`. - Now we're almost ready! Section *"Implement the SSZ-QL engine."* in the [meta tracking issue](https://github.com/OffchainLabs/prysm/issues/15587) is done. We can move onto the next section, *Expose SSZ-QL as a new endpoint.*. ## Details ### Accesing n-th element in `List`/`Vector` {%preview https://github.com/OffchainLabs/prysm/pull/15767 %} So far, my SSZ-QL engine can only parse the full `List`/`Vector`, which means it's not possible to get each element on homogenous collection type. The PR above adds an accessor (by index) feature for `PathElement`. For example, we can now query with the path like `.validators[42]`. ```go= // PathElement represents a single element in a path. type PathElement struct { Name string // [Optional] Index for List/Vector elements Index *uint64 } ``` Each `PathElement` might have `Index` as a member. It is the pointer of `uint64` which means it can be `nil` (and most of the case, it should be `nil` unless it is either `List` or `Vector`.). ```go= // 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", index, elem.Name) } 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", index, elem.Name) } offset += index * vectorInfo.element.Size() walk = vectorInfo.element default: return nil, 0, 0, fmt.Errorf("field %s type %s cannot apply index", elem.Name, walk.sszType) } } ``` Now in `CalculateOffsetAndLength` function, it checks whether `Index` is `nil` or not. If not, the current `walk` should be either `List`/`Vector`. For `Vector` case, it is quite easy: adding a multiple of each element's size on current `offset`. However, for the case of `List`, it becomes sophisticated. We should check whether the element is variable-sized or not. Fortunately, we've saved all the size (in bytes) at `elementSizes` slice, so that we can sum up until we reaches the index. I added a "hacky" part for the case If this is the last element of given path: As `elementSizes` is managed by `listInfo` instance, it is impossible for the element to know its size in its own. If the path ends with index accessing (e.g., `.validators[42]`), I thought it would be better to return with the correct byte sizes when it encounters the end of the path. ### Implementing a SSZ-QL engine is done! ![image](https://hackmd.io/_uploads/B1utP5Inll.png) In the [meta tracking issue](https://github.com/OffchainLabs/prysm/issues/15587), I divided my **must have** part into two. Provided the PR above get merged in a few days, I'm confident that the first part of **must have** (*"Implement the SSZ-QL engine"*) is DONE. Adding a couple of new endpoints shouldn't be a blocker, as the core feature is already implemented for last few weeks. 🚀