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  }