mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
The module already called itself github.com/luxfi/node while containing no node in Go: cmd/luxd was twenty-one lines that printed the size of a few types, and the Go column of the matrix measured a VM plugin. A repo that fetches the thing it claims to be has not replaced it, so the Go sources move in — 1515 files, the daemon and everything under it — and the build reaches outside this module for nothing. Rust and C++ sit beside it unchanged and are still measured against it by the differential. That is the point of one repo: the reference implementation and the two ports are the same checkout, so parity is a thing you can run rather than a thing you assert. Verified: builds, and the closure carries no lux-private — checked at build time rather than believed. The daemon starts twelve chains on a local network (P X C Q Z A B G K M F, plus D through its plugin) and reports healthy.
408 lines
13 KiB
Go
408 lines
13 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package rpcdb
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"sync"
|
|
|
|
zapwire "github.com/luxfi/api/zap"
|
|
"github.com/luxfi/database"
|
|
rpcdb "github.com/luxfi/proto/rpcdb"
|
|
)
|
|
|
|
// ZAP db channel MsgType IDs. These are the wire-level Layer-A
|
|
// dispatch tags for the rpcdb service over ZAP. They live in their
|
|
// own listener (one per VM plugin); they do NOT collide with the VM
|
|
// lifecycle MsgTypes (1..31) or sender (40..49) or warp (50..59)
|
|
// because each listener has its own dispatch table.
|
|
//
|
|
// Order is the canonical Lane A assignment — the cevm side hard-codes
|
|
// these numbers in `RemoteZapDB::call`. Do not reorder.
|
|
const (
|
|
MsgDBHas zapwire.MessageType = 1
|
|
MsgDBGet zapwire.MessageType = 2
|
|
MsgDBPut zapwire.MessageType = 3
|
|
MsgDBDelete zapwire.MessageType = 4
|
|
MsgDBWriteBatch zapwire.MessageType = 5
|
|
MsgDBCompact zapwire.MessageType = 6
|
|
MsgDBClose zapwire.MessageType = 7
|
|
MsgDBHealthCheck zapwire.MessageType = 8
|
|
MsgDBIteratorNew zapwire.MessageType = 9
|
|
MsgDBIteratorNext zapwire.MessageType = 10
|
|
MsgDBIteratorError zapwire.MessageType = 11
|
|
MsgDBIteratorRelease zapwire.MessageType = 12
|
|
)
|
|
|
|
// Wire-byte values for the Error enum. These match
|
|
// rpcdb.Error_ERROR_* but are encoded as a single byte on the ZAP
|
|
// wire so the cevm side can decode them without pulling in protobuf.
|
|
const (
|
|
dbErrUnspecified uint8 = 0
|
|
dbErrClosed uint8 = 1
|
|
dbErrNotFound uint8 = 2
|
|
)
|
|
|
|
func errCodeToByte(code rpcdb.Error) uint8 {
|
|
switch code {
|
|
case rpcdb.Error_ERROR_CLOSED:
|
|
return dbErrClosed
|
|
case rpcdb.Error_ERROR_NOT_FOUND:
|
|
return dbErrNotFound
|
|
default:
|
|
return dbErrUnspecified
|
|
}
|
|
}
|
|
|
|
// ZAPServer is the ZAP transport adapter for the rpcdb Service. It
|
|
// owns the listener and the dispatch loop; the actual storage logic
|
|
// lives in *Service. To swap the wire format (eg. to add framing
|
|
// over a different reliable byte stream) write a new file with a new
|
|
// adapter — never edit Service.
|
|
type ZAPServer struct {
|
|
svc *Service
|
|
|
|
listener *zapwire.Listener
|
|
server *zapwire.Server
|
|
|
|
// closeOnce guards Close so callers can call it from both the
|
|
// client lifecycle and a deferred test cleanup without panicking
|
|
// on a double-close of the underlying listener.
|
|
closeOnce sync.Once
|
|
}
|
|
|
|
// NewZAPServer wraps a database.Database for serving over ZAP.
|
|
//
|
|
// Equivalent to NewZAPServerFromService(NewService(db)) — kept as a
|
|
// one-liner because cevm-side test fixtures and the production
|
|
// rpcchainvm/zap path both build it this way.
|
|
func NewZAPServer(db database.Database) *ZAPServer {
|
|
return NewZAPServerFromService(NewService(db))
|
|
}
|
|
|
|
// NewZAPServerFromService wraps an existing Service for serving over
|
|
// ZAP. Useful when a single Service needs multiple transport adapters
|
|
// at once (eg. tests that want both ZAP and direct in-process calls).
|
|
func NewZAPServerFromService(svc *Service) *ZAPServer {
|
|
return &ZAPServer{svc: svc}
|
|
}
|
|
|
|
// Listen binds the ZAP listener to addr.
|
|
func (s *ZAPServer) Listen(addr string) error {
|
|
listener, err := zapwire.Listen(addr, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("rpcdb zap: listen %s: %w", addr, err)
|
|
}
|
|
s.listener = listener
|
|
s.server = zapwire.NewServer(listener, zapwire.HandlerFunc(s.handle))
|
|
return nil
|
|
}
|
|
|
|
// ListenOn wraps an existing net.Listener (for ephemeral ports etc.).
|
|
func (s *ZAPServer) ListenOn(raw net.Listener) {
|
|
s.listener = zapwire.NewListener(raw, nil)
|
|
s.server = zapwire.NewServer(s.listener, zapwire.HandlerFunc(s.handle))
|
|
}
|
|
|
|
// Addr returns the bound address (or nil if not yet listening).
|
|
func (s *ZAPServer) Addr() net.Addr {
|
|
if s.listener == nil {
|
|
return nil
|
|
}
|
|
return s.listener.Addr()
|
|
}
|
|
|
|
// Serve blocks until ctx is cancelled or Close is called.
|
|
func (s *ZAPServer) Serve(ctx context.Context) error {
|
|
if s.server == nil {
|
|
return errors.New("rpcdb zap: not initialized — call Listen first")
|
|
}
|
|
return s.server.Serve(ctx)
|
|
}
|
|
|
|
// Close releases all iterators and closes the listener. Caller is
|
|
// expected to also cancel the context passed to Serve so the accept
|
|
// loop exits cleanly. Safe to call multiple times.
|
|
//
|
|
// We deliberately do NOT call s.server.Close() because the upstream
|
|
// zapwire.Server.Close races with in-flight accept (it nils its conns
|
|
// map mid-Serve, causing "assignment to entry in nil map"). Closing
|
|
// the listener is enough to make Accept return; ctx cancellation does
|
|
// the rest.
|
|
func (s *ZAPServer) Close() error {
|
|
var err error
|
|
s.closeOnce.Do(func() {
|
|
s.svc.CloseIterators()
|
|
if s.listener != nil {
|
|
err = s.listener.Close()
|
|
}
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (s *ZAPServer) handle(ctx context.Context, msgType zapwire.MessageType, payload []byte) (zapwire.MessageType, []byte, error) {
|
|
switch msgType {
|
|
case MsgDBHas:
|
|
return s.handleHas(ctx, payload)
|
|
case MsgDBGet:
|
|
return s.handleGet(ctx, payload)
|
|
case MsgDBPut:
|
|
return s.handlePut(ctx, payload)
|
|
case MsgDBDelete:
|
|
return s.handleDelete(ctx, payload)
|
|
case MsgDBWriteBatch:
|
|
return s.handleWriteBatch(ctx, payload)
|
|
case MsgDBCompact:
|
|
return s.handleCompact(ctx, payload)
|
|
case MsgDBClose:
|
|
return s.handleClose(ctx, payload)
|
|
case MsgDBHealthCheck:
|
|
return s.handleHealthCheck(ctx)
|
|
case MsgDBIteratorNew:
|
|
return s.handleIteratorNew(ctx, payload)
|
|
case MsgDBIteratorNext:
|
|
return s.handleIteratorNext(ctx, payload)
|
|
case MsgDBIteratorError:
|
|
return s.handleIteratorError(ctx, payload)
|
|
case MsgDBIteratorRelease:
|
|
return s.handleIteratorRelease(ctx, payload)
|
|
default:
|
|
return 0, nil, fmt.Errorf("rpcdb zap: unknown msg type %d", msgType)
|
|
}
|
|
}
|
|
|
|
func (s *ZAPServer) handleHas(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
|
r := zapwire.NewReader(payload)
|
|
key, err := r.ReadBytes()
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("rpcdb Has decode: %w", err)
|
|
}
|
|
resp, err := s.svc.Has(ctx, &rpcdb.HasRequest{Key: key})
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
if resp.Has {
|
|
buf.WriteUint8(1)
|
|
} else {
|
|
buf.WriteUint8(0)
|
|
}
|
|
buf.WriteUint8(errCodeToByte(resp.Err))
|
|
return MsgDBHas, append([]byte(nil), buf.Bytes()...), nil
|
|
}
|
|
|
|
func (s *ZAPServer) handleGet(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
|
r := zapwire.NewReader(payload)
|
|
key, err := r.ReadBytes()
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("rpcdb Get decode: %w", err)
|
|
}
|
|
resp, err := s.svc.Get(ctx, &rpcdb.GetRequest{Key: key})
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
buf.WriteBytes(resp.Value)
|
|
buf.WriteUint8(errCodeToByte(resp.Err))
|
|
return MsgDBGet, append([]byte(nil), buf.Bytes()...), nil
|
|
}
|
|
|
|
func (s *ZAPServer) handlePut(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
|
r := zapwire.NewReader(payload)
|
|
key, err := r.ReadBytes()
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("rpcdb Put decode key: %w", err)
|
|
}
|
|
value, err := r.ReadBytes()
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("rpcdb Put decode value: %w", err)
|
|
}
|
|
resp, err := s.svc.Put(ctx, &rpcdb.PutRequest{Key: key, Value: value})
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
buf.WriteUint8(errCodeToByte(resp.Err))
|
|
return MsgDBPut, append([]byte(nil), buf.Bytes()...), nil
|
|
}
|
|
|
|
func (s *ZAPServer) handleDelete(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
|
r := zapwire.NewReader(payload)
|
|
key, err := r.ReadBytes()
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("rpcdb Delete decode: %w", err)
|
|
}
|
|
resp, err := s.svc.Delete(ctx, &rpcdb.DeleteRequest{Key: key})
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
buf.WriteUint8(errCodeToByte(resp.Err))
|
|
return MsgDBDelete, append([]byte(nil), buf.Bytes()...), nil
|
|
}
|
|
|
|
func (s *ZAPServer) handleWriteBatch(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
|
r := zapwire.NewReader(payload)
|
|
|
|
nputs, err := r.ReadUint32()
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("rpcdb WriteBatch decode nputs: %w", err)
|
|
}
|
|
puts := make([]*rpcdb.PutRequest, 0, nputs)
|
|
for i := uint32(0); i < nputs; i++ {
|
|
key, err := r.ReadBytes()
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("rpcdb WriteBatch put[%d] key: %w", i, err)
|
|
}
|
|
value, err := r.ReadBytes()
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("rpcdb WriteBatch put[%d] value: %w", i, err)
|
|
}
|
|
puts = append(puts, &rpcdb.PutRequest{Key: key, Value: value})
|
|
}
|
|
|
|
ndels, err := r.ReadUint32()
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("rpcdb WriteBatch decode ndels: %w", err)
|
|
}
|
|
dels := make([]*rpcdb.DeleteRequest, 0, ndels)
|
|
for i := uint32(0); i < ndels; i++ {
|
|
key, err := r.ReadBytes()
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("rpcdb WriteBatch del[%d]: %w", i, err)
|
|
}
|
|
dels = append(dels, &rpcdb.DeleteRequest{Key: key})
|
|
}
|
|
|
|
resp, err := s.svc.WriteBatch(ctx, &rpcdb.WriteBatchRequest{Puts: puts, Deletes: dels})
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
buf.WriteUint8(errCodeToByte(resp.Err))
|
|
return MsgDBWriteBatch, append([]byte(nil), buf.Bytes()...), nil
|
|
}
|
|
|
|
func (s *ZAPServer) handleCompact(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
|
r := zapwire.NewReader(payload)
|
|
start, err := r.ReadBytes()
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("rpcdb Compact decode start: %w", err)
|
|
}
|
|
limit, err := r.ReadBytes()
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("rpcdb Compact decode limit: %w", err)
|
|
}
|
|
resp, err := s.svc.Compact(ctx, &rpcdb.CompactRequest{Start: start, Limit: limit})
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
buf.WriteUint8(errCodeToByte(resp.Err))
|
|
return MsgDBCompact, append([]byte(nil), buf.Bytes()...), nil
|
|
}
|
|
|
|
func (s *ZAPServer) handleClose(ctx context.Context, _ []byte) (zapwire.MessageType, []byte, error) {
|
|
resp, err := s.svc.Close(ctx, &rpcdb.CloseRequest{})
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
buf.WriteUint8(errCodeToByte(resp.Err))
|
|
return MsgDBClose, append([]byte(nil), buf.Bytes()...), nil
|
|
}
|
|
|
|
func (s *ZAPServer) handleHealthCheck(ctx context.Context) (zapwire.MessageType, []byte, error) {
|
|
resp, err := s.svc.HealthCheck(ctx)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
buf.WriteBytes(resp.Details)
|
|
return MsgDBHealthCheck, append([]byte(nil), buf.Bytes()...), nil
|
|
}
|
|
|
|
func (s *ZAPServer) handleIteratorNew(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
|
r := zapwire.NewReader(payload)
|
|
start, err := r.ReadBytes()
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("rpcdb IteratorNew decode start: %w", err)
|
|
}
|
|
prefix, err := r.ReadBytes()
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("rpcdb IteratorNew decode prefix: %w", err)
|
|
}
|
|
resp, err := s.svc.NewIteratorWithStartAndPrefix(ctx, &rpcdb.NewIteratorWithStartAndPrefixRequest{Start: start, Prefix: prefix})
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
buf.WriteUint64(resp.Id)
|
|
return MsgDBIteratorNew, append([]byte(nil), buf.Bytes()...), nil
|
|
}
|
|
|
|
func (s *ZAPServer) handleIteratorNext(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
|
r := zapwire.NewReader(payload)
|
|
id, err := r.ReadUint64()
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("rpcdb IteratorNext decode id: %w", err)
|
|
}
|
|
resp, err := s.svc.IteratorNext(ctx, &rpcdb.IteratorNextRequest{Id: id})
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
buf.WriteUint32(uint32(len(resp.Data)))
|
|
for _, e := range resp.Data {
|
|
buf.WriteBytes(e.Key)
|
|
buf.WriteBytes(e.Value)
|
|
}
|
|
return MsgDBIteratorNext, append([]byte(nil), buf.Bytes()...), nil
|
|
}
|
|
|
|
func (s *ZAPServer) handleIteratorError(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
|
r := zapwire.NewReader(payload)
|
|
id, err := r.ReadUint64()
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("rpcdb IteratorError decode id: %w", err)
|
|
}
|
|
resp, err := s.svc.IteratorError(ctx, &rpcdb.IteratorErrorRequest{Id: id})
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
buf.WriteUint8(errCodeToByte(resp.Err))
|
|
return MsgDBIteratorError, append([]byte(nil), buf.Bytes()...), nil
|
|
}
|
|
|
|
func (s *ZAPServer) handleIteratorRelease(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
|
r := zapwire.NewReader(payload)
|
|
id, err := r.ReadUint64()
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("rpcdb IteratorRelease decode id: %w", err)
|
|
}
|
|
resp, err := s.svc.IteratorRelease(ctx, &rpcdb.IteratorReleaseRequest{Id: id})
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
buf.WriteUint8(errCodeToByte(resp.Err))
|
|
return MsgDBIteratorRelease, append([]byte(nil), buf.Bytes()...), nil
|
|
}
|