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  }