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 }