Program Escrow — Circuit Breaker
Last updated: 16 August 2026
The only Soroban contract in this project is grainhack-escrow
(initialise, fund, publish_root, claim, is_claimed, sweep), and it
has never been deployed. The program-escrow and bounty-escrow surfaces this
page describes — publish_program, lock_program_funds, single_payout,
batch_payout, create_bounty and the rest — have no implementation in any
repository, verified 16 August 2026.
This page is kept as a design record. Do not read it as a description of working software, and do not implement against it without checking what the contract actually exposes. See Payout path — what exists today.
This document describes the circuit breaker, admin reset, and security model for the program-escrow contract.
Overview
The circuit breaker protects payout operations from cascading failures by tracking consecutive failures and moving through three states:
Closed— normal operationOpen— all protected operations rejected (requires admin action)HalfOpen— trial period; successes move toClosed, failures re-open and restart the recovery timeout
The circuit breaker lives in persistent storage under keys defined in error_recovery::CircuitBreakerKey.
Admin Hard Reset: reset_circuit_breaker
We added an admin-only entrypoint to immediately reset the circuit breaker to Closed and clear failure counters.
- Signature:
reset_circuit_breaker(env: Env, program_id: String) -> Result<(), Error> - Authorization: caller must be the contract admin (value stored at
DataKey::Admin) and must sign the transaction. - Effects:
- Calls into
error_recovery::close_circuit()which sets state toClosed, clearsFailureCountandSuccessCount, and clearsOpenedAt. - Additionally ensures
FailureCountis zero (defensive). - Emits an audit event
(circuit, admin_reset)with payload(program_id, admin_address, timestamp).
- Calls into
Security Notes
- Only the contract admin can call this entrypoint; the admin address is read from instance storage and
require_auth()is enforced. - The function performs a defensive clear of failure counters and emits a deterministic, auditable event so off-chain monitors can verify the manual reset.
- Prefer manual resets only after a root-cause investigation; use
HalfOpentrial behavior for staged recovery when appropriate.
Tests
Unit tests were added under contracts/program-escrow/src/test_admin_reset.rs verifying that:
- An admin-authorized reset transitions an
Opencircuit toClosedand clears counters. - A non-authorized caller cannot reset the circuit (panics / is rejected).
Additional enforcement tests now cover the full HalfOpen lifecycle:
Open→HalfOpenafterrecovery_windowelapsesHalfOpen→Closedon a successful probeHalfOpen→Openon a failed probe, with the open timer reset
Note: the repository contains many existing tests; depending on the environment some test suites may not compile or run fully.
Implementation Notes
- The implementation reuses
error_recovery::close_circuit()for the state transition and counter resets. - The audit event topic
admin_resetis stable and includes theprogram_idto help indexers correlate the reset to a program context.
If you want, I can also update integration tests to exercise the end-to-end flow for batch_payout/single_payout interactions with the breaker.