Signing a spork database

Part of Board instructions. How to check a spork database, sign its sporks again with board keys and install the result on the node of this machine.

hedgehog util spork-db works on the file and never asks a node. It treats every spork type alike, so it also signs and installs the storage, ledger and communication sporks, which a node run without the hidden --preview switch keeps in spork.db but neither acts on nor publishes. The board key comes from -k, --key-file or $HEDGEHOG_BOARD_KEY, and --network-keys names the keys the release is built with. A file that holds a board keys spork is judged by the keys its chain makes active.

The steps

  1. hedgehog util spork-db sign spork.db.bak --output once.db --key-file <key file of member 1>
  2. hedgehog util spork-db sign once.db --output twice.db --key-file <key file of member 2>
  3. hedgehog util spork-db validate twice.db
  4. Stop the node, run hedgehog util spork-db import twice.db, then start it again.
flowchart LR
    A["spork.db.bak<br/>signed by keys the board<br/>no longer holds"] -->|"sign<br/>key of member 1"| B["once.db<br/>signed once"]
    B -->|"sign<br/>key of member 2"| C["twice.db<br/>signed and co-signed"]
    C -->|validate| V{"valid: yes?"}
    V -->|no| X["find the reason in the<br/>lines validate prints"]
    V -->|yes| I["stop the node<br/>import twice.db<br/>start the node"]
    I --> K["the old spork.db is kept<br/>as a backup beside it"]
    I --> P["the node publishes the sporks,<br/>and every peer takes them"]

The two members never need to be on one machine: member 1 hands once.db to member 2, who signs it with their own key. Neither file holds a key.

Mind these when following the steps:

  • validate spork.db.bak ends valid: no and exits with 1 before the signing; that is expected, as the board keys of today did not sign it.
  • sign never overwrites its output, so delete once.db or twice.db before running a step again.
  • Run import as the user the node runs as, or with the same XDG_DATA_HOME. Run as another user it installs into that user’s data directory, which the node never reads, and prints backup: none, the node holds no spork database. The full path of the installed file shows only in the last line, imported: <path>; check that it is the data directory of the node.
  • A refused import prints the reason after the report, where it made one: read the line after valid: yes or valid: no. A refusal with exit code 2 is followed by the usage.
  • The node logs nothing by default. Start it with -vv to see the warning on sporks of its spork.db that two current board keys did not sign, which it leaves out and keeps in spork.db.unsigned-<epoch seconds>.

The three commands are:

Command Arguments What it does
validate <file> Prints each spork with its digest, signers, log length, whether the board signed it and whether its content holds, then valid: yes or valid: no
sign <file> --output <new file> and a board key Writes a new file with each spork signed or co-signed
import <file> Installs the file as spork.db in the data directory of this machine

validate exits with 0 when two current board keys signed every spork and the content of each holds, with 1 when not, and with 2 when the file cannot be read. A line looks like

MINT_SUPPLY: made 2026-10-01T09:00:00Z, digest 7b1e04aa9d2c8f31, signer 44f21a0c9b7e6d18, co-signer 7c0e35b9a1d24f68, log entries 0, signed by the board, content valid

and a type that the file does not hold is VESTING_STORAGE: absent. A signer that is no active board key reads not a board key, a spork that only one key signed reads co-signer none and not signed by the board, a spork that only switched-off keys signed reads signed by board keys that are switched off, and content that does not hold reads content not valid: <reason>. It ends valid: yes or valid: no.

sign needs an --output file that does not exist yet, and refuses a file or a link there with --output <file> exists and is not overwritten. It judges every spork and builds its text before it shows or signs any, then prints each spork in full and signs it. A spork that two active board keys signed is left as it is and printed as MINT_SUPPLY: signed by the board, left as it is. A spork that they did not sign becomes a first version of the same data, made now, and is printed as MINT_SUPPLY: a first version of its data, made at 2026-10-23T10:00:00Z and signed by 44f21a0c9b7e6d18. A first version that one active board key signed is co-signed and printed as MINT_SUPPLY: co-signed by 7c0e35b9a1d24f68. The command exits with 0 and prints output: <file>. A refusal exits with 2 and, when the new output file was made, leaves no output file.

A spork signed once but not as a first version, such as a proposal over a stored spork, previous data that was never stored, or a signature log, is refused:

Nothing was signed: the MINT_SUPPLY spork is signed once and is no first version of its data, which is all this command co-signs; co-sign a proposal over a stored spork with gridspork-cosign

A second run with the key that signed is refused with

Nothing was signed: the MINT_SUPPLY spork cannot be signed: A second, different network key has to co-sign the spork

Content that does not hold, text that cannot be shown as it is, and a first version stamped ahead of the clock of this machine are refused the same way, with Nothing was signed: and the reason.

A board keys spork in the file is signed like any other: its first version holds the chain of the file, and the two keys that sign it are keys that chain makes active.

What the file holds is judged as history the network already carried, not as a change, so its entries are not checked as new mints are. A mint storage entry at a height of the old chain was paid by that chain; the ledger mints by time, to ledger accounts.

import checks the file in this order, and installs nothing when a check fails:

  1. Its board keys carry on the chain of board keys of the node, else it would take the node back to keys that the board switched off, or onto a fork: Nothing was imported: the board keys of <file> do not carry on the chain of board keys of the node, which would take the node back to keys that the board switched off, or onto a fork, exit 1.
  2. It holds no less of the chain of board keys than this machine’s record (board-keys-<16 hex of the root>.seen), and no chain that parts from it: The file <file> holds ..., exit 2.
  3. It prints the same lines as validate, and the last must be valid: yes: Nothing was imported: two current board keys did not sign every spork of <file>, or the content of one does not hold, exit 1.
  4. No other process holds spork.db.lock: Nothing was imported: the spork database in <data directory> is in use: a node runs on it, or an import; stop the node of this machine first, exit 2.

It then copies the spork.db it replaces to spork.db.<epoch seconds> and prints backup: <path>, or backup: none, the node holds no spork database, installs the file as a node reads it from the wire, prints imported: <path> and exits with 0. A file that holds more of the chain than the record updates the record after the install; if that write fails, the command prints The chain of board keys of <file> was not recorded: <reason> and still exits with 0. A file that cannot be read or installed ends it with exit code 2.

A node of an earlier release takes no lock, so stop it before an import all the same: it keeps its sporks in memory and writes them over the file when it saves. The command prints the reminder first:

note: stop the node of this machine before the import, as it keeps its sporks in memory and writes them over the file when it saves

A file that is not a spork database, holds a time too far in the past or the future to be read, holds a spork in the slot of another type, or holds an amount beyond the bounds of the wire ends any of the three commands with exit code 2 and Cannot read the spork database <file>: <reason>. For a misplaced spork the reason is the MINT_STORAGE slot holds a spork that is no MINT_STORAGE spork; for an amount it is the MINT_SUPPLY spork holds an amount beyond the bounds of the wire.

Installing on one node is enough: a node that starts with the database publishes its sporks every three minutes and whenever a connection comes up; a peer with no spork of a type takes the first version, and one with another first version takes the newer by its time stamp. Peers hold the sporks about two minutes after the node starts again.

In the source

  • application/src/main/java/org/unigrid/hedgehog/command/util/SporkDbCommand.java, SporkDbValidate.java, SporkDbSign.java, SporkDbImport.java and SporkDatabaseFile.java: checking, signing again and installing a spork database
  • application/src/main/java/org/unigrid/hedgehog/model/spork/BoardKeysAuthority.java and BoardKeysRecord.java: installing a chain and the record it must carry on
  • application/src/main/java/org/unigrid/hedgehog/model/spork/SporkDatabaseLock.java: the lock a running node holds on spork.db
  • application/src/main/java/org/unigrid/hedgehog/model/spork/content/SporkContent.java: the content rules a spork is checked by