17. Node roles and the database set
A block header commits three authenticated databases, and every node that keeps any of them keeps
the same roots. This chapter fixes the three-member database set, the durability barrier and the
pruning rules that govern it, the parameters that affect a root and are therefore pinned, and what
each role MUST commit and MAY discard. It is also the home of the deployment obligations, the
second normative category of README.md: requirements a conforming deployment MUST
meet that do not decide whether two implementations agree on the chain. Operational topology,
sync procedure, availability-layer obligations, and the derived query tier are outside the
specification and are named where they live.
17.1 The database set
The database set is the glue DatabaseSet three-tuple that a Makechain node opens.
| Member | Native type | Commits | Retention |
|---|---|---|---|
| Validity state | Shared<current::unordered> with variable-size values |
state_root and state_target |
every role keeps it at active size (section 9) |
| Permanent membership log | Shared<keyless::fixed> |
membership_target |
windowed on validators and on the exporter, complete in the store (section 9) |
| Block-history accumulator | Shared<keyless> |
history_target |
floor zero forever on every role (section 9) |
Crash recovery is owned by glue. The targets and trust boundaries of startup reconciliation are
fixed below; the procedure itself is operational and is described in
../docs/operations.md.
R-17.1. A node MUST open exactly these three members as one database set, and MUST keep no application-level commit journal.
R-17.2. A validator and an exporter MUST run the block-history accumulator in its compact variant, and the proof service reconstructs the full accumulator from the store.
R-17.3. Validity-state values MUST be variable-size, not fixed, so that a large record never pads every small record to the maximum.
R-17.4. Every applied height MUST be covered by a successful database-set barrier before its marshal acknowledgement is released; one barrier MAY cover several heights, and finalization is per-member durable rather than cross-member atomic.
R-17.5. Startup MUST reconcile the members against the certified header targets through glue's
SyncPlan.
R-17.21. An exporter, active or standby, state-syncs only at its first installation, and state
sync installs a height range without running the finalized hook, so nothing in that range reaches
the installing node's queue. In this rule, a node's queue coverage is the highest height its
durable queue holds contiguously from the height at which that queue began, W is the publication
watermark observed in both the store and the mirror when the installation starts, and A is the
sync target at which glue completes the installation, which the node records durably before it
acknowledges any block; its queue coverage begins at A + 1. From the start of the installation
until A is recorded, the node MUST re-evaluate, at every retargeting of its sync, that every
height in (W, current target] is durable elsewhere: either the publication watermark has reached
the current target, or the other roster member is healthy, meaning its queue coverage is at
least the current target and its publication watermark has advanced within the exporter publish-lag
bound (section 19). If neither holds at any evaluation, the node MUST abort the installation and
alert; an installation that completes anyway loses (W, A] under the first cause of section 4. Once
an installation completes, the node follows marshal alone and MUST NOT state-sync again. The first
exporter of a chain, and a standby that starts at genesis, start without state sync; because the
genesis block reaches neither the capture nor the finalized hook, each MUST queue the genesis block
and the location-zero slice of both logs from its own initialization before height one, so the
store holds every height from zero. Each such slice is the native bootstrap commit operation at
location zero with the operation-range proof and pinned nodes of that one-operation range at size
one; the exporter MUST reconstruct it rather than read it, since the compact member retains no
operation, and MUST verify it against the membership_target and history_target of the
published genesis block (section 5.3, R-5.4), taken from that block after checking its hash against
the genesis hash published with chain_id, never from a block it constructed locally, stopping and
alerting if the slice does not verify. An
exporter for which max(own queue coverage, publication watermark) lies below the validators'
retained window (section 17.5) MUST stop and alert, because no source then holds the missing range.
17.2 Pinned root-affecting parameters
R-17.6. Every root-affecting parameter MUST be pinned in the chainspec (section 5), namely:
- (a) the merkle family per member, which is MMR at launch, with bagging library-fixed;
- (b) the
currentbitmap chunk size, which fixes the grafting height and therefore the canonical root; - (c) each member's commit-metadata convention.
R-17.7. A root-affecting parameter has no governed path and changes only by reset (section 18).
17.3 Pruning
Pruning below the committed floor is not erasure, and it never changes a root (section 9).
R-17.8. Pruning MUST be driven by PruneConfig through each member's own adapter: the compact
accumulator prunes its witness journal by target size, and the full logs prune by inactivity floor.
R-17.9. No separate task may mutate a live handle.
R-17.10. An exporter MUST append a block's bytes, its certificate, the block's slices of both operation logs, and the history-table rows projected from the block to its durable publication queue inside the finalized hook, durably before the hook returns and before it prunes that range, and MUST NOT acknowledge a queue entry before both the store and the mirror have accepted it. It MUST extract each slice in the capture hook, before the block's batches are applied, from that log member's merkleized batch for the block: the raw canonical operations together with their operation-range proof and pinned nodes, handed to the finalized hook as owned data on the serial path. When the queue is at its bound, the finalized hook MUST wait for space rather than skip the append, which holds back the block's marshal acknowledgement. The queue entry is the only source of a block's slices: a height above the publication watermark and at or below the highest height whose finalized hook has returned that has no queue entry, other than a range installed by state sync under R-17.21, is a fatal publication gap, and the exporter MUST stop and alert rather than reconstruct it. A height above that awaits marshal redelivery and is not a gap. The queue bound MUST admit the entry of the largest valid block into an empty queue.
The extraction is synchronous and bounded by the block's own operation count and the frontier beneath it, so the capture hook stays within R-12.25.
17.4 Roles
| Role | Consensus | Logs | Serves |
|---|---|---|---|
| Validator | votes | state at active size; membership windowed; compact nothing else | consensus peers only: votes, block backfill, and state-sync sources; diagnostics to its operator; no client-facing read |
| Exporter | non-voting mesh peer | the same three members under the same committed floor and the same retention as a validator, plus a durable publication queue | publication of blocks, certificates, operation-log slices, and the history-table rows projected from each block; current-state proofs and within-window membership proofs from its own live databases |
| Proof service | none | the store's operation logs | membership inclusion for any location against the root at any published size, and block-history ancestry proofs |
| Gateway | none | none | admission, fan-out, proof API pass-through, query API pass-through |
| Indexer | none | none of its own | command-inclusion proofs and publication receipts from stored blocks, the query tier and event subscription over the store's history tables, dedup, "not found" |
| Relayer | none | none | Tempo evidence capture and import submission |
| Availability | none | objects | retrieval and ACLs |
R-17.11. Every role that opens the database set MUST commit the same roots and the same committed floor for every log; such roles differ only in which bytes they keep.
R-17.12. A gRPC follower, namely a gateway, an indexer, or a client, is a gRPC client and MUST NOT be a mesh peer. The active exporter and its standby are non-voting mesh peers, listed in the chainspec exporter roster (section 5), which is how they receive block gossip, certificates, and marshal backfill; they never vote, and their membership never counts toward a quorum (section 13).
R-17.13. A validator MUST NOT publish blocks, certificates, or operation-log slices; an exporter owns publication and MUST fail closed when it cannot publish.
R-17.14. A validator serves no historical proofs, an exporter serves only current-state proofs and membership proofs within the committed window, and neither validators nor gateways serve any list, search, or pagination query.
Every non-proof read goes to the history query tier, which is a derived view and never a trust
boundary; it is described in ../docs/history-query.md. The invariant
that fixes the shared floor is stated with the membership log in section 9 (INV-MEMBER-06).
17.5 Retention versus publication
R-17.15. A deployment MUST keep history in two copies of different form, the store and the mirror, and MUST run one active exporter, which owns the publication watermark, together with one standby that is failure-isolated from the active, sharing with it no host, no disk, and no power or network failure domain.
R-17.16. Every finalized block MUST be published as a discrete object carrying its certificate and its slices of both operation logs, with a durable watermark.
R-17.17. A validator prunes its marshal block store after retained_marshal_blocks, a chainspec
value chosen so that an exporter outage shorter than that window loses nothing, and calibrated
against exporter publish-lag tolerance (section 19); the gap between the validator floor and the
publication watermark is readiness-critical, and a deployment MUST monitor it, alert before the gap
reaches that window, and treat a deployment whose watermark has fallen below the window as not
ready.
R-17.18. A validator MUST NOT block on the exporter watermark.
R-17.22. The standby MUST append every finalized block, its certificate, the block's slices of both operation logs, and the history-table rows projected from the block to its own durable queue on the same terms as the active (R-17.10), and MUST retain an entry until it observes both the store and the mirror watermark past that entry's height.
A range is lost only in one of two ways. Either both exporters fall behind by more than
retained_marshal_blocks, after which validators have pruned the bodies and nothing holds the
range; or a range that was queued is lost by the queues, the store, and the mirror together. The
first is what R-17.17, R-17.21, and the standby's independent queue exist to prevent, and section 4
records the residue as an accepted risk. The operational procedure for the alerts is in
../docs/operations.md.
R-17.19. A checker MUST take each comparison target from a header whose finalization certificate it has verified under its own trust anchor (section 15), MUST compare the store's root at every ingested block against that header's membership and history targets, and MUST halt the active exporter's drain on a mismatch rather than fail a proof request. The checker is detective and not preventive, because proofs are servable from the moment the store publishes a watermark.
R-17.20. A deployment MUST run a periodic replay of the stored blocks from genesis, each block once under the ruleset active at its height (section 18), taking each block from a header whose finalization certificate it has verified as in R-17.19, and asserting every header root; the cadence of that replay is a calibration item (section 19).
17.6 Rationale
This subsection is informative.
Three database members rather than one separate the three lifetimes the header must commit: priced
current state, a permanent windowed log, and a compact per-block accumulator. A fourth member for
query indexes was rejected because indexes are derived, not consensus, and nothing derived enters a
root. Completeness is a publication duty and not a retention duty: the exporter runs the same three
members under the same pruning as a validator and owes only a durable queue, so no node has to carry
an unbounded database for the chain's history to stay provable. One active exporter and one standby
is the simplest arrangement that keeps a single watermark authoritative, and the standby queues on
the same terms so that no single host ever holds the only copy of an unpublished range. The
per-store decisions and
the alternatives rejected for each are recorded in
../docs/architecture.md.
How the protocol changes.