---
# System prepended metadata

title: JAM Protocol Mini Fuzzer Test Infra
tags: [Testing]

---

# MiniFuzz in JAM Conformance Testing 實作細節整理

[Source Code](https://github.com/davxy/jam-conformance/blob/main/fuzz-proto/minifuzz/minifuzz.py)

[Document](https://github.com/davxy/jam-conformance/blob/main/fuzz-proto/README.md)

## schema
使用 JAM codec 的訊息 schema（fuzz-v1.asn 對應的 coder/decoder）。

接收/回傳 decode/encode message 

### Types：

| Request       | Response   | 說明 |
|---------------|-----------|------|
| **PeerInfo**  | PeerInfo  | 連線握手、版本協商（需驗證 `fuzz_version` / `jam_version` / `fuzz_features`） |
| **Initialize**| StateRoot | 初始化或重置 target 狀態，回傳初始狀態 root |
| **ImportBlock** | StateRoot / Error | 匯入 block：<br> - 成功 → 回傳新的 state root <br> - 失敗（無效 block 等）→ 回 Error |
| **GetState**  | State     | 回傳完整 key-value 狀態（當 fuzzer 偵測 state root mismatch 時觸發） |
| *(Error)*     | *(特殊)* | 只在協議允許的錯誤時使用（如 ImportBlock 失敗） |


溝通 message 皆是 length-prefix + JAM codec。

### raw_to_json
- 功能：把收到的 **原始位元組資料 (raw bytes)** 依照 JAM codec decode。  
- 輸出：回傳一個 dict，內容包含 **message_kind** (例如 `peer_info`, `state_root`, `error` …) 與其對應的欄位資料。  
- 用途：讓後續程式能依照 message 種類與欄位做驗證或比對。

## 基本通訊

1. Unix domain socket 保持單一連線、同步 request-response
2. 訊息 framing：每個訊息用 4-byte little-endian 前綴長度（uint32 LE），接著完整 payload bytes。
3. Sequential：收到 request -> 處理並回應 -> 下一個 request

## 握手（PeerInfo）
連線建立後，target 必須等待 fuzzer 傳送 PeerInfo 訊息，處理完畢後回傳自己的 PeerInfo。
這是最初的握手程序，主要檢查雙方的協議版本與功能支援是否一致。

Reponse parse 後 包含 peer_info

### PeerInfo 結構

- fuzz_version：Fuzzer 協議版本 (須與預期一致)
- jam_version：JAM 協議版本 (須與預期一致)
- fuzz_features：bitmask，標示支援的功能
```python
target_peer_info = response["peer_info"]
```
fuzz_version, jam_version 需 match
```python
 # Check Fuzz version
fuzz_version_got = target_peer_info.get("fuzz_version")
fuzz_version_exp = precomputed_peer_info.get("fuzz_version")
if fuzz_version_exp != fuzz_version_got:
    print(f"Unexpected Fuzzer protocol version. Expected: {fuzz_version_exp}, Got: {fuzz_version_got}")
    return False
# Check JAM version
jam_version_got = target_peer_info.get("jam_version")
jam_version_exp = precomputed_peer_info.get("jam_version")
if jam_version_exp != jam_version_got:
    print(f"Unexpected JAM protocol version. Expected: {jam_version_exp}, Got: {jam_version_got}")
    return False
return True
```
fuzz_features 要用 bitmask（FEATURE_ANCESTRY(1),  FEATURE_FORKS(2)），不得包含其他bit 。
```python
features_got = target_peer_info.get("fuzz_features")
if features_got & ~FEATURES_MASK != 0:
    print(f"Unexpected Fuzzer features enabled: 0x{features_got:08x}")
    print("Valid features:")
    print(f"- Ancestry: 0x{FEATURE_ANCESTRY:08x}")
    print(f"- Forks: 0x{FEATURE_FORKS:08x}")
    return False
```

## 其他訊息比對 (General Message Match)

除了 peer_info 這類需要額外欄位檢查的訊息，其餘所有訊息 都必須比對各欄位和預先定義的結果 完全一致。
``` python
# All other messages must match
is_matching = response == precomputed
if not is_matching:
    print("Unexpected target response")
    print("--------------------------")
    print("Expected:")
    print(json.dumps(precomputed, indent=4))
    print("---")
    print("Returned:")
    print(json.dumps(response, indent=4))
return is_matching
```
適用訊息種類 error 和 peer_info 以外的 type


## Initialize

- 接收 Initialize：將 fuzzer 設為提供的初始 state。
- 回應當前 head 的 state root

## ImportBlock

- ImportBlock，把 block 套用到目前 head
    - 成功：回 StateRoot (Fuzzer 會比對)
    - 失敗（block invalid / protocol-defined failure）：回 Error（保持連線，等待 request）。


## GetState

- 要求 target 回傳完整 key/value state。

## Error 處理

- 協議內錯誤（block invalid）：回 Kind:Error 和 Error message（保持連線可繼續下一輪）。

### Features
- FEATURE_ANCESTRY： 在 Initialize 處理 ancestors，
- FEATURE_FORKS：能接受同一 parent block 下的多個 mutations


### Minifuzz CLI Flags

| Flag             | Type   | Default                 | 說明 |
|------------------|--------|-------------------------|------|
| `-d, --trace-dir` | str    | 必須在 cli 提供                       | 指定包含預先產生的 message 檔案目錄 |
| `-s, --spec`     | str    | `tiny` (`tiny` / `full`)  | 指定使用的 spec（tiny / full） |
| `-v, --verbose`  | bool   | `false`                 | 開啟詳細輸出，印出完整 JSON 訊息 |
| `--target-sock`  | str    | `/tmp/jam_target.sock`   | 目標 target 綁定的 Unix domain socket 路徑 |
| `--stop-after`   | int    | `1000`                  | 最多處理多少組 fuzzer/target 檔案 pair 後停止 |



### 主要程式 Flow

- 1. 讀取 fuzzer binary 檔

使用 open(..., 'rb') 將整個 .bin 檔案載入 request_bytes（原始 bytes）。
```python
try:
    with open(fuzzer_file, 'rb') as f:
        request_bytes = f.read()
except Exception as e:
    print(f"Error reading fuzzer file '{fuzzer_file}': {e}")
    break
```

- 2. decode（轉成可讀 JSON / dict）

raw_to_json(request_bytes)：把 JAM codec 的 raw bytes decode 成 Python dict。

print_message(...)：在 verbose 模式下印出完整 JSON；否則僅印 <message_kind>。
```python
try:
    request_msg = raw_to_json(request_bytes)
    print_message(request_msg, True, args.verbose)
except Exception as e:
    print(f"Error decoding precomputed fuzzer request: {e}")
    break
```

- 3. 傳送（framing 規則）

framing 規則：4-byte little-endian 長度前綴（struct.pack('<I', length)） + payload bytes。
```python
try:
    request_len = len(request_bytes)
    request_len_bytes = struct.pack('<I', request_len)
    sock.sendall(request_len_bytes)
    sock.sendall(request_bytes)
    if args.verbose:
        print(f"Sent {request_len} bytes from fuzzer file")
except Exception as e:
    print(f"Error sending fuzzer request: {e}")
    break
```

- 4. 接收回應（framing）

先用 recv(4) 讀 4 bytes length。
再用 loop 持續 recv 直到讀到 response_len bytes（recv 不是一次讀完的保證）。
```python
try:
    response_len_bytes = sock.recv(4)
    if len(response_len_bytes) != 4:
        print("Error: Could not read response length")
        break
    response_len = struct.unpack('<I', response_len_bytes)[0]
    response_data = b''
    while len(response_data) < response_len:
        chunk = sock.recv(response_len - len(response_data))
        if not chunk:
            print("Error: Connection closed while reading response")
            break
        response_data += chunk
    if args.verbose:
        print(f"Received {response_len} bytes response")
except Exception as e:
    print(f"Error reading target response: {e}")
    break
```

- 5. decode target 回應（轉成可讀 JSON / dict）

用 raw_to_json(response_data) 把 target 回傳 bytes decode 成 dict。
<message_kind>+ 跟收到的內容。
```python
try:
    response_msg = raw_to_json(response_data)
    print_message(response_msg, False, args.verbose)
except Exception as e:
    print(f"Error decoding target response: {e}")
    break
```

- 6. 讀取預期回應檔並比對

每個 fuzzer_xxx.bin 對應一個 target_xxx.bin（同序列 index）作為該 request 的預期回應。
把 target_xxx.bin decode 成 precomputed_response_msg，呼叫 response_check(...) 做比對：

peer_info 有特別規則（檢查 fuzz_version、jam_version、fuzz_features bitmask）。

其他 message 預期完全相等（字典 equality）。

若比對失敗則停止（break），方便後續調查差異。
```python
try:
    with open(target_file, 'rb') as f:
        precomputed_data = f.read()
    precomputed_response_msg = raw_to_json(precomputed_data)
except Exception as e:
    print(f"Error reading precomputed target file '{target_file}': {e}")
    break

if not response_check(response_msg, precomputed_response_msg):
    break
```


## Questions & Discussions 整理當前實作

### Each file
fuzz.go →  CLI，直接呼叫 library：
- fuzz.NewFuzzServer → 啟 server
- fuzz.NewFuzzClient → 建 client
- client.Handshake / ImportBlock / SetState / GetState → 發 request

client.go → 測試端，送 request 給 server
server.go → 接收 request，轉發給 service
service.go → 呼叫 JAM 本體的邏輯（store, stf, merklization）
messages.go → 定義 client/server 之間的協議格式 decode/encode byte/json
errors.go → 錯誤定義

### Client functions
#### Message framing
- [len (4 bytes LE)] + [type (1 byte)] + payload

#### Handshake
client 啟動時 handshake，拿到 server 的 PeerInfo。

#### ImportBlock
Send ImportBlock (Block) → Get StateRoot

#### SetState
Send (Header, StateKeyVals) → Get StateRoot

#### GetState
- Go：Send HeaderHash → Get StateKeyVals

### To discuss
#### Error Handling(增加 Error Type?)
- Python fuzzer 預期 Server 遇到錯誤時回 Error + 訊息。
- Go 目前只有 local error log，沒送 Message 回 client。

Go:
```go
const (
	MessageType_PeerInfo    MessageType = 0
	MessageType_ImportBlock MessageType = 1
	MessageType_SetState    MessageType = 2
	MessageType_GetState    MessageType = 3
	MessageType_State       MessageType = 4
	MessageType_StateRoot   MessageType = 5
)
```
Python:
```
PeerInfo
ImportBlock
SetState (Initialize)
GetState
State
StateRoot
```
#### Naming Alignment
- Python：Initialize
- Go：SetState

#### GetState 限制

Go GetState 只支援 parent block 的 state : 符合期望?

### Server functions

#### Message framing
- 接收：[len (4 bytes LE)] + [type (1 byte)] + payload → decode
- 回傳：encode Message → [len (4 bytes LE)] + [type (1 byte)] + payload
#### Handshake
- Input：Client PeerInfo
- Output：Server 回 PeerInfo -> function:Handshake()


#### ImportBlock
- Input：Client 送 Block
- 處理：Service.ImportBlock(block) → 驗證 parent hash / state_root → 更新 state
- Output：回傳 StateRoot

#### SetState (Initialize)
- Input：Client 送 (Header, StateKeyVals)
- 處理：Service.SetState(header, state) → 更新 store → Merklization 得到新的 StateRoot
- Output：回傳 StateRoot


#### GetState
- Input：Client 送 HeaderHash
- 處理：Service.GetState(hash) → 查 store 的狀態
- Output：回傳完整的 StateKeyVals



### Service.go
#### Handshake
- Input：Client 傳入一份 PeerInfo
- 處理：忽略 Client 的內容(需加入check bit?)，直接讀取本地 config.Config 生成 PeerInfo
- Output：Server 端的 PeerInfo

#### ImportBlock
- Input：Client 傳入 Block
- 取出 store 最新 block → 算出最新 block hash
- 驗證 block.Header.Parent 是否對得上
- 驗證 block.Header.ParentStateRoot 是否對得上
- 如果驗證成功 → 把 block 存進 store → 跑 STF (RunSTF()) → 算出新的 StateRoot
- Output：新的 StateRoot

#### SetState (Initialize)
- Input：Client 傳入 (Header, StateKeyVals)
- StateKeyVals → State
- 設定到 store PosteriorState
- Merklization 算出 StateRoot
- Output：新的 StateRoot

#### GetState

- Input：Client 傳入 HeaderHash
- 檢查 hash 是否等於當前 ProcessingBlockPointer 的 parent
- 如果不相符 → return error
- 如果符合 → 取出當前 PosteriorStates
- Encode 成 StateKeyVals
- Output：StateKeyVals

#### Error Handling

- 目前 Service 會 return error，交給 server.go 處理。
- server.go 沒有把 error 包裝成 Error message 回給 client，導致 fuzzer 端會 mismatch。