Skip to content

Raft Consensus Configuration

Operator-facing Raft adapter configuration, checks, and limitations.

Source: docs\consensus\raft\raft-consensus-adapter.md.

Configuration

NOOS_CONSENSUS_MODE supports:

  • single_node
  • raft
  • cometbft
  • openbft

Raft-specific environment variables:

  • RAFT_NODE_ID
  • RAFT_CLUSTER_ID
  • RAFT_VOTER_NODE_IDS=node-a,node-b,node-c
  • RAFT_OBSERVER_NODE_IDS=node-observer-a,node-observer-b
  • RAFT_ELECTION_TIMEOUT_MS=1500
  • RAFT_HEARTBEAT_INTERVAL_MS=500
  • RAFT_TRANSPORT=in_process|http|external_process
  • RAFT_ENGINE=noosraft|hashicorp-go
  • RAFT_INSTALL_SNAPSHOT_ENABLED=true
  • RAFT_SNAPSHOT_THRESHOLD_ENTRIES=1000
  • RAFT_BIND_URL=http://127.0.0.1:9001
  • RAFT_PEERS='[{"nodeId":"node-b","baseUrl":"http://127.0.0.1:9002"}]'

RAFT_TRANSPORT=in_process is still available for single-process verification. RAFT_TRANSPORT=http sends Raft messages to peer Fastify servers through POST /raft/message.

RAFT_ENGINE=noosraft uses Noosraft. RAFT_ENGINE=hashicorp-go selects the external HashiCorp Raft sidecar bridge through GoRaftProcessEngine; see HashiCorp Raft Engine Bridge. The sidecar is a replaceable process boundary and still commits opaque NOOSChain block DTOs, never raw database mutations.

RAFT_PEERS is a JSON array of manually configured Raft transport peers. Each entry needs nodeId and baseUrl; an optional peerId links transport events to a trusted_peers row for local peer scoring. This is not peer discovery and does not change validator membership.

HTTP Transport

The HTTP transport keeps the same RaftEngine boundary as the in-process transport. Outbound RequestVote, AppendEntries, and response messages are serialized as:

json
{
  "message": { "...": "RaftMessage" }
}

and posted to:

http
POST /raft/message

The route passes the authenticated message into the local Raft transport handler. It does not execute NOOSChain domain logic itself and it does not write blocks directly. Blocks are still applied only after the Raft engine marks a log entry committed.

Node Authentication

Every Raft HTTP message must include:

http
x-noos-node-id: <sender-node-id>
x-noos-node-timestamp: <iso timestamp>
x-noos-node-signature: <ed25519 signature>

The signed message is:

text
sha256(method + ":" + path + ":" + timestamp + ":" + bodyHash)

where bodyHash is the SHA-256 hash of the canonical JSON request body. The receiver verifies that the sender node exists in nodes, checks timestamp freshness, and verifies the signature with the sender node public key.

Failures are explicit:

  • RAFT_NODE_AUTH_INVALID
  • RAFT_NODE_UNKNOWN
  • RAFT_NODE_TIMESTAMP_STALE

This is infrastructure authentication for Raft transport. It is separate from user transaction signing, API bearer sessions, and bucket permissions.

Persistent Raft State

Noosraft persists Raft metadata in PostgreSQL:

  • raft_node_state: current term, voted-for node, commit index, last applied index.
  • raft_log_entries: per-node log entries, terms, entry hashes, committed flags, and applied flags.

This state is local consensus-engine metadata. It does not store private keys, DEKs, bucket DEKs, bearer tokens, plaintext payloads, or any materialized domain state outside the normal NOOSChain tables.

On startup, a Raft voter reloads its term, vote, commit index, last applied index, and log entries before participating in elections. If a node restarts after committing blocks, it does not forget its term/vote/log. If it was offline while another leader committed blocks, it catches up through normal AppendEntries.

Term and vote changes are persisted before the node sends vote responses. Log entries are persisted before an AppendEntries success response. Commit and apply progress are persisted as entries are committed and successfully applied through NOOSChain deterministic block execution.

Conflict Rules

Incoming leader entries may overwrite only uncommitted local entries. When a leader entry conflicts with a local uncommitted entry, the MVP truncates the uncommitted suffix and appends the leader's entries.

Committed or applied entries are immutable. If an incoming entry conflicts with a committed/applied local entry, the node refuses to truncate it. A deterministic execution mismatch after a committed entry is applied records a critical conflict and enters incident mode.

Roles

Voters run the Raft state machine and can elect leaders. Only the current leader accepts block proposals. Followers reject direct transaction submission with a NOT_LEADER reason and may include the known leader id.

Observers do not vote and do not build blocks. They remain sync-only nodes that catch up through HTTP block synchronization.

Limitations

Noosraft is deliberately small:

  • no production hardening of persisted Raft metadata yet
  • dynamic membership is available only through explicit operator-approved Go HashiCorp Raft runtime membership execution; governance transactions do not auto-mutate voters
  • InstallSnapshot is full-document only; no chunking or streaming yet
  • no snapshot compression yet
  • no advanced log compaction beyond truncating entries covered by an installed snapshot
  • no Byzantine protection
  • HTTP transport is implemented, but it is still an MVP transport and not a production-hardened Raft network stack
  • no direct transaction forwarding to leader yet

NOOSChain deterministic execution, nonce checks, permission checks, replay, and state-root verification remain the source of truth for committed application state.

Validator governance transactions update the intended active validator set, but the Raft MVP does not automatically hot-reconfigure voters from governance state. Runtime voters remain the configured set until an operator executes an approved runtime membership plan. For RAFT_ENGINE=hashicorp-go, the runtime executor can call HashiCorp Raft AddVoter, AddNonvoter, and RemoveServer through the HashiCorp Raft sidecar. For Noosraft, the reconciliation plan remains operator guidance only. Observability reports a mismatch warning when governance intent and runtime voter configuration diverge.

Verification

Use:

bash
npm run test:raft
npm run test:raft:http
npm run test:raft:membership
npm run test:raft:snapshot

test:raft exercises the in-process transport and persistence/restart safety. test:raft:http starts three real Fastify servers with isolated schemas, uses RAFT_TRANSPORT=http, verifies authenticated Raft messages, commits blocks through leader election and failover, restarts a node, and checks chain/replay convergence. test:raft:membership starts a HashiCorp Raft cluster, applies operator-approved runtime add/remove voter operations, rejects unsafe quorum reduction, verifies failover, and checks chain/replay convergence. test:raft:snapshot verifies snapshot catch-up, checkpoint replay, and rejection of tampered, wrong-chain, and wrong-root snapshots.

TLS/mTLS

Raft HTTP transport can use HTTPS/mTLS with NOOS_TLS_* settings. The HashiCorp Raft sidecar HTTP control/callback path uses GO_RAFT_TLS_* settings. The HashiCorp Raft TCP replication transport uses GO_RAFT_TCP_TLS_* settings and can require mTLS for Raft peer connections.

Audience-first NOOSChain documentation.