REST: ledger

Four endpoints under /ledger let a program read the ledger of the node and send a transaction to it. They need the bearer token like every other endpoint, see REST interface. A node started without the hidden --preview switch answers all four with 503 and This release does not include the ledger part, as release 0.0.9 leaves the ledger off. A node started with --preview but not with --ledger, or whose ledger has not started yet, answers them as a node without a ledger: GET /ledger says running: false and the others answer 503. The node never signs for a client: the client signs the transaction and sends its bytes, so a key never reaches the node. Ledger command line is a client of these endpoints.

Endpoint Returns
GET /ledger The state of the ledger: chain, tip, supply, validators and minimum fee
GET /ledger/account/{address} The balance of an account or a legacy address; for what the legacy chain held, see REST: legacy chain
GET /ledger/block/{height} The block at a height, with its transactions
POST /ledger/transaction Hands a signed transaction to the ledger

How values are written. An amount is a string of coins with exactly eight decimals, such as "12.50000000", never a number, so nothing is rounded on the way. A hash is 128 lower-case hex characters. An account is a public key of 64 lower-case hex characters. A legacy address is base58. A time is in Unix milliseconds. A field that does not apply is left out, not set to zero. An error is {"error": "<reason>"}.

GET /ledger

GET /ledger answers 200 with these fields:

Field Meaning
running The ledger has started and has neither stopped nor halted
halted, haltReason Whether the ledger halted, and why; the reason is empty otherwise
signing The node runs, its newest node was built to sign, and its key is one of the validators of the newest ledger
chainId The id of the chain, 128 hex characters
height, tipHash The height and hash of the newest block; 0 and empty before there is a ledger
supply The coins that exist
round The validator round of the node’s clock, never before the tip’s, which the next block it would make is in and a vote is signed for
roundEnds When that round ends and the next starts, in Unix milliseconds
validators The keys of the validators that sign blocks now, in the order that decides who proposes
roundValidators The set that a vote of round is judged by; it differs from validators between the end of a round and the block that closes it
minimumFee The lowest fee of a transfer that this node takes; left out while the fee spork is not set
droppedInbound How many items the node has dropped from its queue of incoming ledger messages
unsetSporks, laggingSporks The ledger sporks that are not set, and those of which a newer version is known that this node does not hold yet, by name: MINT_STORAGE, MINT_SUPPLY, LEDGER_BLACKLIST and LEDGER_FEE

A node answers no for a spork that is unset or lagging, so anything that depends on it is not minted, claimed or taken until it is set. Before the ledger exists the answer holds only running, halted, haltReason, signing, height, tipHash, droppedInbound and the two lists. A halted ledger is still read from the blocks it holds.

GET /ledger/account/{address}

GET /ledger/account/{address} takes an account in lower-case hex or a legacy address, and answers 200 also for an address that holds nothing:

Field Account Legacy address Meaning
address yes yes The address as the ledger writes it
kind yes yes account or legacy
balance yes yes What it holds. For a legacy address that is what mints have put on it, and nothing once it is claimed
nonce yes   The number of transfers the account has made, which the next one must carry
mintedBySporks yes yes The sum of the mints made to the address so far, as the mints named it
claimable   yes What a claim would move now: the unclaimed snapshot balance and the mints; 0 once claimed
claimedBy   yes The account that claimed it, once claimed
{
  "address": "3f2a…c9",
  "kind": "account",
  "balance": "250.00000000",
  "nonce": 3,
  "mintedBySporks": "0.00000000"
}

An address in upper-case hex, or one that is neither an account nor a valid legacy address, is answered with

  1. A 503 means the ledger is not running.

GET /ledger/block/{height}

GET /ledger/block/{height} answers with the block as it was committed:

Field Meaning
height, view, time Where it sits, the view it was decided in, and its time
hash, previousHash The hash of the block and of the one before it, 128 hex characters
stateRoot, transactionRoot The roots it commits to
proposer The key of the validator that proposed it
certifiedBy, endorsements The validators whose precommits make its certificate, and how many
transactions The transactions, each with the fields of its kind

A transaction always has id, the hash of its encoding, and kind. The rest depends on the kind:

Kind Other fields
mint recipient, amount
vote voter, candidate, action (add or remove), round
claim recipient, the account that receives the coins, and claimedAddress, the legacy address that was moved
transfer sender, recipient, amount, fee, nonce

The height is plain digits. A height that is not, or that does not fit a 64-bit number, is answered with 400. A height of 0, or above the newest block, is answered with 404, and 503 means the ledger is not running. The fees of a block are paid to the validators that signed the block before it, so they are not those in certifiedBy, see Ledger.

POST /ledger/transaction

POST /ledger/transaction takes a body of {"transaction": "<hex>"}, where the hex is the encoding of a claim, a transfer or a vote, see Ledger encoding. The client signs it first. A mint is refused: mints come only from the sporks. The call waits for the ledger to say whether it takes the transaction in, which is a check against the state, not a wait for a block. Find the transaction in a block later by its id.

Status Meaning Body
202 The ledger took it in; it waits for a block {"accepted": true, "id": "<hex>"}
409 The ledger refused it, with its own reason, such as Transfer nonce is already used, Balance does not cover the transfer or Fee is below what this node demands {"accepted": false, "id": "<hex>", "reason": "<reason>"}
400 The body is not that JSON, the hex is not valid or is longer than any transaction, the transaction does not decode, or it is a mint {"error": "<reason>"}
413 The body is above 4096 bytes {"error": "<reason>"}
429 16 submits already wait for an answer {"error": "<reason>"}
503 The ledger is not running, or failed to answer {"error": "<reason>"}
504 No answer within 10 seconds; the ledger may still take the transaction in {"error": "<reason>"}

A status of 202 means the transaction is waiting, not that a block holds it. A transaction that waits is sent on to the other nodes, and a validator puts it into a block. A transfer whose nonce is above the next one, by less than 16, is taken in too and waits for the transfers before it.

The limit of 16. Every submit that waits holds a thread of the server, and the server has no other limit on them, so at most 16 may wait at once and the next gets 429. A place is given back when the ledger has answered, not when the response is sent, so a client that gives up and tries again cannot queue more than the limit. A place is also given back after 60 seconds at the latest, so a ledger that never answers cannot fill all 16 for good.

In the source

  • application/src/main/java/org/unigrid/hedgehog/server/rest/LedgerResource.java: the four endpoints; the JSON records they answer with are in server/rest/entity/ (LedgerInfo, LedgerAccount, LedgerBlock, LedgerTransactionView, LedgerSubmission and LedgerSubmitted).
  • application/src/main/java/org/unigrid/hedgehog/model/bootstrap/LedgerText.java: how addresses and coin amounts are read and written, shared with the command line and the mint spork.