mirror of
https://github.com/hanzoai/base.git
synced 2026-10-21 03:41:50 +00:00
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.
84 lines
3.1 KiB
Go
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 }
|