package util import ( "github.com/synapsecns/sanguine/core" "math/big" "sync" ) // Chunk represents an individual chunk of startBlock-startBlock. // TODO: this needs to be moved to scribe. type Chunk struct { // StartBlock for this chunk. It is less then end block in ascending txes and greater in descending. StartBlock *big.Int // EndBlock for this chunk EndBlock *big.Int // ascending is whether or not the chunk was made in ascending mode. This is used // for min/max without a comparotor operation ascending bool } // MinBlock returns the minimum of start block and end block. This is useful because start/end change // based on the ordering of the chunk. func (c *Chunk) MinBlock() *big.Int { if c.ascending { return c.StartBlock } return c.EndBlock } // MaxBlock returns the minimum of start block and end block. This is useful because start/end change // based on the ordering of the chunk. func (c *Chunk) MaxBlock() *big.Int { if c.ascending { return c.EndBlock } return c.StartBlock } // baseChunkIterator contains a non-directional chunk iterator. type baseChunkIterator struct { // chunkSize is the chunk size chunkSize int // mux locks the iterator to prevent concurrent issues //nolint: structcheck // triggers a false positive. This is used in ascendingChunkIterator and descendingChunkIterator mux sync.Mutex } // ascendingChunkIterator returns an iterator going from lowest -> highest. type ascendingChunkIterator struct { *baseChunkIterator // endBlock is the last block to chunk at endBlock *big.Int // lastIteratedBlock is the most recent iterated block. This will always be less then endBlock lastIteratedBlock *big.Int } // descendingChunkIterator returns an iterator going from highest->lowest. type descendingChunkIterator struct { *baseChunkIterator // startBlock is the last block to chunk at startBlock *big.Int // lastIteratedBlock is the block we most recently iterator over. This will always be greater then start block lastIteratedBlock *big.Int } // ChunkIterator is an instantiation of a stateful iterator that groups startBlock-endBlock blocks into length // chunkSize. type ChunkIterator interface { // NextChunk gets the next chunk. If the iterator has completed null will be returned. NextChunk() *Chunk } // NewChunkIterator creates an iterator for a range of elements (startBlock-endBlock) split into groups the length of chunkSize // the final chunk has any remaining elements. An iterator is used here as opposed to a slice to save memory while iterating over *very // large* chunks.This follows the stateful iterator pattern: https://ewencp.org/blog/golang-iterators/index.html func NewChunkIterator(startBlock, endBlock *big.Int, chunkSize int, ascending bool) ChunkIterator { baseIterator := &baseChunkIterator{ chunkSize: chunkSize, } if ascending { return &ascendingChunkIterator{ baseChunkIterator: baseIterator, endBlock: core.CopyBigInt(endBlock), // lastIteratedBlock is set to StartBlock - 1 lastIteratedBlock: big.NewInt(0).Sub(startBlock, big.NewInt(1)), } } return &descendingChunkIterator{ baseChunkIterator: baseIterator, startBlock: core.CopyBigInt(startBlock), // lastIteratedBlock is set to endBlock + 1 since we subtract one on every chunk lastIteratedBlock: big.NewInt(0).Add(endBlock, big.NewInt(1)), } } // NextChunk gets the next chunk in descending order. Returns nil if complete. func (d *descendingChunkIterator) NextChunk() *Chunk { d.mux.Lock() defer d.mux.Unlock() startBlock := big.NewInt(0).Sub(d.lastIteratedBlock, big.NewInt(1)) // if the last block is greater than the start block, we're done if startBlock.Cmp(d.startBlock) < 0 { return nil } endBlock := big.NewInt(0).Sub(startBlock, big.NewInt(int64(d.chunkSize))) // if the last block is less then the end block, return the last block if endBlock.Cmp(d.startBlock) < 0 { endBlock = core.CopyBigInt(d.startBlock) } d.lastIteratedBlock = core.CopyBigInt(endBlock) return &Chunk{ StartBlock: startBlock, EndBlock: endBlock, ascending: false, } } // NextChunk gets the next chunk in ascending order. Returns nil if complete. func (c *ascendingChunkIterator) NextChunk() *Chunk { c.mux.Lock() defer c.mux.Unlock() startBlock := big.NewInt(0).Add(c.lastIteratedBlock, big.NewInt(1)) // if the last block is greater than the start block, we're done if startBlock.Cmp(c.endBlock) > 0 { return nil } endBlock := big.NewInt(0).Add(startBlock, big.NewInt(int64(c.chunkSize))) // if the last block is greater then the end block, return the last block if endBlock.Cmp(c.endBlock) > 0 { endBlock = core.CopyBigInt(c.endBlock) } c.lastIteratedBlock = core.CopyBigInt(endBlock) return &Chunk{ StartBlock: startBlock, EndBlock: endBlock, ascending: true, } }