github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/network/bqueue/queue.go (about) 1 package bqueue 2 3 import ( 4 "sync" 5 "sync/atomic" 6 7 "github.com/nspcc-dev/neo-go/pkg/core/block" 8 "go.uber.org/zap" 9 ) 10 11 // Blockqueuer is an interface for a block queue. 12 type Blockqueuer interface { 13 AddBlock(block *block.Block) error 14 AddHeaders(...*block.Header) error 15 BlockHeight() uint32 16 } 17 18 // Queue is the block queue. 19 type Queue struct { 20 log *zap.Logger 21 queueLock sync.RWMutex 22 queue []*block.Block 23 lastQ uint32 24 checkBlocks chan struct{} 25 chain Blockqueuer 26 relayF func(*block.Block) 27 discarded atomic.Bool 28 len int 29 lenUpdateF func(int) 30 } 31 32 // CacheSize is the amount of blocks above the current height 33 // which are stored in the queue. 34 const CacheSize = 2000 35 36 func indexToPosition(i uint32) int { 37 return int(i) % CacheSize 38 } 39 40 // New creates an instance of BlockQueue. 41 func New(bc Blockqueuer, log *zap.Logger, relayer func(*block.Block), lenMetricsUpdater func(l int)) *Queue { 42 if log == nil { 43 return nil 44 } 45 46 return &Queue{ 47 log: log, 48 queue: make([]*block.Block, CacheSize), 49 checkBlocks: make(chan struct{}, 1), 50 chain: bc, 51 relayF: relayer, 52 lenUpdateF: lenMetricsUpdater, 53 } 54 } 55 56 // Run runs the BlockQueue queueing loop. It must be called in a separate routine. 57 func (bq *Queue) Run() { 58 var lastHeight = bq.chain.BlockHeight() 59 for { 60 _, ok := <-bq.checkBlocks 61 if !ok { 62 break 63 } 64 for { 65 h := bq.chain.BlockHeight() 66 pos := indexToPosition(h + 1) 67 bq.queueLock.Lock() 68 b := bq.queue[pos] 69 // The chain moved forward using blocks from other sources (consensus). 70 for i := lastHeight; i < h; i++ { 71 old := indexToPosition(i + 1) 72 if bq.queue[old] != nil && bq.queue[old].Index == i { 73 bq.len-- 74 bq.queue[old] = nil 75 } 76 } 77 bq.queueLock.Unlock() 78 lastHeight = h 79 if b == nil { 80 break 81 } 82 83 err := bq.chain.AddBlock(b) 84 if err != nil { 85 // The block might already be added by the consensus. 86 if bq.chain.BlockHeight() < b.Index { 87 bq.log.Warn("blockQueue: failed adding block into the blockchain", 88 zap.String("error", err.Error()), 89 zap.Uint32("blockHeight", bq.chain.BlockHeight()), 90 zap.Uint32("nextIndex", b.Index)) 91 } 92 } else if bq.relayF != nil { 93 bq.relayF(b) 94 } 95 bq.queueLock.Lock() 96 bq.len-- 97 l := bq.len 98 if bq.queue[pos] == b { 99 bq.queue[pos] = nil 100 } 101 bq.queueLock.Unlock() 102 if bq.lenUpdateF != nil { 103 bq.lenUpdateF(l) 104 } 105 } 106 } 107 } 108 109 // PutBlock enqueues block to be added to the chain. 110 func (bq *Queue) PutBlock(block *block.Block) error { 111 h := bq.chain.BlockHeight() 112 bq.queueLock.Lock() 113 defer bq.queueLock.Unlock() 114 if bq.discarded.Load() { 115 return nil 116 } 117 if block.Index <= h || h+CacheSize < block.Index { 118 // can easily happen when fetching the same blocks from 119 // different peers, thus not considered as error 120 return nil 121 } 122 pos := indexToPosition(block.Index) 123 // If we already have it, keep the old block, throw away the new one. 124 if bq.queue[pos] == nil || bq.queue[pos].Index < block.Index { 125 bq.len++ 126 bq.queue[pos] = block 127 for pos < CacheSize && bq.queue[pos] != nil && bq.lastQ+1 == bq.queue[pos].Index { 128 bq.lastQ = bq.queue[pos].Index 129 pos++ 130 } 131 } 132 // update metrics 133 if bq.lenUpdateF != nil { 134 bq.lenUpdateF(bq.len) 135 } 136 select { 137 case bq.checkBlocks <- struct{}{}: 138 // ok, signalled to goroutine processing queue 139 default: 140 // it's already busy processing blocks 141 } 142 return nil 143 } 144 145 // LastQueued returns the index of the last queued block and the queue's capacity 146 // left. 147 func (bq *Queue) LastQueued() (uint32, int) { 148 bq.queueLock.RLock() 149 defer bq.queueLock.RUnlock() 150 return bq.lastQ, CacheSize - bq.len 151 } 152 153 // Discard stops the queue and prevents it from accepting more blocks to enqueue. 154 func (bq *Queue) Discard() { 155 if bq.discarded.CompareAndSwap(false, true) { 156 bq.queueLock.Lock() 157 close(bq.checkBlocks) 158 // Technically we could bq.queue = nil, but this would cost 159 // another if in Run(). 160 for i := 0; i < len(bq.queue); i++ { 161 bq.queue[i] = nil 162 } 163 bq.len = 0 164 bq.queueLock.Unlock() 165 } 166 }