Ledger sporks

Part of Ledger. The ledger takes its policy from four Grid sporks: what to mint and when, how much may exist, which legacy addresses may not claim, and what a transfer pays: its lowest fee and the least amount to a new account. Each is changed by two board keys like any other spork. This page says what each one means to the ledger and how to change it. The code behind it is on Ledger in the daemon.

Spork Id What the ledger does with it Without it
Mint storage 1000 Mints each entry once, when it is due Nothing is minted
Mint supply 1010 Mints nothing that would pass the maximum supply Nothing is minted
Ledger blacklist 1040 Holds back claims of the listed legacy addresses No claim is taken
Ledger fee 1050 Takes no transfer below the minimum fee, nor one to a new account below its least amount No transfer is taken

A spork is policy, not a rule of the chain. Each node answers from the sporks it holds, and the answers are asked only of what is not certified yet. A block that more than two thirds of the validators signed stands whatever a node’s sporks say, so a change of spork never forks the chain, and a node that replays old blocks is not bothered by sporks that changed since. A node whose sporks differ from the others’ only refuses to vote for some blocks, which delays them.

A node that does not know waits. A spork that is not set is a no, as the table shows. So is a spork that the node knows to be out of date: when a peer passes on a newer version, signed by the network keys, that this node has not stored yet, the node answers no for that spork until it has stored the newer one. Only a version signed by two network keys counts. This also holds for a newer version that the node turned down, for example a rival version of the history, until the network settles on one. A peer without a network key cannot make a node wait.

A proposal changes nothing. While a change waits for its second signature the nodes go on under the spork in force, so a single board key cannot hold the ledger back. The change takes effect on a node when it has stored the co-signed spork.

sequenceDiagram
    participant A as Board member A
    participant NA as Node of A
    participant B as Board member B
    participant N as The other ledger nodes
    A->>NA: propose a new fee, signed once
    NA->>N: the proposal
    Note over NA,N: transfers are still judged on the old fee
    B->>N: co-sign the proposal
    N->>N: store the new fee spork
    Note over NA,N: transfers are judged on the new fee

Agree on a change before it is proposed and co-sign it soon after: until the second signature the old rule stays in force, so a blacklist entry does not stop a claim of the listed address before then. A renewal proposes every stored spork at once and is no different.

Before the ledger can run

These are for the foundation to do:

  • make the Ed25519 keys of the validators, with hedgehog util ledger-key-generate;
  • pin the snapshot release, and make the genesis from it and the public keys with hedgehog util ledger-genesis, which writes the file and prints the snapshot hash, the total and the chain id;
  • set the mint supply spork, and the blacklist spork with the 67 addresses of legacy spork 18, so that claims can be taken;
  • set the fee spork, so that transfers can be taken; its least amount to a new account is 0 unless it is set too.

Mint storage

The entries of the mint storage spork, an address, a number and an amount, are minted by the ledger. The number says when, and what it means depends on the address:

Address The number is Minted at Example key
An old-style Unigrid address, base58 A height of the legacy chain The time of the snapshot’s last block, plus one minute for every height after it <old-style address>/1263520
A ledger account: its public key as 64 lower-case hex characters Unix time in whole seconds, from 0 to 4,294,967,295 (1970 to 2106) That time 3f2a…c9/1796083200

A legacy height above the snapshot tip becomes a time one minute per height after the tip time; an account entry names its time; entries whose time has passed are due

  • An entry at or below the snapshot’s last height never mints. Its amount is already in the snapshot balances and reaches the ledger through a claim.
  • An entry to an old-style address goes to that address on the ledger. Its owner claims it with the rest of the address’s balance, and once the address is claimed, later mints to it go to the claimer.
  • An entry mints once. Its reference is derived from the address and the number, so whatever node proposes it, it is made once. A second entry for the same address with another number is a second grant.
  • An address is minted what the spork grants it, and no more. The grant is the sum of the amounts of all the entries of the address that can mint: every entry of an account, and for an old-style address those above the snapshot’s last height. A mint is refused once the mints made to the address so far, plus this one, would pass that sum. That closes a gap: a later version of the spork can replace an entry by another for the same address, with a new number and so a new reference, and without the cap the address would be paid twice for what the spork grants it once. A spork that only moves an entry that was paid, to another height or time, therefore mints nothing more. The total is counted from the blocks, so every node and a replay of the log agree, and it counts under the address the entry names: a legacy address that has been claimed goes on counting under its own address, and what fits is still paid to the claimer.
  • An entry that cannot be minted is skipped and logged once at debug level: an address of neither form, upper-case hex, or an amount that is not a whole number of 1e-8 units above zero. Amounts are cut at eight decimals, never rounded up. The command refuses such an entry before it signs, with the reason, so that no node finds out only when the entry is due.
  • An amount in a spork has at most 8 decimals and 12 digits before the point. A frame with a larger number is dropped as corrupt, and the command refuses one before it signs.

Propose an entry for an old-style address with a height of the legacy chain, or for a ledger account with its time. --height and --time are the same option; for an account it takes the seconds or an ISO-8601 instant with a zone, which the command turns into seconds:

$ hedgehog cli gridspork-grow mint-storage --address <old-style address> --height 1263520 \
    -D 500 -k <key of A>
$ hedgehog cli gridspork-grow mint-storage --address <account, 64 hex> --time 2026-12-01T00:00:00Z \
    -D 500 -k <key of A>

For the account, the entry appears under the key <account>/1796083200, and the ledger mints it at that moment. An instant is refused for an old-style address, which takes a height in digits only. A time that has passed is refused for an account unless --allow-past is given, since it would mint at once and a date written in digits, such as 20260601, is easily taken for seconds. GET /gridspork/mint-storage/{address}/{height} reads one entry back, with the seconds in place of the height for an account. It answers 400 when the address or the number does not fit its kind, 404 when no entry has that key, and 204 when the node holds no mint storage spork.

Mint supply

The ledger mints nothing that would take the supply above the maximum of the mint supply spork. The limit holds for a block as a whole: each mint is judged on the supply with the mints before it in the same block. A mint that can never fit is not offered at all, so it does not keep the validators busy. The maximum is cut at eight decimals. Set it as described on Grid sporks.

Ledger blacklist

A list of legacy addresses whose claims the ledger holds back, at most 4096 of them. It is held as the 20-byte hashes of the addresses, in lower-case hex, so equal lists sign the same bytes. A set replaces the whole list.

The command takes a file with one old-style address per line. Text after # is a comment, and blank lines are skipped:

# From legacy spork 18
<old-style address>
<old-style address>   # second address
$ hedgehog cli gridspork-set ledger-blacklist -D blacklist.txt -k <key of A>
Stored LEDGER_BLACKLIST data:
none
Proposed LEDGER_BLACKLIST data, signed now:
{
  "legacyAddresses" : [ "0a1b…4567" ]
}
202 Accepted
{
  "type" : "LEDGER_BLACKLIST",
  …
  "digest" : "41fe9d15…",
  …
}
$ hedgehog cli gridspork-get ledger-blacklist

The command decodes the addresses itself and stops at the first line that is not a legacy address, naming the line. The data of the spork that GET /gridspork/ledger-blacklist shows holds the hashes; the endpoint is for display and answers 204 while none is set:

{ "legacyAddresses": [ "0a1b2c3d4e5f60718293a4b5c6d7e8f901234567" ] }

A claim that a block already holds stands, blacklist or not, since the block was certified.

Ledger fee

The fee spork holds two amounts in coins, from 0 with at most eight decimals: the lowest fee a transfer must pay for this node to take it, and the least amount that a transfer must send to an address the ledger has no account for. The fee is at most one coin, which keeps a mistyped fee from pricing every transfer out of the ledger. The amount to a new account is at most a hundred coins, which keeps a mistyped amount from keeping everyone without an account out of the ledger. A fee of 0 refuses no transfer for its fee, and an amount of 0 opens an account for any amount. Only a transfer to an address the ledger has no account for needs it: an account it has made stays, at 0 when its coins are spent, and a claimed legacy address redirects to the account that claimed it.

$ hedgehog cli gridspork-set ledger-fee -D 0.001:1 -k <key of A>
Stored LEDGER_FEE data:
none
Proposed LEDGER_FEE data, signed now:
{
  "minimumTransferFee" : 0.001,
  "minimumNewAccountAmount" : 1
}
202 Accepted
{
  "type" : "LEDGER_FEE",
  …
  "digest" : "41fe9d15…",
  …
}
$ hedgehog cli gridspork-get ledger-fee

The data is the fee, or the fee and the amount to a new account after a colon: -D 0.001 sets the amount to a new account to 0, and -D 0.001:1 sets it to one coin. The command keeps the digits as typed, so no rounding reaches the node. The data of the spork that GET /gridspork/ledger-fee shows reads { "minimumTransferFee": 0.001, "minimumNewAccountAmount": 1 }; the endpoint is for display and answers 204 while none is set. The fees paid go to the validators that signed the block before, see Ledger.

Co-signing

Both new sporks are governed and co-signed like every other: the set command prints the stored and proposed data, signs on the machine and answers 202 Accepted with the proposal and its digest. The second member compares data with what was agreed and co-signs, see Grid sporks. Nodes publish them to their peers with the other sporks every three minutes and when a connection comes up, but only between nodes that run --ledger: they travel only over connections that settled on the ledger protocol. A node of release 0.0.8 would misread them, and never receives them. Both board members must therefore propose and co-sign these two sporks on nodes started with --ledger, or the change does not reach the network.

In the source

  • application/src/main/java/org/unigrid/hedgehog/service/ledger/SporkLedgerAuthority.java: what the ledger asks of the sporks
  • application/src/main/java/org/unigrid/hedgehog/model/bootstrap/MintEntry.java: an entry of the mint spork as a mint
  • application/src/main/java/org/unigrid/hedgehog/model/spork/LedgerBlacklist.java and LedgerFee.java: the two sporks
  • application/src/main/java/org/unigrid/hedgehog/model/spork/SporkLag.java: which sporks a node knows to be out of date
  • application/src/main/java/org/unigrid/hedgehog/command/cli/spork/: ledger-blacklist, ledger-fee and mint-storage