REST interface
After 0.0.8. The gridnode endpoints, file storage through
/storagewith its 64 GiB limit, the storage settings spork and the/ledgerendpoints are on themasterbranch and not in release 0.0.8.
A running Hedgehog node can be controlled and queried over HTTPS, on a port separate from the one it uses
to talk to other nodes. Operators use it to read the network settings the board maintains, to inspect
and edit the node’s list of peers, to start or stop a gridnode, and to store and retrieve files on the
network. The hedgehog cli command is simply a client of this interface, so everything it can do, any
other program can do as well.
What it offers
The interface is a set of web resources, grouped by purpose:
- Grid sporks. Read the network-wide settings described in Grid sporks, see the log of who signed each change and list proposed changes. A program fetches a stored spork or a proposal in wire form and hands a signed spork back; the node checks the signatures and never signs. A change takes effect only after two different board keys have signed it.
- Peers. List the nodes this node knows about, and add or remove one. See Peer-to-peer network protocol for how nodes talk to each other.
- Gridnode. Start or stop announcing this node as a gridnode, list the known gridnodes and read the collateral required for the current number of active ones.
- File storage. Store a file and get back a fingerprint, then read or delete it with that fingerprint. The data is spread over gridnodes with erasure coding, as described in Network storage and Erasure coding.
- Legacy chain data. Look up the balance and transactions of an address in the legacy chain snapshot, including mints still waiting to be paid out. See Legacy chain snapshot.
- Ledger. Read the state of the node’s ledger, the balance of an account or a legacy address and any block, and send in a signed transaction. See The ledger endpoints.
- Node control. Report the version, the chain height and the download status of the snapshot, and shut the node down.
- S3-style buckets. A small subset of the Amazon S3 object-store operations (create, list and delete
buckets; put, get, copy, list and delete objects) kept in the node’s own data directory. These files stay
on the one node and are not replicated. On the
s3branch they are gone, replaced by the S3 endpoint on a port of its own.
How it works
The node runs an HTTPS server next to its peer-to-peer listener. Each request passes a token check first and is then handed to the matching resource, which reads or changes the same state the rest of the daemon uses. For how this listener fits with the other parts of the daemon, see Architecture overview.
flowchart LR
A[HTTPS request] --> B[Bearer token check]
B --> C[Resource class]
C --> D[Daemon state<br/>sporks, peers, storage]
D --> E[JSON response]
Authentication. Every request must carry the header Authorization: Bearer <token>, including requests
for paths that do not exist. The token is taken from --resttoken or the environment variable
HEDGEHOG_REST_TOKEN. When neither is set, the node generates a random token at each start and writes it to
a file named rest.token in its data directory, readable only by its owner, so a hedgehog cli on the same
machine works without configuration. A client on another machine passes the token explicitly. The token is
checked as soon as the head of a request arrives: a request without it is answered with 401 before any of its
body is read, and its connection is closed.
Encryption. The server uses a self-signed certificate created at each start. The bundled client accepts
any certificate, so the connection is encrypted but the server is not authenticated. For this reason the
interface listens on localhost by default and should be exposed beyond it only with that in mind.
Spork changes are signed by the board, not the node. The wire endpoints hand out a stored spork or a
proposal in the form peers exchange, and take a signed spork back. A board member’s command fetches the
wire form, checks it, shows what the change sets, signs on its own machine with a key from -k,
--key-file or $HEDGEHOG_BOARD_KEY, and posts the signed spork. The node checks the signatures and the
signature history as it checks a spork from a peer. The board key never reaches the node. A change is
stored once a second board key has co-signed it and held as a proposal until then, and only then is it
published to the network. The same signing rule is described in Grid sporks.
Uploads. File storage through /storage accepts files up to the limit in the table below and streams
them, so a large file does not need to fit in memory. The fingerprint returned on upload is the only key to
the file and is sent in a header rather than in the URL.
Body limits. The server bounds every request body before a resource reads it: 1 MiB on every path but a
signed spork and the storage uploads. A signed spork posted to /gridspork/wire may be up to the 16 MiB a
peer may send. The storage uploads (/storage and the S3-style buckets) take up to 64 GiB, or
--restmaxupload if that is larger. A body above its limit is answered with 413 and the connection is
closed. A body of any other path that is not complete within 30 seconds is answered with 408. On the s3
branch the S3-style buckets and --restmaxupload are gone.
The spork endpoints
A spork is signed on the machine of a board member, never on the node. The wire endpoints carry the form peers exchange, so the bytes a signature covers are the bytes every node reads:
| Endpoint | Answers |
|---|---|
GET /gridspork/wire/{TYPE} |
200 with the stored spork in wire form, 204 when the node holds none, 404 for a type it does not know |
GET /gridspork/pending/{digest} |
200 with the proposal in wire form, 404 when no proposal has that digest |
POST /gridspork/wire |
The signed spork as the body, application/octet-stream, at most 16 MiB (16,777,216 bytes): 200 when the node stores it, 202 when it holds it as a proposal, with PendingSporkInfo as JSON |
TYPE is one of MINT_STORAGE, MINT_SUPPLY, VESTING_STORAGE, STORAGE, LEDGER_BLACKLIST,
LEDGER_FEE and STATISTICS_PUBKEY. A body that is not a spork, or that holds data no node can act on, is
answered 400 with the reason in plain text. One that no current board key signed, or that no second current
board key co-signed, is answered 401 with No current board key signed the spork, or no second one co-signed
it. One that does not follow on the stored spork is answered 409 with The spork does not follow on the
stored spork and the proposal held. The 202 answer holds type, timeStamp, expires, signer, digest
and data.
The command line is a client of these endpoints: it fetches, checks and signs on its own machine, then posts the result, see Grid sporks. A board key never reaches the node.
The JSON endpoints show what the node holds and change nothing:
| Endpoint | Shows | 204 when unset |
|---|---|---|
GET /gridspork |
Every stored spork | |
GET /gridspork/log |
The signature history of every stored spork | yes |
GET /gridspork/pending |
The proposals awaiting a co-signature, each with its digest | yes |
GET /gridspork/mint-storage |
The mint storage spork | yes |
GET /gridspork/mint-storage/{address}/{height} |
The amount of one entry; 404 when that key has none, 400 when the address or the number does not fit its kind | yes |
GET /gridspork/mint-supply |
The mint supply spork | yes |
GET /gridspork/vesting-storage |
The vesting storage spork | yes |
GET /gridspork/vesting-storage/{address} |
The vesting schedule of one address; 404 when it has none | yes |
GET /gridspork/storage |
The storage spork | yes |
GET /gridspork/ledger-blacklist |
The ledger blacklist spork | yes |
GET /gridspork/ledger-fee |
The ledger fee spork | yes |
The ledger endpoints
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. A node that was not started 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 |
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>"}.
Reading the state
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} 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
- A 503 means the ledger is not running.
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.
Sending a 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.
Key facts
| Item | Value |
|---|---|
| Protocol | HTTPS with a self-signed certificate |
| Default address | localhost, port 52884 (options --resthost, --restport) |
| Peer-to-peer port, for comparison | 52883, listening on all addresses by default |
| Authentication | Bearer token on every request (--resttoken, HEDGEHOG_REST_TOKEN or rest.token) |
Largest file through /storage |
64 GiB |
| Largest upload to the S3-style buckets | 1 GiB by default (--restmaxupload); gone on the s3 branch, see S3 endpoint |
| Largest request body | 1 MiB, 16 MiB on POST /gridspork/wire, 64 GiB on the storage uploads; 413 above it |
| Largest signed spork | 16,777,216 bytes (16 MiB) |
| Spork signing | On the machine of a board member; the node checks both signatures and the key never reaches it |
| Time to send a body other than an upload | 30 seconds, then 408 |
Largest body of POST /ledger/transaction |
4096 bytes, answered with 413 above it |
| Submits that may wait at once | 16, and a place is freed after 60 seconds at the latest |
| Wait for the ledger’s answer to a submit | 10 seconds, then 504 |
| Data format | JSON; the wire form of a spork in application/octet-stream; XML for the S3-style buckets, which the s3 branch removes |
Protocol versions reported by /version |
hedgehog/0.0.4, gridspork/0.0.4 |
In the source
application/src/main/java/org/unigrid/hedgehog/server/rest/RestServer.java: starts the HTTPS listener and lists every resource class.application/src/main/java/org/unigrid/hedgehog/server/rest/BearerTokenFilter.java: the token check.application/src/main/java/org/unigrid/hedgehog/server/rest/GridSporkWireResource.java: the wire endpoints that hand out a stored spork or a proposal and take a signed spork back.application/src/main/java/org/unigrid/hedgehog/server/rest/GridSporkResource.java: sporks, proposals and signature histories as JSON, for display; the other spork resources sit in the same package.application/src/main/java/org/unigrid/hedgehog/model/network/codec/SporkWire.java: the wire form.application/src/main/java/org/unigrid/hedgehog/client/rest/SporkClient.java: the client of the wire endpoints that the command line uses.application/src/main/java/org/unigrid/hedgehog/command/option/BoardKeyOptions.java: the three sources of the board key and their messages.application/src/main/java/org/unigrid/hedgehog/server/rest/RequestBodyLimit.java: the body limits and the 413 answer.application/src/main/java/org/unigrid/hedgehog/server/rest/StorageResource.java: file storage by fingerprint.application/src/main/java/org/unigrid/hedgehog/server/rest/LedgerResource.java: the four/ledgerendpoints; the JSON records they answer with are inserver/rest/entity/(LedgerInfo,LedgerAccount,LedgerBlock,LedgerTransactionView,LedgerSubmissionandLedgerSubmitted).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.application/src/main/java/org/unigrid/hedgehog/command/option/RestOptions.java: the command-line options.application/src/main/java/org/unigrid/hedgehog/client/rest/RestClient.java: the client the CLI uses.application/src/main/java/org/unigrid/hedgehog/command/CLI.java: thehedgehog clisubcommands.