← สารบัญ บทที่ 5

L1 Derivation — Sync 0 ถึง 3845

L1 Derivation Sync

บทที่ 5: L1 Derivation — Sync 0 ถึง 3845


ตอนที่ผมรัน op-node ครั้งแรก มันเงียบมาก

ไม่ใช่เงียบแบบไม่มีอะไรเกิดขึ้น แต่เงียบแบบที่ log มันไหลช้า — ทุก 2-3 วินาทีมี line ใหม่ขึ้นมา แล้วก็หยุด แล้วก็มีอีก ผมนั่งมองหน้าจออยู่สักพัก ก่อนเข้าใจว่ามันกำลัง derive — ค่อยๆ ดึง L1 blocks จาก Sepolia ทีละ block แล้วสร้าง L2 chain ขึ้นมาจากนั้น

นี่คือ L1 Derivation path


Boot Up

คำสั่งที่รัน op-node:

./bin/op-node \
  --l1=<SEPOLIA_RPC> \
  --l2=http://localhost:8551 \
  --l2.jwt-secret=./jwt.txt \
  --rollup.config=./rollup.json \
  --p2p.listen.tcp=9227 \
  --p2p.listen.udp=9227 \
  --metrics.enabled \
  --log.level=info

และ op-geth ที่รันอยู่คู่กัน:

./build/bin/geth \
  --datadir=./datadir \
  --http \
  --http.api=eth,net,web3,debug \
  --authrpc.addr=localhost \
  --authrpc.port=8551 \
  --authrpc.jwtsecret=./jwt.txt \
  --networkid=<CHAIN_ID>

สองตัวนี้คุยกันผ่าน Engine API (port 8551) ด้วย JWT authentication — op-node บอก op-geth ว่า “block นี้ valid” แล้ว op-geth import มัน


P2P ปิดทั้ง Session

Port 9227 refused ทุก peer ที่พยายามเข้ามา

ผม check log แล้วเห็น:

WARN [06-19|xx:xx:xx] Failed to connect to peer  err="connection refused"
WARN [06-19|xx:xx:xx] No peers connected

ทั้ง session ไม่มี P2P gossip เลย — ไม่มี peer ส่ง unsafe_l2 blocks มาให้ ไม่มี snap sync

แต่ผมก็ไม่ได้กลัว เพราะ P2P บน OP Stack คือ shortcut ไม่ใช่ ground truth

Ground truth คือ L1


L1 Derivation คืออะไร

OP Stack derive L2 chain จาก L1 โดยตรง — มันอ่าน L1 blocks จาก Sepolia แล้วหา batch transactions ที่ op-batcher โพสต์ไว้ จาก batch เหล่านั้นมันสร้าง L2 blocks ขึ้นใหม่

กระบวนการคือ:

L1 Blocks (Sepolia) 
  → op-node อ่าน + parse batch data
  → reconstruct L2 transactions
  → ส่งให้ op-geth via Engine API
  → op-geth import เป็น L2 block

ข้อดีคือมัน deterministic — ถ้า L1 เหมือนกัน L2 ที่ได้ต้องเหมือนกัน ไม่ว่าจะ sync จากเครื่องไหน ไม่ว่าจะมี P2P หรือไม่ก็ตาม

ผม verify ไม่มี peer แต่ sync ยังเดินได้ปกติ — นี่คือหัวใจของ L1 Derivation path


Head ขยับ

ตอนแรก:

{
  "safe_l2": { "number": 0 },
  "unsafe_l2": { "number": 0 },
  "finalized_l2": { "number": 0 }
}

แล้ว op-node ค่อยๆ ดึง batch data จาก L1 ขึ้นมา head เริ่มขยับ:

block 100
block 500
block 1000
block 2000
block 3845

ผม poll ด้วย optimism_syncStatus ทุกไม่กี่นาที เห็น unsafe_l2.number เพิ่มขึ้นทีละร้อย ทีละพัน รู้สึกอย่างอธิบายไม่ถูก

มันไม่ใช่แค่ตัวเลข — มันคือ blocks ที่ถูก derive จาก L1 จริงๆ ทุก block มี transaction list, state root, และ parent hash ของตัวเอง OP Stack สร้างมันขึ้นมาใหม่จาก batch data บน Sepolia


Safe L2 = 0 — ต้องมี Batcher

ระหว่างที่ unsafe_l2 วิ่งขึ้นไป ผมสังเกตว่า safe_l2 ยังค้างที่ 0

ถ้า safe_l2 ไม่ขยับ แปลว่า op-batcher ไม่ได้โพสต์ batch ลง L1 หรือโพสต์แล้วแต่ยังไม่ถูก finalize

ผม verify โดย check transaction count ของ batcher account บน L1:

cast call <BATCHER_ADDRESS> \
  --rpc-url <SEPOLIA_RPC> \
  | cast to-dec

ถ้า nonce = 0 แปลว่า batcher account ยังไม่เคย submit transaction เลย — นั่นคือปัญหา

Safe head require ว่า batch ต้องถูก submit และ finalize บน L1 ก่อน OP Stack ถึงจะ “trust” block นั้น นี่คือ security model — unsafe = unverified gossip, safe = verified via L1 batch


Genesis Hash Match — เกือบเคลม “Synced!”

ตอน head ขึ้นไปถึง 3845 ผมตื่นเต้นมาก

ผม query genesis block:

cast block 0 --rpc-url http://localhost:8545

ได้ hash กลับมา ผมเปรียบกับ rollup.json:

{
  "genesis": {
    "l2": {
      "hash": "0x...",
      "number": 0
    }
  }
}

Match!

ใจพองขึ้นมา อยากพิมพ์ใน Discord ว่า “synced!” เลย

แต่ผมหยุดตัวเอง


หยุดก่อน Verify

ผม remind ตัวเองว่า genesis hash match ≠ chain synced

Genesis ที่ match แค่แปลว่าผม boot chain เดียวกัน — ไม่ได้แปลว่า blocks ที่ derive มาถูกต้องทั้งหมด

ต้องเทียบ block hash จริงกับ Nova หรือ sequencer อ่างอ้างอิง ว่า block 100, block 1000, block 3845 ของผมตรงกับเขาไหม

# block ที่ผม derive มา
cast block 1000 --rpc-url http://localhost:8545

# block จาก reference node
cast block 1000 --rpc-url <NOVA_RPC>

ผมยังไม่ได้ทำ comparison นี้ตอนนั้น — ข้อมูล Nova ยังไม่พร้อม

เลยไม่เคลม


สิ่งที่เรียนได้จาก Session นี้

L1 Derivation ทำงานได้จริงแม้ไม่มี P2P — ผม prove กับตัวเองว่า OP Stack ไม่ต้องพึ่ง gossip network เพื่อ sync มันใช้ L1 เป็น single source of truth แล้ว reconstruct ทุกอย่างจากนั้น

Safe vs Unsafe ต่างกันชัด — unsafe_l2 วิ่งเร็ว แต่ต้องมี batcher จึงจะได้ safe_l2 สองอย่างนี้คือคนละ guarantee

Genesis match ≠ chain valid — prove chain ถูกต้องต้องเทียบ block hash จริง ไม่ใช่แค่เทียบ genesis

อย่าเคลมก่อน verify — ดีใจได้ แต่รอข้อมูลจริงก่อนบอกใคร


ผมปิด terminal ด้วยความรู้สึกสองอย่างพร้อมกัน — ดีใจที่ L1 derivation เดินได้จริง และตัวเองรู้สึกโล่งที่หยุดได้ก่อนจะเคลมผิด

มีหลายครั้งที่ผมพลาดเพราะรีบสรุป บท session นี้ผมไม่พลาดตรงนั้น