github.com/ledgerwatch/erigon-lib@v1.0.0/txpool/txpool_grpc_server.go (about) 1 /* 2 Copyright 2021 The Erigon contributors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package txpool 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "math" 24 "net" 25 "sync" 26 "time" 27 28 grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" 29 grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" 30 "github.com/holiman/uint256" 31 "github.com/ledgerwatch/erigon-lib/txpool/txpoolcfg" 32 "github.com/ledgerwatch/log/v3" 33 "google.golang.org/grpc" 34 "google.golang.org/grpc/credentials" 35 "google.golang.org/grpc/health" 36 "google.golang.org/grpc/health/grpc_health_v1" 37 "google.golang.org/grpc/keepalive" 38 "google.golang.org/grpc/reflection" 39 "google.golang.org/protobuf/types/known/emptypb" 40 41 "github.com/ledgerwatch/erigon-lib/common" 42 "github.com/ledgerwatch/erigon-lib/gointerfaces" 43 txpool_proto "github.com/ledgerwatch/erigon-lib/gointerfaces/txpool" 44 types2 "github.com/ledgerwatch/erigon-lib/gointerfaces/types" 45 "github.com/ledgerwatch/erigon-lib/kv" 46 "github.com/ledgerwatch/erigon-lib/types" 47 ) 48 49 // TxPoolAPIVersion 50 var TxPoolAPIVersion = &types2.VersionReply{Major: 1, Minor: 0, Patch: 0} 51 52 type txPool interface { 53 ValidateSerializedTxn(serializedTxn []byte) error 54 55 PeekBest(n uint16, txs *types.TxsRlp, tx kv.Tx, onTopOf, availableGas, availableBlobGas uint64) (bool, error) 56 GetRlp(tx kv.Tx, hash []byte) ([]byte, error) 57 AddLocalTxs(ctx context.Context, newTxs types.TxSlots, tx kv.Tx) ([]txpoolcfg.DiscardReason, error) 58 deprecatedForEach(_ context.Context, f func(rlp []byte, sender common.Address, t SubPoolType), tx kv.Tx) 59 CountContent() (int, int, int) 60 IdHashKnown(tx kv.Tx, hash []byte) (bool, error) 61 NonceFromAddress(addr [20]byte) (nonce uint64, inPool bool) 62 } 63 64 var _ txpool_proto.TxpoolServer = (*GrpcServer)(nil) // compile-time interface check 65 var _ txpool_proto.TxpoolServer = (*GrpcDisabled)(nil) // compile-time interface check 66 67 var ErrPoolDisabled = fmt.Errorf("TxPool Disabled") 68 69 type GrpcDisabled struct { 70 txpool_proto.UnimplementedTxpoolServer 71 } 72 73 func (*GrpcDisabled) Version(ctx context.Context, empty *emptypb.Empty) (*types2.VersionReply, error) { 74 return nil, ErrPoolDisabled 75 } 76 func (*GrpcDisabled) FindUnknown(ctx context.Context, hashes *txpool_proto.TxHashes) (*txpool_proto.TxHashes, error) { 77 return nil, ErrPoolDisabled 78 } 79 func (*GrpcDisabled) Add(ctx context.Context, request *txpool_proto.AddRequest) (*txpool_proto.AddReply, error) { 80 return nil, ErrPoolDisabled 81 } 82 func (*GrpcDisabled) Transactions(ctx context.Context, request *txpool_proto.TransactionsRequest) (*txpool_proto.TransactionsReply, error) { 83 return nil, ErrPoolDisabled 84 } 85 func (*GrpcDisabled) All(ctx context.Context, request *txpool_proto.AllRequest) (*txpool_proto.AllReply, error) { 86 return nil, ErrPoolDisabled 87 } 88 func (*GrpcDisabled) Pending(ctx context.Context, empty *emptypb.Empty) (*txpool_proto.PendingReply, error) { 89 return nil, ErrPoolDisabled 90 } 91 func (*GrpcDisabled) OnAdd(request *txpool_proto.OnAddRequest, server txpool_proto.Txpool_OnAddServer) error { 92 return ErrPoolDisabled 93 } 94 func (*GrpcDisabled) Status(ctx context.Context, request *txpool_proto.StatusRequest) (*txpool_proto.StatusReply, error) { 95 return nil, ErrPoolDisabled 96 } 97 func (*GrpcDisabled) Nonce(ctx context.Context, request *txpool_proto.NonceRequest) (*txpool_proto.NonceReply, error) { 98 return nil, ErrPoolDisabled 99 } 100 101 type GrpcServer struct { 102 txpool_proto.UnimplementedTxpoolServer 103 ctx context.Context 104 txPool txPool 105 db kv.RoDB 106 NewSlotsStreams *NewSlotsStreams 107 108 chainID uint256.Int 109 logger log.Logger 110 } 111 112 func NewGrpcServer(ctx context.Context, txPool txPool, db kv.RoDB, chainID uint256.Int, logger log.Logger) *GrpcServer { 113 return &GrpcServer{ctx: ctx, txPool: txPool, db: db, NewSlotsStreams: &NewSlotsStreams{}, chainID: chainID, logger: logger} 114 } 115 116 func (s *GrpcServer) Version(context.Context, *emptypb.Empty) (*types2.VersionReply, error) { 117 return TxPoolAPIVersion, nil 118 } 119 func convertSubPoolType(t SubPoolType) txpool_proto.AllReply_TxnType { 120 switch t { 121 case PendingSubPool: 122 return txpool_proto.AllReply_PENDING 123 case BaseFeeSubPool: 124 return txpool_proto.AllReply_BASE_FEE 125 case QueuedSubPool: 126 return txpool_proto.AllReply_QUEUED 127 default: 128 panic("unknown") 129 } 130 } 131 func (s *GrpcServer) All(ctx context.Context, _ *txpool_proto.AllRequest) (*txpool_proto.AllReply, error) { 132 tx, err := s.db.BeginRo(ctx) 133 if err != nil { 134 return nil, err 135 } 136 defer tx.Rollback() 137 reply := &txpool_proto.AllReply{} 138 reply.Txs = make([]*txpool_proto.AllReply_Tx, 0, 32) 139 s.txPool.deprecatedForEach(ctx, func(rlp []byte, sender common.Address, t SubPoolType) { 140 reply.Txs = append(reply.Txs, &txpool_proto.AllReply_Tx{ 141 Sender: gointerfaces.ConvertAddressToH160(sender), 142 TxnType: convertSubPoolType(t), 143 RlpTx: common.Copy(rlp), 144 }) 145 }, tx) 146 return reply, nil 147 } 148 149 func (s *GrpcServer) Pending(ctx context.Context, _ *emptypb.Empty) (*txpool_proto.PendingReply, error) { 150 tx, err := s.db.BeginRo(ctx) 151 if err != nil { 152 return nil, err 153 } 154 defer tx.Rollback() 155 reply := &txpool_proto.PendingReply{} 156 reply.Txs = make([]*txpool_proto.PendingReply_Tx, 0, 32) 157 txSlots := types.TxsRlp{} 158 if _, err := s.txPool.PeekBest(math.MaxInt16, &txSlots, tx, 0 /* onTopOf */, math.MaxUint64 /* availableGas */, math.MaxUint64 /* availableBlobGas */); err != nil { 159 return nil, err 160 } 161 var senderArr [20]byte 162 for i := range txSlots.Txs { 163 copy(senderArr[:], txSlots.Senders.At(i)) // TODO: optimize 164 reply.Txs = append(reply.Txs, &txpool_proto.PendingReply_Tx{ 165 Sender: gointerfaces.ConvertAddressToH160(senderArr), 166 RlpTx: txSlots.Txs[i], 167 IsLocal: txSlots.IsLocal[i], 168 }) 169 } 170 return reply, nil 171 } 172 173 func (s *GrpcServer) FindUnknown(ctx context.Context, in *txpool_proto.TxHashes) (*txpool_proto.TxHashes, error) { 174 return nil, fmt.Errorf("unimplemented") 175 } 176 177 func (s *GrpcServer) Add(ctx context.Context, in *txpool_proto.AddRequest) (*txpool_proto.AddReply, error) { 178 tx, err := s.db.BeginRo(ctx) 179 if err != nil { 180 return nil, err 181 } 182 defer tx.Rollback() 183 184 var slots types.TxSlots 185 parseCtx := types.NewTxParseContext(s.chainID).ChainIDRequired() 186 parseCtx.ValidateRLP(s.txPool.ValidateSerializedTxn) 187 188 reply := &txpool_proto.AddReply{Imported: make([]txpool_proto.ImportResult, len(in.RlpTxs)), Errors: make([]string, len(in.RlpTxs))} 189 190 j := 0 191 for i := 0; i < len(in.RlpTxs); i++ { // some incoming txs may be rejected, so - need second index 192 slots.Resize(uint(j + 1)) 193 slots.Txs[j] = &types.TxSlot{} 194 slots.IsLocal[j] = true 195 if _, err := parseCtx.ParseTransaction(in.RlpTxs[i], 0, slots.Txs[j], slots.Senders.At(j), false /* hasEnvelope */, true /* wrappedWithBlobs */, func(hash []byte) error { 196 if known, _ := s.txPool.IdHashKnown(tx, hash); known { 197 return types.ErrAlreadyKnown 198 } 199 return nil 200 }); err != nil { 201 if errors.Is(err, types.ErrAlreadyKnown) { // Noop, but need to handle to not count these 202 reply.Errors[i] = txpoolcfg.AlreadyKnown.String() 203 reply.Imported[i] = txpool_proto.ImportResult_ALREADY_EXISTS 204 } else if errors.Is(err, types.ErrRlpTooBig) { // Noop, but need to handle to not count these 205 reply.Errors[i] = txpoolcfg.RLPTooLong.String() 206 reply.Imported[i] = txpool_proto.ImportResult_INVALID 207 } else { 208 reply.Errors[i] = err.Error() 209 reply.Imported[i] = txpool_proto.ImportResult_INTERNAL_ERROR 210 } 211 continue 212 } 213 j++ 214 } 215 216 discardReasons, err := s.txPool.AddLocalTxs(ctx, slots, tx) 217 if err != nil { 218 return nil, err 219 } 220 221 j = 0 222 for i := range reply.Imported { 223 if reply.Imported[i] != txpool_proto.ImportResult_SUCCESS { 224 j++ 225 continue 226 } 227 228 reply.Imported[i] = mapDiscardReasonToProto(discardReasons[j]) 229 reply.Errors[i] = discardReasons[j].String() 230 j++ 231 } 232 return reply, nil 233 } 234 235 func mapDiscardReasonToProto(reason txpoolcfg.DiscardReason) txpool_proto.ImportResult { 236 switch reason { 237 case txpoolcfg.Success: 238 return txpool_proto.ImportResult_SUCCESS 239 case txpoolcfg.AlreadyKnown: 240 return txpool_proto.ImportResult_ALREADY_EXISTS 241 case txpoolcfg.UnderPriced, txpoolcfg.ReplaceUnderpriced, txpoolcfg.FeeTooLow: 242 return txpool_proto.ImportResult_FEE_TOO_LOW 243 case txpoolcfg.InvalidSender, txpoolcfg.NegativeValue, txpoolcfg.OversizedData, txpoolcfg.InitCodeTooLarge, txpoolcfg.RLPTooLong, txpoolcfg.CreateBlobTxn, txpoolcfg.NoBlobs, txpoolcfg.TooManyBlobs, txpoolcfg.TypeNotActivated, txpoolcfg.UnequalBlobTxExt, txpoolcfg.BlobHashCheckFail, txpoolcfg.UnmatchedBlobTxExt: 244 // TODO(eip-4844) TypeNotActivated may be transient (e.g. a blob transaction is submitted 1 sec prior to Cancun activation) 245 return txpool_proto.ImportResult_INVALID 246 default: 247 return txpool_proto.ImportResult_INTERNAL_ERROR 248 } 249 } 250 251 func (s *GrpcServer) OnAdd(req *txpool_proto.OnAddRequest, stream txpool_proto.Txpool_OnAddServer) error { 252 s.logger.Info("New txs subscriber joined") 253 //txpool.Loop does send messages to this streams 254 remove := s.NewSlotsStreams.Add(stream) 255 defer remove() 256 select { 257 case <-stream.Context().Done(): 258 return stream.Context().Err() 259 case <-s.ctx.Done(): 260 return s.ctx.Err() 261 } 262 } 263 264 func (s *GrpcServer) Transactions(ctx context.Context, in *txpool_proto.TransactionsRequest) (*txpool_proto.TransactionsReply, error) { 265 tx, err := s.db.BeginRo(ctx) 266 if err != nil { 267 return nil, err 268 } 269 defer tx.Rollback() 270 271 reply := &txpool_proto.TransactionsReply{RlpTxs: make([][]byte, len(in.Hashes))} 272 for i := range in.Hashes { 273 h := gointerfaces.ConvertH256ToHash(in.Hashes[i]) 274 txnRlp, err := s.txPool.GetRlp(tx, h[:]) 275 if err != nil { 276 return nil, err 277 } 278 if txnRlp == nil { 279 reply.RlpTxs[i] = []byte{} 280 continue 281 } 282 reply.RlpTxs[i] = txnRlp 283 } 284 285 return reply, nil 286 } 287 288 func (s *GrpcServer) Status(_ context.Context, _ *txpool_proto.StatusRequest) (*txpool_proto.StatusReply, error) { 289 pending, baseFee, queued := s.txPool.CountContent() 290 return &txpool_proto.StatusReply{ 291 PendingCount: uint32(pending), 292 QueuedCount: uint32(queued), 293 BaseFeeCount: uint32(baseFee), 294 }, nil 295 } 296 297 // returns nonce for address 298 func (s *GrpcServer) Nonce(ctx context.Context, in *txpool_proto.NonceRequest) (*txpool_proto.NonceReply, error) { 299 addr := gointerfaces.ConvertH160toAddress(in.Address) 300 nonce, inPool := s.txPool.NonceFromAddress(addr) 301 return &txpool_proto.NonceReply{ 302 Nonce: nonce, 303 Found: inPool, 304 }, nil 305 } 306 307 // NewSlotsStreams - it's safe to use this class as non-pointer 308 type NewSlotsStreams struct { 309 chans map[uint]txpool_proto.Txpool_OnAddServer 310 mu sync.Mutex 311 id uint 312 } 313 314 func (s *NewSlotsStreams) Add(stream txpool_proto.Txpool_OnAddServer) (remove func()) { 315 s.mu.Lock() 316 defer s.mu.Unlock() 317 if s.chans == nil { 318 s.chans = make(map[uint]txpool_proto.Txpool_OnAddServer) 319 } 320 s.id++ 321 id := s.id 322 s.chans[id] = stream 323 return func() { s.remove(id) } 324 } 325 326 func (s *NewSlotsStreams) Broadcast(reply *txpool_proto.OnAddReply, logger log.Logger) { 327 s.mu.Lock() 328 defer s.mu.Unlock() 329 for id, stream := range s.chans { 330 err := stream.Send(reply) 331 if err != nil { 332 logger.Debug("failed send to mined block stream", "err", err) 333 select { 334 case <-stream.Context().Done(): 335 delete(s.chans, id) 336 default: 337 } 338 } 339 } 340 } 341 342 func (s *NewSlotsStreams) remove(id uint) { 343 s.mu.Lock() 344 defer s.mu.Unlock() 345 _, ok := s.chans[id] 346 if !ok { // double-unsubscribe support 347 return 348 } 349 delete(s.chans, id) 350 } 351 352 func StartGrpc(txPoolServer txpool_proto.TxpoolServer, miningServer txpool_proto.MiningServer, addr string, creds *credentials.TransportCredentials, logger log.Logger) (*grpc.Server, error) { 353 lis, err := net.Listen("tcp", addr) 354 if err != nil { 355 return nil, fmt.Errorf("could not create listener: %w, addr=%s", err, addr) 356 } 357 358 var ( 359 streamInterceptors []grpc.StreamServerInterceptor 360 unaryInterceptors []grpc.UnaryServerInterceptor 361 ) 362 streamInterceptors = append(streamInterceptors, grpc_recovery.StreamServerInterceptor()) 363 unaryInterceptors = append(unaryInterceptors, grpc_recovery.UnaryServerInterceptor()) 364 365 //if metrics.Enabled { 366 // streamInterceptors = append(streamInterceptors, grpc_prometheus.StreamServerInterceptor) 367 // unaryInterceptors = append(unaryInterceptors, grpc_prometheus.UnaryServerInterceptor) 368 //} 369 370 //cpus := uint32(runtime.GOMAXPROCS(-1)) 371 opts := []grpc.ServerOption{ 372 //grpc.NumStreamWorkers(cpus), // reduce amount of goroutines 373 grpc.ReadBufferSize(0), // reduce buffers to save mem 374 grpc.WriteBufferSize(0), // reduce buffers to save mem 375 // Don't drop the connection, settings accordign to this comment on GitHub 376 // https://github.com/grpc/grpc-go/issues/3171#issuecomment-552796779 377 grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{ 378 MinTime: 10 * time.Second, 379 PermitWithoutStream: true, 380 }), 381 grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(streamInterceptors...)), 382 grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(unaryInterceptors...)), 383 } 384 if creds == nil { 385 // no specific opts 386 } else { 387 opts = append(opts, grpc.Creds(*creds)) 388 } 389 grpcServer := grpc.NewServer(opts...) 390 reflection.Register(grpcServer) // Register reflection service on gRPC server. 391 if txPoolServer != nil { 392 txpool_proto.RegisterTxpoolServer(grpcServer, txPoolServer) 393 } 394 if miningServer != nil { 395 txpool_proto.RegisterMiningServer(grpcServer, miningServer) 396 } 397 398 //if metrics.Enabled { 399 // grpc_prometheus.Register(grpcServer) 400 //} 401 402 healthServer := health.NewServer() 403 grpc_health_v1.RegisterHealthServer(grpcServer, healthServer) 404 405 go func() { 406 defer healthServer.Shutdown() 407 if err := grpcServer.Serve(lis); err != nil { 408 logger.Error("private RPC server fail", "err", err) 409 } 410 }() 411 logger.Info("Started gRPC server", "on", addr) 412 return grpcServer, nil 413 }