非同期プローバー
SQS → Step Functions → ECS Fargate による非同期証明パイプラインの設計を解説します。
STARK 証明の生成は 64 票で約 6 分を要するため、同期的な HTTP リクエスト内では完了できません。非同期パイプラインにより、Web リクエストのタイムアウトを回避しつつ、高負荷な証明生成を安全に実行します。
この章は FINALIZE_ASYNC_MODE=true で動作する非同期経路を対象としています。
パイプライン全体像
flowchart TB
API["POST /api/finalize"] --> SQS["SQS<br/>ワークキュー"]
SQS --> DP["prover-dispatch-proxy<br/>Lambda"]
DP --> S3U["S3<br/>input.json 保存"]
DP --> SFN["Step Functions<br/>StartExecution"]
SFN --> SIG["check-image-signature<br/>Lambda"]
SIG --> CHK{"署名 COMPLETE?"}
CHK -->|Yes| ECS["ECS Fargate<br/>RunTask"]
CHK -->|No| SIGFAIL["署名検証失敗"]
ECS --> S3W["S3<br/>bundle.zip 保存"]
ECS --> CB["finalize-callback-runner<br/>Lambda"]
SIGFAIL --> CB
CB --> DDB["AppSync/DynamoDB<br/>セッション更新"]
詳細な失敗分岐(署名検証 NG、プローバー実行失敗、タイムアウト)は、後続のステートマシン図で示します。
各ステージの詳細
ステージ 1: リクエスト受付
クライアントが POST /api/finalize を呼び出すと、API ハンドラーは以下の処理を行います。
以下は FINALIZE_ASYNC_MODE=true の場合です。
- セッションの状態を検証(全投票が完了していること)
- zkVM 入力を構築(入力ビルダーが投票データ + Merkle パスを組み立て)
- セッションの
finalizationStateを「pending」に更新 - SQS キューにメッセージを送信
- クライアントに 202 Accepted を返却(
FINALIZE_ASYNC_MODE=falseの場合は同期処理のレスポンスを返却)
クライアントはその後、GET /api/sessions/:id/status をポーリングして進捗を確認します。
ステージ 2: ディスパッチ
prover-dispatch-proxy Lambda が SQS メッセージを受信し、以下を実行します。
- zkVM 入力 JSON を S3 にアップロード(
sessions/{sessionId}/{executionId}/input.json) - Step Functions のステートマシンを
StartExecutionで起動 - セッションの
finalizationStateを「running」に更新し、executionIdを記録
ステージ 3: 証明生成
Step Functions ステートマシンが 3 つのステップを順次実行します。
stateDiagram-v2 [*] --> VerifyImageSignature VerifyImageSignature --> CheckImageSignature CheckImageSignature --> RunProver: 署名 COMPLETE CheckImageSignature --> FinalizeSignatureFailed: 署名 NG RunProver --> FinalizeSucceeded: 成功 RunProver --> FinalizeFailed: 失敗 FinalizeSignatureFailed --> [*] FinalizeSucceeded --> [*] FinalizeFailed --> [*]
VerifyImageSignature
check-image-signature Lambda を呼び出し、ECR イメージのダイジェストに対する AWS Signer 署名のステータスを確認します。詳細は イメージ署名 を参照してください。
CheckImageSignature
Choice ステートで署名ステータスを判定します。COMPLETE(署名検証済み)であれば RunProver に進み、それ以外は FinalizeSignatureFailed に遷移してコールバック Lambda に失敗を通知します。
RunProver
ECS Fargate タスクを ecs:runTask.sync(同期モード)で起動します。Step Functions はタスクの完了を待機し、成功/失敗に応じて対応するコールバックステートに遷移します。
ECS タスクのコンテナには、Step Functions の入力からセッション固有の環境変数が注入されます。
| 環境変数 | 値の由来 | 説明 |
|---|---|---|
ENV_NAME | Terraform 変数 | 環境名(develop / main) |
S3_PROOF_BUCKET | Terraform 変数 | 証明バンドルバケット名 |
INPUT_S3_BUCKET | Terraform 変数 | 入力ファイルのバケット(同上) |
INPUT_S3_KEY | Step Functions 入力 | セッション固有の入力パス |
OUTPUT_S3_BUCKET | Terraform 変数 | 出力先バケット(同上) |
OUTPUT_S3_PREFIX | Step Functions 入力 | sessions/{sessionId}/{executionId}/ |
ステージ 4: 結果通知
Step Functions が finalize-callback-runner Lambda を呼び出し、以下の情報をセッションに書き戻します。
- 成功時: S3 上のバンドルメタデータ(バケット、キー)、証明結果
- 失敗時: エラー情報(イメージ署名失敗、プローバーエラーなど)
ECS タスクの実行フロー
ECS Fargate タスク内のコンテナは、エントリポイントスクリプトにより以下の処理を順次実行します。
flowchart TD A["S3 から入力 JSON を<br/>ダウンロード"] --> B["入力の構造を検証<br/>(必須フィールド確認)"] B --> C["zkVM ホストバイナリを実行<br/>(タイムアウト: 900 秒)"] C --> D["ジャーナルを JSON に変換"] D --> E["public-input.json を構築"] E --> F["bundle.zip を作成<br/>(receipt + journal + public-input)"] F --> G["S3 にアップロード<br/>(リトライ付き)"]
入力の検証
エントリポイントは、zkVM 入力 JSON に必要なフィールドが存在することを確認します。欠落があればタスクは即座に失敗し、Step Functions が失敗コールバックを実行します。
zkVM ホストバイナリの実行
コンテナ内の /opt/zkvm/bin/host がプローバーとして起動されます。タイムアウトは 900 秒(15 分)です。本番モード(RISC0_DEV_MODE 未設定)では実際の STARK 証明が生成され、64 票で約 370 秒を要します。
S3 アップロード
生成されたアーティファクトは、指数バックオフ付きのリトライ(最大 3 回、基底 2 秒)で S3 にアップロードされます。実際にアップロードされるファイルは以下です。
| ファイル | 説明 |
|---|---|
*-receipt.json | zkVM host の生出力(レシート) |
*-output.json | zkVM host の生出力(集計結果) |
public-input.json | エントリポイントが構築する公開入力 |
bundle.zip | receipt.json + journal.json + public-input.json の公開バンドル |
journal.json は *-output.json からエントリポイント内で再構成され、bundle.zip の中に receipt.json とともに格納されます。
SQS キュー設計
ワークキュー
| 項目 | 設定 | 理由 |
|---|---|---|
| 可視性タイムアウト | 1000 秒 | zkVM 実行タイムアウト(900 秒)+ バッファ |
| メッセージ保持期間 | 4 日 | 一時的な障害からの回復猶予 |
| ロングポーリング | 20 秒 | Lambda のポーリングコスト最適化 |
| 暗号化 | SQS マネージド SSE | デフォルト暗号化 |
デッドレターキュー(DLQ)
3 回の受信失敗後、メッセージは DLQ に移動されます。DLQ のメッセージ保持期間は 14 日で、手動での障害調査と再処理に使用されます。
ECS Fargate タスク仕様
| 項目 | 設定 |
|---|---|
| CPU | 16 vCPU(16384 ユニット) |
| メモリ | 32 GB(32768 MiB) |
| アーキテクチャ | ARM64(Graviton) |
| ネットワークモード | awsvpc |
| 起動モデル | RunTask(サービスなし、1 回限りのタスク) |
| イメージ指定 | ダイジェスト固定(@sha256:...) |
| ログドライバー | CloudWatch Logs(awslogs) |
ARM64 アーキテクチャの選択は、RISC Zero の STARK 証明生成における Graviton プロセッサのコスト効率に基づいています。
クライアント側のポーリング
非同期証明の進捗は、クライアントが GET /api/sessions/:id/status をポーリングして確認します。
stateDiagram-v2 [*] --> pending: POST /api/finalize → 202 pending --> running: dispatch-proxy が SFN を起動 running --> succeeded: callback-runner が結果を書き込み running --> failed: エラー発生 succeeded --> [*] failed --> [*]
| ステータス | 説明 |
|---|---|
pending | ファイナライズ要求を受理済み(実装上は pending 更新後に SQS 送信) |
running | Step Functions が実行中 |
succeeded | 証明生成と結果の書き戻しが完了 |
failed | いずれかのステージで失敗 |
timeout | finalize-callback-runner が TIMED_OUT を受理した場合の状態(現行 State Machine では通常未使用) |
障害時の調査導線
非同期証明がスタックした場合の最小調査パスです。
flowchart TD
START["finalize がスタック"] --> Q1{"SQS に<br/>メッセージあり?"}
Q1 -->|No| A1["API → SQS の送信を確認<br/>環境変数 PROVER_WORK_QUEUE_URL"]
Q1 -->|Yes| Q2{"dispatch-proxy<br/>ログに成功あり?"}
Q2 -->|No| A2["Lambda ログを確認<br/>SQS → Lambda のトリガー設定"]
Q2 -->|Yes| Q3{"SFN の<br/>実行状態は?"}
Q3 -->|署名失敗| A3["ECR イメージ署名を確認<br/>Signer プロファイル設定"]
Q3 -->|ECS 失敗| Q4{"ECS タスク<br/>ログに出力あり?"}
Q4 -->|No| A4["タスク起動失敗<br/>IAM / サブネット / イメージ URI"]
Q4 -->|Yes| A5["エントリポイントエラーを確認<br/>入力検証 / プローバー実行"]
Q3 -->|コールバック失敗| A6["callback-runner ログを確認<br/>AppSync 書き込み権限"]