diff --git a/go.mod b/go.mod index 9c0fe8c2d0..975a7cd8ea 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,6 @@ require ( github.com/klauspost/compress v1.16.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mitchellh/mapstructure v1.5.0 - github.com/umbracle/ethgo v0.1.4-0.20230810113823-c9c19bcd8a1e github.com/valyala/fastjson v1.6.3 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/sys v0.12.0 // indirect @@ -97,6 +96,7 @@ require ( github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect + github.com/umbracle/ethgo v0.1.4-0.20230919101354-1000cf3e2fbd // indirect go.uber.org/dig v1.16.1 // indirect go.uber.org/fx v1.19.2 // indirect golang.org/x/exp v0.0.0-20230725012225-302865e7556b // indirect diff --git a/go.sum b/go.sum index 82191ec7a7..81e25d696d 100644 --- a/go.sum +++ b/go.sum @@ -81,7 +81,9 @@ github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pY github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= @@ -93,6 +95,7 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -144,6 +147,7 @@ github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6Uh github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= @@ -200,6 +204,7 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= @@ -374,16 +379,20 @@ github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABo github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -485,6 +494,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= @@ -516,6 +526,7 @@ github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXS github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -705,8 +716,10 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/umbracle/ethgo v0.1.4-0.20230810113823-c9c19bcd8a1e h1:CPUqjupBwC5EpV/eY/K+E65ZvFZkcXYgWf6KzypBQDY= -github.com/umbracle/ethgo v0.1.4-0.20230810113823-c9c19bcd8a1e/go.mod h1:J+OZNfRCtbaYW3AEc0m47GhwAzlNJjcr9vO86nzOr6E= +github.com/umbracle/ethgo v0.1.3 h1:s8D7Rmphnt71zuqrgsGTMS5gTNbueGO1zKLh7qsFzTM= +github.com/umbracle/ethgo v0.1.3/go.mod h1:g9zclCLixH8liBI27Py82klDkW7Oo33AxUOr+M9lzrU= +github.com/umbracle/ethgo v0.1.4-0.20230919101354-1000cf3e2fbd h1:le2HWiMd9csTLBVdDGxM1Fr49pNtuS0Wkm9ckPsu1CI= +github.com/umbracle/ethgo v0.1.4-0.20230919101354-1000cf3e2fbd/go.mod h1:J+OZNfRCtbaYW3AEc0m47GhwAzlNJjcr9vO86nzOr6E= github.com/umbracle/fastrlp v0.1.1-0.20230504065717-58a1b8a9929d h1:HAg1Kpr9buwRxEiC2UXU9oT2AU8uCU7o3/WTH+Lt5wo= github.com/umbracle/fastrlp v0.1.1-0.20230504065717-58a1b8a9929d/go.mod h1:5RHgqiFjd4vLJESMWagP/E7su+5Gzk0iqqmrotR8WdA= github.com/umbracle/go-eth-bn256 v0.0.0-20230125114011-47cb310d9b0b h1:5/xofhZiOG0I9DQXqDSPxqYObk6QI7mBGMJI+ngyIgc= @@ -792,6 +805,7 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= diff --git a/tracker/blocktracker.go b/tracker/blocktracker.go new file mode 100644 index 0000000000..fe35ce104f --- /dev/null +++ b/tracker/blocktracker.go @@ -0,0 +1,273 @@ +package tracker + +import ( + "context" + "fmt" + "sync" + + "github.com/hashicorp/go-hclog" + "github.com/umbracle/ethgo" + bt "github.com/umbracle/ethgo/blocktracker" +) + +const ( + defaultMaxBlockBacklog = 10 +) + +// BlockTracker is an interface to track new blocks on the chain +type BlockTracker struct { + config *Config + blocks []*ethgo.Block + blocksLock sync.Mutex + subscriber bt.BlockTrackerInterface + blockChs []chan *bt.BlockEvent + blockChsLock sync.Mutex + provider bt.BlockProvider + closeCh chan struct{} + logger hclog.Logger +} + +type Config struct { + Tracker bt.BlockTrackerInterface + MaxBlockBacklog uint64 +} + +func DefaultConfig() *Config { + return &Config{ + MaxBlockBacklog: defaultMaxBlockBacklog, + } +} + +type ConfigOption func(*Config) + +func WithBlockMaxBacklog(b uint64) ConfigOption { + return func(c *Config) { + c.MaxBlockBacklog = b + } +} + +func WithTracker(b bt.BlockTrackerInterface) ConfigOption { + return func(c *Config) { + c.Tracker = b + } +} + +func NewBlockTracker(provider bt.BlockProvider, logger hclog.Logger, opts ...ConfigOption) *BlockTracker { + config := DefaultConfig() + for _, opt := range opts { + opt(config) + } + + tracker := config.Tracker + if tracker == nil { + tracker = bt.NewJSONBlockTracker(provider) + } + + return &BlockTracker{ + blocks: nil, + blockChs: nil, + config: config, + subscriber: tracker, + provider: provider, + closeCh: make(chan struct{}), + logger: logger, + blocksLock: sync.Mutex{}, + blockChsLock: sync.Mutex{}, + } +} + +func (t *BlockTracker) Subscribe() chan *bt.BlockEvent { + t.blockChsLock.Lock() + defer t.blockChsLock.Unlock() + + ch := make(chan *bt.BlockEvent, 1) + t.blockChs = append(t.blockChs, ch) + + return ch +} + +func (t *BlockTracker) AcquireLock() bt.Lock { + return bt.NewLock(&t.blocksLock) +} + +func (t *BlockTracker) Init() error { + return nil +} + +func (t *BlockTracker) MaxBlockBacklog() uint64 { + return t.config.MaxBlockBacklog +} + +func (t *BlockTracker) LastBlocked() *ethgo.Block { + if len(t.blocks) == 0 { + return nil + } + + return t.blocks[len(t.blocks)-1].Copy() +} + +func (t *BlockTracker) BlocksBlocked() []*ethgo.Block { + res := make([]*ethgo.Block, len(t.blocks)) + for i, block := range t.blocks { + res[i] = block.Copy() + } + + return res +} + +func (t *BlockTracker) Len() int { + return len(t.blocks) +} + +func (t *BlockTracker) Close() error { + close(t.closeCh) + + return nil +} + +func (t *BlockTracker) Start() error { + ctx, cancelFn := context.WithCancel(context.Background()) + go func() { + <-t.closeCh + cancelFn() + }() + + // start the polling + return t.subscriber.Track(ctx, func(block *ethgo.Block) error { + return t.HandleTrackedBlock(block) + }) +} + +func (t *BlockTracker) AddBlockLocked(block *ethgo.Block) error { + if uint64(len(t.blocks)) == t.config.MaxBlockBacklog { + // remove past blocks if there are more than maxReconcileBlocks + t.blocks = append([]*ethgo.Block{}, t.blocks[1:]...) + } + + if len(t.blocks) != 0 { + if lastNum := t.blocks[len(t.blocks)-1].Number; lastNum+1 != block.Number { + return fmt.Errorf("bad number sequence. %d and %d", lastNum, block.Number) + } + } + + t.blocks = append(t.blocks, block) + + return nil +} + +func (t *BlockTracker) blockAtIndex(hash ethgo.Hash) int { + for indx, b := range t.blocks { + if b.Hash == hash { + return indx + } + } + + return -1 +} + +func (t *BlockTracker) handleReconcileImpl(block *ethgo.Block) ([]*ethgo.Block, int, error) { + // Append to the head of the chain + if len(t.blocks) > 0 && t.blocks[len(t.blocks)-1].Hash == block.ParentHash { + return []*ethgo.Block{block}, -1, nil + } + + // The block already exists, but if not last, remove all the following + if indx := t.blockAtIndex(block.Hash); indx != -1 { + return nil, indx + 1, nil + } + + // Try to find parent of a block from the existing state blocks + // If there is no parent, then query the chain. + // If there is no parent for MaxBlockBacklog blocks, all the existing state blocks will be removed + // and replaced with retrieved blocks + var ( + added = []*ethgo.Block{block} + count uint64 = 0 + indx = 0 // if indx stays 0 - it means remove all from the current state + err error + ) + + for ; count < t.config.MaxBlockBacklog && block.Number > 0; count++ { + parentHash := block.ParentHash + + // if there is a parent at some index, break loop and update from where should we delete + if indx = t.blockAtIndex(parentHash); indx != -1 { + indx++ + + break + } + + block, err = t.provider.GetBlockByHash(parentHash, false) + if err != nil { + return nil, -1, fmt.Errorf("parent with hash retrieving error: %w", err) + } else if block == nil { + // if block does not exist (for example reorg happened) GetBlockByHash will return nil, nil + return nil, -1, fmt.Errorf("parent with hash %s not found", parentHash) + } + + added = append(added, block) + } + + // need the blocks in reverse order + for i := len(added)/2 - 1; i >= 0; i-- { + opp := len(added) - 1 - i + added[i], added[opp] = added[opp], added[i] + } + + if count == t.config.MaxBlockBacklog { + t.logger.Info("reconcile did not found parent for new blocks", "hash", added[0].Hash) + } + + return added, indx, nil +} + +func (t *BlockTracker) HandleBlockEvent(block *ethgo.Block) (*bt.BlockEvent, error) { + t.blocksLock.Lock() + defer t.blocksLock.Unlock() + + blocks, indx, err := t.handleReconcileImpl(block) + if err != nil { + return nil, err + } + + if len(blocks) == 0 { + return nil, nil + } + + blockEvnt := &bt.BlockEvent{} + + // there are some blocks to remove + if indx >= 0 && indx < len(t.blocks) { + for i := indx; i < len(t.blocks); i++ { + blockEvnt.Removed = append(blockEvnt.Removed, t.blocks[i]) + } + + t.blocks = t.blocks[:indx] + } + + // include the new blocks + for _, block := range blocks { + blockEvnt.Added = append(blockEvnt.Added, block) + t.blocks = append(t.blocks, block) + } + + return blockEvnt, nil +} + +func (t *BlockTracker) HandleTrackedBlock(block *ethgo.Block) error { + blockEvnt, err := t.HandleBlockEvent(block) + if err != nil { + return err + } + + if blockEvnt != nil { + t.blockChsLock.Lock() + defer t.blockChsLock.Unlock() + + for _, ch := range t.blockChs { + ch <- blockEvnt + } + } + + return nil +} diff --git a/tracker/blocktracker_test.go b/tracker/blocktracker_test.go new file mode 100644 index 0000000000..fc69e9c217 --- /dev/null +++ b/tracker/blocktracker_test.go @@ -0,0 +1,256 @@ +package tracker + +import ( + "testing" + "time" + + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/blocktracker" + "github.com/umbracle/ethgo/testutil" +) + +func TestBlockTracker_Events(t *testing.T) { + t.Parallel() + + type TestEvent struct { + Added testutil.MockList + Removed testutil.MockList + } + + type Reconcile struct { + block *testutil.MockBlock + event *TestEvent + } + + cases := []struct { + Name string + Scenario testutil.MockList + History testutil.MockList + Reconcile []Reconcile + Expected testutil.MockList + }{ + { + Name: "Empty history", + Reconcile: []Reconcile{ + { + block: testutil.Mock(0x0), + event: &TestEvent{ + Added: testutil.MockList{ + testutil.Mock(0x0), + }, + }, + }, + }, + Expected: []*testutil.MockBlock{ + testutil.Mock(0), + }, + }, + { + Name: "Repeated header", + History: []*testutil.MockBlock{ + testutil.Mock(0x1), + }, + Reconcile: []Reconcile{ + { + block: testutil.Mock(0x1), + }, + }, + Expected: []*testutil.MockBlock{ + testutil.Mock(0x1), + }, + }, + { + Name: "New head", + History: testutil.MockList{ + testutil.Mock(0x1), + }, + Reconcile: []Reconcile{ + { + block: testutil.Mock(0x2), + event: &TestEvent{ + Added: testutil.MockList{ + testutil.Mock(0x2), + }, + }, + }, + }, + Expected: testutil.MockList{ + testutil.Mock(0x1), + testutil.Mock(0x2), + }, + }, + { + Name: "Ignore block already on history", + History: testutil.MockList{ + testutil.Mock(0x1), + testutil.Mock(0x2), + testutil.Mock(0x3), + }, + Reconcile: []Reconcile{ + { + block: testutil.Mock(0x2), + }, + }, + Expected: testutil.MockList{ + testutil.Mock(0x1), + testutil.Mock(0x2), + testutil.Mock(0x3), + }, + }, + { + Name: "Multi Roll back", + History: testutil.MockList{ + testutil.Mock(0x1), + testutil.Mock(0x2), + testutil.Mock(0x3), + testutil.Mock(0x4), + }, + Reconcile: []Reconcile{ + { + block: testutil.Mock(0x30).Parent(0x2), + event: &TestEvent{ + Added: testutil.MockList{ + testutil.Mock(0x30).Parent(0x2), + }, + Removed: testutil.MockList{ + testutil.Mock(0x3), + testutil.Mock(0x4), + }, + }, + }, + }, + Expected: testutil.MockList{ + testutil.Mock(0x1), + testutil.Mock(0x2), + testutil.Mock(0x30).Parent(0x2), + }, + }, + { + Name: "Backfills missing blocks", + Scenario: testutil.MockList{ + testutil.Mock(0x3), + testutil.Mock(0x4), + }, + History: testutil.MockList{ + testutil.Mock(0x1), + testutil.Mock(0x2), + }, + Reconcile: []Reconcile{ + { + block: testutil.Mock(0x5), + event: &TestEvent{ + Added: testutil.MockList{ + testutil.Mock(0x3), + testutil.Mock(0x4), + testutil.Mock(0x5), + }, + }, + }, + }, + Expected: testutil.MockList{ + testutil.Mock(0x1), + testutil.Mock(0x2), + testutil.Mock(0x3), + testutil.Mock(0x4), + testutil.Mock(0x5), + }, + }, + { + Name: "Rolls back and backfills", + Scenario: testutil.MockList{ + testutil.Mock(0x30).Parent(0x2).Num(3), + testutil.Mock(0x40).Parent(0x30).Num(4), + }, + History: testutil.MockList{ + testutil.Mock(0x1), + testutil.Mock(0x2), + testutil.Mock(0x3), + testutil.Mock(0x4), + }, + Reconcile: []Reconcile{ + { + block: testutil.Mock(0x50).Parent(0x40).Num(5), + event: &TestEvent{ + Added: testutil.MockList{ + testutil.Mock(0x30).Parent(0x2).Num(3), + testutil.Mock(0x40).Parent(0x30).Num(4), + testutil.Mock(0x50).Parent(0x40).Num(5), + }, + Removed: testutil.MockList{ + testutil.Mock(0x3), + testutil.Mock(0x4), + }, + }, + }, + }, + Expected: testutil.MockList{ + testutil.Mock(0x1), + testutil.Mock(0x2), + testutil.Mock(0x30).Parent(0x2).Num(3), + testutil.Mock(0x40).Parent(0x30).Num(4), + testutil.Mock(0x50).Parent(0x40).Num(5), + }, + }, + } + + for _, c := range cases { + c := c + + t.Run(c.Name, func(t *testing.T) { + t.Parallel() + + // safe check for now, we ma need to restart the tracker and mock client for every reconcile scenario? + require.Len(t, c.Reconcile, 1) + + m := &testutil.MockClient{} + + // add the full scenario with the logs + m.AddScenario(c.Scenario) + + tt := NewBlockTracker(m, hclog.NewNullLogger()) + + // build past block history + for _, b := range c.History.ToBlocks() { + require.NoError(t, tt.AddBlockLocked(b)) + } + + sub := tt.Subscribe() + for _, b := range c.Reconcile { + require.NoError(t, tt.HandleTrackedBlock(b.block.Block())) + + if b.event == nil { + continue + } + + var blockEvnt *blocktracker.BlockEvent + select { + case blockEvnt = <-sub: + case <-time.After(1 * time.Second): + t.Fatal("block event timeout") + } + + toBlocks := func(bls testutil.MockList) []*ethgo.Block { + if bls == nil { + return nil + } + + e := []*ethgo.Block{} + + for _, i := range bls { + e = append(e, i.Block()) + } + + return e + } + + // check blocks + require.Equal(t, toBlocks(b.event.Added), blockEvnt.Added) + require.Equal(t, toBlocks(b.event.Removed), blockEvnt.Removed) + } + + require.True(t, testutil.CompareBlocks(tt.blocks, c.Expected.ToBlocks())) + }) + } +} diff --git a/tracker/event_tracker.go b/tracker/event_tracker.go index 14e1d18553..90a3984075 100644 --- a/tracker/event_tracker.go +++ b/tracker/event_tracker.go @@ -76,10 +76,11 @@ func (e *EventTracker) Start(ctx context.Context) error { jsonBlockTracker := blocktracker.NewJSONBlockTracker(provider.Eth()) jsonBlockTracker.PollInterval = e.pollInterval - blockTracker := blocktracker.NewBlockTracker( + blockTracker := NewBlockTracker( provider.Eth(), - blocktracker.WithBlockMaxBacklog(blockMaxBacklog), - blocktracker.WithTracker(jsonBlockTracker), + e.logger.Named("blocktracker"), + WithBlockMaxBacklog(blockMaxBacklog), + WithTracker(jsonBlockTracker), ) go func() { diff --git a/tracker/event_tracker_test.go b/tracker/event_tracker_test.go index bc247c3a69..857cd81fa3 100644 --- a/tracker/event_tracker_test.go +++ b/tracker/event_tracker_test.go @@ -51,9 +51,10 @@ func TestEventTracker_TrackSyncEvents(t *testing.T) { server := testutil.DeployTestServer(t, nil) tmpDir, err := os.MkdirTemp("/tmp", "test-event-tracker") - defer os.RemoveAll(tmpDir) require.NoError(t, err) + defer os.RemoveAll(tmpDir) + cc := &testutil.Contract{} cc.AddCallback(func() string { return `