github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/sync/validate_beacon_blocks.go (about) 1 package sync 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "time" 8 9 "github.com/libp2p/go-libp2p-core/peer" 10 pubsub "github.com/libp2p/go-libp2p-pubsub" 11 types "github.com/prysmaticlabs/eth2-types" 12 "github.com/prysmaticlabs/prysm/beacon-chain/core/blocks" 13 "github.com/prysmaticlabs/prysm/beacon-chain/core/feed" 14 blockfeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/block" 15 "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" 16 "github.com/prysmaticlabs/prysm/beacon-chain/core/state" 17 ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 18 "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1/wrapper" 19 "github.com/prysmaticlabs/prysm/proto/interfaces" 20 "github.com/prysmaticlabs/prysm/shared/bytesutil" 21 "github.com/prysmaticlabs/prysm/shared/params" 22 "github.com/prysmaticlabs/prysm/shared/timeutils" 23 "github.com/prysmaticlabs/prysm/shared/traceutil" 24 "github.com/sirupsen/logrus" 25 "go.opencensus.io/trace" 26 ) 27 28 // validateBeaconBlockPubSub checks that the incoming block has a valid BLS signature. 29 // Blocks that have already been seen are ignored. If the BLS signature is any valid signature, 30 // this method rebroadcasts the message. 31 func (s *Service) validateBeaconBlockPubSub(ctx context.Context, pid peer.ID, msg *pubsub.Message) pubsub.ValidationResult { 32 receivedTime := timeutils.Now() 33 // Validation runs on publish (not just subscriptions), so we should approve any message from 34 // ourselves. 35 if pid == s.cfg.P2P.PeerID() { 36 return pubsub.ValidationAccept 37 } 38 39 // We should not attempt to process blocks until fully synced, but propagation is OK. 40 if s.cfg.InitialSync.Syncing() { 41 return pubsub.ValidationIgnore 42 } 43 44 ctx, span := trace.StartSpan(ctx, "sync.validateBeaconBlockPubSub") 45 defer span.End() 46 47 m, err := s.decodePubsubMessage(msg) 48 if err != nil { 49 log.WithError(err).Debug("Could not decode message") 50 traceutil.AnnotateError(span, err) 51 return pubsub.ValidationReject 52 } 53 54 s.validateBlockLock.Lock() 55 defer s.validateBlockLock.Unlock() 56 57 rblk, ok := m.(*ethpb.SignedBeaconBlock) 58 if !ok { 59 log.WithError(errors.New("msg is not ethpb.SignedBeaconBlock")).Debug("Rejected block") 60 return pubsub.ValidationReject 61 } 62 blk := wrapper.WrappedPhase0SignedBeaconBlock(rblk) 63 64 if blk.IsNil() || blk.Block().IsNil() { 65 log.WithError(errors.New("block.Block is nil")).Debug("Rejected block") 66 return pubsub.ValidationReject 67 } 68 69 // Broadcast the block on a feed to notify other services in the beacon node 70 // of a received block (even if it does not process correctly through a state transition). 71 s.cfg.BlockNotifier.BlockFeed().Send(&feed.Event{ 72 Type: blockfeed.ReceivedBlock, 73 Data: &blockfeed.ReceivedBlockData{ 74 SignedBlock: blk, 75 }, 76 }) 77 78 // Verify the block is the first block received for the proposer for the slot. 79 if s.hasSeenBlockIndexSlot(blk.Block().Slot(), blk.Block().ProposerIndex()) { 80 return pubsub.ValidationIgnore 81 } 82 83 blockRoot, err := blk.Block().HashTreeRoot() 84 if err != nil { 85 log.WithError(err).WithField("blockSlot", blk.Block().Slot()).Debug("Ignored block") 86 return pubsub.ValidationIgnore 87 } 88 if s.cfg.DB.HasBlock(ctx, blockRoot) { 89 return pubsub.ValidationIgnore 90 } 91 // Check if parent is a bad block and then reject the block. 92 if s.hasBadBlock(bytesutil.ToBytes32(blk.Block().ParentRoot())) { 93 s.setBadBlock(ctx, blockRoot) 94 e := fmt.Errorf("received block with root %#x that has an invalid parent %#x", blockRoot, blk.Block().ParentRoot()) 95 log.WithError(e).WithField("blockSlot", blk.Block().Slot()).Debug("Rejected block") 96 return pubsub.ValidationReject 97 } 98 99 s.pendingQueueLock.RLock() 100 if s.seenPendingBlocks[blockRoot] { 101 s.pendingQueueLock.RUnlock() 102 return pubsub.ValidationIgnore 103 } 104 s.pendingQueueLock.RUnlock() 105 106 // Be lenient in handling early blocks. Instead of discarding blocks arriving later than 107 // MAXIMUM_GOSSIP_CLOCK_DISPARITY in future, we tolerate blocks arriving at max two slots 108 // earlier (SECONDS_PER_SLOT * 2 seconds). Queue such blocks and process them at the right slot. 109 genesisTime := uint64(s.cfg.Chain.GenesisTime().Unix()) 110 if err := helpers.VerifySlotTime(genesisTime, blk.Block().Slot(), earlyBlockProcessingTolerance); err != nil { 111 log.WithError(err).WithField("blockSlot", blk.Block().Slot()).Debug("Ignored block") 112 return pubsub.ValidationIgnore 113 } 114 115 // Add metrics for block arrival time subtracts slot start time. 116 if err := captureArrivalTimeMetric(genesisTime, blk.Block().Slot()); err != nil { 117 log.WithError(err).WithField("blockSlot", blk.Block().Slot()).Debug("Ignored block") 118 return pubsub.ValidationIgnore 119 } 120 121 startSlot, err := helpers.StartSlot(s.cfg.Chain.FinalizedCheckpt().Epoch) 122 if err != nil { 123 log.WithError(err).WithField("blockSlot", blk.Block().Slot()).Debug("Ignored block") 124 return pubsub.ValidationIgnore 125 } 126 if startSlot >= blk.Block().Slot() { 127 e := fmt.Errorf("finalized slot %d greater or equal to block slot %d", startSlot, blk.Block().Slot()) 128 log.WithError(e).WithField("blockSlot", blk.Block().Slot()).Debug("Ignored block") 129 return pubsub.ValidationIgnore 130 } 131 132 // Process the block if the clock jitter is less than MAXIMUM_GOSSIP_CLOCK_DISPARITY. 133 // Otherwise queue it for processing in the right slot. 134 if isBlockQueueable(genesisTime, blk.Block().Slot(), receivedTime) { 135 s.pendingQueueLock.Lock() 136 if err := s.insertBlockToPendingQueue(blk.Block().Slot(), blk, blockRoot); err != nil { 137 s.pendingQueueLock.Unlock() 138 log.WithError(err).WithField("blockSlot", blk.Block().Slot()).Debug("Ignored block") 139 return pubsub.ValidationIgnore 140 } 141 s.pendingQueueLock.Unlock() 142 log.WithError(errors.New("early block")).WithField("currentSlot", s.cfg.Chain.CurrentSlot()).WithField("blockSlot", blk.Block().Slot()).Debug("Ignored block") 143 return pubsub.ValidationIgnore 144 } 145 146 // Handle block when the parent is unknown. 147 if !s.cfg.DB.HasBlock(ctx, bytesutil.ToBytes32(blk.Block().ParentRoot())) { 148 s.pendingQueueLock.Lock() 149 if err := s.insertBlockToPendingQueue(blk.Block().Slot(), blk, blockRoot); err != nil { 150 s.pendingQueueLock.Unlock() 151 log.WithError(err).WithField("blockSlot", blk.Block().Slot()).Debug("Ignored block") 152 return pubsub.ValidationIgnore 153 } 154 s.pendingQueueLock.Unlock() 155 log.WithError(errors.New("unknown parent")).WithField("blockSlot", blk.Block().Slot()).Debug("Ignored block") 156 return pubsub.ValidationIgnore 157 } 158 159 if err := s.validateBeaconBlock(ctx, blk, blockRoot); err != nil { 160 log.WithError(err).WithField("blockSlot", blk.Block().Slot()).Warn("Rejected block") 161 return pubsub.ValidationReject 162 } 163 // Record attribute of valid block. 164 span.AddAttributes(trace.Int64Attribute("slotInEpoch", int64(blk.Block().Slot()%params.BeaconConfig().SlotsPerEpoch))) 165 msg.ValidatorData = rblk // Used in downstream subscriber 166 167 // Log the arrival time of the accepted block 168 startTime, err := helpers.SlotToTime(genesisTime, blk.Block().Slot()) 169 if err != nil { 170 log.WithError(err).WithField("blockSlot", blk.Block().Slot()).Debug("Couldn't get slot start time") 171 return pubsub.ValidationIgnore 172 } 173 log.WithFields(logrus.Fields{ 174 "blockSlot": blk.Block().Slot(), 175 "sinceSlotStartTime": receivedTime.Sub(startTime), 176 }).Debug("Received block") 177 return pubsub.ValidationAccept 178 } 179 180 func (s *Service) validateBeaconBlock(ctx context.Context, blk interfaces.SignedBeaconBlock, blockRoot [32]byte) error { 181 ctx, span := trace.StartSpan(ctx, "sync.validateBeaconBlock") 182 defer span.End() 183 184 if err := s.cfg.Chain.VerifyBlkDescendant(ctx, bytesutil.ToBytes32(blk.Block().ParentRoot())); err != nil { 185 s.setBadBlock(ctx, blockRoot) 186 return err 187 } 188 189 hasStateSummaryDB := s.cfg.DB.HasStateSummary(ctx, bytesutil.ToBytes32(blk.Block().ParentRoot())) 190 if !hasStateSummaryDB { 191 _, err := s.cfg.StateGen.RecoverStateSummary(ctx, bytesutil.ToBytes32(blk.Block().ParentRoot())) 192 if err != nil { 193 return err 194 } 195 } 196 parentState, err := s.cfg.StateGen.StateByRoot(ctx, bytesutil.ToBytes32(blk.Block().ParentRoot())) 197 if err != nil { 198 return err 199 } 200 201 if err := blocks.VerifyBlockSignature(parentState, blk.Block().ProposerIndex(), blk.Signature(), blk.Block().HashTreeRoot); err != nil { 202 s.setBadBlock(ctx, blockRoot) 203 return err 204 } 205 // There is an epoch lookahead for validator proposals 206 // for the next epoch from the start of our current epoch. We 207 // use the randao mix at the end of the previous epoch as the seed 208 // to determine proposals. 209 // Seed for Next Epoch => Derived From Randao Mix at the end of the Previous Epoch. 210 // Which is why we simply set the slot over here. 211 nextEpoch := helpers.NextEpoch(parentState) 212 expectedEpoch := helpers.SlotToEpoch(blk.Block().Slot()) 213 if expectedEpoch <= nextEpoch { 214 err = parentState.SetSlot(blk.Block().Slot()) 215 if err != nil { 216 return err 217 } 218 } else { 219 // In the event the block is more than an epoch ahead from its 220 // parent state, we have to advance the state forward. 221 parentState, err = state.ProcessSlots(ctx, parentState, blk.Block().Slot()) 222 if err != nil { 223 return err 224 } 225 } 226 idx, err := helpers.BeaconProposerIndex(parentState) 227 if err != nil { 228 return err 229 } 230 if blk.Block().ProposerIndex() != idx { 231 s.setBadBlock(ctx, blockRoot) 232 return errors.New("incorrect proposer index") 233 } 234 235 return nil 236 } 237 238 // Returns true if the block is not the first block proposed for the proposer for the slot. 239 func (s *Service) hasSeenBlockIndexSlot(slot types.Slot, proposerIdx types.ValidatorIndex) bool { 240 s.seenBlockLock.RLock() 241 defer s.seenBlockLock.RUnlock() 242 b := append(bytesutil.Bytes32(uint64(slot)), bytesutil.Bytes32(uint64(proposerIdx))...) 243 _, seen := s.seenBlockCache.Get(string(b)) 244 return seen 245 } 246 247 // Set block proposer index and slot as seen for incoming blocks. 248 func (s *Service) setSeenBlockIndexSlot(slot types.Slot, proposerIdx types.ValidatorIndex) { 249 s.seenBlockLock.Lock() 250 defer s.seenBlockLock.Unlock() 251 b := append(bytesutil.Bytes32(uint64(slot)), bytesutil.Bytes32(uint64(proposerIdx))...) 252 s.seenBlockCache.Add(string(b), true) 253 } 254 255 // Returns true if the block is marked as a bad block. 256 func (s *Service) hasBadBlock(root [32]byte) bool { 257 s.badBlockLock.RLock() 258 defer s.badBlockLock.RUnlock() 259 _, seen := s.badBlockCache.Get(string(root[:])) 260 return seen 261 } 262 263 // Set bad block in the cache. 264 func (s *Service) setBadBlock(ctx context.Context, root [32]byte) { 265 s.badBlockLock.Lock() 266 defer s.badBlockLock.Unlock() 267 if ctx.Err() != nil { // Do not mark block as bad if it was due to context error. 268 return 269 } 270 s.badBlockCache.Add(string(root[:]), true) 271 } 272 273 // This captures metrics for block arrival time by subtracts slot start time. 274 func captureArrivalTimeMetric(genesisTime uint64, currentSlot types.Slot) error { 275 startTime, err := helpers.SlotToTime(genesisTime, currentSlot) 276 if err != nil { 277 return err 278 } 279 ms := timeutils.Now().Sub(startTime) / time.Millisecond 280 arrivalBlockPropagationHistogram.Observe(float64(ms)) 281 282 return nil 283 } 284 285 // isBlockQueueable checks if the slot_time in the block is greater than 286 // current_time + MAXIMUM_GOSSIP_CLOCK_DISPARITY. in short, this function 287 // returns true if the corresponding block should be queued and false if 288 // the block should be processed immediately. 289 func isBlockQueueable(genesisTime uint64, slot types.Slot, receivedTime time.Time) bool { 290 slotTime, err := helpers.SlotToTime(genesisTime, slot) 291 if err != nil { 292 return false 293 } 294 295 currentTimeWithDisparity := receivedTime.Add(params.BeaconNetworkConfig().MaximumGossipClockDisparity) 296 return currentTimeWithDisparity.Unix() < slotTime.Unix() 297 }