github.com/amazechain/amc@v0.1.3/internal/sync/initial-sync/blocks_fetcher.go (about) 1 package initialsync 2 3 import ( 4 "context" 5 "github.com/amazechain/amc/api/protocol/sync_pb" 6 "github.com/amazechain/amc/api/protocol/types_pb" 7 "github.com/amazechain/amc/common" 8 "github.com/amazechain/amc/common/crypto/rand" 9 "github.com/amazechain/amc/internal/p2p" 10 leakybucket "github.com/amazechain/amc/internal/p2p/leaky-bucket" 11 amcsync "github.com/amazechain/amc/internal/sync" 12 "github.com/amazechain/amc/utils" 13 "github.com/holiman/uint256" 14 "sync" 15 "time" 16 17 "github.com/libp2p/go-libp2p/core/peer" 18 "github.com/pkg/errors" 19 "go.opencensus.io/trace" 20 ) 21 22 const ( 23 // maxPendingRequests limits how many concurrent fetch request one can initiate. 24 maxPendingRequests = 64 25 // peersPercentagePerRequest caps percentage of peers to be used in a request. 26 peersPercentagePerRequest = 0.75 27 // handshakePollingInterval is a polling interval for checking the number of received handshakes. 28 handshakePollingInterval = 5 * time.Second 29 // peerLocksPollingInterval is a polling interval for checking if there are stale peer locks. 30 peerLocksPollingInterval = 5 * time.Minute 31 // peerLockMaxAge is maximum time before stale lock is purged. 32 peerLockMaxAge = 60 * time.Minute 33 // peerFilterCapacityWeight defines how peer's capacity affects peer's score. Provided as 34 // percentage, i.e. 0.3 means capacity will determine 30% of peer's score. 35 peerFilterCapacityWeight = 0.2 36 ) 37 38 var ( 39 errNoPeersAvailable = errors.New("no peers available, waiting for reconnect") 40 errFetcherCtxIsDone = errors.New("fetcher's context is done, reinitialize") 41 errBlockNrIsTooHigh = errors.New("block number is higher than the target block number") 42 errBlockAlreadyProcessed = errors.New("block is already processed") 43 errParentDoesNotExist = errors.New("node doesn't have a parent in db with root") 44 errNoPeersWithAltBlocks = errors.New("no peers with alternative blocks found") 45 ) 46 47 // blocksFetcherConfig is a config to setup the block fetcher. 48 type blocksFetcherConfig struct { 49 chain common.IBlockChain 50 p2p p2p.P2P 51 peerFilterCapacityWeight float64 52 mode syncMode 53 } 54 55 // blocksFetcher is a service to fetch chain data from peers. 56 // On an incoming requests, requested block range is evenly divided 57 // among available peers (for fair network load distribution). 58 type blocksFetcher struct { 59 sync.Mutex 60 ctx context.Context 61 cancel context.CancelFunc 62 rand *rand.Rand 63 chain common.IBlockChain 64 p2p p2p.P2P 65 blocksPerPeriod uint64 66 rateLimiter *leakybucket.Collector 67 peerLocks map[peer.ID]*peerLock 68 fetchRequests chan *fetchRequestParams 69 fetchResponses chan *fetchRequestResponse 70 capacityWeight float64 // how remaining capacity affects peer selection 71 mode syncMode // allows to use fetcher in different sync scenarios 72 quit chan struct{} // termination notifier 73 } 74 75 // peerLock restricts fetcher actions on per peer basis. Currently, used for rate limiting. 76 type peerLock struct { 77 sync.Mutex 78 accessed time.Time 79 } 80 81 // fetchRequestParams holds parameters necessary to schedule a fetch request. 82 type fetchRequestParams struct { 83 ctx context.Context // if provided, it is used instead of global fetcher's context 84 start *uint256.Int // starting slot 85 count uint64 // how many slots to receive (fetcher may return fewer slots) 86 } 87 88 // fetchRequestResponse is a combined type to hold results of both successful executions and errors. 89 // Valid usage pattern will be to check whether result's `err` is nil, before using `blocks`. 90 type fetchRequestResponse struct { 91 pid peer.ID 92 start *uint256.Int 93 count uint64 94 blocks []*types_pb.Block 95 err error 96 } 97 98 // newBlocksFetcher creates ready to use fetcher. 99 func newBlocksFetcher(ctx context.Context, cfg *blocksFetcherConfig) *blocksFetcher { 100 101 // Initialize block limits. 102 allowedBlocksPerSecond := float64(cfg.p2p.GetConfig().P2PLimit.BlockBatchLimit) 103 allowedBlocksBurst := int64(cfg.p2p.GetConfig().P2PLimit.BlockBatchLimitBurstFactor * cfg.p2p.GetConfig().P2PLimit.BlockBatchLimit) 104 105 blockLimiterPeriod := time.Duration(cfg.p2p.GetConfig().P2PLimit.BlockBatchLimiterPeriod) * time.Second 106 107 // Allow fetcher to go almost to the full burst capacity (less a single batch). 108 //rateLimiter := leakybucket.NewCollector(allowedBlocksPerSecond, allowedBlocksBurst-allowedBlocksBurst, blockLimiterPeriod, false /* deleteEmptyBuckets */) 109 rateLimiter := leakybucket.NewCollector(allowedBlocksPerSecond, allowedBlocksBurst, blockLimiterPeriod, false /* deleteEmptyBuckets */) 110 111 capacityWeight := cfg.peerFilterCapacityWeight 112 if capacityWeight >= 1 { 113 capacityWeight = peerFilterCapacityWeight 114 } 115 116 ctx, cancel := context.WithCancel(ctx) 117 return &blocksFetcher{ 118 ctx: ctx, 119 cancel: cancel, 120 rand: rand.NewGenerator(), 121 chain: cfg.chain, 122 p2p: cfg.p2p, 123 blocksPerPeriod: uint64(allowedBlocksPerSecond), 124 rateLimiter: rateLimiter, 125 peerLocks: make(map[peer.ID]*peerLock), 126 fetchRequests: make(chan *fetchRequestParams, maxPendingRequests), 127 fetchResponses: make(chan *fetchRequestResponse, maxPendingRequests), 128 capacityWeight: capacityWeight, 129 mode: cfg.mode, 130 quit: make(chan struct{}), 131 } 132 } 133 134 // start boots up the fetcher, which starts listening for incoming fetch requests. 135 func (f *blocksFetcher) start() error { 136 select { 137 case <-f.ctx.Done(): 138 return errFetcherCtxIsDone 139 default: 140 go f.loop() 141 return nil 142 } 143 } 144 145 // stop terminates all fetcher operations. 146 func (f *blocksFetcher) stop() { 147 defer func() { 148 if f.rateLimiter != nil { 149 f.rateLimiter.Free() 150 f.rateLimiter = nil 151 } 152 }() 153 f.cancel() 154 <-f.quit // make sure that loop() is done 155 } 156 157 // requestResponses exposes a channel into which fetcher pushes generated request responses. 158 func (f *blocksFetcher) requestResponses() <-chan *fetchRequestResponse { 159 return f.fetchResponses 160 } 161 162 // loop is a main fetcher loop, listens for incoming requests/cancellations, forwards outgoing responses. 163 func (f *blocksFetcher) loop() { 164 defer close(f.quit) 165 166 // Wait for all loop's goroutines to finish, and safely release resources. 167 wg := &sync.WaitGroup{} 168 defer func() { 169 wg.Wait() 170 close(f.fetchResponses) 171 }() 172 173 // Periodically remove stale peer locks. 174 go func() { 175 ticker := time.NewTicker(peerLocksPollingInterval) 176 defer ticker.Stop() 177 for { 178 select { 179 case <-ticker.C: 180 f.removeStalePeerLocks(peerLockMaxAge) 181 case <-f.ctx.Done(): 182 return 183 } 184 } 185 }() 186 187 // Main loop. 188 for { 189 // Make sure there is are available peers before processing requests. 190 if _, err := f.waitForMinimumPeers(f.ctx); err != nil { 191 log.Error("cannot wait peers", "err", err) 192 } 193 194 select { 195 case <-f.ctx.Done(): 196 log.Debug("Context closed, exiting goroutine (blocks fetcher)") 197 return 198 case req := <-f.fetchRequests: 199 wg.Add(1) 200 go func() { 201 defer wg.Done() 202 select { 203 case <-f.ctx.Done(): 204 case f.fetchResponses <- f.handleRequest(req.ctx, req.start, req.count): 205 } 206 }() 207 } 208 } 209 } 210 211 // scheduleRequest adds request to incoming queue. 212 func (f *blocksFetcher) scheduleRequest(ctx context.Context, start *uint256.Int, count uint64) error { 213 if ctx.Err() != nil { 214 return ctx.Err() 215 } 216 217 request := &fetchRequestParams{ 218 ctx: ctx, 219 start: start, 220 count: count, 221 } 222 select { 223 case <-f.ctx.Done(): 224 return errFetcherCtxIsDone 225 case f.fetchRequests <- request: 226 } 227 return nil 228 } 229 230 // handleRequest parses fetch request and forwards it to response builder. 231 func (f *blocksFetcher) handleRequest(ctx context.Context, start *uint256.Int, count uint64) *fetchRequestResponse { 232 ctx, span := trace.StartSpan(ctx, "initialsync.handleRequest") 233 defer span.End() 234 235 response := &fetchRequestResponse{ 236 start: start, 237 count: count, 238 blocks: []*types_pb.Block{}, 239 err: nil, 240 } 241 242 if ctx.Err() != nil { 243 response.err = ctx.Err() 244 return response 245 } 246 247 _, peers := f.p2p.Peers().BestPeers(f.p2p.GetConfig().MinSyncPeers, f.chain.CurrentBlock().Number64()) 248 if len(peers) == 0 { 249 response.err = errNoPeersAvailable 250 return response 251 } 252 253 response.blocks, response.pid, response.err = f.fetchBlocksFromPeer(ctx, start, count, peers) 254 return response 255 } 256 257 // fetchBlocksFromPeer fetches blocks from a single randomly selected peer. 258 func (f *blocksFetcher) fetchBlocksFromPeer(ctx context.Context, start *uint256.Int, count uint64, peers []peer.ID) ([]*types_pb.Block, peer.ID, error) { 259 ctx, span := trace.StartSpan(ctx, "initialsync.fetchBlocksFromPeer") 260 defer span.End() 261 262 peers = f.filterPeers(ctx, peers, peersPercentagePerRequest) 263 req := &sync_pb.BodiesByRangeRequest{ 264 StartBlockNumber: utils.ConvertUint256IntToH256(start), 265 Count: count, 266 Step: 1, 267 } 268 for i := 0; i < len(peers); i++ { 269 blocks, err := f.requestBlocks(ctx, req, peers[i]) 270 if err == nil { 271 f.p2p.Peers().Scorers().BlockProviderScorer().Touch(peers[i]) 272 return blocks, peers[i], err 273 } else { 274 log.Warn("Could not request blocks by range", "err", err) 275 } 276 } 277 return nil, "", errNoPeersAvailable 278 } 279 280 // requestBlocks is a wrapper for handling BeaconBlocksByRangeRequest requests/streams. 281 func (f *blocksFetcher) requestBlocks(ctx context.Context, req *sync_pb.BodiesByRangeRequest, pid peer.ID) ([]*types_pb.Block, error) { 282 if ctx.Err() != nil { 283 return nil, ctx.Err() 284 } 285 l := f.peerLock(pid) 286 l.Lock() 287 log.Debug("Requesting blocks", 288 "peer", pid, 289 "start", utils.ConvertH256ToUint256Int(req.StartBlockNumber).Uint64(), 290 "count", req.Count, 291 "step", req.Step, 292 "capacity", f.rateLimiter.Remaining(pid.String()), 293 "score", f.p2p.Peers().Scorers().BlockProviderScorer().FormatScorePretty(pid), 294 ) 295 296 if f.rateLimiter.Remaining(pid.String()) < int64(req.Count) { 297 if err := f.waitForBandwidth(pid, req.Count); err != nil { 298 l.Unlock() 299 return nil, err 300 } 301 } 302 f.rateLimiter.Add(pid.String(), int64(req.Count)) 303 l.Unlock() 304 return amcsync.SendBodiesByRangeRequest(ctx, f.chain, f.p2p, pid, req, nil) 305 } 306 307 // waitForBandwidth blocks up until peer's bandwidth is restored. 308 func (f *blocksFetcher) waitForBandwidth(pid peer.ID, count uint64) error { 309 310 rem := f.rateLimiter.Remaining(pid.String()) 311 if uint64(rem) >= count { 312 // Exit early if we have sufficient capacity 313 return nil 314 } 315 //todo 316 //intCount, err := math.Int(count) 317 //if err != nil { 318 // return err 319 //} 320 toWait := timeToWait(int64(count), rem, f.rateLimiter.Capacity(), f.rateLimiter.TillEmpty(pid.String())) 321 timer := time.NewTimer(toWait) 322 323 log.Debug("Slowing down for rate limit", "peer", pid, "timeToWait", common.PrettyDuration(toWait)) 324 defer timer.Stop() 325 select { 326 case <-f.ctx.Done(): 327 return errFetcherCtxIsDone 328 case <-timer.C: 329 // Peer has gathered enough capacity to be polled again. 330 } 331 return nil 332 } 333 334 // Determine how long it will take for us to have the required number of blocks allowed by our rate limiter. 335 // We do this by calculating the duration till the rate limiter can request these blocks without exceeding 336 // the provided bandwidth limits per peer. 337 func timeToWait(wanted, rem, capacity int64, timeTillEmpty time.Duration) time.Duration { 338 // Defensive check if we have more than enough blocks 339 // to request from the peer. 340 if rem >= wanted { 341 return 0 342 } 343 // Handle edge case where capacity is equal to the remaining amount 344 // of blocks. This also handles the impossible case in where remaining blocks 345 // exceed the limiter's capacity. 346 if capacity <= rem { 347 return 0 348 } 349 blocksNeeded := wanted - rem 350 currentNumBlks := capacity - rem 351 expectedTime := int64(timeTillEmpty) * blocksNeeded / currentNumBlks 352 return time.Duration(expectedTime) 353 }