Running a ledger node
Part of Ledger. A node runs the ledger when it is started with --ledger. It then follows the
chain, checks every block and serves the blocks it holds to its peers. Given the key of a validator, it also
votes and signs blocks once the validators hold that key, and stops when they vote it out. No genesis comes with
the node yet, so a ledger runs only on a genesis that is given to it. Without --ledger a node behaves as
one of the earlier release, and nothing on this page applies.
What a ledger node needs
| Observer | Validator | |
|---|---|---|
A build of the master branch, see Build, testing and native image |
yes | yes |
--ledger |
yes | yes |
The genesis of the chain, given with --ledger-genesis |
yes | yes |
The bootstrap snapshot that the genesis names, bootstrap.dat in the data directory |
yes | yes |
A key file, given with --ledger-key, whose public key a validator is, in the genesis or voted in |
yes | |
| The ledger sporks held by the node, for mints, claims and transfers to be taken | yes |
The snapshot is the one of Legacy chain snapshot. A daemon downloads it in the background when it has none, and the ledger waits for it, since every claim is checked against it.
Starting
A validator first makes its key and gives the public key to whoever writes the genesis:
$ hedgehog util ledger-key-generate ~/.ledger.key
3f2a…c9
The command writes a random seed into a new file that only its owner can read, prints the public key, and refuses a path that exists, so a key is never replaced. The seed is never printed. Keep the file: a validator that loses it can no longer sign as that key.
Then start the daemon:
$ hedgehog daemon --ledger --ledger-genesis genesis.json --ledger-key ~/.ledger.key
Leave out --ledger-key to run an observer. The node logs, in order:
The ledger is of the chain <chain id>. The chain id is the hash of the genesis: compare it with other operators to know that you are on the same chain.Waiting for the bootstrap snapshot <file> before the ledger starts, once a minute while the snapshot is missing. The ledger looks for it every 5 seconds.The ledger keeps its block log and its sign state in <folder>, once the snapshot is there and is the one of the genesis. The folder is the one of this chain, see Where the files are.- Nothing more while all is well. The node replays its block log, starts as an observer, catches up with its peers, and after a grace period of 30 seconds builds a signing node, which signs once the validators hold its key.
Options
These go after daemon. They are checked before anything else starts, and a value that is wrong stops the
daemon with a message and exit code 2.
| Option | Meaning |
|---|---|
--ledger |
Run the ledger: follow and serve its blocks. Off by default |
--ledger-genesis <file> |
The genesis of the chain. Needed until a genesis comes with the node |
--ledger-key <file> |
Validate with the seed in this file, as made by util ledger-key-generate. Without it the node follows the ledger and never signs |
--ledger-trusted <height>:<hash> |
The block a synced ledger must have, with the hash as 128 hexadecimal characters and a height of 1 or more |
A key that the genesis does not list is not an error. The node warns
The key <public key> is not a validator of this genesis, this node signs once the validators vote it in,
and follows the chain without signing until they do.
A trusted checkpoint protects a new node against a fork. Take the height and hash of a block from a node you trust. A node whose ledger holds another block at that height, or that sees two certified blocks at one height, halts its ledger instead of following a fork.
The genesis
The genesis is a JSON file with exactly these fields:
{
"time": 1796083200000,
"roundMillis": 3600000,
"validators": [
"3f2a…c9",
"8be0…14"
],
"snapshotHash": "<128 hex characters>",
"snapshotTotal": 12345678900000000
}
| Field | Meaning |
|---|---|
time |
When the chain starts, in Unix milliseconds |
roundMillis |
The length of a round in milliseconds, 60000 or more; rounds follow each other from time, which starts round 0 |
validators |
The public keys of the first validators, 64 hex characters each, 1 to 1000 of them, in the order that decides who proposes. They can never be voted out |
snapshotHash |
The SHA-512 of the bootstrap snapshot, without its signature |
snapshotTotal |
The sum of the snapshot’s balances, in units of 1e-8 coins |
A round is at least 60000 ms: twice the 30000 ms by which a validator lets a block run ahead of its clock. A field that is missing, repeated or unknown is refused, and so is a key that is listed twice, so a typo cannot pass. The file is public: it holds no secret. Every node of a chain must use the same values, since the chain id is computed from them and every signature on the chain covers the chain id. Spacing and the order of the fields do not matter, the order of the validators does.
Make the file with hedgehog util ledger-genesis <snapshot> --validator <key> --time <ms> --round-millis <ms>
--output <file>, giving --validator once for each validator in proposer order. It reads the snapshot for the
hash and the total, refuses a malformed or repeated key, one that is not an Ed25519 public key, a round below
60000 ms and an output file that exists, writes the genesis and prints snapshotHash, snapshotTotal,
validators and chainId. Operators compare the chain id with the one hedgehog cli ledger info shows.
Where the files are
The ledger keeps its files in the folder ledger of the data directory, see
Running Hedgehog, in a folder of its own for each chain. The name of
that folder is the first 16 characters of the chain id, and the node logs it at start as
The ledger keeps its block log and its sign state in <folder>:
| File | Holds |
|---|---|
ledger/<chain>/blocks.log |
Every block of the chain |
ledger/<chain>/sign.state |
What this validator signed for the height it works on, with sign.state.lock beside it |
ledger/<chain>/ledger.lock |
Taken by the process that uses the folder, for as long as its ledger is open |
- One process to a folder. The first node of a process takes
ledger.lock, an observer too, and holds it until the ledger closes. A second process on the same folder is refused withThe ledger directory <folder> is locked: another process uses it, and its ledger does not run. - Never delete or roll back
sign.stateof a validator. It is what keeps the validator from signing two different blocks at one height after a restart, and a deleted or older copy is not noticed. - The block log can be fetched again from peers, but not on its own: a log that is shorter than the sign state needs stops the node, which asks for the tail of the log to be restored from a peer.
- The state is not stored. At every start the node replays the whole log and checks every block again, so a long chain takes a while to start. A validator replays it twice.
- Another genesis, another folder. A genesis with other values has another chain id, so the node starts on an empty folder and never meets the files of the chain it had. The folders of chains that are left stay as they were.
When the ledger does not run
A wrong option, genesis or key file stops the daemon at start, before anything runs, with one of these messages and exit code 2:
| Message | Cause | What to do |
|---|---|---|
--ledger needs a genesis: give --ledger-genesis <file> (no genesis is bundled with this release) |
No genesis given, and none comes with the node | Give --ledger-genesis |
The ledger genesis <file>: … |
The genesis is not valid; the rest names the field | Fix the file, or get it again |
… must be readable by its owner only, try chmod 600 |
The key file can be read by others | chmod 600 the key file |
--ledger-trusted must be <height>:<128 hexadecimal characters of the hash>, … |
The checkpoint is malformed | Give a height of 1 or more and the full hash |
What goes wrong later leaves the node running without its ledger, and the node logs why once:
| Log line | Cause | What to do |
|---|---|---|
The ledger is not running, the node goes on without it: The snapshot <file> has the content hash … |
The snapshot is not the one of the genesis | Fetch the snapshot release that the genesis names |
The ledger driver halted: The ledger node could not be built: … Cannot replay the block log … |
The log does not fit this genesis or snapshot, or is damaged in the middle | Check the genesis; move the folder of the chain away to start over |
The ledger driver halted: … |
The checkpoint differs, two certified blocks were seen at one height, or the node failed twice within 10 seconds | Read the reason; a fork needs an operator |
After a halt the service starts the ledger again over the same files, after 1 minute, then 2 and then 4, and
logs The ledger halted, it starts again in <n> ms: <reason>. A ledger that halts after 3 starts within 6
hours stays halted, with The ledger halted after 3 starts again, it stays halted and serves the blocks it
has, the node goes on: <reason>; only a restart of the daemon tries again then.
A halted ledger still serves the blocks it holds, so its peers can go on syncing from it.
Using a running node
A node that runs the ledger answers over the REST interface, and the commands of Ledger command line use it. This one shows that the node is up and on your chain:
$ hedgehog cli ledger info
It shows "running" : true, the chain id to compare with other operators, the height of the tip, and a
signing of true while the node runs, its newest node was built to sign and its key is one of the
validators of its newest ledger. Its unsetSporks and laggingSporks name the
ledger sporks the node still waits for. While one is listed, what depends on it is not
minted, claimed or taken, and a chain on a running node may stand still. A transfer, a claim and a vote are
sent with the same command, signed on the machine that runs it.
What a node cannot do yet
- Start without a genesis file. No genesis comes with the node.
- Keep waiting transactions across a restart. They are lost when the process stops.
In the source
application/src/main/java/org/unigrid/hedgehog/command/option/LedgerOptions.java: the optionsapplication/src/main/java/org/unigrid/hedgehog/service/ledger/LedgerSetup.java: the checks at startapplication/src/main/java/org/unigrid/hedgehog/service/ledger/GenesisFile.java: the genesisapplication/src/main/java/org/unigrid/hedgehog/service/ledger/LedgerService.java: what the node does with the ledgerapplication/src/main/java/org/unigrid/hedgehog/server/rest/LedgerResource.java: how the ledger is read and used over REST