From 17772f37116b824a22672291fe923d14fcbfab88 Mon Sep 17 00:00:00 2001 From: Chris Cushman <104409744+vreff@users.noreply.github.com> Date: Thu, 28 Sep 2023 05:33:59 -0400 Subject: [PATCH 1/8] [VRF-616] Fix test flake for trusted BHS (#10728) * [VRF-616] Fix test flake for trusted BHS * Modulate waitblocks on trusted bhs added delay test * fix merge conflict --- core/services/vrf/v1/integration_test.go | 2 +- core/services/vrf/v2/bhs_feeder_test.go | 2 +- core/services/vrf/v2/integration_helpers_test.go | 9 +++++++-- core/services/vrf/v2/integration_v2_test.go | 1 - core/services/vrf/vrftesthelpers/helpers.go | 4 ++-- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/core/services/vrf/v1/integration_test.go b/core/services/vrf/v1/integration_test.go index 14cbb36c9d..8626b43dd2 100644 --- a/core/services/vrf/v1/integration_test.go +++ b/core/services/vrf/v1/integration_test.go @@ -150,7 +150,7 @@ func TestIntegration_VRF_WithBHS(t *testing.T) { // Create BHS Job and start it bhsJob := vrftesthelpers.CreateAndStartBHSJob(t, sendingKeys, app, cu.BHSContractAddress.String(), - cu.RootContractAddress.String(), "", "", "", 0, 200, 0) + cu.RootContractAddress.String(), "", "", "", 0, 200, 0, 100) // Ensure log poller is ready and has all logs. require.NoError(t, app.GetRelayers().LegacyEVMChains().Slice()[0].LogPoller().Ready()) diff --git a/core/services/vrf/v2/bhs_feeder_test.go b/core/services/vrf/v2/bhs_feeder_test.go index b843dbca9e..0da28378d0 100644 --- a/core/services/vrf/v2/bhs_feeder_test.go +++ b/core/services/vrf/v2/bhs_feeder_test.go @@ -82,7 +82,7 @@ func TestStartHeartbeats(t *testing.T) { _ = vrftesthelpers.CreateAndStartBHSJob( t, bhsKeyAddresses, app, uni.bhsContractAddress.String(), "", - v2CoordinatorAddress, v2PlusCoordinatorAddress, "", 0, 200, heartbeatPeriod) + v2CoordinatorAddress, v2PlusCoordinatorAddress, "", 0, 200, heartbeatPeriod, 100) // Ensure log poller is ready and has all logs. require.NoError(t, app.GetRelayers().LegacyEVMChains().Slice()[0].LogPoller().Ready()) diff --git a/core/services/vrf/v2/integration_helpers_test.go b/core/services/vrf/v2/integration_helpers_test.go index 81f3edfbb2..262ac7ef48 100644 --- a/core/services/vrf/v2/integration_helpers_test.go +++ b/core/services/vrf/v2/integration_helpers_test.go @@ -241,7 +241,7 @@ func testMultipleConsumersNeedBHS( _ = vrftesthelpers.CreateAndStartBHSJob( t, bhsKeyAddresses, app, uni.bhsContractAddress.String(), "", - v2CoordinatorAddress, v2PlusCoordinatorAddress, "", 0, 200, 0) + v2CoordinatorAddress, v2PlusCoordinatorAddress, "", 0, 200, 0, 100) // Ensure log poller is ready and has all logs. require.NoError(t, app.GetRelayers().LegacyEVMChains().Slice()[0].LogPoller().Ready()) @@ -350,6 +350,7 @@ func testMultipleConsumersNeedTrustedBHS( config, db := heavyweight.FullTestDBV2(t, "vrfv2_needs_trusted_blockhash_store", func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, assets.GWei(10), keySpecificOverrides...)(c, s) c.EVM[0].MinIncomingConfirmations = ptr[uint32](2) + c.EVM[0].GasEstimator.LimitDefault = ptr(uint32(5_000_000)) c.Feature.LogPoller = ptr(true) c.EVM[0].FinalityDepth = ptr[uint32](2) c.EVM[0].LogPollInterval = models.MustNewDuration(time.Second) @@ -384,9 +385,13 @@ func testMultipleConsumersNeedTrustedBHS( v2PlusCoordinatorAddress = coordinatorAddress.String() } + waitBlocks := 100 + if addedDelay { + waitBlocks = 400 + } _ = vrftesthelpers.CreateAndStartBHSJob( t, bhsKeyAddressesStrings, app, "", "", - v2CoordinatorAddress, v2PlusCoordinatorAddress, uni.trustedBhsContractAddress.String(), 20, 1000, 0) + v2CoordinatorAddress, v2PlusCoordinatorAddress, uni.trustedBhsContractAddress.String(), 20, 1000, 0, waitBlocks) // Ensure log poller is ready and has all logs. chain := app.GetRelayers().LegacyEVMChains().Slice()[0] diff --git a/core/services/vrf/v2/integration_v2_test.go b/core/services/vrf/v2/integration_v2_test.go index 1f6a9dd3f9..531c7ebb66 100644 --- a/core/services/vrf/v2/integration_v2_test.go +++ b/core/services/vrf/v2/integration_v2_test.go @@ -1320,7 +1320,6 @@ func TestVRFV2Integration_SingleConsumer_NeedsTrustedBlockhashStore(t *testing.T } func TestVRFV2Integration_SingleConsumer_NeedsTrustedBlockhashStore_AfterDelay(t *testing.T) { - t.Skip("TODO: VRF-616") t.Parallel() ownerKey := cltest.MustGenerateRandomKey(t) uni := newVRFCoordinatorV2PlusUniverse(t, ownerKey, 2, true) diff --git a/core/services/vrf/vrftesthelpers/helpers.go b/core/services/vrf/vrftesthelpers/helpers.go index 15af16f6fd..f36fb1ea02 100644 --- a/core/services/vrf/vrftesthelpers/helpers.go +++ b/core/services/vrf/vrftesthelpers/helpers.go @@ -51,7 +51,7 @@ func CreateAndStartBHSJob( app *cltest.TestApplication, bhsAddress, coordinatorV1Address, coordinatorV2Address, coordinatorV2PlusAddress string, trustedBlockhashStoreAddress string, trustedBlockhashStoreBatchSize int32, lookback int, - heartbeatPeriod time.Duration, + heartbeatPeriod time.Duration, waitBlocks int, ) job.Job { jid := uuid.New() s := testspecs.GenerateBlockhashStoreSpec(testspecs.BlockhashStoreSpecParams{ @@ -60,7 +60,7 @@ func CreateAndStartBHSJob( CoordinatorV1Address: coordinatorV1Address, CoordinatorV2Address: coordinatorV2Address, CoordinatorV2PlusAddress: coordinatorV2PlusAddress, - WaitBlocks: 100, + WaitBlocks: waitBlocks, LookbackBlocks: lookback, HeartbeatPeriod: heartbeatPeriod, BlockhashStoreAddress: bhsAddress, From 71d314fe1b6aa1c35fc7c7e6d65525fee3d18bdd Mon Sep 17 00:00:00 2001 From: Cedric Date: Thu, 28 Sep 2023 10:45:33 +0100 Subject: [PATCH 2/8] [BCF-2674] Make BalanceMonitor test deterministic; add WorkDone to *sleeperTask for tests. (#10821) * Fix balance monitor flakey test * Refactor session reaper test to use the WorkDone() test helper method --- core/chains/evm/monitor/balance.go | 4 ++- .../evm/monitor/balance_helpers_test.go | 7 ++++ core/chains/evm/monitor/balance_test.go | 18 ++++------- core/sessions/reaper.go | 20 ++---------- core/sessions/reaper_helper_test.go | 5 --- core/sessions/reaper_test.go | 8 ++--- core/utils/sleeper_task.go | 32 ++++++++++++++----- 7 files changed, 46 insertions(+), 48 deletions(-) create mode 100644 core/chains/evm/monitor/balance_helpers_test.go delete mode 100644 core/sessions/reaper_helper_test.go diff --git a/core/chains/evm/monitor/balance.go b/core/chains/evm/monitor/balance.go index 8dbd4ed850..28682f2509 100644 --- a/core/chains/evm/monitor/balance.go +++ b/core/chains/evm/monitor/balance.go @@ -47,8 +47,10 @@ type ( NullBalanceMonitor struct{} ) +var _ BalanceMonitor = (*balanceMonitor)(nil) + // NewBalanceMonitor returns a new balanceMonitor -func NewBalanceMonitor(ethClient evmclient.Client, ethKeyStore keystore.Eth, logger logger.Logger) BalanceMonitor { +func NewBalanceMonitor(ethClient evmclient.Client, ethKeyStore keystore.Eth, logger logger.Logger) *balanceMonitor { chainId := ethClient.ConfiguredChainID() bm := &balanceMonitor{ utils.StartStopOnce{}, diff --git a/core/chains/evm/monitor/balance_helpers_test.go b/core/chains/evm/monitor/balance_helpers_test.go new file mode 100644 index 0000000000..624aa69f06 --- /dev/null +++ b/core/chains/evm/monitor/balance_helpers_test.go @@ -0,0 +1,7 @@ +package monitor + +func (bm *balanceMonitor) WorkDone() <-chan struct{} { + return bm.sleeperTask.(interface { + WorkDone() <-chan struct{} + }).WorkDone() +} diff --git a/core/chains/evm/monitor/balance_test.go b/core/chains/evm/monitor/balance_test.go index c908c39567..d641738181 100644 --- a/core/chains/evm/monitor/balance_test.go +++ b/core/chains/evm/monitor/balance_test.go @@ -169,12 +169,9 @@ func TestBalanceMonitor_OnNewLongestChain_UpdatesBalance(t *testing.T) { // Do the thing bm.OnNewLongestChain(testutils.Context(t), head) - gomega.NewWithT(t).Eventually(func() *big.Int { - return bm.GetEthBalance(k0Addr).ToInt() - }).Should(gomega.Equal(k0bal)) - gomega.NewWithT(t).Eventually(func() *big.Int { - return bm.GetEthBalance(k1Addr).ToInt() - }).Should(gomega.Equal(k1bal)) + <-bm.WorkDone() + assert.Equal(t, k0bal, bm.GetEthBalance(k0Addr).ToInt()) + assert.Equal(t, k1bal, bm.GetEthBalance(k1Addr).ToInt()) // Do it again k0bal2 := big.NewInt(142) @@ -187,12 +184,9 @@ func TestBalanceMonitor_OnNewLongestChain_UpdatesBalance(t *testing.T) { bm.OnNewLongestChain(testutils.Context(t), head) - gomega.NewWithT(t).Eventually(func() *big.Int { - return bm.GetEthBalance(k0Addr).ToInt() - }).Should(gomega.Equal(k0bal2)) - gomega.NewWithT(t).Eventually(func() *big.Int { - return bm.GetEthBalance(k1Addr).ToInt() - }).Should(gomega.Equal(k1bal2)) + <-bm.WorkDone() + assert.Equal(t, k0bal2, bm.GetEthBalance(k0Addr).ToInt()) + assert.Equal(t, k1bal2, bm.GetEthBalance(k1Addr).ToInt()) }) } diff --git a/core/sessions/reaper.go b/core/sessions/reaper.go index a80f0124bb..c4f0ed6796 100644 --- a/core/sessions/reaper.go +++ b/core/sessions/reaper.go @@ -13,10 +13,6 @@ type sessionReaper struct { db *sql.DB config SessionReaperConfig lggr logger.Logger - - // Receive from this for testing via sr.RunSignal() - // to be notified after each reaper run. - runSignal chan struct{} } type SessionReaperConfig interface { @@ -26,18 +22,11 @@ type SessionReaperConfig interface { // NewSessionReaper creates a reaper that cleans stale sessions from the store. func NewSessionReaper(db *sql.DB, config SessionReaperConfig, lggr logger.Logger) utils.SleeperTask { - return utils.NewSleeperTask(NewSessionReaperWorker(db, config, lggr)) -} - -func NewSessionReaperWorker(db *sql.DB, config SessionReaperConfig, lggr logger.Logger) *sessionReaper { - return &sessionReaper{ + return utils.NewSleeperTask(&sessionReaper{ db, config, lggr.Named("SessionReaper"), - - // For testing only. - make(chan struct{}, 10), - } + }) } func (sr *sessionReaper) Name() string { @@ -51,11 +40,6 @@ func (sr *sessionReaper) Work() { if err != nil { sr.lggr.Error("unable to reap stale sessions: ", err) } - - select { - case sr.runSignal <- struct{}{}: - default: - } } // DeleteStaleSessions deletes all sessions before the passed time. diff --git a/core/sessions/reaper_helper_test.go b/core/sessions/reaper_helper_test.go deleted file mode 100644 index cec9b72d7e..0000000000 --- a/core/sessions/reaper_helper_test.go +++ /dev/null @@ -1,5 +0,0 @@ -package sessions - -func (sr *sessionReaper) RunSignal() <-chan struct{} { - return sr.runSignal -} diff --git a/core/sessions/reaper_test.go b/core/sessions/reaper_test.go index 1e325ea506..a96c3822ef 100644 --- a/core/sessions/reaper_test.go +++ b/core/sessions/reaper_test.go @@ -10,7 +10,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger/audit" "github.com/smartcontractkit/chainlink/v2/core/sessions" "github.com/smartcontractkit/chainlink/v2/core/store/models" - "github.com/smartcontractkit/chainlink/v2/core/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -34,8 +33,7 @@ func TestSessionReaper_ReapSessions(t *testing.T) { lggr := logger.TestLogger(t) orm := sessions.NewORM(db, config.SessionTimeout().Duration(), lggr, pgtest.NewQConfig(true), audit.NoopLogger) - rw := sessions.NewSessionReaperWorker(db.DB, config, lggr) - r := utils.NewSleeperTask(rw) + r := sessions.NewSessionReaper(db.DB, config, lggr) t.Cleanup(func() { assert.NoError(t, r.Stop()) @@ -70,7 +68,9 @@ func TestSessionReaper_ReapSessions(t *testing.T) { }) r.WakeUp() - <-rw.RunSignal() + <-r.(interface { + WorkDone() <-chan struct{} + }).WorkDone() sessions, err := orm.Sessions(0, 10) assert.NoError(t, err) diff --git a/core/utils/sleeper_task.go b/core/utils/sleeper_task.go index d020799a9c..0b45507a82 100644 --- a/core/utils/sleeper_task.go +++ b/core/utils/sleeper_task.go @@ -19,10 +19,11 @@ type Worker interface { } type sleeperTask struct { - worker Worker - chQueue chan struct{} - chStop chan struct{} - chDone chan struct{} + worker Worker + chQueue chan struct{} + chStop chan struct{} + chDone chan struct{} + chWorkDone chan struct{} StartStopOnce } @@ -36,10 +37,11 @@ type sleeperTask struct { // WakeUp does not block. func NewSleeperTask(worker Worker) SleeperTask { s := &sleeperTask{ - worker: worker, - chQueue: make(chan struct{}, 1), - chStop: make(chan struct{}), - chDone: make(chan struct{}), + worker: worker, + chQueue: make(chan struct{}, 1), + chStop: make(chan struct{}), + chDone: make(chan struct{}), + chWorkDone: make(chan struct{}, 10), } _ = s.StartOnce("SleeperTask-"+worker.Name(), func() error { @@ -83,6 +85,19 @@ func (s *sleeperTask) WakeUp() { } } +func (s *sleeperTask) workDone() { + select { + case s.chWorkDone <- struct{}{}: + default: + } +} + +// WorkDone isn't part of the SleeperTask interface, but can be +// useful in tests to assert that the work has been done. +func (s *sleeperTask) WorkDone() <-chan struct{} { + return s.chWorkDone +} + func (s *sleeperTask) workerLoop() { defer close(s.chDone) @@ -90,6 +105,7 @@ func (s *sleeperTask) workerLoop() { select { case <-s.chQueue: s.worker.Work() + s.workDone() case <-s.chStop: return } From bfdd7d19b609e0cd1cc3ffd068c31dcd27e20fa9 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 28 Sep 2023 11:53:57 -0400 Subject: [PATCH 3/8] Increase error log detail for enhanced telemetry (#10822) - Relates to MERC-1957 --- core/services/ocrcommon/telemetry.go | 31 ++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/core/services/ocrcommon/telemetry.go b/core/services/ocrcommon/telemetry.go index 636a18262e..3a0ba04903 100644 --- a/core/services/ocrcommon/telemetry.go +++ b/core/services/ocrcommon/telemetry.go @@ -3,6 +3,7 @@ package ocrcommon import ( "context" "encoding/json" + "fmt" "github.com/ethereum/go-ethereum/common" @@ -361,9 +362,13 @@ func (e *EnhancedTelemetryService[T]) getPricesFromResults(startTask pipeline.Ta return 0, 0, 0 } if benchmarkPriceTask.Task.Type() == pipeline.TaskTypeJSONParse { - benchmarkPrice, ok = benchmarkPriceTask.Result.Value.(float64) - if !ok { - e.lggr.Warnf("cannot parse enhanced EA telemetry benchmark price, job %d, id %s", e.job.ID, benchmarkPriceTask.Task.DotID()) + if benchmarkPriceTask.Result.Error != nil { + e.lggr.Warnw(fmt.Sprintf("got error for enhanced EA telemetry benchmark price, job %d, id %s: %s", e.job.ID, benchmarkPriceTask.Task.DotID(), benchmarkPriceTask.Result.Error), "err", benchmarkPriceTask.Result.Error) + } else { + benchmarkPrice, ok = benchmarkPriceTask.Result.Value.(float64) + if !ok { + e.lggr.Warnf("cannot parse enhanced EA telemetry benchmark price, job %d, id %s (expected float64, got type: %T)", e.job.ID, benchmarkPriceTask.Task.DotID(), benchmarkPriceTask.Result.Value) + } } } @@ -373,9 +378,13 @@ func (e *EnhancedTelemetryService[T]) getPricesFromResults(startTask pipeline.Ta return 0, 0, 0 } if bidTask.Task.Type() == pipeline.TaskTypeJSONParse { - bidPrice, ok = bidTask.Result.Value.(float64) - if !ok { - e.lggr.Warnf("cannot parse enhanced EA telemetry bid price, job %d, id %s", e.job.ID, bidTask.Task.DotID()) + if bidTask.Result.Error != nil { + e.lggr.Warnw(fmt.Sprintf("got error for enhanced EA telemetry bid price, job %d, id %s: %s", e.job.ID, bidTask.Task.DotID(), bidTask.Result.Error), "err", bidTask.Result.Error) + } else { + bidPrice, ok = bidTask.Result.Value.(float64) + if !ok { + e.lggr.Warnf("cannot parse enhanced EA telemetry bid price, job %d, id %s (expected float64, got type: %T)", e.job.ID, bidTask.Task.DotID(), bidTask.Result.Value) + } } } @@ -385,9 +394,13 @@ func (e *EnhancedTelemetryService[T]) getPricesFromResults(startTask pipeline.Ta return 0, 0, 0 } if askTask.Task.Type() == pipeline.TaskTypeJSONParse { - askPrice, ok = askTask.Result.Value.(float64) - if !ok { - e.lggr.Warnf("cannot parse enhanced EA telemetry ask price, job %d, id %s", e.job.ID, askTask.Task.DotID()) + if bidTask.Result.Error != nil { + e.lggr.Warnw(fmt.Sprintf("got error for enhanced EA telemetry ask price, job %d, id %s: %s", e.job.ID, askTask.Task.DotID(), askTask.Result.Error), "err", askTask.Result.Error) + } else { + askPrice, ok = askTask.Result.Value.(float64) + if !ok { + e.lggr.Warnf("cannot parse enhanced EA telemetry ask price, job %d, id %s (expected float64, got type: %T)", e.job.ID, askTask.Task.DotID(), askTask.Result.Value) + } } } From de737a5c08d43ed3304151f4ac66592d8bdb9376 Mon Sep 17 00:00:00 2001 From: Adam Hamrick Date: Thu, 28 Sep 2023 13:22:31 -0400 Subject: [PATCH 4/8] Better Log Collection (#10824) --- integration-tests/docker/test_env/test_env.go | 80 +++++++++---------- integration-tests/example.env | 2 +- 2 files changed, 40 insertions(+), 42 deletions(-) diff --git a/integration-tests/docker/test_env/test_env.go b/integration-tests/docker/test_env/test_env.go index e3a9037da2..7f805f5fb8 100644 --- a/integration-tests/docker/test_env/test_env.go +++ b/integration-tests/docker/test_env/test_env.go @@ -248,6 +248,45 @@ func (te *CLClusterTestEnv) Cleanup(t *testing.T) error { return errors.New("chainlink nodes are nil, unable to return funds from chainlink nodes") } + // TODO: This is an imperfect and temporary solution, see TT-590 for a more sustainable solution + // Collect logs if the test fails, or if we just want them + if t.Failed() || os.Getenv("TEST_LOG_COLLECT") == "true" { + folder := fmt.Sprintf("./logs/%s-%s", t.Name(), time.Now().Format("2006-01-02T15-04-05")) + if err := os.MkdirAll(folder, os.ModePerm); err != nil { + return err + } + + te.l.Info().Msg("Collecting test logs") + eg := &errgroup.Group{} + for _, n := range te.CLNodes { + node := n + eg.Go(func() error { + logFileName := filepath.Join(folder, fmt.Sprintf("node-%s.log", node.ContainerName)) + logFile, err := os.OpenFile(logFileName, os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer logFile.Close() + logReader, err := node.Container.Logs(context.Background()) + if err != nil { + return err + } + _, err = io.Copy(logFile, logReader) + if err != nil { + return err + } + te.l.Info().Str("Node", node.ContainerName).Str("File", logFileName).Msg("Wrote Logs") + return nil + }) + } + + if err := eg.Wait(); err != nil { + return err + } + + te.l.Info().Str("Logs Location", folder).Msg("Wrote test logs") + } + // Check if we need to return funds if te.EVMClient.NetworkSimulated() { te.l.Info().Str("Network Name", te.EVMClient.GetNetworkName()). @@ -277,46 +316,5 @@ func (te *CLClusterTestEnv) Cleanup(t *testing.T) error { } } - // TODO: This is an imperfect and temporary solution, see TT-590 for a more sustainable solution - // Collect logs if the test failed - if !t.Failed() { - return nil - } - - folder := fmt.Sprintf("./logs/%s-%s", t.Name(), time.Now().Format("2006-01-02T15-04-05")) - if err := os.MkdirAll(folder, os.ModePerm); err != nil { - return err - } - - te.l.Warn().Msg("Test failed, collecting logs") - eg := &errgroup.Group{} - for _, n := range te.CLNodes { - node := n - eg.Go(func() error { - logFileName := filepath.Join(folder, fmt.Sprintf("node-%s.log", node.ContainerName)) - logFile, err := os.OpenFile(logFileName, os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return err - } - defer logFile.Close() - logReader, err := node.Container.Logs(context.Background()) - if err != nil { - return err - } - _, err = io.Copy(logFile, logReader) - if err != nil { - return err - } - te.l.Info().Str("Node", node.ContainerName).Str("File", logFileName).Msg("Wrote Logs") - return nil - }) - } - - if err := eg.Wait(); err != nil { - return err - } - - te.l.Info().Str("Logs Location", folder).Msg("Wrote Logs for Failed Test") - return nil } diff --git a/integration-tests/example.env b/integration-tests/example.env index 428f76b914..3e27ac2c7a 100644 --- a/integration-tests/example.env +++ b/integration-tests/example.env @@ -2,11 +2,11 @@ # `source ./integration-tests/.env` ########## General Test Settings ########## -export KEEP_ENVIRONMENTS="Never" # Always | OnFail | Never export CHAINLINK_IMAGE="public.ecr.aws/chainlink/chainlink" # Image repo to pull the Chainlink image from export CHAINLINK_VERSION="2.4.0" # Version of the Chainlink image to pull export CHAINLINK_ENV_USER="Satoshi-Nakamoto" # Name of the person running the tests (change to your own) export TEST_LOG_LEVEL="info" # info | debug | trace +export TEST_LOG_COLLECT="false" # true | false, whether to collect the test logs after the test is complete, this will always be done if the test fails, regardless of this setting ########## Soak/Chaos/Load Test Specific Settings ########## # Remote Runner From 24dce590b260d50dc05a99080b24f6eee84fc9a1 Mon Sep 17 00:00:00 2001 From: Mateusz Sekara Date: Thu, 28 Sep 2023 20:48:12 +0200 Subject: [PATCH 5/8] Moving observability for LogPoller closer to the database layer (#10674) * Moving observability for LogPoller closer to the database layer * Minor fixes * Post rebase fixes * Post rebase fixes --- core/chains/evm/chain.go | 2 +- core/chains/evm/logpoller/helper_test.go | 2 +- core/chains/evm/logpoller/log_poller.go | 30 +-- .../evm/logpoller/log_poller_internal_test.go | 2 +- core/chains/evm/logpoller/log_poller_test.go | 8 +- core/chains/evm/logpoller/observability.go | 206 +++++++++++++----- .../evm/logpoller/observability_test.go | 69 +++--- core/chains/evm/logpoller/orm.go | 115 +++++++--- core/chains/evm/logpoller/orm_test.go | 70 +++--- 9 files changed, 319 insertions(+), 185 deletions(-) diff --git a/core/chains/evm/chain.go b/core/chains/evm/chain.go index 8704d0c1fc..b4986ad0c2 100644 --- a/core/chains/evm/chain.go +++ b/core/chains/evm/chain.go @@ -250,7 +250,7 @@ func newChain(ctx context.Context, cfg *evmconfig.ChainScoped, nodes []*toml.Nod if opts.GenLogPoller != nil { logPoller = opts.GenLogPoller(chainID) } else { - logPoller = logpoller.NewObservedLogPoller(logpoller.NewORM(chainID, db, l, cfg.Database()), client, l, cfg.EVM().LogPollInterval(), int64(cfg.EVM().FinalityDepth()), int64(cfg.EVM().LogBackfillBatchSize()), int64(cfg.EVM().RPCDefaultBatchSize()), int64(cfg.EVM().LogKeepBlocksDepth())) + logPoller = logpoller.NewLogPoller(logpoller.NewObservedORM(chainID, db, l, cfg.Database()), client, l, cfg.EVM().LogPollInterval(), int64(cfg.EVM().FinalityDepth()), int64(cfg.EVM().LogBackfillBatchSize()), int64(cfg.EVM().RPCDefaultBatchSize()), int64(cfg.EVM().LogKeepBlocksDepth())) } } diff --git a/core/chains/evm/logpoller/helper_test.go b/core/chains/evm/logpoller/helper_test.go index 4c764d7d74..447a467358 100644 --- a/core/chains/evm/logpoller/helper_test.go +++ b/core/chains/evm/logpoller/helper_test.go @@ -36,7 +36,7 @@ type TestHarness struct { Lggr logger.Logger // Chain2/ORM2 is just a dummy second chain, doesn't have a client. ChainID, ChainID2 *big.Int - ORM, ORM2 *logpoller.ORM + ORM, ORM2 *logpoller.DbORM LogPoller logpoller.LogPollerTest Client *backends.SimulatedBackend Owner *bind.TransactOpts diff --git a/core/chains/evm/logpoller/log_poller.go b/core/chains/evm/logpoller/log_poller.go index b9d1ba2e98..f4b3a7f4f0 100644 --- a/core/chains/evm/logpoller/log_poller.go +++ b/core/chains/evm/logpoller/log_poller.go @@ -87,7 +87,7 @@ var ( type logPoller struct { utils.StartStopOnce ec Client - orm *ORM + orm ORM lggr logger.Logger pollPeriod time.Duration // poll period set by block production rate finalityDepth int64 // finality depth is taken to mean that block (head - finality) is finalized @@ -119,7 +119,7 @@ type logPoller struct { // // How fast that can be done depends largely on network speed and DB, but even for the fastest // support chain, polygon, which has 2s block times, we need RPCs roughly with <= 500ms latency -func NewLogPoller(orm *ORM, ec Client, lggr logger.Logger, pollPeriod time.Duration, +func NewLogPoller(orm ORM, ec Client, lggr logger.Logger, pollPeriod time.Duration, finalityDepth int64, backfillBatchSize int64, rpcBatchSize int64, keepBlocksDepth int64) *logPoller { return &logPoller{ @@ -676,7 +676,7 @@ func (lp *logPoller) backfill(ctx context.Context, start, end int64) error { } lp.lggr.Debugw("Backfill found logs", "from", from, "to", to, "logs", len(gethLogs), "blocks", blocks) - err = lp.orm.q.WithOpts(pg.WithParentCtx(ctx)).Transaction(func(tx pg.Queryer) error { + err = lp.orm.Q().WithOpts(pg.WithParentCtx(ctx)).Transaction(func(tx pg.Queryer) error { return lp.orm.InsertLogs(convertLogs(gethLogs, blocks, lp.lggr, lp.ec.ConfiguredChainID()), pg.WithQueryer(tx)) }) if err != nil { @@ -747,7 +747,7 @@ func (lp *logPoller) getCurrentBlockMaybeHandleReorg(ctx context.Context, curren // the canonical set per read. Typically, if an application took action on a log // it would be saved elsewhere e.g. evm.txes, so it seems better to just support the fast reads. // Its also nicely analogous to reading from the chain itself. - err2 = lp.orm.q.WithOpts(pg.WithParentCtx(ctx)).Transaction(func(tx pg.Queryer) error { + err2 = lp.orm.Q().WithOpts(pg.WithParentCtx(ctx)).Transaction(func(tx pg.Queryer) error { // These deletes are bounded by reorg depth, so they are // fast and should not slow down the log readers. err3 := lp.orm.DeleteBlocksAfter(blockAfterLCA.Number, pg.WithQueryer(tx)) @@ -844,7 +844,7 @@ func (lp *logPoller) PollAndSaveLogs(ctx context.Context, currentBlockNumber int return } lp.lggr.Debugw("Unfinalized log query", "logs", len(logs), "currentBlockNumber", currentBlockNumber, "blockHash", currentBlock.Hash, "timestamp", currentBlock.Timestamp.Unix()) - err = lp.orm.q.WithOpts(pg.WithParentCtx(ctx)).Transaction(func(tx pg.Queryer) error { + err = lp.orm.Q().WithOpts(pg.WithParentCtx(ctx)).Transaction(func(tx pg.Queryer) error { if err2 := lp.orm.InsertBlock(h, currentBlockNumber, currentBlock.Timestamp, pg.WithQueryer(tx)); err2 != nil { return err2 } @@ -937,11 +937,11 @@ func (lp *logPoller) pruneOldBlocks(ctx context.Context) error { // Logs returns logs matching topics and address (exactly) in the given block range, // which are canonical at time of query. func (lp *logPoller) Logs(start, end int64, eventSig common.Hash, address common.Address, qopts ...pg.QOpt) ([]Log, error) { - return lp.orm.SelectLogsByBlockRangeFilter(start, end, address, eventSig, qopts...) + return lp.orm.SelectLogs(start, end, address, eventSig, qopts...) } func (lp *logPoller) LogsWithSigs(start, end int64, eventSigs []common.Hash, address common.Address, qopts ...pg.QOpt) ([]Log, error) { - return lp.orm.SelectLogsWithSigsByBlockRangeFilter(start, end, address, eventSigs, qopts...) + return lp.orm.SelectLogsWithSigs(start, end, address, eventSigs, qopts...) } func (lp *logPoller) LogsCreatedAfter(eventSig common.Hash, address common.Address, after time.Time, confs int, qopts ...pg.QOpt) ([]Log, error) { @@ -955,7 +955,7 @@ func (lp *logPoller) IndexedLogs(eventSig common.Hash, address common.Address, t // IndexedLogsByBlockRange finds all the logs that have a topic value in topicValues at index topicIndex within the block range func (lp *logPoller) IndexedLogsByBlockRange(start, end int64, eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, qopts ...pg.QOpt) ([]Log, error) { - return lp.orm.SelectIndexedLogsByBlockRangeFilter(start, end, address, eventSig, topicIndex, topicValues, qopts...) + return lp.orm.SelectIndexedLogsByBlockRange(start, end, address, eventSig, topicIndex, topicValues, qopts...) } func (lp *logPoller) IndexedLogsCreatedAfter(eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, after time.Time, confs int, qopts ...pg.QOpt) ([]Log, error) { @@ -968,28 +968,28 @@ func (lp *logPoller) IndexedLogsByTxHash(eventSig common.Hash, txHash common.Has // LogsDataWordGreaterThan note index is 0 based. func (lp *logPoller) LogsDataWordGreaterThan(eventSig common.Hash, address common.Address, wordIndex int, wordValueMin common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { - return lp.orm.SelectDataWordGreaterThan(address, eventSig, wordIndex, wordValueMin, confs, qopts...) + return lp.orm.SelectLogsDataWordGreaterThan(address, eventSig, wordIndex, wordValueMin, confs, qopts...) } // LogsDataWordRange note index is 0 based. func (lp *logPoller) LogsDataWordRange(eventSig common.Hash, address common.Address, wordIndex int, wordValueMin, wordValueMax common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { - return lp.orm.SelectDataWordRange(address, eventSig, wordIndex, wordValueMin, wordValueMax, confs, qopts...) + return lp.orm.SelectLogsDataWordRange(address, eventSig, wordIndex, wordValueMin, wordValueMax, confs, qopts...) } // IndexedLogsTopicGreaterThan finds all the logs that have a topic value greater than topicValueMin at index topicIndex. // Only works for integer topics. func (lp *logPoller) IndexedLogsTopicGreaterThan(eventSig common.Hash, address common.Address, topicIndex int, topicValueMin common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { - return lp.orm.SelectIndexLogsTopicGreaterThan(address, eventSig, topicIndex, topicValueMin, confs, qopts...) + return lp.orm.SelectIndexedLogsTopicGreaterThan(address, eventSig, topicIndex, topicValueMin, confs, qopts...) } // LogsUntilBlockHashDataWordGreaterThan note index is 0 based. // If the blockhash is not found (i.e. a stale fork) it will error. func (lp *logPoller) LogsUntilBlockHashDataWordGreaterThan(eventSig common.Hash, address common.Address, wordIndex int, wordValueMin common.Hash, untilBlockHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { - return lp.orm.SelectUntilBlockHashDataWordGreaterThan(address, eventSig, wordIndex, wordValueMin, untilBlockHash, qopts...) + return lp.orm.SelectLogsUntilBlockHashDataWordGreaterThan(address, eventSig, wordIndex, wordValueMin, untilBlockHash, qopts...) } func (lp *logPoller) IndexedLogsTopicRange(eventSig common.Hash, address common.Address, topicIndex int, topicValueMin common.Hash, topicValueMax common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { - return lp.orm.SelectIndexLogsTopicRange(address, eventSig, topicIndex, topicValueMin, topicValueMax, confs, qopts...) + return lp.orm.SelectIndexedLogsTopicRange(address, eventSig, topicIndex, topicValueMin, topicValueMax, confs, qopts...) } // LatestBlock returns the latest block the log poller is on. It tracks blocks to be able @@ -1009,7 +1009,7 @@ func (lp *logPoller) BlockByNumber(n int64, qopts ...pg.QOpt) (*LogPollerBlock, // LatestLogByEventSigWithConfs finds the latest log that has confs number of blocks on top of the log. func (lp *logPoller) LatestLogByEventSigWithConfs(eventSig common.Hash, address common.Address, confs int, qopts ...pg.QOpt) (*Log, error) { - return lp.orm.SelectLatestLogEventSigWithConfs(eventSig, address, confs, qopts...) + return lp.orm.SelectLatestLogByEventSigWithConfs(eventSig, address, confs, qopts...) } func (lp *logPoller) LatestLogEventSigsAddrsWithConfs(fromBlock int64, eventSigs []common.Hash, addresses []common.Address, confs int, qopts ...pg.QOpt) ([]Log, error) { @@ -1017,7 +1017,7 @@ func (lp *logPoller) LatestLogEventSigsAddrsWithConfs(fromBlock int64, eventSigs } func (lp *logPoller) LatestBlockByEventSigsAddrsWithConfs(fromBlock int64, eventSigs []common.Hash, addresses []common.Address, confs int, qopts ...pg.QOpt) (int64, error) { - return lp.orm.SelectLatestBlockNumberEventSigsAddrsWithConfs(fromBlock, eventSigs, addresses, confs, qopts...) + return lp.orm.SelectLatestBlockByEventSigsAddrsWithConfs(fromBlock, eventSigs, addresses, confs, qopts...) } // GetBlocksRange tries to get the specified block numbers from the log pollers diff --git a/core/chains/evm/logpoller/log_poller_internal_test.go b/core/chains/evm/logpoller/log_poller_internal_test.go index 5f1c21a5b8..271d8c2a58 100644 --- a/core/chains/evm/logpoller/log_poller_internal_test.go +++ b/core/chains/evm/logpoller/log_poller_internal_test.go @@ -31,7 +31,7 @@ var ( ) // Validate that filters stored in log_filters_table match the filters stored in memory -func validateFiltersTable(t *testing.T, lp *logPoller, orm *ORM) { +func validateFiltersTable(t *testing.T, lp *logPoller, orm *DbORM) { filters, err := orm.LoadFilters() require.NoError(t, err) require.Equal(t, len(filters), len(lp.filters)) diff --git a/core/chains/evm/logpoller/log_poller_test.go b/core/chains/evm/logpoller/log_poller_test.go index 3d4218d6ff..e21fc0f383 100644 --- a/core/chains/evm/logpoller/log_poller_test.go +++ b/core/chains/evm/logpoller/log_poller_test.go @@ -43,7 +43,7 @@ func logRuntime(t testing.TB, start time.Time) { t.Log("runtime", time.Since(start)) } -func populateDatabase(t testing.TB, o *logpoller.ORM, chainID *big.Int) (common.Hash, common.Address, common.Address) { +func populateDatabase(t testing.TB, o *logpoller.DbORM, chainID *big.Int) (common.Hash, common.Address, common.Address) { event1 := EmitterABI.Events["Log1"].ID address1 := common.HexToAddress("0x2ab9a2Dc53736b361b72d900CdF9F78F9406fbbb") address2 := common.HexToAddress("0x6E225058950f237371261C985Db6bDe26df2200E") @@ -110,7 +110,7 @@ func TestPopulateLoadedDB(t *testing.T) { func() { defer logRuntime(t, time.Now()) - _, err1 := o.SelectLogsByBlockRangeFilter(750000, 800000, address1, event1) + _, err1 := o.SelectLogs(750000, 800000, address1, event1) require.NoError(t, err1) }() func() { @@ -123,7 +123,7 @@ func TestPopulateLoadedDB(t *testing.T) { require.NoError(t, o.InsertBlock(common.HexToHash("0x10"), 1000000, time.Now())) func() { defer logRuntime(t, time.Now()) - lgs, err1 := o.SelectDataWordRange(address1, event1, 0, logpoller.EvmWord(500000), logpoller.EvmWord(500020), 0) + lgs, err1 := o.SelectLogsDataWordRange(address1, event1, 0, logpoller.EvmWord(500000), logpoller.EvmWord(500020), 0) require.NoError(t, err1) // 10 since every other log is for address1 assert.Equal(t, 10, len(lgs)) @@ -138,7 +138,7 @@ func TestPopulateLoadedDB(t *testing.T) { func() { defer logRuntime(t, time.Now()) - lgs, err1 := o.SelectIndexLogsTopicRange(address1, event1, 1, logpoller.EvmWord(500000), logpoller.EvmWord(500020), 0) + lgs, err1 := o.SelectIndexedLogsTopicRange(address1, event1, 1, logpoller.EvmWord(500000), logpoller.EvmWord(500020), 0) require.NoError(t, err1) assert.Equal(t, 10, len(lgs)) }() diff --git a/core/chains/evm/logpoller/observability.go b/core/chains/evm/logpoller/observability.go index 8dfa6e81d0..4e0ebe7418 100644 --- a/core/chains/evm/logpoller/observability.go +++ b/core/chains/evm/logpoller/observability.go @@ -1,11 +1,13 @@ package logpoller import ( + "math/big" "time" "github.com/ethereum/go-ethereum/common" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/smartcontractkit/sqlx" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -46,113 +48,199 @@ var ( }, []string{"evmChainID", "query"}) ) -// ObservedLogPoller is a decorator layer for LogPoller, responsible for pushing Prometheus metrics reporting duration and size of result set for some of the queries. -// It doesn't change internal logic, because all calls are delegated to the origin LogPoller -type ObservedLogPoller struct { - LogPoller +// ObservedORM is a decorator layer for ORM used by LogPoller, responsible for pushing Prometheus metrics reporting duration and size of result set for the queries. +// It doesn't change internal logic, because all calls are delegated to the origin ORM +type ObservedORM struct { + ORM queryDuration *prometheus.HistogramVec datasetSize *prometheus.GaugeVec chainId string } -// NewObservedLogPoller creates an observed version of log poller created by NewLogPoller +// NewObservedORM creates an observed version of log poller's ORM created by NewORM // Please see ObservedLogPoller for more details on how latencies are measured -func NewObservedLogPoller(orm *ORM, ec Client, lggr logger.Logger, pollPeriod time.Duration, - finalityDepth int64, backfillBatchSize int64, rpcBatchSize int64, keepBlocksDepth int64) LogPoller { - - return &ObservedLogPoller{ - LogPoller: NewLogPoller(orm, ec, lggr, pollPeriod, finalityDepth, backfillBatchSize, rpcBatchSize, keepBlocksDepth), +func NewObservedORM(chainID *big.Int, db *sqlx.DB, lggr logger.Logger, cfg pg.QConfig) *ObservedORM { + return &ObservedORM{ + ORM: NewORM(chainID, db, lggr, cfg), queryDuration: lpQueryDuration, datasetSize: lpQueryDataSets, - chainId: orm.chainID.String(), + chainId: chainID.String(), } } -func (o *ObservedLogPoller) LogsCreatedAfter(eventSig common.Hash, address common.Address, after time.Time, confs int, qopts ...pg.QOpt) ([]Log, error) { - return withObservedQueryAndResults(o, "LogsCreatedAfter", func() ([]Log, error) { - return o.LogPoller.LogsCreatedAfter(eventSig, address, after, confs, qopts...) +func (o *ObservedORM) Q() pg.Q { + return o.ORM.Q() +} + +func (o *ObservedORM) InsertLogs(logs []Log, qopts ...pg.QOpt) error { + return withObservedExec(o, "InsertLogs", func() error { + return o.ORM.InsertLogs(logs, qopts...) + }) +} + +func (o *ObservedORM) InsertBlock(h common.Hash, n int64, t time.Time, qopts ...pg.QOpt) error { + return withObservedExec(o, "InsertBlock", func() error { + return o.ORM.InsertBlock(h, n, t, qopts...) + }) +} + +func (o *ObservedORM) InsertFilter(filter Filter, qopts ...pg.QOpt) error { + return withObservedExec(o, "InsertFilter", func() error { + return o.ORM.InsertFilter(filter, qopts...) + }) +} + +func (o *ObservedORM) LoadFilters(qopts ...pg.QOpt) (map[string]Filter, error) { + return withObservedQuery(o, "LoadFilters", func() (map[string]Filter, error) { + return o.ORM.LoadFilters(qopts...) + }) +} + +func (o *ObservedORM) DeleteFilter(name string, qopts ...pg.QOpt) error { + return withObservedExec(o, "DeleteFilter", func() error { + return o.ORM.DeleteFilter(name, qopts...) + }) +} + +func (o *ObservedORM) DeleteBlocksAfter(start int64, qopts ...pg.QOpt) error { + return withObservedExec(o, "DeleteBlocksAfter", func() error { + return o.ORM.DeleteBlocksAfter(start, qopts...) + }) +} + +func (o *ObservedORM) DeleteBlocksBefore(end int64, qopts ...pg.QOpt) error { + return withObservedExec(o, "DeleteBlocksBefore", func() error { + return o.ORM.DeleteBlocksBefore(end, qopts...) + }) +} + +func (o *ObservedORM) DeleteLogsAfter(start int64, qopts ...pg.QOpt) error { + return withObservedExec(o, "DeleteLogsAfter", func() error { + return o.ORM.DeleteLogsAfter(start, qopts...) + }) +} + +func (o *ObservedORM) DeleteExpiredLogs(qopts ...pg.QOpt) error { + return withObservedExec(o, "DeleteExpiredLogs", func() error { + return o.ORM.DeleteExpiredLogs(qopts...) + }) +} + +func (o *ObservedORM) SelectBlockByNumber(n int64, qopts ...pg.QOpt) (*LogPollerBlock, error) { + return withObservedQuery(o, "SelectBlockByNumber", func() (*LogPollerBlock, error) { + return o.ORM.SelectBlockByNumber(n, qopts...) + }) +} + +func (o *ObservedORM) SelectLatestBlock(qopts ...pg.QOpt) (*LogPollerBlock, error) { + return withObservedQuery(o, "SelectLatestBlock", func() (*LogPollerBlock, error) { + return o.ORM.SelectLatestBlock(qopts...) + }) +} + +func (o *ObservedORM) SelectLatestLogByEventSigWithConfs(eventSig common.Hash, address common.Address, confs int, qopts ...pg.QOpt) (*Log, error) { + return withObservedQuery(o, "SelectLatestLogByEventSigWithConfs", func() (*Log, error) { + return o.ORM.SelectLatestLogByEventSigWithConfs(eventSig, address, confs, qopts...) + }) +} + +func (o *ObservedORM) SelectLogsWithSigs(start, end int64, address common.Address, eventSigs []common.Hash, qopts ...pg.QOpt) ([]Log, error) { + return withObservedQueryAndResults(o, "SelectLogsWithSigs", func() ([]Log, error) { + return o.ORM.SelectLogsWithSigs(start, end, address, eventSigs, qopts...) }) } -func (o *ObservedLogPoller) LatestLogByEventSigWithConfs(eventSig common.Hash, address common.Address, confs int, qopts ...pg.QOpt) (*Log, error) { - return withObservedQuery(o, "LatestLogByEventSigWithConfs", func() (*Log, error) { - return o.LogPoller.LatestLogByEventSigWithConfs(eventSig, address, confs, qopts...) +func (o *ObservedORM) SelectLogsCreatedAfter(address common.Address, eventSig common.Hash, after time.Time, confs int, qopts ...pg.QOpt) ([]Log, error) { + return withObservedQueryAndResults(o, "SelectLogsCreatedAfter", func() ([]Log, error) { + return o.ORM.SelectLogsCreatedAfter(address, eventSig, after, confs, qopts...) }) } -func (o *ObservedLogPoller) LatestLogEventSigsAddrsWithConfs(fromBlock int64, eventSigs []common.Hash, addresses []common.Address, confs int, qopts ...pg.QOpt) ([]Log, error) { - return withObservedQueryAndResults(o, "LatestLogEventSigsAddrsWithConfs", func() ([]Log, error) { - return o.LogPoller.LatestLogEventSigsAddrsWithConfs(fromBlock, eventSigs, addresses, confs, qopts...) +func (o *ObservedORM) SelectIndexedLogs(address common.Address, eventSig common.Hash, topicIndex int, topicValues []common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { + return withObservedQueryAndResults(o, "SelectIndexedLogs", func() ([]Log, error) { + return o.ORM.SelectIndexedLogs(address, eventSig, topicIndex, topicValues, confs, qopts...) }) } -func (o *ObservedLogPoller) LatestBlockByEventSigsAddrsWithConfs(fromBlock int64, eventSigs []common.Hash, addresses []common.Address, confs int, qopts ...pg.QOpt) (int64, error) { - return withObservedQuery(o, "LatestBlockByEventSigsAddrsWithConfs", func() (int64, error) { - return o.LogPoller.LatestBlockByEventSigsAddrsWithConfs(fromBlock, eventSigs, addresses, confs, qopts...) +func (o *ObservedORM) SelectIndexedLogsByBlockRange(start, end int64, address common.Address, eventSig common.Hash, topicIndex int, topicValues []common.Hash, qopts ...pg.QOpt) ([]Log, error) { + return withObservedQueryAndResults(o, "SelectIndexedLogsByBlockRange", func() ([]Log, error) { + return o.ORM.SelectIndexedLogsByBlockRange(start, end, address, eventSig, topicIndex, topicValues, qopts...) }) } -func (o *ObservedLogPoller) IndexedLogs(eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { - return withObservedQueryAndResults(o, "IndexedLogs", func() ([]Log, error) { - return o.LogPoller.IndexedLogs(eventSig, address, topicIndex, topicValues, confs, qopts...) +func (o *ObservedORM) SelectIndexedLogsCreatedAfter(address common.Address, eventSig common.Hash, topicIndex int, topicValues []common.Hash, after time.Time, confs int, qopts ...pg.QOpt) ([]Log, error) { + return withObservedQueryAndResults(o, "SelectIndexedLogsCreatedAfter", func() ([]Log, error) { + return o.ORM.SelectIndexedLogsCreatedAfter(address, eventSig, topicIndex, topicValues, after, confs, qopts...) }) } -func (o *ObservedLogPoller) IndexedLogsByBlockRange(start, end int64, eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, qopts ...pg.QOpt) ([]Log, error) { - return withObservedQueryAndResults(o, "IndexedLogsByBlockRange", func() ([]Log, error) { - return o.LogPoller.IndexedLogsByBlockRange(start, end, eventSig, address, topicIndex, topicValues, qopts...) +func (o *ObservedORM) SelectIndexedLogsWithSigsExcluding(sigA, sigB common.Hash, topicIndex int, address common.Address, startBlock, endBlock int64, confs int, qopts ...pg.QOpt) ([]Log, error) { + return withObservedQueryAndResults(o, "SelectIndexedLogsWithSigsExcluding", func() ([]Log, error) { + return o.ORM.SelectIndexedLogsWithSigsExcluding(sigA, sigB, topicIndex, address, startBlock, endBlock, confs, qopts...) }) } -func (o *ObservedLogPoller) IndexedLogsCreatedAfter(eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, after time.Time, confs int, qopts ...pg.QOpt) ([]Log, error) { - return withObservedQueryAndResults(o, "IndexedLogsCreatedAfter", func() ([]Log, error) { - return o.LogPoller.IndexedLogsCreatedAfter(eventSig, address, topicIndex, topicValues, after, confs, qopts...) +func (o *ObservedORM) SelectLogs(start, end int64, address common.Address, eventSig common.Hash, qopts ...pg.QOpt) ([]Log, error) { + return withObservedQueryAndResults(o, "SelectLogs", func() ([]Log, error) { + return o.ORM.SelectLogs(start, end, address, eventSig, qopts...) }) } -func (o *ObservedLogPoller) IndexedLogsByTxHash(eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { +func (o *ObservedORM) IndexedLogsByTxHash(eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { return withObservedQueryAndResults(o, "IndexedLogsByTxHash", func() ([]Log, error) { - return o.LogPoller.IndexedLogsByTxHash(eventSig, txHash, qopts...) + return o.ORM.SelectIndexedLogsByTxHash(eventSig, txHash, qopts...) }) } -func (o *ObservedLogPoller) IndexedLogsTopicGreaterThan(eventSig common.Hash, address common.Address, topicIndex int, topicValueMin common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { - return withObservedQueryAndResults(o, "IndexedLogsTopicGreaterThan", func() ([]Log, error) { - return o.LogPoller.IndexedLogsTopicGreaterThan(eventSig, address, topicIndex, topicValueMin, confs, qopts...) +func (o *ObservedORM) GetBlocksRange(start uint64, end uint64, qopts ...pg.QOpt) ([]LogPollerBlock, error) { + return withObservedQueryAndResults(o, "GetBlocksRange", func() ([]LogPollerBlock, error) { + return o.ORM.GetBlocksRange(start, end, qopts...) }) } -func (o *ObservedLogPoller) IndexedLogsTopicRange(eventSig common.Hash, address common.Address, topicIndex int, topicValueMin common.Hash, topicValueMax common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { - return withObservedQueryAndResults(o, "IndexedLogsTopicRange", func() ([]Log, error) { - return o.LogPoller.IndexedLogsTopicRange(eventSig, address, topicIndex, topicValueMin, topicValueMax, confs, qopts...) +func (o *ObservedORM) SelectLatestLogEventSigsAddrsWithConfs(fromBlock int64, addresses []common.Address, eventSigs []common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { + return withObservedQueryAndResults(o, "SelectLatestLogEventSigsAddrsWithConfs", func() ([]Log, error) { + return o.ORM.SelectLatestLogEventSigsAddrsWithConfs(fromBlock, addresses, eventSigs, confs, qopts...) }) } -func (o *ObservedLogPoller) IndexedLogsWithSigsExcluding(address common.Address, eventSigA, eventSigB common.Hash, topicIndex int, fromBlock, toBlock int64, confs int, qopts ...pg.QOpt) ([]Log, error) { - return withObservedQueryAndResults(o, "IndexedLogsWithSigsExcluding", func() ([]Log, error) { - return o.LogPoller.IndexedLogsWithSigsExcluding(address, eventSigA, eventSigB, topicIndex, fromBlock, toBlock, confs, qopts...) +func (o *ObservedORM) SelectLatestBlockByEventSigsAddrsWithConfs(fromBlock int64, eventSigs []common.Hash, addresses []common.Address, confs int, qopts ...pg.QOpt) (int64, error) { + return withObservedQuery(o, "SelectLatestBlockByEventSigsAddrsWithConfs", func() (int64, error) { + return o.ORM.SelectLatestBlockByEventSigsAddrsWithConfs(fromBlock, eventSigs, addresses, confs, qopts...) }) } -func (o *ObservedLogPoller) LogsDataWordRange(eventSig common.Hash, address common.Address, wordIndex int, wordValueMin, wordValueMax common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { - return withObservedQueryAndResults(o, "LogsDataWordRange", func() ([]Log, error) { - return o.LogPoller.LogsDataWordRange(eventSig, address, wordIndex, wordValueMin, wordValueMax, confs, qopts...) +func (o *ObservedORM) SelectLogsDataWordRange(address common.Address, eventSig common.Hash, wordIndex int, wordValueMin, wordValueMax common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { + return withObservedQueryAndResults(o, "SelectLogsDataWordRange", func() ([]Log, error) { + return o.ORM.SelectLogsDataWordRange(address, eventSig, wordIndex, wordValueMin, wordValueMax, confs, qopts...) }) } -func (o *ObservedLogPoller) LogsDataWordGreaterThan(eventSig common.Hash, address common.Address, wordIndex int, wordValueMin common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { - return withObservedQueryAndResults(o, "LogsDataWordGreaterThan", func() ([]Log, error) { - return o.LogPoller.LogsDataWordGreaterThan(eventSig, address, wordIndex, wordValueMin, confs, qopts...) +func (o *ObservedORM) SelectLogsDataWordGreaterThan(address common.Address, eventSig common.Hash, wordIndex int, wordValueMin common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { + return withObservedQueryAndResults(o, "SelectLogsDataWordGreaterThan", func() ([]Log, error) { + return o.ORM.SelectLogsDataWordGreaterThan(address, eventSig, wordIndex, wordValueMin, confs, qopts...) }) } -func (o *ObservedLogPoller) LogsUntilBlockHashDataWordGreaterThan(eventSig common.Hash, address common.Address, wordIndex int, wordValueMin common.Hash, untilBlockHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { - return withObservedQueryAndResults(o, "LogsUntilBlockHashDataWordGreaterThan", func() ([]Log, error) { - return o.LogPoller.LogsUntilBlockHashDataWordGreaterThan(eventSig, address, wordIndex, wordValueMin, untilBlockHash, qopts...) +func (o *ObservedORM) SelectLogsUntilBlockHashDataWordGreaterThan(address common.Address, eventSig common.Hash, wordIndex int, wordValueMin common.Hash, untilBlockHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { + return withObservedQueryAndResults(o, "SelectLogsUntilBlockHashDataWordGreaterThan", func() ([]Log, error) { + return o.ORM.SelectLogsUntilBlockHashDataWordGreaterThan(address, eventSig, wordIndex, wordValueMin, untilBlockHash, qopts...) }) } -func withObservedQueryAndResults[T any](o *ObservedLogPoller, queryName string, query func() ([]T, error)) ([]T, error) { +func (o *ObservedORM) SelectIndexedLogsTopicGreaterThan(address common.Address, eventSig common.Hash, topicIndex int, topicValueMin common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { + return withObservedQueryAndResults(o, "SelectIndexedLogsTopicGreaterThan", func() ([]Log, error) { + return o.ORM.SelectIndexedLogsTopicGreaterThan(address, eventSig, topicIndex, topicValueMin, confs, qopts...) + }) +} + +func (o *ObservedORM) SelectIndexedLogsTopicRange(address common.Address, eventSig common.Hash, topicIndex int, topicValueMin, topicValueMax common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { + return withObservedQueryAndResults(o, "SelectIndexedLogsTopicRange", func() ([]Log, error) { + return o.ORM.SelectIndexedLogsTopicRange(address, eventSig, topicIndex, topicValueMin, topicValueMax, confs, qopts...) + }) +} + +func withObservedQueryAndResults[T any](o *ObservedORM, queryName string, query func() ([]T, error)) ([]T, error) { results, err := withObservedQuery(o, queryName, query) if err == nil { o.datasetSize. @@ -162,7 +250,7 @@ func withObservedQueryAndResults[T any](o *ObservedLogPoller, queryName string, return results, err } -func withObservedQuery[T any](o *ObservedLogPoller, queryName string, query func() (T, error)) (T, error) { +func withObservedQuery[T any](o *ObservedORM, queryName string, query func() (T, error)) (T, error) { queryStarted := time.Now() defer func() { o.queryDuration. @@ -171,3 +259,13 @@ func withObservedQuery[T any](o *ObservedLogPoller, queryName string, query func }() return query() } + +func withObservedExec(o *ObservedORM, query string, exec func() error) error { + queryStarted := time.Now() + defer func() { + o.queryDuration. + WithLabelValues(o.chainId, query). + Observe(float64(time.Since(queryStarted))) + }() + return exec() +} diff --git a/core/chains/evm/logpoller/observability_test.go b/core/chains/evm/logpoller/observability_test.go index 5bd0a772d9..c26b487bbd 100644 --- a/core/chains/evm/logpoller/observability_test.go +++ b/core/chains/evm/logpoller/observability_test.go @@ -25,20 +25,22 @@ func TestMultipleMetricsArePublished(t *testing.T) { lp := createObservedPollLogger(t, 100) require.Equal(t, 0, testutil.CollectAndCount(lp.queryDuration)) - _, _ = lp.IndexedLogs(common.Hash{}, common.Address{}, 1, []common.Hash{}, 1, pg.WithParentCtx(ctx)) - _, _ = lp.IndexedLogsByBlockRange(0, 1, common.Hash{}, common.Address{}, 1, []common.Hash{}, pg.WithParentCtx(ctx)) - _, _ = lp.IndexedLogsTopicGreaterThan(common.Hash{}, common.Address{}, 1, common.Hash{}, 1, pg.WithParentCtx(ctx)) - _, _ = lp.IndexedLogsTopicRange(common.Hash{}, common.Address{}, 1, common.Hash{}, common.Hash{}, 1, pg.WithParentCtx(ctx)) - _, _ = lp.IndexedLogsWithSigsExcluding(common.Address{}, common.Hash{}, common.Hash{}, 1, 0, 1, 1, pg.WithParentCtx(ctx)) - _, _ = lp.LogsDataWordRange(common.Hash{}, common.Address{}, 0, common.Hash{}, common.Hash{}, 1, pg.WithParentCtx(ctx)) - _, _ = lp.LogsDataWordGreaterThan(common.Hash{}, common.Address{}, 0, common.Hash{}, 1, pg.WithParentCtx(ctx)) - _, _ = lp.LogsCreatedAfter(common.Hash{}, common.Address{}, time.Now(), 0, pg.WithParentCtx(ctx)) - _, _ = lp.LatestLogByEventSigWithConfs(common.Hash{}, common.Address{}, 0, pg.WithParentCtx(ctx)) - _, _ = lp.LatestLogEventSigsAddrsWithConfs(0, []common.Hash{{}}, []common.Address{{}}, 1, pg.WithParentCtx(ctx)) - _, _ = lp.IndexedLogsCreatedAfter(common.Hash{}, common.Address{}, 0, []common.Hash{}, time.Now(), 0, pg.WithParentCtx(ctx)) - _, _ = lp.LogsUntilBlockHashDataWordGreaterThan(common.Hash{}, common.Address{}, 0, common.Hash{}, common.Hash{}, pg.WithParentCtx(ctx)) - - require.Equal(t, 12, testutil.CollectAndCount(lp.queryDuration)) + _, _ = lp.SelectIndexedLogs(common.Address{}, common.Hash{}, 1, []common.Hash{}, 1, pg.WithParentCtx(ctx)) + _, _ = lp.SelectIndexedLogsByBlockRange(0, 1, common.Address{}, common.Hash{}, 1, []common.Hash{}, pg.WithParentCtx(ctx)) + _, _ = lp.SelectIndexedLogsTopicGreaterThan(common.Address{}, common.Hash{}, 1, common.Hash{}, 1, pg.WithParentCtx(ctx)) + _, _ = lp.SelectIndexedLogsTopicRange(common.Address{}, common.Hash{}, 1, common.Hash{}, common.Hash{}, 1, pg.WithParentCtx(ctx)) + _, _ = lp.SelectIndexedLogsWithSigsExcluding(common.Hash{}, common.Hash{}, 1, common.Address{}, 0, 1, 1, pg.WithParentCtx(ctx)) + _, _ = lp.SelectLogsDataWordRange(common.Address{}, common.Hash{}, 0, common.Hash{}, common.Hash{}, 1, pg.WithParentCtx(ctx)) + _, _ = lp.SelectLogsDataWordGreaterThan(common.Address{}, common.Hash{}, 0, common.Hash{}, 1, pg.WithParentCtx(ctx)) + _, _ = lp.SelectLogsCreatedAfter(common.Address{}, common.Hash{}, time.Now(), 0, pg.WithParentCtx(ctx)) + _, _ = lp.SelectLatestLogByEventSigWithConfs(common.Hash{}, common.Address{}, 0, pg.WithParentCtx(ctx)) + _, _ = lp.SelectLatestLogEventSigsAddrsWithConfs(0, []common.Address{{}}, []common.Hash{{}}, 1, pg.WithParentCtx(ctx)) + _, _ = lp.SelectIndexedLogsCreatedAfter(common.Address{}, common.Hash{}, 0, []common.Hash{}, time.Now(), 0, pg.WithParentCtx(ctx)) + _, _ = lp.SelectLogsUntilBlockHashDataWordGreaterThan(common.Address{}, common.Hash{}, 0, common.Hash{}, common.Hash{}, pg.WithParentCtx(ctx)) + _ = lp.InsertLogs([]Log{}, pg.WithParentCtx(ctx)) + _ = lp.InsertBlock(common.Hash{}, 0, time.Now(), pg.WithParentCtx(ctx)) + + require.Equal(t, 14, testutil.CollectAndCount(lp.queryDuration)) require.Equal(t, 10, testutil.CollectAndCount(lp.datasetSize)) resetMetrics(*lp) } @@ -48,31 +50,15 @@ func TestShouldPublishDurationInCaseOfError(t *testing.T) { lp := createObservedPollLogger(t, 200) require.Equal(t, 0, testutil.CollectAndCount(lp.queryDuration)) - _, err := lp.LatestLogByEventSigWithConfs(common.Hash{}, common.Address{}, 0, pg.WithParentCtx(ctx)) + _, err := lp.SelectLatestLogByEventSigWithConfs(common.Hash{}, common.Address{}, 0, pg.WithParentCtx(ctx)) require.Error(t, err) require.Equal(t, 1, testutil.CollectAndCount(lp.queryDuration)) - require.Equal(t, 1, counterFromHistogramByLabels(t, lp.queryDuration, "200", "LatestLogByEventSigWithConfs")) + require.Equal(t, 1, counterFromHistogramByLabels(t, lp.queryDuration, "200", "SelectLatestLogByEventSigWithConfs")) resetMetrics(*lp) } -func TestNotObservedFunctions(t *testing.T) { - ctx := testutils.Context(t) - lp := createObservedPollLogger(t, 300) - require.Equal(t, 0, testutil.CollectAndCount(lp.queryDuration)) - - _, err := lp.Logs(0, 1, common.Hash{}, common.Address{}, pg.WithParentCtx(ctx)) - require.NoError(t, err) - - _, err = lp.LogsWithSigs(0, 1, []common.Hash{{}}, common.Address{}, pg.WithParentCtx(ctx)) - require.NoError(t, err) - - require.Equal(t, 0, testutil.CollectAndCount(lp.queryDuration)) - require.Equal(t, 0, testutil.CollectAndCount(lp.datasetSize)) - resetMetrics(*lp) -} - func TestMetricsAreProperlyPopulatedWithLabels(t *testing.T) { lp := createObservedPollLogger(t, 420) expectedCount := 9 @@ -105,16 +91,23 @@ func TestNotPublishingDatasetSizeInCaseOfError(t *testing.T) { require.Equal(t, 0, counterFromGaugeByLabels(lp.datasetSize, "420", "errorQuery")) } -func createObservedPollLogger(t *testing.T, chainId int64) *ObservedLogPoller { +func TestMetricsAreProperlyPopulatedForWrites(t *testing.T) { + lp := createObservedPollLogger(t, 420) + require.NoError(t, withObservedExec(lp, "execQuery", func() error { return nil })) + require.Error(t, withObservedExec(lp, "execQuery", func() error { return fmt.Errorf("error") })) + + require.Equal(t, 2, counterFromHistogramByLabels(t, lp.queryDuration, "420", "execQuery")) +} + +func createObservedPollLogger(t *testing.T, chainId int64) *ObservedORM { lggr, _ := logger.TestLoggerObserved(t, zapcore.ErrorLevel) db := pgtest.NewSqlxDB(t) - orm := NewORM(big.NewInt(chainId), db, lggr, pgtest.NewQConfig(true)) - return NewObservedLogPoller( - orm, nil, lggr, 1, 1, 1, 1, 1000, - ).(*ObservedLogPoller) + return NewObservedORM( + big.NewInt(chainId), db, lggr, pgtest.NewQConfig(true), + ) } -func resetMetrics(lp ObservedLogPoller) { +func resetMetrics(lp ObservedORM) { lp.queryDuration.Reset() lp.datasetSize.Reset() } diff --git a/core/chains/evm/logpoller/orm.go b/core/chains/evm/logpoller/orm.go index b26c4ac7df..8cb80094c0 100644 --- a/core/chains/evm/logpoller/orm.go +++ b/core/chains/evm/logpoller/orm.go @@ -16,23 +16,67 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/utils" ) -type ORM struct { +// ORM represents the persistent data access layer used by the log poller. At this moment, it's a bit leaky abstraction, because +// it exposes some of the database implementation details (e.g. pg.Q). Ideally it should be agnostic and could be applied to any persistence layer. +// What is more, LogPoller should not be aware of the underlying database implementation and delegate all the queries to the ORM. +type ORM interface { + Q() pg.Q + InsertLogs(logs []Log, qopts ...pg.QOpt) error + InsertBlock(h common.Hash, n int64, t time.Time, qopts ...pg.QOpt) error + InsertFilter(filter Filter, qopts ...pg.QOpt) error + + LoadFilters(qopts ...pg.QOpt) (map[string]Filter, error) + DeleteFilter(name string, qopts ...pg.QOpt) error + + DeleteBlocksAfter(start int64, qopts ...pg.QOpt) error + DeleteBlocksBefore(end int64, qopts ...pg.QOpt) error + DeleteLogsAfter(start int64, qopts ...pg.QOpt) error + DeleteExpiredLogs(qopts ...pg.QOpt) error + + GetBlocksRange(start uint64, end uint64, qopts ...pg.QOpt) ([]LogPollerBlock, error) + SelectBlockByNumber(n int64, qopts ...pg.QOpt) (*LogPollerBlock, error) + SelectLatestBlock(qopts ...pg.QOpt) (*LogPollerBlock, error) + + SelectLogs(start, end int64, address common.Address, eventSig common.Hash, qopts ...pg.QOpt) ([]Log, error) + SelectLogsWithSigs(start, end int64, address common.Address, eventSigs []common.Hash, qopts ...pg.QOpt) ([]Log, error) + SelectLogsCreatedAfter(address common.Address, eventSig common.Hash, after time.Time, confs int, qopts ...pg.QOpt) ([]Log, error) + SelectLatestLogByEventSigWithConfs(eventSig common.Hash, address common.Address, confs int, qopts ...pg.QOpt) (*Log, error) + SelectLatestLogEventSigsAddrsWithConfs(fromBlock int64, addresses []common.Address, eventSigs []common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) + SelectLatestBlockByEventSigsAddrsWithConfs(fromBlock int64, eventSigs []common.Hash, addresses []common.Address, confs int, qopts ...pg.QOpt) (int64, error) + + SelectIndexedLogs(address common.Address, eventSig common.Hash, topicIndex int, topicValues []common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) + SelectIndexedLogsByBlockRange(start, end int64, address common.Address, eventSig common.Hash, topicIndex int, topicValues []common.Hash, qopts ...pg.QOpt) ([]Log, error) + SelectIndexedLogsCreatedAfter(address common.Address, eventSig common.Hash, topicIndex int, topicValues []common.Hash, after time.Time, confs int, qopts ...pg.QOpt) ([]Log, error) + SelectIndexedLogsTopicGreaterThan(address common.Address, eventSig common.Hash, topicIndex int, topicValueMin common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) + SelectIndexedLogsTopicRange(address common.Address, eventSig common.Hash, topicIndex int, topicValueMin, topicValueMax common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) + SelectIndexedLogsWithSigsExcluding(sigA, sigB common.Hash, topicIndex int, address common.Address, startBlock, endBlock int64, confs int, qopts ...pg.QOpt) ([]Log, error) + SelectIndexedLogsByTxHash(eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) + SelectLogsDataWordRange(address common.Address, eventSig common.Hash, wordIndex int, wordValueMin, wordValueMax common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) + SelectLogsDataWordGreaterThan(address common.Address, eventSig common.Hash, wordIndex int, wordValueMin common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) + SelectLogsUntilBlockHashDataWordGreaterThan(address common.Address, eventSig common.Hash, wordIndex int, wordValueMin common.Hash, untilBlockHash common.Hash, qopts ...pg.QOpt) ([]Log, error) +} + +type DbORM struct { chainID *big.Int q pg.Q } -// NewORM creates an ORM scoped to chainID. -func NewORM(chainID *big.Int, db *sqlx.DB, lggr logger.Logger, cfg pg.QConfig) *ORM { +// NewORM creates a DbORM scoped to chainID. +func NewORM(chainID *big.Int, db *sqlx.DB, lggr logger.Logger, cfg pg.QConfig) *DbORM { namedLogger := lggr.Named("Configs") q := pg.NewQ(db, namedLogger, cfg) - return &ORM{ + return &DbORM{ chainID: chainID, q: q, } } +func (o *DbORM) Q() pg.Q { + return o.q +} + // InsertBlock is idempotent to support replays. -func (o *ORM) InsertBlock(h common.Hash, n int64, t time.Time, qopts ...pg.QOpt) error { +func (o *DbORM) InsertBlock(h common.Hash, n int64, t time.Time, qopts ...pg.QOpt) error { q := o.q.WithOpts(qopts...) err := q.ExecQ(`INSERT INTO evm.log_poller_blocks (evm_chain_id, block_hash, block_number, block_timestamp, created_at) VALUES ($1, $2, $3, $4, NOW()) ON CONFLICT DO NOTHING`, utils.NewBig(o.chainID), h[:], n, t) @@ -43,7 +87,7 @@ func (o *ORM) InsertBlock(h common.Hash, n int64, t time.Time, qopts ...pg.QOpt) // // Each address/event pair must have a unique job id, so it may be removed when the job is deleted. // If a second job tries to overwrite the same pair, this should fail. -func (o *ORM) InsertFilter(filter Filter, qopts ...pg.QOpt) (err error) { +func (o *DbORM) InsertFilter(filter Filter, qopts ...pg.QOpt) (err error) { q := o.q.WithOpts(qopts...) addresses := make([][]byte, 0) events := make([][]byte, 0) @@ -65,13 +109,13 @@ func (o *ORM) InsertFilter(filter Filter, qopts ...pg.QOpt) (err error) { } // DeleteFilter removes all events,address pairs associated with the Filter -func (o *ORM) DeleteFilter(name string, qopts ...pg.QOpt) error { +func (o *DbORM) DeleteFilter(name string, qopts ...pg.QOpt) error { q := o.q.WithOpts(qopts...) return q.ExecQ(`DELETE FROM evm.log_poller_filters WHERE name = $1 AND evm_chain_id = $2`, name, utils.NewBig(o.chainID)) } // LoadFiltersForChain returns all filters for this chain -func (o *ORM) LoadFilters(qopts ...pg.QOpt) (map[string]Filter, error) { +func (o *DbORM) LoadFilters(qopts ...pg.QOpt) (map[string]Filter, error) { q := o.q.WithOpts(qopts...) rows := make([]Filter, 0) err := q.Select(&rows, `SELECT name, @@ -88,7 +132,7 @@ func (o *ORM) LoadFilters(qopts ...pg.QOpt) (map[string]Filter, error) { return filters, err } -func (o *ORM) SelectBlockByHash(h common.Hash, qopts ...pg.QOpt) (*LogPollerBlock, error) { +func (o *DbORM) SelectBlockByHash(h common.Hash, qopts ...pg.QOpt) (*LogPollerBlock, error) { q := o.q.WithOpts(qopts...) var b LogPollerBlock if err := q.Get(&b, `SELECT * FROM evm.log_poller_blocks WHERE block_hash = $1 AND evm_chain_id = $2`, h, utils.NewBig(o.chainID)); err != nil { @@ -97,7 +141,7 @@ func (o *ORM) SelectBlockByHash(h common.Hash, qopts ...pg.QOpt) (*LogPollerBloc return &b, nil } -func (o *ORM) SelectBlockByNumber(n int64, qopts ...pg.QOpt) (*LogPollerBlock, error) { +func (o *DbORM) SelectBlockByNumber(n int64, qopts ...pg.QOpt) (*LogPollerBlock, error) { q := o.q.WithOpts(qopts...) var b LogPollerBlock if err := q.Get(&b, `SELECT * FROM evm.log_poller_blocks WHERE block_number = $1 AND evm_chain_id = $2`, n, utils.NewBig(o.chainID)); err != nil { @@ -106,7 +150,7 @@ func (o *ORM) SelectBlockByNumber(n int64, qopts ...pg.QOpt) (*LogPollerBlock, e return &b, nil } -func (o *ORM) SelectLatestBlock(qopts ...pg.QOpt) (*LogPollerBlock, error) { +func (o *DbORM) SelectLatestBlock(qopts ...pg.QOpt) (*LogPollerBlock, error) { q := o.q.WithOpts(qopts...) var b LogPollerBlock if err := q.Get(&b, `SELECT * FROM evm.log_poller_blocks WHERE evm_chain_id = $1 ORDER BY block_number DESC LIMIT 1`, utils.NewBig(o.chainID)); err != nil { @@ -115,7 +159,7 @@ func (o *ORM) SelectLatestBlock(qopts ...pg.QOpt) (*LogPollerBlock, error) { return &b, nil } -func (o *ORM) SelectLatestLogEventSigWithConfs(eventSig common.Hash, address common.Address, confs int, qopts ...pg.QOpt) (*Log, error) { +func (o *DbORM) SelectLatestLogByEventSigWithConfs(eventSig common.Hash, address common.Address, confs int, qopts ...pg.QOpt) (*Log, error) { q := o.q.WithOpts(qopts...) var l Log if err := q.Get(&l, `SELECT * FROM evm.logs @@ -130,19 +174,19 @@ func (o *ORM) SelectLatestLogEventSigWithConfs(eventSig common.Hash, address com } // DeleteBlocksAfter delete all blocks after and including start. -func (o *ORM) DeleteBlocksAfter(start int64, qopts ...pg.QOpt) error { +func (o *DbORM) DeleteBlocksAfter(start int64, qopts ...pg.QOpt) error { q := o.q.WithOpts(qopts...) return q.ExecQ(`DELETE FROM evm.log_poller_blocks WHERE block_number >= $1 AND evm_chain_id = $2`, start, utils.NewBig(o.chainID)) } // DeleteBlocksBefore delete all blocks before and including end. -func (o *ORM) DeleteBlocksBefore(end int64, qopts ...pg.QOpt) error { +func (o *DbORM) DeleteBlocksBefore(end int64, qopts ...pg.QOpt) error { q := o.q.WithOpts(qopts...) _, err := q.Exec(`DELETE FROM evm.log_poller_blocks WHERE block_number <= $1 AND evm_chain_id = $2`, end, utils.NewBig(o.chainID)) return err } -func (o *ORM) DeleteLogsAfter(start int64, qopts ...pg.QOpt) error { +func (o *DbORM) DeleteLogsAfter(start int64, qopts ...pg.QOpt) error { q := o.q.WithOpts(qopts...) return q.ExecQ(`DELETE FROM evm.logs WHERE block_number >= $1 AND evm_chain_id = $2`, start, utils.NewBig(o.chainID)) } @@ -155,7 +199,7 @@ type Exp struct { ShouldDelete bool } -func (o *ORM) DeleteExpiredLogs(qopts ...pg.QOpt) error { +func (o *DbORM) DeleteExpiredLogs(qopts ...pg.QOpt) error { qopts = append(qopts, pg.WithLongQueryTimeout()) q := o.q.WithOpts(qopts...) @@ -170,7 +214,7 @@ func (o *ORM) DeleteExpiredLogs(qopts ...pg.QOpt) error { } // InsertLogs is idempotent to support replays. -func (o *ORM) InsertLogs(logs []Log, qopts ...pg.QOpt) error { +func (o *DbORM) InsertLogs(logs []Log, qopts ...pg.QOpt) error { for _, log := range logs { if o.chainID.Cmp(log.EvmChainId.ToInt()) != 0 { return errors.Errorf("invalid chainID in log got %v want %v", log.EvmChainId.ToInt(), o.chainID) @@ -203,7 +247,7 @@ func (o *ORM) InsertLogs(logs []Log, qopts ...pg.QOpt) error { return nil } -func (o *ORM) SelectLogsByBlockRange(start, end int64) ([]Log, error) { +func (o *DbORM) SelectLogsByBlockRange(start, end int64) ([]Log, error) { var logs []Log err := o.q.Select(&logs, ` SELECT * FROM evm.logs @@ -216,7 +260,7 @@ func (o *ORM) SelectLogsByBlockRange(start, end int64) ([]Log, error) { } // SelectLogsByBlockRangeFilter finds the logs in a given block range. -func (o *ORM) SelectLogsByBlockRangeFilter(start, end int64, address common.Address, eventSig common.Hash, qopts ...pg.QOpt) ([]Log, error) { +func (o *DbORM) SelectLogs(start, end int64, address common.Address, eventSig common.Hash, qopts ...pg.QOpt) ([]Log, error) { var logs []Log q := o.q.WithOpts(qopts...) err := q.Select(&logs, ` @@ -231,7 +275,7 @@ func (o *ORM) SelectLogsByBlockRangeFilter(start, end int64, address common.Addr } // SelectLogsCreatedAfter finds logs created after some timestamp. -func (o *ORM) SelectLogsCreatedAfter(address common.Address, eventSig common.Hash, after time.Time, confs int, qopts ...pg.QOpt) ([]Log, error) { +func (o *DbORM) SelectLogsCreatedAfter(address common.Address, eventSig common.Hash, after time.Time, confs int, qopts ...pg.QOpt) ([]Log, error) { minBlock, maxBlock, err := o.blocksRangeAfterTimestamp(after, confs, qopts...) if err != nil { return nil, err @@ -255,7 +299,7 @@ func (o *ORM) SelectLogsCreatedAfter(address common.Address, eventSig common.Has // SelectLogsWithSigsByBlockRangeFilter finds the logs in the given block range with the given event signatures // emitted from the given address. -func (o *ORM) SelectLogsWithSigsByBlockRangeFilter(start, end int64, address common.Address, eventSigs []common.Hash, qopts ...pg.QOpt) (logs []Log, err error) { +func (o *DbORM) SelectLogsWithSigs(start, end int64, address common.Address, eventSigs []common.Hash, qopts ...pg.QOpt) (logs []Log, err error) { q := o.q.WithOpts(qopts...) sigs := make([][]byte, 0, len(eventSigs)) for _, sig := range eventSigs { @@ -293,7 +337,7 @@ ORDER BY (evm.logs.block_number, evm.logs.log_index)`, a) return logs, err } -func (o *ORM) GetBlocksRange(start uint64, end uint64, qopts ...pg.QOpt) ([]LogPollerBlock, error) { +func (o *DbORM) GetBlocksRange(start uint64, end uint64, qopts ...pg.QOpt) ([]LogPollerBlock, error) { var blocks []LogPollerBlock q := o.q.WithOpts(qopts...) err := q.Select(&blocks, ` @@ -307,7 +351,7 @@ func (o *ORM) GetBlocksRange(start uint64, end uint64, qopts ...pg.QOpt) ([]LogP } // SelectLatestLogEventSigsAddrsWithConfs finds the latest log by (address, event) combination that matches a list of Addresses and list of events -func (o *ORM) SelectLatestLogEventSigsAddrsWithConfs(fromBlock int64, addresses []common.Address, eventSigs []common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { +func (o *DbORM) SelectLatestLogEventSigsAddrsWithConfs(fromBlock int64, addresses []common.Address, eventSigs []common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { var logs []Log sigs := concatBytes(eventSigs) addrs := concatBytes(addresses) @@ -332,7 +376,7 @@ func (o *ORM) SelectLatestLogEventSigsAddrsWithConfs(fromBlock int64, addresses } // SelectLatestBlockNumberEventSigsAddrsWithConfs finds the latest block number that matches a list of Addresses and list of events. It returns 0 if there is no matching block -func (o *ORM) SelectLatestBlockNumberEventSigsAddrsWithConfs(fromBlock int64, eventSigs []common.Hash, addresses []common.Address, confs int, qopts ...pg.QOpt) (int64, error) { +func (o *DbORM) SelectLatestBlockByEventSigsAddrsWithConfs(fromBlock int64, eventSigs []common.Hash, addresses []common.Address, confs int, qopts ...pg.QOpt) (int64, error) { var blockNumber int64 sigs := concatBytes(eventSigs) addrs := concatBytes(addresses) @@ -352,7 +396,7 @@ func (o *ORM) SelectLatestBlockNumberEventSigsAddrsWithConfs(fromBlock int64, ev return blockNumber, nil } -func (o *ORM) SelectDataWordRange(address common.Address, eventSig common.Hash, wordIndex int, wordValueMin, wordValueMax common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { +func (o *DbORM) SelectLogsDataWordRange(address common.Address, eventSig common.Hash, wordIndex int, wordValueMin, wordValueMax common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { var logs []Log q := o.q.WithOpts(qopts...) err := q.Select(&logs, @@ -369,7 +413,7 @@ func (o *ORM) SelectDataWordRange(address common.Address, eventSig common.Hash, return logs, nil } -func (o *ORM) SelectDataWordGreaterThan(address common.Address, eventSig common.Hash, wordIndex int, wordValueMin common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { +func (o *DbORM) SelectLogsDataWordGreaterThan(address common.Address, eventSig common.Hash, wordIndex int, wordValueMin common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { var logs []Log q := o.q.WithOpts(qopts...) err := q.Select(&logs, @@ -385,7 +429,7 @@ func (o *ORM) SelectDataWordGreaterThan(address common.Address, eventSig common. return logs, nil } -func (o *ORM) SelectIndexLogsTopicGreaterThan(address common.Address, eventSig common.Hash, topicIndex int, topicValueMin common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { +func (o *DbORM) SelectIndexedLogsTopicGreaterThan(address common.Address, eventSig common.Hash, topicIndex int, topicValueMin common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { if err := validateTopicIndex(topicIndex); err != nil { return nil, err } @@ -405,7 +449,7 @@ func (o *ORM) SelectIndexLogsTopicGreaterThan(address common.Address, eventSig c return logs, nil } -func (o *ORM) SelectUntilBlockHashDataWordGreaterThan(address common.Address, eventSig common.Hash, wordIndex int, wordValueMin common.Hash, untilBlockHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { +func (o *DbORM) SelectLogsUntilBlockHashDataWordGreaterThan(address common.Address, eventSig common.Hash, wordIndex int, wordValueMin common.Hash, untilBlockHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { var logs []Log q := o.q.WithOpts(qopts...) err := q.Transaction(func(tx pg.Queryer) error { @@ -430,7 +474,7 @@ func (o *ORM) SelectUntilBlockHashDataWordGreaterThan(address common.Address, ev return logs, nil } -func (o *ORM) SelectIndexLogsTopicRange(address common.Address, eventSig common.Hash, topicIndex int, topicValueMin, topicValueMax common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { +func (o *DbORM) SelectIndexedLogsTopicRange(address common.Address, eventSig common.Hash, topicIndex int, topicValueMin, topicValueMax common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { if err := validateTopicIndex(topicIndex); err != nil { return nil, err } @@ -451,7 +495,7 @@ func (o *ORM) SelectIndexLogsTopicRange(address common.Address, eventSig common. return logs, nil } -func (o *ORM) SelectIndexedLogs(address common.Address, eventSig common.Hash, topicIndex int, topicValues []common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { +func (o *DbORM) SelectIndexedLogs(address common.Address, eventSig common.Hash, topicIndex int, topicValues []common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { if err := validateTopicIndex(topicIndex); err != nil { return nil, err } @@ -474,7 +518,7 @@ func (o *ORM) SelectIndexedLogs(address common.Address, eventSig common.Hash, to } // SelectIndexedLogsByBlockRangeFilter finds the indexed logs in a given block range. -func (o *ORM) SelectIndexedLogsByBlockRangeFilter(start, end int64, address common.Address, eventSig common.Hash, topicIndex int, topicValues []common.Hash, qopts ...pg.QOpt) ([]Log, error) { +func (o *DbORM) SelectIndexedLogsByBlockRange(start, end int64, address common.Address, eventSig common.Hash, topicIndex int, topicValues []common.Hash, qopts ...pg.QOpt) ([]Log, error) { if err := validateTopicIndex(topicIndex); err != nil { return nil, err } @@ -502,12 +546,11 @@ func validateTopicIndex(index int) error { return nil } -func (o *ORM) SelectIndexedLogsCreatedAfter(address common.Address, eventSig common.Hash, topicIndex int, topicValues []common.Hash, after time.Time, confs int, qopts ...pg.QOpt) ([]Log, error) { +func (o *DbORM) SelectIndexedLogsCreatedAfter(address common.Address, eventSig common.Hash, topicIndex int, topicValues []common.Hash, after time.Time, confs int, qopts ...pg.QOpt) ([]Log, error) { minBlock, maxBlock, err := o.blocksRangeAfterTimestamp(after, confs, qopts...) if err != nil { return nil, err } - var logs []Log q := o.q.WithOpts(qopts...) topicValuesBytes := concatBytes(topicValues) @@ -527,7 +570,7 @@ func (o *ORM) SelectIndexedLogsCreatedAfter(address common.Address, eventSig com return logs, nil } -func (o *ORM) SelectIndexedLogsByTxHash(eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { +func (o *DbORM) SelectIndexedLogsByTxHash(eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { q := o.q.WithOpts(qopts...) var logs []Log err := q.Select(&logs, ` @@ -544,7 +587,7 @@ func (o *ORM) SelectIndexedLogsByTxHash(eventSig common.Hash, txHash common.Hash } // SelectIndexedLogsWithSigsExcluding query's for logs that have signature A and exclude logs that have a corresponding signature B, matching is done based on the topic index both logs should be inside the block range and have the minimum number of confirmations -func (o *ORM) SelectIndexedLogsWithSigsExcluding(sigA, sigB common.Hash, topicIndex int, address common.Address, startBlock, endBlock int64, confs int, qopts ...pg.QOpt) ([]Log, error) { +func (o *DbORM) SelectIndexedLogsWithSigsExcluding(sigA, sigB common.Hash, topicIndex int, address common.Address, startBlock, endBlock int64, confs int, qopts ...pg.QOpt) ([]Log, error) { if err := validateTopicIndex(topicIndex); err != nil { return nil, err } @@ -582,7 +625,7 @@ func (o *ORM) SelectIndexedLogsWithSigsExcluding(sigA, sigB common.Hash, topicIn return logs, nil } -func (o *ORM) blocksRangeAfterTimestamp(after time.Time, confs int, qopts ...pg.QOpt) (int64, int64, error) { +func (o *DbORM) blocksRangeAfterTimestamp(after time.Time, confs int, qopts ...pg.QOpt) (int64, int64, error) { type blockRange struct { MinBlockNumber int64 `db:"min_block"` MaxBlockNumber int64 `db:"max_block"` diff --git a/core/chains/evm/logpoller/orm_test.go b/core/chains/evm/logpoller/orm_test.go index f9b51000bb..531aec2ff3 100644 --- a/core/chains/evm/logpoller/orm_test.go +++ b/core/chains/evm/logpoller/orm_test.go @@ -293,36 +293,36 @@ func TestORM(t *testing.T) { require.Equal(t, 1, len(logs)) assert.Equal(t, []byte("hello"), logs[0].Data) - logs, err = o1.SelectLogsByBlockRangeFilter(1, 1, common.HexToAddress("0x1234"), topic) + logs, err = o1.SelectLogs(1, 1, common.HexToAddress("0x1234"), topic) require.NoError(t, err) assert.Equal(t, 0, len(logs)) - logs, err = o1.SelectLogsByBlockRangeFilter(10, 10, common.HexToAddress("0x1234"), topic) + logs, err = o1.SelectLogs(10, 10, common.HexToAddress("0x1234"), topic) require.NoError(t, err) require.Equal(t, 1, len(logs)) // With no blocks, should be an error - _, err = o1.SelectLatestLogEventSigWithConfs(topic, common.HexToAddress("0x1234"), 0) + _, err = o1.SelectLatestLogByEventSigWithConfs(topic, common.HexToAddress("0x1234"), 0) require.Error(t, err) assert.True(t, errors.Is(err, sql.ErrNoRows)) // With block 10, only 0 confs should work require.NoError(t, o1.InsertBlock(common.HexToHash("0x1234"), 10, time.Now())) - log, err := o1.SelectLatestLogEventSigWithConfs(topic, common.HexToAddress("0x1234"), 0) + log, err := o1.SelectLatestLogByEventSigWithConfs(topic, common.HexToAddress("0x1234"), 0) require.NoError(t, err) assert.Equal(t, int64(10), log.BlockNumber) - _, err = o1.SelectLatestLogEventSigWithConfs(topic, common.HexToAddress("0x1234"), 1) + _, err = o1.SelectLatestLogByEventSigWithConfs(topic, common.HexToAddress("0x1234"), 1) require.Error(t, err) assert.True(t, errors.Is(err, sql.ErrNoRows)) // With block 12, anything <=2 should work require.NoError(t, o1.DeleteBlocksAfter(10)) require.NoError(t, o1.InsertBlock(common.HexToHash("0x1234"), 11, time.Now())) require.NoError(t, o1.InsertBlock(common.HexToHash("0x1235"), 12, time.Now())) - _, err = o1.SelectLatestLogEventSigWithConfs(topic, common.HexToAddress("0x1234"), 0) + _, err = o1.SelectLatestLogByEventSigWithConfs(topic, common.HexToAddress("0x1234"), 0) require.NoError(t, err) - _, err = o1.SelectLatestLogEventSigWithConfs(topic, common.HexToAddress("0x1234"), 1) + _, err = o1.SelectLatestLogByEventSigWithConfs(topic, common.HexToAddress("0x1234"), 1) require.NoError(t, err) - _, err = o1.SelectLatestLogEventSigWithConfs(topic, common.HexToAddress("0x1234"), 2) + _, err = o1.SelectLatestLogByEventSigWithConfs(topic, common.HexToAddress("0x1234"), 2) require.NoError(t, err) - _, err = o1.SelectLatestLogEventSigWithConfs(topic, common.HexToAddress("0x1234"), 3) + _, err = o1.SelectLatestLogByEventSigWithConfs(topic, common.HexToAddress("0x1234"), 3) require.Error(t, err) assert.True(t, errors.Is(err, sql.ErrNoRows)) @@ -423,7 +423,7 @@ func TestORM(t *testing.T) { require.Zero(t, len(logs)) } -func insertLogsTopicValueRange(t *testing.T, chainID *big.Int, o *logpoller.ORM, addr common.Address, blockNumber int, eventSig common.Hash, start, stop int) { +func insertLogsTopicValueRange(t *testing.T, chainID *big.Int, o *logpoller.DbORM, addr common.Address, blockNumber int, eventSig common.Hash, start, stop int) { var lgs []logpoller.Log for i := start; i <= stop; i++ { lgs = append(lgs, logpoller.Log{ @@ -459,45 +459,45 @@ func TestORM_IndexedLogs(t *testing.T) { require.NoError(t, err) assert.Equal(t, 2, len(lgs)) - lgs, err = o1.SelectIndexedLogsByBlockRangeFilter(1, 1, addr, eventSig, 1, []common.Hash{logpoller.EvmWord(1)}) + lgs, err = o1.SelectIndexedLogsByBlockRange(1, 1, addr, eventSig, 1, []common.Hash{logpoller.EvmWord(1)}) require.NoError(t, err) assert.Equal(t, 1, len(lgs)) - lgs, err = o1.SelectIndexedLogsByBlockRangeFilter(1, 2, addr, eventSig, 1, []common.Hash{logpoller.EvmWord(2)}) + lgs, err = o1.SelectIndexedLogsByBlockRange(1, 2, addr, eventSig, 1, []common.Hash{logpoller.EvmWord(2)}) require.NoError(t, err) assert.Equal(t, 1, len(lgs)) - lgs, err = o1.SelectIndexedLogsByBlockRangeFilter(1, 2, addr, eventSig, 1, []common.Hash{logpoller.EvmWord(1)}) + lgs, err = o1.SelectIndexedLogsByBlockRange(1, 2, addr, eventSig, 1, []common.Hash{logpoller.EvmWord(1)}) require.NoError(t, err) assert.Equal(t, 1, len(lgs)) - _, err = o1.SelectIndexedLogsByBlockRangeFilter(1, 2, addr, eventSig, 0, []common.Hash{logpoller.EvmWord(1)}) + _, err = o1.SelectIndexedLogsByBlockRange(1, 2, addr, eventSig, 0, []common.Hash{logpoller.EvmWord(1)}) require.Error(t, err) assert.Contains(t, err.Error(), "invalid index for topic: 0") - _, err = o1.SelectIndexedLogsByBlockRangeFilter(1, 2, addr, eventSig, 4, []common.Hash{logpoller.EvmWord(1)}) + _, err = o1.SelectIndexedLogsByBlockRange(1, 2, addr, eventSig, 4, []common.Hash{logpoller.EvmWord(1)}) require.Error(t, err) assert.Contains(t, err.Error(), "invalid index for topic: 4") - lgs, err = o1.SelectIndexLogsTopicGreaterThan(addr, eventSig, 1, logpoller.EvmWord(2), 0) + lgs, err = o1.SelectIndexedLogsTopicGreaterThan(addr, eventSig, 1, logpoller.EvmWord(2), 0) require.NoError(t, err) assert.Equal(t, 2, len(lgs)) - lgs, err = o1.SelectIndexLogsTopicRange(addr, eventSig, 1, logpoller.EvmWord(3), logpoller.EvmWord(3), 0) + lgs, err = o1.SelectIndexedLogsTopicRange(addr, eventSig, 1, logpoller.EvmWord(3), logpoller.EvmWord(3), 0) require.NoError(t, err) assert.Equal(t, 1, len(lgs)) assert.Equal(t, logpoller.EvmWord(3).Bytes(), lgs[0].GetTopics()[1].Bytes()) - lgs, err = o1.SelectIndexLogsTopicRange(addr, eventSig, 1, logpoller.EvmWord(1), logpoller.EvmWord(3), 0) + lgs, err = o1.SelectIndexedLogsTopicRange(addr, eventSig, 1, logpoller.EvmWord(1), logpoller.EvmWord(3), 0) require.NoError(t, err) assert.Equal(t, 3, len(lgs)) // Check confirmations work as expected. require.NoError(t, o1.InsertBlock(common.HexToHash("0x2"), 2, time.Now())) - lgs, err = o1.SelectIndexLogsTopicRange(addr, eventSig, 1, logpoller.EvmWord(4), logpoller.EvmWord(4), 1) + lgs, err = o1.SelectIndexedLogsTopicRange(addr, eventSig, 1, logpoller.EvmWord(4), logpoller.EvmWord(4), 1) require.NoError(t, err) assert.Equal(t, 0, len(lgs)) require.NoError(t, o1.InsertBlock(common.HexToHash("0x3"), 3, time.Now())) - lgs, err = o1.SelectIndexLogsTopicRange(addr, eventSig, 1, logpoller.EvmWord(4), logpoller.EvmWord(4), 1) + lgs, err = o1.SelectIndexedLogsTopicRange(addr, eventSig, 1, logpoller.EvmWord(4), logpoller.EvmWord(4), 1) require.NoError(t, err) assert.Equal(t, 1, len(lgs)) } @@ -600,48 +600,48 @@ func TestORM_DataWords(t *testing.T) { }, })) // Outside range should fail. - lgs, err := o1.SelectDataWordRange(addr, eventSig, 0, logpoller.EvmWord(2), logpoller.EvmWord(2), 0) + lgs, err := o1.SelectLogsDataWordRange(addr, eventSig, 0, logpoller.EvmWord(2), logpoller.EvmWord(2), 0) require.NoError(t, err) assert.Equal(t, 0, len(lgs)) // Range including log should succeed - lgs, err = o1.SelectDataWordRange(addr, eventSig, 0, logpoller.EvmWord(1), logpoller.EvmWord(2), 0) + lgs, err = o1.SelectLogsDataWordRange(addr, eventSig, 0, logpoller.EvmWord(1), logpoller.EvmWord(2), 0) require.NoError(t, err) assert.Equal(t, 1, len(lgs)) // Range only covering log should succeed - lgs, err = o1.SelectDataWordRange(addr, eventSig, 0, logpoller.EvmWord(1), logpoller.EvmWord(1), 0) + lgs, err = o1.SelectLogsDataWordRange(addr, eventSig, 0, logpoller.EvmWord(1), logpoller.EvmWord(1), 0) require.NoError(t, err) assert.Equal(t, 1, len(lgs)) // Cannot query for unconfirmed second log. - lgs, err = o1.SelectDataWordRange(addr, eventSig, 1, logpoller.EvmWord(3), logpoller.EvmWord(3), 0) + lgs, err = o1.SelectLogsDataWordRange(addr, eventSig, 1, logpoller.EvmWord(3), logpoller.EvmWord(3), 0) require.NoError(t, err) assert.Equal(t, 0, len(lgs)) // Confirm it, then can query. require.NoError(t, o1.InsertBlock(common.HexToHash("0x2"), 2, time.Now())) - lgs, err = o1.SelectDataWordRange(addr, eventSig, 1, logpoller.EvmWord(3), logpoller.EvmWord(3), 0) + lgs, err = o1.SelectLogsDataWordRange(addr, eventSig, 1, logpoller.EvmWord(3), logpoller.EvmWord(3), 0) require.NoError(t, err) assert.Equal(t, 1, len(lgs)) assert.Equal(t, lgs[0].Data, append(logpoller.EvmWord(2).Bytes(), logpoller.EvmWord(3).Bytes()...)) // Check greater than 1 yields both logs. - lgs, err = o1.SelectDataWordGreaterThan(addr, eventSig, 0, logpoller.EvmWord(1), 0) + lgs, err = o1.SelectLogsDataWordGreaterThan(addr, eventSig, 0, logpoller.EvmWord(1), 0) require.NoError(t, err) assert.Equal(t, 2, len(lgs)) // Unknown hash should an error - lgs, err = o1.SelectUntilBlockHashDataWordGreaterThan(addr, eventSig, 0, logpoller.EvmWord(1), common.HexToHash("0x3")) + lgs, err = o1.SelectLogsUntilBlockHashDataWordGreaterThan(addr, eventSig, 0, logpoller.EvmWord(1), common.HexToHash("0x3")) require.Error(t, err) assert.Equal(t, 0, len(lgs)) // 1 block should include first log - lgs, err = o1.SelectUntilBlockHashDataWordGreaterThan(addr, eventSig, 0, logpoller.EvmWord(1), common.HexToHash("0x1")) + lgs, err = o1.SelectLogsUntilBlockHashDataWordGreaterThan(addr, eventSig, 0, logpoller.EvmWord(1), common.HexToHash("0x1")) require.NoError(t, err) assert.Equal(t, 1, len(lgs)) // 2 block should include both - lgs, err = o1.SelectUntilBlockHashDataWordGreaterThan(addr, eventSig, 0, logpoller.EvmWord(1), common.HexToHash("0x2")) + lgs, err = o1.SelectLogsUntilBlockHashDataWordGreaterThan(addr, eventSig, 0, logpoller.EvmWord(1), common.HexToHash("0x2")) require.NoError(t, err) assert.Equal(t, 2, len(lgs)) } @@ -651,7 +651,7 @@ func TestORM_SelectLogsWithSigsByBlockRangeFilter(t *testing.T) { o1 := th.ORM // Insert logs on different topics, should be able to read them - // back using SelectLogsWithSigsByBlockRangeFilter and specifying + // back using SelectLogsWithSigs and specifying // said topics. topic := common.HexToHash("0x1599") topic2 := common.HexToHash("0x1600") @@ -727,7 +727,7 @@ func TestORM_SelectLogsWithSigsByBlockRangeFilter(t *testing.T) { require.NoError(t, o1.InsertLogs(inputLogs)) startBlock, endBlock := int64(10), int64(15) - logs, err := o1.SelectLogsWithSigsByBlockRangeFilter(startBlock, endBlock, sourceAddr, []common.Hash{ + logs, err := o1.SelectLogsWithSigs(startBlock, endBlock, sourceAddr, []common.Hash{ topic, topic2, }) @@ -792,7 +792,7 @@ func TestLogPoller_Logs(t *testing.T) { assert.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000005", lgs[5].BlockHash.String()) // Filter by Address and topic - lgs, err = th.ORM.SelectLogsByBlockRangeFilter(1, 3, address1, event1) + lgs, err = th.ORM.SelectLogs(1, 3, address1, event1) require.NoError(t, err) require.Equal(t, 2, len(lgs)) assert.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000003", lgs[0].BlockHash.String()) @@ -802,7 +802,7 @@ func TestLogPoller_Logs(t *testing.T) { assert.Equal(t, address1, lgs[1].Address) // Filter by block - lgs, err = th.ORM.SelectLogsByBlockRangeFilter(2, 2, address2, event1) + lgs, err = th.ORM.SelectLogs(2, 2, address2, event1) require.NoError(t, err) require.Equal(t, 1, len(lgs)) assert.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000004", lgs[0].BlockHash.String()) @@ -832,7 +832,7 @@ func BenchmarkLogs(b *testing.B) { require.NoError(b, o.InsertLogs(lgs)) b.ResetTimer() for n := 0; n < b.N; n++ { - _, err := o.SelectDataWordRange(addr, EmitterABI.Events["Log1"].ID, 0, logpoller.EvmWord(8000), logpoller.EvmWord(8002), 0) + _, err := o.SelectLogsDataWordRange(addr, EmitterABI.Events["Log1"].ID, 0, logpoller.EvmWord(8000), logpoller.EvmWord(8002), 0) require.NoError(b, err) } } @@ -1165,7 +1165,7 @@ func TestSelectLatestBlockNumberEventSigsAddrsWithConfs(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - blockNumber, err := th.ORM.SelectLatestBlockNumberEventSigsAddrsWithConfs(tt.fromBlock, tt.events, tt.addrs, tt.confs) + blockNumber, err := th.ORM.SelectLatestBlockByEventSigsAddrsWithConfs(tt.fromBlock, tt.events, tt.addrs, tt.confs) require.NoError(t, err) assert.Equal(t, tt.expectedBlockNumber, blockNumber) }) From f935479c240ed5668ca7b1ef22da4f028b13d269 Mon Sep 17 00:00:00 2001 From: Adam Hamrick Date: Thu, 28 Sep 2023 18:44:57 -0400 Subject: [PATCH 6/8] [TT-609] Fix Bad Context Cancel (#10826) * Fix Bad Context Cancel * Update * Move cancel to the right spot --- integration-tests/testsetups/ocr.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-tests/testsetups/ocr.go b/integration-tests/testsetups/ocr.go index 5f12995cc0..259831189d 100644 --- a/integration-tests/testsetups/ocr.go +++ b/integration-tests/testsetups/ocr.go @@ -555,14 +555,13 @@ func (o *OCRSoakTest) setFilterQuery() { func (o *OCRSoakTest) observeOCREvents() error { eventLogs := make(chan types.Log) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() eventSub, err := o.chainClient.SubscribeFilterLogs(ctx, o.filterQuery, eventLogs) + cancel() if err != nil { return err } go func() { - defer cancel() for { select { case event := <-eventLogs: @@ -589,6 +588,7 @@ func (o *OCRSoakTest) observeOCREvents() error { Msg("Error while subscribed to OCR Logs. Resubscribing") ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second) eventSub, err = o.chainClient.SubscribeFilterLogs(ctx, o.filterQuery, eventLogs) + cancel() } } } From eff698d7552312180f397c77c9265e5d2105b4d2 Mon Sep 17 00:00:00 2001 From: Lukasz <120112546+lukaszcl@users.noreply.github.com> Date: Fri, 29 Sep 2023 13:50:51 +0200 Subject: [PATCH 7/8] Add Mercury v0.3 contract wrappers for E2E tests (#10823) * wip * wip 2 * Add go wrapper for werc20 mock * Add wrapper function * fix * Add new functions for werc20 wrapper * Fix werc20 wrapper location --- .../scripts/native_solc_compile_all_shared | 1 + ...rapper-dependency-versions-do-not-edit.txt | 1 + .../generated/werc20_mock/werc20_mock.go | 1052 +++++++++++++++++ ...rapper-dependency-versions-do-not-edit.txt | 1 + core/gethwrappers/shared/go_generate.go | 1 + .../contracts/contract_deployer.go | 100 ++ .../contracts/contract_loader.go | 74 +- .../contracts/contract_models.go | 26 +- .../contracts/ethereum_contracts.go | 180 ++- 9 files changed, 1421 insertions(+), 15 deletions(-) create mode 100644 core/gethwrappers/shared/generated/werc20_mock/werc20_mock.go diff --git a/contracts/scripts/native_solc_compile_all_shared b/contracts/scripts/native_solc_compile_all_shared index e7199f81fe..705cedce7a 100755 --- a/contracts/scripts/native_solc_compile_all_shared +++ b/contracts/scripts/native_solc_compile_all_shared @@ -27,4 +27,5 @@ compileContract () { compileContract shared/token/ERC677/BurnMintERC677.sol compileContract shared/token/ERC677/LinkToken.sol +compileContract shared/mocks/WERC20Mock.sol compileContract vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/ERC20.sol diff --git a/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 4bb84a770a..abc3b47db2 100644 --- a/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -7,3 +7,4 @@ llo_feeds_test: ../../../contracts/solc/v0.8.16/ExposedVerifier.abi ../../../con reward_manager: ../../../contracts/solc/v0.8.16/RewardManager.abi ../../../contracts/solc/v0.8.16/RewardManager.bin db73e9062b17a1d5aa14c06881fe2be49bd95b00b7f1a8943910c5e4ded5b221 verifier: ../../../contracts/solc/v0.8.16/Verifier.abi ../../../contracts/solc/v0.8.16/Verifier.bin df12786bbeccf3a8f3389479cf93c055b4efd5904b9f99a4835f81af43fe62bf verifier_proxy: ../../../contracts/solc/v0.8.16/VerifierProxy.abi ../../../contracts/solc/v0.8.16/VerifierProxy.bin 6393443d0a323f2dbe9687dc30fd77f8dfa918944b61c651759746ff2d76e4e5 +werc20_mock: ../../../contracts/solc/v0.8.19/WERC20Mock.abi ../../../contracts/solc/v0.8.19/WERC20Mock.bin ff2ca3928b2aa9c412c892cb8226c4d754c73eeb291bb7481c32c48791b2aa94 diff --git a/core/gethwrappers/shared/generated/werc20_mock/werc20_mock.go b/core/gethwrappers/shared/generated/werc20_mock/werc20_mock.go new file mode 100644 index 0000000000..3d8660c701 --- /dev/null +++ b/core/gethwrappers/shared/generated/werc20_mock/werc20_mock.go @@ -0,0 +1,1052 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package werc20_mock + +import ( + "errors" + "fmt" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated" +) + +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +var WERC20MockMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"dst\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"src\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Withdrawal\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"burn\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"subtractedValue\",\"type\":\"uint256\"}],\"name\":\"decreaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"deposit\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"addedValue\",\"type\":\"uint256\"}],\"name\":\"increaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]", + Bin: "0x60806040523480156200001157600080fd5b506040518060400160405280600a8152602001695745524332304d6f636b60b01b815250604051806040016040528060048152602001635745524360e01b815250816003908162000063919062000120565b50600462000072828262000120565b505050620001ec565b634e487b7160e01b600052604160045260246000fd5b600181811c90821680620000a657607f821691505b602082108103620000c757634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200011b57600081815260208120601f850160051c81016020861015620000f65750805b601f850160051c820191505b81811015620001175782815560010162000102565b5050505b505050565b81516001600160401b038111156200013c576200013c6200007b565b62000154816200014d845462000091565b84620000cd565b602080601f8311600181146200018c5760008415620001735750858301515b600019600386901b1c1916600185901b17855562000117565b600085815260208120601f198616915b82811015620001bd578886015182559484019460019091019084016200019c565b5085821015620001dc5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b610fc980620001fc6000396000f3fe6080604052600436106100ec5760003560e01c806340c10f191161008a578063a457c2d711610059578063a457c2d71461028e578063a9059cbb146102ae578063d0e30db0146102ce578063dd62ed3e146102d657600080fd5b806340c10f19146101f657806370a082311461021657806395d89b41146102595780639dc29fac1461026e57600080fd5b806323b872dd116100c657806323b872dd1461017a5780632e1a7d4d1461019a578063313ce567146101ba57806339509351146101d657600080fd5b806306fdde0314610100578063095ea7b31461012b57806318160ddd1461015b57600080fd5b366100fb576100f9610329565b005b600080fd5b34801561010c57600080fd5b5061011561036a565b6040516101229190610dc6565b60405180910390f35b34801561013757600080fd5b5061014b610146366004610e5b565b6103fc565b6040519015158152602001610122565b34801561016757600080fd5b506002545b604051908152602001610122565b34801561018657600080fd5b5061014b610195366004610e85565b610416565b3480156101a657600080fd5b506100f96101b5366004610ec1565b61043a565b3480156101c657600080fd5b5060405160128152602001610122565b3480156101e257600080fd5b5061014b6101f1366004610e5b565b6104c6565b34801561020257600080fd5b506100f9610211366004610e5b565b610512565b34801561022257600080fd5b5061016c610231366004610eda565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b34801561026557600080fd5b50610115610520565b34801561027a57600080fd5b506100f9610289366004610e5b565b61052f565b34801561029a57600080fd5b5061014b6102a9366004610e5b565b610539565b3480156102ba57600080fd5b5061014b6102c9366004610e5b565b61060f565b6100f9610329565b3480156102e257600080fd5b5061016c6102f1366004610efc565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260016020908152604080832093909416825291909152205490565b610333333461061d565b60405134815233907fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c9060200160405180910390a2565b60606003805461037990610f2f565b80601f01602080910402602001604051908101604052809291908181526020018280546103a590610f2f565b80156103f25780601f106103c7576101008083540402835291602001916103f2565b820191906000526020600020905b8154815290600101906020018083116103d557829003601f168201915b5050505050905090565b60003361040a818585610710565b60019150505b92915050565b6000336104248582856108c4565b61042f85858561099b565b506001949350505050565b3360009081526020819052604090205481111561045657600080fd5b6104603382610c0a565b604051339082156108fc029083906000818181858888f1935050505015801561048d573d6000803e3d6000fd5b5060405181815233907f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b659060200160405180910390a250565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716845290915281205490919061040a908290869061050d908790610f82565b610710565b61051c828261061d565b5050565b60606004805461037990610f2f565b61051c8282610c0a565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716845290915281205490919083811015610602576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760448201527f207a65726f00000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b61042f8286868403610710565b60003361040a81858561099b565b73ffffffffffffffffffffffffffffffffffffffff821661069a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016105f9565b80600260008282546106ac9190610f82565b909155505073ffffffffffffffffffffffffffffffffffffffff8216600081815260208181526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b73ffffffffffffffffffffffffffffffffffffffff83166107b2576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460448201527f726573730000000000000000000000000000000000000000000000000000000060648201526084016105f9565b73ffffffffffffffffffffffffffffffffffffffff8216610855576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f20616464726560448201527f737300000000000000000000000000000000000000000000000000000000000060648201526084016105f9565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b73ffffffffffffffffffffffffffffffffffffffff8381166000908152600160209081526040808320938616835292905220547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146109955781811015610988576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016105f9565b6109958484848403610710565b50505050565b73ffffffffffffffffffffffffffffffffffffffff8316610a3e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f20616460448201527f647265737300000000000000000000000000000000000000000000000000000060648201526084016105f9565b73ffffffffffffffffffffffffffffffffffffffff8216610ae1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201527f657373000000000000000000000000000000000000000000000000000000000060648201526084016105f9565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604090205481811015610b97576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e742065786365656473206260448201527f616c616e6365000000000000000000000000000000000000000000000000000060648201526084016105f9565b73ffffffffffffffffffffffffffffffffffffffff848116600081815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3610995565b73ffffffffffffffffffffffffffffffffffffffff8216610cad576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f2061646472657360448201527f730000000000000000000000000000000000000000000000000000000000000060648201526084016105f9565b73ffffffffffffffffffffffffffffffffffffffff821660009081526020819052604090205481811015610d63576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e60448201527f636500000000000000000000000000000000000000000000000000000000000060648201526084016105f9565b73ffffffffffffffffffffffffffffffffffffffff83166000818152602081815260408083208686039055600280548790039055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91016108b7565b600060208083528351808285015260005b81811015610df357858101830151858201604001528201610dd7565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610e5657600080fd5b919050565b60008060408385031215610e6e57600080fd5b610e7783610e32565b946020939093013593505050565b600080600060608486031215610e9a57600080fd5b610ea384610e32565b9250610eb160208501610e32565b9150604084013590509250925092565b600060208284031215610ed357600080fd5b5035919050565b600060208284031215610eec57600080fd5b610ef582610e32565b9392505050565b60008060408385031215610f0f57600080fd5b610f1883610e32565b9150610f2660208401610e32565b90509250929050565b600181811c90821680610f4357607f821691505b602082108103610f7c577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b80820180821115610410577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea164736f6c6343000813000a", +} + +var WERC20MockABI = WERC20MockMetaData.ABI + +var WERC20MockBin = WERC20MockMetaData.Bin + +func DeployWERC20Mock(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *WERC20Mock, error) { + parsed, err := WERC20MockMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(WERC20MockBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &WERC20Mock{WERC20MockCaller: WERC20MockCaller{contract: contract}, WERC20MockTransactor: WERC20MockTransactor{contract: contract}, WERC20MockFilterer: WERC20MockFilterer{contract: contract}}, nil +} + +type WERC20Mock struct { + address common.Address + abi abi.ABI + WERC20MockCaller + WERC20MockTransactor + WERC20MockFilterer +} + +type WERC20MockCaller struct { + contract *bind.BoundContract +} + +type WERC20MockTransactor struct { + contract *bind.BoundContract +} + +type WERC20MockFilterer struct { + contract *bind.BoundContract +} + +type WERC20MockSession struct { + Contract *WERC20Mock + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type WERC20MockCallerSession struct { + Contract *WERC20MockCaller + CallOpts bind.CallOpts +} + +type WERC20MockTransactorSession struct { + Contract *WERC20MockTransactor + TransactOpts bind.TransactOpts +} + +type WERC20MockRaw struct { + Contract *WERC20Mock +} + +type WERC20MockCallerRaw struct { + Contract *WERC20MockCaller +} + +type WERC20MockTransactorRaw struct { + Contract *WERC20MockTransactor +} + +func NewWERC20Mock(address common.Address, backend bind.ContractBackend) (*WERC20Mock, error) { + abi, err := abi.JSON(strings.NewReader(WERC20MockABI)) + if err != nil { + return nil, err + } + contract, err := bindWERC20Mock(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &WERC20Mock{address: address, abi: abi, WERC20MockCaller: WERC20MockCaller{contract: contract}, WERC20MockTransactor: WERC20MockTransactor{contract: contract}, WERC20MockFilterer: WERC20MockFilterer{contract: contract}}, nil +} + +func NewWERC20MockCaller(address common.Address, caller bind.ContractCaller) (*WERC20MockCaller, error) { + contract, err := bindWERC20Mock(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &WERC20MockCaller{contract: contract}, nil +} + +func NewWERC20MockTransactor(address common.Address, transactor bind.ContractTransactor) (*WERC20MockTransactor, error) { + contract, err := bindWERC20Mock(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &WERC20MockTransactor{contract: contract}, nil +} + +func NewWERC20MockFilterer(address common.Address, filterer bind.ContractFilterer) (*WERC20MockFilterer, error) { + contract, err := bindWERC20Mock(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &WERC20MockFilterer{contract: contract}, nil +} + +func bindWERC20Mock(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := WERC20MockMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +func (_WERC20Mock *WERC20MockRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _WERC20Mock.Contract.WERC20MockCaller.contract.Call(opts, result, method, params...) +} + +func (_WERC20Mock *WERC20MockRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _WERC20Mock.Contract.WERC20MockTransactor.contract.Transfer(opts) +} + +func (_WERC20Mock *WERC20MockRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _WERC20Mock.Contract.WERC20MockTransactor.contract.Transact(opts, method, params...) +} + +func (_WERC20Mock *WERC20MockCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _WERC20Mock.Contract.contract.Call(opts, result, method, params...) +} + +func (_WERC20Mock *WERC20MockTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _WERC20Mock.Contract.contract.Transfer(opts) +} + +func (_WERC20Mock *WERC20MockTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _WERC20Mock.Contract.contract.Transact(opts, method, params...) +} + +func (_WERC20Mock *WERC20MockCaller) Allowance(opts *bind.CallOpts, owner common.Address, spender common.Address) (*big.Int, error) { + var out []interface{} + err := _WERC20Mock.contract.Call(opts, &out, "allowance", owner, spender) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_WERC20Mock *WERC20MockSession) Allowance(owner common.Address, spender common.Address) (*big.Int, error) { + return _WERC20Mock.Contract.Allowance(&_WERC20Mock.CallOpts, owner, spender) +} + +func (_WERC20Mock *WERC20MockCallerSession) Allowance(owner common.Address, spender common.Address) (*big.Int, error) { + return _WERC20Mock.Contract.Allowance(&_WERC20Mock.CallOpts, owner, spender) +} + +func (_WERC20Mock *WERC20MockCaller) BalanceOf(opts *bind.CallOpts, account common.Address) (*big.Int, error) { + var out []interface{} + err := _WERC20Mock.contract.Call(opts, &out, "balanceOf", account) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_WERC20Mock *WERC20MockSession) BalanceOf(account common.Address) (*big.Int, error) { + return _WERC20Mock.Contract.BalanceOf(&_WERC20Mock.CallOpts, account) +} + +func (_WERC20Mock *WERC20MockCallerSession) BalanceOf(account common.Address) (*big.Int, error) { + return _WERC20Mock.Contract.BalanceOf(&_WERC20Mock.CallOpts, account) +} + +func (_WERC20Mock *WERC20MockCaller) Decimals(opts *bind.CallOpts) (uint8, error) { + var out []interface{} + err := _WERC20Mock.contract.Call(opts, &out, "decimals") + + if err != nil { + return *new(uint8), err + } + + out0 := *abi.ConvertType(out[0], new(uint8)).(*uint8) + + return out0, err + +} + +func (_WERC20Mock *WERC20MockSession) Decimals() (uint8, error) { + return _WERC20Mock.Contract.Decimals(&_WERC20Mock.CallOpts) +} + +func (_WERC20Mock *WERC20MockCallerSession) Decimals() (uint8, error) { + return _WERC20Mock.Contract.Decimals(&_WERC20Mock.CallOpts) +} + +func (_WERC20Mock *WERC20MockCaller) Name(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _WERC20Mock.contract.Call(opts, &out, "name") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +func (_WERC20Mock *WERC20MockSession) Name() (string, error) { + return _WERC20Mock.Contract.Name(&_WERC20Mock.CallOpts) +} + +func (_WERC20Mock *WERC20MockCallerSession) Name() (string, error) { + return _WERC20Mock.Contract.Name(&_WERC20Mock.CallOpts) +} + +func (_WERC20Mock *WERC20MockCaller) Symbol(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _WERC20Mock.contract.Call(opts, &out, "symbol") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +func (_WERC20Mock *WERC20MockSession) Symbol() (string, error) { + return _WERC20Mock.Contract.Symbol(&_WERC20Mock.CallOpts) +} + +func (_WERC20Mock *WERC20MockCallerSession) Symbol() (string, error) { + return _WERC20Mock.Contract.Symbol(&_WERC20Mock.CallOpts) +} + +func (_WERC20Mock *WERC20MockCaller) TotalSupply(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _WERC20Mock.contract.Call(opts, &out, "totalSupply") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_WERC20Mock *WERC20MockSession) TotalSupply() (*big.Int, error) { + return _WERC20Mock.Contract.TotalSupply(&_WERC20Mock.CallOpts) +} + +func (_WERC20Mock *WERC20MockCallerSession) TotalSupply() (*big.Int, error) { + return _WERC20Mock.Contract.TotalSupply(&_WERC20Mock.CallOpts) +} + +func (_WERC20Mock *WERC20MockTransactor) Approve(opts *bind.TransactOpts, spender common.Address, amount *big.Int) (*types.Transaction, error) { + return _WERC20Mock.contract.Transact(opts, "approve", spender, amount) +} + +func (_WERC20Mock *WERC20MockSession) Approve(spender common.Address, amount *big.Int) (*types.Transaction, error) { + return _WERC20Mock.Contract.Approve(&_WERC20Mock.TransactOpts, spender, amount) +} + +func (_WERC20Mock *WERC20MockTransactorSession) Approve(spender common.Address, amount *big.Int) (*types.Transaction, error) { + return _WERC20Mock.Contract.Approve(&_WERC20Mock.TransactOpts, spender, amount) +} + +func (_WERC20Mock *WERC20MockTransactor) Burn(opts *bind.TransactOpts, account common.Address, amount *big.Int) (*types.Transaction, error) { + return _WERC20Mock.contract.Transact(opts, "burn", account, amount) +} + +func (_WERC20Mock *WERC20MockSession) Burn(account common.Address, amount *big.Int) (*types.Transaction, error) { + return _WERC20Mock.Contract.Burn(&_WERC20Mock.TransactOpts, account, amount) +} + +func (_WERC20Mock *WERC20MockTransactorSession) Burn(account common.Address, amount *big.Int) (*types.Transaction, error) { + return _WERC20Mock.Contract.Burn(&_WERC20Mock.TransactOpts, account, amount) +} + +func (_WERC20Mock *WERC20MockTransactor) DecreaseAllowance(opts *bind.TransactOpts, spender common.Address, subtractedValue *big.Int) (*types.Transaction, error) { + return _WERC20Mock.contract.Transact(opts, "decreaseAllowance", spender, subtractedValue) +} + +func (_WERC20Mock *WERC20MockSession) DecreaseAllowance(spender common.Address, subtractedValue *big.Int) (*types.Transaction, error) { + return _WERC20Mock.Contract.DecreaseAllowance(&_WERC20Mock.TransactOpts, spender, subtractedValue) +} + +func (_WERC20Mock *WERC20MockTransactorSession) DecreaseAllowance(spender common.Address, subtractedValue *big.Int) (*types.Transaction, error) { + return _WERC20Mock.Contract.DecreaseAllowance(&_WERC20Mock.TransactOpts, spender, subtractedValue) +} + +func (_WERC20Mock *WERC20MockTransactor) Deposit(opts *bind.TransactOpts) (*types.Transaction, error) { + return _WERC20Mock.contract.Transact(opts, "deposit") +} + +func (_WERC20Mock *WERC20MockSession) Deposit() (*types.Transaction, error) { + return _WERC20Mock.Contract.Deposit(&_WERC20Mock.TransactOpts) +} + +func (_WERC20Mock *WERC20MockTransactorSession) Deposit() (*types.Transaction, error) { + return _WERC20Mock.Contract.Deposit(&_WERC20Mock.TransactOpts) +} + +func (_WERC20Mock *WERC20MockTransactor) IncreaseAllowance(opts *bind.TransactOpts, spender common.Address, addedValue *big.Int) (*types.Transaction, error) { + return _WERC20Mock.contract.Transact(opts, "increaseAllowance", spender, addedValue) +} + +func (_WERC20Mock *WERC20MockSession) IncreaseAllowance(spender common.Address, addedValue *big.Int) (*types.Transaction, error) { + return _WERC20Mock.Contract.IncreaseAllowance(&_WERC20Mock.TransactOpts, spender, addedValue) +} + +func (_WERC20Mock *WERC20MockTransactorSession) IncreaseAllowance(spender common.Address, addedValue *big.Int) (*types.Transaction, error) { + return _WERC20Mock.Contract.IncreaseAllowance(&_WERC20Mock.TransactOpts, spender, addedValue) +} + +func (_WERC20Mock *WERC20MockTransactor) Mint(opts *bind.TransactOpts, account common.Address, amount *big.Int) (*types.Transaction, error) { + return _WERC20Mock.contract.Transact(opts, "mint", account, amount) +} + +func (_WERC20Mock *WERC20MockSession) Mint(account common.Address, amount *big.Int) (*types.Transaction, error) { + return _WERC20Mock.Contract.Mint(&_WERC20Mock.TransactOpts, account, amount) +} + +func (_WERC20Mock *WERC20MockTransactorSession) Mint(account common.Address, amount *big.Int) (*types.Transaction, error) { + return _WERC20Mock.Contract.Mint(&_WERC20Mock.TransactOpts, account, amount) +} + +func (_WERC20Mock *WERC20MockTransactor) Transfer(opts *bind.TransactOpts, to common.Address, amount *big.Int) (*types.Transaction, error) { + return _WERC20Mock.contract.Transact(opts, "transfer", to, amount) +} + +func (_WERC20Mock *WERC20MockSession) Transfer(to common.Address, amount *big.Int) (*types.Transaction, error) { + return _WERC20Mock.Contract.Transfer(&_WERC20Mock.TransactOpts, to, amount) +} + +func (_WERC20Mock *WERC20MockTransactorSession) Transfer(to common.Address, amount *big.Int) (*types.Transaction, error) { + return _WERC20Mock.Contract.Transfer(&_WERC20Mock.TransactOpts, to, amount) +} + +func (_WERC20Mock *WERC20MockTransactor) TransferFrom(opts *bind.TransactOpts, from common.Address, to common.Address, amount *big.Int) (*types.Transaction, error) { + return _WERC20Mock.contract.Transact(opts, "transferFrom", from, to, amount) +} + +func (_WERC20Mock *WERC20MockSession) TransferFrom(from common.Address, to common.Address, amount *big.Int) (*types.Transaction, error) { + return _WERC20Mock.Contract.TransferFrom(&_WERC20Mock.TransactOpts, from, to, amount) +} + +func (_WERC20Mock *WERC20MockTransactorSession) TransferFrom(from common.Address, to common.Address, amount *big.Int) (*types.Transaction, error) { + return _WERC20Mock.Contract.TransferFrom(&_WERC20Mock.TransactOpts, from, to, amount) +} + +func (_WERC20Mock *WERC20MockTransactor) Withdraw(opts *bind.TransactOpts, wad *big.Int) (*types.Transaction, error) { + return _WERC20Mock.contract.Transact(opts, "withdraw", wad) +} + +func (_WERC20Mock *WERC20MockSession) Withdraw(wad *big.Int) (*types.Transaction, error) { + return _WERC20Mock.Contract.Withdraw(&_WERC20Mock.TransactOpts, wad) +} + +func (_WERC20Mock *WERC20MockTransactorSession) Withdraw(wad *big.Int) (*types.Transaction, error) { + return _WERC20Mock.Contract.Withdraw(&_WERC20Mock.TransactOpts, wad) +} + +func (_WERC20Mock *WERC20MockTransactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) { + return _WERC20Mock.contract.RawTransact(opts, nil) +} + +func (_WERC20Mock *WERC20MockSession) Receive() (*types.Transaction, error) { + return _WERC20Mock.Contract.Receive(&_WERC20Mock.TransactOpts) +} + +func (_WERC20Mock *WERC20MockTransactorSession) Receive() (*types.Transaction, error) { + return _WERC20Mock.Contract.Receive(&_WERC20Mock.TransactOpts) +} + +type WERC20MockApprovalIterator struct { + Event *WERC20MockApproval + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *WERC20MockApprovalIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(WERC20MockApproval) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(WERC20MockApproval) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *WERC20MockApprovalIterator) Error() error { + return it.fail +} + +func (it *WERC20MockApprovalIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type WERC20MockApproval struct { + Owner common.Address + Spender common.Address + Value *big.Int + Raw types.Log +} + +func (_WERC20Mock *WERC20MockFilterer) FilterApproval(opts *bind.FilterOpts, owner []common.Address, spender []common.Address) (*WERC20MockApprovalIterator, error) { + + var ownerRule []interface{} + for _, ownerItem := range owner { + ownerRule = append(ownerRule, ownerItem) + } + var spenderRule []interface{} + for _, spenderItem := range spender { + spenderRule = append(spenderRule, spenderItem) + } + + logs, sub, err := _WERC20Mock.contract.FilterLogs(opts, "Approval", ownerRule, spenderRule) + if err != nil { + return nil, err + } + return &WERC20MockApprovalIterator{contract: _WERC20Mock.contract, event: "Approval", logs: logs, sub: sub}, nil +} + +func (_WERC20Mock *WERC20MockFilterer) WatchApproval(opts *bind.WatchOpts, sink chan<- *WERC20MockApproval, owner []common.Address, spender []common.Address) (event.Subscription, error) { + + var ownerRule []interface{} + for _, ownerItem := range owner { + ownerRule = append(ownerRule, ownerItem) + } + var spenderRule []interface{} + for _, spenderItem := range spender { + spenderRule = append(spenderRule, spenderItem) + } + + logs, sub, err := _WERC20Mock.contract.WatchLogs(opts, "Approval", ownerRule, spenderRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(WERC20MockApproval) + if err := _WERC20Mock.contract.UnpackLog(event, "Approval", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_WERC20Mock *WERC20MockFilterer) ParseApproval(log types.Log) (*WERC20MockApproval, error) { + event := new(WERC20MockApproval) + if err := _WERC20Mock.contract.UnpackLog(event, "Approval", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type WERC20MockDepositIterator struct { + Event *WERC20MockDeposit + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *WERC20MockDepositIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(WERC20MockDeposit) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(WERC20MockDeposit) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *WERC20MockDepositIterator) Error() error { + return it.fail +} + +func (it *WERC20MockDepositIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type WERC20MockDeposit struct { + Dst common.Address + Wad *big.Int + Raw types.Log +} + +func (_WERC20Mock *WERC20MockFilterer) FilterDeposit(opts *bind.FilterOpts, dst []common.Address) (*WERC20MockDepositIterator, error) { + + var dstRule []interface{} + for _, dstItem := range dst { + dstRule = append(dstRule, dstItem) + } + + logs, sub, err := _WERC20Mock.contract.FilterLogs(opts, "Deposit", dstRule) + if err != nil { + return nil, err + } + return &WERC20MockDepositIterator{contract: _WERC20Mock.contract, event: "Deposit", logs: logs, sub: sub}, nil +} + +func (_WERC20Mock *WERC20MockFilterer) WatchDeposit(opts *bind.WatchOpts, sink chan<- *WERC20MockDeposit, dst []common.Address) (event.Subscription, error) { + + var dstRule []interface{} + for _, dstItem := range dst { + dstRule = append(dstRule, dstItem) + } + + logs, sub, err := _WERC20Mock.contract.WatchLogs(opts, "Deposit", dstRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(WERC20MockDeposit) + if err := _WERC20Mock.contract.UnpackLog(event, "Deposit", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_WERC20Mock *WERC20MockFilterer) ParseDeposit(log types.Log) (*WERC20MockDeposit, error) { + event := new(WERC20MockDeposit) + if err := _WERC20Mock.contract.UnpackLog(event, "Deposit", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type WERC20MockTransferIterator struct { + Event *WERC20MockTransfer + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *WERC20MockTransferIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(WERC20MockTransfer) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(WERC20MockTransfer) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *WERC20MockTransferIterator) Error() error { + return it.fail +} + +func (it *WERC20MockTransferIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type WERC20MockTransfer struct { + From common.Address + To common.Address + Value *big.Int + Raw types.Log +} + +func (_WERC20Mock *WERC20MockFilterer) FilterTransfer(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*WERC20MockTransferIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _WERC20Mock.contract.FilterLogs(opts, "Transfer", fromRule, toRule) + if err != nil { + return nil, err + } + return &WERC20MockTransferIterator{contract: _WERC20Mock.contract, event: "Transfer", logs: logs, sub: sub}, nil +} + +func (_WERC20Mock *WERC20MockFilterer) WatchTransfer(opts *bind.WatchOpts, sink chan<- *WERC20MockTransfer, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _WERC20Mock.contract.WatchLogs(opts, "Transfer", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(WERC20MockTransfer) + if err := _WERC20Mock.contract.UnpackLog(event, "Transfer", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_WERC20Mock *WERC20MockFilterer) ParseTransfer(log types.Log) (*WERC20MockTransfer, error) { + event := new(WERC20MockTransfer) + if err := _WERC20Mock.contract.UnpackLog(event, "Transfer", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type WERC20MockWithdrawalIterator struct { + Event *WERC20MockWithdrawal + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *WERC20MockWithdrawalIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(WERC20MockWithdrawal) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(WERC20MockWithdrawal) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *WERC20MockWithdrawalIterator) Error() error { + return it.fail +} + +func (it *WERC20MockWithdrawalIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type WERC20MockWithdrawal struct { + Src common.Address + Wad *big.Int + Raw types.Log +} + +func (_WERC20Mock *WERC20MockFilterer) FilterWithdrawal(opts *bind.FilterOpts, src []common.Address) (*WERC20MockWithdrawalIterator, error) { + + var srcRule []interface{} + for _, srcItem := range src { + srcRule = append(srcRule, srcItem) + } + + logs, sub, err := _WERC20Mock.contract.FilterLogs(opts, "Withdrawal", srcRule) + if err != nil { + return nil, err + } + return &WERC20MockWithdrawalIterator{contract: _WERC20Mock.contract, event: "Withdrawal", logs: logs, sub: sub}, nil +} + +func (_WERC20Mock *WERC20MockFilterer) WatchWithdrawal(opts *bind.WatchOpts, sink chan<- *WERC20MockWithdrawal, src []common.Address) (event.Subscription, error) { + + var srcRule []interface{} + for _, srcItem := range src { + srcRule = append(srcRule, srcItem) + } + + logs, sub, err := _WERC20Mock.contract.WatchLogs(opts, "Withdrawal", srcRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(WERC20MockWithdrawal) + if err := _WERC20Mock.contract.UnpackLog(event, "Withdrawal", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_WERC20Mock *WERC20MockFilterer) ParseWithdrawal(log types.Log) (*WERC20MockWithdrawal, error) { + event := new(WERC20MockWithdrawal) + if err := _WERC20Mock.contract.UnpackLog(event, "Withdrawal", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +func (_WERC20Mock *WERC20Mock) ParseLog(log types.Log) (generated.AbigenLog, error) { + switch log.Topics[0] { + case _WERC20Mock.abi.Events["Approval"].ID: + return _WERC20Mock.ParseApproval(log) + case _WERC20Mock.abi.Events["Deposit"].ID: + return _WERC20Mock.ParseDeposit(log) + case _WERC20Mock.abi.Events["Transfer"].ID: + return _WERC20Mock.ParseTransfer(log) + case _WERC20Mock.abi.Events["Withdrawal"].ID: + return _WERC20Mock.ParseWithdrawal(log) + + default: + return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) + } +} + +func (WERC20MockApproval) Topic() common.Hash { + return common.HexToHash("0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925") +} + +func (WERC20MockDeposit) Topic() common.Hash { + return common.HexToHash("0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c") +} + +func (WERC20MockTransfer) Topic() common.Hash { + return common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") +} + +func (WERC20MockWithdrawal) Topic() common.Hash { + return common.HexToHash("0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65") +} + +func (_WERC20Mock *WERC20Mock) Address() common.Address { + return _WERC20Mock.address +} + +type WERC20MockInterface interface { + Allowance(opts *bind.CallOpts, owner common.Address, spender common.Address) (*big.Int, error) + + BalanceOf(opts *bind.CallOpts, account common.Address) (*big.Int, error) + + Decimals(opts *bind.CallOpts) (uint8, error) + + Name(opts *bind.CallOpts) (string, error) + + Symbol(opts *bind.CallOpts) (string, error) + + TotalSupply(opts *bind.CallOpts) (*big.Int, error) + + Approve(opts *bind.TransactOpts, spender common.Address, amount *big.Int) (*types.Transaction, error) + + Burn(opts *bind.TransactOpts, account common.Address, amount *big.Int) (*types.Transaction, error) + + DecreaseAllowance(opts *bind.TransactOpts, spender common.Address, subtractedValue *big.Int) (*types.Transaction, error) + + Deposit(opts *bind.TransactOpts) (*types.Transaction, error) + + IncreaseAllowance(opts *bind.TransactOpts, spender common.Address, addedValue *big.Int) (*types.Transaction, error) + + Mint(opts *bind.TransactOpts, account common.Address, amount *big.Int) (*types.Transaction, error) + + Transfer(opts *bind.TransactOpts, to common.Address, amount *big.Int) (*types.Transaction, error) + + TransferFrom(opts *bind.TransactOpts, from common.Address, to common.Address, amount *big.Int) (*types.Transaction, error) + + Withdraw(opts *bind.TransactOpts, wad *big.Int) (*types.Transaction, error) + + Receive(opts *bind.TransactOpts) (*types.Transaction, error) + + FilterApproval(opts *bind.FilterOpts, owner []common.Address, spender []common.Address) (*WERC20MockApprovalIterator, error) + + WatchApproval(opts *bind.WatchOpts, sink chan<- *WERC20MockApproval, owner []common.Address, spender []common.Address) (event.Subscription, error) + + ParseApproval(log types.Log) (*WERC20MockApproval, error) + + FilterDeposit(opts *bind.FilterOpts, dst []common.Address) (*WERC20MockDepositIterator, error) + + WatchDeposit(opts *bind.WatchOpts, sink chan<- *WERC20MockDeposit, dst []common.Address) (event.Subscription, error) + + ParseDeposit(log types.Log) (*WERC20MockDeposit, error) + + FilterTransfer(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*WERC20MockTransferIterator, error) + + WatchTransfer(opts *bind.WatchOpts, sink chan<- *WERC20MockTransfer, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseTransfer(log types.Log) (*WERC20MockTransfer, error) + + FilterWithdrawal(opts *bind.FilterOpts, src []common.Address) (*WERC20MockWithdrawalIterator, error) + + WatchWithdrawal(opts *bind.WatchOpts, sink chan<- *WERC20MockWithdrawal, src []common.Address) (event.Subscription, error) + + ParseWithdrawal(log types.Log) (*WERC20MockWithdrawal, error) + + ParseLog(log types.Log) (generated.AbigenLog, error) + + Address() common.Address +} diff --git a/core/gethwrappers/shared/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/shared/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 3094b1a94f..7ac7e77a4b 100644 --- a/core/gethwrappers/shared/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/shared/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -2,3 +2,4 @@ GETH_VERSION: 1.12.0 burn_mint_erc677: ../../../contracts/solc/v0.8.19/BurnMintERC677.abi ../../../contracts/solc/v0.8.19/BurnMintERC677.bin 405c9016171e614b17e10588653ef8d33dcea21dd569c3fddc596a46fcff68a3 erc20: ../../../contracts/solc/v0.8.19/ERC20.abi ../../../contracts/solc/v0.8.19/ERC20.bin 5b1a93d9b24f250e49a730c96335a8113c3f7010365cba578f313b483001d4fc link_token: ../../../contracts/solc/v0.8.19/LinkToken.abi ../../../contracts/solc/v0.8.19/LinkToken.bin c0ef9b507103aae541ebc31d87d051c2764ba9d843076b30ec505d37cdfffaba +werc20_mock: ../../../contracts/solc/v0.8.19/WERC20Mock.abi ../../../contracts/solc/v0.8.19/WERC20Mock.bin ff2ca3928b2aa9c412c892cb8226c4d754c73eeb291bb7481c32c48791b2aa94 diff --git a/core/gethwrappers/shared/go_generate.go b/core/gethwrappers/shared/go_generate.go index 169a87b9b2..85a01670c9 100644 --- a/core/gethwrappers/shared/go_generate.go +++ b/core/gethwrappers/shared/go_generate.go @@ -5,3 +5,4 @@ package gethwrappers //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/BurnMintERC677.abi ../../../contracts/solc/v0.8.19/BurnMintERC677.bin BurnMintERC677 burn_mint_erc677 //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/LinkToken.abi ../../../contracts/solc/v0.8.19/LinkToken.bin LinkToken link_token //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/ERC20.abi ../../../contracts/solc/v0.8.19/ERC20.bin ERC20 erc20 +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/WERC20Mock.abi ../../../contracts/solc/v0.8.19/WERC20Mock.bin WERC20Mock werc20_mock diff --git a/integration-tests/contracts/contract_deployer.go b/integration-tests/contracts/contract_deployer.go index dcf81edede..e92b3870fa 100644 --- a/integration-tests/contracts/contract_deployer.go +++ b/integration-tests/contracts/contract_deployer.go @@ -51,6 +51,11 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/oracle_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/test_api_consumer_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/upkeep_transcoder" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/fee_manager" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/reward_manager" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/verifier" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/verifier_proxy" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/werc20_mock" ) // ContractDeployer is an interface for abstracting the contract deployment methods across network implementations @@ -60,6 +65,7 @@ type ContractDeployer interface { DeployFlags(rac string) (Flags, error) DeployFluxAggregatorContract(linkAddr string, fluxOptions FluxAggregatorOptions) (FluxAggregator, error) DeployLinkTokenContract() (LinkToken, error) + DeployWERC20Mock() (WERC20Mock, error) LoadLinkToken(address common.Address) (LinkToken, error) DeployOffChainAggregator(linkAddr string, offchainOptions OffchainOptions) (OffchainAggregator, error) LoadOffChainAggregator(address *common.Address) (OffchainAggregator, error) @@ -117,6 +123,10 @@ type ContractDeployer interface { DeployKeeperRegistry11Mock() (KeeperRegistry11Mock, error) DeployKeeperRegistrar12Mock() (KeeperRegistrar12Mock, error) DeployKeeperGasWrapperMock() (KeeperGasWrapperMock, error) + DeployMercuryVerifierContract(verifierProxyAddr common.Address) (MercuryVerifier, error) + DeployMercuryVerifierProxyContract(accessControllerAddr common.Address) (MercuryVerifierProxy, error) + DeployMercuryFeeManager(linkAddress common.Address, nativeAddress common.Address, proxyAddress common.Address, rewardManagerAddress common.Address) (MercuryFeeManager, error) + DeployMercuryRewardManager(linkAddress common.Address) (MercuryRewardManager, error) } // NewContractDeployer returns an instance of a contract deployer based on the client type @@ -1436,3 +1446,93 @@ func (e *EthereumContractDeployer) DeployOffchainAggregatorV2( l: e.l, }, err } + +func (e *EthereumContractDeployer) DeployMercuryVerifierContract(verifierProxyAddr common.Address) (MercuryVerifier, error) { + address, _, instance, err := e.client.DeployContract("Mercury Verifier", func( + auth *bind.TransactOpts, + backend bind.ContractBackend, + ) (common.Address, *types.Transaction, interface{}, error) { + return verifier.DeployVerifier(auth, backend, verifierProxyAddr) + }) + if err != nil { + return nil, err + } + return &EthereumMercuryVerifier{ + client: e.client, + instance: instance.(*verifier.Verifier), + address: *address, + l: e.l, + }, err +} + +func (e *EthereumContractDeployer) DeployMercuryVerifierProxyContract(accessControllerAddr common.Address) (MercuryVerifierProxy, error) { + address, _, instance, err := e.client.DeployContract("Mercury Verifier Proxy", func( + auth *bind.TransactOpts, + backend bind.ContractBackend, + ) (common.Address, *types.Transaction, interface{}, error) { + return verifier_proxy.DeployVerifierProxy(auth, backend, accessControllerAddr) + }) + if err != nil { + return nil, err + } + return &EthereumMercuryVerifierProxy{ + client: e.client, + instance: instance.(*verifier_proxy.VerifierProxy), + address: *address, + l: e.l, + }, err +} + +func (e *EthereumContractDeployer) DeployMercuryFeeManager(linkAddress common.Address, nativeAddress common.Address, proxyAddress common.Address, rewardManagerAddress common.Address) (MercuryFeeManager, error) { + address, _, instance, err := e.client.DeployContract("Mercury Fee Manager", func( + auth *bind.TransactOpts, + backend bind.ContractBackend, + ) (common.Address, *types.Transaction, interface{}, error) { + return fee_manager.DeployFeeManager(auth, backend, linkAddress, nativeAddress, proxyAddress, rewardManagerAddress) + }) + if err != nil { + return nil, err + } + return &EthereumMercuryFeeManager{ + client: e.client, + instance: instance.(*fee_manager.FeeManager), + address: *address, + l: e.l, + }, err +} + +func (e *EthereumContractDeployer) DeployMercuryRewardManager(linkAddress common.Address) (MercuryRewardManager, error) { + address, _, instance, err := e.client.DeployContract("Mercury Reward Manager", func( + auth *bind.TransactOpts, + backend bind.ContractBackend, + ) (common.Address, *types.Transaction, interface{}, error) { + return reward_manager.DeployRewardManager(auth, backend, linkAddress) + }) + if err != nil { + return nil, err + } + return &EthereumMercuryRewardManager{ + client: e.client, + instance: instance.(*reward_manager.RewardManager), + address: *address, + l: e.l, + }, err +} + +func (e *EthereumContractDeployer) DeployWERC20Mock() (WERC20Mock, error) { + address, _, instance, err := e.client.DeployContract("WERC20 Mock", func( + auth *bind.TransactOpts, + backend bind.ContractBackend, + ) (common.Address, *types.Transaction, interface{}, error) { + return werc20_mock.DeployWERC20Mock(auth, backend) + }) + if err != nil { + return nil, err + } + return &EthereumWERC20Mock{ + client: e.client, + instance: instance.(*werc20_mock.WERC20Mock), + address: *address, + l: e.l, + }, err +} diff --git a/integration-tests/contracts/contract_loader.go b/integration-tests/contracts/contract_loader.go index a790747e38..fc0272b107 100644 --- a/integration-tests/contracts/contract_loader.go +++ b/integration-tests/contracts/contract_loader.go @@ -14,8 +14,11 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/authorized_forwarder" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/link_token_interface" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/operator_wrapper" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/fee_manager" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/reward_manager" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/verifier" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/verifier_proxy" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/werc20_mock" ) // ContractLoader is an interface for abstracting the contract loading methods across network implementations @@ -30,8 +33,12 @@ type ContractLoader interface { LoadFunctionsLoadTestClient(addr string) (FunctionsLoadTestClient, error) // Mercury - LoadMercuryVerifier(addr string) (MercuryVerifier, error) - LoadMercuryVerifierProxy(addr string) (MercuryVerifierProxy, error) + LoadMercuryVerifier(addr common.Address) (MercuryVerifier, error) + LoadMercuryVerifierProxy(addr common.Address) (MercuryVerifierProxy, error) + LoadMercuryFeeManager(addr common.Address) (MercuryFeeManager, error) + LoadMercuryRewardManager(addr common.Address) (MercuryRewardManager, error) + + LoadWERC20Mock(addr common.Address) (WERC20Mock, error) } // NewContractLoader returns an instance of a contract Loader based on the client type @@ -204,8 +211,8 @@ func (e *EthereumContractLoader) LoadAuthorizedForwarder(address common.Address) } // LoadMercuryVerifier returns Verifier contract deployed on given address -func (e *EthereumContractLoader) LoadMercuryVerifier(addr string) (MercuryVerifier, error) { - instance, err := e.client.LoadContract("Mercury Verifier", common.HexToAddress(addr), func( +func (e *EthereumContractLoader) LoadMercuryVerifier(addr common.Address) (MercuryVerifier, error) { + instance, err := e.client.LoadContract("Mercury Verifier", addr, func( address common.Address, backend bind.ContractBackend, ) (interface{}, error) { @@ -217,13 +224,13 @@ func (e *EthereumContractLoader) LoadMercuryVerifier(addr string) (MercuryVerifi return &EthereumMercuryVerifier{ client: e.client, instance: instance.(*verifier.Verifier), - address: common.HexToAddress(addr), + address: addr, }, err } // LoadMercuryVerifierProxy returns VerifierProxy contract deployed on given address -func (e *EthereumContractLoader) LoadMercuryVerifierProxy(addr string) (MercuryVerifierProxy, error) { - instance, err := e.client.LoadContract("Mercury Verifier Proxy", common.HexToAddress(addr), func( +func (e *EthereumContractLoader) LoadMercuryVerifierProxy(addr common.Address) (MercuryVerifierProxy, error) { + instance, err := e.client.LoadContract("Mercury Verifier Proxy", addr, func( address common.Address, backend bind.ContractBackend, ) (interface{}, error) { @@ -235,6 +242,57 @@ func (e *EthereumContractLoader) LoadMercuryVerifierProxy(addr string) (MercuryV return &EthereumMercuryVerifierProxy{ client: e.client, instance: instance.(*verifier_proxy.VerifierProxy), - address: common.HexToAddress(addr), + address: addr, + }, err +} + +func (e *EthereumContractLoader) LoadMercuryFeeManager(addr common.Address) (MercuryFeeManager, error) { + instance, err := e.client.LoadContract("Mercury Fee Manager", addr, func( + address common.Address, + backend bind.ContractBackend, + ) (interface{}, error) { + return fee_manager.NewFeeManager(address, backend) + }) + if err != nil { + return nil, err + } + return &EthereumMercuryFeeManager{ + client: e.client, + instance: instance.(*fee_manager.FeeManager), + address: addr, + }, err +} + +func (e *EthereumContractLoader) LoadMercuryRewardManager(addr common.Address) (MercuryRewardManager, error) { + instance, err := e.client.LoadContract("Mercury Reward Manager", addr, func( + address common.Address, + backend bind.ContractBackend, + ) (interface{}, error) { + return reward_manager.NewRewardManager(address, backend) + }) + if err != nil { + return nil, err + } + return &EthereumMercuryRewardManager{ + client: e.client, + instance: instance.(*reward_manager.RewardManager), + address: addr, + }, err +} + +func (e *EthereumContractLoader) LoadWERC20Mock(addr common.Address) (WERC20Mock, error) { + instance, err := e.client.LoadContract("WERC20 Mock", addr, func( + address common.Address, + backend bind.ContractBackend, + ) (interface{}, error) { + return werc20_mock.NewWERC20Mock(address, backend) + }) + if err != nil { + return nil, err + } + return &EthereumWERC20Mock{ + client: e.client, + instance: instance.(*werc20_mock.WERC20Mock), + address: addr, }, err } diff --git a/integration-tests/contracts/contract_models.go b/integration-tests/contracts/contract_models.go index 9f1130dce9..51fce7cb12 100644 --- a/integration-tests/contracts/contract_models.go +++ b/integration-tests/contracts/contract_models.go @@ -17,6 +17,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/flux_aggregator_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/functions_billing_registry_events_mock" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/operator_factory" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/verifier" ) type FluxAggregatorOptions struct { @@ -369,12 +370,33 @@ type FunctionsLoadTestClient interface { } type MercuryVerifier interface { - Address() string + Address() common.Address Verify(signedReport []byte, sender common.Address) error + SetConfig(feedId [32]byte, signers []common.Address, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte, recipientAddressesAndWeights []verifier.CommonAddressAndWeight) (*types.Transaction, error) + LatestConfigDetails(ctx context.Context, feedId [32]byte) (verifier.LatestConfigDetails, error) } type MercuryVerifierProxy interface { - Address() string + Address() common.Address + InitializeVerifier(verifierAddress common.Address) (*types.Transaction, error) Verify(signedReport []byte, parameterPayload []byte, value *big.Int) (*types.Transaction, error) VerifyBulk(signedReports [][]byte, parameterPayload []byte, value *big.Int) (*types.Transaction, error) + SetFeeManager(feeManager common.Address) (*types.Transaction, error) +} + +type MercuryFeeManager interface { + Address() common.Address +} + +type MercuryRewardManager interface { + Address() common.Address + SetFeeManager(feeManager common.Address) (*types.Transaction, error) +} + +type WERC20Mock interface { + Address() common.Address + BalanceOf(ctx context.Context, addr string) (*big.Int, error) + Approve(to string, amount *big.Int) error + Transfer(to string, amount *big.Int) error + Mint(account common.Address, amount *big.Int) (*types.Transaction, error) } diff --git a/integration-tests/contracts/ethereum_contracts.go b/integration-tests/contracts/ethereum_contracts.go index d3db6913c5..fdeccfedad 100644 --- a/integration-tests/contracts/ethereum_contracts.go +++ b/integration-tests/contracts/ethereum_contracts.go @@ -47,8 +47,11 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/operator_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/oracle_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/test_api_consumer_wrapper" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/fee_manager" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/reward_manager" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/verifier" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/verifier_proxy" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/werc20_mock" ) // EthereumOracle oracle for "directrequest" job tests @@ -2269,10 +2272,11 @@ type EthereumMercuryVerifier struct { address common.Address client blockchain.EVMClient instance *verifier.Verifier + l zerolog.Logger } -func (e *EthereumMercuryVerifier) Address() string { - return e.address.Hex() +func (e *EthereumMercuryVerifier) Address() common.Address { + return e.address } func (e *EthereumMercuryVerifier) Verify(signedReport []byte, sender common.Address) error { @@ -2287,14 +2291,57 @@ func (e *EthereumMercuryVerifier) Verify(signedReport []byte, sender common.Addr return e.client.ProcessTransaction(tx) } +func (e *EthereumMercuryVerifier) SetConfig(feedId [32]byte, signers []common.Address, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte, recipientAddressesAndWeights []verifier.CommonAddressAndWeight) (*types.Transaction, error) { + opts, err := e.client.TransactionOpts(e.client.GetDefaultWallet()) + if err != nil { + return nil, err + } + tx, err := e.instance.SetConfig(opts, feedId, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig, recipientAddressesAndWeights) + e.l.Info().Err(err).Str("contractAddress", e.address.Hex()).Hex("feedId", feedId[:]).Msg("Called EthereumMercuryVerifier.SetConfig()") + if err != nil { + return nil, err + } + return tx, e.client.ProcessTransaction(tx) +} + +func (e *EthereumMercuryVerifier) LatestConfigDetails(ctx context.Context, feedId [32]byte) (verifier.LatestConfigDetails, error) { + opts := &bind.CallOpts{ + From: common.HexToAddress(e.client.GetDefaultWallet().Address()), + Context: ctx, + } + d, err := e.instance.LatestConfigDetails(opts, feedId) + e.l.Info().Err(err).Str("contractAddress", e.address.Hex()).Hex("feedId", feedId[:]). + Interface("details", d). + Msg("Called EthereumMercuryVerifier.LatestConfigDetails()") + if err != nil { + return verifier.LatestConfigDetails{}, err + } + return d, nil +} + type EthereumMercuryVerifierProxy struct { address common.Address client blockchain.EVMClient instance *verifier_proxy.VerifierProxy + l zerolog.Logger } -func (e *EthereumMercuryVerifierProxy) Address() string { - return e.address.Hex() +func (e *EthereumMercuryVerifierProxy) Address() common.Address { + return e.address +} + +func (e *EthereumMercuryVerifierProxy) InitializeVerifier(verifierAddress common.Address) (*types.Transaction, error) { + opts, err := e.client.TransactionOpts(e.client.GetDefaultWallet()) + if err != nil { + return nil, err + } + tx, err := e.instance.InitializeVerifier(opts, verifierAddress) + e.l.Info().Err(err).Str("contractAddress", e.address.Hex()).Str("verifierAddress", verifierAddress.Hex()). + Msg("Called EthereumMercuryVerifierProxy.InitializeVerifier()") + if err != nil { + return nil, err + } + return tx, e.client.ProcessTransaction(tx) } func (e *EthereumMercuryVerifierProxy) Verify(signedReport []byte, parameterPayload []byte, value *big.Int) (*types.Transaction, error) { @@ -2305,7 +2352,7 @@ func (e *EthereumMercuryVerifierProxy) Verify(signedReport []byte, parameterPayl if err != nil { return nil, err } - tx, err := e.instance.Verify(opts, parameterPayload, signedReport) + tx, err := e.instance.Verify(opts, signedReport, parameterPayload) if err != nil { return nil, err } @@ -2326,3 +2373,126 @@ func (e *EthereumMercuryVerifierProxy) VerifyBulk(signedReports [][]byte, parame } return tx, e.client.ProcessTransaction(tx) } + +func (e *EthereumMercuryVerifierProxy) SetFeeManager(feeManager common.Address) (*types.Transaction, error) { + opts, err := e.client.TransactionOpts(e.client.GetDefaultWallet()) + if err != nil { + return nil, err + } + tx, err := e.instance.SetFeeManager(opts, feeManager) + e.l.Info().Err(err).Str("feeManager", feeManager.Hex()).Msg("Called MercuryVerifierProxy.SetFeeManager()") + if err != nil { + return nil, err + } + return tx, e.client.ProcessTransaction(tx) +} + +type EthereumMercuryFeeManager struct { + address common.Address + client blockchain.EVMClient + instance *fee_manager.FeeManager + l zerolog.Logger +} + +func (e *EthereumMercuryFeeManager) Address() common.Address { + return e.address +} + +type EthereumMercuryRewardManager struct { + address common.Address + client blockchain.EVMClient + instance *reward_manager.RewardManager + l zerolog.Logger +} + +func (e *EthereumMercuryRewardManager) Address() common.Address { + return e.address +} + +func (e *EthereumMercuryRewardManager) SetFeeManager(feeManager common.Address) (*types.Transaction, error) { + opts, err := e.client.TransactionOpts(e.client.GetDefaultWallet()) + if err != nil { + return nil, err + } + tx, err := e.instance.SetFeeManager(opts, feeManager) + e.l.Info().Err(err).Str("feeManager", feeManager.Hex()).Msg("Called EthereumMercuryRewardManager.SetFeeManager()") + if err != nil { + return nil, err + } + return tx, e.client.ProcessTransaction(tx) +} + +type EthereumWERC20Mock struct { + address common.Address + client blockchain.EVMClient + instance *werc20_mock.WERC20Mock + l zerolog.Logger +} + +func (e *EthereumWERC20Mock) Address() common.Address { + return e.address +} + +func (l *EthereumWERC20Mock) Approve(to string, amount *big.Int) error { + opts, err := l.client.TransactionOpts(l.client.GetDefaultWallet()) + if err != nil { + return err + } + l.l.Info(). + Str("From", l.client.GetDefaultWallet().Address()). + Str("To", to). + Str("Amount", amount.String()). + Uint64("Nonce", opts.Nonce.Uint64()). + Msg("Approving LINK Transfer") + tx, err := l.instance.Approve(opts, common.HexToAddress(to), amount) + if err != nil { + return err + } + return l.client.ProcessTransaction(tx) +} + +func (l *EthereumWERC20Mock) BalanceOf(ctx context.Context, addr string) (*big.Int, error) { + opts := &bind.CallOpts{ + From: common.HexToAddress(l.client.GetDefaultWallet().Address()), + Context: ctx, + } + balance, err := l.instance.BalanceOf(opts, common.HexToAddress(addr)) + if err != nil { + return nil, err + } + return balance, nil +} + +func (l *EthereumWERC20Mock) Transfer(to string, amount *big.Int) error { + opts, err := l.client.TransactionOpts(l.client.GetDefaultWallet()) + if err != nil { + return err + } + l.l.Info(). + Str("From", l.client.GetDefaultWallet().Address()). + Str("To", to). + Str("Amount", amount.String()). + Uint64("Nonce", opts.Nonce.Uint64()). + Msg("EthereumWERC20Mock.Transfer()") + tx, err := l.instance.Transfer(opts, common.HexToAddress(to), amount) + if err != nil { + return err + } + return l.client.ProcessTransaction(tx) +} + +func (l *EthereumWERC20Mock) Mint(account common.Address, amount *big.Int) (*types.Transaction, error) { + opts, err := l.client.TransactionOpts(l.client.GetDefaultWallet()) + if err != nil { + return nil, err + } + l.l.Info(). + Str("account", account.Hex()). + Str("amount", amount.String()). + Msg("EthereumWERC20Mock.Mint()") + tx, err := l.instance.Mint(opts, account, amount) + if err != nil { + return tx, err + } + return tx, l.client.ProcessTransaction(tx) +} From 5233de330fc25a916566a12dddd87b2a3dd30cd5 Mon Sep 17 00:00:00 2001 From: george-dorin <120329946+george-dorin@users.noreply.github.com> Date: Fri, 29 Sep 2023 16:37:54 +0300 Subject: [PATCH 8/8] Fix enhanced EA Mercury telemetry prices are 0 when task result is not float64 (#10828) * Use utils.ToDecimal instead of casting to float64 * Add CHANGELOG * - Update logging --- core/services/ocrcommon/telemetry.go | 34 +++++++++++++++-------- core/services/ocrcommon/telemetry_test.go | 6 ++-- docs/CHANGELOG.md | 1 + 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/core/services/ocrcommon/telemetry.go b/core/services/ocrcommon/telemetry.go index 3a0ba04903..54a5002093 100644 --- a/core/services/ocrcommon/telemetry.go +++ b/core/services/ocrcommon/telemetry.go @@ -354,7 +354,7 @@ func ShouldCollectEnhancedTelemetryMercury(job *job.Job) bool { // bid and ask. This functions expects the pipeline.TaskRunResults to be correctly ordered func (e *EnhancedTelemetryService[T]) getPricesFromResults(startTask pipeline.TaskRunResult, allTasks *pipeline.TaskRunResults) (float64, float64, float64) { var benchmarkPrice, askPrice, bidPrice float64 - var ok bool + var err error //We rely on task results to be sorted in the correct order benchmarkPriceTask := allTasks.GetNextTaskOf(startTask) if benchmarkPriceTask == nil { @@ -365,9 +365,9 @@ func (e *EnhancedTelemetryService[T]) getPricesFromResults(startTask pipeline.Ta if benchmarkPriceTask.Result.Error != nil { e.lggr.Warnw(fmt.Sprintf("got error for enhanced EA telemetry benchmark price, job %d, id %s: %s", e.job.ID, benchmarkPriceTask.Task.DotID(), benchmarkPriceTask.Result.Error), "err", benchmarkPriceTask.Result.Error) } else { - benchmarkPrice, ok = benchmarkPriceTask.Result.Value.(float64) - if !ok { - e.lggr.Warnf("cannot parse enhanced EA telemetry benchmark price, job %d, id %s (expected float64, got type: %T)", e.job.ID, benchmarkPriceTask.Task.DotID(), benchmarkPriceTask.Result.Value) + benchmarkPrice, err = getResultFloat64(benchmarkPriceTask) + if err != nil { + e.lggr.Warnw(fmt.Sprintf("cannot parse enhanced EA telemetry benchmark price, job %d, id %s", e.job.ID, benchmarkPriceTask.Task.DotID()), "err", err) } } } @@ -375,15 +375,15 @@ func (e *EnhancedTelemetryService[T]) getPricesFromResults(startTask pipeline.Ta bidTask := allTasks.GetNextTaskOf(*benchmarkPriceTask) if bidTask == nil { e.lggr.Warnf("cannot parse enhanced EA telemetry bid price, task is nil, job %d, id %s", e.job.ID) - return 0, 0, 0 + return benchmarkPrice, 0, 0 } if bidTask.Task.Type() == pipeline.TaskTypeJSONParse { if bidTask.Result.Error != nil { e.lggr.Warnw(fmt.Sprintf("got error for enhanced EA telemetry bid price, job %d, id %s: %s", e.job.ID, bidTask.Task.DotID(), bidTask.Result.Error), "err", bidTask.Result.Error) } else { - bidPrice, ok = bidTask.Result.Value.(float64) - if !ok { - e.lggr.Warnf("cannot parse enhanced EA telemetry bid price, job %d, id %s (expected float64, got type: %T)", e.job.ID, bidTask.Task.DotID(), bidTask.Result.Value) + bidPrice, err = getResultFloat64(bidTask) + if err != nil { + e.lggr.Warnw(fmt.Sprintf("cannot parse enhanced EA telemetry bid price, job %d, id %s", e.job.ID, bidTask.Task.DotID()), "err", err) } } } @@ -391,15 +391,15 @@ func (e *EnhancedTelemetryService[T]) getPricesFromResults(startTask pipeline.Ta askTask := allTasks.GetNextTaskOf(*bidTask) if askTask == nil { e.lggr.Warnf("cannot parse enhanced EA telemetry ask price, task is nil, job %d, id %s", e.job.ID) - return 0, 0, 0 + return benchmarkPrice, bidPrice, 0 } if askTask.Task.Type() == pipeline.TaskTypeJSONParse { if bidTask.Result.Error != nil { e.lggr.Warnw(fmt.Sprintf("got error for enhanced EA telemetry ask price, job %d, id %s: %s", e.job.ID, askTask.Task.DotID(), askTask.Result.Error), "err", askTask.Result.Error) } else { - askPrice, ok = askTask.Result.Value.(float64) - if !ok { - e.lggr.Warnf("cannot parse enhanced EA telemetry ask price, job %d, id %s (expected float64, got type: %T)", e.job.ID, askTask.Task.DotID(), askTask.Result.Value) + askPrice, err = getResultFloat64(askTask) + if err != nil { + e.lggr.Warnw(fmt.Sprintf("cannot parse enhanced EA telemetry ask price, job %d, id %s", e.job.ID, askTask.Task.DotID()), "err", err) } } } @@ -431,3 +431,13 @@ func EnqueueEnhancedTelem[T EnhancedTelemetryData | EnhancedTelemetryMercuryData default: } } + +// getResultFloat64 will check the result type and force it to float64 or returns an error if the conversion cannot be made +func getResultFloat64(task *pipeline.TaskRunResult) (float64, error) { + result, err := utils.ToDecimal(task.Result.Value) + if err != nil { + return 0, err + } + resultFloat64, _ := result.Float64() + return resultFloat64, nil +} diff --git a/core/services/ocrcommon/telemetry_test.go b/core/services/ocrcommon/telemetry_test.go index e7c41c4676..495dc6fe78 100644 --- a/core/services/ocrcommon/telemetry_test.go +++ b/core/services/ocrcommon/telemetry_test.go @@ -416,7 +416,7 @@ var trrsMercury = pipeline.TaskRunResults{ BaseTask: pipeline.NewBaseTask(3, "ds3_ask", nil, nil, 3), }, Result: pipeline.Result{ - Value: float64(123456789.1), + Value: int64(321123), }, }, } @@ -461,7 +461,7 @@ func TestGetPricesFromResults(t *testing.T) { benchmarkPrice, bid, ask := e.getPricesFromResults(trrsMercury[0], &trrsMercury) require.Equal(t, 123456.123456, benchmarkPrice) require.Equal(t, 1234567.1234567, bid) - require.Equal(t, 123456789.1, ask) + require.Equal(t, float64(321123), ask) benchmarkPrice, bid, ask = e.getPricesFromResults(trrsMercury[0], &pipeline.TaskRunResults{}) require.Equal(t, float64(0), benchmarkPrice) @@ -601,7 +601,7 @@ func TestCollectMercuryEnhancedTelemetry(t *testing.T) { DataSource: "data-source-name", DpBenchmarkPrice: 123456.123456, DpBid: 1234567.1234567, - DpAsk: 123456789.1, + DpAsk: 321123, CurrentBlockNumber: 123456789, CurrentBlockHash: common.HexToHash("0x123321").String(), CurrentBlockTimestamp: 987654321, diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 299e248428..64bace1993 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -41,6 +41,7 @@ All nodes will have to remove the following configuration field: `ExplorerURL` - Fixed a bug where `evmChainId` is requested instead of `id` or `evm-chain-id` in CLI error verbatim - Fixed a bug that would cause the node to shut down while performing backup - Fixed health checker to include more services in the prometheus `health` metric and HTTP `/health` endpoint +- Fixed a bug where prices would not be parsed correctly in telemetry data