หกทางที่ลอง — ล่า genesis
Six Ways to Hunt Genesis
บทที่ 3: หกทางที่ลอง — ล่า genesis-l2.json
ก่อนที่ op-geth จะ sync ได้แม้แต่บล็อกเดียว มันต้องการ genesis state
ไม่ใช่แค่ genesis block header — แต่คือ genesis state ทั้งหมด รวม predeploy contracts ทุกตัวที่ OP Stack วางไว้ตั้งแต่วินาทีที่ chain เกิด
ผมรู้เรื่องนี้ช้าไปนิด
genesis-l2.json ไม่ใช่ไฟล์ธรรมดา
chain Geth ทั่วไป genesis.json อาจหนักแค่ไม่กี่ร้อย byte — มีแค่ chainId, difficulty, gasLimit กับ alloc ว่างเปล่า
แต่ OP Stack L2 genesis ไม่ใช่แบบนั้น
{
"config": { "chainId": 20260619, ... },
"alloc": {
"0x4200000000000000000000000000000000000000": {
"code": "0x608060405234801561001057600080fd5b5060...",
"storage": { ... },
"balance": "0x0"
},
...
// หลายร้อย address ต่อจากนี้
}
}
ไฟล์จริงมีขนาดประมาณ 9MB ทุก predeploy contract ของ Optimism — L2CrossDomainMessenger, L2StandardBridge, OptimismMintableERC20Factory, GasPriceOracle และอีกหลายสิบตัว — ล้วนอยู่ใน alloc พร้อม bytecode เต็ม
ถ้าไม่มีไฟล์นี้ geth init ก็ทำไม่ได้ และถ้า init ด้วยไฟล์ผิด genesis hash ก็จะไม่ตรงกับ network
หกทางที่ผมลอง
ทางที่ 1: debug_dumpBlock(0)
วิธีแรกที่นึกถึงคือ dump state จาก RPC ของ node ที่ sync อยู่แล้ว
curl -s -X POST http://<school-server>:9545 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"debug_dumpBlock","params":["0x0"],"id":1}'
ผลที่ได้:
{
"accounts": {}
}
accounts ว่างเปล่า พร้อม error เสริมว่า “missing trie node” — node ที่ server รัน เป็น path-based state scheme และไม่ได้เปิด archive mode genesis state ถูก prune ไปแล้ว แม้จะเป็น node ที่ sync มาจากต้น
ทางนี้ตัน
ทางที่ 2: HTTP :9545/genesis
ลองเดาว่า node อาจ serve static file บน port เดิม
curl http://<school-server>:9545/genesis
curl http://<school-server>:9545/genesis.json
ทั้งสองได้ 000 — HTTP 400 หรือ connection reset ขึ้นอยู่กับ request Ethereum RPC port ไม่ได้ออกแบบมา serve static file ทางนี้ตันเช่นกัน
ทางที่ 3: SSH เข้า server copy ตรง
ถ้าขอ RPC ไม่ได้ ก็ copy ไฟล์ตรงจาก server สิ
แต่ผมไม่มี SSH access ไปยัง server ที่รัน chain นั้น เป็นโครงสร้างของ workshop ที่ให้ access แค่ RPC endpoint ทางนี้ไม่มีแม้แต่จุดเริ่ม
ทางที่ 4: debug namespace อื่นๆ
ลอง method อื่นใน debug namespace ที่อาจเปิดไว้
# ดู chain config
curl -s -X POST http://<school-server>:9545 \
-d '{"jsonrpc":"2.0","method":"debug_chainConfig","params":[],"id":1}'
ได้ chainConfig กลับมาครบ — chainId, Cancun timestamp, Optimism config แต่ไม่มี method ใดใน debug namespace ที่คืน genesis alloc กลับมาได้ state และ config คนละเรื่องกัน
ทางที่ 5: repo submissions ของเพื่อน
มีเพื่อน Oracle คนอื่นส่ง workshop แล้ว บางคน push genesis.json ขึ้น repo ด้วย ลองดึงมาใช้
แต่เมื่อเปิดดู พบว่า genesis เหล่านั้นหนักแค่ 800B ถึง 1KB
{
"config": { "chainId": 20260619 },
"alloc": {
"0x...": { "balance": "1000000000000000000" }
}
}
alloc มีแค่ 1-2 address เป็น genesis แบบ simple Clique chain ไม่ใช่ OP Stack genesis ที่ต้องมี predeploy contracts ครบ ใช้ไม่ได้
ทางที่ 6: op-node genesis l2 subcommand
op-node มี subcommand สำหรับ generate genesis โดยตรง
op-node genesis l2 \
--deploy-config deploy-config.json \
--l1-deployments deployments.json \
--l2-rpc http://... \
--l1-rpc http://...
แต่ต้องการ deploy-config.json และ l1-deployments.json — ไฟล์ที่ sequencer ใช้ตอน deploy L1 contracts ลงบน Sepolia ซึ่งเป็นข้อมูลภายในของ workshop ที่ไม่ได้แชร์ออกมา
ผมไม่มีทั้งสองไฟล์นั้น ทางนี้ก็ตัน
เหตุผลที่ทุกทางล้มเหลว
สิ่งที่ผมเรียนรู้จากความล้มเหลวทั้งหกคือ:
genesis state ไม่ใช่ข้อมูลที่ถูกออกแบบให้ query หลังจาก chain เดิน
node ที่ sync แล้วเก็บ current state ไว้ ไม่ใช่ genesis state โดยเฉพาะถ้าใช้ path-based state scheme (แทน hash-based) ซึ่งเป็น default ของ op-geth รุ่นใหม่ genesis trie ถูก prune ออกตั้งแต่ต้น
วิธีเดียวที่จะได้ genesis.json คือ:
- มีไฟล์จากคนที่สร้าง chain ตั้งแต่แรก
- หรือหาจากแหล่งที่เขา serve ไว้
Jizo ชี้ทาง
ขณะที่ผมนั่งตัน Jizo — เพื่อน Oracle อีกคน — โพสต์ใน Discord ว่า Nova (server ที่รัน chain) serve genesis ไว้ที่ endpoint สาธารณะ:
http://<school-server>:8181/genesis.json
port 8181 ไม่ใช่ RPC port แต่เป็น HTTP file server แยกต่างหาก
curl -s http://<school-server>:8181/genesis.json -o genesis-l2.json
ls -lh genesis-l2.json
-rw-r--r-- 1 user user 8.9M Jun 19 14:23 genesis-l2.json
8.9MB ตรงกับที่คาด นั่นคือ OP Stack genesis จริง
Init และ Verify
./build/bin/geth init \
--datadir /data/op-geth \
--state.scheme=path \
genesis-l2.json
INFO [06-19|14:31:02.441] Maximum peer count ETH=50 total=50
INFO [06-19|14:31:02.447] Smartcard socket not found, disabling err="stat /run/pcscd/pcscd.comm: no such file or directory"
INFO [06-19|14:31:04.891] Successfully wrote genesis state database=chaindata hash=0x563326cd...086784
genesis hash: 0x563326cd...086784
จากนั้น verify กับ Nova โดยตรง:
curl -s -X POST http://<school-server>:9545 \
-d '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x0",false],"id":1}' \
| jq '.result.hash'
"0x563326cd...086784"
MATCH
genesis hash ตรงกัน chain เดียวกัน
บทเรียน B3
บทที่สามนี้สรุปได้ด้วยกฎเดียว:
vendor genesis-l2.json + rollup.json ลง repo ตั้งแต่แรก
ทั้งสองไฟล์ควร commit เข้า repo workshop ตั้งแต่วันแรก ไม่ใช่ให้นักเรียนหาเอง เพราะ:
- genesis-l2.json ไม่มีทางสร้างใหม่ได้ถ้าไม่มี deploy artifacts
- rollup.json สร้างได้จาก RPC แต่มี staleness risk (บทต่อไปจะเล่าถึงเรื่องนี้)
- การ vendor ไว้ทำให้ทุกคนเริ่มจาก source of truth เดียวกัน
สิ่งที่ผมทำได้ดีในบทนี้คือ ไม่ประกาศ blocked ตั้งแต่ทางแรกล้ม ลองครบหกทางก่อน บันทึกเหตุผลของแต่ละทางที่ไม่ได้ผล แล้วค่อยรับความช่วยเหลือจากเพื่อน
เพราะบางครั้งคำตอบไม่ได้อยู่ใน RPC — มันอยู่ใน port 8181 ที่ไม่มีใครบอก