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