Repository navigation
Ledger implementation
Upcoming. This page describes the
ledger-nodebranch. It is not part of a release, and details may change.
These pages are for a reader who wants to open the code of the ledger or to build on it. The concepts, what the ledger records, how a block is agreed and why it needs more than two thirds of the validators, are on the Ledger page. This page gives the module, its classes and its API. The detail is on these pages:
| Page | What it covers |
|---|---|
| Ledger encoding | The bytes of every record and message, what each signature covers, hashes, roots and references |
| Ledger state and rules | The state, the order of every check, fees, rounds, the waiting transactions and heartbeats |
| Ledger consensus engine | The voting engine, the sign state and the node that ties them to a ledger |
| Ledger storage | The block log, the sign state file and the replay at start |
| Ledger networking | The driver: intake, limits, gossip, sync, promotion and halting, and the transport over QUIC |
| Ledger in the daemon | The service, the genesis and key files, the snapshot, the authority over the sporks |
The ledger is the Maven module ledger, artifact hedgehog-ledger, with one package,
org.unigrid.hedgehog.ledger, exported by its module-info.java. It has no socket code, no files of its own
choosing, no options and no threads. The application module depends on it
(requires org.unigrid.hedgehog.ledger) and supplies all of those, in the package
org.unigrid.hedgehog.service.ledger.
| Dependencies | BouncyCastle bcprov-jdk18on 1.85, SLF4J 2.0.20, Lombok 1.18.48 |
| Test dependencies | jqwik 1.10.2, Hamcrest 2.2, Jimfs 1.3.2 |
| Compiler release | 25, set in the parent pom.xml
|
| Language features | Records, sealed Transaction and Address, pattern-matching switch, switch expressions |
| Run its tests |
mvn -pl ledger test from the repository root, -Dtest=BlockLogTest for one class |
Rules and state, see Ledger state and rules
| Class | Job |
|---|---|
Ledger |
Implements ConsensusApp: ties the state, the waiting transactions and the block log together |
LedgerState |
Balances, references, votes, validators, supply and the tip. Holds every validation rule and computes the state root |
LedgerAuthority |
What the ledger asks the node: which mints are due, and which mints, claims and transfers it accepts |
LedgerGenesis, Snapshot
|
The start of the chain with the hash and total of the legacy snapshot, and the balances behind it |
MintSchedule |
Turns a height of the legacy chain into a time |
Mempool |
Waiting transactions and the newest heartbeat of each validator |
ValidatorSet |
The ordered keys with the proposer and quorum rules |
VerifiedSignatures |
Remembers the claims and transfers whose signatures were checked |
Data, see Ledger encoding
| Class | Job |
|---|---|
Transaction, Mint, Vote, Claim, Transfer
|
The four kinds of transaction. Transaction is sealed, so there are no others |
Address, AccountKey, LegacyAddress
|
An Ed25519 public key or a 20-byte legacy address, and the sealed type over both |
LegacySignature |
Verifies a signature of the legacy wallet |
Reference |
A 32-byte single-use mint reference, and the two ways to derive one |
Block, Certificate, Endorsement, Precommit
|
A header with its certificates and transactions, a view with its signatures, one signature, and what it covers |
Heartbeat, Status
|
A validator's signed statement that it sees a tip, and an unsigned claim of where a chain stands |
BlockCodec, TransactionCodec, ConsensusCodec, WireCodec
|
Byte encoding of blocks, transactions, consensus messages and the rest |
MerkleRoot, Digests, Ed25519, Hashing
|
Hashing and signing, and a salted ranking of peer ids |
BlockLog |
The append-only file of blocks and its crash recovery, see Ledger storage |
BlockRequest, BlockBatch
|
A request for blocks, and up to 16 blocks in answer |
Consensus and node, see Ledger consensus engine
| Class | Job |
|---|---|
Consensus, ConsensusApp
|
The voting engine for one validator, and the interface it uses to ask the ledger |
Message, Proposal, Ballot, Decision, BallotBox
|
The three consensus messages and the box that counts the ballots of a height |
Action, Timeout, Timeouts
|
What the engine asks its caller to do, a timer id, and the waiting times |
SignState, SignStore, SignStateFile
|
What a validator has signed, and the file that keeps it |
LedgerNode |
One ledger, one engine and one sign state tied together |
ChainGuard |
Refuses blocks and statuses that contradict the chain held or a trusted checkpoint |
Driver and network, see Ledger networking
| Class | Job |
|---|---|
LedgerDriver |
The only caller of a node: intake, gossip, sync, promotion and halting |
NodeSupervisor, NodeCall, Promotion, Recency
|
Replaces a node that failed, decides when an observer may vote, and remembers recent trouble |
Syncer, SyncState, PeerView
|
Fetches blocks from a peer that is ahead, keeps the claims and doubts about peers, lists the peers |
Gossip |
Passes on what is new and charges the sender for what is forged |
Door, DoorKeeper, InboundLanes, QueueBudget, PeerLimiter
|
Sorting, queueing and rate limiting of what arrives |
LedgerNetwork, LedgerPeer, Serial, Scheduler
|
What the application provides: peers, a serial executor and timers |
NodeFactory, DriverOptions, Checkpoint
|
Inside LedgerDriver: how a node is built, the settings, and the block a synced ledger must have |
Errors are InvalidBlockException, CorruptLogException, CorruptSignStateException,
FileLockedException and BlockLogBehindException. Bytes holds package-private helpers.
In the application, package org.unigrid.hedgehog.service.ledger, see
Ledger in the daemon and Ledger networking
| Class | Job |
|---|---|
LedgerService |
The ledger as a part of the daemon: starts, reads and stops it |
LedgerNodes, LedgerRuntime
|
The NodeFactory over the files of the node, and the Serial and Scheduler
|
LedgerSetup, LedgerKeyFile, GenesisFile
|
The checks of the options at start, the validator's seed, and the genesis as JSON |
LedgerSnapshot, SnapshotCheck
|
The bootstrap snapshot as the ledger's Snapshot, and whether it is the one of the genesis |
SporkLedgerAuthority |
The LedgerAuthority over the sporks |
LedgerGateway, LedgerProtocol, NettyLedgerNetwork, NettyLedgerPeer
|
The transport: the gate, the protocol, the peers and one stream |
Beside them are MintEntry in model.bootstrap, the sporks LedgerBlacklist and LedgerFee and the
tracker SporkLag in model.spork, and LedgerOptions in command.option.
flowchart TD
Service[LedgerService] --> Driver[LedgerDriver]
Service --> Nodes[LedgerNodes]
Gateway[LedgerGateway] --> Driver
Driver --> Supervisor[NodeSupervisor]
Driver --> Network[LedgerNetwork]
Supervisor -.builds through.-> Nodes
Supervisor --> Node[LedgerNode]
Node --> Consensus
Node --> Ledger
Node --> SignState[SignStateFile]
Consensus --> BallotBox
Consensus -.asks.-> Ledger
Ledger --> LedgerState
Ledger --> Mempool
Ledger --> BlockLog
LedgerState --> Authority[LedgerAuthority]
LedgerState --> ValidatorSet
LedgerState --> MerkleRoot
Authority -.implemented by.-> Spork[SporkLedgerAuthority]
BlockLog --> BlockCodec
BlockCodec --> TransactionCodec
A ledger is put together by the caller, in three steps:
-
new LedgerState(genesis, authority), or with aSnapshotas a third argument; -
BlockLog.open(path), which creates the file and its directory if they are missing; -
new Ledger(state, log), with an optional clock as a third argument, which replays every block of the log into the state with all checks. A log that was tampered with makes the constructor throwInvalidBlockException. A replay is vouched, so it ignores what the authority says now.
The caller owns the log and closes it. The waiting transactions are kept by a Mempool that the ledger
creates itself, with room for 10,000 of them. A node is built on top of that, see
Ledger consensus engine, and in a running system the driver builds the
nodes through its NodeFactory. In the daemon that is LedgerNodes, and LedgerService builds the driver.
Ledger implements ConsensusApp, see Ledger consensus engine, and
adds:
| Method | What it does |
|---|---|
submit(Transaction), submit(Heartbeat)
|
Empty when accepted, the reason otherwise |
blocksFrom(from, max), hashAt(height)
|
Read the log for peers, without waiting for a commit |
status() |
The chain id, height and tip hash, as a Status
|
validators(), height(), tipHash(), chainId(), round()
|
Where the chain stands |
supply(), balanceOf(Address), stateRoot(), pending()
|
What it holds |
isProgressing(now, window) |
Whether more than two thirds of the validators sent a recent heartbeat |
commit(block) is package-private: only LedgerNode commits. Every public method except blocksFrom and
hashAt is synchronized; LedgerState itself is not thread-safe and relies on that.
The authority has four methods, all of them the node's policy:
| Method | Asked |
|---|---|
due(time) |
When a block is proposed or work is looked for. The mints this node knows to be due, in a fixed order; the ledger leaves out the ones already made |
authorizes(Mint, time, supply) |
When a mint is submitted, proposed or checked before a vote. The time is the node's clock on submit and the block's time otherwise, and the supply is that of the state the mint would be made on |
authorizes(Claim) |
The same, for a claim |
authorizes(Transfer) |
The same, for the fee a transfer pays |
The last two have no default, so an authority that forgot one would not let anything through by accident.
None of them is asked when a block that already carries a certificate is checked: the state is then
vouched for, because more than two thirds of the validators asked their own authorities before they
voted. The authority of the daemon is SporkLedgerAuthority, see
Ledger in the daemon.
Rejections that are expected, such as a bad transaction or block, are returned as a readable reason in an
Optional<String>. An exception is thrown where the caller made a mistake or the data cannot be trusted:
InvalidBlockException for a refused block in apply and commit; IllegalArgumentException for a
malformed value or a second endorsement; CorruptLogException and CorruptSignStateException for a log or
sign state that cannot be trusted; FileLockedException for a sign state held by another node;
BlockLogBehindException for a log that lacks blocks the sign state has passed; and ArithmeticException
where a sum would overflow, which a check turns into Could not be checked.
Logging is a warning when a torn record is dropped, one when a check could not be made, and what the driver
reports when it halts or replaces a node. In the daemon, LedgerService logs the chain id at start, the wait
for the snapshot, and once why the ledger is not running or has halted; SporkLedgerAuthority logs each
cause of a refusal once, at debug level. A seed is never logged.
The tests use jqwik properties and examples, with Hamcrest assertions and a small Expect.assertThrows.
Every test of the ledger module that touches a file uses a Jimfs file system.
| Test | Covers |
|---|---|
AccountKeyTest, AddressTest, ReferenceTest
|
Size, equality, order, both address kinds, copies, derived references |
MintTest, VoteTest, ClaimTest, TransferTest, HeartbeatTest
|
Encodings, and that every field is under the signature or the id |
LegacySignatureTest |
Both key encodings, low s, a header that must match the key, ASCII only, a vector made by an independent implementation |
TransactionCodecTest, BlockCodecTest, CertificateTest
|
Round trips including the largest block, every truncation, trailing bytes, count limits |
DigestsTest, MerkleRootTest, Ed25519Test
|
Hashing, tree shape for odd and empty inputs, a published Ed25519 test vector |
BlockTest, ValidatorSetTest, LedgerGenesisTest
|
Header size, a hash that ignores the certificate, quorum for every set size, rotation by view, a chain id that commits to every field |
MintScheduleTest |
The height-to-time rule, overflow and negative values |
LedgerStateTest, LedgerClaimTest, LedgerTransferTest, LedgerFeeTest, LedgerConservationTest
|
Mints, claims, transfers, fee payout, and that no money is made or lost |
LedgerCertificateTest, LedgerLastCommitTest, LedgerVoteTest, LedgerRoundTest
|
Exact quorum, views, the last commit, every vote rule, the end of a round, genesis validators never lost |
LedgerClockTest, LedgerWorkTest, LedgerValidateTest, LedgerAuthorityTest, LedgerBlacklistTest
|
Due mints, the 30-second limit, what counts as work, the supply passed to the authority, certified blocks that policy would refuse, a throwing authority |
MempoolTest, MempoolTransferTest
|
Slots, limits, the nonce window, replacing by fee, heartbeats and progress |
LedgerTest, LedgerServeTest
|
Propose, validate and commit end to end, restart from the log, serving blocks |
BlockLogTest |
A torn tail at every cut, a zero tail, damage in the middle, an impossible length, a rolled-back write |
ConsensusViewTest, ConsensusViewsTest, ConsensusRestoreTest
|
One view, view changes, timers, locks, proofs, resting, restoring a saved state |
ConsensusSimulationTest, LedgerNetworkTest
|
Many nodes on a simulated network with delays, loss, partitions, crashes and restarts from disk |
SignStateTest, SignStateFileTest, LedgerNodeTest, ChainGuardTest
|
The format, every damaged byte, atomic save, the lock, save before send, a log behind the sign state |
LedgerDriverTest, LedgerDriverSyncTest, LedgerDriverDoorTest, LedgerDriverNetworkTest, PeerLimiterTest, WireCodecTest
|
Events, forwarding, scoring, halting, sync and promotion, queue limits, drivers on a virtual network |
LedgerFixtures and CodecFixtures build keys, blocks and random valid values. FlakyChannel wraps a real
file channel and fails a write after a chosen number of bytes; it is handed to the log through the
package-private BlockLog.over. A simulated network, a virtual-time memory network and a manual scheduler
drive the engine and the driver.
In the application, LedgerSyncNetworkTest runs real QUIC servers on one host and syncs, gossips and halts on
a wrong checkpoint; NettyLedgerNetworkTest covers peer naming, ordering and limits; further tests cover the
protocol gate, the pipeline and the options. LedgerFleetTest runs several daemons with their
LedgerService in one process, see Ledger in the daemon.
-
No REST endpoints or commands for the ledger itself: nothing reads
LedgerService.status(), a balance or a block, and nothing submits a transaction from outside the node. -
No bundled genesis. A node runs a ledger only with
--ledger-genesis. -
One folder for every chain. The block log and sign state sit in
ledger/of the data directory whatever the genesis, so a node moved to another genesis must have the old files moved away. - A node whose ledger did not start still offers the ledger protocol, and drops what its peers send.
-
Heartbeats go unused.
isProgressinghas no caller. - No penalty for equivocation. The evidence is recorded and lost at the next start.
- Waiting transactions are not stored. A restart drops them.
- Signed heights depend on the file. A sign state that is deleted or replaced by an older copy is not noticed, and the directory is not flushed on Windows or an in-memory file system.
-
A fork among an earlier validator set goes unseen.
ChainGuardchecks certificates against the current set only. -
Rounds follow the traffic. With no empty blocks, a round of
nblocks lasts as long as it takes to make them, and a change of validators waits for its end. - Everything is kept in memory. The log holds every block, the state every used reference, and a start replays every block and verifies every signature, twice for a validator.
- Work is repeated. A block is checked with its certificate when the engine decides, again when the ledger commits it and a third time when the state applies it, and every waiting transaction is checked again afterwards. The authority decodes every entry of the mint spork each time it is asked.
- No lock on the log file. Two processes could open the same block log.
-
ledger/src/main/java/org/unigrid/hedgehog/ledger/: the module, one package -
ledger/src/main/java/org/unigrid/hedgehog/ledger/Ledger.java: the state, the waiting transactions and the log together -
ledger/src/main/java/org/unigrid/hedgehog/ledger/ConsensusApp.java: the interface the engine asks the ledger through -
ledger/src/main/java/org/unigrid/hedgehog/ledger/LedgerAuthority.java: the questions the node answers -
ledger/src/main/java/org/unigrid/hedgehog/ledger/LedgerDriver.java: a node run against a network -
application/src/main/java/org/unigrid/hedgehog/service/ledger/: the ledger in the daemon -
ledger/src/test/java/org/unigrid/hedgehog/ledger/andapplication/src/test/java/org/unigrid/hedgehog/service/ledger/: the tests and their fixtures
Each subpage ends with the files of its part.
Using Hedgehog
Code
Network
Releases
Upcoming