C-3 deterministic


A 系・B 系は「契約」を検証してきました。C 系は「ノード」――この公開レコードを、運営の秘密のデータベースや API に頼らず、チェーンだけから誰でも再現できるかが論点です。この項目(C-3)では、別々の VPS で動く独立した2ノードが、同じチェーンを空DBから取り込み、再構築した公開レコードが決定論的に一致するかを照合ハッシュで確かめます。レビュアーが「決定打」と呼ぶ、§19 #13–15(空DB歴史的再構築/決定論的エクスポートハッシュ/2ノード独立再実行で一致)への回答です。

やさしく言うと

2人の会計士に、同じ取引台帳(ブロックチェーン)だけを渡して、別々に帳簿を組み直してもらう。最後に両者の「照合印(ハッシュ)」がピタリ一致すれば、「誰も書き換えていないし、運営の秘密ノートを覗かなくても、チェーンだけから同じ帳簿を再現できる」と分かります。これを、別々の VPS で動く2つのノードでやりました。

チェーン同じ入力

2つの独立VPSが再構築空DB

照合ハッシュ一致2ノード

結果、共通範囲 309,672 アクションの照合ハッシュは完全一致。「運営の API に頼らず、チェーンだけから同じ公開レコードを再現できる」を、独立した2台で実証しました。

このページは検証台帳C-3 項目の詳細記録です。A/B 系(契約の検証)と違い、C 系はノードの独立性――「運営に頼らずチェーンだけから公開レコードを再現できるか」を扱う本丸です。

1. 主張(検証対象)

別々のインフラで動く独立した2ノードが、同じチェーン(Base Sepolia, chainId 84532)を空DBから取り込んで再構築した公開レコードが、決定論的に一致すること。すなわち、運営のプライベート DB/API/インデクサーに依存せず、チェーンだけから同じ公開レコードを再現できること(§19 #13–15)。

2. 検証方法

1

2つの独立ノードを用意

いずれも契約した VPS。node1(210.131.213.228)と node2(85.131.253.87OS を削除して Ubuntu を再インストール、インストーラを再実行して空DBから再構築)。公開レコードの組み立ては L2 Blockchain → RawEventsGatherer → ActionProcessor → PostgreSQL と流れ、Api は外向きの配信用 REST で取り込み依存ではありません(運営 API への逆依存なし)。

2

ノード自身の決定論ハッシュ(parentHash)

ノードは取り込み時に rolling hash チェーンを計算します:起点 'genesis'、canonical 順 (blockNumber, logIndex)parentHash[n] = keccak256(parentHash[n-1] ‖ JSON(オンチェーン action))。入力は action 本体(actionType/senderId/text 等)のみで、壁時計・乱数・外部 API は混入しません。

3

2ノードを突き合わせ、変数を切り分け

両ノードの再構築結果を照合ハッシュで比較。startBlock 設定の違いを切り分けるため共通範囲で突き合わせ、さらにソース差分で「ハッシュ計算コードが両版で同一」かまで確認しました。

3. 判定

独立した2つの VPS が、チェーンだけから同じ公開レコードを決定論的に再構築。共通範囲の照合ハッシュは2ノードで完全一致。
共通範囲 [41,803,823〜43,181,982]309,672 アクションについて、キー(block/logIndex/txHash)も中身(デコード済み action)もバイト一致。さらに、再構築+ハッシュのコードは両ノードのコミット間で完全同一とソース差分で確認。

  • 一致 キー指紋(block/logIndex/txHash の全順序)993db07…b339 が両ノードで一致(309,672件)。取りこぼし・改変ゼロ。
  • 一致 コンテンツ指紋(デコード済み action 本体まで含む)82651606…1df57 が両ノードで一致。中身までバイト同一。
  • 同一 再構築+ハッシュ経路のコード(listenForRawEvents.tsRawEventsGatherer/index.tspackActions.ts)は両コミット間で差分ゼロ。版差は API/フロント/validator/テストのみ。
この検証が示すこと/まだ示せないこと

示せたこと:別々の VPS・別インストールで、同じソフトが同じ公開レコードを決定論的に再構築した(ソフトウェアの再現性)。当初の生 parentHash 不一致は startBlock 設定の違いだけが原因で(node1=41,548,637/node2=41,803,823)、共通範囲(suffix)で突き合わせて切り分け済み――データの分岐ではありません。

まだ示せないこと(open door):両ノードの運用者は同一(私)です。レビュアー §13 が究極に求める「別オペレータによる再現」は、第三者が独立にノードを建てて同じハッシュに到達して初めて満たされます。ここは正直な open door として残し、第三者ノードでの一致を将来の強化とします。

補足:両ノードはクライアントのコミットが異なります(node1=7ff0ff4 は node2=e2074718 の祖先)が、取り込み・デコード・ハッシュの経路はソース上バイト同一と確認済み。決定論はバージョン跨ぎで成立しており、版差は無関係な層に限局されています。照合は読み出しブロック時点のスナップショット(2026-06-22)。

4. receipt(公開証明)

2ノードの構成です。chainId は両者 84532(Base Sepolia)。startBlock だけが異なり、node2 の履歴は node1 の綺麗な suffix でした。

ノード VPS / commit startBlock rows head block
node1 210.131.213.228 / 7ff0ff4 41,548,637 310,468 43,182,074
node2 85.131.253.87 / e2074718 41,803,823 309,804 43,182,070

共通範囲 [41,803,823〜43,181,982] の照合結果(両ノードで一致):

指紋 対象 値(両ノード共通) 件数
キー block/logIndex/txHash 993db0738a59e980b73c8547ba04b339 309,672
コンテンツ +デコード済み action 82651606cdbe919be65de5b227f1df57 309,672

再現は、両ノードの PostgreSQL で共通範囲のコンテンツ指紋を取り、突き合わせるだけです(createdAt 等ノード固有値は除外し、チェーン由来の中身だけをハッシュ):

SELECT md5(string_agg(
  "blockNumber"||'-'||"logIndex"||'-'||"transactionHash"||'-'||"data"::text,
  '|' ORDER BY "blockNumber","logIndex","transactionHash"
)) AS contentfp, COUNT(*) AS n
FROM "RawEvent"
WHERE "chainId"=84532 AND "blockNumber">=41803823 AND "blockNumber"<=43181982;
# 両ノードで contentfp == 82651606cdbe919be65de5b227f1df57 / n == 309672

さらに、ハッシュ計算経路のコードが両ノードのコミット間で同一であることも検証できます:

git diff 7ff0ff4 e2074718 -- \
  client/src/services/RawEventsGatherer/listenForRawEvents.ts \
  client/src/services/RawEventsGatherer/index.ts \
  client/src/utils/packActions.ts
# → 差分なし(取り込み・デコード・ハッシュの経路は両版で同一)

参考:startBlock が違うため、生 parentHash の head は当然ずれます(node1 0x3332defe…f27622/node2 0xcfbf2b38…cca77278)。共通範囲で突き合わせて、この差が startBlock 由来だけであることを確認しました。

5. 注記・限界

対象はオンチェーンの action ストリーム(CAW/RECAW/LIKE/FOLLOW 等)です。DM・画像はオフチェーン(DM 中継/local disk・Filebase)で、この決定論チェーンの対象外=正直な open door。Base L2 のシーケンサに依存している点も既知の open door です。

決定論は公開クライアントのデコード/ハッシュ基準です。両ノードはコミットが異なりますが、当該経路はソースで同一と確認済みなので、独立オペレータが公開クライアントで再現可能、という主張は保てます。

より強い形を望むなら、node2 を node1 と同じ startBlock=41,548,637 で空DB再同期すれば、生 parentHash の head も頭から一致するはずです(時間はかかる・任意の追加確認)。本項目の判定はそれを待たずとも、上記のコンテンツ一致とコード同一性で十分に成立します。

説明は組み直せない。チェーンは組み直せる。
だから2台の独立した機械で組み直して、同じ印が出るまで、検証済みとは書かない。

The A and B series verified the contracts. The C series is about the node — whether this public record can be reproduced from the chain alone, by anyone, without relying on the operator’s private database or API. This item (C-3) checks whether two independent nodes, running on separate VPS, ingest the same chain from an empty DB and reconstruct a public record that is deterministically identical — by comparing checksums. It is the answer to what the reviewer calls the “decisive” proof: §19 #13–15 (empty-DB historical reconstruction / deterministic export hash / independent re-execution matching across two nodes).

In plain terms

Hand two accountants only the same transaction journal (the blockchain) and have each rebuild the ledger independently. If their final “seal (hash)” matches exactly, you know “no one altered it, and you can reproduce the same ledger from the chain alone — without peeking at the operator’s secret notebook.” We did this with two nodes on separate VPS.

chainsame input

2 independent VPS rebuildempty DB

checksums match2 nodes

The result: the checksum over the common range of 309,672 actions matched exactly. We demonstrated, on two independent machines, that “the same public record can be reproduced from the chain alone, without relying on the operator’s API.”

This page is the detailed record for item C-3 of the Verification Ledger. Unlike the A/B series (contract verification), the C series covers node independence — “can the public record be reproduced from the chain alone, without relying on the operator?” — the main event.

1. Claim (what is verified)

That two independent nodes on separate infrastructure, ingesting the same chain (Base Sepolia, chainId 84532) from an empty DB, reconstruct a public record that is deterministically identical. That is: the public record can be reproduced from the chain alone, without depending on the operator’s private DB / API / indexer (§19 #13–15).

2. Method

1

Two independent nodes

Both are rented VPS: node1 (210.131.213.228) and node2 (85.131.253.87, OS deleted, Ubuntu reinstalled, installer re-run, rebuilt from an empty DB). The public record is assembled L2 Blockchain → RawEventsGatherer → ActionProcessor → PostgreSQL; Api is an outbound serving REST, not an ingest dependency (no reverse dependency on an operator API).

2

The node’s own deterministic hash (parentHash)

On ingest, the node computes a rolling hash chain: seed 'genesis', canonical order (blockNumber, logIndex), parentHash[n] = keccak256(parentHash[n-1] ‖ JSON(on-chain action)). The input is only the action body (actionType/senderId/text, etc.) — no wall clock, no randomness, no external API.

3

Reconcile two nodes and isolate the variable

Compare the nodes’ reconstructions by checksum. To isolate a difference in the startBlock setting, compare over the common range, and additionally confirm via a source diff that the hashing code is identical between the two versions.

3. Verdict

Two independent VPS reconstruct the same public record from the chain alone, deterministically. The common-range checksum matches exactly across the two nodes.
Over the common range [41,803,823–43,181,982], all 309,672 actions match byte-for-byte in both keys (block/logIndex/txHash) and content (decoded action). Furthermore, the reconstruction-and-hash code is identical between the two nodes’ commits, confirmed by source diff.

  • match Key fingerprint (total order over block/logIndex/txHash) 993db07…b339 matches on both nodes (309,672 rows). Zero gaps or alterations.
  • match Content fingerprint (including the decoded action body) 82651606…1df57 matches on both nodes. Byte-identical down to the content.
  • identical The reconstruction-and-hash code (listenForRawEvents.ts / RawEventsGatherer/index.ts / packActions.ts) has zero diff between the two commits. The version difference is only in the API/frontend/validator/test layers.
What this proves / what it does not yet prove

Proven: on separate VPS and separate installs, the same software reconstructs the same public record deterministically (software reproducibility). The initial raw parentHash mismatch was caused only by the different startBlock (node1 = 41,548,637 / node2 = 41,803,823), isolated by comparing the common range (a suffix) — not a data divergence.

Not yet proven (open door): both nodes are run by the same operator (me). The reviewer’s §13 ideal of “independent operator” reproduction is met only when a third party stands up a node independently and reaches the same hash. This remains an honest open door; a third-party match is the future strengthening.

Note: the two nodes run different client commits (node1 = 7ff0ff4 is an ancestor of node2 = e2074718), but the ingest/decode/hash path is confirmed byte-identical in source. Determinism holds across the version range, and the difference is confined to unrelated layers. The reconciliation is a snapshot at the read block (2026-06-22).

4. Receipt (public proof)

The two nodes’ setup. chainId is 84532 (Base Sepolia) on both. Only the startBlock differs, and node2’s history is a clean suffix of node1’s.

Node VPS / commit startBlock rows head block
node1 210.131.213.228 / 7ff0ff4 41,548,637 310,468 43,182,074
node2 85.131.253.87 / e2074718 41,803,823 309,804 43,182,070

The reconciliation over the common range [41,803,823–43,181,982] (matching on both nodes):

Fingerprint Covers Value (same on both) Count
Key block/logIndex/txHash 993db0738a59e980b73c8547ba04b339 309,672
Content + decoded action 82651606cdbe919be65de5b227f1df57 309,672

To reproduce, take the common-range content fingerprint on each node’s PostgreSQL and compare (excluding node-local fields like createdAt, hashing only chain-derived content):

SELECT md5(string_agg(
  "blockNumber"||'-'||"logIndex"||'-'||"transactionHash"||'-'||"data"::text,
  '|' ORDER BY "blockNumber","logIndex","transactionHash"
)) AS contentfp, COUNT(*) AS n
FROM "RawEvent"
WHERE "chainId"=84532 AND "blockNumber">=41803823 AND "blockNumber"<=43181982;
# on both nodes: contentfp == 82651606cdbe919be65de5b227f1df57 / n == 309672

You can also verify that the hashing code is identical between the two nodes’ commits:

git diff 7ff0ff4 e2074718 -- \
  client/src/services/RawEventsGatherer/listenForRawEvents.ts \
  client/src/services/RawEventsGatherer/index.ts \
  client/src/utils/packActions.ts
# -> no diff (ingest/decode/hash path is identical in both versions)

For reference: because the startBlock differs, the raw parentHash heads naturally differ (node1 0x3332defe…f27622 / node2 0xcfbf2b38…cca77278). Comparing over the common range confirmed this difference is solely due to startBlock.

5. Notes & limits

The scope is the on-chain action stream (CAW/RECAW/LIKE/FOLLOW, etc.). DMs and images are off-chain (DM relay / local disk, Filebase) and outside this deterministic chain — an honest open door. Dependence on the Base L2 sequencer is also a known open door.

Determinism is relative to the open-source client’s decode/hash logic. The two nodes run different commits, but that path is confirmed identical in source, so the claim “reproducible by an independent operator using the public client” holds.

For a stronger form, re-syncing node2 from an empty DB with the same startBlock=41,548,637 as node1 would make the raw parentHash heads match from the start too (time-consuming, optional). This item’s verdict stands without it, on the content match and code identity above.

An explanation cannot be rebuilt. A chain can.
So we rebuild it on two independent machines, and until the same seal appears, we do not write “verified.”
Copyright ©  CAW JAPAN.com All Rights Reserved.
PAGE TOP
夜の薔薇園へ