github.com/prysmaticlabs/prysm@v1.4.4/slasher/beaconclient/receivers.go (about)

     1  package beaconclient
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"time"
     9  
    10  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    11  	"github.com/prysmaticlabs/prysm/shared/slotutil"
    12  	"github.com/sirupsen/logrus"
    13  	"go.opencensus.io/trace"
    14  	"google.golang.org/grpc/codes"
    15  	"google.golang.org/grpc/connectivity"
    16  	"google.golang.org/grpc/status"
    17  	"google.golang.org/protobuf/types/known/emptypb"
    18  )
    19  
    20  // reconnectPeriod is the frequency that we try to restart our
    21  // streams when the beacon chain is node does not respond.
    22  var reconnectPeriod = 5 * time.Second
    23  
    24  // ReceiveBlocks starts a gRPC client stream listener to obtain
    25  // blocks from the beacon node. Upon receiving a block, the service
    26  // broadcasts it to a feed for other services in slasher to subscribe to.
    27  func (s *Service) ReceiveBlocks(ctx context.Context) {
    28  	ctx, span := trace.StartSpan(ctx, "beaconclient.ReceiveBlocks")
    29  	defer span.End()
    30  	stream, err := s.cfg.BeaconClient.StreamBlocks(ctx, &ethpb.StreamBlocksRequest{} /* Prefers unverified block to catch slashing */)
    31  	if err != nil {
    32  		log.WithError(err).Error("Failed to retrieve blocks stream")
    33  		return
    34  	}
    35  	for {
    36  		res, err := stream.Recv()
    37  		// If the stream is closed, we stop the loop.
    38  		if errors.Is(err, io.EOF) {
    39  			break
    40  		}
    41  		// If context is canceled we stop the loop.
    42  		if ctx.Err() == context.Canceled {
    43  			log.WithError(ctx.Err()).Error("Context canceled - shutting down blocks receiver")
    44  			return
    45  		}
    46  		if err != nil {
    47  			if e, ok := status.FromError(err); ok {
    48  				switch e.Code() {
    49  				case codes.Canceled, codes.Internal, codes.Unavailable:
    50  					log.WithError(err).Infof("Trying to restart connection. rpc status: %v", e.Code())
    51  					err = s.restartBeaconConnection(ctx)
    52  					if err != nil {
    53  						log.WithError(err).Error("Could not restart beacon connection")
    54  						return
    55  					}
    56  					stream, err = s.cfg.BeaconClient.StreamBlocks(ctx, &ethpb.StreamBlocksRequest{} /* Prefers unverified block to catch slashing */)
    57  					if err != nil {
    58  						log.WithError(err).Error("Could not restart block stream")
    59  						return
    60  					}
    61  					log.Info("Block stream restarted...")
    62  				default:
    63  					log.WithError(err).Errorf("Could not receive block from beacon node. rpc status: %v", e.Code())
    64  					return
    65  				}
    66  			} else {
    67  				log.WithError(err).Error("Could not receive blocks from beacon node")
    68  				return
    69  			}
    70  		}
    71  		if res == nil {
    72  			continue
    73  		}
    74  		root, err := res.Block.HashTreeRoot()
    75  		if err != nil {
    76  			log.WithError(err).Error("Could not hash block")
    77  			return
    78  		}
    79  
    80  		log.WithFields(logrus.Fields{
    81  			"slot":           res.Block.Slot,
    82  			"proposer_index": res.Block.ProposerIndex,
    83  			"root":           fmt.Sprintf("%#x...", root[:8]),
    84  		}).Info("Received block from beacon node")
    85  		// We send the received block over the block feed.
    86  		s.blockFeed.Send(res)
    87  	}
    88  }
    89  
    90  // ReceiveAttestations starts a gRPC client stream listener to obtain
    91  // attestations from the beacon node. Upon receiving an attestation, the service
    92  // broadcasts it to a feed for other services in slasher to subscribe to.
    93  func (s *Service) ReceiveAttestations(ctx context.Context) {
    94  	ctx, span := trace.StartSpan(ctx, "beaconclient.ReceiveAttestations")
    95  	defer span.End()
    96  	stream, err := s.cfg.BeaconClient.StreamIndexedAttestations(ctx, &emptypb.Empty{})
    97  	if err != nil {
    98  		log.WithError(err).Error("Failed to retrieve attestations stream")
    99  		return
   100  	}
   101  
   102  	go s.collectReceivedAttestations(ctx)
   103  	for {
   104  		res, err := stream.Recv()
   105  		// If the stream is closed, we stop the loop.
   106  		if errors.Is(err, io.EOF) {
   107  			log.Info("Attestation stream closed")
   108  			break
   109  		}
   110  		// If context is canceled we stop the loop.
   111  		if ctx.Err() == context.Canceled {
   112  			log.WithError(ctx.Err()).Error("Context canceled - shutting down attestations receiver")
   113  			return
   114  		}
   115  		if err != nil {
   116  			if e, ok := status.FromError(err); ok {
   117  				switch e.Code() {
   118  				case codes.Canceled, codes.Internal, codes.Unavailable:
   119  					log.WithError(err).Infof("Trying to restart connection. rpc status: %v", e.Code())
   120  					err = s.restartBeaconConnection(ctx)
   121  					if err != nil {
   122  						log.WithError(err).Error("Could not restart beacon connection")
   123  						return
   124  					}
   125  					stream, err = s.cfg.BeaconClient.StreamIndexedAttestations(ctx, &emptypb.Empty{})
   126  					if err != nil {
   127  						log.WithError(err).Error("Could not restart attestation stream")
   128  						return
   129  					}
   130  					log.Info("Attestation stream restarted...")
   131  				default:
   132  					log.WithError(err).Errorf("Could not receive attestations from beacon node. rpc status: %v", e.Code())
   133  					return
   134  				}
   135  			} else {
   136  				log.WithError(err).Error("Could not receive attestations from beacon node")
   137  				return
   138  			}
   139  		}
   140  		if res == nil {
   141  			continue
   142  		}
   143  		s.receivedAttestationsBuffer <- res
   144  	}
   145  }
   146  
   147  func (s *Service) collectReceivedAttestations(ctx context.Context) {
   148  	ctx, span := trace.StartSpan(ctx, "beaconclient.collectReceivedAttestations")
   149  	defer span.End()
   150  
   151  	var atts []*ethpb.IndexedAttestation
   152  	halfSlot := slotutil.DivideSlotBy(2 /* 1/2 slot duration */)
   153  	ticker := time.NewTicker(halfSlot)
   154  	defer ticker.Stop()
   155  	for {
   156  		select {
   157  		case <-ticker.C:
   158  			if len(atts) > 0 {
   159  				s.collectedAttestationsBuffer <- atts
   160  				atts = []*ethpb.IndexedAttestation{}
   161  			}
   162  		case att := <-s.receivedAttestationsBuffer:
   163  			atts = append(atts, att)
   164  		case collectedAtts := <-s.collectedAttestationsBuffer:
   165  			if err := s.cfg.SlasherDB.SaveIndexedAttestations(ctx, collectedAtts); err != nil {
   166  				log.WithError(err).Error("Could not save indexed attestation")
   167  				continue
   168  			}
   169  			log.WithFields(logrus.Fields{
   170  				"amountSaved": len(collectedAtts),
   171  				"slot":        collectedAtts[0].Data.Slot,
   172  			}).Info("Attestations saved to slasher DB")
   173  			slasherNumAttestationsReceived.Add(float64(len(collectedAtts)))
   174  
   175  			// After saving, we send the received attestation over the attestation feed.
   176  			for _, att := range collectedAtts {
   177  				log.WithFields(logrus.Fields{
   178  					"slot":    att.Data.Slot,
   179  					"indices": att.AttestingIndices,
   180  				}).Debug("Sending attestation to detection service")
   181  				s.attestationFeed.Send(att)
   182  			}
   183  		case <-ctx.Done():
   184  			return
   185  		}
   186  	}
   187  }
   188  
   189  func (s *Service) restartBeaconConnection(ctx context.Context) error {
   190  	ticker := time.NewTicker(reconnectPeriod)
   191  	defer ticker.Stop()
   192  	for {
   193  		select {
   194  		case <-ticker.C:
   195  			if s.conn.GetState() == connectivity.TransientFailure || s.conn.GetState() == connectivity.Idle {
   196  				log.Debugf("Connection status %v", s.conn.GetState())
   197  				log.Info("Beacon node is still down")
   198  				continue
   199  			}
   200  			s, err := s.cfg.NodeClient.GetSyncStatus(ctx, &emptypb.Empty{})
   201  			if err != nil {
   202  				log.WithError(err).Error("Could not fetch sync status")
   203  				continue
   204  			}
   205  			if s == nil || s.Syncing {
   206  				log.Info("Waiting for beacon node to be fully synced...")
   207  				continue
   208  			}
   209  			log.Info("Beacon node is fully synced")
   210  			return nil
   211  		case <-ctx.Done():
   212  			log.Debug("Context closed, exiting reconnect routine")
   213  			return errors.New("context closed, no longer attempting to restart stream")
   214  		}
   215  	}
   216  }