fix: remove postgres/valkey/legacy mode — consensus+ZapDB only

- Removed --mode, --api-db, --api-kv, --db flags
- Removed legacy NATS/Consul transport (runNode, ~200 lines)
- Removed standalone API server (runAPIOnly, ~200 lines)
- Removed Consul helpers, NATS connection, interactive prompts
- Dashboard now uses ZapDB (SQLite) instead of postgres
- pkg/db: removed postgres backend, removed valkey client
- Consensus mode is the only mode. Zero external deps.
This commit is contained in:
2026-04-13 04:14:40 -07:00
parent 0d84c8c16d
commit c649ce7600
5 changed files with 122 additions and 788 deletions
+68 -738
View File
@@ -24,11 +24,9 @@ import (
"github.com/mr-tron/base58"
"github.com/hashicorp/consul/api"
"github.com/nats-io/nats.go"
"github.com/spf13/viper"
"github.com/urfave/cli/v3"
"golang.org/x/term"
"github.com/hanzoai/base"
"github.com/hanzoai/base/core"
@@ -36,13 +34,9 @@ import (
mpcapi "github.com/luxfi/mpc/pkg/api"
"github.com/luxfi/mpc/pkg/backup"
"github.com/luxfi/mpc/pkg/config"
"github.com/luxfi/mpc/pkg/constant"
"github.com/luxfi/mpc/pkg/db"
"github.com/luxfi/mpc/pkg/event"
"github.com/luxfi/mpc/pkg/eventconsumer"
"github.com/luxfi/mpc/pkg/identity"
"github.com/luxfi/mpc/pkg/infra"
"github.com/luxfi/mpc/pkg/keyinfo"
"github.com/luxfi/mpc/pkg/kvstore"
"github.com/luxfi/mpc/pkg/logger"
@@ -67,40 +61,27 @@ func main() {
Name: "start",
Usage: "Start a Lux MPC node",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Aliases: []string{"n"},
Usage: "Node name",
Required: false, // Not required in consensus mode
},
&cli.StringFlag{
Name: "mode",
Aliases: []string{"m"},
Usage: "Transport mode: 'consensus' (ZAP/PoA) or 'legacy' (NATS/Consul, deprecated)",
Value: "consensus",
},
// Consensus mode flags
&cli.StringFlag{
Name: "node-id",
Usage: "Node ID (consensus mode)",
Usage: "Node ID",
},
&cli.StringFlag{
Name: "listen",
Usage: "P2P listen address (consensus mode)",
Usage: "P2P listen address",
Value: ":9651",
},
&cli.StringFlag{
Name: "api",
Usage: "API listen address (consensus mode)",
Usage: "Internal API listen address",
Value: ":9800",
},
&cli.StringFlag{
Name: "data",
Usage: "Data directory (consensus mode)",
Usage: "Data directory",
},
&cli.StringFlag{
Name: "keys",
Usage: "Keys directory (consensus mode)",
Usage: "Keys directory",
},
&cli.IntFlag{
Name: "threshold",
@@ -117,15 +98,6 @@ func main() {
Usage: "Log level (debug, info, warn, error)",
Value: "info",
},
// Dashboard API flags
&cli.StringFlag{
Name: "api-db",
Usage: "PostgreSQL connection URL for dashboard API",
},
&cli.StringFlag{
Name: "api-kv",
Usage: "Valkey/Redis address for KV cache (e.g. localhost:6379)",
},
&cli.StringFlag{
Name: "api-listen",
Usage: "Dashboard API listen address",
@@ -165,18 +137,6 @@ func main() {
Sources: cli.EnvVars("MPC_HSM_ATTEST"),
Value: false,
},
// Legacy mode flags
&cli.BoolFlag{
Name: "decrypt-private-key",
Aliases: []string{"d"},
Value: false,
Usage: "Decrypt node private key (legacy mode)",
},
&cli.BoolFlag{
Name: "prompt-credentials",
Aliases: []string{"p"},
Usage: "Prompt for sensitive parameters (legacy mode)",
},
&cli.BoolFlag{
Name: "debug",
Usage: "Enable debug logging",
@@ -184,52 +144,9 @@ func main() {
},
},
Action: func(ctx context.Context, c *cli.Command) error {
mode := c.String("mode")
if mode == "legacy" {
logger.Warn("Legacy mode (NATS/Consul) is deprecated and will be removed in a future release. Migrate to --mode=consensus.")
return runNode(ctx, c)
}
return runNodeConsensus(ctx, c)
},
},
{
Name: "api",
Usage: "Start Dashboard API server only (no MPC transport)",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "db",
Usage: "PostgreSQL connection URL",
Sources: cli.EnvVars("DATABASE_URL", "MPC_API_DB"),
},
&cli.StringFlag{
Name: "listen",
Usage: "API listen address",
Value: ":8081",
},
&cli.StringFlag{
Name: "jwt-secret",
Usage: "JWT signing secret",
Sources: cli.EnvVars("JWT_SECRET", "MPC_JWT_SECRET"),
},
&cli.StringFlag{
Name: "cluster-url",
Usage: "MPC cluster URL for forwarding operations (e.g. http://mpc-node:9800)",
Sources: cli.EnvVars("MPC_CLUSTER_URL"),
},
&cli.StringFlag{
Name: "cluster-api-key",
Usage: "API key for authenticating with MPC cluster",
Sources: cli.EnvVars("MPC_CLUSTER_API_KEY"),
},
&cli.BoolFlag{
Name: "debug",
Usage: "Enable debug logging",
},
},
Action: func(ctx context.Context, c *cli.Command) error {
return runAPIOnly(ctx, c)
},
},
{
Name: "version",
Usage: "Display detailed version information",
@@ -247,282 +164,7 @@ func main() {
}
}
// Deprecated: runNode uses legacy NATS/Consul transport. Use runNodeConsensus (--mode=consensus) instead.
func runNode(ctx context.Context, c *cli.Command) error {
nodeName := c.String("name")
decryptPrivateKey := c.Bool("decrypt-private-key")
usePrompts := c.Bool("prompt-credentials")
debug := c.Bool("debug")
viper.SetDefault("backup_enabled", true)
config.InitViperConfig()
environment := viper.GetString("environment")
logger.Init(environment, debug)
// Create environment-prefixed node ID
nodeID := fmt.Sprintf("lux-%s-%s", environment, nodeName)
logger.Info("Starting MPC node", "nodeID", nodeID, "environment", environment)
// Resolve ZapDB password via HSM provider (supports AWS KMS, GCP Cloud KMS, Azure Key Vault, env, file)
// This must happen before checkRequiredConfigValues so cloud KMS passwords work.
var zapDBPassword string
if usePrompts {
promptForSensitiveCredentials()
zapDBPassword = viper.GetString("zapdb_password")
} else {
// When using an HSM provider other than env (which reads from config anyway),
// skip the zapdb_password config check — the password comes from KMS.
hsmProvider := c.String("hsm-provider")
skipPasswordCheck := hsmProvider != "" && hsmProvider != "env"
checkRequiredConfigValues(skipPasswordCheck)
zapDBPassword = resolveZapDBPassword(ctx, c)
}
consulClient := infra.GetConsulClient(environment)
keyinfoStore := keyinfo.NewStore(consulClient.KV())
peers := LoadPeersFromConsul(consulClient)
// Use the environment-prefixed nodeID we created above
// nodeID is already set with environment prefix
zapKV := NewZapKV(nodeName, nodeID, zapDBPassword)
defer zapKV.Close()
// Wrap ZapDB store with KMS-enabled store if configured
var kvStore kvstore.KVStore = zapKV
kmsEnabledStore, err := mpc.NewKMSEnabledKVStore(zapKV, nodeID)
if err != nil {
logger.Warn("Failed to create KMS-enabled store, using regular ZapDB", "error", err)
} else {
kvStore = kmsEnabledStore
logger.Info("Using KMS-enabled storage for sensitive keys")
}
// Start background backup job
backupEnabled := viper.GetBool("backup_enabled")
if backupEnabled {
backupPeriodSeconds := viper.GetInt("backup_period_seconds")
stopBackup := StartPeriodicBackup(ctx, zapKV, backupPeriodSeconds)
defer stopBackup()
}
identityStore, err := identity.NewFileStore("identity", nodeName, decryptPrivateKey)
if err != nil {
logger.Fatal("Failed to create identity store", err)
}
natsConn, err := GetNATSConnection(environment)
if err != nil {
logger.Fatal("Failed to connect to NATS", err)
}
defer natsConn.Close()
pubsub := messaging.NewNATSPubSub(natsConn)
keygenBroker, err := messaging.NewJetStreamBroker(ctx, natsConn, event.KeygenBrokerStream, []string{
event.KeygenRequestTopic,
})
if err != nil {
logger.Fatal("Failed to create keygen jetstream broker", err)
}
signingBroker, err := messaging.NewJetStreamBroker(ctx, natsConn, event.SigningPublisherStream, []string{
event.SigningRequestTopic,
})
if err != nil {
logger.Fatal("Failed to create signing jetstream broker", err)
}
_ = messaging.NewNatsDirectMessaging(natsConn) // directMessaging available for future use
mqManager := messaging.NewNATsMessageQueueManager("mpc", []string{
"mpc.mpc_keygen_result.*",
event.SigningResultTopic,
"mpc.mpc_reshare_result.*",
}, natsConn)
genKeyResultQueue := mqManager.NewMessageQueue("mpc_keygen_result")
defer genKeyResultQueue.Close()
singingResultQueue := mqManager.NewMessageQueue("mpc_signing_result")
defer singingResultQueue.Close()
reshareResultQueue := mqManager.NewMessageQueue("mpc_reshare_result")
defer reshareResultQueue.Close()
logger.Info("Node is running", "peerID", nodeID, "name", nodeName)
peerNodeIDs := GetPeerIDs(peers)
peerRegistry := mpc.NewRegistry(nodeID, peerNodeIDs, consulClient.KV())
mpcNode := mpc.NewNode(
nodeID,
peerNodeIDs,
pubsub,
kvStore,
keyinfoStore,
peerRegistry,
identityStore,
)
eventConsumer := eventconsumer.NewEventConsumer(
mpcNode,
pubsub,
genKeyResultQueue,
singingResultQueue,
reshareResultQueue,
identityStore,
)
eventConsumer.Run()
defer eventConsumer.Close()
timeoutConsumer := eventconsumer.NewTimeOutConsumer(
natsConn,
singingResultQueue,
)
timeoutConsumer.Run()
defer timeoutConsumer.Close()
keygenConsumer := eventconsumer.NewKeygenConsumer(natsConn, keygenBroker, pubsub, peerRegistry)
signingConsumer := eventconsumer.NewSigningConsumer(natsConn, signingBroker, pubsub, peerRegistry)
// Make the node ready before starting the signing consumer
if err := peerRegistry.Ready(); err != nil {
logger.Error("Failed to mark peer registry as ready", err)
}
logger.Info("[READY] Node is ready", "nodeID", nodeID)
appContext, cancel := context.WithCancel(context.Background())
// Setup signal handling to cancel context on termination signals.
go func() {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
<-sigChan
logger.Warn("Shutdown signal received, canceling context...")
cancel()
// Gracefully close consumers
if err := keygenConsumer.Close(); err != nil {
logger.Error("Failed to close keygen consumer", err)
}
if err := signingConsumer.Close(); err != nil {
logger.Error("Failed to close signing consumer", err)
}
}()
var wg sync.WaitGroup
errChan := make(chan error, 2)
wg.Add(1)
go func() {
defer wg.Done()
if err := keygenConsumer.Run(appContext); err != nil {
logger.Error("error running keygen consumer", err)
errChan <- fmt.Errorf("keygen consumer error: %w", err)
return
}
logger.Info("Keygen consumer finished successfully")
}()
wg.Add(1)
go func() {
defer wg.Done()
if err := signingConsumer.Run(appContext); err != nil {
logger.Error("error running signing consumer", err)
errChan <- fmt.Errorf("signing consumer error: %w", err)
return
}
logger.Info("Signing consumer finished successfully")
}()
go func() {
wg.Wait()
logger.Info("All consumers have finished")
close(errChan)
}()
for err := range errChan {
if err != nil {
logger.Error("Consumer error received", err)
cancel()
return err
}
}
return nil
}
// Prompt user for sensitive configuration values
func promptForSensitiveCredentials() {
fmt.Println("WARNING: Please back up your ZapDB password in a secure location.")
fmt.Println("If you lose this password, you will permanently lose access to your data!")
// Prompt for ZapDB password with confirmation
var zapPass []byte
var confirmPass []byte
var err error
for {
fmt.Print("Enter ZapDB password: ")
zapPass, err = term.ReadPassword(syscall.Stdin)
if err != nil {
logger.Fatal("Failed to read ZapDB password", err)
}
fmt.Println() // Add newline after password input
if len(zapPass) == 0 {
fmt.Println("Password cannot be empty. Please try again.")
continue
}
fmt.Print("Confirm ZapDB password: ")
confirmPass, err = term.ReadPassword(syscall.Stdin)
if err != nil {
logger.Fatal("Failed to read confirmation password", err)
}
fmt.Println() // Add newline after password input
if string(zapPass) != string(confirmPass) {
fmt.Println("Passwords do not match. Please try again.")
continue
}
break
}
// Show masked password for confirmation
maskedPassword := maskString(string(zapPass))
fmt.Printf("Password set: %s\n", maskedPassword)
viper.Set("zapdb_password", string(zapPass))
// Prompt for initiator public key (using regular input since it's not as sensitive)
var initiatorKey string
fmt.Print("Enter event initiator public key (hex): ")
if _, err := fmt.Scanln(&initiatorKey); err != nil {
logger.Fatal("Failed to read initiator key", err)
}
if initiatorKey == "" {
logger.Fatal("Initiator public key cannot be empty", nil)
}
// Show masked key for confirmation
maskedKey := maskString(initiatorKey)
fmt.Printf("Event initiator public key set: %s\n", maskedKey)
viper.Set("event_initiator_pubkey", initiatorKey)
fmt.Println("\n✓ Configuration complete!")
}
// maskString shows the first and last character of a string, replacing the middle with asterisks
func maskString(s string) string {
if len(s) <= 2 {
return s // Too short to mask
}
masked := s[0:1]
for i := 0; i < len(s)-2; i++ {
masked += "*"
}
masked += s[len(s)-1:]
return masked
}
// Check required configuration values are present.
// checkRequiredConfigValues verifies required viper config values are present.
// skipPasswordCheck should be true when ZapDB password will be resolved via HSM provider.
func checkRequiredConfigValues(skipPasswordCheck bool) {
// Show warning if we're using file-based config but no password is set
@@ -577,49 +219,6 @@ func resolveZapDBPassword(ctx context.Context, c *cli.Command) string {
return password
}
func NewConsulClient(addr string) *api.Client {
// Create a new Consul client
consulConfig := api.DefaultConfig()
consulConfig.Address = addr
consulClient, err := api.NewClient(consulConfig)
if err != nil {
logger.Fatal("Failed to create consul client", err)
}
logger.Info("Connected to consul!")
return consulClient
}
// Deprecated: LoadPeersFromConsul is used by legacy mode only.
func LoadPeersFromConsul(consulClient *api.Client) []config.Peer { // Create a Consul Key-Value store client
kv := consulClient.KV()
peers, err := config.LoadPeersFromConsul(kv, "mpc_peers/")
if err != nil {
logger.Fatal("Failed to load peers from Consul", err)
}
logger.Info("Loaded peers from consul", "peers", peers)
return peers
}
func GetPeerIDs(peers []config.Peer) []string {
var peersIDs []string
for _, peer := range peers {
peersIDs = append(peersIDs, peer.ID)
}
return peersIDs
}
// Given node name, loop through peers and find the matching ID
func GetIDFromName(name string, peers []config.Peer) string {
// Get nodeID from node name
nodeID := config.GetNodeID(name, peers)
if nodeID == "" {
logger.Fatal("Empty Node ID", fmt.Errorf("node ID not found for name %s", name))
}
return nodeID
}
func NewZapKV(nodeName, nodeID, password string) *kvstore.Store {
// ZapDB KV store
// Use configured db_path or default to current directory + "db"
@@ -678,39 +277,7 @@ func StartPeriodicBackup(ctx context.Context, zapKV *kvstore.Store, periodSecond
return backupCancel
}
// Deprecated: GetNATSConnection is used by legacy mode only.
func GetNATSConnection(environment string) (*nats.Conn, error) {
url := viper.GetString("nats.url")
opts := []nats.Option{
nats.MaxReconnects(-1), // retry forever
nats.ReconnectWait(2 * time.Second),
nats.DisconnectHandler(func(nc *nats.Conn) {
logger.Warn("Disconnected from NATS")
}),
nats.ReconnectHandler(func(nc *nats.Conn) {
logger.Info("Reconnected to NATS", "url", nc.ConnectedUrl())
}),
nats.ClosedHandler(func(nc *nats.Conn) {
logger.Info("NATS connection closed!")
}),
}
if environment == constant.EnvProduction {
clientCert := filepath.Join(".", "certs", "client-cert.pem")
clientKey := filepath.Join(".", "certs", "client-key.pem")
caCert := filepath.Join(".", "certs", "rootCA.pem")
opts = append(opts,
nats.ClientCert(clientCert, clientKey),
nats.RootCAs(caCert),
nats.UserInfo(viper.GetString("nats.username"), viper.GetString("nats.password")),
)
}
return nats.Connect(url, opts...)
}
// runNodeConsensus runs the MPC node with consensus-embedded transport (no NATS/Consul)
// runNodeConsensus runs the MPC node with consensus-embedded transport
func runNodeConsensus(ctx context.Context, c *cli.Command) error {
nodeID := c.String("node-id")
listenAddr := c.String("listen")
@@ -1093,85 +660,73 @@ func runNodeConsensus(ctx context.Context, c *cli.Command) error {
}
}
// Start Dashboard API server if PostgreSQL URL is provided
apiDBURL := c.String("api-db")
if apiDBURL == "" {
apiDBURL = os.Getenv("MPC_API_DB")
// Start Dashboard API server (ZapDB-backed, no external dependencies)
apiListenAddr := c.String("api-listen")
jwtSecret := c.String("jwt-secret")
if jwtSecret == "" {
jwtSecret = os.Getenv("MPC_JWT_SECRET")
}
if apiDBURL != "" {
apiListenAddr := c.String("api-listen")
jwtSecret := c.String("jwt-secret")
if jwtSecret == "" {
jwtSecret = os.Getenv("MPC_JWT_SECRET")
}
if jwtSecret == "" {
return fmt.Errorf("--jwt-secret is required (set JWT_SECRET or MPC_JWT_SECRET env var, source from KMS)")
}
apiKVAddr := c.String("api-kv")
if apiKVAddr == "" {
apiKVAddr = os.Getenv("MPC_API_KV")
}
database, err := db.New(apiDBURL, apiKVAddr)
if jwtSecret != "" {
dbPath := filepath.Join(dataDir, "dashboard.db")
database, err := db.New(dbPath, "")
if err != nil {
logger.Error("Failed to connect to dashboard database", err)
logger.Error("Failed to open dashboard database", err)
} else {
defer database.Close()
{
mpcBackend := &ConsensusMPCBackend{
pubSub: pubSub,
peerRegistry: peerRegistry,
factory: factory,
keyInfoStore: factory.KeyInfoStore(),
identity: consensusIdentity,
nodeID: nodeID,
threshold: threshold,
}
apiServer := mpcapi.NewServer(database, mpcBackend, jwtSecret)
apiServer.StartScheduler(ctx)
// Wire HSM signer for intent co-signing
signerType := c.String("hsm-signer")
if signerType != "" {
signer, signerErr := hsm.NewSigner(signerType, nil)
if signerErr != nil {
logger.Error("Failed to create HSM signer", signerErr, "provider", signerType)
} else {
apiServer.SetHSM(signer)
logger.Info("HSM signer configured for co-signing", "provider", signer.Provider())
}
}
// HSM threshold attestation (key share vault storage)
if c.Bool("hsm-attest") {
logger.Info("HSM threshold attestation enabled",
"signer", c.String("hsm-signer"),
"attest_key", c.String("hsm-signer-key-id"),
"vault_provider", c.String("hsm-provider"),
)
}
// Mount chi handler on Base
os.Args = []string{"mpcd", "serve", "--http", apiListenAddr}
baseApp := base.New()
baseApp.OnServe().BindFunc(func(e *core.ServeEvent) error {
e.Router.Any("/{path...}", func(re *core.RequestEvent) error {
apiServer.Handler().ServeHTTP(re.Response, re.Request)
return nil
})
return e.Next()
})
logger.Info("Dashboard API starting (Base)", "addr", apiListenAddr)
go func() {
if err := baseApp.Start(); err != nil {
logger.Error("Dashboard API server failed", err)
}
}()
logger.Info("Dashboard API ready", "addr", apiListenAddr)
mpcBackend := &ConsensusMPCBackend{
pubSub: pubSub,
peerRegistry: peerRegistry,
factory: factory,
keyInfoStore: factory.KeyInfoStore(),
identity: consensusIdentity,
nodeID: nodeID,
threshold: threshold,
}
apiServer := mpcapi.NewServer(database, mpcBackend, jwtSecret)
apiServer.StartScheduler(ctx)
// Wire HSM signer for intent co-signing
signerType := c.String("hsm-signer")
if signerType != "" {
signer, signerErr := hsm.NewSigner(signerType, nil)
if signerErr != nil {
logger.Error("Failed to create HSM signer", signerErr, "provider", signerType)
} else {
apiServer.SetHSM(signer)
logger.Info("HSM signer configured for co-signing", "provider", signer.Provider())
}
}
// HSM threshold attestation (key share vault storage)
if c.Bool("hsm-attest") {
logger.Info("HSM threshold attestation enabled",
"signer", c.String("hsm-signer"),
"attest_key", c.String("hsm-signer-key-id"),
"vault_provider", c.String("hsm-provider"),
)
}
// Mount chi handler on Base
os.Args = []string{"mpcd", "serve", "--http", apiListenAddr}
baseApp := base.New()
baseApp.OnServe().BindFunc(func(e *core.ServeEvent) error {
e.Router.Any("/{path...}", func(re *core.RequestEvent) error {
apiServer.Handler().ServeHTTP(re.Response, re.Request)
return nil
})
return e.Next()
})
logger.Info("Dashboard API starting (Base+SQLite)", "addr", apiListenAddr)
go func() {
if err := baseApp.Start(); err != nil {
logger.Error("Dashboard API server failed", err)
}
}()
logger.Info("Dashboard API ready", "addr", apiListenAddr, "db", dbPath)
}
}
@@ -1792,228 +1347,3 @@ func eddsaPubKeyToSolAddress(pubKey []byte) string {
return base58.Encode(pubKey)
}
// runAPIOnly starts only the Dashboard API server without any MPC transport.
// This is used for the cloud dashboard deployment where MPC operations are
// forwarded to the MPC nodes separately.
func runAPIOnly(ctx context.Context, c *cli.Command) error {
debug := c.Bool("debug")
logger.Init("api", debug)
dbURL := c.String("db")
if dbURL == "" {
return fmt.Errorf("--db (or DATABASE_URL env) is required")
}
listenAddr := c.String("listen")
jwtSecret := c.String("jwt-secret")
if jwtSecret == "" {
return fmt.Errorf("--jwt-secret (or JWT_SECRET env) is required")
}
logger.Info("Starting Dashboard API server (standalone)", "addr", listenAddr)
database, err := db.New(dbURL, "")
if err != nil {
return fmt.Errorf("failed to connect to database: %w", err)
}
defer database.Close()
logger.Info("Database connected")
// Determine MPC backend: if cluster-url is provided, forward to the cluster;
// otherwise use a stub that returns descriptive errors.
clusterURL := c.String("cluster-url")
clusterAPIKey := c.String("cluster-api-key")
var mpcBackend mpcapi.MPCBackend
if clusterURL != "" {
mpcBackend = newAPIOnlyMPCBackend(clusterURL, clusterAPIKey)
logger.Info("API-only mode: forwarding MPC operations to cluster", "url", clusterURL)
} else {
mpcBackend = &stubMPCBackend{}
logger.Warn("API-only mode: no MPC_CLUSTER_URL set, MPC operations will fail")
}
apiServer := mpcapi.NewServer(database, mpcBackend, jwtSecret)
apiServer.StartScheduler(ctx)
// Wire HSM signer for intent co-signing
signerType := os.Getenv("MPC_HSM_SIGNER")
if signerType == "" {
signerType = "local"
}
signer, signerErr := hsm.NewSigner(signerType, nil)
if signerErr != nil {
logger.Error("Failed to create HSM signer", signerErr, "provider", signerType)
} else {
apiServer.SetHSM(signer)
logger.Info("HSM signer configured for co-signing", "provider", signer.Provider())
}
// Mount chi handler on Base
os.Args = []string{"mpcd", "serve", "--http", listenAddr}
baseApp := base.New()
baseApp.OnServe().BindFunc(func(e *core.ServeEvent) error {
e.Router.Any("/{path...}", func(re *core.RequestEvent) error {
apiServer.Handler().ServeHTTP(re.Response, re.Request)
return nil
})
return e.Next()
})
logger.Info("Dashboard API starting (Base)", "addr", listenAddr)
// Base owns the process lifecycle (blocks until shutdown)
if err := baseApp.Start(); err != nil {
return fmt.Errorf("API server failed: %w", err)
}
return nil
}
// stubMPCBackend returns errors for MPC operations when no cluster URL is configured.
type stubMPCBackend struct{}
func (s *stubMPCBackend) TriggerKeygen(orgID, walletID string) (*mpcapi.KeygenResult, error) {
return nil, fmt.Errorf("MPC operations not available: set MPC_CLUSTER_URL to enable forwarding")
}
func (s *stubMPCBackend) TriggerSign(orgID, walletID string, payload []byte) (*mpcapi.SignResult, error) {
return nil, fmt.Errorf("MPC operations not available: set MPC_CLUSTER_URL to enable forwarding")
}
func (s *stubMPCBackend) TriggerReshare(orgID, walletID string, newThreshold int, newParticipants []string) error {
return fmt.Errorf("MPC operations not available: set MPC_CLUSTER_URL to enable forwarding")
}
func (s *stubMPCBackend) ExportKeyShare(orgID, walletID string) ([]byte, error) {
return nil, fmt.Errorf("MPC operations not available: set MPC_CLUSTER_URL to enable forwarding")
}
func (s *stubMPCBackend) GetClusterStatus() *mpcapi.ClusterStatus {
return &mpcapi.ClusterStatus{
NodeID: "api-only",
Mode: "api-only",
Ready: false,
Version: Version,
}
}
// apiOnlyMPCBackend forwards MPC operations to the actual MPC cluster via HTTP.
type apiOnlyMPCBackend struct {
clusterURL string
apiKey string
httpClient *http.Client
}
func newAPIOnlyMPCBackend(clusterURL, apiKey string) *apiOnlyMPCBackend {
// Strip trailing slash from cluster URL
clusterURL = strings.TrimRight(clusterURL, "/")
return &apiOnlyMPCBackend{
clusterURL: clusterURL,
apiKey: apiKey,
httpClient: &http.Client{
Timeout: 120 * time.Second, // MPC keygen can take ~30s; allow generous timeout
},
}
}
// doRequest sends an HTTP request to the MPC cluster and decodes the JSON response.
func (a *apiOnlyMPCBackend) doRequest(method, path string, reqBody interface{}, respBody interface{}) error {
var bodyReader *strings.Reader
if reqBody != nil {
data, err := json.Marshal(reqBody)
if err != nil {
return fmt.Errorf("failed to marshal request: %w", err)
}
bodyReader = strings.NewReader(string(data))
} else {
bodyReader = strings.NewReader("")
}
url := a.clusterURL + path
req, err := http.NewRequest(method, url, bodyReader)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
if a.apiKey != "" {
req.Header.Set("Authorization", "Bearer "+a.apiKey)
}
resp, err := a.httpClient.Do(req)
if err != nil {
return fmt.Errorf("cluster request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
var errBody struct {
Error string `json:"error"`
}
_ = json.NewDecoder(resp.Body).Decode(&errBody)
if errBody.Error != "" {
return fmt.Errorf("cluster returned %d: %s", resp.StatusCode, errBody.Error)
}
return fmt.Errorf("cluster returned status %d", resp.StatusCode)
}
if respBody != nil {
if err := json.NewDecoder(resp.Body).Decode(respBody); err != nil {
return fmt.Errorf("failed to decode cluster response: %w", err)
}
}
return nil
}
func (a *apiOnlyMPCBackend) TriggerKeygen(orgID, walletID string) (*mpcapi.KeygenResult, error) {
reqBody := map[string]string{"org_id": orgID, "wallet_id": walletID}
var result mpcapi.KeygenResult
// Internal MPC API on port 9800 uses /keygen (not /v1/keygen)
if err := a.doRequest("POST", "/keygen", reqBody, &result); err != nil {
return nil, fmt.Errorf("keygen forwarding failed: %w", err)
}
return &result, nil
}
func (a *apiOnlyMPCBackend) TriggerSign(orgID, walletID string, payload []byte) (*mpcapi.SignResult, error) {
reqBody := map[string]interface{}{
"org_id": orgID,
"wallet_id": walletID,
"payload": hex.EncodeToString(payload),
}
var result mpcapi.SignResult
if err := a.doRequest("POST", "/v1/sign", reqBody, &result); err != nil {
return nil, fmt.Errorf("sign forwarding failed: %w", err)
}
return &result, nil
}
func (a *apiOnlyMPCBackend) TriggerReshare(orgID, walletID string, newThreshold int, newParticipants []string) error {
reqBody := map[string]interface{}{
"org_id": orgID,
"wallet_id": walletID,
"new_threshold": newThreshold,
"new_participants": newParticipants,
}
if err := a.doRequest("POST", "/v1/reshare", reqBody, nil); err != nil {
return fmt.Errorf("reshare forwarding failed: %w", err)
}
return nil
}
func (a *apiOnlyMPCBackend) ExportKeyShare(orgID, walletID string) ([]byte, error) {
return nil, fmt.Errorf("ExportKeyShare not available in api-only mode; use consensus mode for backup operations")
}
func (a *apiOnlyMPCBackend) GetClusterStatus() *mpcapi.ClusterStatus {
var status mpcapi.ClusterStatus
// Internal MPC API on port 9800 uses /health (not /v1/status)
logger.Warn("Failed to get cluster status", "err", err, "url", a.clusterURL)
return &mpcapi.ClusterStatus{
NodeID: "api-only",
Mode: "api-only-proxy",
Ready: false,
Version: Version,
}
}
return &status
}
+2 -2
View File
@@ -26,7 +26,6 @@ require (
github.com/gtank/ristretto255 v0.2.0
github.com/hanzoai/base v0.39.1
github.com/hanzoai/dbx v1.13.0
github.com/hanzoai/kv-go/v9 v9.18.0
github.com/hanzoai/orm v0.3.2
github.com/hashicorp/consul/api v1.33.7
github.com/luxfi/crypto v1.17.45
@@ -117,6 +116,7 @@ require (
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/gtank/merlin v0.1.1 // indirect
github.com/hanzoai/kv-go/v9 v9.18.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
@@ -154,7 +154,7 @@ require (
github.com/luxfi/sampler v1.0.0 // indirect
github.com/luxfi/utils v1.1.4 // indirect
github.com/luxfi/zap v0.2.0 // indirect
github.com/luxfi/zapdb v1.0.0 // indirect
github.com/luxfi/zapdb/v4 v4.9.3 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
+34
View File
@@ -472,10 +472,14 @@ github.com/luxfi/address v1.0.1/go.mod h1:5j3Eh66v9zvv1GbNdZwt+23krV8JlSDaRzmWZU
github.com/luxfi/ai v0.0.0-20251225021023-3f15131f2bd1/go.mod h1:4+FVvnCo1QqM3NaVUte0da4zgH2IblVLmUxKQi0BwE0=
github.com/luxfi/api v1.0.1/go.mod h1:N2Ymyqo3SoB6/DmPXRV6W377k2qNqZujSP/B8fbIB9U=
github.com/luxfi/atomic v0.0.1/go.mod h1:UzFk1K7VXjJhjm2c4Yiu5HydKbWi36kFtrlDKbm8MG8=
github.com/luxfi/atomic v1.0.0 h1:xUV60MuzRvXngaQ1sM0yVC2v4TRoLlUGkkH7M9PS4yw=
github.com/luxfi/atomic v1.0.0/go.mod h1:0G2mTlQ6TXWHICUHrUUPu1/qAiIyR4gSZ2tva9ci/bI=
github.com/luxfi/bft v0.1.0/go.mod h1:IUT167Erk2h8z36197Yrmu/3CBIsZku5WlEG8O2NMp4=
github.com/luxfi/cache v1.1.0/go.mod h1:9GvlEEE9rFPaaWxvVpSPwW8ZMo2+8VMNNcuPa4AwzPg=
github.com/luxfi/cache v1.2.0/go.mod h1:Esn48WMB3JL/+UOdCEV2Chyjr1MG3SSckXWLhB/P080=
github.com/luxfi/codec v1.1.2/go.mod h1:T+DV0Jau5bi+8wMcaoj51zS1rgt80VovQKzaN2CV5Mg=
github.com/luxfi/codec v1.1.4 h1:Yl8ZalMNkqo7cD6R9AjczAajkLOmsjyZ9+DASVYHrvg=
github.com/luxfi/codec v1.1.4/go.mod h1:oGQ3j6E8c2P0pL0irYtWkrB1hmDUFIE0puXHK4gV5KI=
github.com/luxfi/compress v0.0.2/go.mod h1:cfa7L3STdZdsMwJc9faZML1modLNh1n2u72n/DOfuNU=
github.com/luxfi/compress v0.0.4/go.mod h1:/eN0eca98igoE6DDsLv/4w7n1gsQKwa+HUTxmWz/iWw=
github.com/luxfi/concurrent v0.0.2/go.mod h1:SwEDrQXVAXgBiObXVg0pfmLzU1xsa9ZeBRptkBdyNUU=
@@ -488,6 +492,8 @@ github.com/luxfi/consensus v1.22.62/go.mod h1:sEAGM6n/tGXx7Sd5d7F22862rqT8n2y+Z1
github.com/luxfi/consensus v1.22.67/go.mod h1:mUffRVhwE3zz8bHwAIXT0xSroqOceszepHl8Tp6GwrI=
github.com/luxfi/const v1.4.1/go.mod h1:qWfecjdImPY3kXsy3UAe4ZVpMcsp2/iUlHwUMPVREoI=
github.com/luxfi/constants v1.4.2/go.mod h1:xk+0eA7UY1GkHrmI7yqR+9LDe4TUe3p1VfRvs6cJbNM=
github.com/luxfi/constants v1.4.7 h1:e/Qs+DQP3pugle3Zncq6fZCxKgqqtbyD/z7Gm4ZjsYg=
github.com/luxfi/constants v1.4.7/go.mod h1:hOszZ2NDQ8gMZKncfcZ67PXkb5OIbnwAzXC3oFbQwW0=
github.com/luxfi/container v0.0.2/go.mod h1:nnK+TxHmGry0mGnfun4jx0tLqWJgV/GbZXTETMuFJn4=
github.com/luxfi/coreth v0.15.71/go.mod h1:Z/OTRrw+q+uSNrCh1Fg0hkMf1fRquzbTQelx7wR0HOM=
github.com/luxfi/crypto v1.17.28/go.mod h1:v6eaW5ejuzW2gecChWKQqx4CtVhoM1b+e40ro/6WVPg=
@@ -495,39 +501,57 @@ github.com/luxfi/crypto v1.17.36/go.mod h1:UHzltsXPDF/tGcmTiL/DbkClAflr3YmvOaXlw
github.com/luxfi/crypto v1.17.37/go.mod h1:vu+pCHdyPQZULzrA+K+lTFYrg8vOtNdu+/5Vko8RzZ0=
github.com/luxfi/crypto v1.17.38/go.mod h1:G2t1GQvPsrwnzwyVEj0LQDuX2AWZVI5kEAPyVeicc5o=
github.com/luxfi/crypto v1.17.39/go.mod h1:mChLWmW4CLR1wAN6CeJTveCzUv0DTzGQnYgq01x3W0U=
github.com/luxfi/crypto v1.17.45 h1:uGK0y4+aLipE/M0YIQ5hcsWv0ZG0E4cPv03a94K/eLE=
github.com/luxfi/crypto v1.17.45/go.mod h1:GnAkhQ7HNs3X0Tzx5nOONS3kl0yRmWHbDcRO5ffILsg=
github.com/luxfi/czmq/v4 v4.2.2/go.mod h1:WFY9ldUbeU900P8bcIRr7Jim23veOhJL5A5zSpTogdU=
github.com/luxfi/database v1.17.35/go.mod h1:dYxaF30KGJQzuxA3VH7+WGU5/dhwRyzLgzSk9F3wje0=
github.com/luxfi/database v1.17.38/go.mod h1:q6BJNN+47VtkmkGrswQmC0ikERYAdlmZCYhGTXkF4mM=
github.com/luxfi/database v1.17.39/go.mod h1:iHE5evTMxltmYGe3QkewrZv1xgGrDBbqcv0y4NkDJcI=
github.com/luxfi/database v1.17.40/go.mod h1:du1q0PemBc0gZcdtHhsARs6mIsZM3a5R/C040DVYLCM=
github.com/luxfi/database v1.17.42/go.mod h1:0nArLBm0r42J103oHR2xkAe8+mftuz5jtFgNylU1hgg=
github.com/luxfi/database v1.17.44 h1:hfiTls7sqbweW+o4iaZqB8P997paC+vpgWmhN6v5MJ0=
github.com/luxfi/database v1.17.44/go.mod h1:6Ey5y3I0WNLHbxIlIdFqUuKfBg+b0fAgTA8FgRgQ8zg=
github.com/luxfi/evm v0.8.30/go.mod h1:S0fChMeuWV4XqY4OB6RozqvyHcm+imJCKyqsMY713hY=
github.com/luxfi/fhe v1.7.6-0.20260106060801-28e308e4c2f8/go.mod h1:SVO5KV3GKMW5U0XeQSV4x7fryNWHsCjyhhhAoIPCpG8=
github.com/luxfi/fhe v1.7.7 h1:RP26i3CxvNy2y/3LKiJPgd9uR5MrlG/MvQnv5rkQFhk=
github.com/luxfi/fhe v1.7.7/go.mod h1:FHfGNgKL5ZLcnKVKFVNLDJOBbQyQm5DGpVrMnY8ASJo=
github.com/luxfi/filesystem v0.0.1/go.mod h1:OQVSU6XNwqrr1AI+MqkID2taHUclx7NYmmr3svgttec=
github.com/luxfi/formatting v1.0.0/go.mod h1:lswh6dPQ4E9Q+nZLwispV+Jdthr5R1PkYTBA/LhuSgE=
github.com/luxfi/formatting v1.0.1/go.mod h1:mYzNf5DJOiqSSKUPzNj5dKy4tstFbN3pZlkI5716eKc=
github.com/luxfi/genesis v1.5.21/go.mod h1:XI1T2oMh/VS/f+LWMWPDQIEhEneCNVHodhn2Qr3rFMU=
github.com/luxfi/genesis v1.5.22/go.mod h1:ernOqYxKPEZnwu/QvgJ9y4Lb+IUpfoOtZ3/DubzExv0=
github.com/luxfi/geth v1.16.73/go.mod h1:CPzps7Zp3I7Og3KT3m+Qs41WW8z+YPGx1TOJQnHdBHY=
github.com/luxfi/geth v1.16.79 h1:MtP8ZUuSVZDjmZDa1kTJCl0PpG8+wGbQft652GO6a3A=
github.com/luxfi/geth v1.16.79/go.mod h1:6xNi4sHoh0v1kBf0TOjmFhBg5+4umbSqrwiFZsDanZ0=
github.com/luxfi/go-bip32 v1.0.2/go.mod h1:bc7/LXDKAJQZ/F0Xjf5yXaTZxY9/ssLb4FC+Hxn/cDk=
github.com/luxfi/go-bip39 v1.1.2/go.mod h1:96de9VkR2kY/ASAnhMtvt3TSh+PZkAFAngNj0GjRGDo=
github.com/luxfi/gpu v0.30.0/go.mod h1:7pFsHqra1Vrmy2aGXVEPKeKsPR+Bn+QmBXuByK3fdPA=
github.com/luxfi/hid v0.9.3/go.mod h1:XJ/7DZAHf5dggm3zWNbitKuFGB7J96b4iX+8NO3BsnY=
github.com/luxfi/hsm v1.1.1 h1:sIJZLSHD844/DdLx70tA2j8Ql1q3oy1zmsuV4TwVNXU=
github.com/luxfi/hsm v1.1.1/go.mod h1:+tB7UEipw6h4cGNRFhvQsviM95fnseyMt894bqiRrbU=
github.com/luxfi/ids v1.2.7/go.mod h1:svLsj7e6ixJVfRYaQqv9RWjBIW11Vz738+d08BVpeCg=
github.com/luxfi/ids v1.2.8/go.mod h1:14vKgM828w7yqnh6z0LhNrObXHVvtyRDyWBEmWBj5i8=
github.com/luxfi/keychain v1.0.1/go.mod h1:X7HLk5QRZ1VHJxovAyBL6z7f/nlosoXlveEtvUGhoaw=
github.com/luxfi/keychain v1.0.2/go.mod h1:q/4ULgZBlstKkwzOzG/0T6y73BDPgnkrcibbJyTvmbU=
github.com/luxfi/keys v1.0.7/go.mod h1:Faz87SfMIzVpE+2uIsYQnw+36rRhQNKydd/3mEEXPbg=
github.com/luxfi/lattice/v7 v7.0.1 h1:RsK+YR0+fKdey08t95Is0GdiinXEvyYY/dXDbHDdmJg=
github.com/luxfi/lattice/v7 v7.0.1/go.mod h1:eGIdt34H0QsHlvSsB9PAbE0Bxa7BLOsj6HVHqbFS/5I=
github.com/luxfi/ledger v1.1.6/go.mod h1:hyNV+4a6nI3yfwhyJcmXICwJeWfnxA1PbMZEOZ1VFnw=
github.com/luxfi/log v1.2.1/go.mod h1:0vJzb4Hl9fvgwWDMKcbCD/SYH3bO0iSmgZMcYdfdl1o=
github.com/luxfi/log v1.4.0/go.mod h1:64IE3xRMJcpkQwnPUfJw3pDj7wU0kRS7BZ9wM7R72jk=
github.com/luxfi/lpm v1.0.5/go.mod h1:dS/NS6muB9zlHWbO/VeCMYCe53YwIjXDHg/jif7TejI=
github.com/luxfi/math v1.2.2/go.mod h1:C8STnF2H+D6rqBPt248CiWY2TGuJgdtv/+4UqrT15iM=
github.com/luxfi/math v1.2.4 h1:iVz7s0gToCNUU/2cLT8gUa8MLzc0YRp4jBmeXBeKdXk=
github.com/luxfi/math v1.2.4/go.mod h1:i+Am9YvFsqDE75ZW8MPE62XCXGukXNiN/7mD9SKxxZY=
github.com/luxfi/math/safe v0.0.1/go.mod h1:EejrmOJHh03YAD8+Zww8cPcMR1K3Q2I7w1dX4sMloeo=
github.com/luxfi/mdns v0.1.0 h1:VB3mQcETc9j5SY1S6lAgFtuGr/rjWuDgPYnxS+OKWMQ=
github.com/luxfi/mdns v0.1.0/go.mod h1:/3dheKVjUk2yiS/ocH1IDzeLXOIe+kpVsErIGDOZdiQ=
github.com/luxfi/metric v1.4.9/go.mod h1:JlAAc4Jm5apoU2DHEgt57UHD7+8AjMdpP2rQJ/zaACY=
github.com/luxfi/metric v1.4.10/go.mod h1:3OIQ+e6mciesm3cik3Q4CSOAvTlBy4JA0ZoKzCB9APo=
github.com/luxfi/metric v1.4.11/go.mod h1:EAFmkzd5EX654G48HXKIsyJufOUa4sxmOJKvwSRbjng=
github.com/luxfi/metric v1.4.12/go.mod h1:EAFmkzd5EX654G48HXKIsyJufOUa4sxmOJKvwSRbjng=
github.com/luxfi/metric v1.5.1 h1:6tVarXMnNR3Xzud8FYUHjtXabTll3HI/OEqG0tgLAcI=
github.com/luxfi/metric v1.5.1/go.mod h1:PkD4D4JoGuyKtfUkqPNYkrg9xKrJeNVZFdW/5XvAe/A=
github.com/luxfi/mock v0.1.0/go.mod h1:izF+9K0gGzFC9zERn6Po37v46eLdPB+EIsDjL3GLk+U=
github.com/luxfi/net v0.0.1/go.mod h1:JTPsJHaUcpF0HruczW14FlUTjPk78xF73XGSMQa86NA=
github.com/luxfi/netrunner v1.14.35/go.mod h1:vfeCr7L49R9yoSjZCpZe2+xtoNdPt4fstEtbKt7Jtls=
@@ -561,6 +585,8 @@ github.com/luxfi/staking v1.0.0/go.mod h1:E+fm3upfQTOu0fLCr3Uo4xYgAm7r/vEzgcUpNA
github.com/luxfi/staking v1.1.0/go.mod h1:xwYacmreoKoy31ELT6QPno6b5M8q7bg4zJrmmt524X4=
github.com/luxfi/sys v0.0.0-20260110090042-50187ec5ffd8/go.mod h1:Y0UwjnEJYSbGM7IvJbKYLf8BWNVLfT8Oz00bEiDITHw=
github.com/luxfi/threshold v1.5.1/go.mod h1:OsWmH2foqB2K4vVpHTjSHrVM4oveSzqOZb/k+fqNsYY=
github.com/luxfi/threshold v1.6.0 h1:pq/n1iYowws+CXe/+slJodjPlJ152hFF94luHp2E82c=
github.com/luxfi/threshold v1.6.0/go.mod h1:XWSfWM3n/tVggluh/LH7is4vYL85zmyMHCqfz2qrYVU=
github.com/luxfi/timer v1.0.1/go.mod h1:PQ15H3AEUfvWVf0kv5WhIGPWmhr9BxBv/VKLyyekpk4=
github.com/luxfi/tls v1.0.0/go.mod h1:QZXWuYFZPfGA9AyRIBXgIuXPrmvWWHtQkJtplZnTlWc=
github.com/luxfi/tls v1.0.2/go.mod h1:t6Fr+KYKDZEz1KxciGHzRrj/KsQIx8V3iG/8YiSqpvU=
@@ -570,6 +596,8 @@ github.com/luxfi/units v1.0.0/go.mod h1:tma28v4ed1tupdS0kpSeyO+u1wWK/g1NqODPbN1Y
github.com/luxfi/upgrade v1.0.0/go.mod h1:DZF7dO4erhUEKf907B2e65k4/vGkCPkUngpvIEUS3eI=
github.com/luxfi/utils v1.1.0/go.mod h1:ABqhBdGNig0CaDcnNYldv1byS0BTNi5IKPbJSPF1p98=
github.com/luxfi/utils v1.1.1/go.mod h1:DL7634uXEcCu2tCBll0bmX0SVnMJeABbmhm5NkLMu78=
github.com/luxfi/utils v1.1.4 h1:8OY0jXCkpp6OotN1Y++6DATJkvtEwzq2k0p56imJlAk=
github.com/luxfi/utils v1.1.4/go.mod h1:c3yz1RjzrB+cs5GZm+q1T3/2cCKElO9vxm9yRRtgSEM=
github.com/luxfi/utxo v0.0.1/go.mod h1:cwkUv/J6zNohRI7+xrj03wi39bm6CYkek+6XRhQrPTU=
github.com/luxfi/utxo v0.2.3/go.mod h1:I23ZJND7kU6NUklD2nY24mU93jQXBPGu1c9Y1hjX9Vk=
github.com/luxfi/validators v1.0.0/go.mod h1:QGppjtI/gqprbplWc10J+4OIK8UYWpv+53mfH2aKhy4=
@@ -587,7 +615,13 @@ github.com/luxfi/warp v1.18.2/go.mod h1:qu3TZv98repNhIDf04I6pzXePSgHjzP+JoSfWmF4
github.com/luxfi/warp v1.18.4/go.mod h1:5kXaZGYBBzOFyzPoDukVynvTTPskElrA8EQ5TVgovqc=
github.com/luxfi/warp v1.18.5/go.mod h1:SFyC529HDvbP/TWRAdYQSyJUliMa5JKFRtBrTLEElp4=
github.com/luxfi/zap v0.0.0-20260126200933-22a2a4c72dfb/go.mod h1:uvmQO9kVvOfBcqPhY52wx5Z1O6L/4Mv2tBrJDc3gVgM=
github.com/luxfi/zap v0.2.0 h1:RzvOkp3EoN5UCkpnqfObHLM1sEHy7YcxXKuermhE/VA=
github.com/luxfi/zap v0.2.0/go.mod h1:2hydPSa/XMCMtfW6/DC9M5Bt03N5h75QwCV5Vypsqr4=
github.com/luxfi/zapdb v1.0.0 h1:u8TF4g/bWew5mQwiR/Mcu+QIFYTT6BP5Ty8oBaDgalA=
github.com/luxfi/zapdb v1.0.0/go.mod h1:tT2WF2iSabQHrAYe9l7LHscaIBfN2YtCbdgOrCyCI2A=
github.com/luxfi/zapdb/v4 v4.9.1/go.mod h1:gYJjLcFCxmUATZ6Zg7iZHwAbgBMxGVP1g8UOWomK70Y=
github.com/luxfi/zapdb/v4 v4.9.3 h1:KV1DrbZqtZsZvrWyPw5D8WHR0DxzqW2jHjqx6obxgoM=
github.com/luxfi/zapdb/v4 v4.9.3/go.mod h1:BrpLaDeYy+smON4ASDPCFKWrQ58CXhy3WNEPGna7vuU=
github.com/luxfi/zmq/v4 v4.2.2/go.mod h1:omy8ymZAhx60lx/nPsWT6uOV11uNFVnjtls+Hi5KqaU=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+17 -47
View File
@@ -4,65 +4,38 @@ import (
"fmt"
"strings"
kv "github.com/hanzoai/kv-go/v9"
"github.com/hanzoai/orm"
ormdb "github.com/hanzoai/orm/db"
)
// Database holds the ORM entity store and optional KV cache.
// Database holds the ORM entity store backed by SQLite.
type Database struct {
ORM orm.DB
KV kv.UniversalClient
}
// New creates a Database. Detects backend from DSN:
// - "" or "sqlite://..." or file path → SQLite (default, zero-config)
// - "postgres://..." → PostgreSQL
//
// kvURL is optional — pass "" to skip KV cache.
func New(dsn, kvURL string) (*Database, error) {
var ormInstance orm.DB
// New creates a Database backed by SQLite.
// dsn accepts "" (defaults to mpc.db), "sqlite://path", or a ".db" file path.
// kvURL is ignored (retained for call-site compat, will be removed).
func New(dsn, _ string) (*Database, error) {
path := "mpc.db"
switch {
case dsn == "" || strings.HasPrefix(dsn, "sqlite://") || strings.HasSuffix(dsn, ".db"):
// SQLite — default, zero external deps.
path := "mpc.db"
if strings.HasPrefix(dsn, "sqlite://") {
path = strings.TrimPrefix(dsn, "sqlite://")
} else if strings.HasSuffix(dsn, ".db") {
path = dsn
}
sqliteDB, err := ormdb.NewSQLiteDB(&ormdb.SQLiteDBConfig{Path: path})
if err != nil {
return nil, fmt.Errorf("sqlite: %w", err)
}
ormInstance = orm.AdaptDB(sqliteDB)
case strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://"):
sqlDB, err := ormdb.NewSQLDB(&ormdb.SQLConfig{
DSN: dsn,
MaxConns: 20,
MinConns: 2,
})
if err != nil {
return nil, fmt.Errorf("postgres: %w", err)
}
ormInstance = orm.AdaptDB(sqlDB)
case dsn == "":
// default
case strings.HasPrefix(dsn, "sqlite://"):
path = strings.TrimPrefix(dsn, "sqlite://")
case strings.HasSuffix(dsn, ".db"):
path = dsn
default:
return nil, fmt.Errorf("unsupported DSN: %q (use sqlite://, postgres://, or .db path)", dsn)
return nil, fmt.Errorf("unsupported DSN: %q (use sqlite:// or .db path)", dsn)
}
var kvClient kv.UniversalClient
if kvURL != "" {
kvClient = kv.NewUniversalClient(&kv.UniversalOptions{
Addrs: []string{kvURL},
})
sqliteDB, err := ormdb.NewSQLiteDB(&ormdb.SQLiteDBConfig{Path: path})
if err != nil {
return nil, fmt.Errorf("sqlite: %w", err)
}
return &Database{
ORM: ormInstance,
KV: kvClient,
ORM: orm.AdaptDB(sqliteDB),
}, nil
}
@@ -71,7 +44,4 @@ func (d *Database) Close() {
if d.ORM != nil {
d.ORM.Close()
}
if d.KV != nil {
d.KV.Close()
}
}
+1 -1
View File
@@ -435,7 +435,7 @@ type DeviceEnrollment struct {
func init() { orm.Register[DeviceEnrollment]("device-enrollment") }
// PendingTrade represents a trade awaiting user biometric approval before the
// ATS shard co-signs. The trade is recorded in PostgreSQL; the MPC signing only
// ATS shard co-signs. The trade is recorded in the database; the MPC signing only
// happens after the user approves via Face ID / Touch ID on their device.
//
// Status flow: pending_approval → approved → signing → signed → settled