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 }