Skip to content

fix: let a failed operator or watchtower proof task restart, and tell the node when it never will - #535

Open
KSlashh wants to merge 2 commits into
devfrom
audit/14-2-proof-task-failure
Open

KSlashh wants to merge 2 commits into
devfrom
audit/14-2-proof-task-failure

Conversation

@KSlashh

@KSlashh KSlashh commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator

Audit items M35, L62, L64, L65. Open against dev.

Failed tasks (M35). An operator or watchtower task that failed was final: every later submission got HTTP 200 and "not ready, state 3", and the node polled every 60 seconds until the assert window had passed, with no alert. Now:

  • Restart on resubmission: when the task owner submits a failed task again, the proof builder restarts it.
    • With the same inputs, it restarts at most twice (MAX_TASK_RESETS).
    • New inputs replace the stored ones and start the count over. For the operator task, the stored Watchtower slots are synced to the new inputs.
    • A task whose inputs can never be proven records why (invalid_reason). The same inputs do not restart it; new inputs do.
  • State in the response: both submit responses carry state: pending, proven, failed or cancelled. failed comes only when no restart is left, and error says why.
  • Node:
    • On failed, the node logs an error, raises operator_proof_failed / watchtower_proof_failed through the instance alert metric, and stops resubmitting: the assert or challenge job ends.
    • On cancelled, a watchtower stops quietly: its own timeout means the operator already asserted.

Request errors (L64). Bad watchtower lists in an operator task (length mismatch, bad or duplicate keys, a txid disagreeing with its included flag) were checked only while writing the task and came back as HTTP 500, which the node retries. They are now checked with the other request checks and return 400. The node treats a 400 from the proof builder as final, like failed. Other 4xx codes and 5xx are still retried.

One operator task per graph (L62). operator_proof gets a unique index on (instance_id, graph_id), and the migration keeps one row per graph: a proven task first, then an unfinished one, then the oldest. A racing second submission now fails its transaction, and the retry finds the stored task. Watchtower rows already have their own unique key.

Cancelled state (L65).

  • New ProofState::Cancelled (4).
  • The Watchtower slot rows that an operator task creates for watchtowers that never asked for a proof are now Cancelled, not Failed, so they no longer count as failed watchtower proofs.
  • The two timeout endpoints now set Cancelled as well.
  • bitvm_proof_builder_tasks reports these as state="cancelled".
  • Not changed: on-demand tasks still go from New straight to the result. A Proving step would race with the timeout, which only applies to New tasks.

Migration. 20261111000000_on_demand_proof_reset.sql rebuilds both tables, because SQLite cannot change their CHECK (proof_state IN (...)) in place. It adds reset_count and invalid_reason to both, and input_hash to operator_proof. Existing rows keep their state.

Review notes

  • API change for the frontend: ProofDesc.state can now be Cancelled, and an operator task's Failed can return to New after a resubmission.
  • The node and the proof builder must be upgraded together: an older node cannot read the new state field, and an older proof builder does not send it.

Tests: a failed operator task restarts twice on the same inputs, is refused the third time and restarts again on new inputs; a task with inputs that can never be proven restarts only on new inputs; a watchtower task restarts the same way; missing watchtower slots are cancelled; a second operator task for a graph is refused; a bad watchtower list is a 400; the node treats only a 400 and a failed task as final.

The test coverage and validation notes above are from the zkm PR notes. Branch scope and diff formatting were checked locally; builds and tests were not rerun during publication.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant