A guide for diagnosing problems encountered while using QuorumProof — whether you're an issuer, a verifier, or an end user whose credential isn't behaving as expected. It's organized as: common errors and fixes, a decision tree to route you to the right fix quickly, and a guide to reading the logs you'll be looking at along the way.
For integration-specific failures (SDK/RPC-level issues hit while building against QuorumProof), see Integration Patterns Guide §5 instead — this document is about diagnosing a problem with a specific credential, transaction, or deployment.
| Error / Symptom | What it means | Solution |
|---|---|---|
Error(Contract, #1) — CredentialNotFound |
The credential ID doesn't exist on this contract/network | Confirm you're pointed at the right CONTRACT_QUORUM_PROOF address and network (testnet vs mainnet); confirm the ID was actually returned by a prior issue_credential call |
Error(Contract, #3) — ContractPaused |
An admin has paused the contract (usually during an incident or upgrade) | Wait for unpause; check the project status page or ask the issuer when service will resume |
Error(Contract, #4) — DuplicateCredential |
A credential already exists for this subject + issuer + type | Not necessarily a bug — look up the existing credential instead of re-issuing; see Integration Patterns Guide for the idempotency pattern |
| Verification says "not attested" but you were told it was approved | The quorum slice threshold hasn't been met yet, or you're checking against the wrong slice_id |
Confirm the slice_id used at attestation time and check get_attestation_count against the slice's threshold |
| Verification says a credential is invalid, but you have proof it was issued | The credential was revoked or suspended after issuance | Call get_credential and check the revoked / suspended fields, then check RevocationLog events for the reason |
| Transaction submitted but nothing happens | The transaction may still be in flight, or failed simulation silently | Look up the transaction hash via getTransaction on the RPC endpoint; a PENDING status just needs more time, FAILED needs the error inspected |
| "Insufficient fee" or resource-limit errors | Soroban's resource-based fee model rejected the transaction footprint | Re-simulate the transaction to get an updated resource estimate rather than reusing a stale one |
Snapshot restore fails with Error(Contract, #85) |
The snapshot_id passed to restore_from_snapshot doesn't exist |
Call list_snapshots() to see valid IDs |
Snapshot restore fails with Error(Contract, #86) |
The snapshot's stored data no longer matches its own recorded hash — the snapshot record was corrupted | Use a different snapshot, or fall back to the off-chain backup described in Backup System |
| Metadata (IPFS content) won't resolve | The metadata was never pinned redundantly, or the pinning service expired | Check pin status with your IPFS provider; this is a known partial-mitigation risk, see Threat Model — Metadata Availability Loss |
The full per-code reference, including every error across all three contracts, is in Error Code Reference — use the table above to triage quickly, then look up the exact code there for the authoritative recovery steps.
Start at the top and follow the first branch that matches your symptom.
Something isn't working. What are you seeing?
│
├─ A transaction/call raised "Error(Contract, #N)"
│ │
│ ├─ Is N in the 1-10 range (not-found / duplicate / basic validation)?
│ │ → Look up #N in Error Code Reference — usually a caller-side
│ │ mistake (wrong ID, wrong network, already-done action).
│ │
│ ├─ Is it #3 (ContractPaused)?
│ │ → Not a bug on your end. Check SECURITY.md / status channel
│ │ for an active incident, then retry after unpause.
│ │
│ ├─ Is it #85 or #86 (snapshot-related)?
│ │ → See Backup System — On-Chain State Snapshots.
│ │
│ └─ Is it something else / unrecognized?
│ → Capture the full error string + tx hash, check Error Code
│ Reference for the code's contract of origin, then escalate
│ (see §4) if still unclear.
│
├─ No error was raised, but the result looks wrong
│ │
│ ├─ A credential you expect to be valid reads as invalid
│ │ → Check revoked/suspended flags first (see §1), then check
│ │ attestation status against the *correct* slice_id.
│ │
│ ├─ A count (credential/slice count) looks lower than expected
│ │ → You may be reading from a stale RPC node, or an indexer
│ │ (if you built one) missed events. Re-read from the RPC node
│ │ that processed the write, or reconcile against
│ │ create_state_snapshot / get_snapshot.
│ │
│ └─ Metadata content (off-chain, e.g. IPFS) won't load
│ → The hash on-chain is still valid; this is an availability
│ problem with the metadata host, not a contract issue.
│
└─ Nothing happens at all (no error, no result)
│
├─ Did you call sendTransaction and stop, without polling getTransaction?
│ → That's expected — sendTransaction returns immediately with a
│ PENDING-style ack; poll getTransaction until SUCCESS/FAILED.
│
└─ Still nothing after polling for a full ledger close cycle (~5-6s+)?
→ Check RPC node health / status; see Logs Interpretation below
for what to look for in your own service logs.
QuorumProof produces two kinds of logs you'll typically be reading: on-chain contract events (the authoritative record) and your own application/service logs (network calls, retries, RPC responses).
Every state change emits a Stellar contract event. The canonical field reference is Audit Log Format; the fields you'll use most often when troubleshooting:
event_type— tells you what happened (CredentialIssued,CredentialRevoked,AttestationRecorded, etc.). Start here to confirm the action you expect actually occurred on-chain.timestamp— ledger close time (Unix seconds); compare against your application's local timestamp for the same action to spot clock drift or delayed propagation.credential_id/slice_id— the handles you'll cross-reference against your own system of record. A mismatch between "the ID my backend has" and "the ID that emitted this event" is one of the most common root causes of "verification says invalid" reports.reason(onCredentialRevokedand dispute-related events) — the human-readable justification supplied by the issuer or disputant; check this before assuming a revocation was erroneous.
To fetch events for a specific credential or time range, use the RPC
getEvents call filtered by contract ID and ledger range, as shown in
Integration Patterns Guide §3 (Auditor Pattern).
If you operate an issuer, verifier, or auditor backend:
- Log the transaction hash on every submitted call — it's the only reliable way to correlate an application-level failure with what actually happened on-chain (or didn't).
- Log the raw RPC error body, not just a summarized message —
Error(Contract, #N)is easy to grep for across a fleet of logs, and losing the code during summarization is a common cause of "I don't know which error this was" support tickets. - Distinguish simulation failures (rejected before submission — e.g. bad footprint, insufficient resource fee) from execution failures (submitted, included in a ledger, but the contract call itself panicked) — they need different fixes. Simulation failures are usually infrastructure/SDK issues; execution failures are usually the contract error codes covered in §1.
- If you run the on-chain snapshot/backup tooling from
Backup System, the scheduled GitHub Actions workflow
(
.github/workflows/backup.yml) logs are the first place to check for a missed or failed backup —gh run list --workflow backup.yml.
- Error Code Reference — authoritative per-code recovery
- Integration Patterns Guide — patterns and retry logic for developers
- Audit Log Format — full event schema
- Threat Model — why a given risk is rated the way it is
- Backup System — recovering from data loss or corruption
- Monitoring Guide — dashboards and alerts for ongoing operations
If your issue isn't covered above and you believe it's a security vulnerability rather than an operational problem, follow the reporting process in SECURITY.md instead of filing a public issue.