Error codes
Errors answer with a status code and a body like:
{"error": "spent", "message": "note 3:17 is already spent"}error is one of the stable codes below, listed on ApiError::code in
worgl-server.
message is for people and may change. worgl-client keeps the code in
Error::code, and the TypeScript SDK in MintError.code.
The request itself
Section titled “The request itself”| Code | Status | Meaning |
|---|---|---|
malformed |
400, 413, 415 or 422 | The body is not the JSON the route takes, is too large (over 64 KiB), lacks content-type: application/json, or spends no notes; or a new mint’s ledger, policy or period is refused. |
policy |
400 | The request breaks the mint’s policy: too many or too few inputs or outputs, an amount that is zero or not an allowed denomination, or outputs that do not add up to the inputs. |
invalid_note |
400 | An input note is malformed, forged, altered, from another mint, or from a generation the mint has no key for. |
duplicate_input |
400 | The same note appears twice among the inputs. |
Signatures and tokens
Section titled “Signatures and tokens”| Code | Status | Meaning |
|---|---|---|
unauthorized |
403 | A holder’s signature does not verify or was made by a key that is not the note’s, or a mint authority’s signature does not verify. |
unknown_authority |
403 | A mint request names a key the policy does not list as a mint authority. |
admin_unauthorized |
401 | The operator or mint admin token is missing or wrong. |
unknown_mint |
404 | No mint here has that issuer key, or the path does not hold an issuer key. On a cluster, also for a moment after a mint is created, until this member has read it. |
The mint’s state
Section titled “The mint’s state”| Code | Status | Meaning |
|---|---|---|
spent |
409 | An input was already spent. |
expired |
409 | An input’s layer has been dropped. |
frozen |
409 | An input is in the last layer and cannot move until the next rotation. |
unissued |
409 | The mint signed this note but its ledger has no record of it, which only happens if the mint’s state was lost or rolled back. |
overdrawn |
409 | The inputs spend more from a layer than the mint counts as live in it. This happens only after the mint’s state was rolled back, as by a restore from a backup, and it handed out the same serials again. See Backups. |
stale_mint |
409 | A mint request reuses a sequence number, or goes below one, that its authority already used. |
stale_rotation |
409 | A scheduled rotation is due no later than the last one logged, which already covered it. |
layer_full |
503 | The layer the new notes would land in has too few free bits left, and gets none back until a rotation drops it. A transfer refused so is refused every time; a mint request may pass after the next rotation. Nothing was done. |
paused |
503 | The mint is paused. Its notes keep their places until it resumes. |
busy |
503 | The mint holds receipt_capacity receipts, all younger than receipt_min_age_secs or, for a while after a restart, counted so, and takes no new transfer or burn until the oldest may go. The Retry-After header says in how many seconds. Nothing was done. |
The server
Section titled “The server”| Code | Status | Meaning |
|---|---|---|
storage |
500 or 503 | With 500: the mint could not record the change, so nothing was applied; but if its log could not even be cut back after the failed write, the change may have been applied. Once the server has been restarted, send a transfer, burn or mint request again, but check the mint’s info before sending a rotation again, which would rotate a second time. On a cluster, 500 also means this member’s copy of the mint has stopped. With 503: a read of a mint whose last change did not reach its disk and that could not reread its state from the disk, which it tries again at its next request; or, if its log could not be cut back after the failed write, a read before the server is restarted. See Running a server. GET /readyz answers 503 storage too while any mint is failing, ahead or stopped. |
internal |
500 | Something else went wrong on the server. |
no_leader |
503 | On a cluster: no member leading the mint, or the cluster’s registry, could be reached, or the leader changed while the request was carried out. Nothing was done. |
timeout |
503 | On a cluster: the mint’s group or the registry did not answer in time. The change may still be made. |
kek_mismatch |
403 | Only on a cluster member’s Raft port, which clients never use: the member that sent the Raft request holds another kek.key. See Running a cluster. |
kek_unavailable |
503 | Only on a cluster member’s Raft port: the member could not use its own key-encryption key to check the sender’s. The sender tries again later. |
misdirected |
421 | Only between cluster members, at a Raft port: a member’s Raft request reached that member itself, because its cluster.json gives another member an address that leads back to it. See Moving a member. |
Sending a request again
Section titled “Sending a request again”Transfers, burns and mint requests are safe to send again unchanged, and so are pauses and resumes. The mint answers a resent transfer or burn from its record of recent ones, and the latest mint request of an authority with the same note, and a pause of a paused mint or a resume of a running one changes nothing, so nothing happens twice.
- After
no_leaderortimeout, send the same transfer, burn, mint, pause or resume request again. - After
busy, send the same request again once the seconds itsRetry-Afterheader gives have passed. Both SDKs wait that long before their next try, up to 30 seconds. - A rotation (
POST /v1/mints/{mint}/rotate) is made again each time it is sent, and each one drops a layer, so note the last layer’sgenerationininfobefore you rotate. One answeredno_leaderwas not made. One answeredtimeout, or not answered at all, may have been, and may still be made after aninforead that does not show it, until the mint has carried out a change sent after that answer. To make one with the admin token, pause the mint, or resume it if it is paused; a transfer, burn or mint request sent after the answer counts too. Once the mint has carried it out, readinfo, and rotate again only if the last layer’sgenerationis still the one you noted, resuming the mint first if you paused it. - A creation of a mint (
POST /v1/admin/mints) answeredno_leadermade nothing, so send it again. One answeredtimeoutmay have made a mint the answer never named: list the mints before creating another. - Sending a rotation or a creation to another member of a cluster is sending it again, so check first there too.
paused,busy,storageandinternalmay go away later. Both SDKs count them, and any other 5xx status, as worth trying again:Error::is_transientin Rust andisTransient(error)in TypeScript.layer_fullon a transfer does not go away: sent again unchanged, its outputs would land in the same full generation after every rotation, until the note it spends expires. Neither SDK counts it as worth trying again, though its status is 503, and a purse sends such a transfer once. A mint request refused so may pass after the next rotation, which brings a newer generation to the mint layer. See Layer size.- A transfer or burn sent again is answered as before for at least
receipt_min_age_secs(128 seconds by default), and while it is among the lastreceipt_capacity(1,048,576 by default) the mint took. One sent again after that is refused asspenteven though it went through. See receipt_capacity and receipt_min_age_secs. - A rotation does not change a transfer’s answer while the mint remembers
it: a transfer sent again after its inputs’ layer was dropped still gets
the same notes, until those notes expire in turn. A burn sent again is
answered the same while its inputs’ layer is live or one of the last
layers − 1dropped, and refused asexpiredafter. - A transfer sent again and refused as
spentorexpiredis possibly applied: it may have gone through before, sent by the other side or by an earlier try whose answer was lost, and the mint no longer keeps the answer. For a transfer a purse made or is paid by, both SDKs say what may be lost, inRefused::at_stakein Rust andatStakein TypeScript, andworglprints it. If the other side submitted it, it got the mint’s answer and can hand the notes over:worgl receivewrites the payer’s change, andworgl submitthe payee’s notes, each kept withworgl accept(Purse::acceptorpurse.acceptin the SDKs). - A purse acts on the refusal of a transfer it made. One refused as
spentorexpiredwith status 409 is dropped, since the note it spends is gone either way, but the keys of its change are kept, so change handed over later is still taken in. One refused aslayer_fullwith status 503 is cancelled and its note taken back: the mint checks that the note is live before it looks for room, so it never applied the transfer, and never will. So is one refused aspolicywhile the policy the mint publishes refuses it too. Any other refusal leaves it outgoing, to send again:unknown_mintandunissued, for one, say nothing about the transfer, only about the server, and the mint never answersunauthorizedorduplicate_inputto a transfer a purse signed, so those come from something else, such as a gateway.Refused::outcomein Rust andoutcomein TypeScript say what was done, andworglprints it (see Refused transfers).
v0.1Not yet independently security reviewed. Don't use it to hold anything you can't afford to lose.MIT or Apache-2.0