Step-by-step upgrade guides for every versioned surface of QuorumProof, with the breaking changes in each version and how to roll back (issue #1641).
QuorumProof has four independently versioned surfaces. Find the one(s) your upgrade touches, then follow that guide:
| Surface | Versions | Who migrates | Guide |
|---|---|---|---|
| REST API | v1 → v2 | API clients / integrators | API v1 → v2 |
Contract state layout (StateVersion) |
0 → 1 | Contract admin | Contract state v0 → v1 |
Credential data schema (SchemaVersion) |
V1 → V2 | Contract admin | Credential schema V1 → V2 |
| Contract WASM | any → any | Contract admin | Contract WASM upgrade |
| API-server database | 0001 → 0009 |
API-server operator | Database schema |
Every guide follows the same shape: breaking changes → prerequisites → steps → verify → rollback. Rollback procedures are collected in Rollback procedures, and how to rehearse a migration before running it for real is in the migration testing guide.
Before any migration: take a backup (
./scripts/backup.sh --encrypt), confirm it with./scripts/verify_backup.sh, and read the rollback section for the surface you are touching. Never start a migration you do not know how to undo.
| Change | v1 | v2 | Action |
|---|---|---|---|
| Response envelope | { "ok", "version", "data" } |
Raw resource object | Read fields from the body root. |
| Credential metadata field | metadata |
metadata_hash |
Rename in your models. |
| Address field | address |
stellar_address |
Rename in your models. |
| Pagination cursor | next_cursor |
cursor |
Rename when paging. |
| Errors | { "error": "…" } |
RFC 9457 Problem Details (type, title, status, detail) |
Read title/detail; switch on status. |
| New endpoints | — | /api/v2/proof-requests, /api/v2/revocation-registry, /api/v2/bbs-credentials |
Optional adoption. |
v1 lifecycle: maintenance since 2026-09-01 (no new features, Deprecation
and Sunset headers on every response), sunset 2027-03-01 (returns
410 Gone). Unversioned /api/... paths behave as v1.
No data transformation. Version 1 is the versioning baseline: it records a
StateVersion so that future layout changes can be migrated one step at a
time. See ADR-011.
Credential metadata is migrated to the V2 schema by a chunked, resumable migration job. Reads during the migration are unaffected; the per-credential metadata schema version is tracked as described in METADATA_SCHEMA_VERSIONING_PLAN.md. The invariants the migration must preserve are listed in migration-invariants.md.
Contract releases follow the semver rules in contract-upgrade-guide.md: a MAJOR bump means a breaking change to storage, error codes or public function signatures. Each release's breaking changes are listed in its changelog entry under Breaking Changes and Migration Required; that entry is the input to the WASM upgrade guide below.
Who: anyone calling the REST API. Downtime: none — v1 and v2 are served side by side until the v1 sunset.
- Know which endpoints you call. The
API-Versionresponse header and theDeprecationheader on v1 responses show where you are still on v1. - Your client can be deployed with v1 and v2 code paths behind a flag.
- Switch path prefix. Replace
/api/or/api/v1/with/api/v2/for the endpoints you call. - Remove envelope unwrapping. v1 clients typically read
body.data; read the body directly instead.- const credential = (await res.json()).data; + const credential = await res.json();
- Rename fields.
metadata→metadata_hash,address→stellar_address,next_cursor→cursor.- const hash = credential.metadata; + const hash = credential.metadata_hash; - while (page.next_cursor) { page = await list(page.next_cursor); } + while (page.cursor) { page = await list(page.cursor); }
- Update error handling. Parse Problem Details: branch on HTTP status,
show
title/detailto users.erroris no longer present.- if (!res.ok) throw new Error(body.error); + if (!res.ok) throw new Error(`${body.title}: ${body.detail ?? ''}`);
- Roll out behind a flag. Ship both code paths, enable v2 for a small share of traffic, compare responses and error rates, then ramp to 100%.
- Remove v1 code once v2 has run at 100% for a full release cycle, and before 2027-03-01.
Working v2 clients in four languages are in code-examples.md; every v1/v2 response shape is in api-response-examples.md.
- No response on your traffic carries a
Deprecationheader. - Error-rate and parse-failure metrics are unchanged after the ramp.
Flip the flag back to the v1 code path. Nothing is stored server-side per client version, so rollback is immediate. This option disappears on 2027-03-01.
Who: contract admin. Downtime: none (one transaction).
- The contract is running a WASM that includes
migrate_state. - Backup taken and verified.
- Read the current version — it must be
0:stellar contract invoke --id "$CONTRACT_QUORUM_PROOF" --network "$NETWORK" \ -- get_state_version
- Migrate one step.
to_versionmust equalfrom_version + 1:stellar contract invoke --id "$CONTRACT_QUORUM_PROOF" --network "$NETWORK" \ --source-account admin -- migrate_state \ --admin "$ADMIN_ADDRESS" --from_version 0 --to_version 1
- For future versions, repeat one step at a time (
1 → 2,2 → 3, …). Skipping versions is rejected by the contract.
get_state_version returns 1, and the smoke tests pass:
./scripts/testnet_smoke_test.sh.
v0 → v1 changes no stored data, so rolling back is not required; a
previous WASM ignores the StateVersion key. For later versions that do
transform data, see Rollback procedures.
Who: contract admin. Downtime: none; the migration runs in chunks across many transactions while the contract stays live.
get_schema_versionreturns1.- The contract is not paused (chunk calls are rejected while paused).
- Backup taken and verified.
- Rehearsed on a testnet copy of mainnet state (migration testing guide).
- Start the job:
stellar contract invoke --id "$CONTRACT_QUORUM_PROOF" --network "$NETWORK" \ --source-account admin -- start_migration_v1_to_v2 --admin "$ADMIN_ADDRESS"
- Drive chunks to completion. Use the crash-safe orchestrator, which
reads progress from the chain on every run and can be restarted at any
time:
Or call
CONTRACT_QUORUM_PROOF=... STELLAR_SECRET_KEY=... \ python3 scripts/migration_orchestrator.py --to-version 2 --chunk-size 100
migrate_chunk_v1_to_v2 --admin ... --chunk_size 100by hand until the checkpoint status isCompleted. - Monitor progress:
Watch
stellar contract invoke --id "$CONTRACT_QUORUM_PROOF" --network "$NETWORK" \ -- get_migration_status
migrated_items,failed_itemsandstatus. - Pause if needed.
pause_migrationstops progress without losing the checkpoint;resume_migrationcontinues from where it stopped.
stellar contract invoke --id "$CONTRACT_QUORUM_PROOF" --network "$NETWORK" \
-- validate_migration_integrity # must return true
stellar contract invoke --id "$CONTRACT_QUORUM_PROOF" --network "$NETWORK" \
-- get_schema_version # must return 2validate_migration_integrity is true only when the schema version is 2,
the checkpoint is Completed and failed_items is 0.
- While
InProgressorPaused: callrollback_migration --admin .... It resets the schema version to V1 and marks the checkpointFailed. - After
Completed:rollback_migrationhas no effect. Follow Restore from backup.
Who: contract admin. Downtime: none; upgrade swaps the WASM in place
and keeps the contract address and storage.
- Release changelog read; every item under Migration Required planned.
- Static compatibility check passes against the deployed tag:
python3 scripts/upgrade/check_state_compat.py --base <deployed-tag>. - contract-upgrade-checklist.md complete.
- Contract not paused (upgrades are blocked while paused).
- Record the current WASM hash — this is your rollback target:
stellar contract invoke --id "$CONTRACT_QUORUM_PROOF" --network "$NETWORK" \ -- get_upgrade_history
- Run pre-upgrade checks:
./scripts/pre_upgrade_checks.sh "$CONTRACT_QUORUM_PROOF" target/wasm32-unknown-unknown/release/quorum_proof.wasm - Upgrade with automatic smoke-test rollback:
The script snapshots the current hash, installs the new WASM, runs post-upgrade smoke tests and reverts automatically if they fail.
./scripts/upgrade_rollback.sh "$CONTRACT_QUORUM_PROOF" <new.wasm> <admin_key>
- Run any state or schema migrations the release requires (guides above).
- Repeat for
zk_verifierandsbt_registryif the release touches them, in deployment order.
For mainnet, follow the per-contract confirmation gates in mainnet-deployment-runbook.md. To schedule the upgrade for a later ledger, see scheduled-upgrades.md.
get_upgrade_history shows the new hash as the latest record, smoke tests
pass, and contract monitoring (contract-monitoring.md)
shows no new error codes.
Who: API-server operator. Downtime: none for additive migrations; check each migration's notes for locking operations.
- Back up Postgres.
- Deploy the new API-server build (migrations ship with it).
- Apply pending migrations:
The runner applies each pending
cd api-server && npm run migrate
NNNN_*.up.sqlin order, one transaction per migration, and records it inschema_migrations.
schema_migrations lists the new migrations, and /health/ready returns
200.
cd api-server && npm run migrate:rollback # undo the latest migration
cd api-server && npm run migrate:rollback -- 3 # undo the latest 3Each rollback runs the matching .down.sql. Roll the database back
before rolling the API server back to an older build. See
database-migrations.md.
Rollback order is the reverse of the upgrade order: API clients → API-server database → schema/state migrations → contract WASM.
- Find the previous WASM hash in
get_upgrade_history(or the snapshot file written byupgrade_rollback.sh). - Call
upgradewith that hash:On testnet,stellar contract invoke --id "$CONTRACT_QUORUM_PROOF" --network "$NETWORK" \ --source-account admin -- upgrade \ --admin "$ADMIN_ADDRESS" --new_wasm_hash <previous_hash>
./scripts/testnet_rollback.shautomates this. - Only revert the WASM if the old code can still read the current storage. If a data migration already rewrote storage into a format the old WASM cannot decode, roll the data back first (below) — otherwise the reverted contract will fail on read.
| Migration | In progress / paused | Completed |
|---|---|---|
| Credential schema V1 → V2 | rollback_migration |
Restore from backup |
| State version N → N+1 (data-changing) | n/a (single transaction) | Ship and run a reverse migration, or restore from backup |
Database NNNN |
n/a (transactional) | npm run migrate:rollback |
Use when a completed migration must be undone and no reverse migration exists.
pausethe contract to stop new writes.- Restore state with
./scripts/restore_from_backup.shfollowing backup-system.md and disaster-recovery.md. - Reconcile with
./scripts/reconcile_state.shand review any writes that happened between the backup and the pause. - Revert the WASM if needed, then
unpause.
- Record what happened and why in the release's incident notes.
- Keep the failed migration job's checkpoint for diagnosis — do not delete it.
- Fix forward in a new release; do not re-run the same migration unchanged.
Every release with a breaking change adds a section to this page in the same PR that introduces the change:
- Add a row under Breaking changes by version.
- Add a guide section using the breaking changes → prerequisites → steps → verify → rollback shape.
- Add or update the rehearsal steps in migration-testing-guide.md.
- Link the guide from the release's changelog Migration Required entry.
See also: contract-upgrade-guide.md · contract-upgrade-strategy.md · FAQ · Glossary