C-2 Validator Independence
C-2 Validator Independence
C-1/C-3/C-4 は「ノードがレコードを読む側」の独立性でした。C-2 はバリデータ――署名アクションを検証し、バッチにしてチェーンへ書く側――を扱います。論点は3つ:#16 誰でもバリデータになれるか(承認)、#17 単一バリデータが署名アクションを検閲できるか、#18 別バリデータが公開入力から同じキューを再構築できるか。ここは正直に言うと、3つのうち2つは明確に検証済み、1つ(#18)は設計通り――調べ切った結果、再構築できないのが意図的な設計だと確認できた項目です。
バリデータは、ユーザーが署名した手紙を束ねて公共の掲示板(チェーン)に貼る公証人のような役です。誰でも公証人になれるか(#16)、ある公証人が貼るのを拒んでも別の人が貼れるか(#17)、そして貼られる前の順番待ちの列を外部から同じに再現できるか(#18)――を確かめます。
→
誰でも登録・誰でも提出承認ゲート無し
→
検閲不能/確定順序は再現可保留列は open door
結果:登録も提出も全段で許可不要、署名アクションは誰でも(本人でも)提出できるので単一バリデータは検閲できない。確定したアクションの順序は再構築可能(C-3)。ただし保留中の列を公開入力だけから厳密に再現する公開 gossip は無く、ここは open door として残します。
1. 主張(検証対象)
#16 バリデータ参加(クライアント作成・インスタンス登録・バッチ送信)が許可不要であること。#17 署名アクションを単一バリデータが検閲できないこと。#18 別バリデータが公開入力から同じキューを再構築できること。
2. 検証方法
#16 提出経路の呼び出しゲートを読む
デプロイ済み契約(live V1)の processActions、および createClient/registerInstance に、運営による承認ゲートがあるかをソースで確認。
#17 検閲できる単一点があるか
署名アクションを「誰が提出できるか」を確認。特定バリデータに限定されるなら検閲点、誰でも(本人でも)提出できるなら検閲不能。client 側のピアミラー機構も確認。
#18 保留キューの入口と共有方法
保留アクションがどこから入り、外部バリデータがそれを公開入力から再構築できるか(公開 gossip/mempool の有無)を確認。
3. 判定
createClient・registerInstance・processActions のいずれにも承認ゲートが無く、安全性は各アクションの EIP-712 署名。保留キューを公開入力だけから厳密再構築することは意図的にできない設計で、#18 は「設計通り」(中央集権の穴ではない)。- #16 許可不要
createClient(public)・registerInstance(ゲート無)・processActions(caller 無制限)。validatorIdは帰属パラメータのみ。私の instanceId 232(本番レジストリ上・owner=自ノードの validator0x4fe9…380D)が自己登録の実例で、さらにランダム住所からの登録 simulate が新 id(475)を返す=今も許可不要をライブ実証。 - #17 検閲耐性 署名アクションは誰でも・本人でも提出可(安全性は EIP-712 署名)。さらに client 側のピアミラー(
validated_by_peer/”peer mirror submitted”)が冗長化し、単一バリデータは検閲不能。 - #18 部分 確定順序はオンチェーンで canonical=再構築可(C-3)。一方保留キューは各ノード API→TxQueue でピア間ミラーされるが、任意外部からの厳密再構築用の公開 gossip は無し=open door。
示せたこと:バリデータ層は許可不要かつ検閲耐性あり。クライアント作成→インスタンス登録→バッチ送信のどこにも運営の承認ゲートは無く、提出は msg.sender 無制限で各アクションの署名だけが効く。あるバリデータが出さなくても、別ピアやユーザー本人が提出でき、ピアミラーが自動で冗長化します。
まだ示せないこと(open door)= #18:保留中のアクション列を公開入力だけから外部が厳密に再構築することはできません。理由は2つあり、いずれも設計上の意図です:(1) 公開 API で吸い出せない――/txqueue/status は認証必須かつ自分の senderId 分のみ返す(監査修正 Round 5 API MED-1:誰でも連番 id を列挙して署名済みペイロード〔受信者・金額・予約チップ〕を吸い出すのを防ぐプライバシー保護)。(2) グローバルな保留 mempool が存在しない――保留は各ノードの TxQueue にローカルに入り、FE が署名アクションを複数ピアへ fan-out して競争提出する方式で、全体像を持つ単一主体がいない。したがって #18 は「中央集権の穴」ではなく、プライバシー+分散提出による意図的な非再構築です。確定後(オンチェーン)の順序は C-3 で再構築可能、検閲耐性(#17)はこの欠如と独立に成立。文字どおりの「厳密再構築」要件は原理的に未達ですが、それが意図的な健全設計と確認できたため #18 は「設計通り」に分類します(欠陥ではない)。両ノードの運用者が同一である点も C-1/C-3 と共通。
a26e0063)、client は e2074718 時点・2026-06-23。4. receipt(公開証明)
提出経路と登録の許可不要性(デプロイ済み契約のソース):
| 対象 | 可視性/ゲート | 意味 |
|---|---|---|
| processActions(validatorId, packedActions, sigs, …) | external payable (caller 無制限) |
誰でもバッチ提出可。validatorId は帰属、安全性は "Sigs don't cover all actions"(EIP-712) |
| createClient(…) | public(ゲート無) | クライアント作成は許可不要 |
| registerInstance(clientId, apiUrl, validatorAddress) | ゲート無 nextInstanceId++ |
インスタンス登録は許可不要。instanceOwner[id]=msg.sender |
| updateInstance / deactivate / activate | instanceOwner==msg.sender | 自分のインスタンスのみ変更可(正当な所有権ゲート) |
processActions 内の msg.sender チェックは内部 self-call ガードのみで、外部呼び出しを制限しません:
CawActions.sol processActions(...) external payable # 呼び出しゲート無し require(c.actionsSeen == actionCount, "Sigs don't cover all actions") # 署名で担保 require(msg.sender == address(this), "Only self") # 内部 self-call のみ(外部制限ではない)
#16 のライブ実証:本番 CawClientManager(L1 Sepolia, 0xA5C515…1424C7a)への eth_call(state 変更なし)。無関係なランダム住所を from に登録を simulate し、新 id が返れば誰でも登録できる証拠です:
# 本番 CawClientManager(L1 Sepolia)への eth_call nextInstanceId() # → 475(474 件登録済みの稼働レジストリ) instanceOwner(232) # → 0x4fe9…380D(私の node1 validator=232 は自分の自己登録) registerInstance(1,…) from=0x…DeAdBeef # → 475(新 id 返却)=client 1 存在・caller ゲート無し=今も許可不要
#17 のピアミラー(単一バリデータが出さなくても別ピアが提出):
ValidatorService/index.ts status: 'validated_by_peer' # ピアが検証 "action already on chain (peer mirror submitted) — marking done" # 別ピアが提出して冗長化
#18 の線引き(確定順序と保留キュー):
保留キュー(API→TxQueue)公開 gossip 無し=open door
5. 注記・限界
#18 が本項目最大の open doorです。保留列を公開入力だけから厳密再構築することは原理的にできませんが、これは2つの設計意図によります:(1) /txqueue/status は認証必須+自分の senderId 分のみ返す(監査修正によるプライバシー保護)、(2) グローバル保留 mempool が無く、各ノード TxQueue+FE の複数ピア fan-out で分散提出される。よって中央集権の穴ではなく、意図的な非再構築です。確定後の順序は C-3 で再構築可能で、検閲耐性(#17)はこの欠如とは独立に成立します。
なお validatorId には手数料の帰属がぶら下がるため、提出には経済的インセンティブの設計が絡みますが、提出そのものを構造的に妨げるゲートは無い(本人提出も可)という事実が #17 の根拠です。
対象はオンチェーンのアクション。DM・画像はオフチェーン、Base L2 シーケンサ依存、両ノード運用者が同一である点は C-1/C-3 と共通の open door です。
誰でも入れることを示し、誰も閉め出せないことを示し、まだ見通せない奥の列は、見通せないと正直に書く。
C-1 / C-3 / C-4 covered the independence of the side that reads the record. C-2 is about the validator — the side that verifies signed actions, batches them, and writes them to the chain. Three questions: #16 can anyone become a validator (approval), #17 can a single validator censor signed actions, and #18 can a different validator reconstruct the same queue from public inputs. Honestly: two of the three are clearly verified, and one (#18) is by design — examined and found to be an intentional design, not a defect.
A validator is like a notary who bundles user-signed letters and posts them to a public board (the chain). We check whether anyone can become a notary (#16), whether someone else can still post a letter if one notary refuses (#17), and whether an outsider can reconstruct the exact waiting line before posting (#18).
→
anyone registers / submitsno approval gate
→
uncensorable / settled order reproduciblepending line is an open door
The result: registration and submission are permissionless at every stage, and because signed actions can be submitted by anyone (including the user), no single validator can censor. The settled action order is reconstructable (C-3). But there is no public gossip to reconstruct the pending line exactly from public inputs — that remains an open door.
1. Claim (what is verified)
#16 that validator participation (client creation, instance registration, batch submission) is permissionless; #17 that a single validator cannot censor signed actions; #18 that a different validator can reconstruct the same queue from public inputs.
2. Method
#16 read the call gates on the submission path
Confirm in source whether the deployed (live V1) processActions, and createClient / registerInstance, have any operator approval gate.
#17 is there a single point that can censor
Confirm who can submit a signed action. Restricted to a specific validator = a censorship point; submittable by anyone (including the user) = uncensorable. Also check the client-side peer-mirror mechanism.
#18 the pending queue’s entry and sharing
Confirm where pending actions enter and whether an external validator can reconstruct them from public inputs (is there a public gossip/mempool).
3. Verdict
createClient / registerInstance / processActions has an approval gate; security rests on each action’s EIP-712 signature. Reconstructing the pending queue exactly from public inputs is intentionally not possible by design — #18 is “by design” (not a centralization gap).- #16 permissionless
createClient(public),registerInstance(no gate),processActions(caller unrestricted).validatorIdis an attribution param only. My instanceId 232 (on the production registry, owner = my node’s validator0x4fe9…380D) is a real self-registration, and a register simulation from an unrelated random address returns a fresh id (475) — still permissionless, confirmed live. - #17 uncensorable Signed actions can be submitted by anyone, including the user (security is the EIP-712 signature). The client-side peer mirror (
validated_by_peer/ “peer mirror submitted”) adds redundancy, so a single validator cannot censor. - #18 partial The settled order is canonical on-chain = reconstructable (C-3). The pending queue enters via each node’s API → TxQueue and is mirrored among peers, but there is no public gossip for exact external reconstruction = open door.
Proven: the validator layer is permissionless and censorship-resistant. There is no operator approval gate anywhere from client creation to instance registration to batch submission; submission is msg.sender-unrestricted and only each action’s signature counts. If one validator withholds, another peer or the user can submit, and the peer mirror adds redundancy automatically.
Not yet proven (open door) = #18: an outsider cannot reconstruct the pending action line exactly from public inputs. There are two reasons, both intentional design: (1) the public API won’t dump it — /txqueue/status requires auth and returns only your own senderIds (audit fix Round 5 API MED-1: a privacy protection so no one can enumerate sequential ids and dump signed payloads — recipients, amounts, scheduled tips); (2) there is no global pending mempool — pending actions live in each node’s local TxQueue, and the FE fans each signed action out to several peers that race to submit, so no single party holds the whole picture. So #18 is not a centralization gap but an intentional non-reconstructability from privacy + distributed submission. The settled (on-chain) order is reconstructable via C-3, and censorship resistance (#17) holds independently. Since the literal “exact reconstruction” requirement is unattainable in principle and this is an intentional sound design, we classify #18 as “by design” (not a defect). That both nodes share one operator is the same open door as C-1 / C-3.
a26e0063); client at e2074718, 2026-06-23.4. Receipt (public proof)
The submission path and registration permissionlessness (deployed-contract source):
| Target | Visibility / gate | Meaning |
|---|---|---|
| processActions(validatorId, packedActions, sigs, …) | external payable (caller unrestricted) |
Anyone can submit a batch. validatorId is attribution; security is "Sigs don't cover all actions" (EIP-712) |
| createClient(…) | public (no gate) | Client creation is permissionless |
| registerInstance(clientId, apiUrl, validatorAddress) | no gate nextInstanceId++ |
Instance registration is permissionless. instanceOwner[id]=msg.sender |
| updateInstance / deactivate / activate | instanceOwner==msg.sender | Only your own instance can be changed (a legitimate ownership gate) |
The msg.sender checks inside processActions are internal self-call guards only; they do not restrict external callers:
CawActions.sol processActions(...) external payable # no call gate require(c.actionsSeen == actionCount, "Sigs don't cover all actions") # secured by signatures require(msg.sender == address(this), "Only self") # internal self-call only (not an external restriction)
#16 live demonstration: an eth_call to the production CawClientManager (L1 Sepolia, 0xA5C515…1424C7a), no state change. Simulating registration with an unrelated random address as from; a fresh id returned proves anyone can register:
# eth_call to the production CawClientManager (L1 Sepolia) nextInstanceId() # → 475 (a live registry with 474 instances) instanceOwner(232) # → 0x4fe9…380D (my node1 validator = instanceId 232 is my own registration) registerInstance(1,…) from=0x…DeAdBeef # → 475 (fresh id) = client 1 exists, no caller gate = still permissionless
#17’s peer mirror (another peer submits if one validator withholds):
ValidatorService/index.ts status: 'validated_by_peer' # validated by a peer "action already on chain (peer mirror submitted) — marking done" # another peer submitted, adding redundancy
#18’s boundary (settled order vs pending queue):
pending queue (API→TxQueue)no public gossip = open door
5. Notes & limits
#18 is this item’s biggest open door. The pending line can’t be reconstructed exactly from public inputs — but for two intentional design reasons: (1) /txqueue/status requires auth and returns only your own senderIds (a privacy fix from the audit), and (2) there is no global pending mempool — actions live in each node’s TxQueue and the FE fans them out to several peers for distributed submission. So this is not a centralization gap but intentional non-reconstructability. The settled order is reconstructable via C-3, and censorship resistance (#17) holds independently.
Note that validatorId carries fee attribution, so submission involves economic-incentive design; but the fact that nothing structurally blocks submission (self-submission is allowed) is the basis for #17.
The scope is the on-chain action stream. DMs and images off-chain, the Base L2 sequencer, and both nodes sharing one operator are the same open doors as C-1 / C-3.
Show that anyone may enter, show that no one can be shut out, and say plainly when the line further in is not yet visible.






