Files
zeekay ff1ee17c4f feat(network): Quasar-replicated WAL frames for Base (Phase 1)
Core runtime for network/ per docs/NETWORK.md. One env var
(BASE_NETWORK=quasar) opts Base into PQ-native HA replication via
luxfi/[email protected] Quasar DAG; one DAG per shard. Standalone
mode (default) returns a no-op Network with zero overhead.

Package surface:

  network.FromEnv()                      — parse BASE_* env, build Network
  network.Network.InstallWALHook(c, id)  — install modernc commit hook
  network.Network.WriterFor(shardID)     — router owner lookup
  network.Network.MembersFor(shardID)    — router replica set
  network.Network.Metrics()              — Prom collectors

Frame = {ShardID, Seq, PrevSeq, Ts, Salt, Payload, Cksm, Bytes}.
Content-addressed by (Salt, Cksm) so apply is idempotent across
pod restarts. Frame.Bytes is the exact serialised wire form —
archive replay never re-signs.

Quasar adapter (quasar.go): the upstream public API is Engine-based
(Submit(Block)/Finalized() <-chan Block), not the shard-scoped
Submit(shardID, frame) the design doc assumed. Adapter wraps one
Engine per shard; Block.ChainID = SHA256(shardID), Block.Data =
Frame.encode(). threshold < 2 routes to NewTestEngine for
single-node paths; production multi-node uses NewEngine with
k = floor(N/2)+1.

modernc.org/sqlite exposes RegisterCommitHook, not sqlite3_wal_hook
— same semantics for our use. core/base_network.go bridges
sqlite.HookRegisterer to network.HookRegisterer via a one-line
adapter so the network package stays driver-agnostic.

Hook into core/base.go: one field (network baseNetwork), one call
at end of Bootstrap (app.attachNetwork(ctx)), one call in
ResetBootstrapState (app.network.Stop). Standalone path unchanged.

Metrics: every collector in the doc (base_shards, base_active_shards,
base_wal_lag_bytes, base_hot_shards, base_lease_contentions,
base_archive_lag_bytes) plus frames/errors counters for the
produce/finalize loop. Callers bind to their own registry;
prometheus.DefaultRegisterer untouched.

Tests — 20 pass, 0 fail:
  wal_test.go           frame encode/decode roundtrip, tamper, idempotency key
  router_test.go        HRW determinism, balance, quorum math
  network_test.go       FromEnv standalone/quasar, fail-fast validation
  integration_test.go   3 in-process nodes share memoryHub transport; A and B
                        write different shards; all 3 finalise both frames
                        and increment FramesIngested on the non-writer.
  core/ regression      384 PASS / 2 FAIL — identical to pre-change baseline.

go.mod: +github.com/luxfi/consensus v1.22.85.
2026-04-22 13:39:01 -07:00

39 lines
1.6 KiB
Go

package network
import "context"
// Envelope is what the transport ships between nodes: a shardID header +
// the serialisable frame. Signing and transport security live at the
// transport layer; this struct is the minimal contract.
type Envelope struct {
ShardID string
Frame Frame
}
// Transport is the peer-to-peer plane. Production implementations carry
// envelopes over QUIC/gRPC with mTLS + PQ-identity. The in-process test
// transport ships envelopes through Go channels so the integration test
// runs with no sockets and no Docker.
type Transport interface {
// Start begins delivering inbound envelopes to recv. It must be
// idempotent-on-stop (Stop may be called after Start errors).
Start(ctx context.Context, recv func(Envelope)) error
// Publish fans out env to every peer in the local node's router view.
// Best-effort: peers discover missed frames via Quasar's DAG sync on
// reconnect. Implementations should not block for slow peers.
Publish(env Envelope) error
// Stop releases transport resources. Safe to call multiple times.
Stop(ctx context.Context) error
}
// nopTransport is the default transport when none is injected. It neither
// publishes nor receives — useful for single-node dev runs where Quasar is
// still wanted for the WAL/apply path but no cross-pod traffic happens.
type nopTransport struct{}
func (nopTransport) Start(context.Context, func(Envelope)) error { return nil }
func (nopTransport) Publish(Envelope) error { return nil }
func (nopTransport) Stop(context.Context) error { return nil }