github.com/MetalBlockchain/metalgo@v1.11.9/database/rpcdb/db_server.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package rpcdb
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"errors"
    10  	"sync"
    11  
    12  	"google.golang.org/protobuf/types/known/emptypb"
    13  
    14  	"github.com/MetalBlockchain/metalgo/database"
    15  	"github.com/MetalBlockchain/metalgo/utils/units"
    16  
    17  	rpcdbpb "github.com/MetalBlockchain/metalgo/proto/pb/rpcdb"
    18  )
    19  
    20  const iterationBatchSize = 128 * units.KiB
    21  
    22  var errUnknownIterator = errors.New("unknown iterator")
    23  
    24  // DatabaseServer is a database that is managed over RPC.
    25  type DatabaseServer struct {
    26  	rpcdbpb.UnsafeDatabaseServer
    27  
    28  	db database.Database
    29  
    30  	// iteratorLock protects [nextIteratorID] and [iterators] from concurrent
    31  	// modifications. Similarly to [batchLock], [iteratorLock] does not protect
    32  	// the actual Iterator. Iterators are documented as not being safe for
    33  	// concurrent use. Therefore, it is up to the client to respect this
    34  	// invariant.
    35  	iteratorLock   sync.RWMutex
    36  	nextIteratorID uint64
    37  	iterators      map[uint64]database.Iterator
    38  }
    39  
    40  // NewServer returns a database instance that is managed remotely
    41  func NewServer(db database.Database) *DatabaseServer {
    42  	return &DatabaseServer{
    43  		db:        db,
    44  		iterators: make(map[uint64]database.Iterator),
    45  	}
    46  }
    47  
    48  // Has delegates the Has call to the managed database and returns the result
    49  func (db *DatabaseServer) Has(_ context.Context, req *rpcdbpb.HasRequest) (*rpcdbpb.HasResponse, error) {
    50  	has, err := db.db.Has(req.Key)
    51  	return &rpcdbpb.HasResponse{
    52  		Has: has,
    53  		Err: ErrorToErrEnum[err],
    54  	}, ErrorToRPCError(err)
    55  }
    56  
    57  // Get delegates the Get call to the managed database and returns the result
    58  func (db *DatabaseServer) Get(_ context.Context, req *rpcdbpb.GetRequest) (*rpcdbpb.GetResponse, error) {
    59  	value, err := db.db.Get(req.Key)
    60  	return &rpcdbpb.GetResponse{
    61  		Value: value,
    62  		Err:   ErrorToErrEnum[err],
    63  	}, ErrorToRPCError(err)
    64  }
    65  
    66  // Put delegates the Put call to the managed database and returns the result
    67  func (db *DatabaseServer) Put(_ context.Context, req *rpcdbpb.PutRequest) (*rpcdbpb.PutResponse, error) {
    68  	err := db.db.Put(req.Key, req.Value)
    69  	return &rpcdbpb.PutResponse{Err: ErrorToErrEnum[err]}, ErrorToRPCError(err)
    70  }
    71  
    72  // Delete delegates the Delete call to the managed database and returns the
    73  // result
    74  func (db *DatabaseServer) Delete(_ context.Context, req *rpcdbpb.DeleteRequest) (*rpcdbpb.DeleteResponse, error) {
    75  	err := db.db.Delete(req.Key)
    76  	return &rpcdbpb.DeleteResponse{Err: ErrorToErrEnum[err]}, ErrorToRPCError(err)
    77  }
    78  
    79  // Compact delegates the Compact call to the managed database and returns the
    80  // result
    81  func (db *DatabaseServer) Compact(_ context.Context, req *rpcdbpb.CompactRequest) (*rpcdbpb.CompactResponse, error) {
    82  	err := db.db.Compact(req.Start, req.Limit)
    83  	return &rpcdbpb.CompactResponse{Err: ErrorToErrEnum[err]}, ErrorToRPCError(err)
    84  }
    85  
    86  // Close delegates the Close call to the managed database and returns the result
    87  func (db *DatabaseServer) Close(context.Context, *rpcdbpb.CloseRequest) (*rpcdbpb.CloseResponse, error) {
    88  	err := db.db.Close()
    89  	return &rpcdbpb.CloseResponse{Err: ErrorToErrEnum[err]}, ErrorToRPCError(err)
    90  }
    91  
    92  // HealthCheck performs a heath check against the underlying database.
    93  func (db *DatabaseServer) HealthCheck(ctx context.Context, _ *emptypb.Empty) (*rpcdbpb.HealthCheckResponse, error) {
    94  	health, err := db.db.HealthCheck(ctx)
    95  	if err != nil {
    96  		return &rpcdbpb.HealthCheckResponse{}, err
    97  	}
    98  
    99  	details, err := json.Marshal(health)
   100  	return &rpcdbpb.HealthCheckResponse{
   101  		Details: details,
   102  	}, err
   103  }
   104  
   105  // WriteBatch takes in a set of key-value pairs and atomically writes them to
   106  // the internal database
   107  func (db *DatabaseServer) WriteBatch(_ context.Context, req *rpcdbpb.WriteBatchRequest) (*rpcdbpb.WriteBatchResponse, error) {
   108  	batch := db.db.NewBatch()
   109  	for _, put := range req.Puts {
   110  		if err := batch.Put(put.Key, put.Value); err != nil {
   111  			return &rpcdbpb.WriteBatchResponse{
   112  				Err: ErrorToErrEnum[err],
   113  			}, ErrorToRPCError(err)
   114  		}
   115  	}
   116  	for _, del := range req.Deletes {
   117  		if err := batch.Delete(del.Key); err != nil {
   118  			return &rpcdbpb.WriteBatchResponse{
   119  				Err: ErrorToErrEnum[err],
   120  			}, ErrorToRPCError(err)
   121  		}
   122  	}
   123  
   124  	err := batch.Write()
   125  	return &rpcdbpb.WriteBatchResponse{
   126  		Err: ErrorToErrEnum[err],
   127  	}, ErrorToRPCError(err)
   128  }
   129  
   130  // NewIteratorWithStartAndPrefix allocates an iterator and returns the iterator
   131  // ID
   132  func (db *DatabaseServer) NewIteratorWithStartAndPrefix(_ context.Context, req *rpcdbpb.NewIteratorWithStartAndPrefixRequest) (*rpcdbpb.NewIteratorWithStartAndPrefixResponse, error) {
   133  	it := db.db.NewIteratorWithStartAndPrefix(req.Start, req.Prefix)
   134  
   135  	db.iteratorLock.Lock()
   136  	defer db.iteratorLock.Unlock()
   137  
   138  	id := db.nextIteratorID
   139  	db.iterators[id] = it
   140  	db.nextIteratorID++
   141  	return &rpcdbpb.NewIteratorWithStartAndPrefixResponse{Id: id}, nil
   142  }
   143  
   144  // IteratorNext attempts to call next on the requested iterator
   145  func (db *DatabaseServer) IteratorNext(_ context.Context, req *rpcdbpb.IteratorNextRequest) (*rpcdbpb.IteratorNextResponse, error) {
   146  	db.iteratorLock.RLock()
   147  	it, exists := db.iterators[req.Id]
   148  	db.iteratorLock.RUnlock()
   149  	if !exists {
   150  		return nil, errUnknownIterator
   151  	}
   152  
   153  	var (
   154  		size int
   155  		data []*rpcdbpb.PutRequest
   156  	)
   157  	for size < iterationBatchSize && it.Next() {
   158  		key := it.Key()
   159  		value := it.Value()
   160  		size += len(key) + len(value)
   161  
   162  		data = append(data, &rpcdbpb.PutRequest{
   163  			Key:   key,
   164  			Value: value,
   165  		})
   166  	}
   167  
   168  	return &rpcdbpb.IteratorNextResponse{Data: data}, nil
   169  }
   170  
   171  // IteratorError attempts to report any errors that occurred during iteration
   172  func (db *DatabaseServer) IteratorError(_ context.Context, req *rpcdbpb.IteratorErrorRequest) (*rpcdbpb.IteratorErrorResponse, error) {
   173  	db.iteratorLock.RLock()
   174  	it, exists := db.iterators[req.Id]
   175  	db.iteratorLock.RUnlock()
   176  	if !exists {
   177  		return nil, errUnknownIterator
   178  	}
   179  	err := it.Error()
   180  	return &rpcdbpb.IteratorErrorResponse{Err: ErrorToErrEnum[err]}, ErrorToRPCError(err)
   181  }
   182  
   183  // IteratorRelease attempts to release the resources allocated to an iterator
   184  func (db *DatabaseServer) IteratorRelease(_ context.Context, req *rpcdbpb.IteratorReleaseRequest) (*rpcdbpb.IteratorReleaseResponse, error) {
   185  	db.iteratorLock.Lock()
   186  	it, exists := db.iterators[req.Id]
   187  	if !exists {
   188  		db.iteratorLock.Unlock()
   189  		return &rpcdbpb.IteratorReleaseResponse{}, nil
   190  	}
   191  	delete(db.iterators, req.Id)
   192  	db.iteratorLock.Unlock()
   193  
   194  	err := it.Error()
   195  	it.Release()
   196  	return &rpcdbpb.IteratorReleaseResponse{Err: ErrorToErrEnum[err]}, ErrorToRPCError(err)
   197  }