mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
rpcdb: decomplect into Layer A/B/C topology
Layer A — wire framing: github.com/luxfi/api/zap (unchanged) Layer B — service spec: github.com/luxfi/proto/rpcdb (transport-agnostic data carriers, was node/proto/zap/rpcdb) Layer C — service impl + transports: node/db/rpcdb/ - service.go transport-neutral Service wrapping database.Database - zap_server.go ZAP transport adapter (default; used by cevm) - grpc_server.go gRPC transport adapter (-tags=grpc) One Service. Many transport adapters. Adding a transport is a new adapter file wrapping *Service; storage logic stays in service.go. Removed (-1390 LOC of duplicate/dead code): - node/proto/zap/rpcdb/rpcdb.go (moved to luxfi/proto/rpcdb) - node/proto/rpcdb/rpcdb_zap.go (dead HandlerRegistry path, no callers) - node/proto/rpcdb/rpcdb_grpc.go (duplicated gRPC server, replaced by node/db/rpcdb/grpc_server.go) - node/db/rpcdb/db.go (re-export shim, replaced by Service) Consumers updated to import the canonical adapter at node/db/rpcdb: - vms/rpcchainvm/zap/client.go - vms/rpcchainvm/zap/client_dbserver_test.go - vms/rpcchainvm/zap/cevm_e2e_test.go go.mod: add `replace github.com/luxfi/proto => ../proto` for in-tree development of the central wire-types module. Tests: db/rpcdb (3/3) + rpcchainvm/zap (4/4 incl. cevm cross-process e2e — KP_META written via ZAP db channel, no fallback to local zapdb).
This commit is contained in:
@@ -1,548 +0,0 @@
|
||||
//go:build grpc
|
||||
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package rpcdb provides database RPC functionality
|
||||
package rpcdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/luxfi/database"
|
||||
rpcdbpb "github.com/luxfi/node/proto/pb/rpcdb"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
var (
|
||||
_ database.Database = (*DatabaseClient)(nil)
|
||||
_ rpcdbpb.DatabaseServer = (*DatabaseServer)(nil)
|
||||
)
|
||||
|
||||
// DatabaseServer is a database server that listens over RPC.
|
||||
type DatabaseServer struct {
|
||||
rpcdbpb.UnimplementedDatabaseServer
|
||||
db database.Database
|
||||
|
||||
iteratorLock sync.RWMutex
|
||||
nextIteratorID uint64
|
||||
iterators map[uint64]database.Iterator
|
||||
}
|
||||
|
||||
// NewServer returns a new database server
|
||||
func NewServer(db database.Database) *DatabaseServer {
|
||||
return &DatabaseServer{
|
||||
db: db,
|
||||
iterators: make(map[uint64]database.Iterator),
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DatabaseServer) Has(ctx context.Context, req *rpcdbpb.HasRequest) (*rpcdbpb.HasResponse, error) {
|
||||
has, err := db.db.Has(req.Key)
|
||||
if err != nil {
|
||||
return &rpcdbpb.HasResponse{
|
||||
Has: false,
|
||||
Err: errorToRPCError(err),
|
||||
}, nil
|
||||
}
|
||||
return &rpcdbpb.HasResponse{
|
||||
Has: has,
|
||||
Err: rpcdbpb.Error_ERROR_UNSPECIFIED,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *DatabaseServer) Get(ctx context.Context, req *rpcdbpb.GetRequest) (*rpcdbpb.GetResponse, error) {
|
||||
value, err := db.db.Get(req.Key)
|
||||
if err != nil {
|
||||
return &rpcdbpb.GetResponse{
|
||||
Value: nil,
|
||||
Err: errorToRPCError(err),
|
||||
}, nil
|
||||
}
|
||||
return &rpcdbpb.GetResponse{
|
||||
Value: value,
|
||||
Err: rpcdbpb.Error_ERROR_UNSPECIFIED,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *DatabaseServer) Put(ctx context.Context, req *rpcdbpb.PutRequest) (*rpcdbpb.PutResponse, error) {
|
||||
err := db.db.Put(req.Key, req.Value)
|
||||
return &rpcdbpb.PutResponse{
|
||||
Err: errorToRPCError(err),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *DatabaseServer) Delete(ctx context.Context, req *rpcdbpb.DeleteRequest) (*rpcdbpb.DeleteResponse, error) {
|
||||
err := db.db.Delete(req.Key)
|
||||
return &rpcdbpb.DeleteResponse{
|
||||
Err: errorToRPCError(err),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *DatabaseServer) WriteBatch(ctx context.Context, req *rpcdbpb.WriteBatchRequest) (*rpcdbpb.WriteBatchResponse, error) {
|
||||
batch := db.db.NewBatch()
|
||||
|
||||
for _, put := range req.Puts {
|
||||
if err := batch.Put(put.Key, put.Value); err != nil {
|
||||
return &rpcdbpb.WriteBatchResponse{
|
||||
Err: errorToRPCError(err),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, del := range req.Deletes {
|
||||
if err := batch.Delete(del.Key); err != nil {
|
||||
return &rpcdbpb.WriteBatchResponse{
|
||||
Err: errorToRPCError(err),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
err := batch.Write()
|
||||
return &rpcdbpb.WriteBatchResponse{
|
||||
Err: errorToRPCError(err),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *DatabaseServer) Compact(ctx context.Context, req *rpcdbpb.CompactRequest) (*rpcdbpb.CompactResponse, error) {
|
||||
err := db.db.Compact(req.Start, req.Limit)
|
||||
return &rpcdbpb.CompactResponse{
|
||||
Err: errorToRPCError(err),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *DatabaseServer) Close(ctx context.Context, req *rpcdbpb.CloseRequest) (*rpcdbpb.CloseResponse, error) {
|
||||
err := db.db.Close()
|
||||
return &rpcdbpb.CloseResponse{
|
||||
Err: errorToRPCError(err),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *DatabaseServer) HealthCheck(ctx context.Context, req *emptypb.Empty) (*rpcdbpb.HealthCheckResponse, error) {
|
||||
health, err := db.db.HealthCheck(ctx)
|
||||
if err != nil {
|
||||
return &rpcdbpb.HealthCheckResponse{
|
||||
Details: []byte(err.Error()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Convert health data to bytes
|
||||
healthBytes := []byte("healthy")
|
||||
if health != nil {
|
||||
if str, ok := health.(string); ok {
|
||||
healthBytes = []byte(str)
|
||||
}
|
||||
}
|
||||
|
||||
return &rpcdbpb.HealthCheckResponse{
|
||||
Details: healthBytes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *DatabaseServer) NewIteratorWithStartAndPrefix(ctx context.Context, req *rpcdbpb.NewIteratorWithStartAndPrefixRequest) (*rpcdbpb.NewIteratorWithStartAndPrefixResponse, error) {
|
||||
it := db.db.NewIteratorWithStartAndPrefix(req.Start, req.Prefix)
|
||||
|
||||
db.iteratorLock.Lock()
|
||||
id := db.nextIteratorID
|
||||
db.nextIteratorID++
|
||||
db.iterators[id] = it
|
||||
db.iteratorLock.Unlock()
|
||||
|
||||
return &rpcdbpb.NewIteratorWithStartAndPrefixResponse{
|
||||
Id: id,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *DatabaseServer) IteratorNext(ctx context.Context, req *rpcdbpb.IteratorNextRequest) (*rpcdbpb.IteratorNextResponse, error) {
|
||||
db.iteratorLock.RLock()
|
||||
it, ok := db.iterators[req.Id]
|
||||
db.iteratorLock.RUnlock()
|
||||
|
||||
if !ok {
|
||||
return &rpcdbpb.IteratorNextResponse{
|
||||
Data: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Collect data until we have a reasonable batch or iterator is exhausted
|
||||
var data []*rpcdbpb.PutRequest
|
||||
const maxBatchSize = 100
|
||||
|
||||
for i := 0; i < maxBatchSize && it.Next(); i++ {
|
||||
data = append(data, &rpcdbpb.PutRequest{
|
||||
Key: it.Key(),
|
||||
Value: it.Value(),
|
||||
})
|
||||
}
|
||||
|
||||
return &rpcdbpb.IteratorNextResponse{
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *DatabaseServer) IteratorError(ctx context.Context, req *rpcdbpb.IteratorErrorRequest) (*rpcdbpb.IteratorErrorResponse, error) {
|
||||
db.iteratorLock.RLock()
|
||||
it, ok := db.iterators[req.Id]
|
||||
db.iteratorLock.RUnlock()
|
||||
|
||||
if !ok {
|
||||
return &rpcdbpb.IteratorErrorResponse{
|
||||
Err: rpcdbpb.Error_ERROR_NOT_FOUND,
|
||||
}, nil
|
||||
}
|
||||
|
||||
err := it.Error()
|
||||
return &rpcdbpb.IteratorErrorResponse{
|
||||
Err: errorToRPCError(err),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *DatabaseServer) IteratorRelease(ctx context.Context, req *rpcdbpb.IteratorReleaseRequest) (*rpcdbpb.IteratorReleaseResponse, error) {
|
||||
db.iteratorLock.Lock()
|
||||
it, ok := db.iterators[req.Id]
|
||||
if ok {
|
||||
it.Release()
|
||||
delete(db.iterators, req.Id)
|
||||
}
|
||||
db.iteratorLock.Unlock()
|
||||
|
||||
var err rpcdbpb.Error
|
||||
if !ok {
|
||||
err = rpcdbpb.Error_ERROR_NOT_FOUND
|
||||
} else {
|
||||
err = rpcdbpb.Error_ERROR_UNSPECIFIED
|
||||
}
|
||||
|
||||
return &rpcdbpb.IteratorReleaseResponse{
|
||||
Err: err,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DatabaseClient is a database client
|
||||
type DatabaseClient struct {
|
||||
client rpcdbpb.DatabaseClient
|
||||
}
|
||||
|
||||
// NewClient returns a new database client
|
||||
func NewClient(client rpcdbpb.DatabaseClient) *DatabaseClient {
|
||||
return &DatabaseClient{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DatabaseClient) Has(key []byte) (bool, error) {
|
||||
resp, err := db.client.Has(context.Background(), &rpcdbpb.HasRequest{
|
||||
Key: key,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if resp.Err != rpcdbpb.Error_ERROR_UNSPECIFIED {
|
||||
return false, rpcErrorToError(resp.Err)
|
||||
}
|
||||
return resp.Has, nil
|
||||
}
|
||||
|
||||
func (db *DatabaseClient) Get(key []byte) ([]byte, error) {
|
||||
resp, err := db.client.Get(context.Background(), &rpcdbpb.GetRequest{
|
||||
Key: key,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Err != rpcdbpb.Error_ERROR_UNSPECIFIED {
|
||||
return nil, rpcErrorToError(resp.Err)
|
||||
}
|
||||
return resp.Value, nil
|
||||
}
|
||||
|
||||
func (db *DatabaseClient) Put(key []byte, value []byte) error {
|
||||
resp, err := db.client.Put(context.Background(), &rpcdbpb.PutRequest{
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Err != rpcdbpb.Error_ERROR_UNSPECIFIED {
|
||||
return rpcErrorToError(resp.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DatabaseClient) Delete(key []byte) error {
|
||||
resp, err := db.client.Delete(context.Background(), &rpcdbpb.DeleteRequest{
|
||||
Key: key,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Err != rpcdbpb.Error_ERROR_UNSPECIFIED {
|
||||
return rpcErrorToError(resp.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DatabaseClient) NewBatch() database.Batch {
|
||||
return &batch{
|
||||
db: db,
|
||||
puts: []*rpcdbpb.PutRequest{},
|
||||
deletes: []*rpcdbpb.DeleteRequest{},
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DatabaseClient) NewIterator() database.Iterator {
|
||||
return db.NewIteratorWithStartAndPrefix(nil, nil)
|
||||
}
|
||||
|
||||
func (db *DatabaseClient) NewIteratorWithStart(start []byte) database.Iterator {
|
||||
return db.NewIteratorWithStartAndPrefix(start, nil)
|
||||
}
|
||||
|
||||
func (db *DatabaseClient) NewIteratorWithPrefix(prefix []byte) database.Iterator {
|
||||
return db.NewIteratorWithStartAndPrefix(nil, prefix)
|
||||
}
|
||||
|
||||
func (db *DatabaseClient) NewIteratorWithStartAndPrefix(start []byte, prefix []byte) database.Iterator {
|
||||
resp, err := db.client.NewIteratorWithStartAndPrefix(context.Background(), &rpcdbpb.NewIteratorWithStartAndPrefixRequest{
|
||||
Start: start,
|
||||
Prefix: prefix,
|
||||
})
|
||||
if err != nil {
|
||||
return &iterator{
|
||||
db: db,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
return &iterator{
|
||||
db: db,
|
||||
id: resp.Id,
|
||||
}
|
||||
}
|
||||
|
||||
// Backup is not supported over the RPC database client.
|
||||
func (db *DatabaseClient) Backup(_ io.Writer, _ uint64) (uint64, error) {
|
||||
return 0, errors.New("rpcdb: backup not supported")
|
||||
}
|
||||
|
||||
// Load is not supported over the RPC database client.
|
||||
func (db *DatabaseClient) Load(_ io.Reader) error {
|
||||
return errors.New("rpcdb: load not supported")
|
||||
}
|
||||
|
||||
func (db *DatabaseClient) Compact(start []byte, limit []byte) error {
|
||||
resp, err := db.client.Compact(context.Background(), &rpcdbpb.CompactRequest{
|
||||
Start: start,
|
||||
Limit: limit,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Err != rpcdbpb.Error_ERROR_UNSPECIFIED {
|
||||
return rpcErrorToError(resp.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DatabaseClient) Sync() error {
|
||||
// RPC database sync is a no-op on client side
|
||||
// The server handles persistence
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DatabaseClient) Close() error {
|
||||
resp, err := db.client.Close(context.Background(), &rpcdbpb.CloseRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Err != rpcdbpb.Error_ERROR_UNSPECIFIED {
|
||||
return rpcErrorToError(resp.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DatabaseClient) HealthCheck(ctx context.Context) (interface{}, error) {
|
||||
resp, err := db.client.HealthCheck(ctx, &emptypb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Details, nil
|
||||
}
|
||||
|
||||
type batch struct {
|
||||
db *DatabaseClient
|
||||
puts []*rpcdbpb.PutRequest
|
||||
deletes []*rpcdbpb.DeleteRequest
|
||||
size int
|
||||
}
|
||||
|
||||
func (b *batch) Put(key, value []byte) error {
|
||||
b.puts = append(b.puts, &rpcdbpb.PutRequest{
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
b.size += len(key) + len(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *batch) Delete(key []byte) error {
|
||||
b.deletes = append(b.deletes, &rpcdbpb.DeleteRequest{
|
||||
Key: key,
|
||||
})
|
||||
b.size += len(key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *batch) Size() int {
|
||||
return b.size
|
||||
}
|
||||
|
||||
func (b *batch) Write() error {
|
||||
resp, err := b.db.client.WriteBatch(context.Background(), &rpcdbpb.WriteBatchRequest{
|
||||
Puts: b.puts,
|
||||
Deletes: b.deletes,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Err != rpcdbpb.Error_ERROR_UNSPECIFIED {
|
||||
return rpcErrorToError(resp.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *batch) Reset() {
|
||||
b.puts = b.puts[:0]
|
||||
b.deletes = b.deletes[:0]
|
||||
b.size = 0
|
||||
}
|
||||
|
||||
func (b *batch) Replay(w database.KeyValueWriterDeleter) error {
|
||||
for _, put := range b.puts {
|
||||
if err := w.Put(put.Key, put.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, del := range b.deletes {
|
||||
if err := w.Delete(del.Key); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *batch) Inner() database.Batch {
|
||||
return b
|
||||
}
|
||||
|
||||
type iterator struct {
|
||||
db *DatabaseClient
|
||||
id uint64
|
||||
data []*rpcdbpb.PutRequest
|
||||
dataIndex int
|
||||
err error
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (it *iterator) Next() bool {
|
||||
if it.err != nil || it.closed {
|
||||
return false
|
||||
}
|
||||
|
||||
// If we have data left from previous fetch, use it
|
||||
if it.dataIndex < len(it.data)-1 {
|
||||
it.dataIndex++
|
||||
return true
|
||||
}
|
||||
|
||||
// Fetch next batch
|
||||
resp, err := it.db.client.IteratorNext(context.Background(), &rpcdbpb.IteratorNextRequest{
|
||||
Id: it.id,
|
||||
})
|
||||
if err != nil {
|
||||
it.err = err
|
||||
return false
|
||||
}
|
||||
|
||||
if len(resp.Data) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
it.data = resp.Data
|
||||
it.dataIndex = 0
|
||||
return true
|
||||
}
|
||||
|
||||
func (it *iterator) Error() error {
|
||||
if it.err != nil {
|
||||
return it.err
|
||||
}
|
||||
|
||||
resp, err := it.db.client.IteratorError(context.Background(), &rpcdbpb.IteratorErrorRequest{
|
||||
Id: it.id,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Err != rpcdbpb.Error_ERROR_UNSPECIFIED {
|
||||
return rpcErrorToError(resp.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (it *iterator) Key() []byte {
|
||||
if it.dataIndex >= 0 && it.dataIndex < len(it.data) {
|
||||
return it.data[it.dataIndex].Key
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (it *iterator) Value() []byte {
|
||||
if it.dataIndex >= 0 && it.dataIndex < len(it.data) {
|
||||
return it.data[it.dataIndex].Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (it *iterator) Release() {
|
||||
if it.closed {
|
||||
return
|
||||
}
|
||||
it.closed = true
|
||||
it.db.client.IteratorRelease(context.Background(), &rpcdbpb.IteratorReleaseRequest{
|
||||
Id: it.id,
|
||||
})
|
||||
}
|
||||
|
||||
// Helper functions for error conversion
|
||||
func errorToRPCError(err error) rpcdbpb.Error {
|
||||
if err == nil {
|
||||
return rpcdbpb.Error_ERROR_UNSPECIFIED
|
||||
}
|
||||
switch err {
|
||||
case database.ErrNotFound:
|
||||
return rpcdbpb.Error_ERROR_NOT_FOUND
|
||||
case database.ErrClosed:
|
||||
return rpcdbpb.Error_ERROR_CLOSED
|
||||
default:
|
||||
// For any other error, we'll use CLOSED to indicate an error occurred
|
||||
// since we only have limited error types in the proto
|
||||
return rpcdbpb.Error_ERROR_CLOSED
|
||||
}
|
||||
}
|
||||
|
||||
func rpcErrorToError(err rpcdbpb.Error) error {
|
||||
switch err {
|
||||
case rpcdbpb.Error_ERROR_UNSPECIFIED:
|
||||
return nil
|
||||
case rpcdbpb.Error_ERROR_NOT_FOUND:
|
||||
return database.ErrNotFound
|
||||
case rpcdbpb.Error_ERROR_CLOSED:
|
||||
return database.ErrClosed
|
||||
default:
|
||||
return database.ErrClosed
|
||||
}
|
||||
}
|
||||
@@ -1,653 +0,0 @@
|
||||
//go:build !grpc
|
||||
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package rpcdb provides the default ZAP-backed database RPC.
|
||||
//
|
||||
// The host (luxd) holds a `database.Database` (currently zapdb under
|
||||
// the hood) and exposes it to a VM plugin process over the ZAP
|
||||
// transport. The plugin sees a `database.Database` interface that
|
||||
// forwards every call across the wire.
|
||||
//
|
||||
// This is the default build — it uses ZAP for transport and pure-Go
|
||||
// types from proto/zap/rpcdb on the wire (no protobuf). The grpc
|
||||
// build tag selects rpcdb_grpc.go instead, which uses gRPC + the
|
||||
// protobuf-generated types under proto/pb/rpcdb. The two are mutually
|
||||
// exclusive: build with `-tags=grpc` or with no tag, never both.
|
||||
package rpcdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/luxfi/database"
|
||||
zaprpcdb "github.com/luxfi/node/proto/zap/rpcdb"
|
||||
)
|
||||
|
||||
// Re-export the wire types so callers that import "proto/rpcdb"
|
||||
// see a single namespace regardless of which transport is active.
|
||||
type (
|
||||
Error = zaprpcdb.Error
|
||||
HasRequest = zaprpcdb.HasRequest
|
||||
HasResponse = zaprpcdb.HasResponse
|
||||
GetRequest = zaprpcdb.GetRequest
|
||||
GetResponse = zaprpcdb.GetResponse
|
||||
PutRequest = zaprpcdb.PutRequest
|
||||
PutResponse = zaprpcdb.PutResponse
|
||||
DeleteRequest = zaprpcdb.DeleteRequest
|
||||
DeleteResponse = zaprpcdb.DeleteResponse
|
||||
WriteBatchRequest = zaprpcdb.WriteBatchRequest
|
||||
WriteBatchResponse = zaprpcdb.WriteBatchResponse
|
||||
CompactRequest = zaprpcdb.CompactRequest
|
||||
CompactResponse = zaprpcdb.CompactResponse
|
||||
CloseRequest = zaprpcdb.CloseRequest
|
||||
CloseResponse = zaprpcdb.CloseResponse
|
||||
HealthCheckResponse = zaprpcdb.HealthCheckResponse
|
||||
NewIteratorWithStartAndPrefixRequest = zaprpcdb.NewIteratorWithStartAndPrefixRequest
|
||||
NewIteratorWithStartAndPrefixResponse = zaprpcdb.NewIteratorWithStartAndPrefixResponse
|
||||
IteratorNextRequest = zaprpcdb.IteratorNextRequest
|
||||
IteratorNextResponse = zaprpcdb.IteratorNextResponse
|
||||
IteratorErrorRequest = zaprpcdb.IteratorErrorRequest
|
||||
IteratorErrorResponse = zaprpcdb.IteratorErrorResponse
|
||||
IteratorReleaseRequest = zaprpcdb.IteratorReleaseRequest
|
||||
IteratorReleaseResponse = zaprpcdb.IteratorReleaseResponse
|
||||
)
|
||||
|
||||
const (
|
||||
Error_ERROR_UNSPECIFIED = zaprpcdb.Error_ERROR_UNSPECIFIED
|
||||
Error_ERROR_CLOSED = zaprpcdb.Error_ERROR_CLOSED
|
||||
Error_ERROR_NOT_FOUND = zaprpcdb.Error_ERROR_NOT_FOUND
|
||||
)
|
||||
|
||||
// Transport is the minimal RPC dispatcher abstraction the rpcdb
|
||||
// client uses to talk to the server. The default ZAP transport
|
||||
// implementation in vms/rpcchainvm/zap satisfies this interface; any
|
||||
// other in-process or socket-based transport that can dispatch
|
||||
// (method name, request) → response also works.
|
||||
//
|
||||
// Keeping this interface in the rpcdb package — rather than importing
|
||||
// luxfi/api/zap directly — avoids a circular dependency between the
|
||||
// proto layer and the application layer.
|
||||
type Transport interface {
|
||||
// Call dispatches a request to the named method and writes the
|
||||
// response into out. The on-the-wire encoding of req and out is
|
||||
// the transport's responsibility; this package only commits to
|
||||
// the named-method shape.
|
||||
Call(ctx context.Context, method string, req any, out any) error
|
||||
}
|
||||
|
||||
// Method names — kept in one place so the client and server stay in
|
||||
// sync.
|
||||
const (
|
||||
methodHas = "rpcdb.Has"
|
||||
methodGet = "rpcdb.Get"
|
||||
methodPut = "rpcdb.Put"
|
||||
methodDelete = "rpcdb.Delete"
|
||||
methodWriteBatch = "rpcdb.WriteBatch"
|
||||
methodCompact = "rpcdb.Compact"
|
||||
methodClose = "rpcdb.Close"
|
||||
methodHealthCheck = "rpcdb.HealthCheck"
|
||||
methodNewIteratorWithStartAndPrefix = "rpcdb.NewIteratorWithStartAndPrefix"
|
||||
methodIteratorNext = "rpcdb.IteratorNext"
|
||||
methodIteratorError = "rpcdb.IteratorError"
|
||||
methodIteratorRelease = "rpcdb.IteratorRelease"
|
||||
)
|
||||
|
||||
// DatabaseServer hosts a `database.Database` over a ZAP transport.
|
||||
// The server registers its methods with the transport's handler
|
||||
// surface so a remote DatabaseClient can call them. Every database
|
||||
// call (Has/Get/Put/…) becomes one round-trip on the underlying
|
||||
// connection, which today is a Z-Wing-encrypted ZAP channel.
|
||||
type DatabaseServer struct {
|
||||
db database.Database
|
||||
|
||||
iteratorLock sync.RWMutex
|
||||
nextIteratorID uint64
|
||||
iterators map[uint64]database.Iterator
|
||||
}
|
||||
|
||||
// NewServer wraps `db` for service over a ZAP transport.
|
||||
func NewServer(db database.Database) *DatabaseServer {
|
||||
return &DatabaseServer{
|
||||
db: db,
|
||||
iterators: make(map[uint64]database.Iterator),
|
||||
}
|
||||
}
|
||||
|
||||
// Has services rpcdb.Has.
|
||||
func (s *DatabaseServer) Has(_ context.Context, req *HasRequest) (*HasResponse, error) {
|
||||
has, err := s.db.Has(req.Key)
|
||||
if err != nil {
|
||||
return &HasResponse{Has: false, Err: errorToRPCError(err)}, nil
|
||||
}
|
||||
return &HasResponse{Has: has, Err: Error_ERROR_UNSPECIFIED}, nil
|
||||
}
|
||||
|
||||
// Get services rpcdb.Get.
|
||||
func (s *DatabaseServer) Get(_ context.Context, req *GetRequest) (*GetResponse, error) {
|
||||
value, err := s.db.Get(req.Key)
|
||||
if err != nil {
|
||||
return &GetResponse{Value: nil, Err: errorToRPCError(err)}, nil
|
||||
}
|
||||
return &GetResponse{Value: value, Err: Error_ERROR_UNSPECIFIED}, nil
|
||||
}
|
||||
|
||||
// Put services rpcdb.Put.
|
||||
func (s *DatabaseServer) Put(_ context.Context, req *PutRequest) (*PutResponse, error) {
|
||||
return &PutResponse{Err: errorToRPCError(s.db.Put(req.Key, req.Value))}, nil
|
||||
}
|
||||
|
||||
// Delete services rpcdb.Delete.
|
||||
func (s *DatabaseServer) Delete(_ context.Context, req *DeleteRequest) (*DeleteResponse, error) {
|
||||
return &DeleteResponse{Err: errorToRPCError(s.db.Delete(req.Key))}, nil
|
||||
}
|
||||
|
||||
// WriteBatch services rpcdb.WriteBatch.
|
||||
func (s *DatabaseServer) WriteBatch(_ context.Context, req *WriteBatchRequest) (*WriteBatchResponse, error) {
|
||||
batch := s.db.NewBatch()
|
||||
for _, p := range req.Puts {
|
||||
if err := batch.Put(p.Key, p.Value); err != nil {
|
||||
return &WriteBatchResponse{Err: errorToRPCError(err)}, nil
|
||||
}
|
||||
}
|
||||
for _, d := range req.Deletes {
|
||||
if err := batch.Delete(d.Key); err != nil {
|
||||
return &WriteBatchResponse{Err: errorToRPCError(err)}, nil
|
||||
}
|
||||
}
|
||||
return &WriteBatchResponse{Err: errorToRPCError(batch.Write())}, nil
|
||||
}
|
||||
|
||||
// Compact services rpcdb.Compact.
|
||||
func (s *DatabaseServer) Compact(_ context.Context, req *CompactRequest) (*CompactResponse, error) {
|
||||
return &CompactResponse{Err: errorToRPCError(s.db.Compact(req.Start, req.Limit))}, nil
|
||||
}
|
||||
|
||||
// Close services rpcdb.Close.
|
||||
func (s *DatabaseServer) Close(_ context.Context, _ *CloseRequest) (*CloseResponse, error) {
|
||||
return &CloseResponse{Err: errorToRPCError(s.db.Close())}, nil
|
||||
}
|
||||
|
||||
// HealthCheck services rpcdb.HealthCheck.
|
||||
func (s *DatabaseServer) HealthCheck(ctx context.Context) (*HealthCheckResponse, error) {
|
||||
health, err := s.db.HealthCheck(ctx)
|
||||
if err != nil {
|
||||
return &HealthCheckResponse{Details: []byte(err.Error())}, nil
|
||||
}
|
||||
details := []byte("healthy")
|
||||
if health != nil {
|
||||
if str, ok := health.(string); ok {
|
||||
details = []byte(str)
|
||||
}
|
||||
}
|
||||
return &HealthCheckResponse{Details: details}, nil
|
||||
}
|
||||
|
||||
// NewIteratorWithStartAndPrefix services rpcdb.NewIteratorWithStartAndPrefix.
|
||||
func (s *DatabaseServer) NewIteratorWithStartAndPrefix(_ context.Context, req *NewIteratorWithStartAndPrefixRequest) (*NewIteratorWithStartAndPrefixResponse, error) {
|
||||
it := s.db.NewIteratorWithStartAndPrefix(req.Start, req.Prefix)
|
||||
s.iteratorLock.Lock()
|
||||
id := s.nextIteratorID
|
||||
s.nextIteratorID++
|
||||
s.iterators[id] = it
|
||||
s.iteratorLock.Unlock()
|
||||
return &NewIteratorWithStartAndPrefixResponse{Id: id}, nil
|
||||
}
|
||||
|
||||
// IteratorNext services rpcdb.IteratorNext.
|
||||
func (s *DatabaseServer) IteratorNext(_ context.Context, req *IteratorNextRequest) (*IteratorNextResponse, error) {
|
||||
s.iteratorLock.RLock()
|
||||
it, ok := s.iterators[req.Id]
|
||||
s.iteratorLock.RUnlock()
|
||||
if !ok {
|
||||
return &IteratorNextResponse{Data: nil}, nil
|
||||
}
|
||||
const maxBatchSize = 100
|
||||
var data []*PutRequest
|
||||
for i := 0; i < maxBatchSize && it.Next(); i++ {
|
||||
data = append(data, &PutRequest{Key: it.Key(), Value: it.Value()})
|
||||
}
|
||||
return &IteratorNextResponse{Data: data}, nil
|
||||
}
|
||||
|
||||
// IteratorError services rpcdb.IteratorError.
|
||||
func (s *DatabaseServer) IteratorError(_ context.Context, req *IteratorErrorRequest) (*IteratorErrorResponse, error) {
|
||||
s.iteratorLock.RLock()
|
||||
it, ok := s.iterators[req.Id]
|
||||
s.iteratorLock.RUnlock()
|
||||
if !ok {
|
||||
return &IteratorErrorResponse{Err: Error_ERROR_NOT_FOUND}, nil
|
||||
}
|
||||
return &IteratorErrorResponse{Err: errorToRPCError(it.Error())}, nil
|
||||
}
|
||||
|
||||
// IteratorRelease services rpcdb.IteratorRelease.
|
||||
func (s *DatabaseServer) IteratorRelease(_ context.Context, req *IteratorReleaseRequest) (*IteratorReleaseResponse, error) {
|
||||
s.iteratorLock.Lock()
|
||||
it, ok := s.iterators[req.Id]
|
||||
if ok {
|
||||
it.Release()
|
||||
delete(s.iterators, req.Id)
|
||||
}
|
||||
s.iteratorLock.Unlock()
|
||||
if !ok {
|
||||
return &IteratorReleaseResponse{Err: Error_ERROR_NOT_FOUND}, nil
|
||||
}
|
||||
return &IteratorReleaseResponse{Err: Error_ERROR_UNSPECIFIED}, nil
|
||||
}
|
||||
|
||||
// Register installs `s` on `r` so a remote DatabaseClient can dial in.
|
||||
// `r` is a method-name → handler registry; the ZAP transport in
|
||||
// vms/rpcchainvm/zap satisfies this.
|
||||
type HandlerRegistry interface {
|
||||
HandleFunc(method string, handler func(context.Context, any, any) error)
|
||||
}
|
||||
|
||||
// Register binds every database method on `r`. The handler signatures
|
||||
// reflect the `(ctx, request) -> response` shape of every rpcdb call;
|
||||
// the transport is responsible for unmarshaling the request and
|
||||
// marshaling the response.
|
||||
func (s *DatabaseServer) Register(r HandlerRegistry) {
|
||||
r.HandleFunc(methodHas, func(ctx context.Context, in any, out any) error {
|
||||
req := in.(*HasRequest)
|
||||
resp, err := s.Has(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*(out.(*HasResponse)) = *resp
|
||||
return nil
|
||||
})
|
||||
r.HandleFunc(methodGet, func(ctx context.Context, in any, out any) error {
|
||||
req := in.(*GetRequest)
|
||||
resp, err := s.Get(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*(out.(*GetResponse)) = *resp
|
||||
return nil
|
||||
})
|
||||
r.HandleFunc(methodPut, func(ctx context.Context, in any, out any) error {
|
||||
req := in.(*PutRequest)
|
||||
resp, err := s.Put(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*(out.(*PutResponse)) = *resp
|
||||
return nil
|
||||
})
|
||||
r.HandleFunc(methodDelete, func(ctx context.Context, in any, out any) error {
|
||||
req := in.(*DeleteRequest)
|
||||
resp, err := s.Delete(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*(out.(*DeleteResponse)) = *resp
|
||||
return nil
|
||||
})
|
||||
r.HandleFunc(methodWriteBatch, func(ctx context.Context, in any, out any) error {
|
||||
req := in.(*WriteBatchRequest)
|
||||
resp, err := s.WriteBatch(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*(out.(*WriteBatchResponse)) = *resp
|
||||
return nil
|
||||
})
|
||||
r.HandleFunc(methodCompact, func(ctx context.Context, in any, out any) error {
|
||||
req := in.(*CompactRequest)
|
||||
resp, err := s.Compact(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*(out.(*CompactResponse)) = *resp
|
||||
return nil
|
||||
})
|
||||
r.HandleFunc(methodClose, func(ctx context.Context, in any, out any) error {
|
||||
req := in.(*CloseRequest)
|
||||
resp, err := s.Close(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*(out.(*CloseResponse)) = *resp
|
||||
return nil
|
||||
})
|
||||
r.HandleFunc(methodHealthCheck, func(ctx context.Context, _ any, out any) error {
|
||||
resp, err := s.HealthCheck(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*(out.(*HealthCheckResponse)) = *resp
|
||||
return nil
|
||||
})
|
||||
r.HandleFunc(methodNewIteratorWithStartAndPrefix, func(ctx context.Context, in any, out any) error {
|
||||
req := in.(*NewIteratorWithStartAndPrefixRequest)
|
||||
resp, err := s.NewIteratorWithStartAndPrefix(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*(out.(*NewIteratorWithStartAndPrefixResponse)) = *resp
|
||||
return nil
|
||||
})
|
||||
r.HandleFunc(methodIteratorNext, func(ctx context.Context, in any, out any) error {
|
||||
req := in.(*IteratorNextRequest)
|
||||
resp, err := s.IteratorNext(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*(out.(*IteratorNextResponse)) = *resp
|
||||
return nil
|
||||
})
|
||||
r.HandleFunc(methodIteratorError, func(ctx context.Context, in any, out any) error {
|
||||
req := in.(*IteratorErrorRequest)
|
||||
resp, err := s.IteratorError(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*(out.(*IteratorErrorResponse)) = *resp
|
||||
return nil
|
||||
})
|
||||
r.HandleFunc(methodIteratorRelease, func(ctx context.Context, in any, out any) error {
|
||||
req := in.(*IteratorReleaseRequest)
|
||||
resp, err := s.IteratorRelease(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*(out.(*IteratorReleaseResponse)) = *resp
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// DatabaseClient is the plugin-side database that forwards every call
|
||||
// to a remote DatabaseServer over a ZAP Transport. Implements
|
||||
// database.Database, so the plugin can drop it in unmodified.
|
||||
type DatabaseClient struct {
|
||||
t Transport
|
||||
}
|
||||
|
||||
// NewClient builds a DatabaseClient that talks to a DatabaseServer
|
||||
// across `t`.
|
||||
func NewClient(t Transport) *DatabaseClient {
|
||||
return &DatabaseClient{t: t}
|
||||
}
|
||||
|
||||
// Compile-time interface check.
|
||||
var _ database.Database = (*DatabaseClient)(nil)
|
||||
|
||||
// Has forwards rpcdb.Has.
|
||||
func (c *DatabaseClient) Has(key []byte) (bool, error) {
|
||||
resp := &HasResponse{}
|
||||
if err := c.t.Call(context.Background(), methodHas, &HasRequest{Key: key}, resp); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if resp.Err != Error_ERROR_UNSPECIFIED {
|
||||
return false, rpcErrorToError(resp.Err)
|
||||
}
|
||||
return resp.Has, nil
|
||||
}
|
||||
|
||||
// Get forwards rpcdb.Get.
|
||||
func (c *DatabaseClient) Get(key []byte) ([]byte, error) {
|
||||
resp := &GetResponse{}
|
||||
if err := c.t.Call(context.Background(), methodGet, &GetRequest{Key: key}, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Err != Error_ERROR_UNSPECIFIED {
|
||||
return nil, rpcErrorToError(resp.Err)
|
||||
}
|
||||
return resp.Value, nil
|
||||
}
|
||||
|
||||
// Put forwards rpcdb.Put.
|
||||
func (c *DatabaseClient) Put(key, value []byte) error {
|
||||
resp := &PutResponse{}
|
||||
if err := c.t.Call(context.Background(), methodPut, &PutRequest{Key: key, Value: value}, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Err != Error_ERROR_UNSPECIFIED {
|
||||
return rpcErrorToError(resp.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete forwards rpcdb.Delete.
|
||||
func (c *DatabaseClient) Delete(key []byte) error {
|
||||
resp := &DeleteResponse{}
|
||||
if err := c.t.Call(context.Background(), methodDelete, &DeleteRequest{Key: key}, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Err != Error_ERROR_UNSPECIFIED {
|
||||
return rpcErrorToError(resp.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewBatch returns an in-memory batch that flushes via WriteBatch.
|
||||
func (c *DatabaseClient) NewBatch() database.Batch {
|
||||
return &batch{c: c}
|
||||
}
|
||||
|
||||
// NewIterator returns a forward-only iterator over the entire DB.
|
||||
func (c *DatabaseClient) NewIterator() database.Iterator {
|
||||
return c.NewIteratorWithStartAndPrefix(nil, nil)
|
||||
}
|
||||
|
||||
// NewIteratorWithStart returns a forward-only iterator from start.
|
||||
func (c *DatabaseClient) NewIteratorWithStart(start []byte) database.Iterator {
|
||||
return c.NewIteratorWithStartAndPrefix(start, nil)
|
||||
}
|
||||
|
||||
// NewIteratorWithPrefix returns a forward-only iterator over keys
|
||||
// matching prefix.
|
||||
func (c *DatabaseClient) NewIteratorWithPrefix(prefix []byte) database.Iterator {
|
||||
return c.NewIteratorWithStartAndPrefix(nil, prefix)
|
||||
}
|
||||
|
||||
// NewIteratorWithStartAndPrefix forwards rpcdb.NewIteratorWithStartAndPrefix.
|
||||
func (c *DatabaseClient) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator {
|
||||
resp := &NewIteratorWithStartAndPrefixResponse{}
|
||||
err := c.t.Call(context.Background(), methodNewIteratorWithStartAndPrefix,
|
||||
&NewIteratorWithStartAndPrefixRequest{Start: start, Prefix: prefix}, resp)
|
||||
if err != nil {
|
||||
return &iterator{c: c, err: err}
|
||||
}
|
||||
return &iterator{c: c, id: resp.Id}
|
||||
}
|
||||
|
||||
// Backup is not supported across an RPC boundary.
|
||||
func (*DatabaseClient) Backup(io.Writer, uint64) (uint64, error) {
|
||||
return 0, errors.New("rpcdb: backup not supported")
|
||||
}
|
||||
|
||||
// Load is not supported across an RPC boundary.
|
||||
func (*DatabaseClient) Load(io.Reader) error {
|
||||
return errors.New("rpcdb: load not supported")
|
||||
}
|
||||
|
||||
// Compact forwards rpcdb.Compact.
|
||||
func (c *DatabaseClient) Compact(start, limit []byte) error {
|
||||
resp := &CompactResponse{}
|
||||
if err := c.t.Call(context.Background(), methodCompact, &CompactRequest{Start: start, Limit: limit}, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Err != Error_ERROR_UNSPECIFIED {
|
||||
return rpcErrorToError(resp.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sync is a no-op on the client; the server owns persistence.
|
||||
func (*DatabaseClient) Sync() error { return nil }
|
||||
|
||||
// Close forwards rpcdb.Close.
|
||||
func (c *DatabaseClient) Close() error {
|
||||
resp := &CloseResponse{}
|
||||
if err := c.t.Call(context.Background(), methodClose, &CloseRequest{}, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Err != Error_ERROR_UNSPECIFIED {
|
||||
return rpcErrorToError(resp.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HealthCheck forwards rpcdb.HealthCheck.
|
||||
func (c *DatabaseClient) HealthCheck(ctx context.Context) (interface{}, error) {
|
||||
resp := &HealthCheckResponse{}
|
||||
if err := c.t.Call(ctx, methodHealthCheck, struct{}{}, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Details, nil
|
||||
}
|
||||
|
||||
// batch buffers Put/Delete locally and writes once on flush.
|
||||
type batch struct {
|
||||
c *DatabaseClient
|
||||
puts []*PutRequest
|
||||
deletes []*DeleteRequest
|
||||
size int
|
||||
}
|
||||
|
||||
func (b *batch) Put(key, value []byte) error {
|
||||
b.puts = append(b.puts, &PutRequest{Key: key, Value: value})
|
||||
b.size += len(key) + len(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *batch) Delete(key []byte) error {
|
||||
b.deletes = append(b.deletes, &DeleteRequest{Key: key})
|
||||
b.size += len(key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *batch) Size() int { return b.size }
|
||||
|
||||
func (b *batch) Write() error {
|
||||
resp := &WriteBatchResponse{}
|
||||
if err := b.c.t.Call(context.Background(), methodWriteBatch, &WriteBatchRequest{Puts: b.puts, Deletes: b.deletes}, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Err != Error_ERROR_UNSPECIFIED {
|
||||
return rpcErrorToError(resp.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *batch) Reset() {
|
||||
b.puts = b.puts[:0]
|
||||
b.deletes = b.deletes[:0]
|
||||
b.size = 0
|
||||
}
|
||||
|
||||
func (b *batch) Replay(w database.KeyValueWriterDeleter) error {
|
||||
for _, p := range b.puts {
|
||||
if err := w.Put(p.Key, p.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, d := range b.deletes {
|
||||
if err := w.Delete(d.Key); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *batch) Inner() database.Batch { return b }
|
||||
|
||||
// iterator paginates IteratorNext calls.
|
||||
type iterator struct {
|
||||
c *DatabaseClient
|
||||
id uint64
|
||||
data []*PutRequest
|
||||
dataIndex int
|
||||
err error
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (it *iterator) Next() bool {
|
||||
if it.err != nil || it.closed {
|
||||
return false
|
||||
}
|
||||
if it.dataIndex < len(it.data)-1 {
|
||||
it.dataIndex++
|
||||
return true
|
||||
}
|
||||
resp := &IteratorNextResponse{}
|
||||
if err := it.c.t.Call(context.Background(), methodIteratorNext, &IteratorNextRequest{Id: it.id}, resp); err != nil {
|
||||
it.err = err
|
||||
return false
|
||||
}
|
||||
if len(resp.Data) == 0 {
|
||||
return false
|
||||
}
|
||||
it.data = resp.Data
|
||||
it.dataIndex = 0
|
||||
return true
|
||||
}
|
||||
|
||||
func (it *iterator) Error() error {
|
||||
if it.err != nil {
|
||||
return it.err
|
||||
}
|
||||
resp := &IteratorErrorResponse{}
|
||||
if err := it.c.t.Call(context.Background(), methodIteratorError, &IteratorErrorRequest{Id: it.id}, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Err != Error_ERROR_UNSPECIFIED {
|
||||
return rpcErrorToError(resp.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (it *iterator) Key() []byte {
|
||||
if it.dataIndex >= 0 && it.dataIndex < len(it.data) {
|
||||
return it.data[it.dataIndex].Key
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (it *iterator) Value() []byte {
|
||||
if it.dataIndex >= 0 && it.dataIndex < len(it.data) {
|
||||
return it.data[it.dataIndex].Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (it *iterator) Release() {
|
||||
if it.closed {
|
||||
return
|
||||
}
|
||||
it.closed = true
|
||||
resp := &IteratorReleaseResponse{}
|
||||
_ = it.c.t.Call(context.Background(), methodIteratorRelease, &IteratorReleaseRequest{Id: it.id}, resp)
|
||||
}
|
||||
|
||||
func errorToRPCError(err error) Error {
|
||||
if err == nil {
|
||||
return Error_ERROR_UNSPECIFIED
|
||||
}
|
||||
switch err {
|
||||
case database.ErrNotFound:
|
||||
return Error_ERROR_NOT_FOUND
|
||||
case database.ErrClosed:
|
||||
return Error_ERROR_CLOSED
|
||||
default:
|
||||
return Error_ERROR_CLOSED
|
||||
}
|
||||
}
|
||||
|
||||
func rpcErrorToError(err Error) error {
|
||||
switch err {
|
||||
case Error_ERROR_UNSPECIFIED:
|
||||
return nil
|
||||
case Error_ERROR_NOT_FOUND:
|
||||
return database.ErrNotFound
|
||||
case Error_ERROR_CLOSED:
|
||||
return database.ErrClosed
|
||||
default:
|
||||
return database.ErrClosed
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package rpcdb provides the ZAP-native database RPC types — the
|
||||
// shapes that flow over the wire when a VM plugin (separate process)
|
||||
// reads/writes the host's database via the ZAP transport.
|
||||
//
|
||||
// This package is the single source of truth for the on-the-wire
|
||||
// types; the grpc-tagged variant under proto/pb/rpcdb is generated
|
||||
// from these shapes and is mutually exclusive with the ZAP default
|
||||
// (you build with either `-tags=grpc` or no tag, never both).
|
||||
//
|
||||
// The actual storage backend on the server side is luxfi/database —
|
||||
// which is currently zapdb under the hood. The same interface is
|
||||
// presented to plugin clients regardless of which DB engine the host
|
||||
// runs.
|
||||
package rpcdb
|
||||
|
||||
// Error is the on-the-wire error code returned by every database RPC.
|
||||
type Error int32
|
||||
|
||||
const (
|
||||
// Error_ERROR_UNSPECIFIED is the success / no-error sentinel.
|
||||
Error_ERROR_UNSPECIFIED Error = 0
|
||||
// Error_ERROR_CLOSED maps to database.ErrClosed.
|
||||
Error_ERROR_CLOSED Error = 1
|
||||
// Error_ERROR_NOT_FOUND maps to database.ErrNotFound.
|
||||
Error_ERROR_NOT_FOUND Error = 2
|
||||
)
|
||||
|
||||
func (e Error) String() string {
|
||||
switch e {
|
||||
case Error_ERROR_CLOSED:
|
||||
return "CLOSED"
|
||||
case Error_ERROR_NOT_FOUND:
|
||||
return "NOT_FOUND"
|
||||
default:
|
||||
return "UNSPECIFIED"
|
||||
}
|
||||
}
|
||||
|
||||
// HasRequest is the wire payload for Database.Has.
|
||||
type HasRequest struct {
|
||||
Key []byte
|
||||
}
|
||||
|
||||
// HasResponse is the wire reply for Database.Has.
|
||||
type HasResponse struct {
|
||||
Has bool
|
||||
Err Error
|
||||
}
|
||||
|
||||
// GetRequest is the wire payload for Database.Get.
|
||||
type GetRequest struct {
|
||||
Key []byte
|
||||
}
|
||||
|
||||
// GetResponse is the wire reply for Database.Get.
|
||||
type GetResponse struct {
|
||||
Value []byte
|
||||
Err Error
|
||||
}
|
||||
|
||||
// PutRequest is the wire payload for Database.Put (and the PUT entries
|
||||
// in WriteBatch / IteratorNext).
|
||||
type PutRequest struct {
|
||||
Key []byte
|
||||
Value []byte
|
||||
}
|
||||
|
||||
// PutResponse is the wire reply for Database.Put.
|
||||
type PutResponse struct {
|
||||
Err Error
|
||||
}
|
||||
|
||||
// DeleteRequest is the wire payload for Database.Delete.
|
||||
type DeleteRequest struct {
|
||||
Key []byte
|
||||
}
|
||||
|
||||
// DeleteResponse is the wire reply for Database.Delete.
|
||||
type DeleteResponse struct {
|
||||
Err Error
|
||||
}
|
||||
|
||||
// WriteBatchRequest is the wire payload for Database.WriteBatch.
|
||||
type WriteBatchRequest struct {
|
||||
Puts []*PutRequest
|
||||
Deletes []*DeleteRequest
|
||||
}
|
||||
|
||||
// WriteBatchResponse is the wire reply for Database.WriteBatch.
|
||||
type WriteBatchResponse struct {
|
||||
Err Error
|
||||
}
|
||||
|
||||
// CompactRequest is the wire payload for Database.Compact.
|
||||
type CompactRequest struct {
|
||||
Start []byte
|
||||
Limit []byte
|
||||
}
|
||||
|
||||
// CompactResponse is the wire reply for Database.Compact.
|
||||
type CompactResponse struct {
|
||||
Err Error
|
||||
}
|
||||
|
||||
// CloseRequest is the wire payload for Database.Close.
|
||||
type CloseRequest struct{}
|
||||
|
||||
// CloseResponse is the wire reply for Database.Close.
|
||||
type CloseResponse struct {
|
||||
Err Error
|
||||
}
|
||||
|
||||
// HealthCheckResponse is the wire reply for Database.HealthCheck.
|
||||
type HealthCheckResponse struct {
|
||||
Details []byte
|
||||
}
|
||||
|
||||
// NewIteratorWithStartAndPrefixRequest is the wire payload for
|
||||
// Database.NewIteratorWithStartAndPrefix.
|
||||
type NewIteratorWithStartAndPrefixRequest struct {
|
||||
Start []byte
|
||||
Prefix []byte
|
||||
}
|
||||
|
||||
// NewIteratorWithStartAndPrefixResponse is the wire reply for
|
||||
// Database.NewIteratorWithStartAndPrefix.
|
||||
type NewIteratorWithStartAndPrefixResponse struct {
|
||||
Id uint64
|
||||
}
|
||||
|
||||
// IteratorNextRequest is the wire payload for Database.IteratorNext.
|
||||
type IteratorNextRequest struct {
|
||||
Id uint64
|
||||
}
|
||||
|
||||
// IteratorNextResponse is the wire reply for Database.IteratorNext.
|
||||
// Data is the next batch of (key, value) pairs to read; an empty
|
||||
// batch indicates the iterator has been exhausted.
|
||||
type IteratorNextResponse struct {
|
||||
Data []*PutRequest
|
||||
}
|
||||
|
||||
// IteratorErrorRequest is the wire payload for Database.IteratorError.
|
||||
type IteratorErrorRequest struct {
|
||||
Id uint64
|
||||
}
|
||||
|
||||
// IteratorErrorResponse is the wire reply for Database.IteratorError.
|
||||
type IteratorErrorResponse struct {
|
||||
Err Error
|
||||
}
|
||||
|
||||
// IteratorReleaseRequest is the wire payload for Database.IteratorRelease.
|
||||
type IteratorReleaseRequest struct {
|
||||
Id uint64
|
||||
}
|
||||
|
||||
// IteratorReleaseResponse is the wire reply for Database.IteratorRelease.
|
||||
type IteratorReleaseResponse struct {
|
||||
Err Error
|
||||
}
|
||||
Reference in New Issue
Block a user