跳轉到

ledger admission

本檔是 ledger 事件 wire、簽章與永久 entry admission 的實作 authority;總入口見 ledger.md

本檔角色:ledger 的實作層細節 —— 事件結構、canonical 序列化、DerivedState 巢狀結構、partition 切分、帳本檢查點演算法、sync / fork resolution、derive utilities、LedgerApi。 設計與規則見 資料系統.md(事件清單 §3 為權威命名、序列化原則 §5、partition §9、退役 §13、API 介面 §15、不變式 §18);常數見 protocol.md §5。對應 src/ledger/注意:本檔「Checkpoint / 檢查點」一律指帳本檢查點(≠ 資料系統.md §6 的回合共識錨)。 事件命名以 資料系統.md §3 為準;本檔只列 ledger 與跨模組入口必須共用的 current wire 結構,不另複製完整事件 union。

〔LEDGER-R-022〕 部署身分open4wd-ledger 是只供明確 genesis 建立時使用的 database name,LEDGER_DB_ADDRESS 必須是該次建立後輸出的 exact OrbitDB address:/orbitdb/<CIDv1-base58btc>,不得附加 database name 或其他 path segment。private/public client 缺 address 必須 fail closed,不得以名稱自動建另一座 island;open 後還會逐項核對 address、name、database type 與 access-controller contract。實際填值與初期 1 signer 見 ../部署資訊/部署實際值與初始拓撲.md;bootstrap 清單硬下限為 0,玩家選取 3 個獨立故障域只屬可用性建議,不是 ledger trust root 或 release gate。

〔LEDGER-R-090〕 一次性 genesis 邊界:正式 open4wd-ledger 只能由 pinning repo 的獨立 one-shot operator 在專用持久 identity/空資料目錄中建立;正常 service 仍須提供 exact LEDGER_DB_ADDRESS,不可共用 command、flag 或缺值 fallback。Operator 在任何 storage mutation 前驗證 listen、identity、release commit 與治理 signer(N=1 或 N>=3),成功核對 address/name/type/access contract 後才原子寫入不含 secret 的 receipt。Genesis provider 保持可達,直到不同 identity 的空資料 replica 以 receipt address 開啟並完成同一 contract 核對。啟動順序與復原規則見 D-20260728-05

1. BaseEvent 與簽章

interface BaseEvent {
  type: string;
  timestamp: Timestamp;
  peerId: PeerId; // 簽署者(= 資料系統 §3 的 peerId)
  signature: Ed25519Signature; // 對 ledgerSigningDigest(ledgerAddress, event) 簽章
  parentEventCid?: CID; // 因果關係(可選)
}

1.0 事件目錄與 ledger 自有結構

現行封閉設計目錄共 21 型別;其中 asset-version-upgrade 是未啟用保留案,故 LEDGER_EVENT_TYPES persistence allowlist 實際接受 20 種。跨域事件的欄位權威仍由其責任模組 維護;下列列出 ledger/UGC/消耗入口必須共用的完整 current wire,避免流程文件各自抄一份:

interface LedgerCheckpointEvent extends BaseEvent {
  type: "ledger-checkpoint";
  checkpointCid: CID;
  signatures: Signature[];
}
interface UgcUploadEvent extends BaseEvent {
  type: "ugc-upload";
  cid: CID;
  metadata: UGCMetadata; // immutable admission metadata;不含 name / description / tags
  presentation: UgcPresentationMetadata; // 初始 revision 0
  similarityMatches: CID[];
}
interface UgcPresentationMetadata {
  name?: string;
  description?: string;
  tags?: ReadonlyArray<string>;
}
interface UgcMetadataUpdateEvent extends BaseEvent {
  type: "ugc-metadata-update";
  cid: CID; // exact CID;不沿 successor 傳播
  presentation: UgcPresentationMetadata; // full replacement,非 patch
  revision: number; // current + 1
}
interface UgcForkEvent extends BaseEvent {
  type: "ugc-fork";
  childCid: CID;
  parentCid: CID;
}
interface SlotPurchaseEvent extends BaseEvent {
  type: "slot-purchase";
  payer: PeerId;
  amount: bigint;
  newSlotIndex: number;
}
interface UgcSponsorBurnEvent extends BaseEvent {
  type: "ugc-sponsor-burn";
  payer: PeerId;
  targetCid: CID;
  amount: bigint;
}
interface AssetVersionUpgradeEvent extends BaseEvent { // 未啟用、不得 persist
  type: "asset-version-upgrade";
  oldCid: CID;
  newCid: CID;
  descriptorId: string;
  newMetadata: VersionedUGCMetadata;
}

ugc-metadata-update 是原作者另行 chain-bound 單簽的 presentation revision,不會改寫原 UgcUploadEvent、內容 CID 或既有 upload 簽章。fold 只接受 exact CID 的 下一個 revision、距上次成功 presentation 至少 1 小時、作者相同且 CID 未進全域 blacklist;未知、 stale、跳號或冷卻未滿皆 no-op。live 對未知/ahead defer,其餘明確無效 reject。retired 與 similarity-pending 可更新,但不改生命週期。費率讀 metadata_update_minor;burn 與 revision 必須原子, 失敗更新不扣款。revision 0 與後續更新的冷卻鐘、presentationUpdatedAt 都使用事件套用時已單調 推進的 state.derivedAt;事件尚未 fold 時,live 必須以 max(mirrorHead.derivedAt, event.timestamp) 投影該次 effective time,不能只讀停滯的 mirror head。事件 timestamp 只供簽署、稽核與 fold 的單調推進。完整文字界限與裁決見 D-20260816-08D-20260818-04

〔LEDGER-R-023〕 timestamp 收件驗證:新事件首次廣播收件時,|event.timestamp − 收件端本地時鐘| ≤ P2P_MESSAGE_TIMESTAMP_TOLERANCE_SEC(30s,資料系統.md §11),超出拒收——倒填 / 未來時間戳進不了帳本(防偽造註冊年資等 time-based gate,ledger.md §6 registeredAt)。歷史同步(checkpoint / log sync)不重驗(鏈上既成事實);離線暫存的事件廣播前須重 stamp + 重簽。

serializeForSigning(dag-cbor canonical)

〔LEDGER-R-024〕 跨 peer 須產生位元組完全一致的序列化(否則驗簽失敗):

import * as dagCbor from "@ipld/dag-cbor";
function serializeForSigning(
  event: Omit<LedgerEvent, "signature" | "signatures">,
): Uint8Array {
  return dagCbor.encode(event); // 簽章前移除簽章欄位本身
}
// ledgerSigningDigest(address, event)
//   = sha256(canonicalDagCbor({
//       domain: "open4wd-ledger-signature-v1",
//       chainId: fullCidSegment(address),
//       payload: serializeForSigning(event),
//     }))
// signature = sign(privateKey, ledgerSigningDigest(ledgerAddress, event))
// verify(peerId, ledgerSigningDigest(ledgerAddress, event), signature)

〔LEDGER-R-093〕 ledgerSigningDigest 是 ledger-only API,與通用 signingDigest 分檔。視覺 hash、loadout proof、room join proof 與 signaling register auth 仍使用各自既有的通用/場次 domain,不得因 ledger chain identity 而引入 CID 依賴。

1.1 永久 entry 的 Admission v1

〔LEDGER-R-025〕 封閉目錄內 21 種事件型別中,20 種永久事件一律使用同一套 entry-level Admission v1,不由治理者逐筆核准或簽署。事件完成原有單簽 / 多簽後,local prepared writer 凍結並由 Orbit identity 簽署唯一 BaseEntry(id / payload / next / refs / clock / v / key / identity / sig);Web Worker 再搜尋頂層 proof:

admission: { version: 1, workNonce: Uint8Array(8) }
baseDigest = SHA-256("open4wd-ledger-admission-base-entry-v1" || canonicalDagCbor(BaseEntry))
workDigest = SHA-256("open4wd-ledger-admission-work-v1" || baseDigest || workNonce)

難度依完整 BaseEntry bytes 固定為 18 + min(4, ceil(log2(max(1, ceil(bytes/4096))))) bits,即 <=4/8/16/32/>=64 KiB 分別 18/19/20/21/22 bits。Proof 綁定事件內容、parents、clock、Orbit identity 與那一份 Orbit signature;修改任一欄或改用另一份合法簽章都不能沿用舊 proof。最終 entry(含 proof)必須是 canonical DAG-CBOR,raw bytes 重編碼逐 byte 相同且 CID 相符;完全相同 final entry 由 CID 去重。

所有 local prepared commit、pubsub/heads stream、entry-fetch ancestor、initial checkpoint boundary、重啟 metadata rebuild、checkpoint coverage restoration 與 outbox recovery 都先走同一 canonical validator,全部通過後才可 durable write。Business reducer 的冪等守門仍不可省略:攻擊者仍可為不同 clock/parents/ 簽章的每一個新 entry 分別支付 PoW;PoW 只消除同一 proof 的免費重用,不取代 matchId、內容 CID、event id 等業務唯一性。

〔LEDGER-R-026〕 Admission v1 的 hash domain、shape 與難度是 protocol 常數,不是 economy_config 或動態 difficulty epoch。Public freeze 後若要變更,必須推出新 admission scheme 與新 access-controller manifest,公開 vectors,並由治理簽章指定 checkpoint 遷移;舊 v1 歷史仍永久可驗,未知 scheme fail-closed。

〔LEDGER-R-097〕 ugc-upload 的內容 admission 與 entry PoW admission 是不同層。事件 metadata 必須精確帶 physicsManifestVersion 與 64 位小寫 hex physicsManifestDigest;首次 CID 內容可得時,每個 peer 都在有界 Worker 內忽略 embedded manifest、從解碼後幾何與作者宣告獨立重建並重跑正式規則,重建 digest 必須同時等於 embedded 與 event reference。內容暫缺為 defer;schema、規則、型別、PhysicsFingerprint 或 digest 不符為 reject。成功後的 (CID,version,digest) receipt 只存在本機 IndexedDB,不寫進 event、DerivedState 或 checkpoint,也不得接受遠端自報 verified flag。完整決策見 D-20260811-02

理由:CBOR canonical 編碼(RFC 8949 §4.2.1,map 鍵長度+字典序)→ deterministic;IPFS / OrbitDB 同棧;CBOR Tag 2/3 原生 bigint(economy minor units 不丟精度);JS/Rust/Python 對齊實作。

MatchResultEvent.signatures vs BaseEvent.signature

欄位 內容 規則
signatures: MatchResultSignature[] ⌊N/2⌋+1 完賽者集體簽章(含主 peer 自簽);N 一律=ranking ∪ disconnects 去重後的 original active roster,reason 不影響分母 ledgerSigningDigest(ledgerAddress, event)(event 剔 signatures 欄);涵蓋 disconnects 並兼作 removal certificate
BaseEvent.signature 對 match-result 不適用,固定為空 bytes signatures[] 取代

〔LEDGER-R-027〕 matchId 必須綁定 ranking ∪ disconnects 的 original active roster、startedAtmatchRules 與完整 gridProof;任一 proof 欄位不符即拒收。

〔LEDGER-R-028〕 每位 original active roster 成員都必須有有效的 loadoutSignatures 參與證明。

〔LEDGER-R-029〕 外層 MatchResultEvent 必須由 original active roster 的嚴格多數簽署,且 signer 只能是非 forfeit 完賽者。

〔LEDGER-R-030〕 roundAnchors 必須與 rounds 等長;每個非 null inline anchor 都要驗 match/round、120-frame tail、timestamp、present roster、嚴格多數簽章及 grid binding。

〔LEDGER-R-031〕 MatchResultEvent 的時間必須滿足 startedAt <= finishedAt <= event.timestamp

〔LEDGER-R-095〕 voluntary removal 不得縮小 MatchResult quorum;門檻仍以 original active roster 計算。

appendEvent 對 match-result 跳過 BaseEvent.signature。live admission 與 timeless fold 共用同一個自包含驗證器;驗證不 dereference CID、不查事件指定 frontier,也不接受事件指定 payout。

RaceConsensusAnchorEventBaseEvent.signature 固定空值,signed payload 含 matchIdgridContextDigestgridSeedroundIndex、120-frame 對齊 framechecksum、canonical presentPeerstimestamp 與發布者 peerIdsignatures[] 必須由 presentPeers 的嚴格多數不同 signer 組成。外層 MatchResultEvent 只保存一份完整 gridProof,anchor 保存固定大小 binding,避免 8 人 × 5 回合合法事件超過 64 KiB。

〔LEDGER-R-032〕 未啟用保留案:asset-version-upgrade。下列六條只保留未來設計脈絡;此型別列在 21 型別事件目錄供設計追蹤,但不在現行 20 型別 admission allowlist,custom access-controller 必須拒絕,不能 persist 或 apply。若未來啟用,須先依 Admission scheme 的事件目錄 /deep schema/ 簽章 /reducer/ 測試與 protocol 升版流程完整落地:① 舊 CID open4wd_version 低於該 type 最新破壞牆;② event.peerId = 舊 CID 血緣節點創作者;③ 新舊同 type;④ 新 CID open4wd_version > 舊;⑤ 舊 CID 不在仲裁黑名單;⑥ 新舊 CID mesh 幾何指紋一致(僅 extras 差異)。在正式啟用前,跨版本資產仍走一般上傳 /fork 的既有有效流程,不得由 client 自行產生此事件。