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, ðpb.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, ðpb.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 }