Files
base/network/network.go
zeekay 091472b753 fix(network): singleton collapses to standalone; isSelfPeer robust
Two fixes that make one binary scale 1 → N seamlessly:

1. len(Peers) == 0 → New() returns the noop Network. A 1-pod workload
   doesn't bind a listener, doesn't spin a reconnect loop, doesn't
   self-dial. Same code path as BASE_NETWORK=standalone. Scaling up
   from 1 to N flips the same binary into a full network node just by
   adding peer entries — no config change, no rebuild.

2. BASE_PEERS carries the operator-emitted pod FQDN
   (liquid-bd-0.liquid-bd-network.<ns>.svc.cluster.local:9651) while
   BASE_NODE_ID is the bare hostname (liquid-bd-0). Plain equality
   missed the match → transport dialed its own pod → luxfi/zap
   detected dup NodeID and closed → 3s reconnect loop. isSelfPeer now
   strips :port and the first DNS label before comparing.

Also drops the strict Replication > len(Peers)+1 validation — target
quorum size is the user's intent; the shard router already returns
min(Replication, available) internally. Removing the check lets a CR
declare replication=3 and deploy at replicas=1 today without failing
boot.

Tests: TestIsSelfPeer covers the FQDN/port/short-hostname shapes;
TestSingletonCollapsesToStandalone pins the degenerate-case contract.
2026-04-23 04:31:45 -07:00

84 lines
3.1 KiB
Go

// Package network replicates Base SQLite commits across peers via the
// luxfi/consensus Quasar engine. One shard = one Quasar engine = one DAG.
//
// See docs/NETWORK.md for the full design. Contract:
//
// - Enabled() — FromEnv toggles off when BASE_NETWORK is unset.
// - Start/Stop — lifecycle for all shard engines and transport.
// - InstallWALHook — per-connection commit hook; captures frames,
// submits to the shard engine.
// - WriterFor — which pod owns shardID (consistent hash).
// - MembersFor — replica set for shardID.
package network
import (
"context"
"fmt"
)
// Network is the public API Base uses. Standalone mode returns a no-op
// implementation from FromEnv; all methods are safe to call.
type Network interface {
Enabled() bool
Start(ctx context.Context) error
Stop(ctx context.Context) error
// InstallWALHook wires the commit hook on a raw SQLite driver connection
// for the given shard. The hook captures the committed frame, signs it,
// and submits it to the shard's Quasar engine. Apply-on-finalize is
// idempotent by (salt, cksm).
InstallWALHook(rawConn any, shardID string) error
// WriterFor returns the current writer endpoint for shardID and whether
// it is the local pod. Gateway uses this for request routing.
WriterFor(shardID string) (endpoint string, local bool)
// MembersFor returns all replica endpoints for shardID.
MembersFor(shardID string) []string
// Metrics exposes the Prom collectors. Callers register via their own
// registry; we never touch the global default.
Metrics() *Metrics
}
// New constructs a Network from an already-validated Config. Callers who
// build a Config by hand use this; most callers use FromEnv.
//
// Singleton (len(Peers) == 0) collapses to the standalone no-op path. A
// one-pod workload does not need consensus, does not need ZAP, does not
// start a listener, and does not self-dial. Scaling up to N>1 flips the
// same binary into a full network node by adding peers.
func New(cfg Config) (Network, error) {
if err := cfg.validate(); err != nil {
return nil, fmt.Errorf("network: %w", err)
}
if !cfg.Enabled || len(cfg.Peers) == 0 {
return &noop{}, nil
}
return newNode(cfg)
}
// FromEnv reads BASE_* env vars. When BASE_NETWORK is empty or "standalone"
// it returns a no-op implementation with Enabled() == false. On validation
// error the error is returned so the caller can fail fast.
func FromEnv() (Network, error) {
cfg, err := ConfigFromEnv()
if err != nil {
return nil, err
}
return New(cfg)
}
// noop is the standalone-mode implementation. All methods are safe and
// allocate nothing.
type noop struct{}
func (noop) Enabled() bool { return false }
func (noop) Start(context.Context) error { return nil }
func (noop) Stop(context.Context) error { return nil }
func (noop) InstallWALHook(any, string) error { return nil }
func (noop) WriterFor(string) (string, bool) { return "", true }
func (noop) MembersFor(string) []string { return nil }
func (noop) Metrics() *Metrics { return nil }