github.com/0xsequence/ethkit@v1.25.0/ethmonitor/chain.go (about)

     1  package ethmonitor
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	"github.com/0xsequence/ethkit/go-ethereum/common"
     8  	"github.com/0xsequence/ethkit/go-ethereum/core/types"
     9  )
    10  
    11  type Chain struct {
    12  	// blocks ordered from oldest to newest
    13  	blocks Blocks
    14  
    15  	// retentionLimit of total number of blocks in cache
    16  	retentionLimit int
    17  
    18  	// bootstrapMode flag that chain is bootstrapped with blocks
    19  	// before starting the monitor.
    20  	bootstrapMode bool
    21  
    22  	mu               sync.Mutex
    23  	averageBlockTime float64 // in seconds
    24  }
    25  
    26  func newChain(retentionLimit int, bootstrapMode bool) *Chain {
    27  	// a minimum retention limit
    28  	retentionMin := 10
    29  	if retentionLimit < retentionMin {
    30  		retentionLimit = retentionMin
    31  	}
    32  
    33  	// blocks of nil means the chain has not been initialized
    34  	var blocks Blocks = nil
    35  	if !bootstrapMode {
    36  		blocks = make(Blocks, 0, retentionLimit)
    37  	}
    38  
    39  	return &Chain{
    40  		blocks:         blocks,
    41  		retentionLimit: retentionLimit,
    42  		bootstrapMode:  bootstrapMode,
    43  	}
    44  }
    45  
    46  // TODO: unused method..
    47  // func (c *Chain) clear() {
    48  // 	c.mu.Lock()
    49  // 	defer c.mu.Unlock()
    50  // 	c.blocks = c.blocks[:0]
    51  // 	c.averageBlockTime = 0
    52  // }
    53  
    54  // Push to the top of the stack
    55  func (c *Chain) push(nextBlock *Block) error {
    56  	c.mu.Lock()
    57  	defer c.mu.Unlock()
    58  
    59  	// New block validations
    60  	n := len(c.blocks)
    61  	if n > 0 {
    62  		headBlock := c.blocks[n-1]
    63  
    64  		// Assert pointing at prev block
    65  		if nextBlock.ParentHash() != headBlock.Hash() {
    66  			return ErrUnexpectedParentHash
    67  		}
    68  
    69  		// Assert block numbers are in sequence
    70  		if nextBlock.NumberU64() != headBlock.NumberU64()+1 {
    71  			return ErrUnexpectedBlockNumber
    72  		}
    73  
    74  		// Update average block time
    75  		if c.averageBlockTime == 0 {
    76  			c.averageBlockTime = float64(nextBlock.Time() - headBlock.Time())
    77  		} else {
    78  			c.averageBlockTime = (c.averageBlockTime + float64(nextBlock.Time()-headBlock.Time())) / 2
    79  		}
    80  	}
    81  
    82  	// Add to head of stack
    83  	c.blocks = append(c.blocks, nextBlock)
    84  	if len(c.blocks) > c.retentionLimit {
    85  		c.blocks[0] = nil
    86  		c.blocks = c.blocks[1:]
    87  	}
    88  
    89  	return nil
    90  }
    91  
    92  // Pop from the top of the stack
    93  func (c *Chain) pop() *Block {
    94  	c.mu.Lock()
    95  	defer c.mu.Unlock()
    96  
    97  	if len(c.blocks) == 0 {
    98  		return nil
    99  	}
   100  
   101  	n := len(c.blocks) - 1
   102  	block := c.blocks[n]
   103  	c.blocks[n] = nil
   104  	c.blocks = c.blocks[:n]
   105  	return block
   106  }
   107  
   108  func (c *Chain) Head() *Block {
   109  	c.mu.Lock()
   110  	defer c.mu.Unlock()
   111  	return c.blocks.Head()
   112  }
   113  
   114  func (c *Chain) Tail() *Block {
   115  	c.mu.Lock()
   116  	defer c.mu.Unlock()
   117  	return c.blocks.Tail()
   118  }
   119  
   120  func (c *Chain) Blocks() Blocks {
   121  	c.mu.Lock()
   122  	defer c.mu.Unlock()
   123  
   124  	// Copy only OK blocks
   125  	last := len(c.blocks) - 1
   126  	for i := last; i >= 0; i-- {
   127  		if c.blocks[i].OK {
   128  			break
   129  		}
   130  		last = i
   131  	}
   132  	last += 1
   133  
   134  	blocks := make(Blocks, last)
   135  	copy(blocks, c.blocks[:last])
   136  
   137  	return blocks
   138  }
   139  
   140  func (c *Chain) ReadyHead() *Block {
   141  	c.mu.Lock()
   142  	defer c.mu.Unlock()
   143  	for i := len(c.blocks) - 1; i >= 0; i-- {
   144  		if c.blocks[i].OK {
   145  			return c.blocks[i]
   146  		}
   147  	}
   148  	return nil
   149  }
   150  
   151  func (c *Chain) GetBlock(hash common.Hash) *Block {
   152  	c.mu.Lock()
   153  	defer c.mu.Unlock()
   154  	block, _ := c.blocks.FindBlock(hash)
   155  	return block
   156  }
   157  
   158  func (c *Chain) GetBlockByNumber(blockNum uint64, event Event) *Block {
   159  	c.mu.Lock()
   160  	defer c.mu.Unlock()
   161  	for i := len(c.blocks) - 1; i >= 0; i-- {
   162  		if c.blocks[i].NumberU64() == blockNum && c.blocks[i].Event == event {
   163  			return c.blocks[i]
   164  		}
   165  	}
   166  	return nil
   167  }
   168  
   169  // GetTransaction searches our canonical chain of blocks (where each block points at previous),
   170  // and returns the transaction. Aka, searches our chain for mined transactions. Keep in mind
   171  // transactions can still be reorged, but you can check the blockNumber and compare it against
   172  // the head to determine if its final.
   173  func (c *Chain) GetTransaction(txnHash common.Hash) (*types.Transaction, Event) {
   174  	c.mu.Lock()
   175  	defer c.mu.Unlock()
   176  
   177  	// Find any transaction added or removed in the retention cache
   178  	for i := len(c.blocks) - 1; i >= 0; i-- {
   179  		for _, txn := range c.blocks[i].Transactions() {
   180  			if txn.Hash() == txnHash {
   181  				return txn, c.blocks[i].Event
   182  			}
   183  		}
   184  	}
   185  
   186  	return nil, 0
   187  }
   188  
   189  func (c *Chain) PrintAllBlocks() {
   190  	c.mu.Lock()
   191  	defer c.mu.Unlock()
   192  
   193  	for _, b := range c.blocks {
   194  		fmt.Printf("<- [%d] %s\n", b.NumberU64(), b.Hash().Hex())
   195  	}
   196  }
   197  
   198  func (c *Chain) GetAverageBlockTime() float64 {
   199  	c.mu.Lock()
   200  	defer c.mu.Unlock()
   201  	return c.averageBlockTime
   202  }
   203  
   204  type Event uint32
   205  
   206  const (
   207  	Added Event = iota
   208  	Removed
   209  )
   210  
   211  type Block struct {
   212  	*types.Block
   213  
   214  	// Event type where Block is Added or Removed (ie. reorged)
   215  	Event Event
   216  
   217  	// Logs in the block, grouped by transactions:
   218  	// [[txnA logs, ..], [txnB logs, ..], ..]
   219  	// Logs [][]types.Log `json:"logs"`
   220  	Logs []types.Log
   221  
   222  	// OK flag which represents the block is ready for broadcasting
   223  	OK bool
   224  
   225  	// Raw byte payloads for block and logs responses from the nodes.
   226  	// The values are only set if RetainPayloads is set to true on monitor.
   227  	BlockPayload []byte
   228  	LogsPayload  []byte
   229  }
   230  
   231  type Blocks []*Block
   232  
   233  func (b Blocks) LatestBlock() *Block {
   234  	for i := len(b) - 1; i >= 0; i-- {
   235  		if b[i].Event == Added {
   236  			return b[i]
   237  		}
   238  	}
   239  	return nil
   240  }
   241  
   242  func (b Blocks) Head() *Block {
   243  	if len(b) == 0 {
   244  		return nil
   245  	}
   246  	return b[len(b)-1]
   247  }
   248  
   249  func (b Blocks) Tail() *Block {
   250  	if len(b) == 0 {
   251  		return nil
   252  	}
   253  	return b[0]
   254  }
   255  
   256  func (b Blocks) IsOK() bool {
   257  	for _, block := range b {
   258  		if !block.OK {
   259  			return false
   260  		}
   261  	}
   262  	return true
   263  }
   264  
   265  func (b Blocks) Reorg() bool {
   266  	for _, block := range b {
   267  		if block.Event == Removed {
   268  			return true
   269  		}
   270  	}
   271  	return false
   272  }
   273  
   274  func (blocks Blocks) FindBlock(blockHash common.Hash, optEvent ...Event) (*Block, bool) {
   275  	for i := len(blocks) - 1; i >= 0; i-- {
   276  		if blocks[i].Hash() == blockHash {
   277  			if optEvent == nil {
   278  				return blocks[i], true
   279  			} else if len(optEvent) > 0 && blocks[i].Event == optEvent[0] {
   280  				return blocks[i], true
   281  			}
   282  		}
   283  	}
   284  	return nil, false
   285  }
   286  
   287  func (blocks Blocks) EventExists(block *types.Block, event Event) bool {
   288  	b, ok := blocks.FindBlock(block.Hash(), event)
   289  	if !ok {
   290  		return false
   291  	}
   292  	if b.ParentHash() == block.ParentHash() && b.NumberU64() == block.NumberU64() {
   293  		return true
   294  	}
   295  	return false
   296  }
   297  
   298  func (blocks Blocks) Copy() Blocks {
   299  	nb := make(Blocks, len(blocks))
   300  
   301  	for i, b := range blocks {
   302  		var logs []types.Log
   303  		if b.Logs != nil {
   304  			copy(logs, b.Logs)
   305  		}
   306  
   307  		var blockPayload []byte
   308  		if b.BlockPayload != nil {
   309  			copy(blockPayload, b.BlockPayload)
   310  		}
   311  
   312  		var logsPayload []byte
   313  		if b.LogsPayload != nil {
   314  			copy(logsPayload, b.LogsPayload)
   315  		}
   316  
   317  		nb[i] = &Block{
   318  			Block:        b.Block,
   319  			Event:        b.Event,
   320  			Logs:         logs,
   321  			OK:           b.OK,
   322  			BlockPayload: blockPayload,
   323  			LogsPayload:  logsPayload,
   324  		}
   325  	}
   326  
   327  	return nb
   328  }
   329  
   330  func IsBlockEq(a, b *types.Block) bool {
   331  	if a == nil || b == nil {
   332  		return false
   333  	}
   334  	return a.Hash() == b.Hash() && a.NumberU64() == b.NumberU64() && a.ParentHash() == b.ParentHash()
   335  }