github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/sync/initial-sync/blocks_fetcher_utils.go (about) 1 package initialsync 2 3 import ( 4 "context" 5 "fmt" 6 "sort" 7 8 "github.com/libp2p/go-libp2p-core/peer" 9 "github.com/pkg/errors" 10 types "github.com/prysmaticlabs/eth2-types" 11 "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" 12 p2pTypes "github.com/prysmaticlabs/prysm/beacon-chain/p2p/types" 13 "github.com/prysmaticlabs/prysm/cmd/beacon-chain/flags" 14 p2ppb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" 15 "github.com/prysmaticlabs/prysm/proto/interfaces" 16 "github.com/prysmaticlabs/prysm/shared/bytesutil" 17 "github.com/prysmaticlabs/prysm/shared/params" 18 "github.com/sirupsen/logrus" 19 "go.opencensus.io/trace" 20 ) 21 22 // forkData represents alternative chain path supported by a given peer. 23 // Blocks are stored in an ascending slot order. The first block is guaranteed to have parent 24 // either in DB or initial sync cache. 25 type forkData struct { 26 peer peer.ID 27 blocks []interfaces.SignedBeaconBlock 28 } 29 30 // nonSkippedSlotAfter checks slots after the given one in an attempt to find a non-empty future slot. 31 // For efficiency only one random slot is checked per epoch, so returned slot might not be the first 32 // non-skipped slot. This shouldn't be a problem, as in case of adversary peer, we might get incorrect 33 // data anyway, so code that relies on this function must be robust enough to re-request, if no progress 34 // is possible with a returned value. 35 func (f *blocksFetcher) nonSkippedSlotAfter(ctx context.Context, slot types.Slot) (types.Slot, error) { 36 ctx, span := trace.StartSpan(ctx, "initialsync.nonSkippedSlotAfter") 37 defer span.End() 38 39 headEpoch, targetEpoch, peers := f.calculateHeadAndTargetEpochs() 40 log.WithFields(logrus.Fields{ 41 "start": slot, 42 "headEpoch": headEpoch, 43 "targetEpoch": targetEpoch, 44 }).Debug("Searching for non-skipped slot") 45 46 // Exit early if no peers with epoch higher than our known head are found. 47 if targetEpoch <= headEpoch { 48 return 0, errSlotIsTooHigh 49 } 50 51 // Transform peer list to avoid eclipsing (filter, shuffle, trim). 52 peers = f.filterPeers(ctx, peers, peersPercentagePerRequest) 53 return f.nonSkippedSlotAfterWithPeersTarget(ctx, slot, peers, targetEpoch) 54 } 55 56 // nonSkippedSlotWithPeersTarget traverse peers (supporting a given target epoch), in an attempt 57 // to find non-skipped slot among returned blocks. 58 func (f *blocksFetcher) nonSkippedSlotAfterWithPeersTarget( 59 ctx context.Context, slot types.Slot, peers []peer.ID, targetEpoch types.Epoch, 60 ) (types.Slot, error) { 61 // Exit early if no peers are ready. 62 if len(peers) == 0 { 63 return 0, errNoPeersAvailable 64 } 65 66 slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch 67 pidInd := 0 68 69 fetch := func(pid peer.ID, start types.Slot, count, step uint64) (types.Slot, error) { 70 req := &p2ppb.BeaconBlocksByRangeRequest{ 71 StartSlot: start, 72 Count: count, 73 Step: step, 74 } 75 blocks, err := f.requestBlocks(ctx, req, pid) 76 if err != nil { 77 return 0, err 78 } 79 if len(blocks) > 0 { 80 for _, block := range blocks { 81 if block.Block().Slot() > slot { 82 return block.Block().Slot(), nil 83 } 84 } 85 } 86 return 0, nil 87 } 88 89 // Start by checking several epochs fully, w/o resorting to random sampling. 90 start := slot + 1 91 end := start + nonSkippedSlotsFullSearchEpochs*slotsPerEpoch 92 for ind := start; ind < end; ind += slotsPerEpoch { 93 nextSlot, err := fetch(peers[pidInd%len(peers)], ind, uint64(slotsPerEpoch), 1) 94 if err != nil { 95 return 0, err 96 } 97 if nextSlot > slot { 98 return nextSlot, nil 99 } 100 pidInd++ 101 } 102 103 // Quickly find the close enough epoch where a non-empty slot definitely exists. 104 // Only single random slot per epoch is checked - allowing to move forward relatively quickly. 105 slot += nonSkippedSlotsFullSearchEpochs * slotsPerEpoch 106 upperBoundSlot, err := helpers.StartSlot(targetEpoch + 1) 107 if err != nil { 108 return 0, err 109 } 110 for ind := slot + 1; ind < upperBoundSlot; ind += (slotsPerEpoch * slotsPerEpoch) / 2 { 111 start := ind.Add(uint64(f.rand.Intn(int(slotsPerEpoch)))) 112 nextSlot, err := fetch(peers[pidInd%len(peers)], start, uint64(slotsPerEpoch/2), uint64(slotsPerEpoch)) 113 if err != nil { 114 return 0, err 115 } 116 pidInd++ 117 if nextSlot > slot && upperBoundSlot >= nextSlot { 118 upperBoundSlot = nextSlot 119 break 120 } 121 } 122 123 // Epoch with non-empty slot is located. Check all slots within two nearby epochs. 124 if upperBoundSlot > slotsPerEpoch { 125 upperBoundSlot -= slotsPerEpoch 126 } 127 upperBoundSlot, err = helpers.StartSlot(helpers.SlotToEpoch(upperBoundSlot)) 128 if err != nil { 129 return 0, err 130 } 131 nextSlot, err := fetch(peers[pidInd%len(peers)], upperBoundSlot, uint64(slotsPerEpoch*2), 1) 132 if err != nil { 133 return 0, err 134 } 135 s, err := helpers.StartSlot(targetEpoch + 1) 136 if err != nil { 137 return 0, err 138 } 139 if nextSlot < slot || s < nextSlot { 140 return 0, errors.New("invalid range for non-skipped slot") 141 } 142 return nextSlot, nil 143 } 144 145 // findFork queries all peers that have higher head slot, in an attempt to find 146 // ones that feature blocks from alternative branches. Once found, peer is further queried 147 // to find common ancestor slot. On success, all obtained blocks and peer is returned. 148 func (f *blocksFetcher) findFork(ctx context.Context, slot types.Slot) (*forkData, error) { 149 ctx, span := trace.StartSpan(ctx, "initialsync.findFork") 150 defer span.End() 151 152 // Safe-guard, since previous epoch is used when calculating. 153 slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch 154 if slot < slotsPerEpoch*2 { 155 return nil, fmt.Errorf("slot is too low to backtrack, min. expected %d", slotsPerEpoch*2) 156 } 157 158 // The current slot's epoch must be after the finalization epoch, 159 // triggering backtracking on earlier epochs is unnecessary. 160 finalizedEpoch := f.chain.FinalizedCheckpt().Epoch 161 epoch := helpers.SlotToEpoch(slot) 162 if epoch <= finalizedEpoch { 163 return nil, errors.New("slot is not after the finalized epoch, no backtracking is necessary") 164 } 165 // Update slot to the beginning of the current epoch (preserve original slot for comparison). 166 slot, err := helpers.StartSlot(epoch) 167 if err != nil { 168 return nil, err 169 } 170 171 // Select peers that have higher head slot, and potentially blocks from more favourable fork. 172 // Exit early if no peers are ready. 173 _, peers := f.p2p.Peers().BestNonFinalized(1, epoch+1) 174 if len(peers) == 0 { 175 return nil, errNoPeersAvailable 176 } 177 f.rand.Shuffle(len(peers), func(i, j int) { 178 peers[i], peers[j] = peers[j], peers[i] 179 }) 180 181 // Query all found peers, stop on peer with alternative blocks, and try backtracking. 182 for i, pid := range peers { 183 log.WithFields(logrus.Fields{ 184 "peer": pid, 185 "step": fmt.Sprintf("%d/%d", i+1, len(peers)), 186 }).Debug("Searching for alternative blocks") 187 fork, err := f.findForkWithPeer(ctx, pid, slot) 188 if err != nil { 189 log.WithFields(logrus.Fields{ 190 "peer": pid, 191 "error": err.Error(), 192 }).Debug("No alternative blocks found for peer") 193 continue 194 } 195 return fork, nil 196 } 197 return nil, errNoPeersWithAltBlocks 198 } 199 200 // findForkWithPeer loads some blocks from a peer in an attempt to find alternative blocks. 201 func (f *blocksFetcher) findForkWithPeer(ctx context.Context, pid peer.ID, slot types.Slot) (*forkData, error) { 202 // Safe-guard, since previous epoch is used when calculating. 203 slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch 204 if slot < slotsPerEpoch*2 { 205 return nil, fmt.Errorf("slot is too low to backtrack, min. expected %d", slotsPerEpoch*2) 206 } 207 208 // Locate non-skipped slot, supported by a given peer (can survive long periods of empty slots). 209 // When searching for non-empty slot, start an epoch earlier - for those blocks we 210 // definitely have roots. So, spotting a fork will be easier. It is not a problem if unknown 211 // block of the current fork is found: we are searching for forks when FSMs are stuck, so 212 // being able to progress on any fork is good. 213 pidState, err := f.p2p.Peers().ChainState(pid) 214 if err != nil { 215 return nil, fmt.Errorf("cannot obtain peer's status: %w", err) 216 } 217 nonSkippedSlot, err := f.nonSkippedSlotAfterWithPeersTarget( 218 ctx, slot-slotsPerEpoch, []peer.ID{pid}, helpers.SlotToEpoch(pidState.HeadSlot)) 219 if err != nil { 220 return nil, fmt.Errorf("cannot locate non-empty slot for a peer: %w", err) 221 } 222 223 // Request blocks starting from the first non-empty slot. 224 req := &p2ppb.BeaconBlocksByRangeRequest{ 225 StartSlot: nonSkippedSlot, 226 Count: uint64(slotsPerEpoch.Mul(2)), 227 Step: 1, 228 } 229 blocks, err := f.requestBlocks(ctx, req, pid) 230 if err != nil { 231 return nil, fmt.Errorf("cannot fetch blocks: %w", err) 232 } 233 234 // Traverse blocks, and if we've got one that doesn't have parent in DB, backtrack on it. 235 for i, block := range blocks { 236 parentRoot := bytesutil.ToBytes32(block.Block().ParentRoot()) 237 if !f.db.HasBlock(ctx, parentRoot) && !f.chain.HasInitSyncBlock(parentRoot) { 238 log.WithFields(logrus.Fields{ 239 "peer": pid, 240 "slot": block.Block().Slot(), 241 "root": fmt.Sprintf("%#x", parentRoot), 242 }).Debug("Block with unknown parent root has been found") 243 // Backtrack only if the first block is diverging, 244 // otherwise we already know the common ancestor slot. 245 if i == 0 { 246 // Backtrack on a root, to find a common ancestor from which we can resume syncing. 247 fork, err := f.findAncestor(ctx, pid, block) 248 if err != nil { 249 return nil, fmt.Errorf("failed to find common ancestor: %w", err) 250 } 251 return fork, nil 252 } 253 return &forkData{peer: pid, blocks: blocks}, nil 254 } 255 } 256 return nil, errors.New("no alternative blocks exist within scanned range") 257 } 258 259 // findAncestor tries to figure out common ancestor slot that connects a given root to known block. 260 func (f *blocksFetcher) findAncestor(ctx context.Context, pid peer.ID, block interfaces.SignedBeaconBlock) (*forkData, error) { 261 outBlocks := []interfaces.SignedBeaconBlock{block} 262 for i := uint64(0); i < backtrackingMaxHops; i++ { 263 parentRoot := bytesutil.ToBytes32(outBlocks[len(outBlocks)-1].Block().ParentRoot()) 264 if f.db.HasBlock(ctx, parentRoot) || f.chain.HasInitSyncBlock(parentRoot) { 265 // Common ancestor found, forward blocks back to processor. 266 sort.Slice(outBlocks, func(i, j int) bool { 267 return outBlocks[i].Block().Slot() < outBlocks[j].Block().Slot() 268 }) 269 return &forkData{ 270 peer: pid, 271 blocks: outBlocks, 272 }, nil 273 } 274 // Request block's parent. 275 req := &p2pTypes.BeaconBlockByRootsReq{parentRoot} 276 blocks, err := f.requestBlocksByRoot(ctx, req, pid) 277 if err != nil { 278 return nil, err 279 } 280 if len(blocks) == 0 { 281 break 282 } 283 outBlocks = append(outBlocks, blocks[0]) 284 } 285 return nil, errors.New("no common ancestor found") 286 } 287 288 // bestFinalizedSlot returns the highest finalized slot of the majority of connected peers. 289 func (f *blocksFetcher) bestFinalizedSlot() types.Slot { 290 finalizedEpoch, _ := f.p2p.Peers().BestFinalized( 291 params.BeaconConfig().MaxPeersToSync, f.chain.FinalizedCheckpt().Epoch) 292 return params.BeaconConfig().SlotsPerEpoch.Mul(uint64(finalizedEpoch)) 293 } 294 295 // bestNonFinalizedSlot returns the highest non-finalized slot of enough number of connected peers. 296 func (f *blocksFetcher) bestNonFinalizedSlot() types.Slot { 297 headEpoch := helpers.SlotToEpoch(f.chain.HeadSlot()) 298 targetEpoch, _ := f.p2p.Peers().BestNonFinalized(flags.Get().MinimumSyncPeers*2, headEpoch) 299 return params.BeaconConfig().SlotsPerEpoch.Mul(uint64(targetEpoch)) 300 } 301 302 // calculateHeadAndTargetEpochs return node's current head epoch, along with the best known target 303 // epoch. For the latter peers supporting that target epoch are returned as well. 304 func (f *blocksFetcher) calculateHeadAndTargetEpochs() (headEpoch, targetEpoch types.Epoch, peers []peer.ID) { 305 if f.mode == modeStopOnFinalizedEpoch { 306 headEpoch = f.chain.FinalizedCheckpt().Epoch 307 targetEpoch, peers = f.p2p.Peers().BestFinalized(params.BeaconConfig().MaxPeersToSync, headEpoch) 308 } else { 309 headEpoch = helpers.SlotToEpoch(f.chain.HeadSlot()) 310 targetEpoch, peers = f.p2p.Peers().BestNonFinalized(flags.Get().MinimumSyncPeers, headEpoch) 311 } 312 return headEpoch, targetEpoch, peers 313 }