17. Node roles and the database set

Draft protocolSource at 169cbd2 ↗Excerpt SHA-256 c5eb231…949

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 current bitmap 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.