---
# System prepended metadata

title: EPF6 Final Report
tags: [EPF6]

---

## 💪 tl;dr

- 💻 Designed and developed a **query language** for a arbitrary [**SSZ**](https://github.com/ethereum/consensus-specs/blob/master/ssz/simple-serialize.md) object from scratch, on [Prysm](https://github.com/OffchainLabs/prysm).
- 🚀 Shipped total **9** PRs regarding SSZ-QL, and more to come!
    - My [Prysm PRs](https://github.com/OffchainLabs/prysm/pulls?q=is%3Apr+author%3Asyjn99+created%3A2025-06-01..2025-11-01+) from June to October.
- [Project Proposal](https://github.com/eth-protocol-fellows/cohort-six/blob/master/projects/ssz-ql-with-merkle-proofs.md) | [All Updates & Write-ups](https://hackmd.io/@junsong/SkB51ZR1be) | [Prysm Discord Thread](https://discord.com/channels/476244492043812875/1387734369527136297) 
- **Kick-off Presentation** ([Slide](https://docs.google.com/presentation/d/1YMbUS3J7YAartbJ56r4vensSIwpcSV_Jc-lzUeNv-tM/edit?usp=sharing), [Recording](https://youtu.be/UqkQdlyEwdA?si=0JzuP_mUwSnmRgfO&t=1337)) @ EthCC Cannes | **Final Presentation** @ Devconnect Buenos Aires ([Slide](https://docs.google.com/presentation/d/1nw78MCan830kJxkRcvaG81lRh9ynZhxJyhmbpa3Lu9c/edit?usp=sharing), *Recording Coming soon!*)

## 🎯 Abstract

**SSZ Query Language**, (in short, **SSZ-QL**) is a path-based query language on any arbitrary SSZ object. A query result can be accompanied with the corresponding Merkle proof, so that any users like Ethereum Light Client can verify the value by themselves. SSZ-QL introduces a standardized API for fetching specific `BeaconState` fields with cryptographic proofs, resolving the 250>=MB state retrieval inefficiency and dramatically enhancing the **verifiability** of the CL. (You can check the detail in the [project proposal](https://github.com/eth-protocol-fellows/cohort-six/blob/master/projects/ssz-ql-with-merkle-proofs.md#motivation).)

I and [Nando](https://github.com/fernantho/) have **designed** and **implemented** the SSZ-QL on [**Prysm**](https://github.com/offchainlabs/prysm/) from scratch. Currently, there are two beacon endpoints that expose this feature: `POST /eth/v1/beacon/states/{state_id}/query` and
`POST /eth/v1/beacon/blocks/{block_id}/query`. Each endpoint anchors to `BeaconState` and `BeaconBlock` respectively, enabling to ask any nested path with proof. I mostly focus on developing a **SSZ-QL engine** which "compile"s a Go Struct into SSZ object and returns the exact range in byte for a given query.

### 🕹️ Example

Alice wants to verify that "the withdrawal credentials for 42nd validator is `0x00ec...3594`". She now can request to the Prysm beacon node like:

```
POST /eth/v1/beacon/states/head/query
Content-Type: application/json
```

```json
{
    "query": ".validators[42].withdrawal_credentials",
    "include_proof": true
}
```

After a few milliseconds, she would receive **SSZ-serialized bytes** that can be decoded like:

```json
{
  "root": "0xa0afb00ae0b34b29b3ba1ba4af7c93e1129218b4cf1bb0871ca8382a785b2b18",
  "result": "0x00ec7ef7780c9d151597924036262dd28dc60e1228f4da6fecf9d402cb3f3594",
  "proof": {
    "leaf": "0x00ec7ef7780c9d151597924036262dd28dc60e1228f4da6fecf9d402cb3f3594",
    "gindex": 213355,
    "proofs": [
      "0x1111111111111111111111111111111111111111111111111111111111111111",
      "0x2222222222222222222222222222222222222222222222222222222222222222",
      "0x3333333333333333333333333333333333333333333333333333333333333333",
      // ...
      "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
    ]
  }
}
```

With this data, Alice can use the provided `proof` object and the known state `root` to verify that the `result` (the "withdrawal credentials") is indeed a valid part of the `BeaconState` at the requested state ID, without needing to download the entire state.


## 🐣 Implementing SSZ-QL (in a nutshell)

<details>
    <summary>How does Prysm generate a SSZ object?</summary>
    
Every SSZ object is defined as a Proto message in `*.proto`, and a couple of scripts([`./hack/update-go-pbs.sh`](https://github.com/OffchainLabs/prysm/blob/develop/hack/update-go-pbs.sh), [`./hack/update-go-ssz.sh`](https://github.com/OffchainLabs/prysm/blob/develop/hack/update-go-ssz.sh)) generate a Go-compatible Struct definition and SSZ methods like `HashTreeRoot()`.
</details>

First thing first, SSZ-QL engine needs to compile (See `AnalyzeObject` function [here](https://github.com/OffchainLabs/prysm/blob/6735c921f80ff1b78027236b880ecfebd08ee9fb/encoding/ssz/query/analyzer.go#L12-L28)) a Go Struct into SSZ object so that it is aware of all information for handling queries. It matches a Go Struct with equivalent SSZ types using Go Reflection, so it is fully working in generic way. I [intended](https://github.com/eth-protocol-fellows/cohort-six/blob/master/projects/ssz-ql-with-merkle-proofs.md#possible-challenges) to code this way as SSZ can be widely used rather than `BeaconState` or `BeaconBlock`.

With the information returned from the engine, we can simply call a function (See `CalculateOffsetAndLength` function [here](https://github.com/OffchainLabs/prysm/blob/6735c921f80ff1b78027236b880ecfebd08ee9fb/encoding/ssz/query/query.go#L8-L83)) to calculate the **exact range in bytes** to read in an encoded byte string, also following a design choice to be more "generic".

This feature is exposed as a couple of beacon endpoints, which aren't yet standardized but I'm trying to ship at this moment. (See the draft proposal [here](https://github.com/syjn99/beacon-APIs/pull/1).) The API handler leverages on the SSZ-QL engine, by [calling `CalculateOffsetAndLength` function](https://github.com/OffchainLabs/prysm/blob/6735c921f80ff1b78027236b880ecfebd08ee9fb/beacon-chain/rpc/prysm/beacon/ssz_query.go#L86) in the handler body.

## ✅ Takeaways

### Write-ups and Communication

I've tried to document as much as I can for a couple of reasons. First, write-ups help communicating with my mentors and colleague. As it is typical to work in remote, I believe that providing an enough context is crucial for efficient communication. It's fortunate that I enjoy and prefer jotting my thought down. For example, this [write-up](https://hackmd.io/@junsong/Byr3_lfPxl) helped us a lot to discuss an overall direction on this project at the early phase.

Second, this is one of ethoses in Ethereum ecosystem to share what I learnt. All key players in the current core dev scene are proficient on sharing their own thoughts and knowledges, which are essential for learners (like me) to study themselves. I wrote a couple of informative articles ([1](https://hackmd.io/@junsong/H101DKnwxl), [2](https://hackmd.io/@junsong/By4LArZtxg)) about SSZ implementation in Golang.

### Design first, PoC second, and then Implementation

Nando and I spent a couple of weeks (until [Week 5](https://hackmd.io/@junsong/SkrAo85Lxe)) to discuss the "endgame" of SSZ-QL. We had lots of conversations regarding the full specifiction, and tried to scope it down for this cohort period. We also listed tasks with its priority (Must have, Nice to have, etc.).

After design phase, I started to show the feasibility of our design. We often face few discrepancies between a blueprint and an actual implementation, and the only way to bridge the gap is to explore by very basic Proof-of-Concept work. An [update for Week 7](https://hackmd.io/@junsong/S1UsoDuwel) is worth reading as it contains the full story of the first PoC.

PoC is done: I successfully verified a doability of the implementation path. After that, I could fully focus on gradually implementing the features which were already divided in a fine-grained manner. (See [my PRs](https://github.com/OffchainLabs/prysm/pulls?q=is:pr+author:syjn99+created:2025-09-01..2025-11-01+) after August.)

The process I described above is definitiely precious. **I now have an experience of implementing a standalone feature from scratch by structuring the plan myself.**

### ...and Technical Aspects

On top of all these valuable takeaways, I become a better Ethereum dev. Through various experiences on contributing to Prysm, now I feel confident about the **overall project structure** and convention, which is invaluable. 

Also, by delving into SSZ specification itself, I'm now able to explain how a **SSZ** object is serialized/deserialized, and how we can calculate the hash tree root of it.

As implementing a SSZ-QL engine heavily relies on [Go Reflection](https://go.dev/blog/laws-of-reflection), I had to be proficient on dealing with `reflect.Type` or `reflect.Value` for processing the SSZ object as well. Writing in Go is always exciting and fun, I really love the simplicity of the language.

## ⭐️ What's Next?

As the [full specification](https://hackmd.io/@fernantho/rkjsksrIxg) implies, there are sufficient rooms for improvement. The query language itself can be more expressive: for example, it can allow a wildcard or filters. But I believe the current work is enough for showcase, and it would be great if we can hear from the users and Light Client developers to make SSZ-QL better.

Meanwhile, I was also interested in the other topic in Prysm: [**Migrate e2e to Kurtosis**](https://github.com/eth-protocol-fellows/cohort-six/blob/master/projects/project-ideas.md#prysm-migrate-e2e-to-kurtosis). Prysm has a dedicated [issue](https://github.com/OffchainLabs/prysm/issues/15320) for this, and to my knowledge, this project will require a dedicated lead for us to make real headway. I would love to keep this momentum up.

## 🙏 Thanks

First thing first, I would like to appreciate [Josh](https://x.com/joshdavislight) and [Mario](https://x.com/TMIYChao) for coordinating this wonderful program! It is an honor for me to join this fellowship. Without the fellowship, I couldn't meet all these great and nice people in Ethereum. Office hours were fantastic as well, I was able to learn a lot from smart and experienced people.

My mentors, [Radek](https://github.com/rkapka) and [Bastin](https://github.com/Inspector-Butters), have been incredibly supportive and guiding throughout this journey. I also really appreciate Radek's responsive and incisiveness whenever I got stuck in somewhere or need help. Knowing the team members had been busy with shipping Fusaka makes their support even more grateful. [Manu](https://github.com/nalepae) isn't my official mentor, but I learned a lot from offline conversations with him at Cannes. Thanks!

I enjoyed working with my colleague, [Nando](https://github.com/fernantho) for this cohort. I cannot imagine this project without him, we discussed a lot on designing the SSZ-QL from scratch. His insights as a Smart Contract dev and DVT expert were definitely helpful to develop and justify the full concept of SSZ-QL.

And all other fellows who are on the home stretch, congrats! It was nice to meet you all in off-/on-line, let's keep building 🤝.

## 📚 Resources & Others

- [Project Proposal](https://github.com/eth-protocol-fellows/cohort-six/blob/master/projects/ssz-ql-with-merkle-proofs.md)
- [All Updates & Write-ups](https://hackmd.io/@junsong/SkB51ZR1be) 
- [Beacon API Proposal](https://github.com/syjn99/beacon-APIs/pull/1)

### Specification

- [`postStateQuery` & `postBlockQuery` API Specification](https://hackmd.io/@junsong/rkAN9lIIxx)
- [SSZ-QL: Full Specification](https://hackmd.io/@fernantho/rkjsksrIxg)

### Meta Tracking Issue

- [SSZ Query Language Meta Tracking Issue #15587](https://github.com/OffchainLabs/prysm/issues/15587): This issue was used for project management. Normally I splitted the tasks up, and then gradually worked on each task for better reviewing experience. It's fortunate to finish all `MUST HAVE` tasks!


### All merged PRs related to SSZ-QL

<details>
  <summary>🔥 List of PRs:</summary>
  
- [refactor: removing redundant codes in htrutils.go #15453](https://github.com/OffchainLabs/prysm/pull/15453)
- [Initialize SSZ-QL package with support for fixed-size types #15588](https://github.com/OffchainLabs/prysm/pull/15588)
- [SSZ-QL: Handle `List` type & Populate the actual value dynamically #15637](https://github.com/OffchainLabs/prysm/pull/15637)
- [SSZ-QL: Handle `Vector` type & Add SSZ tag parser for multi-dimensional parsing #15668](https://github.com/OffchainLabs/prysm/pull/15668)
- [SSZ-QL: Handle `Bitlist` and `Bitvector` #15704](https://github.com/OffchainLabs/prysm/pull/15704)
- [SSZ-QL: Support nested `List` type #15725](https://github.com/OffchainLabs/prysm/pull/15725)
- [SSZ-QL: Access n-th element in `List`/`Vector`. #15767](https://github.com/OffchainLabs/prysm/pull/15767)
- [SSZ-QL: use `fastssz`-generated `SizeSSZ` method & clarify `Size` method #15864](https://github.com/OffchainLabs/prysm/pull/15864)
- [SSZ-QL: Add endpoints (`BeaconState`/`BeaconBlock`) #15888](https://github.com/OffchainLabs/prysm/pull/15888)

</details>

![image](https://hackmd.io/_uploads/HyI6LxCkZx.png)*Heading to Top 20 contributor in [Prysm](https://github.com/OffchainLabs/prysm)...*