mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
Routine maintenance: config.go, rpcdb/service.go, node/node.go, vms/registry/registry.go, vms/rpcchainvm/runtime/subprocess/initializer.go.
265 lines
9.0 KiB
Go
265 lines
9.0 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Package rpcdb is luxd's canonical Layer C home for the rpcdb service:
|
|
// one Service implementation backed by a database.Database, with a
|
|
// transport adapter per supported wire protocol.
|
|
//
|
|
// Layered topology:
|
|
//
|
|
// Layer A — wire framing (github.com/luxfi/api/zap)
|
|
// Layer B — service spec (data carriers) (github.com/luxfi/proto/rpcdb)
|
|
// Layer C — service impl + transports (this package)
|
|
//
|
|
// One Service. Many transport adapters. Adding a new transport is a
|
|
// new file that wraps `*Service`; the storage logic stays here and
|
|
// stays orthogonal to framing concerns.
|
|
//
|
|
// Files:
|
|
// - service.go (this) — transport-neutral Service struct + methods
|
|
// - grpc_server.go (build tag `grpc`) — gRPC adapter
|
|
// - zap_server.go (default) — ZAP adapter
|
|
package rpcdb
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
|
|
"github.com/luxfi/database"
|
|
rpcdb "github.com/luxfi/proto/rpcdb"
|
|
)
|
|
|
|
// errUnknownIterator is the sentinel returned by IteratorNext / Error
|
|
// / Release when the caller hands in an iterator id that was never
|
|
// allocated (or has already been released). Transport adapters should
|
|
// surface this however their wire spec demands.
|
|
var errUnknownIterator = errors.New("rpcdb: unknown iterator")
|
|
|
|
// iterationBatchBytes is the upper bound on bytes batched into a
|
|
// single IteratorNext response. Picked to match the cevm-side
|
|
// expectation (RemoteZapDB::iterator_next reads up to ~128 KiB per
|
|
// roundtrip).
|
|
const iterationBatchBytes = 128 * 1024
|
|
|
|
// Service is the rpcdb service implementation. It holds one
|
|
// `database.Database` and a server-managed iterator pool, and exposes
|
|
// every rpcdb operation as a (request → response) method on Go data
|
|
// carriers from the proto/rpcdb wire-types package.
|
|
//
|
|
// Service is transport-agnostic: it knows nothing about gRPC, ZAP, or
|
|
// any other framing. Each transport adapter wraps `*Service` and
|
|
// translates its own wire format into these method calls.
|
|
type Service struct {
|
|
db database.Database
|
|
|
|
// iteratorMu serializes mutations of nextIteratorID and iterators.
|
|
// It does NOT serialize calls to a particular iterator — the
|
|
// underlying database.Iterator is documented as not safe for
|
|
// concurrent use, and the caller is expected to respect that.
|
|
iteratorMu sync.RWMutex
|
|
nextIteratorID uint64
|
|
iterators map[uint64]database.Iterator
|
|
}
|
|
|
|
// NewService wraps `db` so it can be served over any transport.
|
|
func NewService(db database.Database) *Service {
|
|
return &Service{
|
|
db: db,
|
|
iterators: make(map[uint64]database.Iterator),
|
|
}
|
|
}
|
|
|
|
// DB returns the underlying database. Adapters that need to surface
|
|
// transport-specific behavior (eg. health-check serialization) can
|
|
// reach in for it; everyone else uses the methods.
|
|
func (s *Service) DB() database.Database {
|
|
return s.db
|
|
}
|
|
|
|
// CloseIterators releases every server-side iterator. Adapters call
|
|
// this on shutdown so iterators don't leak past the listener.
|
|
func (s *Service) CloseIterators() {
|
|
s.iteratorMu.Lock()
|
|
defer s.iteratorMu.Unlock()
|
|
for id, it := range s.iterators {
|
|
it.Release()
|
|
delete(s.iterators, id)
|
|
}
|
|
}
|
|
|
|
// errToCode maps a database error to the wire enum.
|
|
func errToCode(err error) rpcdb.Error {
|
|
switch {
|
|
case err == nil:
|
|
return rpcdb.Error_ERROR_UNSPECIFIED
|
|
case errors.Is(err, database.ErrNotFound):
|
|
return rpcdb.Error_ERROR_NOT_FOUND
|
|
case errors.Is(err, database.ErrClosed):
|
|
return rpcdb.Error_ERROR_CLOSED
|
|
default:
|
|
// Any other error is reported as CLOSED on the wire — the
|
|
// proto enum doesn't have a richer error space. Adapters that
|
|
// can carry a transport-level error message should also pass
|
|
// the original err out-of-band.
|
|
return rpcdb.Error_ERROR_CLOSED
|
|
}
|
|
}
|
|
|
|
// CodeToErr maps a wire-typed Error back to a database sentinel. Used
|
|
// by remote clients on the receive side; lives here so server and
|
|
// client agree on the inverse of errToCode.
|
|
func CodeToErr(code rpcdb.Error) error {
|
|
switch code {
|
|
case rpcdb.Error_ERROR_UNSPECIFIED:
|
|
return nil
|
|
case rpcdb.Error_ERROR_NOT_FOUND:
|
|
return database.ErrNotFound
|
|
case rpcdb.Error_ERROR_CLOSED:
|
|
return database.ErrClosed
|
|
default:
|
|
return database.ErrClosed
|
|
}
|
|
}
|
|
|
|
// Has services rpcdb.Has.
|
|
func (s *Service) Has(_ context.Context, req *rpcdb.HasRequest) (*rpcdb.HasResponse, error) {
|
|
has, err := s.db.Has(req.Key)
|
|
if err != nil {
|
|
return &rpcdb.HasResponse{Has: false, Err: errToCode(err)}, nil
|
|
}
|
|
return &rpcdb.HasResponse{Has: has, Err: rpcdb.Error_ERROR_UNSPECIFIED}, nil
|
|
}
|
|
|
|
// Get services rpcdb.Get.
|
|
func (s *Service) Get(_ context.Context, req *rpcdb.GetRequest) (*rpcdb.GetResponse, error) {
|
|
value, err := s.db.Get(req.Key)
|
|
if err != nil {
|
|
return &rpcdb.GetResponse{Value: nil, Err: errToCode(err)}, nil
|
|
}
|
|
return &rpcdb.GetResponse{Value: value, Err: rpcdb.Error_ERROR_UNSPECIFIED}, nil
|
|
}
|
|
|
|
// Put services rpcdb.Put.
|
|
func (s *Service) Put(_ context.Context, req *rpcdb.PutRequest) (*rpcdb.PutResponse, error) {
|
|
return &rpcdb.PutResponse{Err: errToCode(s.db.Put(req.Key, req.Value))}, nil
|
|
}
|
|
|
|
// Delete services rpcdb.Delete.
|
|
func (s *Service) Delete(_ context.Context, req *rpcdb.DeleteRequest) (*rpcdb.DeleteResponse, error) {
|
|
return &rpcdb.DeleteResponse{Err: errToCode(s.db.Delete(req.Key))}, nil
|
|
}
|
|
|
|
// WriteBatch services rpcdb.WriteBatch.
|
|
func (s *Service) WriteBatch(_ context.Context, req *rpcdb.WriteBatchRequest) (*rpcdb.WriteBatchResponse, error) {
|
|
batch := s.db.NewBatch()
|
|
for _, p := range req.Puts {
|
|
if err := batch.Put(p.Key, p.Value); err != nil {
|
|
return &rpcdb.WriteBatchResponse{Err: errToCode(err)}, nil
|
|
}
|
|
}
|
|
for _, d := range req.Deletes {
|
|
if err := batch.Delete(d.Key); err != nil {
|
|
return &rpcdb.WriteBatchResponse{Err: errToCode(err)}, nil
|
|
}
|
|
}
|
|
return &rpcdb.WriteBatchResponse{Err: errToCode(batch.Write())}, nil
|
|
}
|
|
|
|
// Compact services rpcdb.Compact.
|
|
func (s *Service) Compact(_ context.Context, req *rpcdb.CompactRequest) (*rpcdb.CompactResponse, error) {
|
|
return &rpcdb.CompactResponse{Err: errToCode(s.db.Compact(req.Start, req.Limit))}, nil
|
|
}
|
|
|
|
// Close services rpcdb.Close.
|
|
func (s *Service) Close(_ context.Context, _ *rpcdb.CloseRequest) (*rpcdb.CloseResponse, error) {
|
|
return &rpcdb.CloseResponse{Err: errToCode(s.db.Close())}, nil
|
|
}
|
|
|
|
// HealthCheck services rpcdb.HealthCheck.
|
|
func (s *Service) HealthCheck(ctx context.Context) (*rpcdb.HealthCheckResponse, error) {
|
|
health, err := s.db.HealthCheck(ctx)
|
|
if err != nil {
|
|
return &rpcdb.HealthCheckResponse{Details: []byte(err.Error())}, nil
|
|
}
|
|
details := []byte("healthy")
|
|
if health != nil {
|
|
if str, ok := health.(string); ok {
|
|
details = []byte(str)
|
|
}
|
|
}
|
|
return &rpcdb.HealthCheckResponse{Details: details}, nil
|
|
}
|
|
|
|
// NewIteratorWithStartAndPrefix services rpcdb.NewIteratorWithStartAndPrefix.
|
|
func (s *Service) NewIteratorWithStartAndPrefix(_ context.Context, req *rpcdb.NewIteratorWithStartAndPrefixRequest) (*rpcdb.NewIteratorWithStartAndPrefixResponse, error) {
|
|
it := s.db.NewIteratorWithStartAndPrefix(req.Start, req.Prefix)
|
|
s.iteratorMu.Lock()
|
|
id := s.nextIteratorID
|
|
s.nextIteratorID++
|
|
s.iterators[id] = it
|
|
s.iteratorMu.Unlock()
|
|
return &rpcdb.NewIteratorWithStartAndPrefixResponse{Id: id}, nil
|
|
}
|
|
|
|
// IteratorNext services rpcdb.IteratorNext, returning up to
|
|
// iterationBatchBytes worth of (key, value) pairs per call. An empty
|
|
// Data slice signals exhaustion.
|
|
//
|
|
// Returned bytes are deep-copied so the caller can hold them past the
|
|
// next iterator advance; the underlying database.Iterator is allowed
|
|
// to recycle its internal buffers.
|
|
func (s *Service) IteratorNext(_ context.Context, req *rpcdb.IteratorNextRequest) (*rpcdb.IteratorNextResponse, error) {
|
|
s.iteratorMu.RLock()
|
|
it, ok := s.iterators[req.Id]
|
|
s.iteratorMu.RUnlock()
|
|
if !ok {
|
|
return nil, errUnknownIterator
|
|
}
|
|
|
|
var (
|
|
size int
|
|
data []*rpcdb.PutRequest
|
|
)
|
|
for size < iterationBatchBytes && it.Next() {
|
|
k := it.Key()
|
|
v := it.Value()
|
|
size += len(k) + len(v)
|
|
|
|
kc := make([]byte, len(k))
|
|
copy(kc, k)
|
|
vc := make([]byte, len(v))
|
|
copy(vc, v)
|
|
data = append(data, &rpcdb.PutRequest{Key: kc, Value: vc})
|
|
}
|
|
return &rpcdb.IteratorNextResponse{Data: data}, nil
|
|
}
|
|
|
|
// IteratorError services rpcdb.IteratorError.
|
|
func (s *Service) IteratorError(_ context.Context, req *rpcdb.IteratorErrorRequest) (*rpcdb.IteratorErrorResponse, error) {
|
|
s.iteratorMu.RLock()
|
|
it, ok := s.iterators[req.Id]
|
|
s.iteratorMu.RUnlock()
|
|
if !ok {
|
|
return &rpcdb.IteratorErrorResponse{Err: rpcdb.Error_ERROR_NOT_FOUND}, nil
|
|
}
|
|
return &rpcdb.IteratorErrorResponse{Err: errToCode(it.Error())}, nil
|
|
}
|
|
|
|
// IteratorRelease services rpcdb.IteratorRelease. Idempotent: calling
|
|
// it twice for the same id returns UNSPECIFIED on the second call.
|
|
func (s *Service) IteratorRelease(_ context.Context, req *rpcdb.IteratorReleaseRequest) (*rpcdb.IteratorReleaseResponse, error) {
|
|
s.iteratorMu.Lock()
|
|
it, ok := s.iterators[req.Id]
|
|
if !ok {
|
|
s.iteratorMu.Unlock()
|
|
return &rpcdb.IteratorReleaseResponse{Err: rpcdb.Error_ERROR_UNSPECIFIED}, nil
|
|
}
|
|
delete(s.iterators, req.Id)
|
|
s.iteratorMu.Unlock()
|
|
|
|
dbErr := it.Error()
|
|
it.Release()
|
|
return &rpcdb.IteratorReleaseResponse{Err: errToCode(dbErr)}, nil
|
|
}
|