From 609feeef5927e8ccd963e72cfc15f36b33bb4f7c Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Tue, 30 May 2023 19:40:44 +0200 Subject: [PATCH 01/72] request batches based on time elapsed as well --- orchestrator/main_loops.go | 78 +++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/orchestrator/main_loops.go b/orchestrator/main_loops.go index a05dc99c..df88eee9 100644 --- a/orchestrator/main_loops.go +++ b/orchestrator/main_loops.go @@ -261,15 +261,16 @@ func (s *peggyOrchestrator) ValsetRequesterLoop(ctx context.Context) (err error) func (s *peggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) { logger := log.WithField("loop", "BatchRequesterLoop") + startTime := time.Now() + eightHoursPassed := false + return loops.RunLoop(ctx, defaultLoopDur, func() error { // get All the denominations // check if threshold is met // broadcast Request batch var pg loops.ParanoidGroup - pg.Go(func() error { - var unbatchedTokensWithFees []*types.BatchFees if err := retry.Do(func() (err error) { @@ -277,44 +278,61 @@ func (s *peggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) return }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { logger.WithError(err).Errorf("failed to get UnbatchedTokensWithFees, will retry (%d)", n) - })); err != nil { + }), + ); err != nil { // non-fatal, just alert logger.Warningln("unable to get UnbatchedTokensWithFees for the token") return nil } - if len(unbatchedTokensWithFees) > 0 { - logger.WithField("unbatchedTokensWithFees", unbatchedTokensWithFees).Debugln("Check if token fees meets set threshold amount and send batch request") - for _, unbatchedToken := range unbatchedTokensWithFees { - return retry.Do(func() (err error) { - // check if the token is present in cosmos denom. if so, send batch request with cosmosDenom - tokenAddr := ethcmn.HexToAddress(unbatchedToken.Token) - - var denom string - if cosmosDenom, ok := s.erc20ContractMapping[tokenAddr]; ok { - // cosmos denom - denom = cosmosDenom - } else { - // peggy denom - denom = types.PeggyDenomString(tokenAddr) - } - - // send batch request only if fee threshold is met. - if s.CheckFeeThreshold(tokenAddr, unbatchedToken.TotalFees, s.minBatchFeeUSD) { - logger.WithFields(log.Fields{"tokenContract": tokenAddr, "denom": denom}).Infoln("sending batch request") - _ = s.peggyBroadcastClient.SendRequestBatch(ctx, denom) - } + if len(unbatchedTokensWithFees) == 0 { + logger.Debugln("No outgoing withdraw tx or Unbatched token fee less than threshold") + return nil + } + logger.WithField("unbatchedTokensWithFees", unbatchedTokensWithFees).Debugln("Check if token fees meets set threshold amount and send batch request") + for _, unbatchedToken := range unbatchedTokensWithFees { + return retry.Do(func() (err error) { + // check if the token is present in cosmos denom. if so, send batch request with cosmosDenom + tokenAddr := ethcmn.HexToAddress(unbatchedToken.Token) + + var denom string + if cosmosDenom, ok := s.erc20ContractMapping[tokenAddr]; ok { + // cosmos denom + denom = cosmosDenom + } else { + // peggy denom + denom = types.PeggyDenomString(tokenAddr) + } + + // don't do anything if neither fee threshold is met or 8-hour window hasn't passed + if !s.CheckFeeThreshold(tokenAddr, unbatchedToken.TotalFees, s.minBatchFeeUSD) && + time.Since(startTime) < time.Hour*8 { return nil - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Errorf("failed to get LatestUnbatchOutgoingTx, will retry (%d)", n) - })) - } - } else { - logger.Debugln("No outgoing withdraw tx or Unbatched token fee less than threshold") + } + + logger.WithFields(log.Fields{"tokenContract": tokenAddr, "denom": denom}).Infoln("sending batch request") + _ = s.peggyBroadcastClient.SendRequestBatch(ctx, denom) + + if time.Since(startTime) >= time.Hour*8 { + // update window flag + eightHoursPassed = true + } + + return nil + }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { + logger.WithError(err).Errorf("failed to get LatestUnbatchOutgoingTx, will retry (%d)", n) + })) } + + if eightHoursPassed { + startTime = time.Now() + eightHoursPassed = false + } + return nil }) + return pg.Wait() }) } From 466d3cf26dba02b400fc6737b428f1cd858f6d6d Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 31 May 2023 18:07:32 +0200 Subject: [PATCH 02/72] 49.0 min batch fee --- cmd/peggo/orchestrator.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/peggo/orchestrator.go b/cmd/peggo/orchestrator.go index 46890893..69a28789 100644 --- a/cmd/peggo/orchestrator.go +++ b/cmd/peggo/orchestrator.go @@ -262,6 +262,9 @@ func orchestratorCmd(cmd *cli.Cmd) { } coingeckoFeed := coingecko.NewCoingeckoPriceFeed(100, &coingeckoConfig) + // make the flag obsolete and hardcode + *minBatchFeeUSD = 49.0 + svc := orchestrator.NewPeggyOrchestrator( cosmosQueryClient, peggyBroadcaster, From 40c690bf5097a499995f9d4bc3c354e76e62145c Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 31 May 2023 18:42:32 +0200 Subject: [PATCH 03/72] optional periodic batch requesting --- cmd/peggo/options.go | 8 ++++++ cmd/peggo/orchestrator.go | 4 +++ orchestrator/main_loops.go | 12 +++++---- orchestrator/orchestrator.go | 52 +++++++++++++++++++----------------- 4 files changed, 46 insertions(+), 30 deletions(-) diff --git a/cmd/peggo/options.go b/cmd/peggo/options.go index a7a31737..d609347d 100644 --- a/cmd/peggo/options.go +++ b/cmd/peggo/options.go @@ -323,6 +323,7 @@ func initRelayerOptions( func initBatchRequesterOptions( cmd *cli.Cmd, minBatchFeeUSD **float64, + periodicBatchRequesting **bool, ) { *minBatchFeeUSD = cmd.Float64(cli.Float64Opt{ Name: "min_batch_fee_usd", @@ -330,6 +331,13 @@ func initBatchRequesterOptions( EnvVar: "PEGGO_MIN_BATCH_FEE_USD", Value: float64(23.3), }) + + *periodicBatchRequesting = cmd.Bool(cli.BoolOpt{ + Name: "periodic_batch_requesting", + Desc: "If set, batches will be requested every 8 hours regardless of the fee", + EnvVar: "PEGGO_PERIODIC_BATCH_REQUESTING", + Value: false, + }) } // initCoingeckoOptions sets options for coingecko. diff --git a/cmd/peggo/orchestrator.go b/cmd/peggo/orchestrator.go index 69a28789..b99d5d29 100644 --- a/cmd/peggo/orchestrator.go +++ b/cmd/peggo/orchestrator.go @@ -75,6 +75,8 @@ func orchestratorCmd(cmd *cli.Cmd) { // Batch requester config minBatchFeeUSD *float64 + periodicBatchRequesting *bool + coingeckoApi *string ) @@ -127,6 +129,7 @@ func orchestratorCmd(cmd *cli.Cmd) { initBatchRequesterOptions( cmd, &minBatchFeeUSD, + &periodicBatchRequesting, ) initCoingeckoOptions( @@ -277,6 +280,7 @@ func orchestratorCmd(cmd *cli.Cmd) { relayer, *minBatchFeeUSD, coingeckoFeed, + *periodicBatchRequesting, ) go func() { diff --git a/orchestrator/main_loops.go b/orchestrator/main_loops.go index df88eee9..88d97c43 100644 --- a/orchestrator/main_loops.go +++ b/orchestrator/main_loops.go @@ -305,16 +305,18 @@ func (s *peggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) denom = types.PeggyDenomString(tokenAddr) } - // don't do anything if neither fee threshold is met or 8-hour window hasn't passed - if !s.CheckFeeThreshold(tokenAddr, unbatchedToken.TotalFees, s.minBatchFeeUSD) && - time.Since(startTime) < time.Hour*8 { - return nil + // don't do anything if neither fee threshold is met nor 8-hour window hasn't passed + if !s.CheckFeeThreshold(tokenAddr, unbatchedToken.TotalFees, s.minBatchFeeUSD) { + notInjectivePeggoOr8HoursHaventPassed := !s.periodicBatchRequesting || time.Since(startTime) < time.Hour*8 + if notInjectivePeggoOr8HoursHaventPassed { + return nil + } } logger.WithFields(log.Fields{"tokenContract": tokenAddr, "denom": denom}).Infoln("sending batch request") _ = s.peggyBroadcastClient.SendRequestBatch(ctx, denom) - if time.Since(startTime) >= time.Hour*8 { + if s.periodicBatchRequesting && time.Since(startTime) >= time.Hour*8 { // update window flag eightHoursPassed = true } diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index 9e466364..cd69e239 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -31,18 +31,19 @@ type PeggyOrchestrator interface { type peggyOrchestrator struct { svcTags metrics.Tags - tmClient tmclient.TendermintClient - cosmosQueryClient sidechain.PeggyQueryClient - peggyBroadcastClient sidechain.PeggyBroadcastClient - peggyContract peggy.PeggyContract - ethProvider provider.EVMProvider - ethFrom ethcmn.Address - ethSignerFn keystore.SignerFn - ethPersonalSignFn keystore.PersonalSignFn - erc20ContractMapping map[ethcmn.Address]string - relayer relayer.PeggyRelayer - minBatchFeeUSD float64 - priceFeeder *coingecko.CoingeckoPriceFeed + tmClient tmclient.TendermintClient + cosmosQueryClient sidechain.PeggyQueryClient + peggyBroadcastClient sidechain.PeggyBroadcastClient + peggyContract peggy.PeggyContract + ethProvider provider.EVMProvider + ethFrom ethcmn.Address + ethSignerFn keystore.SignerFn + ethPersonalSignFn keystore.PersonalSignFn + erc20ContractMapping map[ethcmn.Address]string + relayer relayer.PeggyRelayer + minBatchFeeUSD float64 + priceFeeder *coingecko.CoingeckoPriceFeed + periodicBatchRequesting bool } func NewPeggyOrchestrator( @@ -57,21 +58,22 @@ func NewPeggyOrchestrator( relayer relayer.PeggyRelayer, minBatchFeeUSD float64, priceFeeder *coingecko.CoingeckoPriceFeed, - + periodicBatchRequesting bool, ) PeggyOrchestrator { return &peggyOrchestrator{ - tmClient: tmClient, - cosmosQueryClient: cosmosQueryClient, - peggyBroadcastClient: peggyBroadcastClient, - peggyContract: peggyContract, - ethProvider: peggyContract.Provider(), - ethFrom: ethFrom, - ethSignerFn: ethSignerFn, - ethPersonalSignFn: ethPersonalSignFn, - erc20ContractMapping: erc20ContractMapping, - relayer: relayer, - minBatchFeeUSD: minBatchFeeUSD, - priceFeeder: priceFeeder, + tmClient: tmClient, + cosmosQueryClient: cosmosQueryClient, + peggyBroadcastClient: peggyBroadcastClient, + peggyContract: peggyContract, + ethProvider: peggyContract.Provider(), + ethFrom: ethFrom, + ethSignerFn: ethSignerFn, + ethPersonalSignFn: ethPersonalSignFn, + erc20ContractMapping: erc20ContractMapping, + relayer: relayer, + minBatchFeeUSD: minBatchFeeUSD, + priceFeeder: priceFeeder, + periodicBatchRequesting: periodicBatchRequesting, svcTags: metrics.Tags{ "svc": "peggy_orchestrator", }, From 0301b138c0e2baf7ce2d22cd49a819884bf98579 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Tue, 6 Jun 2023 18:16:19 +0200 Subject: [PATCH 04/72] fix unbatched tokens loop --- orchestrator/main_loops.go | 92 +++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 42 deletions(-) diff --git a/orchestrator/main_loops.go b/orchestrator/main_loops.go index 88d97c43..30446c41 100644 --- a/orchestrator/main_loops.go +++ b/orchestrator/main_loops.go @@ -264,22 +264,14 @@ func (s *peggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) startTime := time.Now() eightHoursPassed := false + // 1. Query all unbatched txs by token type + // 2. For each potential batch that satisfies the fee threshold, request batch creation return loops.RunLoop(ctx, defaultLoopDur, func() error { - // get All the denominations - // check if threshold is met - // broadcast Request batch - var pg loops.ParanoidGroup pg.Go(func() error { - var unbatchedTokensWithFees []*types.BatchFees - if err := retry.Do(func() (err error) { - unbatchedTokensWithFees, err = s.cosmosQueryClient.UnbatchedTokensWithFees(ctx) - return - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Errorf("failed to get UnbatchedTokensWithFees, will retry (%d)", n) - }), - ); err != nil { + unbatchedTokensWithFees, err := s.getBatchFeesByToken(ctx, logger) + if err != nil { // non-fatal, just alert logger.Warningln("unable to get UnbatchedTokensWithFees for the token") return nil @@ -292,39 +284,25 @@ func (s *peggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) logger.WithField("unbatchedTokensWithFees", unbatchedTokensWithFees).Debugln("Check if token fees meets set threshold amount and send batch request") for _, unbatchedToken := range unbatchedTokensWithFees { - return retry.Do(func() (err error) { - // check if the token is present in cosmos denom. if so, send batch request with cosmosDenom - tokenAddr := ethcmn.HexToAddress(unbatchedToken.Token) - - var denom string - if cosmosDenom, ok := s.erc20ContractMapping[tokenAddr]; ok { - // cosmos denom - denom = cosmosDenom - } else { - // peggy denom - denom = types.PeggyDenomString(tokenAddr) - } - - // don't do anything if neither fee threshold is met nor 8-hour window hasn't passed - if !s.CheckFeeThreshold(tokenAddr, unbatchedToken.TotalFees, s.minBatchFeeUSD) { - notInjectivePeggoOr8HoursHaventPassed := !s.periodicBatchRequesting || time.Since(startTime) < time.Hour*8 - if notInjectivePeggoOr8HoursHaventPassed { - return nil - } + // check if the token is present in cosmos denom. if so, send batch request with cosmosDenom + tokenAddr := ethcmn.HexToAddress(unbatchedToken.Token) + denom := s.getTokenDenom(tokenAddr) + + // don't do anything if neither fee threshold is met nor 8-hour window hasn't passed + if !s.CheckFeeThreshold(tokenAddr, unbatchedToken.TotalFees, s.minBatchFeeUSD) { + notInjectivePeggoOr8HoursHaventPassed := !s.periodicBatchRequesting || time.Since(startTime) < time.Hour*8 + if notInjectivePeggoOr8HoursHaventPassed { + continue } + } - logger.WithFields(log.Fields{"tokenContract": tokenAddr, "denom": denom}).Infoln("sending batch request") - _ = s.peggyBroadcastClient.SendRequestBatch(ctx, denom) - - if s.periodicBatchRequesting && time.Since(startTime) >= time.Hour*8 { - // update window flag - eightHoursPassed = true - } + logger.WithFields(log.Fields{"tokenContract": tokenAddr, "denom": denom}).Infoln("sending batch request") + _ = s.peggyBroadcastClient.SendRequestBatch(ctx, denom) - return nil - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Errorf("failed to get LatestUnbatchOutgoingTx, will retry (%d)", n) - })) + if s.periodicBatchRequesting && time.Since(startTime) >= time.Hour*8 { + // update window flag + eightHoursPassed = true + } } if eightHoursPassed { @@ -339,6 +317,36 @@ func (s *peggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) }) } +func (s *peggyOrchestrator) getBatchFeesByToken(ctx context.Context, log log.Logger) ([]*types.BatchFees, error) { + var unbatchedTokensWithFees []*types.BatchFees + if err := retry.Do(func() error { + fees, err := s.cosmosQueryClient.UnbatchedTokensWithFees(ctx) + if err != nil { + return err + } + + unbatchedTokensWithFees = fees + return nil + }, retry.Context(ctx), + retry.OnRetry(func(n uint, err error) { + log.WithError(err).Errorf("failed to get UnbatchedTokensWithFees, will retry (%d)", n) + }), + ); err != nil { + return nil, err + } + + return unbatchedTokensWithFees, nil +} + +func (s *peggyOrchestrator) getTokenDenom(tokenAddr common.Address) string { + if cosmosDenom, ok := s.erc20ContractMapping[tokenAddr]; ok { + return cosmosDenom + } + + // peggy denom + return types.PeggyDenomString(tokenAddr) +} + func (s *peggyOrchestrator) CheckFeeThreshold(erc20Contract common.Address, totalFee cosmtypes.Int, minFeeInUSD float64) bool { if minFeeInUSD == 0 { return true From 515e3789ea6c51b3effa8c3b7d51793af803b54f Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 7 Jun 2023 12:07:43 +0200 Subject: [PATCH 05/72] create config --- cmd/peggo/options.go | 228 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) diff --git a/cmd/peggo/options.go b/cmd/peggo/options.go index d609347d..ae3b3a63 100644 --- a/cmd/peggo/options.go +++ b/cmd/peggo/options.go @@ -352,3 +352,231 @@ func initCoingeckoOptions( Value: "https://api.coingecko.com/api/v3", }) } + +type Config struct { + // Cosmos params + cosmosChainID *string + cosmosGRPC *string + tendermintRPC *string + cosmosGasPrices *string + + // Cosmos Key Management + cosmosKeyringDir *string + cosmosKeyringAppName *string + cosmosKeyringBackend *string + + cosmosKeyFrom *string + cosmosKeyPassphrase *string + cosmosPrivKey *string + cosmosUseLedger *bool + + // Ethereum params + ethChainID *int + ethNodeRPC *string + ethNodeAlchemyWS *string + ethGasPriceAdjustment *float64 + ethMaxGasPrice *string + + // Ethereum Key Management + ethKeystoreDir *string + ethKeyFrom *string + ethPassphrase *string + ethPrivKey *string + ethUseLedger *bool + + // Relayer config + relayValsets *bool + relayValsetOffsetDur *string + relayBatches *bool + relayBatchOffsetDur *string + pendingTxWaitDuration *string + + // Batch requester config + minBatchFeeUSD *float64 + + periodicBatchRequesting *bool + + coingeckoApi *string +} + +func initConfig(cmd *cli.Cmd) Config { + cfg := Config{} + + /** Injective **/ + + cfg.cosmosChainID = cmd.String(cli.StringOpt{ + Name: "cosmos-chain-id", + Desc: "Specify Chain ID of the Cosmos network.", + EnvVar: "PEGGO_COSMOS_CHAIN_ID", + Value: "888", + }) + + cfg.cosmosGRPC = cmd.String(cli.StringOpt{ + Name: "cosmos-grpc", + Desc: "Cosmos GRPC querying endpoint", + EnvVar: "PEGGO_COSMOS_GRPC", + Value: "tcp://localhost:9900", + }) + + cfg.tendermintRPC = cmd.String(cli.StringOpt{ + Name: "tendermint-rpc", + Desc: "Tendermint RPC endpoint", + EnvVar: "PEGGO_TENDERMINT_RPC", + Value: "http://localhost:26657", + }) + + cfg.cosmosGasPrices = cmd.String(cli.StringOpt{ + Name: "cosmos-gas-prices", + Desc: "Specify Cosmos chain transaction fees as DecCoins gas prices", + EnvVar: "PEGGO_COSMOS_GAS_PRICES", + Value: "", // example: 500000000inj + }) + + cfg.cosmosKeyringBackend = cmd.String(cli.StringOpt{ + Name: "cosmos-keyring", + Desc: "Specify Cosmos keyring backend (os|file|kwallet|pass|test)", + EnvVar: "PEGGO_COSMOS_KEYRING", + Value: "file", + }) + + cfg.cosmosKeyringDir = cmd.String(cli.StringOpt{ + Name: "cosmos-keyring-dir", + Desc: "Specify Cosmos keyring dir, if using file keyring.", + EnvVar: "PEGGO_COSMOS_KEYRING_DIR", + Value: "", + }) + + cfg.cosmosKeyringAppName = cmd.String(cli.StringOpt{ + Name: "cosmos-keyring-app", + Desc: "Specify Cosmos keyring app name.", + EnvVar: "PEGGO_COSMOS_KEYRING_APP", + Value: "peggo", + }) + + cfg.cosmosKeyFrom = cmd.String(cli.StringOpt{ + Name: "cosmos-from", + Desc: "Specify the Cosmos validator key name or address. If specified, must exist in keyring, ledger or match the privkey.", + EnvVar: "PEGGO_COSMOS_FROM", + }) + + cfg.cosmosKeyPassphrase = cmd.String(cli.StringOpt{ + Name: "cosmos-from-passphrase", + Desc: "Specify keyring passphrase, otherwise Stdin will be used.", + EnvVar: "PEGGO_COSMOS_FROM_PASSPHRASE", + Value: "peggo", + }) + + cfg.cosmosPrivKey = cmd.String(cli.StringOpt{ + Name: "cosmos-pk", + Desc: "Provide a raw Cosmos account private key of the validator in hex. USE FOR TESTING ONLY!", + EnvVar: "PEGGO_COSMOS_PK", + }) + + cfg.cosmosUseLedger = cmd.Bool(cli.BoolOpt{ + Name: "cosmos-use-ledger", + Desc: "Use the Cosmos app on hardware ledger to sign transactions.", + EnvVar: "PEGGO_COSMOS_USE_LEDGER", + Value: false, + }) + + /** Ethereum **/ + + cfg.ethChainID = cmd.Int(cli.IntOpt{ + Name: "eth-chain-id", + Desc: "Specify Chain ID of the Ethereum network.", + EnvVar: "PEGGO_ETH_CHAIN_ID", + Value: 42, + }) + + cfg.ethNodeRPC = cmd.String(cli.StringOpt{ + Name: "eth-node-http", + Desc: "Specify HTTP endpoint for an Ethereum node.", + EnvVar: "PEGGO_ETH_RPC", + Value: "http://localhost:1317", + }) + + cfg.ethNodeAlchemyWS = cmd.String(cli.StringOpt{ + Name: "eth-node-alchemy-ws", + Desc: "Specify websocket url for an Alchemy ethereum node.", + EnvVar: "PEGGO_ETH_ALCHEMY_WS", + Value: "", + }) + + cfg.ethGasPriceAdjustment = cmd.Float64(cli.Float64Opt{ + Name: "eth_gas_price_adjustment", + Desc: "gas price adjustment for Ethereum transactions", + EnvVar: "PEGGO_ETH_GAS_PRICE_ADJUSTMENT", + Value: float64(1.3), + }) + + cfg.ethMaxGasPrice = cmd.String(cli.StringOpt{ + Name: "eth-max-gas-price", + Desc: "Specify Max gas price for Ethereum Transactions in GWei", + EnvVar: "PEGGO_ETH_MAX_GAS_PRICE", + Value: "500gwei", + }) + + /** Relayer **/ + + cfg.relayValsets = cmd.Bool(cli.BoolOpt{ + Name: "relay_valsets", + Desc: "If enabled, relayer will relay valsets to ethereum", + EnvVar: "PEGGO_RELAY_VALSETS", + Value: false, + }) + + cfg.relayValsetOffsetDur = cmd.String(cli.StringOpt{ + Name: "relay_valset_offset_dur", + Desc: "If set, relayer will broadcast valsetUpdate only after relayValsetOffsetDur has passed from time of valsetUpdate creation", + EnvVar: "PEGGO_RELAY_VALSET_OFFSET_DUR", + Value: "5m", + }) + + cfg.relayBatches = cmd.Bool(cli.BoolOpt{ + Name: "relay_batches", + Desc: "If enabled, relayer will relay batches to ethereum", + EnvVar: "PEGGO_RELAY_BATCHES", + Value: false, + }) + + cfg.relayBatchOffsetDur = cmd.String(cli.StringOpt{ + Name: "relay_batch_offset_dur", + Desc: "If set, relayer will broadcast batches only after relayBatchOffsetDur has passed from time of batch creation", + EnvVar: "PEGGO_RELAY_BATCH_OFFSET_DUR", + Value: "5m", + }) + + cfg.pendingTxWaitDuration = cmd.String(cli.StringOpt{ + Name: "relay_pending_tx_wait_duration", + Desc: "If set, relayer will broadcast pending batches/valsetupdate only after pendingTxWaitDuration has passed", + EnvVar: "PEGGO_RELAY_PENDING_TX_WAIT_DURATION", + Value: "20m", + }) + + /** Batch Requester **/ + + cfg.minBatchFeeUSD = cmd.Float64(cli.Float64Opt{ + Name: "min_batch_fee_usd", + Desc: "If set, batch request will create batches only if fee threshold exceeds", + EnvVar: "PEGGO_MIN_BATCH_FEE_USD", + Value: float64(23.3), + }) + + cfg.periodicBatchRequesting = cmd.Bool(cli.BoolOpt{ + Name: "periodic_batch_requesting", + Desc: "If set, batches will be requested every 8 hours regardless of the fee", + EnvVar: "PEGGO_PERIODIC_BATCH_REQUESTING", + Value: false, + }) + + /** Coingecko **/ + + cfg.coingeckoApi = cmd.String(cli.StringOpt{ + Name: "coingecko_api", + Desc: "Specify HTTP endpoint for coingecko api.", + EnvVar: "PEGGO_COINGECKO_API", + Value: "https://api.coingecko.com/api/v3", + }) + + return cfg +} From e1e4eedd382524a134930709a90da60e90631e35 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 7 Jun 2023 15:35:08 +0200 Subject: [PATCH 06/72] use config in init --- cmd/peggo/orchestrator.go | 182 +++++++++----------------------------- todo | 0 2 files changed, 43 insertions(+), 139 deletions(-) create mode 100644 todo diff --git a/cmd/peggo/orchestrator.go b/cmd/peggo/orchestrator.go index b99d5d29..394719be 100644 --- a/cmd/peggo/orchestrator.go +++ b/cmd/peggo/orchestrator.go @@ -34,108 +34,7 @@ import ( // $ peggo orchestrator func orchestratorCmd(cmd *cli.Cmd) { // orchestrator-specific CLI options - var ( - // Cosmos params - cosmosChainID *string - cosmosGRPC *string - tendermintRPC *string - cosmosGasPrices *string - - // Cosmos Key Management - cosmosKeyringDir *string - cosmosKeyringAppName *string - cosmosKeyringBackend *string - - cosmosKeyFrom *string - cosmosKeyPassphrase *string - cosmosPrivKey *string - cosmosUseLedger *bool - - // Ethereum params - ethChainID *int - ethNodeRPC *string - ethNodeAlchemyWS *string - ethGasPriceAdjustment *float64 - ethMaxGasPrice *string - - // Ethereum Key Management - ethKeystoreDir *string - ethKeyFrom *string - ethPassphrase *string - ethPrivKey *string - ethUseLedger *bool - - // Relayer config - relayValsets *bool - relayValsetOffsetDur *string - relayBatches *bool - relayBatchOffsetDur *string - pendingTxWaitDuration *string - - // Batch requester config - minBatchFeeUSD *float64 - - periodicBatchRequesting *bool - - coingeckoApi *string - ) - - initCosmosOptions( - cmd, - &cosmosChainID, - &cosmosGRPC, - &tendermintRPC, - &cosmosGasPrices, - ) - - initCosmosKeyOptions( - cmd, - &cosmosKeyringDir, - &cosmosKeyringAppName, - &cosmosKeyringBackend, - &cosmosKeyFrom, - &cosmosKeyPassphrase, - &cosmosPrivKey, - &cosmosUseLedger, - ) - - initEthereumOptions( - cmd, - ðChainID, - ðNodeRPC, - ðNodeAlchemyWS, - ðGasPriceAdjustment, - ðMaxGasPrice, - ) - - initEthereumKeyOptions( - cmd, - ðKeystoreDir, - ðKeyFrom, - ðPassphrase, - ðPrivKey, - ðUseLedger, - ) - - initRelayerOptions( - cmd, - &relayValsets, - &relayValsetOffsetDur, - &relayBatches, - &relayBatchOffsetDur, - &pendingTxWaitDuration, - ) - - initBatchRequesterOptions( - cmd, - &minBatchFeeUSD, - &periodicBatchRequesting, - ) - - initCoingeckoOptions( - cmd, - &coingeckoApi, - ) + cfg := initConfig(cmd) cmd.Before = func() { initMetrics(cmd) @@ -145,30 +44,30 @@ func orchestratorCmd(cmd *cli.Cmd) { // ensure a clean exit defer closer.Close() - if *cosmosUseLedger || *ethUseLedger { + if *cfg.cosmosUseLedger || *cfg.ethUseLedger { log.Fatalln("cannot really use Ledger for orchestrator, since signatures msut be realtime") } valAddress, cosmosKeyring, err := initCosmosKeyring( - cosmosKeyringDir, - cosmosKeyringAppName, - cosmosKeyringBackend, - cosmosKeyFrom, - cosmosKeyPassphrase, - cosmosPrivKey, - cosmosUseLedger, + cfg.cosmosKeyringDir, + cfg.cosmosKeyringAppName, + cfg.cosmosKeyringBackend, + cfg.cosmosKeyFrom, + cfg.cosmosKeyPassphrase, + cfg.cosmosPrivKey, + cfg.cosmosUseLedger, ) if err != nil { log.WithError(err).Fatalln("failed to init Cosmos keyring") } ethKeyFromAddress, signerFn, personalSignFn, err := initEthereumAccountsManager( - uint64(*ethChainID), - ethKeystoreDir, - ethKeyFrom, - ethPassphrase, - ethPrivKey, - ethUseLedger, + uint64(*cfg.ethChainID), + cfg.ethKeystoreDir, + cfg.ethKeyFrom, + cfg.ethPassphrase, + cfg.ethPrivKey, + cfg.ethUseLedger, ) if err != nil { log.WithError(err).Fatalln("failed to init Ethereum account") @@ -177,22 +76,22 @@ func orchestratorCmd(cmd *cli.Cmd) { log.Infoln("Using Cosmos ValAddress", valAddress.String()) log.Infoln("Using Ethereum address", ethKeyFromAddress.String()) - clientCtx, err := chainclient.NewClientContext(*cosmosChainID, valAddress.String(), cosmosKeyring) + clientCtx, err := chainclient.NewClientContext(*cfg.cosmosChainID, valAddress.String(), cosmosKeyring) if err != nil { log.WithError(err).Fatalln("failed to initialize cosmos client context") } - clientCtx = clientCtx.WithNodeURI(*tendermintRPC) - tmRPC, err := rpchttp.New(*tendermintRPC, "/websocket") + clientCtx = clientCtx.WithNodeURI(*cfg.tendermintRPC) + tmRPC, err := rpchttp.New(*cfg.tendermintRPC, "/websocket") if err != nil { log.WithError(err) } clientCtx = clientCtx.WithClient(tmRPC) - daemonClient, err := chainclient.NewChainClient(clientCtx, *cosmosGRPC, common.OptionGasPrices(*cosmosGasPrices)) + daemonClient, err := chainclient.NewChainClient(clientCtx, *cfg.cosmosGRPC, common.OptionGasPrices(*cfg.cosmosGasPrices)) if err != nil { - log.WithError(err).WithFields(log.Fields{ - "endpoint": *cosmosGRPC, - }).Fatalln("failed to connect to daemon, is injectived running?") + log.WithError(err).WithFields( + log.Fields{"endpoint": *cfg.cosmosGRPC}). + Fatalln("failed to connect to daemon, is injectived running?") } log.Infoln("Waiting for injectived GRPC") @@ -234,53 +133,58 @@ func orchestratorCmd(cmd *cli.Cmd) { erc20ContractMapping := make(map[ethcmn.Address]string) erc20ContractMapping[injAddress] = ctypes.InjectiveCoin - evmRPC, err := rpc.Dial(*ethNodeRPC) + evmRPC, err := rpc.Dial(*cfg.ethNodeRPC) if err != nil { - log.WithField("endpoint", *ethNodeRPC).WithError(err).Fatalln("Failed to connect to Ethereum RPC") + log.WithField("endpoint", *cfg.ethNodeRPC).WithError(err).Fatalln("Failed to connect to Ethereum RPC") return } ethProvider := provider.NewEVMProvider(evmRPC) - log.Infoln("Connected to Ethereum RPC at", *ethNodeRPC) + log.Infoln("Connected to Ethereum RPC at", *cfg.ethNodeRPC) - ethCommitter, err := committer.NewEthCommitter(ethKeyFromAddress, *ethGasPriceAdjustment, *ethMaxGasPrice, signerFn, ethProvider) + ethCommitter, err := committer.NewEthCommitter(ethKeyFromAddress, *cfg.ethGasPriceAdjustment, *cfg.ethMaxGasPrice, signerFn, ethProvider) orShutdown(err) pendingTxInputList := peggy.PendingTxInputList{} - pendingTxWaitDuration, err := time.ParseDuration(*pendingTxWaitDuration) + pendingTxWaitDuration, err := time.ParseDuration(*cfg.pendingTxWaitDuration) orShutdown(err) peggyContract, err := peggy.NewPeggyContract(ethCommitter, peggyAddress, pendingTxInputList, pendingTxWaitDuration) orShutdown(err) // If Alchemy Websocket URL is set, then Subscribe to Pending Transaction of Peggy Contract. - if *ethNodeAlchemyWS != "" { - go peggyContract.SubscribeToPendingTxs(*ethNodeAlchemyWS) + if *cfg.ethNodeAlchemyWS != "" { + go peggyContract.SubscribeToPendingTxs(*cfg.ethNodeAlchemyWS) } - relayer := relayer.NewPeggyRelayer(cosmosQueryClient, tmclient.NewRPCClient(*tendermintRPC), peggyContract, *relayValsets, *relayValsetOffsetDur, *relayBatches, *relayBatchOffsetDur) + relayer := relayer.NewPeggyRelayer( + cosmosQueryClient, + tmclient.NewRPCClient(*cfg.tendermintRPC), + peggyContract, + *cfg.relayValsets, + *cfg.relayValsetOffsetDur, + *cfg.relayBatches, + *cfg.relayBatchOffsetDur, + ) - coingeckoConfig := coingecko.Config{ - BaseURL: *coingeckoApi, - } - coingeckoFeed := coingecko.NewCoingeckoPriceFeed(100, &coingeckoConfig) + coingeckoFeed := coingecko.NewCoingeckoPriceFeed(100, &coingecko.Config{BaseURL: *cfg.coingeckoApi}) // make the flag obsolete and hardcode - *minBatchFeeUSD = 49.0 + *cfg.minBatchFeeUSD = 49.0 svc := orchestrator.NewPeggyOrchestrator( cosmosQueryClient, peggyBroadcaster, - tmclient.NewRPCClient(*tendermintRPC), + tmclient.NewRPCClient(*cfg.tendermintRPC), peggyContract, ethKeyFromAddress, signerFn, personalSignFn, erc20ContractMapping, relayer, - *minBatchFeeUSD, + *cfg.minBatchFeeUSD, coingeckoFeed, - *periodicBatchRequesting, + *cfg.periodicBatchRequesting, ) go func() { diff --git a/todo b/todo new file mode 100644 index 00000000..e69de29b From e693009d7ed73420a9a8851f2362f69a635bf5ec Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 7 Jun 2023 16:11:58 +0200 Subject: [PATCH 07/72] remove unused field from ochestrator --- cmd/peggo/orchestrator.go | 12 +++++++++--- orchestrator/orchestrator.go | 14 +------------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/cmd/peggo/orchestrator.go b/cmd/peggo/orchestrator.go index 394719be..776a2208 100644 --- a/cmd/peggo/orchestrator.go +++ b/cmd/peggo/orchestrator.go @@ -76,6 +76,8 @@ func orchestratorCmd(cmd *cli.Cmd) { log.Infoln("Using Cosmos ValAddress", valAddress.String()) log.Infoln("Using Ethereum address", ethKeyFromAddress.String()) + // injective start + clientCtx, err := chainclient.NewClientContext(*cfg.cosmosChainID, valAddress.String(), cosmosKeyring) if err != nil { log.WithError(err).Fatalln("failed to initialize cosmos client context") @@ -114,6 +116,8 @@ func orchestratorCmd(cmd *cli.Cmd) { ctx, cancelFn := context.WithCancel(context.Background()) closer.Bind(cancelFn) + // injective end + peggyParams, err := cosmosQueryClient.PeggyParams(ctx) if err != nil { log.WithError(err).Fatalln("failed to query peggy params, is injectived running?") @@ -133,11 +137,14 @@ func orchestratorCmd(cmd *cli.Cmd) { erc20ContractMapping := make(map[ethcmn.Address]string) erc20ContractMapping[injAddress] = ctypes.InjectiveCoin + // eth start + evmRPC, err := rpc.Dial(*cfg.ethNodeRPC) if err != nil { log.WithField("endpoint", *cfg.ethNodeRPC).WithError(err).Fatalln("Failed to connect to Ethereum RPC") return } + // todo dusan: last error return ethProvider := provider.NewEVMProvider(evmRPC) log.Infoln("Connected to Ethereum RPC at", *cfg.ethNodeRPC) @@ -157,6 +164,8 @@ func orchestratorCmd(cmd *cli.Cmd) { go peggyContract.SubscribeToPendingTxs(*cfg.ethNodeAlchemyWS) } + // eth end + relayer := relayer.NewPeggyRelayer( cosmosQueryClient, tmclient.NewRPCClient(*cfg.tendermintRPC), @@ -175,11 +184,8 @@ func orchestratorCmd(cmd *cli.Cmd) { svc := orchestrator.NewPeggyOrchestrator( cosmosQueryClient, peggyBroadcaster, - tmclient.NewRPCClient(*cfg.tendermintRPC), peggyContract, ethKeyFromAddress, - signerFn, - personalSignFn, erc20ContractMapping, relayer, *cfg.minBatchFeeUSD, diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index cd69e239..9350cbb8 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -5,12 +5,9 @@ import ( ethcmn "github.com/ethereum/go-ethereum/common" - "github.com/InjectiveLabs/peggo/orchestrator/coingecko" - "github.com/InjectiveLabs/peggo/orchestrator/cosmos/tmclient" - "github.com/InjectiveLabs/metrics" + "github.com/InjectiveLabs/peggo/orchestrator/coingecko" sidechain "github.com/InjectiveLabs/peggo/orchestrator/cosmos" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/keystore" "github.com/InjectiveLabs/peggo/orchestrator/ethereum/peggy" "github.com/InjectiveLabs/peggo/orchestrator/ethereum/provider" "github.com/InjectiveLabs/peggo/orchestrator/relayer" @@ -31,14 +28,11 @@ type PeggyOrchestrator interface { type peggyOrchestrator struct { svcTags metrics.Tags - tmClient tmclient.TendermintClient cosmosQueryClient sidechain.PeggyQueryClient peggyBroadcastClient sidechain.PeggyBroadcastClient peggyContract peggy.PeggyContract ethProvider provider.EVMProvider ethFrom ethcmn.Address - ethSignerFn keystore.SignerFn - ethPersonalSignFn keystore.PersonalSignFn erc20ContractMapping map[ethcmn.Address]string relayer relayer.PeggyRelayer minBatchFeeUSD float64 @@ -49,11 +43,8 @@ type peggyOrchestrator struct { func NewPeggyOrchestrator( cosmosQueryClient sidechain.PeggyQueryClient, peggyBroadcastClient sidechain.PeggyBroadcastClient, - tmClient tmclient.TendermintClient, peggyContract peggy.PeggyContract, ethFrom ethcmn.Address, - ethSignerFn keystore.SignerFn, - ethPersonalSignFn keystore.PersonalSignFn, erc20ContractMapping map[ethcmn.Address]string, relayer relayer.PeggyRelayer, minBatchFeeUSD float64, @@ -61,14 +52,11 @@ func NewPeggyOrchestrator( periodicBatchRequesting bool, ) PeggyOrchestrator { return &peggyOrchestrator{ - tmClient: tmClient, cosmosQueryClient: cosmosQueryClient, peggyBroadcastClient: peggyBroadcastClient, peggyContract: peggyContract, ethProvider: peggyContract.Provider(), ethFrom: ethFrom, - ethSignerFn: ethSignerFn, - ethPersonalSignFn: ethPersonalSignFn, erc20ContractMapping: erc20ContractMapping, relayer: relayer, minBatchFeeUSD: minBatchFeeUSD, From 2e6abc66159634942f3376ad76d662201717e537 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 7 Jun 2023 17:19:28 +0200 Subject: [PATCH 08/72] abstract ethereum network --- cmd/peggo/orchestrator.go | 4 +- orchestrator/ethereum/network.go | 64 ++++++++++++++++++++++++++++++++ orchestrator/orchestrator.go | 12 +++++- 3 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 orchestrator/ethereum/network.go diff --git a/cmd/peggo/orchestrator.go b/cmd/peggo/orchestrator.go index 776a2208..e5cb57ac 100644 --- a/cmd/peggo/orchestrator.go +++ b/cmd/peggo/orchestrator.go @@ -111,13 +111,13 @@ func orchestratorCmd(cmd *cli.Cmd) { ) cancelWait() + // injective end + // Query peggy params cosmosQueryClient := cosmos.NewPeggyQueryClient(peggyQuerier) ctx, cancelFn := context.WithCancel(context.Background()) closer.Bind(cancelFn) - // injective end - peggyParams, err := cosmosQueryClient.PeggyParams(ctx) if err != nil { log.WithError(err).Fatalln("failed to query peggy params, is injectived running?") diff --git a/orchestrator/ethereum/network.go b/orchestrator/ethereum/network.go new file mode 100644 index 00000000..127925cc --- /dev/null +++ b/orchestrator/ethereum/network.go @@ -0,0 +1,64 @@ +package ethereum + +import ( + "github.com/InjectiveLabs/peggo/orchestrator/ethereum/committer" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum/peggy" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum/provider" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + ethcmn "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" + log "github.com/xlab/suplog" + "time" +) + +type Network struct { + peggy.PeggyContract +} + +func NewNetwork( + ethNodeRPC string, + peggyContractAddr, + fromAddr ethcmn.Address, + signerFn bind.SignerFn, + gasPriceAdjustment float64, + maxGasPrice string, + pendingTxWaitDuration string, + ethNodeAlchemyWS string, +) (*Network, error) { + + evmRPC, err := rpc.Dial(ethNodeRPC) + if err != nil { + log.WithField("endpoint", ethNodeRPC).WithError(err).Fatalln("Failed to connect to Ethereum RPC") + return nil, err + } + + log.Infoln("Connected to Ethereum RPC at", ethNodeRPC) + + ethCommitter, err := committer.NewEthCommitter( + fromAddr, + gasPriceAdjustment, + maxGasPrice, + signerFn, + provider.NewEVMProvider(evmRPC), + ) + if err != nil { + return nil, err + } + + pendingTxDuration, err := time.ParseDuration(pendingTxWaitDuration) + if err != nil { + return nil, err + } + + peggyContract, err := peggy.NewPeggyContract(ethCommitter, peggyContractAddr, peggy.PendingTxInputList{}, pendingTxDuration) + if err != nil { + return nil, err + } + + // If Alchemy Websocket URL is set, then Subscribe to Pending Transaction of Peggy Contract. + if ethNodeAlchemyWS != "" { + go peggyContract.SubscribeToPendingTxs(ethNodeAlchemyWS) + } + + return &Network{PeggyContract: peggyContract}, nil +} diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index 9350cbb8..a7c7f9f9 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -2,6 +2,8 @@ package orchestrator import ( "context" + "github.com/ethereum/go-ethereum/core/types" + "math/big" ethcmn "github.com/ethereum/go-ethereum/common" @@ -13,6 +15,13 @@ import ( "github.com/InjectiveLabs/peggo/orchestrator/relayer" ) +type Injective interface { +} + +type EthereumNetwork interface { + HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) +} + type PeggyOrchestrator interface { Start(ctx context.Context, validatorMode bool) error @@ -26,7 +35,8 @@ type PeggyOrchestrator interface { } type peggyOrchestrator struct { - svcTags metrics.Tags + svcTags metrics.Tags + ethereum EthereumNetwork cosmosQueryClient sidechain.PeggyQueryClient peggyBroadcastClient sidechain.PeggyBroadcastClient From d94e43aed5b6a411d22a8b7e8c025b8de7d73c34 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Thu, 8 Jun 2023 10:45:06 +0200 Subject: [PATCH 09/72] remove interface --- orchestrator/eth_event_watcher.go | 2 +- orchestrator/main_loops.go | 22 +++++++++++----------- orchestrator/oracle_resync.go | 2 +- orchestrator/orchestrator.go | 18 +++--------------- 4 files changed, 16 insertions(+), 28 deletions(-) diff --git a/orchestrator/eth_event_watcher.go b/orchestrator/eth_event_watcher.go index 8be16582..0de91232 100644 --- a/orchestrator/eth_event_watcher.go +++ b/orchestrator/eth_event_watcher.go @@ -22,7 +22,7 @@ const ethBlockConfirmationDelay = 12 // CheckForEvents checks for events such as a deposit to the Peggy Ethereum contract or a validator set update // or a transaction batch update. It then responds to these events by performing actions on the Cosmos chain if required -func (s *peggyOrchestrator) CheckForEvents( +func (s *PeggyOrchestrator) CheckForEvents( ctx context.Context, startingBlock uint64, ) (currentBlock uint64, err error) { diff --git a/orchestrator/main_loops.go b/orchestrator/main_loops.go index 30446c41..f622bd3d 100644 --- a/orchestrator/main_loops.go +++ b/orchestrator/main_loops.go @@ -25,7 +25,7 @@ const defaultLoopDur = 60 * time.Second // Start combines the all major roles required to make // up the Orchestrator, all of these are async loops. -func (s *peggyOrchestrator) Start(ctx context.Context, validatorMode bool) error { +func (s *PeggyOrchestrator) Start(ctx context.Context, validatorMode bool) error { if !validatorMode { log.Infoln("Starting peggo in relayer (non-validator) mode") return s.startRelayerMode(ctx) @@ -39,7 +39,7 @@ func (s *peggyOrchestrator) Start(ctx context.Context, validatorMode bool) error // and ferried over to Cosmos where they will be used to issue tokens or process batches. // // TODO this loop requires a method to bootstrap back to the correct event nonce when restarted -func (s *peggyOrchestrator) EthOracleMainLoop(ctx context.Context) (err error) { +func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) (err error) { logger := log.WithField("loop", "EthOracleMainLoop") lastResync := time.Now() var lastCheckedBlock uint64 @@ -106,7 +106,7 @@ func (s *peggyOrchestrator) EthOracleMainLoop(ctx context.Context) (err error) { // EthSignerMainLoop simply signs off on any batches or validator sets provided by the validator // since these are provided directly by a trusted Cosmsos node they can simply be assumed to be // valid and signed off on. -func (s *peggyOrchestrator) EthSignerMainLoop(ctx context.Context) (err error) { +func (s *PeggyOrchestrator) EthSignerMainLoop(ctx context.Context) (err error) { logger := log.WithField("loop", "EthSignerMainLoop") var peggyID common.Hash @@ -203,7 +203,7 @@ func (s *peggyOrchestrator) EthSignerMainLoop(ctx context.Context) (err error) { /* Not required any more. The valset request are generated in endblocker of peggy module automatically. Also MsgSendValsetRequest is removed on peggy module. -func (s *peggyOrchestrator) ValsetRequesterLoop(ctx context.Context) (err error) { +func (s *PeggyOrchestrator) ValsetRequesterLoop(ctx context.Context) (err error) { logger := log.WithField("loop", "ValsetRequesterLoop") return loops.RunLoop(ctx, defaultLoopDur, func() error { @@ -259,7 +259,7 @@ func (s *peggyOrchestrator) ValsetRequesterLoop(ctx context.Context) (err error) } **/ -func (s *peggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) { +func (s *PeggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) { logger := log.WithField("loop", "BatchRequesterLoop") startTime := time.Now() eightHoursPassed := false @@ -317,7 +317,7 @@ func (s *peggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) }) } -func (s *peggyOrchestrator) getBatchFeesByToken(ctx context.Context, log log.Logger) ([]*types.BatchFees, error) { +func (s *PeggyOrchestrator) getBatchFeesByToken(ctx context.Context, log log.Logger) ([]*types.BatchFees, error) { var unbatchedTokensWithFees []*types.BatchFees if err := retry.Do(func() error { fees, err := s.cosmosQueryClient.UnbatchedTokensWithFees(ctx) @@ -338,7 +338,7 @@ func (s *peggyOrchestrator) getBatchFeesByToken(ctx context.Context, log log.Log return unbatchedTokensWithFees, nil } -func (s *peggyOrchestrator) getTokenDenom(tokenAddr common.Address) string { +func (s *PeggyOrchestrator) getTokenDenom(tokenAddr common.Address) string { if cosmosDenom, ok := s.erc20ContractMapping[tokenAddr]; ok { return cosmosDenom } @@ -347,7 +347,7 @@ func (s *peggyOrchestrator) getTokenDenom(tokenAddr common.Address) string { return types.PeggyDenomString(tokenAddr) } -func (s *peggyOrchestrator) CheckFeeThreshold(erc20Contract common.Address, totalFee cosmtypes.Int, minFeeInUSD float64) bool { +func (s *PeggyOrchestrator) CheckFeeThreshold(erc20Contract common.Address, totalFee cosmtypes.Int, minFeeInUSD float64) bool { if minFeeInUSD == 0 { return true } @@ -367,7 +367,7 @@ func (s *peggyOrchestrator) CheckFeeThreshold(erc20Contract common.Address, tota return false } -func (s *peggyOrchestrator) RelayerMainLoop(ctx context.Context) (err error) { +func (s *PeggyOrchestrator) RelayerMainLoop(ctx context.Context) (err error) { if s.relayer != nil { return s.relayer.Start(ctx) } else { @@ -417,7 +417,7 @@ func calculateTotalValsetPower(valset *types.Valset) *big.Int { // startValidatorMode runs all orchestrator processes. This is called // when peggo is run alongside a validator injective node. -func (s *peggyOrchestrator) startValidatorMode(ctx context.Context) error { +func (s *PeggyOrchestrator) startValidatorMode(ctx context.Context) error { var pg loops.ParanoidGroup pg.Go(func() error { @@ -439,7 +439,7 @@ func (s *peggyOrchestrator) startValidatorMode(ctx context.Context) error { // startRelayerMode runs orchestrator processes that only relay specific // messages that do not require a validator's signature. This mode is run // alongside a non-validator injective node -func (s *peggyOrchestrator) startRelayerMode(ctx context.Context) error { +func (s *PeggyOrchestrator) startRelayerMode(ctx context.Context) error { var pg loops.ParanoidGroup pg.Go(func() error { diff --git a/orchestrator/oracle_resync.go b/orchestrator/oracle_resync.go index 1a93178f..58ec24b2 100644 --- a/orchestrator/oracle_resync.go +++ b/orchestrator/oracle_resync.go @@ -6,7 +6,7 @@ import ( ) // GetLastCheckedBlock retrieves the last claim event this oracle has relayed to Cosmos. -func (s *peggyOrchestrator) GetLastCheckedBlock(ctx context.Context) (uint64, error) { +func (s *PeggyOrchestrator) GetLastCheckedBlock(ctx context.Context) (uint64, error) { metrics.ReportFuncCall(s.svcTags) doneFn := metrics.ReportFuncTiming(s.svcTags) defer doneFn() diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index a7c7f9f9..673668b9 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -22,19 +22,7 @@ type EthereumNetwork interface { HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) } -type PeggyOrchestrator interface { - Start(ctx context.Context, validatorMode bool) error - - CheckForEvents(ctx context.Context, startingBlock uint64) (currentBlock uint64, err error) - GetLastCheckedBlock(ctx context.Context) (uint64, error) - - EthOracleMainLoop(ctx context.Context) error - EthSignerMainLoop(ctx context.Context) error - BatchRequesterLoop(ctx context.Context) error - RelayerMainLoop(ctx context.Context) error -} - -type peggyOrchestrator struct { +type PeggyOrchestrator struct { svcTags metrics.Tags ethereum EthereumNetwork @@ -60,8 +48,8 @@ func NewPeggyOrchestrator( minBatchFeeUSD float64, priceFeeder *coingecko.CoingeckoPriceFeed, periodicBatchRequesting bool, -) PeggyOrchestrator { - return &peggyOrchestrator{ +) *PeggyOrchestrator { + return &PeggyOrchestrator{ cosmosQueryClient: cosmosQueryClient, peggyBroadcastClient: peggyBroadcastClient, peggyContract: peggyContract, From d14e393879783b764551fe528ca1451343f81b8e Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Thu, 8 Jun 2023 11:27:02 +0200 Subject: [PATCH 10/72] simplify batch request loop --- orchestrator/main_loops.go | 89 ++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 48 deletions(-) diff --git a/orchestrator/main_loops.go b/orchestrator/main_loops.go index f622bd3d..303cbfa2 100644 --- a/orchestrator/main_loops.go +++ b/orchestrator/main_loops.go @@ -262,57 +262,18 @@ func (s *PeggyOrchestrator) ValsetRequesterLoop(ctx context.Context) (err error) func (s *PeggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) { logger := log.WithField("loop", "BatchRequesterLoop") startTime := time.Now() - eightHoursPassed := false - // 1. Query all unbatched txs by token type - // 2. For each potential batch that satisfies the fee threshold, request batch creation - return loops.RunLoop(ctx, defaultLoopDur, func() error { - var pg loops.ParanoidGroup - pg.Go(func() error { - - unbatchedTokensWithFees, err := s.getBatchFeesByToken(ctx, logger) - if err != nil { - // non-fatal, just alert - logger.Warningln("unable to get UnbatchedTokensWithFees for the token") - return nil - } - - if len(unbatchedTokensWithFees) == 0 { - logger.Debugln("No outgoing withdraw tx or Unbatched token fee less than threshold") - return nil - } - - logger.WithField("unbatchedTokensWithFees", unbatchedTokensWithFees).Debugln("Check if token fees meets set threshold amount and send batch request") - for _, unbatchedToken := range unbatchedTokensWithFees { - // check if the token is present in cosmos denom. if so, send batch request with cosmosDenom - tokenAddr := ethcmn.HexToAddress(unbatchedToken.Token) - denom := s.getTokenDenom(tokenAddr) - - // don't do anything if neither fee threshold is met nor 8-hour window hasn't passed - if !s.CheckFeeThreshold(tokenAddr, unbatchedToken.TotalFees, s.minBatchFeeUSD) { - notInjectivePeggoOr8HoursHaventPassed := !s.periodicBatchRequesting || time.Since(startTime) < time.Hour*8 - if notInjectivePeggoOr8HoursHaventPassed { - continue - } - } - - logger.WithFields(log.Fields{"tokenContract": tokenAddr, "denom": denom}).Infoln("sending batch request") - _ = s.peggyBroadcastClient.SendRequestBatch(ctx, denom) - - if s.periodicBatchRequesting && time.Since(startTime) >= time.Hour*8 { - // update window flag - eightHoursPassed = true - } - } + // we're the only ones relaying + isInjectiveRelayer := s.periodicBatchRequesting - if eightHoursPassed { - startTime = time.Now() - eightHoursPassed = false - } - - return nil - }) + return loops.RunLoop(ctx, defaultLoopDur, func() error { + mustRequestBatch := false + if isInjectiveRelayer && time.Since(startTime) > time.Hour*8 { + mustRequestBatch = true + } + var pg loops.ParanoidGroup + pg.Go(func() error { return s.requestBatches(ctx, logger, mustRequestBatch) }) return pg.Wait() }) } @@ -347,6 +308,38 @@ func (s *PeggyOrchestrator) getTokenDenom(tokenAddr common.Address) string { return types.PeggyDenomString(tokenAddr) } +func (s *PeggyOrchestrator) requestBatches(ctx context.Context, logger log.Logger, mustRequest bool) error { + unbatchedTokensWithFees, err := s.getBatchFeesByToken(ctx, logger) + if err != nil { + // non-fatal, just alert + logger.Warningln("unable to get UnbatchedTokensWithFees for the token") + return nil + } + + if len(unbatchedTokensWithFees) == 0 { + logger.Debugln("No outgoing withdraw tx or Unbatched token fee less than threshold") + return nil + } + + logger.WithField("unbatchedTokensWithFees", unbatchedTokensWithFees).Debugln("Check if token fees meets set threshold amount and send batch request") + for _, unbatchedToken := range unbatchedTokensWithFees { + // check if the token is present in cosmos denom. if so, send batch request with cosmosDenom + tokenAddr := ethcmn.HexToAddress(unbatchedToken.Token) + denom := s.getTokenDenom(tokenAddr) + + thresholdMet := s.CheckFeeThreshold(tokenAddr, unbatchedToken.TotalFees, s.minBatchFeeUSD) + if !thresholdMet && !mustRequest { + // non injective relayers only relay when the threshold is met + continue + } + + logger.WithFields(log.Fields{"tokenContract": tokenAddr, "denom": denom}).Infoln("sending batch request") + _ = s.peggyBroadcastClient.SendRequestBatch(ctx, denom) + } + + return nil +} + func (s *PeggyOrchestrator) CheckFeeThreshold(erc20Contract common.Address, totalFee cosmtypes.Int, minFeeInUSD float64) bool { if minFeeInUSD == 0 { return true From 21f68af74bc85fef38e8e629206a9f0e1c3f93cf Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Thu, 8 Jun 2023 13:08:24 +0200 Subject: [PATCH 11/72] write request batch test --- orchestrator/batch_request.go | 120 +++++++++++++++++++++++++++++ orchestrator/batch_request_test.go | 33 ++++++++ orchestrator/main_loops.go | 105 ------------------------- orchestrator/orchestrator.go | 15 +++- 4 files changed, 165 insertions(+), 108 deletions(-) create mode 100644 orchestrator/batch_request.go create mode 100644 orchestrator/batch_request_test.go diff --git a/orchestrator/batch_request.go b/orchestrator/batch_request.go new file mode 100644 index 00000000..d378d0f0 --- /dev/null +++ b/orchestrator/batch_request.go @@ -0,0 +1,120 @@ +package orchestrator + +import ( + "context" + "time" + + "github.com/avast/retry-go" + ethcmn "github.com/ethereum/go-ethereum/common" + "github.com/shopspring/decimal" + log "github.com/xlab/suplog" + + "github.com/InjectiveLabs/peggo/orchestrator/loops" + "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + cosmtypes "github.com/cosmos/cosmos-sdk/types" +) + +func (s *PeggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) { + logger := log.WithField("loop", "BatchRequesterLoop") + startTime := time.Now() + + // we're the only ones relaying + isInjectiveRelayer := s.periodicBatchRequesting + + return loops.RunLoop(ctx, defaultLoopDur, func() error { + mustRequestBatch := false + if isInjectiveRelayer && time.Since(startTime) > time.Hour*8 { + mustRequestBatch = true + } + + var pg loops.ParanoidGroup + pg.Go(func() error { return s.requestBatches(ctx, logger, mustRequestBatch) }) + return pg.Wait() + }) +} + +func (s *PeggyOrchestrator) requestBatches(ctx context.Context, logger log.Logger, mustRequest bool) error { + unbatchedTokensWithFees, err := s.getBatchFeesByToken(ctx, logger) + if err != nil { + // non-fatal, just alert + logger.Warningln("unable to get UnbatchedTokensWithFees for the token") + return nil + } + + if len(unbatchedTokensWithFees) == 0 { + logger.Debugln("No outgoing withdraw tx or Unbatched token fee less than threshold") + return nil + } + + logger.WithField("unbatchedTokensWithFees", unbatchedTokensWithFees).Debugln("Check if token fees meets set threshold amount and send batch request") + for _, unbatchedToken := range unbatchedTokensWithFees { + // check if the token is present in cosmos denom. if so, send batch request with cosmosDenom + tokenAddr := ethcmn.HexToAddress(unbatchedToken.Token) + denom := s.getTokenDenom(tokenAddr) + + thresholdMet := s.CheckFeeThreshold(tokenAddr, unbatchedToken.TotalFees, s.minBatchFeeUSD) + if !thresholdMet && !mustRequest { + // non injective relayers only relay when the threshold is met + continue + } + + logger.WithFields(log.Fields{"tokenContract": tokenAddr, "denom": denom}).Infoln("sending batch request") + _ = s.peggyBroadcastClient.SendRequestBatch(ctx, denom) + } + + return nil +} + +func (s *PeggyOrchestrator) getBatchFeesByToken(ctx context.Context, log log.Logger) ([]*types.BatchFees, error) { + var unbatchedTokensWithFees []*types.BatchFees + retryFn := func() error { + fees, err := s.injective.UnbatchedTokenFees(ctx) + if err != nil { + return err + } + + unbatchedTokensWithFees = fees + return nil + } + + if err := retry.Do( + retryFn, + retry.Context(ctx), + retry.OnRetry(func(n uint, err error) { + log.WithError(err).Errorf("failed to get UnbatchedTokensWithFees, will retry (%d)", n) + }), + ); err != nil { + return nil, err + } + + return unbatchedTokensWithFees, nil +} + +func (s *PeggyOrchestrator) getTokenDenom(tokenAddr ethcmn.Address) string { + if cosmosDenom, ok := s.erc20ContractMapping[tokenAddr]; ok { + return cosmosDenom + } + + // peggy denom + return types.PeggyDenomString(tokenAddr) +} + +func (s *PeggyOrchestrator) CheckFeeThreshold(erc20Contract ethcmn.Address, totalFee cosmtypes.Int, minFeeInUSD float64) bool { + if minFeeInUSD == 0 { + return true + } + + tokenPriceInUSD, err := s.priceFeeder.QueryUSDPrice(erc20Contract) + if err != nil { + return false + } + + tokenPriceInUSDDec := decimal.NewFromFloat(tokenPriceInUSD) + totalFeeInUSDDec := decimal.NewFromBigInt(totalFee.BigInt(), -18).Mul(tokenPriceInUSDDec) + minFeeInUSDDec := decimal.NewFromFloat(minFeeInUSD) + + if totalFeeInUSDDec.GreaterThan(minFeeInUSDDec) { + return true + } + return false +} diff --git a/orchestrator/batch_request_test.go b/orchestrator/batch_request_test.go new file mode 100644 index 00000000..bbcf65e1 --- /dev/null +++ b/orchestrator/batch_request_test.go @@ -0,0 +1,33 @@ +package orchestrator + +import ( + "context" + peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/xlab/suplog" + "testing" +) + +type mockInjective struct { +} + +func (i mockInjective) UnbatchedTokenFees(_ context.Context) ([]*peggytypes.BatchFees, error) { + return nil, errors.New("fail") +} + +func (i mockInjective) SendRequestBatch(_ context.Context, _ string) error { + return nil +} + +func TestRequestBatches(t *testing.T) { + inj := mockInjective{} + + orch := &PeggyOrchestrator{ + injective: inj, + } + + _ = orch + + assert.Nil(t, orch.requestBatches(context.Background(), suplog.DefaultLogger, false)) +} diff --git a/orchestrator/main_loops.go b/orchestrator/main_loops.go index 303cbfa2..845617ce 100644 --- a/orchestrator/main_loops.go +++ b/orchestrator/main_loops.go @@ -9,16 +9,12 @@ import ( "github.com/avast/retry-go" "github.com/ethereum/go-ethereum/common" - "github.com/shopspring/decimal" log "github.com/xlab/suplog" "github.com/InjectiveLabs/sdk-go/chain/peggy/types" "github.com/InjectiveLabs/peggo/orchestrator/cosmos" "github.com/InjectiveLabs/peggo/orchestrator/loops" - - cosmtypes "github.com/cosmos/cosmos-sdk/types" - ethcmn "github.com/ethereum/go-ethereum/common" ) const defaultLoopDur = 60 * time.Second @@ -259,107 +255,6 @@ func (s *PeggyOrchestrator) ValsetRequesterLoop(ctx context.Context) (err error) } **/ -func (s *PeggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) { - logger := log.WithField("loop", "BatchRequesterLoop") - startTime := time.Now() - - // we're the only ones relaying - isInjectiveRelayer := s.periodicBatchRequesting - - return loops.RunLoop(ctx, defaultLoopDur, func() error { - mustRequestBatch := false - if isInjectiveRelayer && time.Since(startTime) > time.Hour*8 { - mustRequestBatch = true - } - - var pg loops.ParanoidGroup - pg.Go(func() error { return s.requestBatches(ctx, logger, mustRequestBatch) }) - return pg.Wait() - }) -} - -func (s *PeggyOrchestrator) getBatchFeesByToken(ctx context.Context, log log.Logger) ([]*types.BatchFees, error) { - var unbatchedTokensWithFees []*types.BatchFees - if err := retry.Do(func() error { - fees, err := s.cosmosQueryClient.UnbatchedTokensWithFees(ctx) - if err != nil { - return err - } - - unbatchedTokensWithFees = fees - return nil - }, retry.Context(ctx), - retry.OnRetry(func(n uint, err error) { - log.WithError(err).Errorf("failed to get UnbatchedTokensWithFees, will retry (%d)", n) - }), - ); err != nil { - return nil, err - } - - return unbatchedTokensWithFees, nil -} - -func (s *PeggyOrchestrator) getTokenDenom(tokenAddr common.Address) string { - if cosmosDenom, ok := s.erc20ContractMapping[tokenAddr]; ok { - return cosmosDenom - } - - // peggy denom - return types.PeggyDenomString(tokenAddr) -} - -func (s *PeggyOrchestrator) requestBatches(ctx context.Context, logger log.Logger, mustRequest bool) error { - unbatchedTokensWithFees, err := s.getBatchFeesByToken(ctx, logger) - if err != nil { - // non-fatal, just alert - logger.Warningln("unable to get UnbatchedTokensWithFees for the token") - return nil - } - - if len(unbatchedTokensWithFees) == 0 { - logger.Debugln("No outgoing withdraw tx or Unbatched token fee less than threshold") - return nil - } - - logger.WithField("unbatchedTokensWithFees", unbatchedTokensWithFees).Debugln("Check if token fees meets set threshold amount and send batch request") - for _, unbatchedToken := range unbatchedTokensWithFees { - // check if the token is present in cosmos denom. if so, send batch request with cosmosDenom - tokenAddr := ethcmn.HexToAddress(unbatchedToken.Token) - denom := s.getTokenDenom(tokenAddr) - - thresholdMet := s.CheckFeeThreshold(tokenAddr, unbatchedToken.TotalFees, s.minBatchFeeUSD) - if !thresholdMet && !mustRequest { - // non injective relayers only relay when the threshold is met - continue - } - - logger.WithFields(log.Fields{"tokenContract": tokenAddr, "denom": denom}).Infoln("sending batch request") - _ = s.peggyBroadcastClient.SendRequestBatch(ctx, denom) - } - - return nil -} - -func (s *PeggyOrchestrator) CheckFeeThreshold(erc20Contract common.Address, totalFee cosmtypes.Int, minFeeInUSD float64) bool { - if minFeeInUSD == 0 { - return true - } - - tokenPriceInUSD, err := s.priceFeeder.QueryUSDPrice(erc20Contract) - if err != nil { - return false - } - - tokenPriceInUSDDec := decimal.NewFromFloat(tokenPriceInUSD) - totalFeeInUSDDec := decimal.NewFromBigInt(totalFee.BigInt(), -18).Mul(tokenPriceInUSDDec) - minFeeInUSDDec := decimal.NewFromFloat(minFeeInUSD) - - if totalFeeInUSDDec.GreaterThan(minFeeInUSDDec) { - return true - } - return false -} - func (s *PeggyOrchestrator) RelayerMainLoop(ctx context.Context) (err error) { if s.relayer != nil { return s.relayer.Start(ctx) diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index 673668b9..9d44558a 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -2,6 +2,7 @@ package orchestrator import ( "context" + peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" "github.com/ethereum/go-ethereum/core/types" "math/big" @@ -15,7 +16,13 @@ import ( "github.com/InjectiveLabs/peggo/orchestrator/relayer" ) -type Injective interface { +type PriceFeed interface { + QueryUSDPrice(address ethcmn.Address) (float64, error) +} + +type InjectiveNetwork interface { + UnbatchedTokenFees(ctx context.Context) ([]*peggytypes.BatchFees, error) + SendRequestBatch(ctx context.Context, denom string) error } type EthereumNetwork interface { @@ -23,8 +30,10 @@ type EthereumNetwork interface { } type PeggyOrchestrator struct { - svcTags metrics.Tags - ethereum EthereumNetwork + svcTags metrics.Tags + pricefeed PriceFeed + injective InjectiveNetwork + ethereum EthereumNetwork cosmosQueryClient sidechain.PeggyQueryClient peggyBroadcastClient sidechain.PeggyBroadcastClient From 379d8ee64833933b8b8a1eee7e04fd81a4779652 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Thu, 8 Jun 2023 14:53:33 +0200 Subject: [PATCH 12/72] write additional test cases --- orchestrator/batch_request.go | 6 +- orchestrator/batch_request_test.go | 125 ++++++++++++++++++++++++++--- 2 files changed, 115 insertions(+), 16 deletions(-) diff --git a/orchestrator/batch_request.go b/orchestrator/batch_request.go index d378d0f0..8567de6d 100644 --- a/orchestrator/batch_request.go +++ b/orchestrator/batch_request.go @@ -50,7 +50,6 @@ func (s *PeggyOrchestrator) requestBatches(ctx context.Context, logger log.Logge for _, unbatchedToken := range unbatchedTokensWithFees { // check if the token is present in cosmos denom. if so, send batch request with cosmosDenom tokenAddr := ethcmn.HexToAddress(unbatchedToken.Token) - denom := s.getTokenDenom(tokenAddr) thresholdMet := s.CheckFeeThreshold(tokenAddr, unbatchedToken.TotalFees, s.minBatchFeeUSD) if !thresholdMet && !mustRequest { @@ -58,8 +57,9 @@ func (s *PeggyOrchestrator) requestBatches(ctx context.Context, logger log.Logge continue } + denom := s.getTokenDenom(tokenAddr) logger.WithFields(log.Fields{"tokenContract": tokenAddr, "denom": denom}).Infoln("sending batch request") - _ = s.peggyBroadcastClient.SendRequestBatch(ctx, denom) + _ = s.injective.SendRequestBatch(ctx, denom) } return nil @@ -104,7 +104,7 @@ func (s *PeggyOrchestrator) CheckFeeThreshold(erc20Contract ethcmn.Address, tota return true } - tokenPriceInUSD, err := s.priceFeeder.QueryUSDPrice(erc20Contract) + tokenPriceInUSD, err := s.pricefeed.QueryUSDPrice(erc20Contract) if err != nil { return false } diff --git a/orchestrator/batch_request_test.go b/orchestrator/batch_request_test.go index bbcf65e1..4e8649c0 100644 --- a/orchestrator/batch_request_test.go +++ b/orchestrator/batch_request_test.go @@ -2,32 +2,131 @@ package orchestrator import ( "context" - peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" - "github.com/pkg/errors" + "errors" + "testing" + + ethcmn "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/xlab/suplog" - "testing" + + peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + cosmtypes "github.com/cosmos/cosmos-sdk/types" ) +type mockPriceFeed struct { + queryFn func(ethcmn.Address) (float64, error) +} + +func (p mockPriceFeed) QueryUSDPrice(address ethcmn.Address) (float64, error) { + return p.queryFn(address) +} + type mockInjective struct { + unbatchedTokenFeesFn func(context.Context) ([]*peggytypes.BatchFees, error) + unbatchedTokenFeesCallCount int + sendRequestBatchFn func(context.Context, string) error + sendRequestBatchCallCount int } -func (i mockInjective) UnbatchedTokenFees(_ context.Context) ([]*peggytypes.BatchFees, error) { - return nil, errors.New("fail") +func (i *mockInjective) UnbatchedTokenFees(ctx context.Context) ([]*peggytypes.BatchFees, error) { + i.unbatchedTokenFeesCallCount++ + return i.unbatchedTokenFeesFn(ctx) } -func (i mockInjective) SendRequestBatch(_ context.Context, _ string) error { - return nil +func (i *mockInjective) SendRequestBatch(ctx context.Context, denom string) error { + i.sendRequestBatchCallCount++ + return i.sendRequestBatchFn(ctx, denom) } func TestRequestBatches(t *testing.T) { - inj := mockInjective{} + t.Parallel() + + t.Run("UnbatchedTokenFees call fails", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + unbatchedTokenFeesFn: func(_ context.Context) ([]*peggytypes.BatchFees, error) { + return nil, errors.New("fail") + }, + }, + } + + assert.Nil(t, orch.requestBatches(context.TODO(), suplog.DefaultLogger, false)) + }) + + t.Run("no unbatched tokens", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + unbatchedTokenFeesFn: func(_ context.Context) ([]*peggytypes.BatchFees, error) { + return nil, nil + }, + }, + } + + assert.Nil(t, orch.requestBatches(context.TODO(), suplog.DefaultLogger, false)) + }) + + t.Run("batch does not meet fee threshold", func(t *testing.T) { + t.Parallel() + + tokenAddr := ethcmn.HexToAddress("0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30") + + injective := &mockInjective{ + sendRequestBatchFn: func(context.Context, string) error { return nil }, + unbatchedTokenFeesFn: func(_ context.Context) ([]*peggytypes.BatchFees, error) { + fees, _ := cosmtypes.NewIntFromString("50000000000000000000") + return []*peggytypes.BatchFees{ + { + Token: tokenAddr.String(), + TotalFees: fees, + }, + }, nil + }, + } + + orch := &PeggyOrchestrator{ + minBatchFeeUSD: 51.0, + erc20ContractMapping: map[ethcmn.Address]string{tokenAddr: "inj"}, + pricefeed: mockPriceFeed{queryFn: func(_ ethcmn.Address) (float64, error) { return 1, nil }}, + injective: injective, + } + + assert.Nil(t, orch.requestBatches(context.TODO(), suplog.DefaultLogger, false)) + assert.Equal(t, injective.sendRequestBatchCallCount, 0) + }) + + t.Run("batch meets threshold and a request is sent", func(t *testing.T) { + t.Parallel() + + tokenAddr := ethcmn.HexToAddress("0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30") + + injective := &mockInjective{ + sendRequestBatchFn: func(context.Context, string) error { return nil }, + unbatchedTokenFeesFn: func(_ context.Context) ([]*peggytypes.BatchFees, error) { + fees, _ := cosmtypes.NewIntFromString("50000000000000000000") + return []*peggytypes.BatchFees{ + { + Token: tokenAddr.String(), + TotalFees: fees, + }, + }, nil + }, + } - orch := &PeggyOrchestrator{ - injective: inj, - } + orch := &PeggyOrchestrator{ + minBatchFeeUSD: 49.0, + erc20ContractMapping: map[ethcmn.Address]string{tokenAddr: "inj"}, + pricefeed: mockPriceFeed{queryFn: func(_ ethcmn.Address) (float64, error) { + return 1, nil + }}, + injective: injective, + } - _ = orch + assert.Nil(t, orch.requestBatches(context.TODO(), suplog.DefaultLogger, false)) + assert.Equal(t, injective.sendRequestBatchCallCount, 1) + }) - assert.Nil(t, orch.requestBatches(context.Background(), suplog.DefaultLogger, false)) } From 2c49e182f95fb858fa2cf6dc424683bfd2233e1d Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Thu, 8 Jun 2023 19:30:20 +0200 Subject: [PATCH 13/72] move oracle loop to own file --- orchestrator/main_loops.go | 68 ----------------- .../{eth_event_watcher.go => oracle.go} | 76 ++++++++++++++++++- 2 files changed, 74 insertions(+), 70 deletions(-) rename orchestrator/{eth_event_watcher.go => oracle.go} (77%) diff --git a/orchestrator/main_loops.go b/orchestrator/main_loops.go index 845617ce..77366967 100644 --- a/orchestrator/main_loops.go +++ b/orchestrator/main_loops.go @@ -31,74 +31,6 @@ func (s *PeggyOrchestrator) Start(ctx context.Context, validatorMode bool) error return s.startValidatorMode(ctx) } -// EthOracleMainLoop is responsible for making sure that Ethereum events are retrieved from the Ethereum blockchain -// and ferried over to Cosmos where they will be used to issue tokens or process batches. -// -// TODO this loop requires a method to bootstrap back to the correct event nonce when restarted -func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) (err error) { - logger := log.WithField("loop", "EthOracleMainLoop") - lastResync := time.Now() - var lastCheckedBlock uint64 - - if err := retry.Do(func() (err error) { - lastCheckedBlock, err = s.GetLastCheckedBlock(ctx) - if lastCheckedBlock == 0 { - peggyParams, err := s.cosmosQueryClient.PeggyParams(ctx) - if err != nil { - log.WithError(err).Fatalln("failed to query peggy params, is injectived running?") - } - lastCheckedBlock = peggyParams.BridgeContractStartHeight - } - return - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get last checked block, will retry (%d)", n) - })); err != nil { - logger.WithError(err).Errorln("got error, loop exits") - return err - } - - logger.WithField("lastCheckedBlock", lastCheckedBlock).Infoln("Start scanning for events") - - return loops.RunLoop(ctx, defaultLoopDur, func() error { - // Relays events from Ethereum -> Cosmos - var currentBlock uint64 - if err := retry.Do(func() (err error) { - currentBlock, err = s.CheckForEvents(ctx, lastCheckedBlock) - return - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("error during Eth event checking, will retry (%d)", n) - })); err != nil { - logger.WithError(err).Errorln("got error, loop exits") - return err - } - - lastCheckedBlock = currentBlock - - /* - Auto re-sync to catch up the nonce. Reasons why event nonce fall behind. - 1. It takes some time for events to be indexed on Ethereum. So if peggo queried events immediately as block produced, there is a chance the event is missed. - we need to re-scan this block to ensure events are not missed due to indexing delay. - 2. if validator was in UnBonding state, the claims broadcasted in last iteration are failed. - 3. if infura call failed while filtering events, the peggo missed to broadcast claim events occured in last iteration. - **/ - if time.Since(lastResync) >= 48*time.Hour { - if err := retry.Do(func() (err error) { - lastCheckedBlock, err = s.GetLastCheckedBlock(ctx) - return - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get last checked block, will retry (%d)", n) - })); err != nil { - logger.WithError(err).Errorln("got error, loop exits") - return err - } - lastResync = time.Now() - logger.WithFields(log.Fields{"lastResync": lastResync, "lastCheckedBlock": lastCheckedBlock}).Infoln("Auto resync") - } - - return nil - }) -} - // EthSignerMainLoop simply signs off on any batches or validator sets provided by the validator // since these are provided directly by a trusted Cosmsos node they can simply be assumed to be // valid and signed off on. diff --git a/orchestrator/eth_event_watcher.go b/orchestrator/oracle.go similarity index 77% rename from orchestrator/eth_event_watcher.go rename to orchestrator/oracle.go index 0de91232..7df62d68 100644 --- a/orchestrator/eth_event_watcher.go +++ b/orchestrator/oracle.go @@ -2,7 +2,10 @@ package orchestrator import ( "context" + "github.com/InjectiveLabs/peggo/orchestrator/loops" + "github.com/avast/retry-go" "strings" + "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/pkg/errors" @@ -16,9 +19,78 @@ import ( // Considering blocktime of up to 3 seconds approx on the Injective Chain and an oracle loop duration = 1 minute, // we broadcast only 20 events in each iteration. // So better to search only 20 blocks to ensure all the events are broadcast to Injective Chain without misses. -const defaultBlocksToSearch = 20 +const ( + ethBlockConfirmationDelay = 12 + defaultBlocksToSearch = 20 +) + +// EthOracleMainLoop is responsible for making sure that Ethereum events are retrieved from the Ethereum blockchain +// and ferried over to Cosmos where they will be used to issue tokens or process batches. +// +// TODO this loop requires a method to bootstrap back to the correct event nonce when restarted +func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) (err error) { + logger := log.WithField("loop", "EthOracleMainLoop") + lastResync := time.Now() + var lastCheckedBlock uint64 + + if err := retry.Do(func() (err error) { + lastCheckedBlock, err = s.GetLastCheckedBlock(ctx) + if lastCheckedBlock == 0 { + peggyParams, err := s.cosmosQueryClient.PeggyParams(ctx) + if err != nil { + log.WithError(err).Fatalln("failed to query peggy params, is injectived running?") + } + lastCheckedBlock = peggyParams.BridgeContractStartHeight + } + return + }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { + logger.WithError(err).Warningf("failed to get last checked block, will retry (%d)", n) + })); err != nil { + logger.WithError(err).Errorln("got error, loop exits") + return err + } -const ethBlockConfirmationDelay = 12 + logger.WithField("lastCheckedBlock", lastCheckedBlock).Infoln("Start scanning for events") + + return loops.RunLoop(ctx, defaultLoopDur, func() error { + // Relays events from Ethereum -> Cosmos + var currentBlock uint64 + if err := retry.Do(func() (err error) { + currentBlock, err = s.CheckForEvents(ctx, lastCheckedBlock) + return + }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { + logger.WithError(err).Warningf("error during Eth event checking, will retry (%d)", n) + })); err != nil { + logger.WithError(err).Errorln("got error, loop exits") + return err + } + + lastCheckedBlock = currentBlock + + /* + Auto re-sync to catch up the nonce. Reasons why event nonce fall behind. + 1. It takes some time for events to be indexed on Ethereum. So if peggo queried events immediately as block produced, there is a chance the event is missed. + we need to re-scan this block to ensure events are not missed due to indexing delay. + 2. if validator was in UnBonding state, the claims broadcasted in last iteration are failed. + 3. if infura call failed while filtering events, the peggo missed to broadcast claim events occured in last iteration. + **/ + if time.Since(lastResync) >= 48*time.Hour { + if err := retry.Do(func() (err error) { + lastCheckedBlock, err = s.GetLastCheckedBlock(ctx) + return + }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { + logger.WithError(err).Warningf("failed to get last checked block, will retry (%d)", n) + })); err != nil { + logger.WithError(err).Errorln("got error, loop exits") + return err + } + lastResync = time.Now() + logger.WithFields(log.Fields{"lastResync": lastResync, "lastCheckedBlock": lastCheckedBlock}).Infoln("Auto resync") + } + + return nil + }) +} // CheckForEvents checks for events such as a deposit to the Peggy Ethereum contract or a validator set update // or a transaction batch update. It then responds to these events by performing actions on the Cosmos chain if required From 1f5ce93c919f480ad10e3a4cd9bb7377e2da7993 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Fri, 9 Jun 2023 13:11:18 +0200 Subject: [PATCH 14/72] abstract methods for oracle --- orchestrator/oracle.go | 30 +++++++++++++++++++++--------- orchestrator/oracle_resync.go | 21 --------------------- orchestrator/oracle_test.go | 1 + orchestrator/orchestrator.go | 20 ++++++++++++++++++++ 4 files changed, 42 insertions(+), 30 deletions(-) delete mode 100644 orchestrator/oracle_resync.go create mode 100644 orchestrator/oracle_test.go diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index 7df62d68..b3bc80aa 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -26,15 +26,13 @@ const ( // EthOracleMainLoop is responsible for making sure that Ethereum events are retrieved from the Ethereum blockchain // and ferried over to Cosmos where they will be used to issue tokens or process batches. -// -// TODO this loop requires a method to bootstrap back to the correct event nonce when restarted -func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) (err error) { +func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { logger := log.WithField("loop", "EthOracleMainLoop") lastResync := time.Now() var lastCheckedBlock uint64 if err := retry.Do(func() (err error) { - lastCheckedBlock, err = s.GetLastCheckedBlock(ctx) + lastCheckedBlock, err = s.getLastKnownEthHeight(ctx) if lastCheckedBlock == 0 { peggyParams, err := s.cosmosQueryClient.PeggyParams(ctx) if err != nil { @@ -51,12 +49,11 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) (err error) { } logger.WithField("lastCheckedBlock", lastCheckedBlock).Infoln("Start scanning for events") - return loops.RunLoop(ctx, defaultLoopDur, func() error { // Relays events from Ethereum -> Cosmos var currentBlock uint64 if err := retry.Do(func() (err error) { - currentBlock, err = s.CheckForEvents(ctx, lastCheckedBlock) + currentBlock, err = s.relayEthEvents(ctx, lastCheckedBlock) return }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { logger.WithError(err).Warningf("error during Eth event checking, will retry (%d)", n) @@ -76,7 +73,7 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) (err error) { **/ if time.Since(lastResync) >= 48*time.Hour { if err := retry.Do(func() (err error) { - lastCheckedBlock, err = s.GetLastCheckedBlock(ctx) + lastCheckedBlock, err = s.getLastKnownEthHeight(ctx) return }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { logger.WithError(err).Warningf("failed to get last checked block, will retry (%d)", n) @@ -92,9 +89,24 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) (err error) { }) } -// CheckForEvents checks for events such as a deposit to the Peggy Ethereum contract or a validator set update +// getLastKnownEthHeight retrieves the last claim event this oracle has relayed to Cosmos. +func (s *PeggyOrchestrator) getLastKnownEthHeight(ctx context.Context) (uint64, error) { + metrics.ReportFuncCall(s.svcTags) + doneFn := metrics.ReportFuncTiming(s.svcTags) + defer doneFn() + + lastClaimEvent, err := s.cosmosQueryClient.LastClaimEventByAddr(ctx, s.peggyBroadcastClient.AccFromAddress()) + if err != nil { + metrics.ReportFuncError(s.svcTags) + return uint64(0), err + } + + return lastClaimEvent.EthereumEventHeight, nil +} + +// relayEthEvents checks for events such as a deposit to the Peggy Ethereum contract or a validator set update // or a transaction batch update. It then responds to these events by performing actions on the Cosmos chain if required -func (s *PeggyOrchestrator) CheckForEvents( +func (s *PeggyOrchestrator) relayEthEvents( ctx context.Context, startingBlock uint64, ) (currentBlock uint64, err error) { diff --git a/orchestrator/oracle_resync.go b/orchestrator/oracle_resync.go deleted file mode 100644 index 58ec24b2..00000000 --- a/orchestrator/oracle_resync.go +++ /dev/null @@ -1,21 +0,0 @@ -package orchestrator - -import ( - "context" - "github.com/InjectiveLabs/metrics" -) - -// GetLastCheckedBlock retrieves the last claim event this oracle has relayed to Cosmos. -func (s *PeggyOrchestrator) GetLastCheckedBlock(ctx context.Context) (uint64, error) { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - lastClaimEvent, err := s.cosmosQueryClient.LastClaimEventByAddr(ctx, s.peggyBroadcastClient.AccFromAddress()) - if err != nil { - metrics.ReportFuncError(s.svcTags) - return uint64(0), err - } - - return lastClaimEvent.EthereumEventHeight, nil -} diff --git a/orchestrator/oracle_test.go b/orchestrator/oracle_test.go new file mode 100644 index 00000000..a8b18adb --- /dev/null +++ b/orchestrator/oracle_test.go @@ -0,0 +1 @@ +package orchestrator diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index 9d44558a..60e8270e 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -2,6 +2,7 @@ package orchestrator import ( "context" + peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" "github.com/ethereum/go-ethereum/core/types" "math/big" @@ -21,12 +22,31 @@ type PriceFeed interface { } type InjectiveNetwork interface { + PeggyParams(ctx context.Context) (*peggytypes.Params, error) + UnbatchedTokenFees(ctx context.Context) ([]*peggytypes.BatchFees, error) SendRequestBatch(ctx context.Context, denom string) error + + LastClaimEvent(ctx context.Context) (*peggytypes.LastClaimEvent, error) + SendEthereumClaims( + ctx context.Context, + lastClaimEvent uint64, + oldDeposits []*peggyevents.PeggySendToCosmosEvent, + deposits []*peggyevents.PeggySendToInjectiveEvent, + withdraws []*peggyevents.PeggyTransactionBatchExecutedEvent, + erc20Deployed []*peggyevents.PeggyERC20DeployedEvent, + valsetUpdates []*peggyevents.PeggyValsetUpdatedEvent, + ) error } type EthereumNetwork interface { HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) + + GetSendToCosmosEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) + GetSendToInjectiveEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) + GetPeggyERC20DeployedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyERC20DeployedEvent, error) + GetValsetUpdatedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyValsetUpdatedEvent, error) + GetTransactionBatchExecutedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) } type PeggyOrchestrator struct { From bd522e3a248511508a20baa01b01ec5818e66cf9 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Fri, 9 Jun 2023 16:11:02 +0200 Subject: [PATCH 15/72] add oracle tests --- orchestrator/batch_request_test.go | 25 --- orchestrator/mocks_test.go | 114 ++++++++++ orchestrator/oracle.go | 340 ++++++++++++++++------------- orchestrator/oracle_test.go | 195 +++++++++++++++++ 4 files changed, 495 insertions(+), 179 deletions(-) create mode 100644 orchestrator/mocks_test.go diff --git a/orchestrator/batch_request_test.go b/orchestrator/batch_request_test.go index 4e8649c0..797e7b0c 100644 --- a/orchestrator/batch_request_test.go +++ b/orchestrator/batch_request_test.go @@ -13,31 +13,6 @@ import ( cosmtypes "github.com/cosmos/cosmos-sdk/types" ) -type mockPriceFeed struct { - queryFn func(ethcmn.Address) (float64, error) -} - -func (p mockPriceFeed) QueryUSDPrice(address ethcmn.Address) (float64, error) { - return p.queryFn(address) -} - -type mockInjective struct { - unbatchedTokenFeesFn func(context.Context) ([]*peggytypes.BatchFees, error) - unbatchedTokenFeesCallCount int - sendRequestBatchFn func(context.Context, string) error - sendRequestBatchCallCount int -} - -func (i *mockInjective) UnbatchedTokenFees(ctx context.Context) ([]*peggytypes.BatchFees, error) { - i.unbatchedTokenFeesCallCount++ - return i.unbatchedTokenFeesFn(ctx) -} - -func (i *mockInjective) SendRequestBatch(ctx context.Context, denom string) error { - i.sendRequestBatchCallCount++ - return i.sendRequestBatchFn(ctx, denom) -} - func TestRequestBatches(t *testing.T) { t.Parallel() diff --git a/orchestrator/mocks_test.go b/orchestrator/mocks_test.go new file mode 100644 index 00000000..110d2b04 --- /dev/null +++ b/orchestrator/mocks_test.go @@ -0,0 +1,114 @@ +package orchestrator + +import ( + "context" + peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" + eth "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "math/big" + + peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" +) + +type mockPriceFeed struct { + queryFn func(eth.Address) (float64, error) +} + +func (p mockPriceFeed) QueryUSDPrice(address eth.Address) (float64, error) { + return p.queryFn(address) +} + +type mockInjective struct { + unbatchedTokenFeesFn func(context.Context) ([]*peggytypes.BatchFees, error) + unbatchedTokenFeesCallCount int + + sendRequestBatchFn func(context.Context, string) error + sendRequestBatchCallCount int + + peggyParamsFn func(context.Context) (*peggytypes.Params, error) + + lastClaimEventFn func(context.Context) (*peggytypes.LastClaimEvent, error) + + sendEthereumClaimsFn func( + ctx context.Context, + lastClaimEvent uint64, + oldDeposits []*peggyevents.PeggySendToCosmosEvent, + deposits []*peggyevents.PeggySendToInjectiveEvent, + withdraws []*peggyevents.PeggyTransactionBatchExecutedEvent, + erc20Deployed []*peggyevents.PeggyERC20DeployedEvent, + valsetUpdates []*peggyevents.PeggyValsetUpdatedEvent, + ) error + sendEthereumClaimsCallCount int +} + +func (i *mockInjective) UnbatchedTokenFees(ctx context.Context) ([]*peggytypes.BatchFees, error) { + i.unbatchedTokenFeesCallCount++ + return i.unbatchedTokenFeesFn(ctx) +} + +func (i *mockInjective) SendRequestBatch(ctx context.Context, denom string) error { + i.sendRequestBatchCallCount++ + return i.sendRequestBatchFn(ctx, denom) +} + +func (i *mockInjective) PeggyParams(ctx context.Context) (*peggytypes.Params, error) { + return i.peggyParamsFn(ctx) +} + +func (i *mockInjective) LastClaimEvent(ctx context.Context) (*peggytypes.LastClaimEvent, error) { + return i.lastClaimEventFn(ctx) +} + +func (i *mockInjective) SendEthereumClaims( + ctx context.Context, + lastClaimEvent uint64, + oldDeposits []*peggyevents.PeggySendToCosmosEvent, + deposits []*peggyevents.PeggySendToInjectiveEvent, + withdraws []*peggyevents.PeggyTransactionBatchExecutedEvent, + erc20Deployed []*peggyevents.PeggyERC20DeployedEvent, + valsetUpdates []*peggyevents.PeggyValsetUpdatedEvent, +) error { + i.sendEthereumClaimsCallCount++ + return i.sendEthereumClaimsFn( + ctx, + lastClaimEvent, + oldDeposits, + deposits, + withdraws, + erc20Deployed, + valsetUpdates, + ) +} + +type mockEthereum struct { + headerByNumberFn func(context.Context, *big.Int) (*ethtypes.Header, error) + getSendToCosmosEventsFn func(uint64, uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) + getSendToInjectiveEventsFn func(uint64, uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) + getPeggyERC20DeployedEventsFn func(uint64, uint64) ([]*peggyevents.PeggyERC20DeployedEvent, error) + getValsetUpdatedEventsFn func(uint64, uint64) ([]*peggyevents.PeggyValsetUpdatedEvent, error) + getTransactionBatchExecutedEventsFn func(uint64, uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) +} + +func (e mockEthereum) HeaderByNumber(ctx context.Context, number *big.Int) (*ethtypes.Header, error) { + return e.headerByNumberFn(ctx, number) +} + +func (e mockEthereum) GetSendToCosmosEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) { + return e.getSendToCosmosEventsFn(startBlock, endBlock) +} + +func (e mockEthereum) GetSendToInjectiveEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) { + return e.getSendToInjectiveEventsFn(startBlock, endBlock) +} + +func (e mockEthereum) GetPeggyERC20DeployedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyERC20DeployedEvent, error) { + return e.getPeggyERC20DeployedEventsFn(startBlock, endBlock) +} + +func (e mockEthereum) GetValsetUpdatedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyValsetUpdatedEvent, error) { + return e.getValsetUpdatedEventsFn(startBlock, endBlock) +} + +func (e mockEthereum) GetTransactionBatchExecutedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) { + return e.getTransactionBatchExecutedEventsFn(startBlock, endBlock) +} diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index b3bc80aa..dbffd425 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -7,7 +7,6 @@ import ( "strings" "time" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/pkg/errors" log "github.com/xlab/suplog" @@ -20,8 +19,8 @@ import ( // we broadcast only 20 events in each iteration. // So better to search only 20 blocks to ensure all the events are broadcast to Injective Chain without misses. const ( - ethBlockConfirmationDelay = 12 - defaultBlocksToSearch = 20 + ethBlockConfirmationDelay uint64 = 12 + defaultBlocksToSearch uint64 = 20 ) // EthOracleMainLoop is responsible for making sure that Ethereum events are retrieved from the Ethereum blockchain @@ -109,12 +108,12 @@ func (s *PeggyOrchestrator) getLastKnownEthHeight(ctx context.Context) (uint64, func (s *PeggyOrchestrator) relayEthEvents( ctx context.Context, startingBlock uint64, -) (currentBlock uint64, err error) { +) (uint64, error) { metrics.ReportFuncCall(s.svcTags) doneFn := metrics.ReportFuncTiming(s.svcTags) defer doneFn() - latestHeader, err := s.ethProvider.HeaderByNumber(ctx, nil) + latestHeader, err := s.ethereum.HeaderByNumber(ctx, nil) if err != nil { metrics.ReportFuncError(s.svcTags) err = errors.Wrap(err, "failed to get latest header") @@ -122,191 +121,224 @@ func (s *PeggyOrchestrator) relayEthEvents( } // add delay to ensure minimum confirmations are received and block is finalised - currentBlock = latestHeader.Number.Uint64() - uint64(ethBlockConfirmationDelay) + currentBlock := latestHeader.Number.Uint64() - ethBlockConfirmationDelay if currentBlock < startingBlock { return currentBlock, nil } - if (currentBlock - startingBlock) > defaultBlocksToSearch { + if currentBlock > defaultBlocksToSearch+startingBlock { currentBlock = startingBlock + defaultBlocksToSearch } - peggyFilterer, err := wrappers.NewPeggyFilterer(s.peggyContract.Address(), s.ethProvider) + // todo: this will be part of each Get**Events method + //peggyFilterer, err := wrappers.NewPeggyFilterer(s.peggyContract.Address(), s.ethProvider) + //if err != nil { + // metrics.ReportFuncError(s.svcTags) + // err = errors.Wrap(err, "failed to init Peggy events filterer") + // return 0, err + //} + + // todo + legacyDeposits, err := s.ethereum.GetSendToCosmosEvents(startingBlock, currentBlock) if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to init Peggy events filterer") return 0, err } - var sendToCosmosEvents []*wrappers.PeggySendToCosmosEvent - { - - iter, err := peggyFilterer.FilterSendToCosmosEvent(&bind.FilterOpts{ - Start: startingBlock, - End: ¤tBlock, - }, nil, nil, nil) - if err != nil { - metrics.ReportFuncError(s.svcTags) - log.WithFields(log.Fields{ - "start": startingBlock, - "end": currentBlock, - }).Errorln("failed to scan past SendToCosmos events from Ethereum") - - if !isUnknownBlockErr(err) { - err = errors.Wrap(err, "failed to scan past SendToCosmos events from Ethereum") - return 0, err - } else if iter == nil { - return 0, errors.New("no iterator returned") - } - } - - for iter.Next() { - sendToCosmosEvents = append(sendToCosmosEvents, iter.Event) - } - - iter.Close() - } + //var sendToCosmosEvents []*wrappers.PeggySendToCosmosEvent + //{ + // + // iter, err := peggyFilterer.FilterSendToCosmosEvent(&bind.FilterOpts{ + // Start: startingBlock, + // End: ¤tBlock, + // }, nil, nil, nil) + // if err != nil { + // metrics.ReportFuncError(s.svcTags) + // log.WithFields(log.Fields{ + // "start": startingBlock, + // "end": currentBlock, + // }).Errorln("failed to scan past SendToCosmos events from Ethereum") + // + // if !isUnknownBlockErr(err) { + // err = errors.Wrap(err, "failed to scan past SendToCosmos events from Ethereum") + // return 0, err + // } else if iter == nil { + // return 0, errors.New("no iterator returned") + // } + // } + // + // for iter.Next() { + // sendToCosmosEvents = append(sendToCosmosEvents, iter.Event) + // } + // + // iter.Close() + //} log.WithFields(log.Fields{ "start": startingBlock, "end": currentBlock, - "OldDeposits": sendToCosmosEvents, + "OldDeposits": legacyDeposits, }).Debugln("Scanned SendToCosmos events from Ethereum") - var sendToInjectiveEvents []*wrappers.PeggySendToInjectiveEvent - { - - iter, err := peggyFilterer.FilterSendToInjectiveEvent(&bind.FilterOpts{ - Start: startingBlock, - End: ¤tBlock, - }, nil, nil, nil) - if err != nil { - metrics.ReportFuncError(s.svcTags) - log.WithFields(log.Fields{ - "start": startingBlock, - "end": currentBlock, - }).Errorln("failed to scan past SendToInjective events from Ethereum") - - if !isUnknownBlockErr(err) { - err = errors.Wrap(err, "failed to scan past SendToInjective events from Ethereum") - return 0, err - } else if iter == nil { - return 0, errors.New("no iterator returned") - } - } - - for iter.Next() { - sendToInjectiveEvents = append(sendToInjectiveEvents, iter.Event) - } - - iter.Close() + // todo + deposits, err := s.ethereum.GetSendToInjectiveEvents(startingBlock, currentBlock) + if err != nil { + return 0, err } + //var sendToInjectiveEvents []*wrappers.PeggySendToInjectiveEvent + //{ + // + // iter, err := peggyFilterer.FilterSendToInjectiveEvent(&bind.FilterOpts{ + // Start: startingBlock, + // End: ¤tBlock, + // }, nil, nil, nil) + // if err != nil { + // metrics.ReportFuncError(s.svcTags) + // log.WithFields(log.Fields{ + // "start": startingBlock, + // "end": currentBlock, + // }).Errorln("failed to scan past SendToInjective events from Ethereum") + // + // if !isUnknownBlockErr(err) { + // err = errors.Wrap(err, "failed to scan past SendToInjective events from Ethereum") + // return 0, err + // } else if iter == nil { + // return 0, errors.New("no iterator returned") + // } + // } + // + // for iter.Next() { + // sendToInjectiveEvents = append(sendToInjectiveEvents, iter.Event) + // } + // + // iter.Close() + //} + log.WithFields(log.Fields{ "start": startingBlock, "end": currentBlock, - "Deposits": sendToInjectiveEvents, + "Deposits": deposits, }).Debugln("Scanned SendToInjective events from Ethereum") - var transactionBatchExecutedEvents []*wrappers.PeggyTransactionBatchExecutedEvent - { - iter, err := peggyFilterer.FilterTransactionBatchExecutedEvent(&bind.FilterOpts{ - Start: startingBlock, - End: ¤tBlock, - }, nil, nil) - if err != nil { - metrics.ReportFuncError(s.svcTags) - log.WithFields(log.Fields{ - "start": startingBlock, - "end": currentBlock, - }).Errorln("failed to scan past TransactionBatchExecuted events from Ethereum") - - if !isUnknownBlockErr(err) { - err = errors.Wrap(err, "failed to scan past TransactionBatchExecuted events from Ethereum") - return 0, err - } else if iter == nil { - return 0, errors.New("no iterator returned") - } - } + // todo + withdrawals, err := s.ethereum.GetTransactionBatchExecutedEvents(startingBlock, currentBlock) + if err != nil { + return 0, err + } - for iter.Next() { - transactionBatchExecutedEvents = append(transactionBatchExecutedEvents, iter.Event) - } + //var transactionBatchExecutedEvents []*wrappers.PeggyTransactionBatchExecutedEvent + //{ + // iter, err := peggyFilterer.FilterTransactionBatchExecutedEvent(&bind.FilterOpts{ + // Start: startingBlock, + // End: ¤tBlock, + // }, nil, nil) + // if err != nil { + // metrics.ReportFuncError(s.svcTags) + // log.WithFields(log.Fields{ + // "start": startingBlock, + // "end": currentBlock, + // }).Errorln("failed to scan past TransactionBatchExecuted events from Ethereum") + // + // if !isUnknownBlockErr(err) { + // err = errors.Wrap(err, "failed to scan past TransactionBatchExecuted events from Ethereum") + // return 0, err + // } else if iter == nil { + // return 0, errors.New("no iterator returned") + // } + // } + // + // for iter.Next() { + // transactionBatchExecutedEvents = append(transactionBatchExecutedEvents, iter.Event) + // } + // + // iter.Close() + //} - iter.Close() - } log.WithFields(log.Fields{ "start": startingBlock, "end": currentBlock, - "Withdraws": transactionBatchExecutedEvents, + "Withdraws": withdrawals, }).Debugln("Scanned TransactionBatchExecuted events from Ethereum") - var erc20DeployedEvents []*wrappers.PeggyERC20DeployedEvent - { - iter, err := peggyFilterer.FilterERC20DeployedEvent(&bind.FilterOpts{ - Start: startingBlock, - End: ¤tBlock, - }, nil) - if err != nil { - metrics.ReportFuncError(s.svcTags) - log.WithFields(log.Fields{ - "start": startingBlock, - "end": currentBlock, - }).Errorln("failed to scan past FilterERC20Deployed events from Ethereum") - - if !isUnknownBlockErr(err) { - err = errors.Wrap(err, "failed to scan past FilterERC20Deployed events from Ethereum") - return 0, err - } else if iter == nil { - return 0, errors.New("no iterator returned") - } - } + // todo + erc20Deployments, err := s.ethereum.GetPeggyERC20DeployedEvents(startingBlock, currentBlock) + if err != nil { + return 0, err + } - for iter.Next() { - erc20DeployedEvents = append(erc20DeployedEvents, iter.Event) - } + //var erc20DeployedEvents []*wrappers.PeggyERC20DeployedEvent + //{ + // iter, err := peggyFilterer.FilterERC20DeployedEvent(&bind.FilterOpts{ + // Start: startingBlock, + // End: ¤tBlock, + // }, nil) + // if err != nil { + // metrics.ReportFuncError(s.svcTags) + // log.WithFields(log.Fields{ + // "start": startingBlock, + // "end": currentBlock, + // }).Errorln("failed to scan past FilterERC20Deployed events from Ethereum") + // + // if !isUnknownBlockErr(err) { + // err = errors.Wrap(err, "failed to scan past FilterERC20Deployed events from Ethereum") + // return 0, err + // } else if iter == nil { + // return 0, errors.New("no iterator returned") + // } + // } + // + // for iter.Next() { + // erc20DeployedEvents = append(erc20DeployedEvents, iter.Event) + // } + // + // iter.Close() + //} - iter.Close() - } log.WithFields(log.Fields{ "start": startingBlock, "end": currentBlock, - "erc20Deployed": erc20DeployedEvents, + "erc20Deployed": erc20Deployments, }).Debugln("Scanned FilterERC20Deployed events from Ethereum") - var valsetUpdatedEvents []*wrappers.PeggyValsetUpdatedEvent - { - iter, err := peggyFilterer.FilterValsetUpdatedEvent(&bind.FilterOpts{ - Start: startingBlock, - End: ¤tBlock, - }, nil) - if err != nil { - metrics.ReportFuncError(s.svcTags) - log.WithFields(log.Fields{ - "start": startingBlock, - "end": currentBlock, - }).Errorln("failed to scan past ValsetUpdatedEvent events from Ethereum") - - if !isUnknownBlockErr(err) { - err = errors.Wrap(err, "failed to scan past ValsetUpdatedEvent events from Ethereum") - return 0, err - } else if iter == nil { - return 0, errors.New("no iterator returned") - } - } - - for iter.Next() { - valsetUpdatedEvents = append(valsetUpdatedEvents, iter.Event) - } - - iter.Close() + // todo + valsetUpdates, err := s.ethereum.GetValsetUpdatedEvents(startingBlock, currentBlock) + if err != nil { + return 0, err } + //var valsetUpdatedEvents []*wrappers.PeggyValsetUpdatedEvent + //{ + // iter, err := peggyFilterer.FilterValsetUpdatedEvent(&bind.FilterOpts{ + // Start: startingBlock, + // End: ¤tBlock, + // }, nil) + // if err != nil { + // metrics.ReportFuncError(s.svcTags) + // log.WithFields(log.Fields{ + // "start": startingBlock, + // "end": currentBlock, + // }).Errorln("failed to scan past ValsetUpdatedEvent events from Ethereum") + // + // if !isUnknownBlockErr(err) { + // err = errors.Wrap(err, "failed to scan past ValsetUpdatedEvent events from Ethereum") + // return 0, err + // } else if iter == nil { + // return 0, errors.New("no iterator returned") + // } + // } + // + // for iter.Next() { + // valsetUpdatedEvents = append(valsetUpdatedEvents, iter.Event) + // } + // + // iter.Close() + //} + log.WithFields(log.Fields{ "start": startingBlock, "end": currentBlock, - "valsetUpdates": valsetUpdatedEvents, + "valsetUpdates": valsetUpdates, }).Debugln("Scanned ValsetUpdatedEvents events from Ethereum") // note that starting block overlaps with our last checked block, because we have to deal with @@ -314,22 +346,22 @@ func (s *PeggyOrchestrator) relayEthEvents( // block, so we also need this routine so make sure we don't send in the first event in this hypothetical // multi event block again. In theory we only send all events for every block and that will pass of fail // atomically but lets not take that risk. - lastClaimEvent, err := s.cosmosQueryClient.LastClaimEventByAddr(ctx, s.peggyBroadcastClient.AccFromAddress()) + lastClaimEvent, err := s.injective.LastClaimEvent(ctx) if err != nil { metrics.ReportFuncError(s.svcTags) err = errors.New("failed to query last claim event from backend") return 0, err } - oldDeposits := filterSendToCosmosEventsByNonce(sendToCosmosEvents, lastClaimEvent.EthereumEventNonce) - deposits := filterSendToInjectiveEventsByNonce(sendToInjectiveEvents, lastClaimEvent.EthereumEventNonce) - withdraws := filterTransactionBatchExecutedEventsByNonce(transactionBatchExecutedEvents, lastClaimEvent.EthereumEventNonce) - erc20Deployments := filterERC20DeployedEventsByNonce(erc20DeployedEvents, lastClaimEvent.EthereumEventNonce) - valsetUpdates := filterValsetUpdateEventsByNonce(valsetUpdatedEvents, lastClaimEvent.EthereumEventNonce) + legacyDeposits = filterSendToCosmosEventsByNonce(legacyDeposits, lastClaimEvent.EthereumEventNonce) + deposits = filterSendToInjectiveEventsByNonce(deposits, lastClaimEvent.EthereumEventNonce) + withdrawals = filterTransactionBatchExecutedEventsByNonce(withdrawals, lastClaimEvent.EthereumEventNonce) + erc20Deployments = filterERC20DeployedEventsByNonce(erc20Deployments, lastClaimEvent.EthereumEventNonce) + valsetUpdates = filterValsetUpdateEventsByNonce(valsetUpdates, lastClaimEvent.EthereumEventNonce) - if len(oldDeposits) > 0 || len(deposits) > 0 || len(withdraws) > 0 || len(erc20Deployments) > 0 || len(valsetUpdates) > 0 { + if len(legacyDeposits) > 0 || len(deposits) > 0 || len(withdrawals) > 0 || len(erc20Deployments) > 0 || len(valsetUpdates) > 0 { // todo get eth chain id from the chain - if err := s.peggyBroadcastClient.SendEthereumClaims(ctx, lastClaimEvent.EthereumEventNonce, oldDeposits, deposits, withdraws, erc20Deployments, valsetUpdates); err != nil { + if err := s.injective.SendEthereumClaims(ctx, lastClaimEvent.EthereumEventNonce, legacyDeposits, deposits, withdrawals, erc20Deployments, valsetUpdates); err != nil { metrics.ReportFuncError(s.svcTags) err = errors.Wrap(err, "failed to send ethereum claims to Cosmos chain") return 0, err diff --git a/orchestrator/oracle_test.go b/orchestrator/oracle_test.go index a8b18adb..53509da7 100644 --- a/orchestrator/oracle_test.go +++ b/orchestrator/oracle_test.go @@ -1 +1,196 @@ package orchestrator + +import ( + "context" + "errors" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/assert" + + wrappers "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" + peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" +) + +func TestRelayEvents(t *testing.T) { + t.Parallel() + + t.Run("ethereum cannot fetch latest header", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ethereum: mockEthereum{ + headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return nil, errors.New("fail") + }, + }} + + _, err := orch.relayEthEvents(context.TODO(), 0) + assert.Error(t, err) + }) + + t.Run("ethereum returns an older block", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ethereum: mockEthereum{ + headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return &types.Header{Number: big.NewInt(50)}, nil + }, + }} + + currentBlock, err := orch.relayEthEvents(context.TODO(), 100) + assert.NoError(t, err) + assert.Equal(t, currentBlock, 50-ethBlockConfirmationDelay) + }) + + t.Run("failed to fetch SendToCosmos events", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + ethereum: mockEthereum{ + headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return &types.Header{Number: big.NewInt(200)}, nil + }, + getSendToCosmosEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToCosmosEvent, error) { + return nil, errors.New("fail") + }, + }, + } + + _, err := orch.relayEthEvents(context.TODO(), 100) + assert.Error(t, err) + }) + + t.Run("failed to get last claim event from injective", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + ethereum: mockEthereum{ + headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return &types.Header{Number: big.NewInt(200)}, nil + }, + getSendToCosmosEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToCosmosEvent, error) { + return []*wrappers.PeggySendToCosmosEvent{}, nil + }, + getTransactionBatchExecutedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyTransactionBatchExecutedEvent, error) { + return nil, nil + }, + getValsetUpdatedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return nil, nil + }, + getPeggyERC20DeployedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyERC20DeployedEvent, error) { + return nil, nil + }, + getSendToInjectiveEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToInjectiveEvent, error) { + return nil, nil + }, + }, + injective: &mockInjective{ + lastClaimEventFn: func(context.Context) (*peggytypes.LastClaimEvent, error) { + return nil, errors.New("fail") + }, + }, + } + + _, err := orch.relayEthEvents(context.TODO(), 100) + assert.Error(t, err) + }) + + t.Run("old events are pruned", func(t *testing.T) { + t.Parallel() + + inj := &mockInjective{ + lastClaimEventFn: func(context.Context) (*peggytypes.LastClaimEvent, error) { + return &peggytypes.LastClaimEvent{EthereumEventNonce: 6}, nil + }, + sendEthereumClaimsFn: func( + context.Context, + uint64, + []*wrappers.PeggySendToCosmosEvent, + []*wrappers.PeggySendToInjectiveEvent, + []*wrappers.PeggyTransactionBatchExecutedEvent, + []*wrappers.PeggyERC20DeployedEvent, + []*wrappers.PeggyValsetUpdatedEvent, + ) error { + return nil + }, + } + + orch := &PeggyOrchestrator{ + ethereum: mockEthereum{ + headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return &types.Header{Number: big.NewInt(200)}, nil + }, + getSendToCosmosEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToCosmosEvent, error) { + return []*wrappers.PeggySendToCosmosEvent{{EventNonce: big.NewInt(5)}}, nil + }, + getTransactionBatchExecutedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyTransactionBatchExecutedEvent, error) { + return nil, nil + }, + getValsetUpdatedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return nil, nil + }, + getPeggyERC20DeployedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyERC20DeployedEvent, error) { + return nil, nil + }, + getSendToInjectiveEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToInjectiveEvent, error) { + return nil, nil + }, + }, + injective: inj, + } + + _, err := orch.relayEthEvents(context.TODO(), 100) + assert.NoError(t, err) + assert.Equal(t, inj.sendEthereumClaimsCallCount, 0) + }) + + t.Run("new events are sent to injective", func(t *testing.T) { + t.Parallel() + + inj := &mockInjective{ + lastClaimEventFn: func(context.Context) (*peggytypes.LastClaimEvent, error) { + return &peggytypes.LastClaimEvent{EthereumEventNonce: 6}, nil + }, + sendEthereumClaimsFn: func( + context.Context, + uint64, + []*wrappers.PeggySendToCosmosEvent, + []*wrappers.PeggySendToInjectiveEvent, + []*wrappers.PeggyTransactionBatchExecutedEvent, + []*wrappers.PeggyERC20DeployedEvent, + []*wrappers.PeggyValsetUpdatedEvent, + ) error { + return nil + }, + } + + orch := &PeggyOrchestrator{ + ethereum: mockEthereum{ + headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return &types.Header{Number: big.NewInt(200)}, nil + }, + getSendToCosmosEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToCosmosEvent, error) { + return []*wrappers.PeggySendToCosmosEvent{{EventNonce: big.NewInt(10)}}, nil + }, + getTransactionBatchExecutedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyTransactionBatchExecutedEvent, error) { + return nil, nil + }, + getValsetUpdatedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return nil, nil + }, + getPeggyERC20DeployedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyERC20DeployedEvent, error) { + return nil, nil + }, + getSendToInjectiveEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToInjectiveEvent, error) { + return nil, nil + }, + }, + injective: inj, + } + + _, err := orch.relayEthEvents(context.TODO(), 100) + assert.NoError(t, err) + assert.Equal(t, inj.sendEthereumClaimsCallCount, 1) + }) +} From 2e667cea5e7cf23bfb62393b0ed69ee397bdc69a Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Mon, 12 Jun 2023 12:06:17 +0200 Subject: [PATCH 16/72] add signer tests --- orchestrator/main_loops.go | 89 --------------------- orchestrator/mocks_test.go | 45 ++++++++++- orchestrator/orchestrator.go | 17 +++- orchestrator/signer.go | 146 +++++++++++++++++++++++++++++++++++ orchestrator/signer_test.go | 146 +++++++++++++++++++++++++++++++++++ 5 files changed, 349 insertions(+), 94 deletions(-) create mode 100644 orchestrator/signer.go create mode 100644 orchestrator/signer_test.go diff --git a/orchestrator/main_loops.go b/orchestrator/main_loops.go index 77366967..7627bd36 100644 --- a/orchestrator/main_loops.go +++ b/orchestrator/main_loops.go @@ -7,13 +7,10 @@ import ( "math/big" "time" - "github.com/avast/retry-go" - "github.com/ethereum/go-ethereum/common" log "github.com/xlab/suplog" "github.com/InjectiveLabs/sdk-go/chain/peggy/types" - "github.com/InjectiveLabs/peggo/orchestrator/cosmos" "github.com/InjectiveLabs/peggo/orchestrator/loops" ) @@ -31,92 +28,6 @@ func (s *PeggyOrchestrator) Start(ctx context.Context, validatorMode bool) error return s.startValidatorMode(ctx) } -// EthSignerMainLoop simply signs off on any batches or validator sets provided by the validator -// since these are provided directly by a trusted Cosmsos node they can simply be assumed to be -// valid and signed off on. -func (s *PeggyOrchestrator) EthSignerMainLoop(ctx context.Context) (err error) { - logger := log.WithField("loop", "EthSignerMainLoop") - - var peggyID common.Hash - if err := retry.Do(func() (err error) { - peggyID, err = s.peggyContract.GetPeggyID(ctx, s.peggyContract.FromAddress()) - return - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get PeggyID from Ethereum contract, will retry (%d)", n) - })); err != nil { - logger.WithError(err).Errorln("got error, loop exits") - return err - } - logger.Debugf("received peggyID %s", peggyID.Hex()) - - return loops.RunLoop(ctx, defaultLoopDur, func() error { - var oldestUnsignedValsets []*types.Valset - if err := retry.Do(func() error { - oldestValsets, err := s.cosmosQueryClient.OldestUnsignedValsets(ctx, s.peggyBroadcastClient.AccFromAddress()) - if err != nil { - if err == cosmos.ErrNotFound || oldestValsets == nil { - logger.Debugln("no Valset waiting to be signed") - return nil - } - - return err - } - oldestUnsignedValsets = oldestValsets - return nil - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get unsigned Valset for signing, will retry (%d)", n) - })); err != nil { - logger.WithError(err).Errorln("got error, loop exits") - return err - } - - for _, oldestValset := range oldestUnsignedValsets { - logger.Infoln("Sending Valset confirm for %d", oldestValset.Nonce) - if err := retry.Do(func() error { - return s.peggyBroadcastClient.SendValsetConfirm(ctx, s.ethFrom, peggyID, oldestValset) - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to sign and send Valset confirmation to Cosmos, will retry (%d)", n) - })); err != nil { - logger.WithError(err).Errorln("got error, loop exits") - return err - } - } - - var oldestUnsignedTransactionBatch *types.OutgoingTxBatch - if err := retry.Do(func() error { - // sign the last unsigned batch, TODO check if we already have signed this - txBatch, err := s.cosmosQueryClient.OldestUnsignedTransactionBatch(ctx, s.peggyBroadcastClient.AccFromAddress()) - if err != nil { - if err == cosmos.ErrNotFound || txBatch == nil { - logger.Debugln("no TransactionBatch waiting to be signed") - return nil - } - return err - } - oldestUnsignedTransactionBatch = txBatch - return nil - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get unsigned TransactionBatch for signing, will retry (%d)", n) - })); err != nil { - logger.WithError(err).Errorln("got error, loop exits") - return err - } - - if oldestUnsignedTransactionBatch != nil { - logger.Infoln("Sending TransactionBatch confirm for BatchNonce %d", oldestUnsignedTransactionBatch.BatchNonce) - if err := retry.Do(func() error { - return s.peggyBroadcastClient.SendBatchConfirm(ctx, s.ethFrom, peggyID, oldestUnsignedTransactionBatch) - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to sign and send TransactionBatch confirmation to Cosmos, will retry (%d)", n) - })); err != nil { - logger.WithError(err).Errorln("got error, loop exits") - return err - } - } - return nil - }) -} - // This loop doesn't have a formal role per say, anyone can request a valset // but there does need to be some strategy to ensure requests are made. Having it // be a function of the orchestrator makes a lot of sense as they are already online diff --git a/orchestrator/mocks_test.go b/orchestrator/mocks_test.go index 110d2b04..c634ed27 100644 --- a/orchestrator/mocks_test.go +++ b/orchestrator/mocks_test.go @@ -25,10 +25,8 @@ type mockInjective struct { sendRequestBatchFn func(context.Context, string) error sendRequestBatchCallCount int - peggyParamsFn func(context.Context) (*peggytypes.Params, error) - - lastClaimEventFn func(context.Context) (*peggytypes.LastClaimEvent, error) - + peggyParamsFn func(context.Context) (*peggytypes.Params, error) + lastClaimEventFn func(context.Context) (*peggytypes.LastClaimEvent, error) sendEthereumClaimsFn func( ctx context.Context, lastClaimEvent uint64, @@ -39,6 +37,16 @@ type mockInjective struct { valsetUpdates []*peggyevents.PeggyValsetUpdatedEvent, ) error sendEthereumClaimsCallCount int + + oldestUnsignedValsetsFn func(context.Context) ([]*peggytypes.Valset, error) + sendValsetConfirmFn func( + ctx context.Context, + peggyID eth.Hash, + valset *peggytypes.Valset, + ) error + + oldestUnsignedTransactionBatchFn func(context.Context) (*peggytypes.OutgoingTxBatch, error) + sendBatchConfirmFn func(context.Context, eth.Hash, *peggytypes.OutgoingTxBatch) error } func (i *mockInjective) UnbatchedTokenFees(ctx context.Context) ([]*peggytypes.BatchFees, error) { @@ -80,6 +88,30 @@ func (i *mockInjective) SendEthereumClaims( ) } +func (i *mockInjective) OldestUnsignedValsets(ctx context.Context) ([]*peggytypes.Valset, error) { + return i.oldestUnsignedValsetsFn(ctx) +} + +func (i *mockInjective) SendValsetConfirm( + ctx context.Context, + peggyID eth.Hash, + valset *peggytypes.Valset, +) error { + return i.sendValsetConfirmFn(ctx, peggyID, valset) +} + +func (i *mockInjective) OldestUnsignedTransactionBatch(ctx context.Context) (*peggytypes.OutgoingTxBatch, error) { + return i.oldestUnsignedTransactionBatchFn(ctx) +} + +func (i *mockInjective) SendBatchConfirm( + ctx context.Context, + peggyID eth.Hash, + batch *peggytypes.OutgoingTxBatch, +) error { + return i.sendBatchConfirmFn(ctx, peggyID, batch) +} + type mockEthereum struct { headerByNumberFn func(context.Context, *big.Int) (*ethtypes.Header, error) getSendToCosmosEventsFn func(uint64, uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) @@ -87,6 +119,7 @@ type mockEthereum struct { getPeggyERC20DeployedEventsFn func(uint64, uint64) ([]*peggyevents.PeggyERC20DeployedEvent, error) getValsetUpdatedEventsFn func(uint64, uint64) ([]*peggyevents.PeggyValsetUpdatedEvent, error) getTransactionBatchExecutedEventsFn func(uint64, uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) + getPeggyIDFn func(context.Context) (eth.Hash, error) } func (e mockEthereum) HeaderByNumber(ctx context.Context, number *big.Int) (*ethtypes.Header, error) { @@ -112,3 +145,7 @@ func (e mockEthereum) GetValsetUpdatedEvents(startBlock, endBlock uint64) ([]*pe func (e mockEthereum) GetTransactionBatchExecutedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) { return e.getTransactionBatchExecutedEventsFn(startBlock, endBlock) } + +func (e mockEthereum) GetPeggyID(ctx context.Context) (eth.Hash, error) { + return e.getPeggyIDFn(ctx) +} diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index 60e8270e..9ef5627c 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -37,11 +37,25 @@ type InjectiveNetwork interface { erc20Deployed []*peggyevents.PeggyERC20DeployedEvent, valsetUpdates []*peggyevents.PeggyValsetUpdatedEvent, ) error + + OldestUnsignedValsets(ctx context.Context) ([]*peggytypes.Valset, error) + SendValsetConfirm( + ctx context.Context, + peggyID ethcmn.Hash, + valset *peggytypes.Valset, + ) error + + OldestUnsignedTransactionBatch(ctx context.Context) (*peggytypes.OutgoingTxBatch, error) + SendBatchConfirm( + ctx context.Context, + peggyID ethcmn.Hash, + batch *peggytypes.OutgoingTxBatch, + ) error } type EthereumNetwork interface { HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) - + GetPeggyID(ctx context.Context) (ethcmn.Hash, error) GetSendToCosmosEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) GetSendToInjectiveEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) GetPeggyERC20DeployedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyERC20DeployedEvent, error) @@ -64,6 +78,7 @@ type PeggyOrchestrator struct { relayer relayer.PeggyRelayer minBatchFeeUSD float64 priceFeeder *coingecko.CoingeckoPriceFeed + maxRetries uint periodicBatchRequesting bool } diff --git a/orchestrator/signer.go b/orchestrator/signer.go new file mode 100644 index 00000000..f4b153bd --- /dev/null +++ b/orchestrator/signer.go @@ -0,0 +1,146 @@ +package orchestrator + +import ( + "context" + "github.com/InjectiveLabs/peggo/orchestrator/cosmos" + "github.com/InjectiveLabs/peggo/orchestrator/loops" + "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + "github.com/avast/retry-go" + "github.com/ethereum/go-ethereum/common" + log "github.com/xlab/suplog" +) + +// EthSignerMainLoop simply signs off on any batches or validator sets provided by the validator +// since these are provided directly by a trusted Injective node they can simply be assumed to be +// valid and signed off on. +func (s *PeggyOrchestrator) EthSignerMainLoop(ctx context.Context) error { + logger := log.WithField("loop", "EthSignerMainLoop") + + peggyID, err := s.getPeggyID(ctx, logger) + if err != nil { + return err + } + + return loops.RunLoop(ctx, defaultLoopDur, func() error { + return s.signerLoop(ctx, logger, peggyID) + }) +} + +func (s *PeggyOrchestrator) getPeggyID(ctx context.Context, logger log.Logger) (common.Hash, error) { + var peggyID common.Hash + if err := retry.Do(func() error { + id, err := s.ethereum.GetPeggyID(ctx) + if err != nil { + return err + } + + peggyID = id + return nil + }, retry.Context(ctx), + retry.Attempts(s.maxRetries), + retry.OnRetry(func(n uint, err error) { + logger.WithError(err).Warningf("failed to get PeggyID from Ethereum contract, will retry (%d)", n) + })); err != nil { + logger.WithError(err).Errorln("got error, loop exits") + return [32]byte{}, err + } + + logger.Debugf("received peggyID %s", peggyID.Hex()) + + return peggyID, nil +} + +func (s *PeggyOrchestrator) signerLoop(ctx context.Context, logger log.Logger, peggyID common.Hash) error { + if err := s.signValsetUpdates(ctx, logger, peggyID); err != nil { + return err + } + + if err := s.signTransactionBatches(ctx, logger, peggyID); err != nil { + return err + } + + return nil +} + +func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Logger, peggyID common.Hash) error { + var oldestUnsignedValsets []*types.Valset + if err := retry.Do(func() error { + oldestValsets, err := s.injective.OldestUnsignedValsets(ctx) + if err != nil { + if err == cosmos.ErrNotFound || oldestValsets == nil { + logger.Debugln("no Valset waiting to be signed") + return nil + } + + // dusan: this will never really trigger + return err + } + oldestUnsignedValsets = oldestValsets + return nil + }, retry.Context(ctx), + retry.Attempts(s.maxRetries), + retry.OnRetry(func(n uint, err error) { + logger.WithError(err).Warningf("failed to get unsigned Valset for signing, will retry (%d)", n) + })); err != nil { + logger.WithError(err).Errorln("got error, loop exits") + return err + } + + for _, oldestValset := range oldestUnsignedValsets { + logger.Infoln("Sending Valset confirm for %d", oldestValset.Nonce) + if err := retry.Do(func() error { + return s.injective.SendValsetConfirm(ctx, peggyID, oldestValset) + }, retry.Context(ctx), + retry.Attempts(s.maxRetries), + retry.OnRetry(func(n uint, err error) { + logger.WithError(err).Warningf("failed to sign and send Valset confirmation to Cosmos, will retry (%d)", n) + })); err != nil { + logger.WithError(err).Errorln("got error, loop exits") + return err + } + } + + return nil +} + +func (s *PeggyOrchestrator) signTransactionBatches(ctx context.Context, logger log.Logger, peggyID common.Hash) error { + var oldestUnsignedTransactionBatch *types.OutgoingTxBatch + if err := retry.Do(func() error { + // sign the last unsigned batch, TODO check if we already have signed this + txBatch, err := s.injective.OldestUnsignedTransactionBatch(ctx) + if err != nil { + if err == cosmos.ErrNotFound || txBatch == nil { + logger.Debugln("no TransactionBatch waiting to be signed") + return nil + } + return err + } + oldestUnsignedTransactionBatch = txBatch + return nil + }, retry.Context(ctx), + retry.Attempts(s.maxRetries), + retry.OnRetry(func(n uint, err error) { + logger.WithError(err).Warningf("failed to get unsigned TransactionBatch for signing, will retry (%d)", n) + })); err != nil { + logger.WithError(err).Errorln("got error, loop exits") + return err + } + + if oldestUnsignedTransactionBatch == nil { + return nil + } + + logger.Infoln("Sending TransactionBatch confirm for BatchNonce %d", oldestUnsignedTransactionBatch.BatchNonce) + if err := retry.Do(func() error { + return s.injective.SendBatchConfirm(ctx, peggyID, oldestUnsignedTransactionBatch) + }, retry.Context(ctx), + retry.Attempts(s.maxRetries), + retry.OnRetry(func(n uint, err error) { + logger.WithError(err).Warningf("failed to sign and send TransactionBatch confirmation to Cosmos, will retry (%d)", n) + })); err != nil { + logger.WithError(err).Errorln("got error, loop exits") + return err + } + + return nil +} diff --git a/orchestrator/signer_test.go b/orchestrator/signer_test.go new file mode 100644 index 00000000..7959dfee --- /dev/null +++ b/orchestrator/signer_test.go @@ -0,0 +1,146 @@ +package orchestrator + +import ( + "context" + "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + cosmtypes "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/xlab/suplog" + "testing" +) + +func TestEthSignerLoop(t *testing.T) { + t.Parallel() + + t.Run("failed to fetch peggy id from contract", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + maxRetries: 1, // todo, hardcode do 10 + ethereum: mockEthereum{ + getPeggyIDFn: func(context.Context) (common.Hash, error) { + return [32]byte{}, errors.New("fail") + }, + }, + } + + err := orch.EthSignerMainLoop(context.TODO()) + assert.Error(t, err) + }) + + t.Run("no valset to sign", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + maxRetries: 1, + injective: &mockInjective{ + oldestUnsignedValsetsFn: func(context.Context) ([]*types.Valset, error) { + return nil, errors.New("fail") + }, + sendValsetConfirmFn: func(_ context.Context, _ common.Hash, _ *types.Valset) error { + return nil + }, + oldestUnsignedTransactionBatchFn: func(_ context.Context) (*types.OutgoingTxBatch, error) { + return nil, nil + }, + sendBatchConfirmFn: func(_ context.Context, _ common.Hash, _ *types.OutgoingTxBatch) error { + return nil + }, + }, + } + + err := orch.signerLoop(context.TODO(), suplog.DefaultLogger, [32]byte{1, 2, 3}) + assert.NoError(t, err) + }) + + t.Run("failed to send valset confirm", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + maxRetries: 1, + injective: &mockInjective{ + oldestUnsignedValsetsFn: func(context.Context) ([]*types.Valset, error) { + return []*types.Valset{ + { + Nonce: 5, + Members: []*types.BridgeValidator{ + { + Power: 100, + EthereumAddress: "abcd", + }, + }, + Height: 500, + RewardAmount: cosmtypes.NewInt(123), + RewardToken: "dusanToken", + }, + }, nil + }, + sendValsetConfirmFn: func(_ context.Context, _ common.Hash, _ *types.Valset) error { + return errors.New("fail") + }, + }, + } + + err := orch.signerLoop(context.TODO(), suplog.DefaultLogger, [32]byte{1, 2, 3}) + assert.Error(t, err) + }) + + t.Run("no transaction batch sign", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + maxRetries: 1, + injective: &mockInjective{ + oldestUnsignedValsetsFn: func(_ context.Context) ([]*types.Valset, error) { return nil, nil }, + sendValsetConfirmFn: func(_ context.Context, _ common.Hash, _ *types.Valset) error { return nil }, + oldestUnsignedTransactionBatchFn: func(_ context.Context) (*types.OutgoingTxBatch, error) { return nil, errors.New("fail") }, + sendBatchConfirmFn: func(_ context.Context, _ common.Hash, _ *types.OutgoingTxBatch) error { return nil }, + }, + } + + err := orch.signerLoop(context.TODO(), suplog.DefaultLogger, [32]byte{}) + assert.NoError(t, err) + }) + + t.Run("failed to send batch confirm", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + maxRetries: 1, + injective: &mockInjective{ + oldestUnsignedValsetsFn: func(_ context.Context) ([]*types.Valset, error) { return nil, nil }, + sendValsetConfirmFn: func(_ context.Context, _ common.Hash, _ *types.Valset) error { return nil }, + oldestUnsignedTransactionBatchFn: func(_ context.Context) (*types.OutgoingTxBatch, error) { + return &types.OutgoingTxBatch{}, nil // non-empty will do + }, + sendBatchConfirmFn: func(_ context.Context, _ common.Hash, _ *types.OutgoingTxBatch) error { return errors.New("fail") }, + }, + } + + err := orch.signerLoop(context.TODO(), suplog.DefaultLogger, [32]byte{}) + assert.Error(t, err) + }) + + t.Run("valset update and transaction batch are confirmed", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + maxRetries: 1, + injective: &mockInjective{ + oldestUnsignedValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{}, nil // non-empty will do + }, + oldestUnsignedTransactionBatchFn: func(_ context.Context) (*types.OutgoingTxBatch, error) { + return &types.OutgoingTxBatch{}, nil // non-empty will do + }, + sendValsetConfirmFn: func(_ context.Context, _ common.Hash, _ *types.Valset) error { return nil }, + sendBatchConfirmFn: func(_ context.Context, _ common.Hash, _ *types.OutgoingTxBatch) error { return nil }, + }, + } + + err := orch.signerLoop(context.TODO(), suplog.DefaultLogger, [32]byte{}) + assert.NoError(t, err) + }) +} From 2bf88a1d50304970aef003c468d0357cc1e0482f Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Tue, 13 Jun 2023 10:34:33 +0200 Subject: [PATCH 17/72] add relayer tests valset --- orchestrator/main_loops.go | 9 - orchestrator/mocks_test.go | 38 +++ orchestrator/orchestrator.go | 21 ++ orchestrator/relayer.go | 369 ++++++++++++++++++++++++ orchestrator/relayer_test.go | 543 +++++++++++++++++++++++++++++++++++ 5 files changed, 971 insertions(+), 9 deletions(-) create mode 100644 orchestrator/relayer.go create mode 100644 orchestrator/relayer_test.go diff --git a/orchestrator/main_loops.go b/orchestrator/main_loops.go index 7627bd36..a10a854a 100644 --- a/orchestrator/main_loops.go +++ b/orchestrator/main_loops.go @@ -2,7 +2,6 @@ package orchestrator import ( "context" - "errors" "math" "math/big" "time" @@ -98,14 +97,6 @@ func (s *PeggyOrchestrator) ValsetRequesterLoop(ctx context.Context) (err error) } **/ -func (s *PeggyOrchestrator) RelayerMainLoop(ctx context.Context) (err error) { - if s.relayer != nil { - return s.relayer.Start(ctx) - } else { - return errors.New("relayer is nil") - } -} - // valPowerDiff returns the difference in power between two bridge validator sets // TODO: this needs to be potentially refactored func valPowerDiff(old *types.Valset, new *types.Valset) float64 { diff --git a/orchestrator/mocks_test.go b/orchestrator/mocks_test.go index c634ed27..de7cc900 100644 --- a/orchestrator/mocks_test.go +++ b/orchestrator/mocks_test.go @@ -5,6 +5,7 @@ import ( peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" eth "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" + tmctypes "github.com/tendermint/tendermint/rpc/core/types" "math/big" peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" @@ -47,6 +48,12 @@ type mockInjective struct { oldestUnsignedTransactionBatchFn func(context.Context) (*peggytypes.OutgoingTxBatch, error) sendBatchConfirmFn func(context.Context, eth.Hash, *peggytypes.OutgoingTxBatch) error + + latestValsetsFn func(context.Context) ([]*peggytypes.Valset, error) + getBlockFn func(context.Context, int64) (*tmctypes.ResultBlock, error) + + allValsetConfirmsFn func(context.Context, uint64) ([]*peggytypes.MsgValsetConfirm, error) + valsetAtFn func(context.Context, uint64) (*peggytypes.Valset, error) } func (i *mockInjective) UnbatchedTokenFees(ctx context.Context) ([]*peggytypes.BatchFees, error) { @@ -104,6 +111,18 @@ func (i *mockInjective) OldestUnsignedTransactionBatch(ctx context.Context) (*pe return i.oldestUnsignedTransactionBatchFn(ctx) } +func (i *mockInjective) GetBlock(ctx context.Context, height int64) (*tmctypes.ResultBlock, error) { + return i.getBlockFn(ctx, height) +} + +func (i *mockInjective) LatestValsets(ctx context.Context) ([]*peggytypes.Valset, error) { + return i.latestValsetsFn(ctx) +} + +func (i *mockInjective) AllValsetConfirms(ctx context.Context, nonce uint64) ([]*peggytypes.MsgValsetConfirm, error) { + return i.allValsetConfirmsFn(ctx, nonce) +} + func (i *mockInjective) SendBatchConfirm( ctx context.Context, peggyID eth.Hash, @@ -112,6 +131,10 @@ func (i *mockInjective) SendBatchConfirm( return i.sendBatchConfirmFn(ctx, peggyID, batch) } +func (i *mockInjective) ValsetAt(ctx context.Context, nonce uint64) (*peggytypes.Valset, error) { + return i.valsetAtFn(ctx, nonce) +} + type mockEthereum struct { headerByNumberFn func(context.Context, *big.Int) (*ethtypes.Header, error) getSendToCosmosEventsFn func(uint64, uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) @@ -120,6 +143,8 @@ type mockEthereum struct { getValsetUpdatedEventsFn func(uint64, uint64) ([]*peggyevents.PeggyValsetUpdatedEvent, error) getTransactionBatchExecutedEventsFn func(uint64, uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) getPeggyIDFn func(context.Context) (eth.Hash, error) + getValsetNonceFn func(context.Context) (*big.Int, error) + sendEthValsetUpdateFn func(context.Context, *peggytypes.Valset, *peggytypes.Valset, []*peggytypes.MsgValsetConfirm) (*eth.Hash, error) } func (e mockEthereum) HeaderByNumber(ctx context.Context, number *big.Int) (*ethtypes.Header, error) { @@ -149,3 +174,16 @@ func (e mockEthereum) GetTransactionBatchExecutedEvents(startBlock, endBlock uin func (e mockEthereum) GetPeggyID(ctx context.Context) (eth.Hash, error) { return e.getPeggyIDFn(ctx) } + +func (e mockEthereum) GetValsetNonce(ctx context.Context) (*big.Int, error) { + return e.getValsetNonceFn(ctx) +} + +func (e mockEthereum) SendEthValsetUpdate( + ctx context.Context, + oldValset *peggytypes.Valset, + newValset *peggytypes.Valset, + confirms []*peggytypes.MsgValsetConfirm, +) (*eth.Hash, error) { + return e.sendEthValsetUpdateFn(ctx, oldValset, newValset, confirms) +} diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index 9ef5627c..6c373ae5 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -5,7 +5,9 @@ import ( peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" "github.com/ethereum/go-ethereum/core/types" + tmctypes "github.com/tendermint/tendermint/rpc/core/types" "math/big" + "time" ethcmn "github.com/ethereum/go-ethereum/common" @@ -51,16 +53,31 @@ type InjectiveNetwork interface { peggyID ethcmn.Hash, batch *peggytypes.OutgoingTxBatch, ) error + + GetBlock(ctx context.Context, height int64) (*tmctypes.ResultBlock, error) + + LatestValsets(ctx context.Context) ([]*peggytypes.Valset, error) + AllValsetConfirms(ctx context.Context, nonce uint64) ([]*peggytypes.MsgValsetConfirm, error) + ValsetAt(ctx context.Context, nonce uint64) (*peggytypes.Valset, error) } type EthereumNetwork interface { HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) GetPeggyID(ctx context.Context) (ethcmn.Hash, error) + GetSendToCosmosEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) GetSendToInjectiveEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) GetPeggyERC20DeployedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyERC20DeployedEvent, error) GetValsetUpdatedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyValsetUpdatedEvent, error) GetTransactionBatchExecutedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) + + GetValsetNonce(ctx context.Context) (*big.Int, error) + SendEthValsetUpdate( + ctx context.Context, + oldValset *peggytypes.Valset, + newValset *peggytypes.Valset, + confirms []*peggytypes.MsgValsetConfirm, + ) (*ethcmn.Hash, error) } type PeggyOrchestrator struct { @@ -80,6 +97,10 @@ type PeggyOrchestrator struct { priceFeeder *coingecko.CoingeckoPriceFeed maxRetries uint periodicBatchRequesting bool + valsetRelayEnabled bool + batchRelayEnabled bool + relayValsetOffsetDur, + relayBatchOffsetDur time.Duration // todo: parsed from string } func NewPeggyOrchestrator( diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go new file mode 100644 index 00000000..edbe3fd6 --- /dev/null +++ b/orchestrator/relayer.go @@ -0,0 +1,369 @@ +package orchestrator + +import ( + "context" + "github.com/InjectiveLabs/metrics" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum/util" + "github.com/InjectiveLabs/peggo/orchestrator/loops" + wrappers "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" + "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + "github.com/avast/retry-go" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/pkg/errors" + log "github.com/xlab/suplog" + "sort" + "time" +) + +func (s *PeggyOrchestrator) RelayerMainLoop(ctx context.Context) (err error) { + logger := log.WithField("loop", "RelayerMainLoop") + + return loops.RunLoop(ctx, defaultLoopDur, func() error { + var pg loops.ParanoidGroup + if s.valsetRelayEnabled { + logger.Info("Valset Relay Enabled. Starting to relay valsets to Ethereum") + pg.Go(func() error { + return retry.Do(func() error { + return s.relayValsets(ctx) + }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { + logger.WithError(err).Warningf("failed to relay Valsets, will retry (%d)", n) + })) + }) + } + + if s.batchRelayEnabled { + logger.Info("Batch Relay Enabled. Starting to relay batches to Ethereum") + pg.Go(func() error { + return retry.Do(func() error { + return s.relayBatches(ctx) + }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { + logger.WithError(err).Warningf("failed to relay TxBatches, will retry (%d)", n) + })) + }) + } + + if pg.Initialized() { + if err := pg.Wait(); err != nil { + logger.WithError(err).Errorln("got error, loop exits") + return err + } + } + return nil + }) +} + +func (s *PeggyOrchestrator) relayValsets(ctx context.Context) error { + metrics.ReportFuncCall(s.svcTags) + doneFn := metrics.ReportFuncTiming(s.svcTags) + defer doneFn() + + // we should determine if we need to relay one + // to Ethereum for that we will find the latest confirmed valset and compare it to the ethereum chain + + //latestValsets, err := s.cosmosQueryClient.LatestValsets(ctx) + latestValsets, err := s.injective.LatestValsets(ctx) + if err != nil { + metrics.ReportFuncError(s.svcTags) + err = errors.Wrap(err, "failed to fetch latest valsets from cosmos") + return err + } + + var latestCosmosSigs []*types.MsgValsetConfirm + var latestCosmosConfirmed *types.Valset + for _, set := range latestValsets { + //sigs, err := s.cosmosQueryClient.AllValsetConfirms(ctx, set.Nonce) + + sigs, err := s.injective.AllValsetConfirms(ctx, set.Nonce) + if err != nil { + metrics.ReportFuncError(s.svcTags) + err = errors.Wrapf(err, "failed to get valset confirms at nonce %d", set.Nonce) + return err + } else if len(sigs) == 0 { + continue + } + + latestCosmosSigs = sigs + latestCosmosConfirmed = set + break + } + + if latestCosmosConfirmed == nil { + log.Debugln("no confirmed valsets found, nothing to relay") + return nil + } + + currentEthValset, err := s.findLatestValset(ctx) + if err != nil { + metrics.ReportFuncError(s.svcTags) + err = errors.Wrap(err, "couldn't find latest confirmed valset on Ethereum") + return err + } + log.WithFields(log.Fields{"currentEthValset": currentEthValset, "latestCosmosConfirmed": latestCosmosConfirmed}).Debugln("Found Latest valsets") + + if latestCosmosConfirmed.Nonce > currentEthValset.Nonce { + + // todo + latestEthereumValsetNonce, err := s.ethereum.GetValsetNonce(ctx) + + //latestEthereumValsetNonce, err := s.peggyContract.GetValsetNonce(ctx, s.peggyContract.FromAddress()) + if err != nil { + metrics.ReportFuncError(s.svcTags) + err = errors.Wrap(err, "failed to get latest Valset nonce") + return err + } + + // Check if latestCosmosConfirmed already submitted by other validators in mean time + if latestCosmosConfirmed.Nonce > latestEthereumValsetNonce.Uint64() { + + // Check custom time delay offset + blockResult, err := s.injective.GetBlock(ctx, int64(latestCosmosConfirmed.Height)) + if err != nil { + return err + } + valsetCreatedAt := blockResult.Block.Time + // todo: do this at init + //relayValsetOffsetDur, err := time.ParseDuration(s.relayValsetOffsetDur) + //if err != nil { + // return err + //} + customTimeDelay := valsetCreatedAt.Add(s.relayValsetOffsetDur) + if time.Now().Sub(customTimeDelay) <= 0 { + return nil + } + + log.Infof("Detected latest cosmos valset nonce %d, but latest valset on Ethereum is %d. Sending update to Ethereum\n", + latestCosmosConfirmed.Nonce, latestEthereumValsetNonce.Uint64()) + + // todo + txHash, err := s.ethereum.SendEthValsetUpdate( + ctx, + currentEthValset, + latestCosmosConfirmed, + latestCosmosSigs, + ) + + // Send Valset Update to Ethereum + //txHash, err := s.peggyContract.SendEthValsetUpdate( + // ctx, + // currentEthValset, + // latestCosmosConfirmed, + // latestCosmosSigs, + //) + if err != nil { + metrics.ReportFuncError(s.svcTags) + return err + } + + log.WithField("tx_hash", txHash.Hex()).Infoln("Sent Ethereum Tx (EthValsetUpdate)") + } + + } + + return nil +} + +func (s *PeggyOrchestrator) relayBatches(ctx context.Context) error { + return nil +} + +const valsetBlocksToSearch = 2000 + +// FindLatestValset finds the latest valset on the Peggy contract by looking back through the event +// history and finding the most recent ValsetUpdatedEvent. Most of the time this will be very fast +// as the latest update will be in recent blockchain history and the search moves from the present +// backwards in time. In the case that the validator set has not been updated for a very long time +// this will take longer. +func (s *PeggyOrchestrator) findLatestValset(ctx context.Context) (*types.Valset, error) { + metrics.ReportFuncCall(s.svcTags) + doneFn := metrics.ReportFuncTiming(s.svcTags) + defer doneFn() + + latestHeader, err := s.ethereum.HeaderByNumber(ctx, nil) + //latestHeader, err := s.ethProvider.HeaderByNumber(ctx, nil) + if err != nil { + metrics.ReportFuncError(s.svcTags) + err = errors.Wrap(err, "failed to get latest header") + return nil, err + } + currentBlock := latestHeader.Number.Uint64() + + //peggyFilterer, err := wrappers.NewPeggyFilterer(s.peggyContract.Address(), s.ethProvider) + //if err != nil { + // metrics.ReportFuncError(s.svcTags) + // err = errors.Wrap(err, "failed to init Peggy events filterer") + // return nil, err + //} + + latestEthereumValsetNonce, err := s.ethereum.GetValsetNonce(ctx) + //latestEthereumValsetNonce, err := s.peggyContract.GetValsetNonce(ctx, s.peggyContract.FromAddress()) + if err != nil { + metrics.ReportFuncError(s.svcTags) + err = errors.Wrap(err, "failed to get latest Valset nonce") + return nil, err + } + + cosmosValset, err := s.injective.ValsetAt(ctx, latestEthereumValsetNonce.Uint64()) + //cosmosValset, err := s.cosmosQueryClient.ValsetAt(ctx, latestEthereumValsetNonce.Uint64()) + if err != nil { + metrics.ReportFuncError(s.svcTags) + err = errors.Wrap(err, "failed to get cosmos Valset") + return nil, err + } + + for currentBlock > 0 { + log.WithField("current_block", currentBlock). + Debugln("About to submit a Valset or Batch looking back into the history to find the last Valset Update") + + var endSearchBlock uint64 + if currentBlock <= valsetBlocksToSearch { + endSearchBlock = 0 + } else { + endSearchBlock = currentBlock - valsetBlocksToSearch + } + + valsetUpdatedEvents, err := s.ethereum.GetValsetUpdatedEvents(endSearchBlock, currentBlock) + if err != nil { + metrics.ReportFuncError(s.svcTags) + err = errors.Wrap(err, "failed to filter past ValsetUpdated events from Ethereum") + return nil, err + } + //var valsetUpdatedEvents []*wrappers.PeggyValsetUpdatedEvent + //iter, err := peggyFilterer.FilterValsetUpdatedEvent(&bind.FilterOpts{ + // Start: endSearchBlock, + // End: ¤tBlock, + //}, nil) + //} else { + // for iter.Next() { + // valsetUpdatedEvents = append(valsetUpdatedEvents, iter.Event) + // } + // + // iter.Close() + //} + + // by default the lowest found valset goes first, we want the highest + // + // TODO(xlab): this follows the original impl, but sort might be skipped there: + // we could access just the latest element later. + sort.Sort(sort.Reverse(PeggyValsetUpdatedEvents(valsetUpdatedEvents))) + + log.Debugln("found events", valsetUpdatedEvents) + + if len(valsetUpdatedEvents) == 0 { + currentBlock = endSearchBlock + continue + } + + // we take only the first event if we find any at all. + event := valsetUpdatedEvents[0] + valset := &types.Valset{ + Nonce: event.NewValsetNonce.Uint64(), + Members: make([]*types.BridgeValidator, 0, len(event.Powers)), + RewardAmount: sdk.NewIntFromBigInt(event.RewardAmount), + RewardToken: event.RewardToken.Hex(), + } + + for idx, p := range event.Powers { + valset.Members = append(valset.Members, &types.BridgeValidator{ + Power: p.Uint64(), + EthereumAddress: event.Validators[idx].Hex(), + }) + } + + s.checkIfValsetsDiffer(cosmosValset, valset) + return valset, nil + + } + + return nil, ErrNotFound +} + +var ErrNotFound = errors.New("not found") + +type PeggyValsetUpdatedEvents []*wrappers.PeggyValsetUpdatedEvent + +func (a PeggyValsetUpdatedEvents) Len() int { return len(a) } +func (a PeggyValsetUpdatedEvents) Less(i, j int) bool { + return a[i].NewValsetNonce.Cmp(a[j].NewValsetNonce) < 0 +} +func (a PeggyValsetUpdatedEvents) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// This function exists to provide a warning if Cosmos and Ethereum have different validator sets +// for a given nonce. In the mundane version of this warning the validator sets disagree on sorting order +// which can happen if some relayer uses an unstable sort, or in a case of a mild griefing attack. +// The Peggy contract validates signatures in order of highest to lowest power. That way it can exit +// the loop early once a vote has enough power, if a relayer where to submit things in the reverse order +// they could grief users of the contract into paying more in gas. +// The other (and far worse) way a disagreement here could occur is if validators are colluding to steal +// funds from the Peggy contract and have submitted a hijacking update. If slashing for off Cosmos chain +// Ethereum signatures is implemented you would put that handler here. +func (s *PeggyOrchestrator) checkIfValsetsDiffer(cosmosValset, ethereumValset *types.Valset) { + if cosmosValset == nil && ethereumValset.Nonce == 0 { + // bootstrapping case + return + } else if cosmosValset == nil { + log.WithField( + "eth_valset_nonce", + ethereumValset.Nonce, + ).Errorln("Cosmos does not have a valset for nonce from Ethereum chain. Possible bridge hijacking!") + return + } + + if cosmosValset.Nonce != ethereumValset.Nonce { + log.WithFields(log.Fields{ + "cosmos_valset_nonce": cosmosValset.Nonce, + "eth_valset_nonce": ethereumValset.Nonce, + }).Errorln("Cosmos does have a wrong valset nonce, differs from Ethereum chain. Possible bridge hijacking!") + return + } + + if len(cosmosValset.Members) != len(ethereumValset.Members) { + log.WithFields(log.Fields{ + "cosmos_valset": len(cosmosValset.Members), + "eth_valset": len(ethereumValset.Members), + }).Errorln("Cosmos and Ethereum Valsets have different length. Possible bridge hijacking!") + return + } + + BridgeValidators(cosmosValset.Members).Sort() + BridgeValidators(ethereumValset.Members).Sort() + + for idx, member := range cosmosValset.Members { + if ethereumValset.Members[idx].EthereumAddress != member.EthereumAddress { + log.Errorln("Valsets are different, a sorting error?") + } + if ethereumValset.Members[idx].Power != member.Power { + log.Errorln("Valsets are different, a sorting error?") + } + } +} + +type BridgeValidators []*types.BridgeValidator + +// Sort sorts the validators by power +func (b BridgeValidators) Sort() { + sort.Slice(b, func(i, j int) bool { + if b[i].Power == b[j].Power { + // Secondary sort on eth address in case powers are equal + return util.EthAddrLessThan(b[i].EthereumAddress, b[j].EthereumAddress) + } + return b[i].Power > b[j].Power + }) +} + +// HasDuplicates returns true if there are duplicates in the set +func (b BridgeValidators) HasDuplicates() bool { + m := make(map[string]struct{}, len(b)) + for i := range b { + m[b[i].EthereumAddress] = struct{}{} + } + return len(m) != len(b) +} + +// GetPowers returns only the power values for all members +func (b BridgeValidators) GetPowers() []uint64 { + r := make([]uint64, len(b)) + for i := range b { + r[i] = b[i].Power + } + return r +} diff --git a/orchestrator/relayer_test.go b/orchestrator/relayer_test.go new file mode 100644 index 00000000..bba513ed --- /dev/null +++ b/orchestrator/relayer_test.go @@ -0,0 +1,543 @@ +package orchestrator + +import ( + "context" + wrappers "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" + "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + cosmtypes "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + ctypes "github.com/ethereum/go-ethereum/core/types" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + tmctypes "github.com/tendermint/tendermint/rpc/core/types" + types2 "github.com/tendermint/tendermint/types" + "math/big" + "testing" + time "time" +) + +func TestValsetRelaying(t *testing.T) { + t.Parallel() + + t.Run("failed to fetch latest valsets from injective", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return nil, errors.New("fail") + }, + }, + } + + assert.Error(t, orch.relayValsets(context.TODO())) + }) + + t.Run("failed to fetch confirms for a valset", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{{}}, nil // non-empty will do + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return nil, errors.New("fail") + }, + }, + } + + assert.Error(t, orch.relayValsets(context.TODO())) + }) + + t.Run("no confirms for valset", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{{}}, nil // non-empty will do + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return nil, nil + }, + }, + } + + assert.NoError(t, orch.relayValsets(context.TODO())) + }) + + t.Run("failed to get latest ethereum header", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{{}}, nil // non-empty will do + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil + }, + }, + ethereum: mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return nil, errors.New("fail") + }, + }, + } + + assert.Error(t, orch.relayValsets(context.TODO())) + }) + + t.Run("failed to get latest ethereum header", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{{}}, nil // non-empty will do + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil + }, + }, + ethereum: mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return nil, errors.New("fail") + }, + }, + } + + assert.Error(t, orch.relayValsets(context.TODO())) + }) + + t.Run("failed to get valset nonce from peggy contract", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{{}}, nil // non-empty will do + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil + }, + }, + ethereum: mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(123)}, nil + }, + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return nil, errors.New("fail") + }, + }, + } + + assert.Error(t, orch.relayValsets(context.TODO())) + }) + + t.Run("failed to get specific valset from injective", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{{}}, nil // non-empty will do + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return nil, errors.New("fail") + }, + }, + ethereum: mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(123)}, nil + }, + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + }, + } + + assert.Error(t, orch.relayValsets(context.TODO())) + }) + + t.Run("failed to get valset update events from ethereum", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{{}}, nil // non-empty will do + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{}, nil // non-empty will do + }, + }, + ethereum: mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(123)}, nil + }, + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return nil, errors.New("fail") + }, + }, + } + + assert.Error(t, orch.relayValsets(context.TODO())) + }) + + t.Run("ethereum valset is not higher than injective valset", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{ + { + Nonce: 333, + RewardAmount: cosmtypes.NewInt(1000), + RewardToken: "0xfafafafafafafafa", + }, + }, nil + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{ + Nonce: 333, + RewardAmount: cosmtypes.NewInt(1000), + RewardToken: "0xfafafafafafafafa", + }, nil // non-empty will do + }, + }, + ethereum: mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(123)}, nil + }, + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(333), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xfafafafafafafafa"), + }, + }, nil + }, + }, + } + + assert.NoError(t, orch.relayValsets(context.TODO())) + }) + + t.Run("injective valset is higher than ethereum but failed to get block from injective", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{ + { + Nonce: 444, + RewardAmount: cosmtypes.NewInt(1000), + RewardToken: "0xfafafafafafafafa", + }, + }, nil + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{ + Nonce: 333, + RewardAmount: cosmtypes.NewInt(1000), + RewardToken: "0xfafafafafafafafa", + }, nil // non-empty will do + }, + getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { + return nil, errors.New("fail") + }, + }, + ethereum: mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(123)}, nil + }, + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(333), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xfafafafafafafafa"), + }, + }, nil + }, + }, + } + + assert.Error(t, orch.relayValsets(context.TODO())) + }) + + t.Run("injective valset is higher than ethereum but valsetOffsetDur has not expired", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + relayValsetOffsetDur: time.Second * 5, + injective: &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{ + { + Nonce: 444, + RewardAmount: cosmtypes.NewInt(1000), + RewardToken: "0xfafafafafafafafa", + }, + }, nil + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{ + Nonce: 333, + RewardAmount: cosmtypes.NewInt(1000), + RewardToken: "0xfafafafafafafafa", + }, nil // non-empty will do + }, + getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { + return &tmctypes.ResultBlock{ + Block: &types2.Block{ + Header: types2.Header{ + Time: time.Now().Add(time.Hour), + }, + }, + }, nil + }, + }, + ethereum: mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(123)}, nil + }, + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(333), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xfafafafafafafafa"), + }, + }, nil + }, + }, + } + + assert.NoError(t, orch.relayValsets(context.TODO())) + }) + + t.Run("injective valset is higher than ethereum but failed to send update tx to ethereum", func(t *testing.T) { + t.Parallel() + + oldTime := time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC) + orch := &PeggyOrchestrator{ + relayValsetOffsetDur: time.Second * 5, + injective: &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{ + { + Nonce: 444, + RewardAmount: cosmtypes.NewInt(1000), + RewardToken: "0xfafafafafafafafa", + }, + }, nil + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{ + Nonce: 333, + RewardAmount: cosmtypes.NewInt(1000), + RewardToken: "0xfafafafafafafafa", + }, nil // non-empty will do + }, + getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { + return &tmctypes.ResultBlock{ + Block: &types2.Block{ + Header: types2.Header{ + Time: oldTime, + }, + }, + }, nil + }, + }, + ethereum: mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(123)}, nil + }, + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(333), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xfafafafafafafafa"), + }, + }, nil + }, + sendEthValsetUpdateFn: func(_ context.Context, _ *types.Valset, _ *types.Valset, _ []*types.MsgValsetConfirm) (*common.Hash, error) { + return nil, errors.New("fail") + }, + }, + } + + assert.Error(t, orch.relayValsets(context.TODO())) + }) + + t.Run("new valset update is sent to ethereum", func(t *testing.T) { + t.Parallel() + + oldTime := time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC) + orch := &PeggyOrchestrator{ + relayValsetOffsetDur: time.Second * 5, + injective: &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{ + { + Nonce: 444, + RewardAmount: cosmtypes.NewInt(1000), + RewardToken: "0xfafafafafafafafa", + }, + }, nil + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{ + Nonce: 333, + RewardAmount: cosmtypes.NewInt(1000), + RewardToken: "0xfafafafafafafafa", + }, nil // non-empty will do + }, + getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { + return &tmctypes.ResultBlock{ + Block: &types2.Block{ + Header: types2.Header{ + Time: oldTime, + }, + }, + }, nil + }, + }, + ethereum: mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(123)}, nil + }, + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(333), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xfafafafafafafafa"), + }, + }, nil + }, + sendEthValsetUpdateFn: func(_ context.Context, _ *types.Valset, _ *types.Valset, _ []*types.MsgValsetConfirm) (*common.Hash, error) { + return &common.Hash{}, nil + }, + }, + } + + assert.NoError(t, orch.relayValsets(context.TODO())) + }) +} From 5a7ff948e2461f32d5b493e74cb3d50f471b148b Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Tue, 13 Jun 2023 13:56:11 +0200 Subject: [PATCH 18/72] add relayer tests: batches --- orchestrator/mocks_test.go | 29 +++ orchestrator/orchestrator.go | 15 ++ orchestrator/relayer.go | 90 +++++++ orchestrator/relayer_test.go | 464 +++++++++++++++++++++++++++++++++++ 4 files changed, 598 insertions(+) diff --git a/orchestrator/mocks_test.go b/orchestrator/mocks_test.go index de7cc900..27cb0a9d 100644 --- a/orchestrator/mocks_test.go +++ b/orchestrator/mocks_test.go @@ -54,6 +54,9 @@ type mockInjective struct { allValsetConfirmsFn func(context.Context, uint64) ([]*peggytypes.MsgValsetConfirm, error) valsetAtFn func(context.Context, uint64) (*peggytypes.Valset, error) + + latestTransactionBatchesFn func(context.Context) ([]*peggytypes.OutgoingTxBatch, error) + transactionBatchSignaturesFn func(context.Context, uint64, eth.Address) ([]*peggytypes.MsgConfirmBatch, error) } func (i *mockInjective) UnbatchedTokenFees(ctx context.Context) ([]*peggytypes.BatchFees, error) { @@ -135,6 +138,14 @@ func (i *mockInjective) ValsetAt(ctx context.Context, nonce uint64) (*peggytypes return i.valsetAtFn(ctx, nonce) } +func (i *mockInjective) LatestTransactionBatches(ctx context.Context) ([]*peggytypes.OutgoingTxBatch, error) { + return i.latestTransactionBatchesFn(ctx) +} + +func (i *mockInjective) TransactionBatchSignatures(ctx context.Context, nonce uint64, tokenContract eth.Address) ([]*peggytypes.MsgConfirmBatch, error) { + return i.transactionBatchSignaturesFn(ctx, nonce, tokenContract) +} + type mockEthereum struct { headerByNumberFn func(context.Context, *big.Int) (*ethtypes.Header, error) getSendToCosmosEventsFn func(uint64, uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) @@ -145,6 +156,8 @@ type mockEthereum struct { getPeggyIDFn func(context.Context) (eth.Hash, error) getValsetNonceFn func(context.Context) (*big.Int, error) sendEthValsetUpdateFn func(context.Context, *peggytypes.Valset, *peggytypes.Valset, []*peggytypes.MsgValsetConfirm) (*eth.Hash, error) + getTxBatchNonceFn func(context.Context, eth.Address) (*big.Int, error) + sendTransactionBatchFn func(context.Context, *peggytypes.Valset, *peggytypes.OutgoingTxBatch, []*peggytypes.MsgConfirmBatch) (*eth.Hash, error) } func (e mockEthereum) HeaderByNumber(ctx context.Context, number *big.Int) (*ethtypes.Header, error) { @@ -187,3 +200,19 @@ func (e mockEthereum) SendEthValsetUpdate( ) (*eth.Hash, error) { return e.sendEthValsetUpdateFn(ctx, oldValset, newValset, confirms) } + +func (e mockEthereum) GetTxBatchNonce( + ctx context.Context, + erc20ContractAddress eth.Address, +) (*big.Int, error) { + return e.getTxBatchNonceFn(ctx, erc20ContractAddress) +} + +func (e mockEthereum) SendTransactionBatch( + ctx context.Context, + currentValset *peggytypes.Valset, + batch *peggytypes.OutgoingTxBatch, + confirms []*peggytypes.MsgConfirmBatch, +) (*eth.Hash, error) { + return e.sendTransactionBatchFn(ctx, currentValset, batch, confirms) +} diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index 6c373ae5..08c803ab 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -59,6 +59,9 @@ type InjectiveNetwork interface { LatestValsets(ctx context.Context) ([]*peggytypes.Valset, error) AllValsetConfirms(ctx context.Context, nonce uint64) ([]*peggytypes.MsgValsetConfirm, error) ValsetAt(ctx context.Context, nonce uint64) (*peggytypes.Valset, error) + + LatestTransactionBatches(ctx context.Context) ([]*peggytypes.OutgoingTxBatch, error) + TransactionBatchSignatures(ctx context.Context, nonce uint64, tokenContract ethcmn.Address) ([]*peggytypes.MsgConfirmBatch, error) } type EthereumNetwork interface { @@ -78,6 +81,18 @@ type EthereumNetwork interface { newValset *peggytypes.Valset, confirms []*peggytypes.MsgValsetConfirm, ) (*ethcmn.Hash, error) + + GetTxBatchNonce( + ctx context.Context, + erc20ContractAddress ethcmn.Address, + ) (*big.Int, error) + + SendTransactionBatch( + ctx context.Context, + currentValset *peggytypes.Valset, + batch *peggytypes.OutgoingTxBatch, + confirms []*peggytypes.MsgConfirmBatch, + ) (*ethcmn.Hash, error) } type PeggyOrchestrator struct { diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index edbe3fd6..cdacabe5 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -9,6 +9,7 @@ import ( "github.com/InjectiveLabs/sdk-go/chain/peggy/types" "github.com/avast/retry-go" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" log "github.com/xlab/suplog" "sort" @@ -163,6 +164,95 @@ func (s *PeggyOrchestrator) relayValsets(ctx context.Context) error { } func (s *PeggyOrchestrator) relayBatches(ctx context.Context) error { + metrics.ReportFuncCall(s.svcTags) + doneFn := metrics.ReportFuncTiming(s.svcTags) + defer doneFn() + + latestBatches, err := s.injective.LatestTransactionBatches(ctx) + //latestBatches, err := s.cosmosQueryClient.LatestTransactionBatches(ctx) + if err != nil { + metrics.ReportFuncError(s.svcTags) + return err + } + var oldestSignedBatch *types.OutgoingTxBatch + var oldestSigs []*types.MsgConfirmBatch + for _, batch := range latestBatches { + sigs, err := s.injective.TransactionBatchSignatures(ctx, batch.BatchNonce, common.HexToAddress(batch.TokenContract)) + if err != nil { + metrics.ReportFuncError(s.svcTags) + return err + } else if len(sigs) == 0 { + continue + } + + oldestSignedBatch = batch + oldestSigs = sigs + } + if oldestSignedBatch == nil { + log.Debugln("could not find batch with signatures, nothing to relay") + return nil + } + + latestEthereumBatch, err := s.ethereum.GetTxBatchNonce( + ctx, + common.HexToAddress(oldestSignedBatch.TokenContract), + ) + if err != nil { + metrics.ReportFuncError(s.svcTags) + return err + } + + currentValset, err := s.findLatestValset(ctx) + if err != nil { + metrics.ReportFuncError(s.svcTags) + return errors.New("failed to find latest valset") + } else if currentValset == nil { + metrics.ReportFuncError(s.svcTags) + return errors.New("latest valset not found") + } + + log.WithFields(log.Fields{"oldestSignedBatchNonce": oldestSignedBatch.BatchNonce, "latestEthereumBatchNonce": latestEthereumBatch.Uint64()}).Debugln("Found Latest valsets") + + if oldestSignedBatch.BatchNonce > latestEthereumBatch.Uint64() { + + latestEthereumBatch, err := s.ethereum.GetTxBatchNonce( + ctx, + common.HexToAddress(oldestSignedBatch.TokenContract), + ) + if err != nil { + metrics.ReportFuncError(s.svcTags) + return err + } + // Check if oldestSignedBatch already submitted by other validators in mean time + if oldestSignedBatch.BatchNonce > latestEthereumBatch.Uint64() { + + // Check custom time delay offset + blockResult, err := s.injective.GetBlock(ctx, int64(oldestSignedBatch.Block)) + if err != nil { + return err + } + batchCreatedAt := blockResult.Block.Time + //relayBatchOffsetDur, err := time.ParseDuration() + //if err != nil { + // return err + //} + customTimeDelay := batchCreatedAt.Add(s.relayBatchOffsetDur) + if time.Now().Sub(customTimeDelay) <= 0 { + return nil + } + + log.Infof("We have detected latest batch %d but latest on Ethereum is %d sending an update!", oldestSignedBatch.BatchNonce, latestEthereumBatch) + + // Send SendTransactionBatch to Ethereum + txHash, err := s.ethereum.SendTransactionBatch(ctx, currentValset, oldestSignedBatch, oldestSigs) + if err != nil { + metrics.ReportFuncError(s.svcTags) + return err + } + log.WithField("tx_hash", txHash.Hex()).Infoln("Sent Ethereum Tx (TransactionBatch)") + } + } + return nil } diff --git a/orchestrator/relayer_test.go b/orchestrator/relayer_test.go index bba513ed..f72a1dd4 100644 --- a/orchestrator/relayer_test.go +++ b/orchestrator/relayer_test.go @@ -541,3 +541,467 @@ func TestValsetRelaying(t *testing.T) { assert.NoError(t, orch.relayValsets(context.TODO())) }) } + +func TestBatchRelaying(t *testing.T) { + t.Parallel() + + t.Run("failed to get latest batches from injective", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return nil, errors.New("fail") + }, + }, + } + + assert.Error(t, orch.relayBatches(context.TODO())) + }) + + t.Run("failed to get latest batches from injective", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{{}}, nil // non-empty will do + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return nil, errors.New("fail") + }, + }, + } + + assert.Error(t, orch.relayBatches(context.TODO())) + }) + + t.Run("no batch confirms", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{{}}, nil // non-empty will do + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return nil, nil + }, + }, + } + + assert.NoError(t, orch.relayBatches(context.TODO())) + }) + + t.Run("failed to get batch nonce from ethereum", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{{}}, nil // non-empty will do + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do + }, + }, + ethereum: mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return nil, errors.New("fail") + }, + }, + } + + assert.Error(t, orch.relayBatches(context.TODO())) + }) + + t.Run("failed to get latest ethereum header", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{{TokenContract: "tokenContract"}}, nil + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do + }, + }, + ethereum: mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return nil, nil + }, + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return nil, errors.New("fail") + }, + }, + } + + assert.Error(t, orch.relayBatches(context.TODO())) + }) + + t.Run("failed to get valset nonce from ethereum", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{{TokenContract: "tokenContract"}}, nil + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do + }, + }, + ethereum: mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return nil, nil + }, + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(100)}, nil + }, + + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return nil, errors.New("fail") + }, + }, + } + + assert.Error(t, orch.relayBatches(context.TODO())) + }) + + t.Run("failed to get specific valset from injective", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{{TokenContract: "tokenContract"}}, nil + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return nil, errors.New("fail") + }, + }, + ethereum: mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return nil, nil + }, + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(100)}, nil + }, + + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + }, + } + + assert.Error(t, orch.relayBatches(context.TODO())) + }) + + t.Run("failed to get valset updated events from ethereum", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{{TokenContract: "tokenContract"}}, nil + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{}, nil + }, + }, + ethereum: mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return nil, nil + }, + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(100)}, nil + }, + + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return nil, errors.New("fail") + }, + }, + } + + assert.Error(t, orch.relayBatches(context.TODO())) + }) + + t.Run("ethereum batch is not lower than injective batch", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{ + { + TokenContract: "tokenContract", + BatchNonce: 202, + }, + }, nil + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{Nonce: 202}, nil + }, + }, + ethereum: mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return big.NewInt(202), nil + }, + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(100)}, nil + }, + + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(202), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xcafecafecafecafe"), + }, + }, nil + }, + }, + } + + assert.NoError(t, orch.relayBatches(context.TODO())) + }) + + t.Run("ethereum batch is lower than injective batch but failed to get block from injhective", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + injective: &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{ + { + TokenContract: "tokenContract", + BatchNonce: 202, + }, + }, nil + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{Nonce: 202}, nil + }, + getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { + return nil, errors.New("fail") + }, + }, + ethereum: mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return big.NewInt(201), nil + }, + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(100)}, nil + }, + + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(202), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xcafecafecafecafe"), + }, + }, nil + }, + }, + } + + assert.Error(t, orch.relayBatches(context.TODO())) + }) + + t.Run("ethereum batch is lower than injective batch but relayBatchOffsetDur has not expired", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + relayBatchOffsetDur: 5 * time.Second, + injective: &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{ + { + TokenContract: "tokenContract", + BatchNonce: 202, + }, + }, nil + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{Nonce: 202}, nil + }, + getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { + return &tmctypes.ResultBlock{ + Block: &types2.Block{ + Header: types2.Header{ + Time: time.Now().Add(time.Hour), + }, + }, + }, nil + }, + }, + ethereum: mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return big.NewInt(201), nil + }, + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(100)}, nil + }, + + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(202), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xcafecafecafecafe"), + }, + }, nil + }, + }, + } + + assert.NoError(t, orch.relayBatches(context.TODO())) + }) + + t.Run("ethereum batch is lower than injective batch but failed to send batch update", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + relayBatchOffsetDur: 5 * time.Second, + injective: &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{ + { + TokenContract: "tokenContract", + BatchNonce: 202, + }, + }, nil + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{Nonce: 202}, nil + }, + getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { + return &tmctypes.ResultBlock{ + Block: &types2.Block{ + Header: types2.Header{ + Time: time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC), + }, + }, + }, nil + }, + }, + ethereum: mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return big.NewInt(201), nil + }, + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(100)}, nil + }, + + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(202), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xcafecafecafecafe"), + }, + }, nil + }, + sendTransactionBatchFn: func(_ context.Context, _ *types.Valset, _ *types.OutgoingTxBatch, _ []*types.MsgConfirmBatch) (*common.Hash, error) { + return nil, errors.New("fail") + }, + }, + } + + assert.Error(t, orch.relayBatches(context.TODO())) + }) + + t.Run("sending a batch update to ethereum", func(t *testing.T) { + t.Parallel() + + orch := &PeggyOrchestrator{ + relayBatchOffsetDur: 5 * time.Second, + injective: &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{ + { + TokenContract: "tokenContract", + BatchNonce: 202, + }, + }, nil + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{Nonce: 202}, nil + }, + getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { + return &tmctypes.ResultBlock{ + Block: &types2.Block{ + Header: types2.Header{ + Time: time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC), + }, + }, + }, nil + }, + }, + ethereum: mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return big.NewInt(201), nil + }, + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(100)}, nil + }, + + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(202), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xcafecafecafecafe"), + }, + }, nil + }, + sendTransactionBatchFn: func(_ context.Context, _ *types.Valset, _ *types.OutgoingTxBatch, _ []*types.MsgConfirmBatch) (*common.Hash, error) { + return &common.Hash{}, nil + }, + }, + } + + assert.NoError(t, orch.relayBatches(context.TODO())) + }) +} From d63fb96f4fc1af3c66a8d03b8b5ceabaabdeda6f Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 14 Jun 2023 10:49:52 +0200 Subject: [PATCH 19/72] replace cosmosQuery with injective interface --- orchestrator/oracle.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index dbffd425..a1daae53 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -31,9 +31,9 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { var lastCheckedBlock uint64 if err := retry.Do(func() (err error) { - lastCheckedBlock, err = s.getLastKnownEthHeight(ctx) + lastCheckedBlock, err = s.getLastConfirmedEthHeight(ctx) if lastCheckedBlock == 0 { - peggyParams, err := s.cosmosQueryClient.PeggyParams(ctx) + peggyParams, err := s.injective.PeggyParams(ctx) if err != nil { log.WithError(err).Fatalln("failed to query peggy params, is injectived running?") } @@ -72,7 +72,7 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { **/ if time.Since(lastResync) >= 48*time.Hour { if err := retry.Do(func() (err error) { - lastCheckedBlock, err = s.getLastKnownEthHeight(ctx) + lastCheckedBlock, err = s.getLastConfirmedEthHeight(ctx) return }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { logger.WithError(err).Warningf("failed to get last checked block, will retry (%d)", n) @@ -88,13 +88,13 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { }) } -// getLastKnownEthHeight retrieves the last claim event this oracle has relayed to Cosmos. -func (s *PeggyOrchestrator) getLastKnownEthHeight(ctx context.Context) (uint64, error) { +// getLastConfirmedEthHeight retrieves the last claim event this oracle has relayed to Cosmos. +func (s *PeggyOrchestrator) getLastConfirmedEthHeight(ctx context.Context) (uint64, error) { metrics.ReportFuncCall(s.svcTags) doneFn := metrics.ReportFuncTiming(s.svcTags) defer doneFn() - lastClaimEvent, err := s.cosmosQueryClient.LastClaimEventByAddr(ctx, s.peggyBroadcastClient.AccFromAddress()) + lastClaimEvent, err := s.injective.LastClaimEvent(ctx) if err != nil { metrics.ReportFuncError(s.svcTags) return uint64(0), err From 68844e318e3f751f788d940008b6f785d5f5dba7 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 14 Jun 2023 11:35:51 +0200 Subject: [PATCH 20/72] cleanup oracle --- orchestrator/oracle.go | 70 +++++++++++++++++++++++-------------- orchestrator/oracle_test.go | 39 +++++++++++++-------- 2 files changed, 69 insertions(+), 40 deletions(-) diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index a1daae53..8e074d64 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -2,16 +2,15 @@ package orchestrator import ( "context" - "github.com/InjectiveLabs/peggo/orchestrator/loops" - "github.com/avast/retry-go" "strings" "time" + "github.com/avast/retry-go" "github.com/pkg/errors" log "github.com/xlab/suplog" "github.com/InjectiveLabs/metrics" - + "github.com/InjectiveLabs/peggo/orchestrator/loops" wrappers "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" ) @@ -28,40 +27,55 @@ const ( func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { logger := log.WithField("loop", "EthOracleMainLoop") lastResync := time.Now() - var lastCheckedBlock uint64 - if err := retry.Do(func() (err error) { - lastCheckedBlock, err = s.getLastConfirmedEthHeight(ctx) - if lastCheckedBlock == 0 { + var lastConfirmedEthHeight uint64 + retryFn := func() error { + height, err := s.getLastConfirmedEthHeight(ctx) + if err != nil { + logger.WithError(err).Warningf("failed to get last claim. Querying peggy params...") + } + + if height == 0 { peggyParams, err := s.injective.PeggyParams(ctx) if err != nil { log.WithError(err).Fatalln("failed to query peggy params, is injectived running?") } - lastCheckedBlock = peggyParams.BridgeContractStartHeight + height = peggyParams.BridgeContractStartHeight } - return - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get last checked block, will retry (%d)", n) - })); err != nil { + lastConfirmedEthHeight = height + return nil + } + + if err := retry.Do(retryFn, + retry.Context(ctx), + retry.Attempts(s.maxRetries), + retry.OnRetry(func(n uint, err error) { + logger.WithError(err).Warningf("failed to get last checked block, will retry (%d)", n) + }), + ); err != nil { logger.WithError(err).Errorln("got error, loop exits") return err } - logger.WithField("lastCheckedBlock", lastCheckedBlock).Infoln("Start scanning for events") + logger.WithField("lastConfirmedEthHeight", lastConfirmedEthHeight).Infoln("Start scanning for events") return loops.RunLoop(ctx, defaultLoopDur, func() error { // Relays events from Ethereum -> Cosmos - var currentBlock uint64 + var currentHeight uint64 if err := retry.Do(func() (err error) { - currentBlock, err = s.relayEthEvents(ctx, lastCheckedBlock) + currentHeight, err = s.relayEthEvents(ctx, lastConfirmedEthHeight) return - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("error during Eth event checking, will retry (%d)", n) - })); err != nil { + }, + retry.Context(ctx), + retry.Attempts(s.maxRetries), + retry.OnRetry(func(n uint, err error) { + logger.WithError(err).Warningf("error during Eth event checking, will retry (%d)", n) + }), + ); err != nil { logger.WithError(err).Errorln("got error, loop exits") return err } - lastCheckedBlock = currentBlock + lastConfirmedEthHeight = currentHeight /* Auto re-sync to catch up the nonce. Reasons why event nonce fall behind. @@ -72,16 +86,21 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { **/ if time.Since(lastResync) >= 48*time.Hour { if err := retry.Do(func() (err error) { - lastCheckedBlock, err = s.getLastConfirmedEthHeight(ctx) + lastConfirmedEthHeight, err = s.getLastConfirmedEthHeight(ctx) return - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get last checked block, will retry (%d)", n) - })); err != nil { + }, + retry.Context(ctx), + retry.Attempts(s.maxRetries), + retry.OnRetry(func(n uint, err error) { + logger.WithError(err).Warningf("failed to get last checked block, will retry (%d)", n) + }), + ); err != nil { logger.WithError(err).Errorln("got error, loop exits") return err } + lastResync = time.Now() - logger.WithFields(log.Fields{"lastResync": lastResync, "lastCheckedBlock": lastCheckedBlock}).Infoln("Auto resync") + logger.WithFields(log.Fields{"lastResync": lastResync, "lastConfirmedEthHeight": lastConfirmedEthHeight}).Infoln("Auto resync") } return nil @@ -122,12 +141,11 @@ func (s *PeggyOrchestrator) relayEthEvents( // add delay to ensure minimum confirmations are received and block is finalised currentBlock := latestHeader.Number.Uint64() - ethBlockConfirmationDelay - if currentBlock < startingBlock { return currentBlock, nil } - if currentBlock > defaultBlocksToSearch+startingBlock { + if currentBlock > startingBlock+defaultBlocksToSearch { currentBlock = startingBlock + defaultBlocksToSearch } diff --git a/orchestrator/oracle_test.go b/orchestrator/oracle_test.go index 53509da7..91ce2b7b 100644 --- a/orchestrator/oracle_test.go +++ b/orchestrator/oracle_test.go @@ -16,34 +16,38 @@ import ( func TestRelayEvents(t *testing.T) { t.Parallel() - t.Run("ethereum cannot fetch latest header", func(t *testing.T) { + t.Run("failed to get latest header from ethereum", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ethereum: mockEthereum{ - headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { - return nil, errors.New("fail") + orch := &PeggyOrchestrator{ + ethereum: mockEthereum{ + headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return nil, errors.New("fail") + }, }, - }} + } _, err := orch.relayEthEvents(context.TODO(), 0) assert.Error(t, err) }) - t.Run("ethereum returns an older block", func(t *testing.T) { + t.Run("latest ethereum header is old", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ethereum: mockEthereum{ - headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { - return &types.Header{Number: big.NewInt(50)}, nil + orch := &PeggyOrchestrator{ + ethereum: mockEthereum{ + headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return &types.Header{Number: big.NewInt(50)}, nil + }, }, - }} + } currentBlock, err := orch.relayEthEvents(context.TODO(), 100) assert.NoError(t, err) assert.Equal(t, currentBlock, 50-ethBlockConfirmationDelay) }) - t.Run("failed to fetch SendToCosmos events", func(t *testing.T) { + t.Run("failed to get SendToCosmos events", func(t *testing.T) { t.Parallel() orch := &PeggyOrchestrator{ @@ -70,8 +74,10 @@ func TestRelayEvents(t *testing.T) { return &types.Header{Number: big.NewInt(200)}, nil }, getSendToCosmosEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToCosmosEvent, error) { - return []*wrappers.PeggySendToCosmosEvent{}, nil + return []*wrappers.PeggySendToCosmosEvent{}, nil // empty slice will do }, + + // no-ops getTransactionBatchExecutedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyTransactionBatchExecutedEvent, error) { return nil, nil }, @@ -85,6 +91,7 @@ func TestRelayEvents(t *testing.T) { return nil, nil }, }, + injective: &mockInjective{ lastClaimEventFn: func(context.Context) (*peggytypes.LastClaimEvent, error) { return nil, errors.New("fail") @@ -117,6 +124,7 @@ func TestRelayEvents(t *testing.T) { } orch := &PeggyOrchestrator{ + injective: inj, ethereum: mockEthereum{ headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { return &types.Header{Number: big.NewInt(200)}, nil @@ -124,6 +132,8 @@ func TestRelayEvents(t *testing.T) { getSendToCosmosEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToCosmosEvent, error) { return []*wrappers.PeggySendToCosmosEvent{{EventNonce: big.NewInt(5)}}, nil }, + + // no-ops getTransactionBatchExecutedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyTransactionBatchExecutedEvent, error) { return nil, nil }, @@ -137,7 +147,6 @@ func TestRelayEvents(t *testing.T) { return nil, nil }, }, - injective: inj, } _, err := orch.relayEthEvents(context.TODO(), 100) @@ -166,6 +175,7 @@ func TestRelayEvents(t *testing.T) { } orch := &PeggyOrchestrator{ + injective: inj, ethereum: mockEthereum{ headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { return &types.Header{Number: big.NewInt(200)}, nil @@ -173,6 +183,8 @@ func TestRelayEvents(t *testing.T) { getSendToCosmosEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToCosmosEvent, error) { return []*wrappers.PeggySendToCosmosEvent{{EventNonce: big.NewInt(10)}}, nil }, + + // no-ops getTransactionBatchExecutedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyTransactionBatchExecutedEvent, error) { return nil, nil }, @@ -186,7 +198,6 @@ func TestRelayEvents(t *testing.T) { return nil, nil }, }, - injective: inj, } _, err := orch.relayEthEvents(context.TODO(), 100) From 1a3065e9489ee868de66d2c80b9b6cf264d697d1 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 14 Jun 2023 13:28:15 +0200 Subject: [PATCH 21/72] impl eth network for oracle --- orchestrator/ethereum/network.go | 184 +++++++++++++++++++++++++++++-- orchestrator/oracle.go | 17 +-- 2 files changed, 178 insertions(+), 23 deletions(-) diff --git a/orchestrator/ethereum/network.go b/orchestrator/ethereum/network.go index 127925cc..a1e2b5f6 100644 --- a/orchestrator/ethereum/network.go +++ b/orchestrator/ethereum/network.go @@ -1,14 +1,23 @@ package ethereum import ( - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/committer" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/peggy" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/provider" + "context" + "math/big" + "strings" + "time" + + "github.com/pkg/errors" + log "github.com/xlab/suplog" + "github.com/ethereum/go-ethereum/accounts/abi/bind" ethcmn "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" - log "github.com/xlab/suplog" - "time" + + "github.com/InjectiveLabs/peggo/orchestrator/ethereum/committer" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum/peggy" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum/provider" + wrappers "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" ) type Network struct { @@ -25,7 +34,6 @@ func NewNetwork( pendingTxWaitDuration string, ethNodeAlchemyWS string, ) (*Network, error) { - evmRPC, err := rpc.Dial(ethNodeRPC) if err != nil { log.WithField("endpoint", ethNodeRPC).WithError(err).Fatalln("Failed to connect to Ethereum RPC") @@ -60,5 +68,167 @@ func NewNetwork( go peggyContract.SubscribeToPendingTxs(ethNodeAlchemyWS) } - return &Network{PeggyContract: peggyContract}, nil + return &Network{ + PeggyContract: peggyContract, + }, nil +} + +func (n *Network) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + return n.Provider().HeaderByNumber(ctx, number) +} + +func (n *Network) GetSendToCosmosEvents(startBlock, endBlock uint64) ([]*wrappers.PeggySendToCosmosEvent, error) { + peggyFilterer, err := wrappers.NewPeggyFilterer(n.Address(), n.Provider()) + if err != nil { + return nil, errors.Wrap(err, "failed to init Peggy events filterer") + } + + endblock := endBlock + iter, err := peggyFilterer.FilterSendToCosmosEvent(&bind.FilterOpts{ + Start: startBlock, + End: &endblock, + }, nil, nil, nil) + if err != nil { + if !isUnknownBlockErr(err) { + return nil, errors.Wrap(err, "failed to scan past SendToCosmos events from Ethereum") + } else if iter == nil { + return nil, errors.New("no iterator returned") + } + } + + var sendToCosmosEvents []*wrappers.PeggySendToCosmosEvent + for iter.Next() { + sendToCosmosEvents = append(sendToCosmosEvents, iter.Event) + } + + iter.Close() + + return sendToCosmosEvents, nil +} + +func (n *Network) GetSendToInjectiveEvents(startBlock, endBlock uint64) ([]*wrappers.PeggySendToInjectiveEvent, error) { + peggyFilterer, err := wrappers.NewPeggyFilterer(n.Address(), n.Provider()) + if err != nil { + return nil, errors.Wrap(err, "failed to init Peggy events filterer") + } + + endblock := endBlock + iter, err := peggyFilterer.FilterSendToInjectiveEvent(&bind.FilterOpts{ + Start: startBlock, + End: &endblock, + }, nil, nil, nil) + if err != nil { + if !isUnknownBlockErr(err) { + return nil, errors.Wrap(err, "failed to scan past SendToCosmos events from Ethereum") + } else if iter == nil { + return nil, errors.New("no iterator returned") + } + } + + var sendToInjectiveEvents []*wrappers.PeggySendToInjectiveEvent + for iter.Next() { + sendToInjectiveEvents = append(sendToInjectiveEvents, iter.Event) + } + + iter.Close() + + return sendToInjectiveEvents, nil +} + +func (n *Network) GetTransactionBatchExecutedEvents(startBlock, endBlock uint64) ([]*wrappers.PeggyTransactionBatchExecutedEvent, error) { + peggyFilterer, err := wrappers.NewPeggyFilterer(n.Address(), n.Provider()) + if err != nil { + return nil, errors.Wrap(err, "failed to init Peggy events filterer") + } + + iter, err := peggyFilterer.FilterTransactionBatchExecutedEvent(&bind.FilterOpts{ + Start: startBlock, + End: &endBlock, + }, nil, nil) + if err != nil { + if !isUnknownBlockErr(err) { + return nil, errors.Wrap(err, "failed to scan past TransactionBatchExecuted events from Ethereum") + } else if iter == nil { + return nil, errors.New("no iterator returned") + } + } + + var transactionBatchExecutedEvents []*wrappers.PeggyTransactionBatchExecutedEvent + for iter.Next() { + transactionBatchExecutedEvents = append(transactionBatchExecutedEvents, iter.Event) + } + + iter.Close() + + return transactionBatchExecutedEvents, nil +} + +func (n *Network) GetPeggyERC20DeployedEvents(startBlock, endBlock uint64) ([]*wrappers.PeggyERC20DeployedEvent, error) { + peggyFilterer, err := wrappers.NewPeggyFilterer(n.Address(), n.Provider()) + if err != nil { + return nil, errors.Wrap(err, "failed to init Peggy events filterer") + } + + iter, err := peggyFilterer.FilterERC20DeployedEvent(&bind.FilterOpts{ + Start: startBlock, + End: &endBlock, + }, nil) + if err != nil { + if !isUnknownBlockErr(err) { + return nil, errors.Wrap(err, "failed to scan past TransactionBatchExecuted events from Ethereum") + } else if iter == nil { + return nil, errors.New("no iterator returned") + } + } + + var transactionBatchExecutedEvents []*wrappers.PeggyERC20DeployedEvent + for iter.Next() { + transactionBatchExecutedEvents = append(transactionBatchExecutedEvents, iter.Event) + } + + iter.Close() + + return transactionBatchExecutedEvents, nil +} + +func (n *Network) GetValsetUpdatedEvents(startBlock, endBlock uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + peggyFilterer, err := wrappers.NewPeggyFilterer(n.Address(), n.Provider()) + if err != nil { + return nil, errors.Wrap(err, "failed to init Peggy events filterer") + } + + iter, err := peggyFilterer.FilterValsetUpdatedEvent(&bind.FilterOpts{ + Start: startBlock, + End: &endBlock, + }, nil) + if err != nil { + if !isUnknownBlockErr(err) { + return nil, errors.Wrap(err, "failed to scan past ValsetUpdatedEvent events from Ethereum") + } else if iter == nil { + return nil, errors.New("no iterator returned") + } + } + + var valsetUpdatedEvents []*wrappers.PeggyValsetUpdatedEvent + for iter.Next() { + valsetUpdatedEvents = append(valsetUpdatedEvents, iter.Event) + } + + iter.Close() + + return valsetUpdatedEvents, nil +} + +func isUnknownBlockErr(err error) bool { + // Geth error + if strings.Contains(err.Error(), "unknown block") { + return true + } + + // Parity error + if strings.Contains(err.Error(), "One of the blocks specified in filter") { + return true + } + + return false } diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index 8e074d64..a686264b 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -2,7 +2,6 @@ package orchestrator import ( "context" - "strings" "time" "github.com/avast/retry-go" @@ -160,6 +159,7 @@ func (s *PeggyOrchestrator) relayEthEvents( // todo legacyDeposits, err := s.ethereum.GetSendToCosmosEvents(startingBlock, currentBlock) if err != nil { + log.WithFields(log.Fields{"start": startingBlock, "end": currentBlock}).Errorln("failed to scan past SendToCosmos events from Ethereum") return 0, err } @@ -322,7 +322,6 @@ func (s *PeggyOrchestrator) relayEthEvents( // todo valsetUpdates, err := s.ethereum.GetValsetUpdatedEvents(startingBlock, currentBlock) if err != nil { - return 0, err } //var valsetUpdatedEvents []*wrappers.PeggyValsetUpdatedEvent @@ -462,17 +461,3 @@ func filterValsetUpdateEventsByNonce( } return res } - -func isUnknownBlockErr(err error) bool { - // Geth error - if strings.Contains(err.Error(), "unknown block") { - return true - } - - // Parity error - if strings.Contains(err.Error(), "One of the blocks specified in filter") { - return true - } - - return false -} From afaf59f48ea2e8fd6ce9cc8a9a07237edf4ca495 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 14 Jun 2023 13:43:01 +0200 Subject: [PATCH 22/72] impl inj network for oracle --- orchestrator/cosmos/network.go | 44 ++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 orchestrator/cosmos/network.go diff --git a/orchestrator/cosmos/network.go b/orchestrator/cosmos/network.go new file mode 100644 index 00000000..0e85fab5 --- /dev/null +++ b/orchestrator/cosmos/network.go @@ -0,0 +1,44 @@ +package cosmos + +import ( + "context" + peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" + + peggy "github.com/InjectiveLabs/sdk-go/chain/peggy/types" +) + +type Network struct { + PeggyQueryClient + PeggyBroadcastClient +} + +func NewNetwork() (*Network, error) { + return nil, nil +} + +func (n *Network) PeggyParams(ctx context.Context) (*peggy.Params, error) { + return n.PeggyQueryClient.PeggyParams(ctx) +} + +func (n *Network) LastClaimEvent(ctx context.Context) (*peggy.LastClaimEvent, error) { + return n.LastClaimEventByAddr(ctx, n.AccFromAddress()) +} + +func (n *Network) SendEthereumClaims( + ctx context.Context, + lastClaimEvent uint64, + oldDeposits []*peggyevents.PeggySendToCosmosEvent, + deposits []*peggyevents.PeggySendToInjectiveEvent, + withdraws []*peggyevents.PeggyTransactionBatchExecutedEvent, + erc20Deployed []*peggyevents.PeggyERC20DeployedEvent, + valsetUpdates []*peggyevents.PeggyValsetUpdatedEvent, +) error { + return n.PeggyBroadcastClient.SendEthereumClaims(ctx, + lastClaimEvent, + oldDeposits, + deposits, + withdraws, + erc20Deployed, + valsetUpdates, + ) +} From d7137e3a49c870277a99c1d3b2d851a6d9fa0605 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 14 Jun 2023 14:14:15 +0200 Subject: [PATCH 23/72] impl inj network for batch requester --- orchestrator/batch_request.go | 16 ++++++------ orchestrator/batch_request_test.go | 42 ++++++++++++++++-------------- orchestrator/cosmos/network.go | 10 ++++++- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/orchestrator/batch_request.go b/orchestrator/batch_request.go index 8567de6d..cb28d360 100644 --- a/orchestrator/batch_request.go +++ b/orchestrator/batch_request.go @@ -5,7 +5,7 @@ import ( "time" "github.com/avast/retry-go" - ethcmn "github.com/ethereum/go-ethereum/common" + eth "github.com/ethereum/go-ethereum/common" "github.com/shopspring/decimal" log "github.com/xlab/suplog" @@ -49,11 +49,11 @@ func (s *PeggyOrchestrator) requestBatches(ctx context.Context, logger log.Logge logger.WithField("unbatchedTokensWithFees", unbatchedTokensWithFees).Debugln("Check if token fees meets set threshold amount and send batch request") for _, unbatchedToken := range unbatchedTokensWithFees { // check if the token is present in cosmos denom. if so, send batch request with cosmosDenom - tokenAddr := ethcmn.HexToAddress(unbatchedToken.Token) + tokenAddr := eth.HexToAddress(unbatchedToken.Token) - thresholdMet := s.CheckFeeThreshold(tokenAddr, unbatchedToken.TotalFees, s.minBatchFeeUSD) + thresholdMet := s.checkFeeThreshold(tokenAddr, unbatchedToken.TotalFees, s.minBatchFeeUSD) if !thresholdMet && !mustRequest { - // non injective relayers only relay when the threshold is met + // non-injective relayers only relay when the threshold is met continue } @@ -77,9 +77,9 @@ func (s *PeggyOrchestrator) getBatchFeesByToken(ctx context.Context, log log.Log return nil } - if err := retry.Do( - retryFn, + if err := retry.Do(retryFn, retry.Context(ctx), + retry.Attempts(s.maxRetries), retry.OnRetry(func(n uint, err error) { log.WithError(err).Errorf("failed to get UnbatchedTokensWithFees, will retry (%d)", n) }), @@ -90,7 +90,7 @@ func (s *PeggyOrchestrator) getBatchFeesByToken(ctx context.Context, log log.Log return unbatchedTokensWithFees, nil } -func (s *PeggyOrchestrator) getTokenDenom(tokenAddr ethcmn.Address) string { +func (s *PeggyOrchestrator) getTokenDenom(tokenAddr eth.Address) string { if cosmosDenom, ok := s.erc20ContractMapping[tokenAddr]; ok { return cosmosDenom } @@ -99,7 +99,7 @@ func (s *PeggyOrchestrator) getTokenDenom(tokenAddr ethcmn.Address) string { return types.PeggyDenomString(tokenAddr) } -func (s *PeggyOrchestrator) CheckFeeThreshold(erc20Contract ethcmn.Address, totalFee cosmtypes.Int, minFeeInUSD float64) bool { +func (s *PeggyOrchestrator) checkFeeThreshold(erc20Contract eth.Address, totalFee cosmtypes.Int, minFeeInUSD float64) bool { if minFeeInUSD == 0 { return true } diff --git a/orchestrator/batch_request_test.go b/orchestrator/batch_request_test.go index 797e7b0c..8eaa255a 100644 --- a/orchestrator/batch_request_test.go +++ b/orchestrator/batch_request_test.go @@ -5,55 +5,57 @@ import ( "errors" "testing" - ethcmn "github.com/ethereum/go-ethereum/common" + eth "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/xlab/suplog" - peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + peggy "github.com/InjectiveLabs/sdk-go/chain/peggy/types" cosmtypes "github.com/cosmos/cosmos-sdk/types" ) func TestRequestBatches(t *testing.T) { t.Parallel() - t.Run("UnbatchedTokenFees call fails", func(t *testing.T) { + t.Run("failed to get unbatched tokens from injective", func(t *testing.T) { t.Parallel() orch := &PeggyOrchestrator{ + maxRetries: 1, injective: &mockInjective{ - unbatchedTokenFeesFn: func(_ context.Context) ([]*peggytypes.BatchFees, error) { + unbatchedTokenFeesFn: func(context.Context) ([]*peggy.BatchFees, error) { return nil, errors.New("fail") }, }, } - assert.Nil(t, orch.requestBatches(context.TODO(), suplog.DefaultLogger, false)) + assert.NoError(t, orch.requestBatches(context.TODO(), suplog.DefaultLogger, false)) }) t.Run("no unbatched tokens", func(t *testing.T) { t.Parallel() orch := &PeggyOrchestrator{ + maxRetries: 1, injective: &mockInjective{ - unbatchedTokenFeesFn: func(_ context.Context) ([]*peggytypes.BatchFees, error) { + unbatchedTokenFeesFn: func(context.Context) ([]*peggy.BatchFees, error) { return nil, nil }, }, } - assert.Nil(t, orch.requestBatches(context.TODO(), suplog.DefaultLogger, false)) + assert.NoError(t, orch.requestBatches(context.TODO(), suplog.DefaultLogger, false)) }) t.Run("batch does not meet fee threshold", func(t *testing.T) { t.Parallel() - tokenAddr := ethcmn.HexToAddress("0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30") + tokenAddr := eth.HexToAddress("0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30") injective := &mockInjective{ sendRequestBatchFn: func(context.Context, string) error { return nil }, - unbatchedTokenFeesFn: func(_ context.Context) ([]*peggytypes.BatchFees, error) { + unbatchedTokenFeesFn: func(context.Context) ([]*peggy.BatchFees, error) { fees, _ := cosmtypes.NewIntFromString("50000000000000000000") - return []*peggytypes.BatchFees{ + return []*peggy.BatchFees{ { Token: tokenAddr.String(), TotalFees: fees, @@ -63,26 +65,27 @@ func TestRequestBatches(t *testing.T) { } orch := &PeggyOrchestrator{ + maxRetries: 1, minBatchFeeUSD: 51.0, - erc20ContractMapping: map[ethcmn.Address]string{tokenAddr: "inj"}, - pricefeed: mockPriceFeed{queryFn: func(_ ethcmn.Address) (float64, error) { return 1, nil }}, + erc20ContractMapping: map[eth.Address]string{tokenAddr: "inj"}, + pricefeed: mockPriceFeed{queryFn: func(_ eth.Address) (float64, error) { return 1, nil }}, injective: injective, } - assert.Nil(t, orch.requestBatches(context.TODO(), suplog.DefaultLogger, false)) + assert.NoError(t, orch.requestBatches(context.TODO(), suplog.DefaultLogger, false)) assert.Equal(t, injective.sendRequestBatchCallCount, 0) }) t.Run("batch meets threshold and a request is sent", func(t *testing.T) { t.Parallel() - tokenAddr := ethcmn.HexToAddress("0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30") + tokenAddr := eth.HexToAddress("0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30") injective := &mockInjective{ sendRequestBatchFn: func(context.Context, string) error { return nil }, - unbatchedTokenFeesFn: func(_ context.Context) ([]*peggytypes.BatchFees, error) { + unbatchedTokenFeesFn: func(_ context.Context) ([]*peggy.BatchFees, error) { fees, _ := cosmtypes.NewIntFromString("50000000000000000000") - return []*peggytypes.BatchFees{ + return []*peggy.BatchFees{ { Token: tokenAddr.String(), TotalFees: fees, @@ -92,15 +95,16 @@ func TestRequestBatches(t *testing.T) { } orch := &PeggyOrchestrator{ + maxRetries: 1, minBatchFeeUSD: 49.0, - erc20ContractMapping: map[ethcmn.Address]string{tokenAddr: "inj"}, - pricefeed: mockPriceFeed{queryFn: func(_ ethcmn.Address) (float64, error) { + erc20ContractMapping: map[eth.Address]string{tokenAddr: "inj"}, + pricefeed: mockPriceFeed{queryFn: func(_ eth.Address) (float64, error) { return 1, nil }}, injective: injective, } - assert.Nil(t, orch.requestBatches(context.TODO(), suplog.DefaultLogger, false)) + assert.NoError(t, orch.requestBatches(context.TODO(), suplog.DefaultLogger, false)) assert.Equal(t, injective.sendRequestBatchCallCount, 1) }) diff --git a/orchestrator/cosmos/network.go b/orchestrator/cosmos/network.go index 0e85fab5..3b9150b2 100644 --- a/orchestrator/cosmos/network.go +++ b/orchestrator/cosmos/network.go @@ -2,8 +2,8 @@ package cosmos import ( "context" - peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" + peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" peggy "github.com/InjectiveLabs/sdk-go/chain/peggy/types" ) @@ -42,3 +42,11 @@ func (n *Network) SendEthereumClaims( valsetUpdates, ) } + +func (n *Network) UnbatchedTokenFees(ctx context.Context) ([]*peggy.BatchFees, error) { + return n.PeggyQueryClient.UnbatchedTokensWithFees(ctx) +} + +func (n *Network) SendRequestBatch(ctx context.Context, denom string) error { + return n.PeggyBroadcastClient.SendRequestBatch(ctx, denom) +} From 76a95ffafdff8fdf97efbe405d432e5403db47a1 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 14 Jun 2023 16:42:12 +0200 Subject: [PATCH 24/72] impl inj/eth network for signer --- orchestrator/cosmos/network.go | 4 +++ orchestrator/ethereum/network.go | 8 ++++++ orchestrator/mocks_test.go | 29 ++++++++------------- orchestrator/orchestrator.go | 13 +++------- orchestrator/signer.go | 34 ++++++++++++++++--------- orchestrator/signer_test.go | 43 +++++++++++++++++++++----------- 6 files changed, 77 insertions(+), 54 deletions(-) diff --git a/orchestrator/cosmos/network.go b/orchestrator/cosmos/network.go index 3b9150b2..7a7acc0c 100644 --- a/orchestrator/cosmos/network.go +++ b/orchestrator/cosmos/network.go @@ -50,3 +50,7 @@ func (n *Network) UnbatchedTokenFees(ctx context.Context) ([]*peggy.BatchFees, e func (n *Network) SendRequestBatch(ctx context.Context, denom string) error { return n.PeggyBroadcastClient.SendRequestBatch(ctx, denom) } + +func (n *Network) OldestUnsignedValsets(ctx context.Context) ([]*peggy.Valset, error) { + return n.PeggyQueryClient.OldestUnsignedValsets(ctx, n.PeggyBroadcastClient.AccFromAddress()) +} diff --git a/orchestrator/ethereum/network.go b/orchestrator/ethereum/network.go index a1e2b5f6..89048db7 100644 --- a/orchestrator/ethereum/network.go +++ b/orchestrator/ethereum/network.go @@ -219,6 +219,14 @@ func (n *Network) GetValsetUpdatedEvents(startBlock, endBlock uint64) ([]*wrappe return valsetUpdatedEvents, nil } +func (n *Network) GetPeggyID(ctx context.Context) (ethcmn.Hash, error) { + return n.PeggyContract.GetPeggyID(ctx, n.FromAddress()) +} + +func (n *Network) FromAddress() ethcmn.Address { + return n.PeggyContract.FromAddress() +} + func isUnknownBlockErr(err error) bool { // Geth error if strings.Contains(err.Error(), "unknown block") { diff --git a/orchestrator/mocks_test.go b/orchestrator/mocks_test.go index 27cb0a9d..13946aa2 100644 --- a/orchestrator/mocks_test.go +++ b/orchestrator/mocks_test.go @@ -40,14 +40,10 @@ type mockInjective struct { sendEthereumClaimsCallCount int oldestUnsignedValsetsFn func(context.Context) ([]*peggytypes.Valset, error) - sendValsetConfirmFn func( - ctx context.Context, - peggyID eth.Hash, - valset *peggytypes.Valset, - ) error + sendValsetConfirmFn func(context.Context, eth.Hash, *peggytypes.Valset, eth.Address) error oldestUnsignedTransactionBatchFn func(context.Context) (*peggytypes.OutgoingTxBatch, error) - sendBatchConfirmFn func(context.Context, eth.Hash, *peggytypes.OutgoingTxBatch) error + sendBatchConfirmFn func(context.Context, eth.Hash, *peggytypes.OutgoingTxBatch, eth.Address) error latestValsetsFn func(context.Context) ([]*peggytypes.Valset, error) getBlockFn func(context.Context, int64) (*tmctypes.ResultBlock, error) @@ -102,12 +98,8 @@ func (i *mockInjective) OldestUnsignedValsets(ctx context.Context) ([]*peggytype return i.oldestUnsignedValsetsFn(ctx) } -func (i *mockInjective) SendValsetConfirm( - ctx context.Context, - peggyID eth.Hash, - valset *peggytypes.Valset, -) error { - return i.sendValsetConfirmFn(ctx, peggyID, valset) +func (i *mockInjective) SendValsetConfirm(ctx context.Context, peggyID eth.Hash, valset *peggytypes.Valset, ethFrom eth.Address) error { + return i.sendValsetConfirmFn(ctx, peggyID, valset, ethFrom) } func (i *mockInjective) OldestUnsignedTransactionBatch(ctx context.Context) (*peggytypes.OutgoingTxBatch, error) { @@ -126,12 +118,8 @@ func (i *mockInjective) AllValsetConfirms(ctx context.Context, nonce uint64) ([] return i.allValsetConfirmsFn(ctx, nonce) } -func (i *mockInjective) SendBatchConfirm( - ctx context.Context, - peggyID eth.Hash, - batch *peggytypes.OutgoingTxBatch, -) error { - return i.sendBatchConfirmFn(ctx, peggyID, batch) +func (i *mockInjective) SendBatchConfirm(ctx context.Context, peggyID eth.Hash, batch *peggytypes.OutgoingTxBatch, ethFrom eth.Address) error { + return i.sendBatchConfirmFn(ctx, peggyID, batch, ethFrom) } func (i *mockInjective) ValsetAt(ctx context.Context, nonce uint64) (*peggytypes.Valset, error) { @@ -147,6 +135,7 @@ func (i *mockInjective) TransactionBatchSignatures(ctx context.Context, nonce ui } type mockEthereum struct { + fromAddressFn func() eth.Address headerByNumberFn func(context.Context, *big.Int) (*ethtypes.Header, error) getSendToCosmosEventsFn func(uint64, uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) getSendToInjectiveEventsFn func(uint64, uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) @@ -160,6 +149,10 @@ type mockEthereum struct { sendTransactionBatchFn func(context.Context, *peggytypes.Valset, *peggytypes.OutgoingTxBatch, []*peggytypes.MsgConfirmBatch) (*eth.Hash, error) } +func (e mockEthereum) FromAddress() eth.Address { + return e.fromAddressFn() +} + func (e mockEthereum) HeaderByNumber(ctx context.Context, number *big.Int) (*ethtypes.Header, error) { return e.headerByNumberFn(ctx, number) } diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index 08c803ab..b2137156 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -41,18 +41,10 @@ type InjectiveNetwork interface { ) error OldestUnsignedValsets(ctx context.Context) ([]*peggytypes.Valset, error) - SendValsetConfirm( - ctx context.Context, - peggyID ethcmn.Hash, - valset *peggytypes.Valset, - ) error + SendValsetConfirm(ctx context.Context, peggyID ethcmn.Hash, valset *peggytypes.Valset, ethFrom ethcmn.Address) error OldestUnsignedTransactionBatch(ctx context.Context) (*peggytypes.OutgoingTxBatch, error) - SendBatchConfirm( - ctx context.Context, - peggyID ethcmn.Hash, - batch *peggytypes.OutgoingTxBatch, - ) error + SendBatchConfirm(ctx context.Context, peggyID ethcmn.Hash, batch *peggytypes.OutgoingTxBatch, ethFrom ethcmn.Address) error GetBlock(ctx context.Context, height int64) (*tmctypes.ResultBlock, error) @@ -65,6 +57,7 @@ type InjectiveNetwork interface { } type EthereumNetwork interface { + FromAddress() ethcmn.Address HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) GetPeggyID(ctx context.Context) (ethcmn.Hash, error) diff --git a/orchestrator/signer.go b/orchestrator/signer.go index f4b153bd..8744df39 100644 --- a/orchestrator/signer.go +++ b/orchestrator/signer.go @@ -28,7 +28,7 @@ func (s *PeggyOrchestrator) EthSignerMainLoop(ctx context.Context) error { func (s *PeggyOrchestrator) getPeggyID(ctx context.Context, logger log.Logger) (common.Hash, error) { var peggyID common.Hash - if err := retry.Do(func() error { + retryFn := func() error { id, err := s.ethereum.GetPeggyID(ctx) if err != nil { return err @@ -36,11 +36,15 @@ func (s *PeggyOrchestrator) getPeggyID(ctx context.Context, logger log.Logger) ( peggyID = id return nil - }, retry.Context(ctx), + } + + if err := retry.Do(retryFn, + retry.Context(ctx), retry.Attempts(s.maxRetries), retry.OnRetry(func(n uint, err error) { logger.WithError(err).Warningf("failed to get PeggyID from Ethereum contract, will retry (%d)", n) - })); err != nil { + }), + ); err != nil { logger.WithError(err).Errorln("got error, loop exits") return [32]byte{}, err } @@ -64,7 +68,7 @@ func (s *PeggyOrchestrator) signerLoop(ctx context.Context, logger log.Logger, p func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Logger, peggyID common.Hash) error { var oldestUnsignedValsets []*types.Valset - if err := retry.Do(func() error { + retryFn := func() error { oldestValsets, err := s.injective.OldestUnsignedValsets(ctx) if err != nil { if err == cosmos.ErrNotFound || oldestValsets == nil { @@ -77,24 +81,30 @@ func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Lo } oldestUnsignedValsets = oldestValsets return nil - }, retry.Context(ctx), + } + + if err := retry.Do(retryFn, + retry.Context(ctx), retry.Attempts(s.maxRetries), retry.OnRetry(func(n uint, err error) { logger.WithError(err).Warningf("failed to get unsigned Valset for signing, will retry (%d)", n) - })); err != nil { + }), + ); err != nil { logger.WithError(err).Errorln("got error, loop exits") return err } - for _, oldestValset := range oldestUnsignedValsets { - logger.Infoln("Sending Valset confirm for %d", oldestValset.Nonce) + for _, vs := range oldestUnsignedValsets { + logger.Infoln("Sending Valset confirm for %d", vs.Nonce) if err := retry.Do(func() error { - return s.injective.SendValsetConfirm(ctx, peggyID, oldestValset) - }, retry.Context(ctx), + return s.injective.SendValsetConfirm(ctx, peggyID, vs, s.ethereum.FromAddress()) + }, + retry.Context(ctx), retry.Attempts(s.maxRetries), retry.OnRetry(func(n uint, err error) { logger.WithError(err).Warningf("failed to sign and send Valset confirmation to Cosmos, will retry (%d)", n) - })); err != nil { + }), + ); err != nil { logger.WithError(err).Errorln("got error, loop exits") return err } @@ -132,7 +142,7 @@ func (s *PeggyOrchestrator) signTransactionBatches(ctx context.Context, logger l logger.Infoln("Sending TransactionBatch confirm for BatchNonce %d", oldestUnsignedTransactionBatch.BatchNonce) if err := retry.Do(func() error { - return s.injective.SendBatchConfirm(ctx, peggyID, oldestUnsignedTransactionBatch) + return s.injective.SendBatchConfirm(ctx, peggyID, oldestUnsignedTransactionBatch, s.ethereum.FromAddress()) }, retry.Context(ctx), retry.Attempts(s.maxRetries), retry.OnRetry(func(n uint, err error) { diff --git a/orchestrator/signer_test.go b/orchestrator/signer_test.go index 7959dfee..5745cfd8 100644 --- a/orchestrator/signer_test.go +++ b/orchestrator/signer_test.go @@ -26,8 +26,7 @@ func TestEthSignerLoop(t *testing.T) { }, } - err := orch.EthSignerMainLoop(context.TODO()) - assert.Error(t, err) + assert.Error(t, orch.EthSignerMainLoop(context.TODO())) }) t.Run("no valset to sign", func(t *testing.T) { @@ -39,20 +38,19 @@ func TestEthSignerLoop(t *testing.T) { oldestUnsignedValsetsFn: func(context.Context) ([]*types.Valset, error) { return nil, errors.New("fail") }, - sendValsetConfirmFn: func(_ context.Context, _ common.Hash, _ *types.Valset) error { + sendValsetConfirmFn: func(context.Context, common.Hash, *types.Valset, common.Address) error { return nil }, - oldestUnsignedTransactionBatchFn: func(_ context.Context) (*types.OutgoingTxBatch, error) { + oldestUnsignedTransactionBatchFn: func(context.Context) (*types.OutgoingTxBatch, error) { return nil, nil }, - sendBatchConfirmFn: func(_ context.Context, _ common.Hash, _ *types.OutgoingTxBatch) error { + sendBatchConfirmFn: func(context.Context, common.Hash, *types.OutgoingTxBatch, common.Address) error { return nil }, }, } - err := orch.signerLoop(context.TODO(), suplog.DefaultLogger, [32]byte{1, 2, 3}) - assert.NoError(t, err) + assert.NoError(t, orch.signerLoop(context.TODO(), suplog.DefaultLogger, [32]byte{1, 2, 3})) }) t.Run("failed to send valset confirm", func(t *testing.T) { @@ -77,10 +75,15 @@ func TestEthSignerLoop(t *testing.T) { }, }, nil }, - sendValsetConfirmFn: func(_ context.Context, _ common.Hash, _ *types.Valset) error { + sendValsetConfirmFn: func(context.Context, common.Hash, *types.Valset, common.Address) error { return errors.New("fail") }, }, + ethereum: mockEthereum{ + fromAddressFn: func() common.Address { + return common.Address{} + }, + }, } err := orch.signerLoop(context.TODO(), suplog.DefaultLogger, [32]byte{1, 2, 3}) @@ -94,9 +97,9 @@ func TestEthSignerLoop(t *testing.T) { maxRetries: 1, injective: &mockInjective{ oldestUnsignedValsetsFn: func(_ context.Context) ([]*types.Valset, error) { return nil, nil }, - sendValsetConfirmFn: func(_ context.Context, _ common.Hash, _ *types.Valset) error { return nil }, + sendValsetConfirmFn: func(context.Context, common.Hash, *types.Valset, common.Address) error { return nil }, oldestUnsignedTransactionBatchFn: func(_ context.Context) (*types.OutgoingTxBatch, error) { return nil, errors.New("fail") }, - sendBatchConfirmFn: func(_ context.Context, _ common.Hash, _ *types.OutgoingTxBatch) error { return nil }, + sendBatchConfirmFn: func(context.Context, common.Hash, *types.OutgoingTxBatch, common.Address) error { return nil }, }, } @@ -111,11 +114,18 @@ func TestEthSignerLoop(t *testing.T) { maxRetries: 1, injective: &mockInjective{ oldestUnsignedValsetsFn: func(_ context.Context) ([]*types.Valset, error) { return nil, nil }, - sendValsetConfirmFn: func(_ context.Context, _ common.Hash, _ *types.Valset) error { return nil }, + sendValsetConfirmFn: func(context.Context, common.Hash, *types.Valset, common.Address) error { return nil }, oldestUnsignedTransactionBatchFn: func(_ context.Context) (*types.OutgoingTxBatch, error) { return &types.OutgoingTxBatch{}, nil // non-empty will do }, - sendBatchConfirmFn: func(_ context.Context, _ common.Hash, _ *types.OutgoingTxBatch) error { return errors.New("fail") }, + sendBatchConfirmFn: func(context.Context, common.Hash, *types.OutgoingTxBatch, common.Address) error { + return errors.New("fail") + }, + }, + ethereum: mockEthereum{ + fromAddressFn: func() common.Address { + return common.Address{} + }, }, } @@ -135,8 +145,13 @@ func TestEthSignerLoop(t *testing.T) { oldestUnsignedTransactionBatchFn: func(_ context.Context) (*types.OutgoingTxBatch, error) { return &types.OutgoingTxBatch{}, nil // non-empty will do }, - sendValsetConfirmFn: func(_ context.Context, _ common.Hash, _ *types.Valset) error { return nil }, - sendBatchConfirmFn: func(_ context.Context, _ common.Hash, _ *types.OutgoingTxBatch) error { return nil }, + sendValsetConfirmFn: func(context.Context, common.Hash, *types.Valset, common.Address) error { return nil }, + sendBatchConfirmFn: func(context.Context, common.Hash, *types.OutgoingTxBatch, common.Address) error { return nil }, + }, + ethereum: mockEthereum{ + fromAddressFn: func() common.Address { + return common.Address{} + }, }, } From e3133016099d45b8015e33232881d472a613aaf5 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 14 Jun 2023 16:46:00 +0200 Subject: [PATCH 25/72] impl inj/eth for relayer --- orchestrator/cosmos/network.go | 53 +++++++++++++++++++++++- orchestrator/ethereum/network.go | 27 +++++++++++++ orchestrator/relayer.go | 69 ++++++++++++++++---------------- 3 files changed, 112 insertions(+), 37 deletions(-) diff --git a/orchestrator/cosmos/network.go b/orchestrator/cosmos/network.go index 7a7acc0c..218239e7 100644 --- a/orchestrator/cosmos/network.go +++ b/orchestrator/cosmos/network.go @@ -2,12 +2,15 @@ package cosmos import ( "context" - + "github.com/InjectiveLabs/peggo/orchestrator/cosmos/tmclient" peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" peggy "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + ethcmn "github.com/ethereum/go-ethereum/common" + tmctypes "github.com/tendermint/tendermint/rpc/core/types" ) type Network struct { + tmclient.TendermintClient PeggyQueryClient PeggyBroadcastClient } @@ -16,6 +19,10 @@ func NewNetwork() (*Network, error) { return nil, nil } +func (n *Network) GetBlock(ctx context.Context, height int64) (*tmctypes.ResultBlock, error) { + return n.TendermintClient.GetBlock(ctx, height) +} + func (n *Network) PeggyParams(ctx context.Context) (*peggy.Params, error) { return n.PeggyQueryClient.PeggyParams(ctx) } @@ -52,5 +59,47 @@ func (n *Network) SendRequestBatch(ctx context.Context, denom string) error { } func (n *Network) OldestUnsignedValsets(ctx context.Context) ([]*peggy.Valset, error) { - return n.PeggyQueryClient.OldestUnsignedValsets(ctx, n.PeggyBroadcastClient.AccFromAddress()) + return n.PeggyQueryClient.OldestUnsignedValsets(ctx, n.AccFromAddress()) +} + +func (n *Network) LatestValsets(ctx context.Context) ([]*peggy.Valset, error) { + return n.PeggyQueryClient.LatestValsets(ctx) +} + +func (n *Network) AllValsetConfirms(ctx context.Context, nonce uint64) ([]*peggy.MsgValsetConfirm, error) { + return n.PeggyQueryClient.AllValsetConfirms(ctx, nonce) +} + +func (n *Network) ValsetAt(ctx context.Context, nonce uint64) (*peggy.Valset, error) { + return n.PeggyQueryClient.ValsetAt(ctx, nonce) +} + +func (n *Network) SendValsetConfirm( + ctx context.Context, + peggyID ethcmn.Hash, + valset *peggy.Valset, + ethFrom ethcmn.Address, +) error { + return n.PeggyBroadcastClient.SendValsetConfirm(ctx, ethFrom, peggyID, valset) +} + +func (n *Network) OldestUnsignedTransactionBatch(ctx context.Context) (*peggy.OutgoingTxBatch, error) { + return n.PeggyQueryClient.OldestUnsignedTransactionBatch(ctx, n.AccFromAddress()) +} + +func (n *Network) LatestTransactionBatches(ctx context.Context) ([]*peggy.OutgoingTxBatch, error) { + return n.PeggyQueryClient.LatestTransactionBatches(ctx) +} + +func (n *Network) TransactionBatchSignatures(ctx context.Context, nonce uint64, tokenContract ethcmn.Address) ([]*peggy.MsgConfirmBatch, error) { + return n.PeggyQueryClient.TransactionBatchSignatures(ctx, nonce, tokenContract) +} + +func (n *Network) SendBatchConfirm( + ctx context.Context, + peggyID ethcmn.Hash, + batch *peggy.OutgoingTxBatch, + ethFrom ethcmn.Address, +) error { + return n.PeggyBroadcastClient.SendBatchConfirm(ctx, ethFrom, peggyID, batch) } diff --git a/orchestrator/ethereum/network.go b/orchestrator/ethereum/network.go index 89048db7..cfa0dad5 100644 --- a/orchestrator/ethereum/network.go +++ b/orchestrator/ethereum/network.go @@ -2,6 +2,7 @@ package ethereum import ( "context" + peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" "math/big" "strings" "time" @@ -227,6 +228,32 @@ func (n *Network) FromAddress() ethcmn.Address { return n.PeggyContract.FromAddress() } +func (n *Network) GetValsetNonce(ctx context.Context) (*big.Int, error) { + return n.PeggyContract.GetValsetNonce(ctx, n.FromAddress()) +} + +func (n *Network) SendEthValsetUpdate( + ctx context.Context, + oldValset *peggytypes.Valset, + newValset *peggytypes.Valset, + confirms []*peggytypes.MsgValsetConfirm, +) (*ethcmn.Hash, error) { + return n.PeggyContract.SendEthValsetUpdate(ctx, oldValset, newValset, confirms) +} + +func (n *Network) GetTxBatchNonce(ctx context.Context, erc20ContractAddress ethcmn.Address) (*big.Int, error) { + return n.PeggyContract.GetTxBatchNonce(ctx, erc20ContractAddress, n.FromAddress()) +} + +func (n *Network) SendTransactionBatch( + ctx context.Context, + currentValset *peggytypes.Valset, + batch *peggytypes.OutgoingTxBatch, + confirms []*peggytypes.MsgConfirmBatch, +) (*ethcmn.Hash, error) { + return n.PeggyContract.SendTransactionBatch(ctx, currentValset, batch, confirms) +} + func isUnknownBlockErr(err error) bool { // Geth error if strings.Contains(err.Error(), "unknown block") { diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index cdacabe5..cd0a5c76 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -212,47 +212,46 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context) error { } log.WithFields(log.Fields{"oldestSignedBatchNonce": oldestSignedBatch.BatchNonce, "latestEthereumBatchNonce": latestEthereumBatch.Uint64()}).Debugln("Found Latest valsets") + if oldestSignedBatch.BatchNonce <= latestEthereumBatch.Uint64() { + return nil + } - if oldestSignedBatch.BatchNonce > latestEthereumBatch.Uint64() { - - latestEthereumBatch, err := s.ethereum.GetTxBatchNonce( - ctx, - common.HexToAddress(oldestSignedBatch.TokenContract), - ) - if err != nil { - metrics.ReportFuncError(s.svcTags) - return err - } - // Check if oldestSignedBatch already submitted by other validators in mean time - if oldestSignedBatch.BatchNonce > latestEthereumBatch.Uint64() { + latestEthereumBatch, err = s.ethereum.GetTxBatchNonce(ctx, common.HexToAddress(oldestSignedBatch.TokenContract)) + if err != nil { + metrics.ReportFuncError(s.svcTags) + return err + } + // Check if oldestSignedBatch already submitted by other validators in mean time + if oldestSignedBatch.BatchNonce <= latestEthereumBatch.Uint64() { + return nil + } - // Check custom time delay offset - blockResult, err := s.injective.GetBlock(ctx, int64(oldestSignedBatch.Block)) - if err != nil { - return err - } - batchCreatedAt := blockResult.Block.Time - //relayBatchOffsetDur, err := time.ParseDuration() - //if err != nil { - // return err - //} - customTimeDelay := batchCreatedAt.Add(s.relayBatchOffsetDur) - if time.Now().Sub(customTimeDelay) <= 0 { - return nil - } + // Check custom time delay offset + blockResult, err := s.injective.GetBlock(ctx, int64(oldestSignedBatch.Block)) + if err != nil { + return err + } + batchCreatedAt := blockResult.Block.Time + //relayBatchOffsetDur, err := time.ParseDuration() + //if err != nil { + // return err + //} + customTimeDelay := batchCreatedAt.Add(s.relayBatchOffsetDur) + if time.Now().Sub(customTimeDelay) <= 0 { + return nil + } - log.Infof("We have detected latest batch %d but latest on Ethereum is %d sending an update!", oldestSignedBatch.BatchNonce, latestEthereumBatch) + log.Infof("We have detected latest batch %d but latest on Ethereum is %d sending an update!", oldestSignedBatch.BatchNonce, latestEthereumBatch) - // Send SendTransactionBatch to Ethereum - txHash, err := s.ethereum.SendTransactionBatch(ctx, currentValset, oldestSignedBatch, oldestSigs) - if err != nil { - metrics.ReportFuncError(s.svcTags) - return err - } - log.WithField("tx_hash", txHash.Hex()).Infoln("Sent Ethereum Tx (TransactionBatch)") - } + // Send SendTransactionBatch to Ethereum + txHash, err := s.ethereum.SendTransactionBatch(ctx, currentValset, oldestSignedBatch, oldestSigs) + if err != nil { + metrics.ReportFuncError(s.svcTags) + return err } + log.WithField("tx_hash", txHash.Hex()).Infoln("Sent Ethereum Tx (TransactionBatch)") + return nil } From 8d58378eda1a0ef48db8acfebc9a1c75b4c75241 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Thu, 15 Jun 2023 11:38:29 +0200 Subject: [PATCH 26/72] clean up init --- cmd/peggo/orchestrator.go | 144 +++++++++---------------------- cmd/peggo/tx.go | 28 +++++- cmd/peggo/util.go | 22 ----- orchestrator/cosmos/network.go | 80 ++++++++++++++++- orchestrator/ethereum/network.go | 4 +- orchestrator/main_loops.go | 4 +- orchestrator/orchestrator.go | 95 +++++++++++--------- 7 files changed, 199 insertions(+), 178 deletions(-) diff --git a/cmd/peggo/orchestrator.go b/cmd/peggo/orchestrator.go index e5cb57ac..0e8a44da 100644 --- a/cmd/peggo/orchestrator.go +++ b/cmd/peggo/orchestrator.go @@ -5,27 +5,16 @@ import ( "os" "time" + ctypes "github.com/InjectiveLabs/sdk-go/chain/types" ethcmn "github.com/ethereum/go-ethereum/common" cli "github.com/jawher/mow.cli" - rpchttp "github.com/tendermint/tendermint/rpc/client/http" "github.com/xlab/closer" log "github.com/xlab/suplog" - "github.com/InjectiveLabs/sdk-go/chain/peggy/types" - chainclient "github.com/InjectiveLabs/sdk-go/client/chain" - "github.com/InjectiveLabs/sdk-go/client/common" - "github.com/InjectiveLabs/peggo/orchestrator" "github.com/InjectiveLabs/peggo/orchestrator/coingecko" "github.com/InjectiveLabs/peggo/orchestrator/cosmos" - "github.com/InjectiveLabs/peggo/orchestrator/cosmos/tmclient" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/committer" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/peggy" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/provider" - "github.com/InjectiveLabs/peggo/orchestrator/relayer" - - ctypes "github.com/InjectiveLabs/sdk-go/chain/types" - "github.com/ethereum/go-ethereum/rpc" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum" ) // startOrchestrator action runs an infinite loop, @@ -76,49 +65,31 @@ func orchestratorCmd(cmd *cli.Cmd) { log.Infoln("Using Cosmos ValAddress", valAddress.String()) log.Infoln("Using Ethereum address", ethKeyFromAddress.String()) - // injective start - - clientCtx, err := chainclient.NewClientContext(*cfg.cosmosChainID, valAddress.String(), cosmosKeyring) - if err != nil { - log.WithError(err).Fatalln("failed to initialize cosmos client context") - } - clientCtx = clientCtx.WithNodeURI(*cfg.tendermintRPC) - tmRPC, err := rpchttp.New(*cfg.tendermintRPC, "/websocket") - if err != nil { - log.WithError(err) - } - clientCtx = clientCtx.WithClient(tmRPC) - - daemonClient, err := chainclient.NewChainClient(clientCtx, *cfg.cosmosGRPC, common.OptionGasPrices(*cfg.cosmosGasPrices)) - if err != nil { - log.WithError(err).WithFields( - log.Fields{"endpoint": *cfg.cosmosGRPC}). - Fatalln("failed to connect to daemon, is injectived running?") - } - - log.Infoln("Waiting for injectived GRPC") - time.Sleep(1 * time.Second) - - daemonWaitCtx, cancelWait := context.WithTimeout(context.Background(), time.Minute) - grpcConn := daemonClient.QueryClient() - waitForService(daemonWaitCtx, grpcConn) - peggyQuerier := types.NewQueryClient(grpcConn) - peggyBroadcaster := cosmos.NewPeggyBroadcastClient( - peggyQuerier, - daemonClient, + // init injective network + injNetwork, err := cosmos.NewNetwork( + *cfg.cosmosChainID, + valAddress.String(), + *cfg.cosmosGRPC, + *cfg.cosmosGasPrices, + *cfg.tendermintRPC, + cosmosKeyring, signerFn, personalSignFn, ) - cancelWait() + orShutdown(err) + + // See if the provided ETH address belongs to a validator and determine in which mode peggo should run + isValidator, err := isValidatorAddress(injNetwork.PeggyQueryClient, ethKeyFromAddress) + if err != nil { + log.WithError(err).Fatalln("failed to query the current validator set from injective") + return + } - // injective end - - // Query peggy params - cosmosQueryClient := cosmos.NewPeggyQueryClient(peggyQuerier) ctx, cancelFn := context.WithCancel(context.Background()) closer.Bind(cancelFn) - peggyParams, err := cosmosQueryClient.PeggyParams(ctx) + // Construct erc20 token mapping + peggyParams, err := injNetwork.PeggyParams(ctx) if err != nil { log.WithError(err).Fatalln("failed to query peggy params, is injectived running?") } @@ -126,78 +97,43 @@ func orchestratorCmd(cmd *cli.Cmd) { peggyAddress := ethcmn.HexToAddress(peggyParams.BridgeEthereumAddress) injAddress := ethcmn.HexToAddress(peggyParams.CosmosCoinErc20Contract) - // Check if the provided ETH address belongs to a validator - isValidator, err := isValidatorAddress(cosmosQueryClient, ethKeyFromAddress) - if err != nil { - log.WithError(err).Fatalln("failed to query the current validator set from injective") - - return - } - erc20ContractMapping := make(map[ethcmn.Address]string) erc20ContractMapping[injAddress] = ctypes.InjectiveCoin - // eth start - - evmRPC, err := rpc.Dial(*cfg.ethNodeRPC) - if err != nil { - log.WithField("endpoint", *cfg.ethNodeRPC).WithError(err).Fatalln("Failed to connect to Ethereum RPC") - return - } - // todo dusan: last error return - ethProvider := provider.NewEVMProvider(evmRPC) - log.Infoln("Connected to Ethereum RPC at", *cfg.ethNodeRPC) - - ethCommitter, err := committer.NewEthCommitter(ethKeyFromAddress, *cfg.ethGasPriceAdjustment, *cfg.ethMaxGasPrice, signerFn, ethProvider) - orShutdown(err) - - pendingTxInputList := peggy.PendingTxInputList{} - - pendingTxWaitDuration, err := time.ParseDuration(*cfg.pendingTxWaitDuration) - orShutdown(err) - - peggyContract, err := peggy.NewPeggyContract(ethCommitter, peggyAddress, pendingTxInputList, pendingTxWaitDuration) - orShutdown(err) - - // If Alchemy Websocket URL is set, then Subscribe to Pending Transaction of Peggy Contract. - if *cfg.ethNodeAlchemyWS != "" { - go peggyContract.SubscribeToPendingTxs(*cfg.ethNodeAlchemyWS) - } - - // eth end - - relayer := relayer.NewPeggyRelayer( - cosmosQueryClient, - tmclient.NewRPCClient(*cfg.tendermintRPC), - peggyContract, - *cfg.relayValsets, - *cfg.relayValsetOffsetDur, - *cfg.relayBatches, - *cfg.relayBatchOffsetDur, + // init ethereum network + ethNetwork, err := ethereum.NewNetwork( + *cfg.ethNodeRPC, + peggyAddress, + ethKeyFromAddress, + signerFn, + *cfg.ethGasPriceAdjustment, + *cfg.ethMaxGasPrice, + *cfg.pendingTxWaitDuration, + *cfg.ethNodeAlchemyWS, ) + orShutdown(err) coingeckoFeed := coingecko.NewCoingeckoPriceFeed(100, &coingecko.Config{BaseURL: *cfg.coingeckoApi}) // make the flag obsolete and hardcode *cfg.minBatchFeeUSD = 49.0 - svc := orchestrator.NewPeggyOrchestrator( - cosmosQueryClient, - peggyBroadcaster, - peggyContract, - ethKeyFromAddress, + peggo, err := orchestrator.NewPeggyOrchestrator( + injNetwork, + ethNetwork, + coingeckoFeed, erc20ContractMapping, - relayer, *cfg.minBatchFeeUSD, - coingeckoFeed, *cfg.periodicBatchRequesting, + *cfg.relayValsets, + *cfg.relayBatches, + *cfg.relayValsetOffsetDur, + *cfg.relayBatchOffsetDur, ) go func() { - if err := svc.Start(ctx, isValidator); err != nil { + if err := peggo.Run(ctx, isValidator); err != nil { log.Errorln(err) - - // signal there that the app failed os.Exit(1) } }() diff --git a/cmd/peggo/tx.go b/cmd/peggo/tx.go index 9035d9f4..ac75366e 100644 --- a/cmd/peggo/tx.go +++ b/cmd/peggo/tx.go @@ -4,15 +4,19 @@ import ( "context" "time" + "google.golang.org/grpc" + "google.golang.org/grpc/connectivity" + cli "github.com/jawher/mow.cli" - rpchttp "github.com/tendermint/tendermint/rpc/client/http" "github.com/xlab/closer" log "github.com/xlab/suplog" - "github.com/InjectiveLabs/peggo/orchestrator/cosmos" "github.com/InjectiveLabs/sdk-go/chain/peggy/types" chainclient "github.com/InjectiveLabs/sdk-go/client/chain" "github.com/InjectiveLabs/sdk-go/client/common" + rpchttp "github.com/tendermint/tendermint/rpc/client/http" + + "github.com/InjectiveLabs/peggo/orchestrator/cosmos" ) // txCmdSubset contains actions that can sign and send messages to Cosmos module @@ -177,3 +181,23 @@ func registerEthKeyCmd(cmd *cli.Cmd) { ethKeyFromAddress, valAddress.String()) } } + +// waitForService awaits an active ClientConn to a GRPC service. +func waitForService(ctx context.Context, clientconn *grpc.ClientConn) { + for { + select { + case <-ctx.Done(): + log.Fatalln("GRPC service wait timed out") + default: + state := clientconn.GetState() + + if state != connectivity.Ready { + log.WithField("state", state.String()).Warningln("state of GRPC connection not ready") + time.Sleep(5 * time.Second) + continue + } + + return + } + } +} diff --git a/cmd/peggo/util.go b/cmd/peggo/util.go index 222c8c97..62d44ac0 100644 --- a/cmd/peggo/util.go +++ b/cmd/peggo/util.go @@ -3,7 +3,6 @@ package main import ( "bufio" "bytes" - "context" "encoding/hex" "fmt" "io/ioutil" @@ -14,7 +13,6 @@ import ( ethcmn "github.com/ethereum/go-ethereum/common" log "github.com/xlab/suplog" "google.golang.org/grpc" - "google.golang.org/grpc/connectivity" ) // readEnv is a special utility that reads `.env` file into actual environment variables @@ -131,26 +129,6 @@ func hexToBytes(str string) ([]byte, error) { return data, nil } -// waitForService awaits an active ClientConn to a GRPC service. -func waitForService(ctx context.Context, clientconn *grpc.ClientConn) { - for { - select { - case <-ctx.Done(): - log.Fatalln("GRPC service wait timed out") - default: - state := clientconn.GetState() - - if state != connectivity.Ready { - log.WithField("state", state.String()).Warningln("state of GRPC connection not ready") - time.Sleep(5 * time.Second) - continue - } - - return - } - } -} - // orShutdown fatals the app if there was an error. func orShutdown(err error) { if err != nil && err != grpc.ErrServerStopped { diff --git a/orchestrator/cosmos/network.go b/orchestrator/cosmos/network.go index 218239e7..9597cab3 100644 --- a/orchestrator/cosmos/network.go +++ b/orchestrator/cosmos/network.go @@ -3,10 +3,22 @@ package cosmos import ( "context" "github.com/InjectiveLabs/peggo/orchestrator/cosmos/tmclient" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum/keystore" peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" peggy "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + chainclient "github.com/InjectiveLabs/sdk-go/client/chain" + "github.com/InjectiveLabs/sdk-go/client/common" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/ethereum/go-ethereum/accounts/abi/bind" ethcmn "github.com/ethereum/go-ethereum/common" + rpchttp "github.com/tendermint/tendermint/rpc/client/http" tmctypes "github.com/tendermint/tendermint/rpc/core/types" + log "github.com/xlab/suplog" + "google.golang.org/grpc" + "google.golang.org/grpc/connectivity" + "time" + + "github.com/InjectiveLabs/sdk-go/chain/peggy/types" ) type Network struct { @@ -15,8 +27,52 @@ type Network struct { PeggyBroadcastClient } -func NewNetwork() (*Network, error) { - return nil, nil +func NewNetwork( + chainID, + validatorAddress, + injectiveGRPC, + injectiveGasPrices, + tendermintRPC string, + keyring keyring.Keyring, + signerFn bind.SignerFn, + personalSignerFn keystore.PersonalSignFn, +) (*Network, error) { + clientCtx, err := chainclient.NewClientContext(chainID, validatorAddress, keyring) + if err != nil { + log.WithError(err).Fatalln("failed to initialize cosmos client context") + } + + clientCtx = clientCtx.WithNodeURI(tendermintRPC) + + tmRPC, err := rpchttp.New(tendermintRPC, "/websocket") + if err != nil { + log.WithError(err) + } + + clientCtx = clientCtx.WithClient(tmRPC) + + daemonClient, err := chainclient.NewChainClient(clientCtx, injectiveGRPC, common.OptionGasPrices(injectiveGasPrices)) + if err != nil { + log.WithError(err).WithFields(log.Fields{"endpoint": injectiveGRPC}).Fatalln("failed to connect to daemon, is injectived running?") + } + + log.Infoln("Waiting for injectived GRPC") + time.Sleep(1 * time.Second) + + daemonWaitCtx, cancelWait := context.WithTimeout(context.Background(), time.Minute) + defer cancelWait() + + grpcConn := daemonClient.QueryClient() + waitForService(daemonWaitCtx, grpcConn) + peggyQuerier := types.NewQueryClient(grpcConn) + + n := &Network{ + TendermintClient: tmclient.NewRPCClient(tendermintRPC), + PeggyQueryClient: NewPeggyQueryClient(peggyQuerier), + PeggyBroadcastClient: NewPeggyBroadcastClient(peggyQuerier, daemonClient, signerFn, personalSignerFn), + } + + return n, nil } func (n *Network) GetBlock(ctx context.Context, height int64) (*tmctypes.ResultBlock, error) { @@ -103,3 +159,23 @@ func (n *Network) SendBatchConfirm( ) error { return n.PeggyBroadcastClient.SendBatchConfirm(ctx, ethFrom, peggyID, batch) } + +// waitForService awaits an active ClientConn to a GRPC service. +func waitForService(ctx context.Context, clientconn *grpc.ClientConn) { + for { + select { + case <-ctx.Done(): + log.Fatalln("GRPC service wait timed out") + default: + state := clientconn.GetState() + + if state != connectivity.Ready { + log.WithField("state", state.String()).Warningln("state of GRPC connection not ready") + time.Sleep(5 * time.Second) + continue + } + + return + } + } +} diff --git a/orchestrator/ethereum/network.go b/orchestrator/ethereum/network.go index cfa0dad5..60b9430c 100644 --- a/orchestrator/ethereum/network.go +++ b/orchestrator/ethereum/network.go @@ -69,9 +69,7 @@ func NewNetwork( go peggyContract.SubscribeToPendingTxs(ethNodeAlchemyWS) } - return &Network{ - PeggyContract: peggyContract, - }, nil + return &Network{PeggyContract: peggyContract}, nil } func (n *Network) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { diff --git a/orchestrator/main_loops.go b/orchestrator/main_loops.go index a10a854a..4a3c6602 100644 --- a/orchestrator/main_loops.go +++ b/orchestrator/main_loops.go @@ -15,9 +15,9 @@ import ( const defaultLoopDur = 60 * time.Second -// Start combines the all major roles required to make +// Run combines the all major roles required to make // up the Orchestrator, all of these are async loops. -func (s *PeggyOrchestrator) Start(ctx context.Context, validatorMode bool) error { +func (s *PeggyOrchestrator) Run(ctx context.Context, validatorMode bool) error { if !validatorMode { log.Infoln("Starting peggo in relayer (non-validator) mode") return s.startRelayerMode(ctx) diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index b2137156..36eabf73 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -2,21 +2,16 @@ package orchestrator import ( "context" - peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" - peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" - "github.com/ethereum/go-ethereum/core/types" - tmctypes "github.com/tendermint/tendermint/rpc/core/types" "math/big" "time" + peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" ethcmn "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + tmctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/InjectiveLabs/metrics" - "github.com/InjectiveLabs/peggo/orchestrator/coingecko" - sidechain "github.com/InjectiveLabs/peggo/orchestrator/cosmos" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/peggy" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/provider" - "github.com/InjectiveLabs/peggo/orchestrator/relayer" + peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" ) type PriceFeed interface { @@ -90,51 +85,65 @@ type EthereumNetwork interface { type PeggyOrchestrator struct { svcTags metrics.Tags - pricefeed PriceFeed injective InjectiveNetwork ethereum EthereumNetwork + pricefeed PriceFeed + + erc20ContractMapping map[ethcmn.Address]string + minBatchFeeUSD float64 + maxRetries uint - cosmosQueryClient sidechain.PeggyQueryClient - peggyBroadcastClient sidechain.PeggyBroadcastClient - peggyContract peggy.PeggyContract - ethProvider provider.EVMProvider - ethFrom ethcmn.Address - erc20ContractMapping map[ethcmn.Address]string - relayer relayer.PeggyRelayer - minBatchFeeUSD float64 - priceFeeder *coingecko.CoingeckoPriceFeed - maxRetries uint - periodicBatchRequesting bool - valsetRelayEnabled bool - batchRelayEnabled bool relayValsetOffsetDur, - relayBatchOffsetDur time.Duration // todo: parsed from string + relayBatchOffsetDur time.Duration + + valsetRelayEnabled bool + batchRelayEnabled bool + + periodicBatchRequesting bool } func NewPeggyOrchestrator( - cosmosQueryClient sidechain.PeggyQueryClient, - peggyBroadcastClient sidechain.PeggyBroadcastClient, - peggyContract peggy.PeggyContract, - ethFrom ethcmn.Address, + injective InjectiveNetwork, + ethereum EthereumNetwork, + priceFeed PriceFeed, erc20ContractMapping map[ethcmn.Address]string, - relayer relayer.PeggyRelayer, minBatchFeeUSD float64, - priceFeeder *coingecko.CoingeckoPriceFeed, - periodicBatchRequesting bool, -) *PeggyOrchestrator { - return &PeggyOrchestrator{ - cosmosQueryClient: cosmosQueryClient, - peggyBroadcastClient: peggyBroadcastClient, - peggyContract: peggyContract, - ethProvider: peggyContract.Provider(), - ethFrom: ethFrom, + periodicBatchRequesting, + valsetRelayingEnabled, + batchRelayingEnabled bool, + valsetRelayingOffset, + batchRelayingOffset string, +) (*PeggyOrchestrator, error) { + orch := &PeggyOrchestrator{ + svcTags: metrics.Tags{"svc": "peggy_orchestrator"}, + injective: injective, + ethereum: ethereum, + pricefeed: priceFeed, erc20ContractMapping: erc20ContractMapping, - relayer: relayer, minBatchFeeUSD: minBatchFeeUSD, - priceFeeder: priceFeeder, periodicBatchRequesting: periodicBatchRequesting, - svcTags: metrics.Tags{ - "svc": "peggy_orchestrator", - }, + valsetRelayEnabled: valsetRelayingEnabled, + batchRelayEnabled: batchRelayingEnabled, + maxRetries: 10, // default is 10 for retry pkg } + + if valsetRelayingEnabled { + dur, err := time.ParseDuration(valsetRelayingOffset) + if err != nil { + return nil, err + } + + orch.relayValsetOffsetDur = dur + } + + if batchRelayingEnabled { + dur, err := time.ParseDuration(batchRelayingOffset) + if err != nil { + return nil, err + } + + orch.relayBatchOffsetDur = dur + } + + return orch, nil } From 30b25016d2dfd65981037aa58061442e9852af85 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Thu, 15 Jun 2023 13:20:01 +0200 Subject: [PATCH 27/72] remove legacy/deprecated code --- orchestrator/batch_request.go | 2 +- orchestrator/batch_request_test.go | 8 +- orchestrator/ethereum/network.go | 57 +++--- orchestrator/main_loops.go | 176 ----------------- orchestrator/oracle.go | 161 +--------------- orchestrator/orchestrator.go | 102 +++++++--- orchestrator/relayer.go | 116 ++++------- orchestrator/relayer/batch_relaying.go | 109 ----------- orchestrator/relayer/find_latest_valset.go | 212 --------------------- orchestrator/relayer/main_loop.go | 50 ----- orchestrator/relayer/relayer.go | 57 ------ orchestrator/relayer/valset_relaying.go | 108 ----------- orchestrator/signer.go | 10 +- orchestrator/signer_test.go | 12 +- 14 files changed, 163 insertions(+), 1017 deletions(-) delete mode 100644 orchestrator/main_loops.go delete mode 100644 orchestrator/relayer/batch_relaying.go delete mode 100644 orchestrator/relayer/find_latest_valset.go delete mode 100644 orchestrator/relayer/main_loop.go delete mode 100644 orchestrator/relayer/relayer.go delete mode 100644 orchestrator/relayer/valset_relaying.go diff --git a/orchestrator/batch_request.go b/orchestrator/batch_request.go index cb28d360..86f18003 100644 --- a/orchestrator/batch_request.go +++ b/orchestrator/batch_request.go @@ -79,7 +79,7 @@ func (s *PeggyOrchestrator) getBatchFeesByToken(ctx context.Context, log log.Log if err := retry.Do(retryFn, retry.Context(ctx), - retry.Attempts(s.maxRetries), + retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { log.WithError(err).Errorf("failed to get UnbatchedTokensWithFees, will retry (%d)", n) }), diff --git a/orchestrator/batch_request_test.go b/orchestrator/batch_request_test.go index 8eaa255a..7286dd41 100644 --- a/orchestrator/batch_request_test.go +++ b/orchestrator/batch_request_test.go @@ -20,7 +20,7 @@ func TestRequestBatches(t *testing.T) { t.Parallel() orch := &PeggyOrchestrator{ - maxRetries: 1, + maxAttempts: 1, injective: &mockInjective{ unbatchedTokenFeesFn: func(context.Context) ([]*peggy.BatchFees, error) { return nil, errors.New("fail") @@ -35,7 +35,7 @@ func TestRequestBatches(t *testing.T) { t.Parallel() orch := &PeggyOrchestrator{ - maxRetries: 1, + maxAttempts: 1, injective: &mockInjective{ unbatchedTokenFeesFn: func(context.Context) ([]*peggy.BatchFees, error) { return nil, nil @@ -65,7 +65,7 @@ func TestRequestBatches(t *testing.T) { } orch := &PeggyOrchestrator{ - maxRetries: 1, + maxAttempts: 1, minBatchFeeUSD: 51.0, erc20ContractMapping: map[eth.Address]string{tokenAddr: "inj"}, pricefeed: mockPriceFeed{queryFn: func(_ eth.Address) (float64, error) { return 1, nil }}, @@ -95,7 +95,7 @@ func TestRequestBatches(t *testing.T) { } orch := &PeggyOrchestrator{ - maxRetries: 1, + maxAttempts: 1, minBatchFeeUSD: 49.0, erc20ContractMapping: map[eth.Address]string{tokenAddr: "inj"}, pricefeed: mockPriceFeed{queryFn: func(_ eth.Address) (float64, error) { diff --git a/orchestrator/ethereum/network.go b/orchestrator/ethereum/network.go index 60b9430c..6820d00d 100644 --- a/orchestrator/ethereum/network.go +++ b/orchestrator/ethereum/network.go @@ -2,23 +2,22 @@ package ethereum import ( "context" - peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" "math/big" "strings" "time" - "github.com/pkg/errors" - log "github.com/xlab/suplog" - "github.com/ethereum/go-ethereum/accounts/abi/bind" ethcmn "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" + "github.com/pkg/errors" + log "github.com/xlab/suplog" "github.com/InjectiveLabs/peggo/orchestrator/ethereum/committer" "github.com/InjectiveLabs/peggo/orchestrator/ethereum/peggy" "github.com/InjectiveLabs/peggo/orchestrator/ethereum/provider" wrappers "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" + peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" ) type Network struct { @@ -72,10 +71,18 @@ func NewNetwork( return &Network{PeggyContract: peggyContract}, nil } +func (n *Network) FromAddress() ethcmn.Address { + return n.PeggyContract.FromAddress() +} + func (n *Network) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { return n.Provider().HeaderByNumber(ctx, number) } +func (n *Network) GetPeggyID(ctx context.Context) (ethcmn.Hash, error) { + return n.PeggyContract.GetPeggyID(ctx, n.FromAddress()) +} + func (n *Network) GetSendToCosmosEvents(startBlock, endBlock uint64) ([]*wrappers.PeggySendToCosmosEvent, error) { peggyFilterer, err := wrappers.NewPeggyFilterer(n.Address(), n.Provider()) if err != nil { @@ -134,16 +141,16 @@ func (n *Network) GetSendToInjectiveEvents(startBlock, endBlock uint64) ([]*wrap return sendToInjectiveEvents, nil } -func (n *Network) GetTransactionBatchExecutedEvents(startBlock, endBlock uint64) ([]*wrappers.PeggyTransactionBatchExecutedEvent, error) { +func (n *Network) GetPeggyERC20DeployedEvents(startBlock, endBlock uint64) ([]*wrappers.PeggyERC20DeployedEvent, error) { peggyFilterer, err := wrappers.NewPeggyFilterer(n.Address(), n.Provider()) if err != nil { return nil, errors.Wrap(err, "failed to init Peggy events filterer") } - iter, err := peggyFilterer.FilterTransactionBatchExecutedEvent(&bind.FilterOpts{ + iter, err := peggyFilterer.FilterERC20DeployedEvent(&bind.FilterOpts{ Start: startBlock, End: &endBlock, - }, nil, nil) + }, nil) if err != nil { if !isUnknownBlockErr(err) { return nil, errors.Wrap(err, "failed to scan past TransactionBatchExecuted events from Ethereum") @@ -152,7 +159,7 @@ func (n *Network) GetTransactionBatchExecutedEvents(startBlock, endBlock uint64) } } - var transactionBatchExecutedEvents []*wrappers.PeggyTransactionBatchExecutedEvent + var transactionBatchExecutedEvents []*wrappers.PeggyERC20DeployedEvent for iter.Next() { transactionBatchExecutedEvents = append(transactionBatchExecutedEvents, iter.Event) } @@ -162,68 +169,60 @@ func (n *Network) GetTransactionBatchExecutedEvents(startBlock, endBlock uint64) return transactionBatchExecutedEvents, nil } -func (n *Network) GetPeggyERC20DeployedEvents(startBlock, endBlock uint64) ([]*wrappers.PeggyERC20DeployedEvent, error) { +func (n *Network) GetValsetUpdatedEvents(startBlock, endBlock uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { peggyFilterer, err := wrappers.NewPeggyFilterer(n.Address(), n.Provider()) if err != nil { return nil, errors.Wrap(err, "failed to init Peggy events filterer") } - iter, err := peggyFilterer.FilterERC20DeployedEvent(&bind.FilterOpts{ + iter, err := peggyFilterer.FilterValsetUpdatedEvent(&bind.FilterOpts{ Start: startBlock, End: &endBlock, }, nil) if err != nil { if !isUnknownBlockErr(err) { - return nil, errors.Wrap(err, "failed to scan past TransactionBatchExecuted events from Ethereum") + return nil, errors.Wrap(err, "failed to scan past ValsetUpdatedEvent events from Ethereum") } else if iter == nil { return nil, errors.New("no iterator returned") } } - var transactionBatchExecutedEvents []*wrappers.PeggyERC20DeployedEvent + var valsetUpdatedEvents []*wrappers.PeggyValsetUpdatedEvent for iter.Next() { - transactionBatchExecutedEvents = append(transactionBatchExecutedEvents, iter.Event) + valsetUpdatedEvents = append(valsetUpdatedEvents, iter.Event) } iter.Close() - return transactionBatchExecutedEvents, nil + return valsetUpdatedEvents, nil } -func (n *Network) GetValsetUpdatedEvents(startBlock, endBlock uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { +func (n *Network) GetTransactionBatchExecutedEvents(startBlock, endBlock uint64) ([]*wrappers.PeggyTransactionBatchExecutedEvent, error) { peggyFilterer, err := wrappers.NewPeggyFilterer(n.Address(), n.Provider()) if err != nil { return nil, errors.Wrap(err, "failed to init Peggy events filterer") } - iter, err := peggyFilterer.FilterValsetUpdatedEvent(&bind.FilterOpts{ + iter, err := peggyFilterer.FilterTransactionBatchExecutedEvent(&bind.FilterOpts{ Start: startBlock, End: &endBlock, - }, nil) + }, nil, nil) if err != nil { if !isUnknownBlockErr(err) { - return nil, errors.Wrap(err, "failed to scan past ValsetUpdatedEvent events from Ethereum") + return nil, errors.Wrap(err, "failed to scan past TransactionBatchExecuted events from Ethereum") } else if iter == nil { return nil, errors.New("no iterator returned") } } - var valsetUpdatedEvents []*wrappers.PeggyValsetUpdatedEvent + var transactionBatchExecutedEvents []*wrappers.PeggyTransactionBatchExecutedEvent for iter.Next() { - valsetUpdatedEvents = append(valsetUpdatedEvents, iter.Event) + transactionBatchExecutedEvents = append(transactionBatchExecutedEvents, iter.Event) } iter.Close() - return valsetUpdatedEvents, nil -} - -func (n *Network) GetPeggyID(ctx context.Context) (ethcmn.Hash, error) { - return n.PeggyContract.GetPeggyID(ctx, n.FromAddress()) -} - -func (n *Network) FromAddress() ethcmn.Address { - return n.PeggyContract.FromAddress() + return transactionBatchExecutedEvents, nil } func (n *Network) GetValsetNonce(ctx context.Context) (*big.Int, error) { diff --git a/orchestrator/main_loops.go b/orchestrator/main_loops.go deleted file mode 100644 index 4a3c6602..00000000 --- a/orchestrator/main_loops.go +++ /dev/null @@ -1,176 +0,0 @@ -package orchestrator - -import ( - "context" - "math" - "math/big" - "time" - - log "github.com/xlab/suplog" - - "github.com/InjectiveLabs/sdk-go/chain/peggy/types" - - "github.com/InjectiveLabs/peggo/orchestrator/loops" -) - -const defaultLoopDur = 60 * time.Second - -// Run combines the all major roles required to make -// up the Orchestrator, all of these are async loops. -func (s *PeggyOrchestrator) Run(ctx context.Context, validatorMode bool) error { - if !validatorMode { - log.Infoln("Starting peggo in relayer (non-validator) mode") - return s.startRelayerMode(ctx) - } - - log.Infoln("Starting peggo in validator mode") - return s.startValidatorMode(ctx) -} - -// This loop doesn't have a formal role per say, anyone can request a valset -// but there does need to be some strategy to ensure requests are made. Having it -// be a function of the orchestrator makes a lot of sense as they are already online -// and have all the required funds, keys, and rpc servers setup -// -// Exactly how to balance optimizing this versus testing is an interesting discussion -// in testing we want to make sure requests are made without any powers changing on the chain -// just to simplify the test environment. But in production that's somewhat wasteful. What this -// routine does it check the current valset versus the last requested valset, if power has changed -// significantly we send in a request. - -/* -Not required any more. The valset request are generated in endblocker of peggy module automatically. Also MsgSendValsetRequest is removed on peggy module. - -func (s *PeggyOrchestrator) ValsetRequesterLoop(ctx context.Context) (err error) { - logger := log.WithField("loop", "ValsetRequesterLoop") - - return loops.RunLoop(ctx, defaultLoopDur, func() error { - var latestValsets []*types.Valset - var currentValset *types.Valset - - var pg loops.ParanoidGroup - - pg.Go(func() error { - return retry.Do(func() (err error) { - latestValsets, err = s.cosmosQueryClient.LatestValsets(ctx) - return - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get latest valsets, will retry (%d)", n) - })) - }) - - pg.Go(func() error { - return retry.Do(func() (err error) { - currentValset, err = s.cosmosQueryClient.CurrentValset(ctx) - return - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get current valset, will retry (%d)", n) - })) - }) - - if err := pg.Wait(); err != nil { - logger.WithError(err).Errorln("got error, loop exits") - return err - } - - if len(latestValsets) == 0 { - retry.Do(func() error { - return s.peggyBroadcastClient.SendValsetRequest(ctx) - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to request Valset to be formed, will retry (%d)", n) - })) - } else { - // if the power difference is more than 1% different than the last valset - if valPowerDiff(latestValsets[0], currentValset) > 0.01 { - log.Debugln("power difference is more than 1%% different than the last valset. Sending valset request") - - retry.Do(func() error { - return s.peggyBroadcastClient.SendValsetRequest(ctx) - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to request Valset to be formed, will retry (%d)", n) - })) - } - } - - return nil - }) -} -**/ - -// valPowerDiff returns the difference in power between two bridge validator sets -// TODO: this needs to be potentially refactored -func valPowerDiff(old *types.Valset, new *types.Valset) float64 { - powers := map[string]int64{} - var totalB int64 - // loop over b and initialize the map with their powers - for _, bv := range old.GetMembers() { - powers[bv.EthereumAddress] = int64(bv.Power) - totalB += int64(bv.Power) - } - - // subtract c powers from powers in the map, initializing - // uninitialized keys with negative numbers - for _, bv := range new.GetMembers() { - if val, ok := powers[bv.EthereumAddress]; ok { - powers[bv.EthereumAddress] = val - int64(bv.Power) - } else { - powers[bv.EthereumAddress] = -int64(bv.Power) - } - } - - var delta float64 - for _, v := range powers { - // NOTE: we care about the absolute value of the changes - delta += math.Abs(float64(v)) - } - - return math.Abs(delta / float64(totalB)) -} - -func calculateTotalValsetPower(valset *types.Valset) *big.Int { - totalValsetPower := new(big.Int) - for _, m := range valset.Members { - mPower := big.NewInt(0).SetUint64(m.Power) - totalValsetPower.Add(totalValsetPower, mPower) - } - - return totalValsetPower -} - -// startValidatorMode runs all orchestrator processes. This is called -// when peggo is run alongside a validator injective node. -func (s *PeggyOrchestrator) startValidatorMode(ctx context.Context) error { - var pg loops.ParanoidGroup - - pg.Go(func() error { - return s.EthOracleMainLoop(ctx) - }) - pg.Go(func() error { - return s.BatchRequesterLoop(ctx) - }) - pg.Go(func() error { - return s.EthSignerMainLoop(ctx) - }) - pg.Go(func() error { - return s.RelayerMainLoop(ctx) - }) - - return pg.Wait() -} - -// startRelayerMode runs orchestrator processes that only relay specific -// messages that do not require a validator's signature. This mode is run -// alongside a non-validator injective node -func (s *PeggyOrchestrator) startRelayerMode(ctx context.Context) error { - var pg loops.ParanoidGroup - - pg.Go(func() error { - return s.BatchRequesterLoop(ctx) - }) - - pg.Go(func() error { - return s.RelayerMainLoop(ctx) - }) - - return pg.Wait() -} diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index a686264b..0a405859 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -47,7 +47,7 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { if err := retry.Do(retryFn, retry.Context(ctx), - retry.Attempts(s.maxRetries), + retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { logger.WithError(err).Warningf("failed to get last checked block, will retry (%d)", n) }), @@ -65,7 +65,7 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { return }, retry.Context(ctx), - retry.Attempts(s.maxRetries), + retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { logger.WithError(err).Warningf("error during Eth event checking, will retry (%d)", n) }), @@ -89,7 +89,7 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { return }, retry.Context(ctx), - retry.Attempts(s.maxRetries), + retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { logger.WithError(err).Warningf("failed to get last checked block, will retry (%d)", n) }), @@ -148,210 +148,55 @@ func (s *PeggyOrchestrator) relayEthEvents( currentBlock = startingBlock + defaultBlocksToSearch } - // todo: this will be part of each Get**Events method - //peggyFilterer, err := wrappers.NewPeggyFilterer(s.peggyContract.Address(), s.ethProvider) - //if err != nil { - // metrics.ReportFuncError(s.svcTags) - // err = errors.Wrap(err, "failed to init Peggy events filterer") - // return 0, err - //} - - // todo legacyDeposits, err := s.ethereum.GetSendToCosmosEvents(startingBlock, currentBlock) if err != nil { log.WithFields(log.Fields{"start": startingBlock, "end": currentBlock}).Errorln("failed to scan past SendToCosmos events from Ethereum") return 0, err } - //var sendToCosmosEvents []*wrappers.PeggySendToCosmosEvent - //{ - // - // iter, err := peggyFilterer.FilterSendToCosmosEvent(&bind.FilterOpts{ - // Start: startingBlock, - // End: ¤tBlock, - // }, nil, nil, nil) - // if err != nil { - // metrics.ReportFuncError(s.svcTags) - // log.WithFields(log.Fields{ - // "start": startingBlock, - // "end": currentBlock, - // }).Errorln("failed to scan past SendToCosmos events from Ethereum") - // - // if !isUnknownBlockErr(err) { - // err = errors.Wrap(err, "failed to scan past SendToCosmos events from Ethereum") - // return 0, err - // } else if iter == nil { - // return 0, errors.New("no iterator returned") - // } - // } - // - // for iter.Next() { - // sendToCosmosEvents = append(sendToCosmosEvents, iter.Event) - // } - // - // iter.Close() - //} - log.WithFields(log.Fields{ "start": startingBlock, "end": currentBlock, "OldDeposits": legacyDeposits, }).Debugln("Scanned SendToCosmos events from Ethereum") - // todo deposits, err := s.ethereum.GetSendToInjectiveEvents(startingBlock, currentBlock) if err != nil { return 0, err } - //var sendToInjectiveEvents []*wrappers.PeggySendToInjectiveEvent - //{ - // - // iter, err := peggyFilterer.FilterSendToInjectiveEvent(&bind.FilterOpts{ - // Start: startingBlock, - // End: ¤tBlock, - // }, nil, nil, nil) - // if err != nil { - // metrics.ReportFuncError(s.svcTags) - // log.WithFields(log.Fields{ - // "start": startingBlock, - // "end": currentBlock, - // }).Errorln("failed to scan past SendToInjective events from Ethereum") - // - // if !isUnknownBlockErr(err) { - // err = errors.Wrap(err, "failed to scan past SendToInjective events from Ethereum") - // return 0, err - // } else if iter == nil { - // return 0, errors.New("no iterator returned") - // } - // } - // - // for iter.Next() { - // sendToInjectiveEvents = append(sendToInjectiveEvents, iter.Event) - // } - // - // iter.Close() - //} - log.WithFields(log.Fields{ "start": startingBlock, "end": currentBlock, "Deposits": deposits, }).Debugln("Scanned SendToInjective events from Ethereum") - // todo withdrawals, err := s.ethereum.GetTransactionBatchExecutedEvents(startingBlock, currentBlock) if err != nil { return 0, err } - //var transactionBatchExecutedEvents []*wrappers.PeggyTransactionBatchExecutedEvent - //{ - // iter, err := peggyFilterer.FilterTransactionBatchExecutedEvent(&bind.FilterOpts{ - // Start: startingBlock, - // End: ¤tBlock, - // }, nil, nil) - // if err != nil { - // metrics.ReportFuncError(s.svcTags) - // log.WithFields(log.Fields{ - // "start": startingBlock, - // "end": currentBlock, - // }).Errorln("failed to scan past TransactionBatchExecuted events from Ethereum") - // - // if !isUnknownBlockErr(err) { - // err = errors.Wrap(err, "failed to scan past TransactionBatchExecuted events from Ethereum") - // return 0, err - // } else if iter == nil { - // return 0, errors.New("no iterator returned") - // } - // } - // - // for iter.Next() { - // transactionBatchExecutedEvents = append(transactionBatchExecutedEvents, iter.Event) - // } - // - // iter.Close() - //} - log.WithFields(log.Fields{ "start": startingBlock, "end": currentBlock, "Withdraws": withdrawals, }).Debugln("Scanned TransactionBatchExecuted events from Ethereum") - // todo erc20Deployments, err := s.ethereum.GetPeggyERC20DeployedEvents(startingBlock, currentBlock) if err != nil { return 0, err } - //var erc20DeployedEvents []*wrappers.PeggyERC20DeployedEvent - //{ - // iter, err := peggyFilterer.FilterERC20DeployedEvent(&bind.FilterOpts{ - // Start: startingBlock, - // End: ¤tBlock, - // }, nil) - // if err != nil { - // metrics.ReportFuncError(s.svcTags) - // log.WithFields(log.Fields{ - // "start": startingBlock, - // "end": currentBlock, - // }).Errorln("failed to scan past FilterERC20Deployed events from Ethereum") - // - // if !isUnknownBlockErr(err) { - // err = errors.Wrap(err, "failed to scan past FilterERC20Deployed events from Ethereum") - // return 0, err - // } else if iter == nil { - // return 0, errors.New("no iterator returned") - // } - // } - // - // for iter.Next() { - // erc20DeployedEvents = append(erc20DeployedEvents, iter.Event) - // } - // - // iter.Close() - //} - log.WithFields(log.Fields{ "start": startingBlock, "end": currentBlock, "erc20Deployed": erc20Deployments, }).Debugln("Scanned FilterERC20Deployed events from Ethereum") - // todo valsetUpdates, err := s.ethereum.GetValsetUpdatedEvents(startingBlock, currentBlock) if err != nil { } - //var valsetUpdatedEvents []*wrappers.PeggyValsetUpdatedEvent - //{ - // iter, err := peggyFilterer.FilterValsetUpdatedEvent(&bind.FilterOpts{ - // Start: startingBlock, - // End: ¤tBlock, - // }, nil) - // if err != nil { - // metrics.ReportFuncError(s.svcTags) - // log.WithFields(log.Fields{ - // "start": startingBlock, - // "end": currentBlock, - // }).Errorln("failed to scan past ValsetUpdatedEvent events from Ethereum") - // - // if !isUnknownBlockErr(err) { - // err = errors.Wrap(err, "failed to scan past ValsetUpdatedEvent events from Ethereum") - // return 0, err - // } else if iter == nil { - // return 0, errors.New("no iterator returned") - // } - // } - // - // for iter.Next() { - // valsetUpdatedEvents = append(valsetUpdatedEvents, iter.Event) - // } - // - // iter.Close() - //} - log.WithFields(log.Fields{ "start": startingBlock, "end": currentBlock, diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index 36eabf73..e5c1ae0d 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -5,25 +5,26 @@ import ( "math/big" "time" - peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" - ethcmn "github.com/ethereum/go-ethereum/common" + eth "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" tmctypes "github.com/tendermint/tendermint/rpc/core/types" + log "github.com/xlab/suplog" "github.com/InjectiveLabs/metrics" + "github.com/InjectiveLabs/peggo/orchestrator/loops" peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" + peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" ) type PriceFeed interface { - QueryUSDPrice(address ethcmn.Address) (float64, error) + QueryUSDPrice(address eth.Address) (float64, error) } type InjectiveNetwork interface { PeggyParams(ctx context.Context) (*peggytypes.Params, error) + GetBlock(ctx context.Context, height int64) (*tmctypes.ResultBlock, error) - UnbatchedTokenFees(ctx context.Context) ([]*peggytypes.BatchFees, error) - SendRequestBatch(ctx context.Context, denom string) error - + // claims LastClaimEvent(ctx context.Context) (*peggytypes.LastClaimEvent, error) SendEthereumClaims( ctx context.Context, @@ -35,26 +36,26 @@ type InjectiveNetwork interface { valsetUpdates []*peggyevents.PeggyValsetUpdatedEvent, ) error - OldestUnsignedValsets(ctx context.Context) ([]*peggytypes.Valset, error) - SendValsetConfirm(ctx context.Context, peggyID ethcmn.Hash, valset *peggytypes.Valset, ethFrom ethcmn.Address) error - + // batches + UnbatchedTokenFees(ctx context.Context) ([]*peggytypes.BatchFees, error) + SendRequestBatch(ctx context.Context, denom string) error OldestUnsignedTransactionBatch(ctx context.Context) (*peggytypes.OutgoingTxBatch, error) - SendBatchConfirm(ctx context.Context, peggyID ethcmn.Hash, batch *peggytypes.OutgoingTxBatch, ethFrom ethcmn.Address) error - - GetBlock(ctx context.Context, height int64) (*tmctypes.ResultBlock, error) + SendBatchConfirm(ctx context.Context, peggyID eth.Hash, batch *peggytypes.OutgoingTxBatch, ethFrom eth.Address) error + LatestTransactionBatches(ctx context.Context) ([]*peggytypes.OutgoingTxBatch, error) + TransactionBatchSignatures(ctx context.Context, nonce uint64, tokenContract eth.Address) ([]*peggytypes.MsgConfirmBatch, error) + // valsets + OldestUnsignedValsets(ctx context.Context) ([]*peggytypes.Valset, error) + SendValsetConfirm(ctx context.Context, peggyID eth.Hash, valset *peggytypes.Valset, ethFrom eth.Address) error LatestValsets(ctx context.Context) ([]*peggytypes.Valset, error) AllValsetConfirms(ctx context.Context, nonce uint64) ([]*peggytypes.MsgValsetConfirm, error) ValsetAt(ctx context.Context, nonce uint64) (*peggytypes.Valset, error) - - LatestTransactionBatches(ctx context.Context) ([]*peggytypes.OutgoingTxBatch, error) - TransactionBatchSignatures(ctx context.Context, nonce uint64, tokenContract ethcmn.Address) ([]*peggytypes.MsgConfirmBatch, error) } type EthereumNetwork interface { - FromAddress() ethcmn.Address + FromAddress() eth.Address HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) - GetPeggyID(ctx context.Context) (ethcmn.Hash, error) + GetPeggyID(ctx context.Context) (eth.Hash, error) GetSendToCosmosEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) GetSendToInjectiveEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) @@ -68,30 +69,31 @@ type EthereumNetwork interface { oldValset *peggytypes.Valset, newValset *peggytypes.Valset, confirms []*peggytypes.MsgValsetConfirm, - ) (*ethcmn.Hash, error) + ) (*eth.Hash, error) GetTxBatchNonce( ctx context.Context, - erc20ContractAddress ethcmn.Address, + erc20ContractAddress eth.Address, ) (*big.Int, error) - SendTransactionBatch( ctx context.Context, currentValset *peggytypes.Valset, batch *peggytypes.OutgoingTxBatch, confirms []*peggytypes.MsgConfirmBatch, - ) (*ethcmn.Hash, error) + ) (*eth.Hash, error) } +const defaultLoopDur = 60 * time.Second + type PeggyOrchestrator struct { svcTags metrics.Tags injective InjectiveNetwork ethereum EthereumNetwork pricefeed PriceFeed - erc20ContractMapping map[ethcmn.Address]string + erc20ContractMapping map[eth.Address]string minBatchFeeUSD float64 - maxRetries uint + maxAttempts uint // max number of times a retry func will be called before exiting relayValsetOffsetDur, relayBatchOffsetDur time.Duration @@ -106,7 +108,7 @@ func NewPeggyOrchestrator( injective InjectiveNetwork, ethereum EthereumNetwork, priceFeed PriceFeed, - erc20ContractMapping map[ethcmn.Address]string, + erc20ContractMapping map[eth.Address]string, minBatchFeeUSD float64, periodicBatchRequesting, valsetRelayingEnabled, @@ -124,7 +126,7 @@ func NewPeggyOrchestrator( periodicBatchRequesting: periodicBatchRequesting, valsetRelayEnabled: valsetRelayingEnabled, batchRelayEnabled: batchRelayingEnabled, - maxRetries: 10, // default is 10 for retry pkg + maxAttempts: 10, // default is 10 for retry pkg } if valsetRelayingEnabled { @@ -147,3 +149,53 @@ func NewPeggyOrchestrator( return orch, nil } + +// Run combines the all major roles required to make +// up the Orchestrator, all of these are async loops. +func (s *PeggyOrchestrator) Run(ctx context.Context, validatorMode bool) error { + if !validatorMode { + log.Infoln("Starting peggo in relayer (non-validator) mode") + return s.startRelayerMode(ctx) + } + + log.Infoln("Starting peggo in validator mode") + return s.startValidatorMode(ctx) +} + +// startValidatorMode runs all orchestrator processes. This is called +// when peggo is run alongside a validator injective node. +func (s *PeggyOrchestrator) startValidatorMode(ctx context.Context) error { + var pg loops.ParanoidGroup + + pg.Go(func() error { + return s.EthOracleMainLoop(ctx) + }) + pg.Go(func() error { + return s.BatchRequesterLoop(ctx) + }) + pg.Go(func() error { + return s.EthSignerMainLoop(ctx) + }) + pg.Go(func() error { + return s.RelayerMainLoop(ctx) + }) + + return pg.Wait() +} + +// startRelayerMode runs orchestrator processes that only relay specific +// messages that do not require a validator's signature. This mode is run +// alongside a non-validator injective node +func (s *PeggyOrchestrator) startRelayerMode(ctx context.Context) error { + var pg loops.ParanoidGroup + + pg.Go(func() error { + return s.BatchRequesterLoop(ctx) + }) + + pg.Go(func() error { + return s.RelayerMainLoop(ctx) + }) + + return pg.Wait() +} diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index cd0a5c76..7ddc3561 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -61,7 +61,6 @@ func (s *PeggyOrchestrator) relayValsets(ctx context.Context) error { // we should determine if we need to relay one // to Ethereum for that we will find the latest confirmed valset and compare it to the ethereum chain - //latestValsets, err := s.cosmosQueryClient.LatestValsets(ctx) latestValsets, err := s.injective.LatestValsets(ctx) if err != nil { metrics.ReportFuncError(s.svcTags) @@ -99,67 +98,54 @@ func (s *PeggyOrchestrator) relayValsets(ctx context.Context) error { err = errors.Wrap(err, "couldn't find latest confirmed valset on Ethereum") return err } + log.WithFields(log.Fields{"currentEthValset": currentEthValset, "latestCosmosConfirmed": latestCosmosConfirmed}).Debugln("Found Latest valsets") - if latestCosmosConfirmed.Nonce > currentEthValset.Nonce { + if latestCosmosConfirmed.Nonce <= currentEthValset.Nonce { + return nil + } - // todo - latestEthereumValsetNonce, err := s.ethereum.GetValsetNonce(ctx) + latestEthereumValsetNonce, err := s.ethereum.GetValsetNonce(ctx) + if err != nil { + metrics.ReportFuncError(s.svcTags) + return errors.Wrap(err, "failed to get latest Valset nonce") + } - //latestEthereumValsetNonce, err := s.peggyContract.GetValsetNonce(ctx, s.peggyContract.FromAddress()) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to get latest Valset nonce") - return err - } + // Check if latestCosmosConfirmed already submitted by other validators in mean time + if latestCosmosConfirmed.Nonce <= latestEthereumValsetNonce.Uint64() { + return nil + } - // Check if latestCosmosConfirmed already submitted by other validators in mean time - if latestCosmosConfirmed.Nonce > latestEthereumValsetNonce.Uint64() { + // Check custom time delay offset + blockResult, err := s.injective.GetBlock(ctx, int64(latestCosmosConfirmed.Height)) + if err != nil { + return err + } - // Check custom time delay offset - blockResult, err := s.injective.GetBlock(ctx, int64(latestCosmosConfirmed.Height)) - if err != nil { - return err - } - valsetCreatedAt := blockResult.Block.Time - // todo: do this at init - //relayValsetOffsetDur, err := time.ParseDuration(s.relayValsetOffsetDur) - //if err != nil { - // return err - //} - customTimeDelay := valsetCreatedAt.Add(s.relayValsetOffsetDur) - if time.Now().Sub(customTimeDelay) <= 0 { - return nil - } + valsetCreatedAt := blockResult.Block.Time + customTimeDelay := valsetCreatedAt.Add(s.relayValsetOffsetDur) - log.Infof("Detected latest cosmos valset nonce %d, but latest valset on Ethereum is %d. Sending update to Ethereum\n", - latestCosmosConfirmed.Nonce, latestEthereumValsetNonce.Uint64()) - - // todo - txHash, err := s.ethereum.SendEthValsetUpdate( - ctx, - currentEthValset, - latestCosmosConfirmed, - latestCosmosSigs, - ) - - // Send Valset Update to Ethereum - //txHash, err := s.peggyContract.SendEthValsetUpdate( - // ctx, - // currentEthValset, - // latestCosmosConfirmed, - // latestCosmosSigs, - //) - if err != nil { - metrics.ReportFuncError(s.svcTags) - return err - } + if time.Now().Sub(customTimeDelay) <= 0 { + return nil + } - log.WithField("tx_hash", txHash.Hex()).Infoln("Sent Ethereum Tx (EthValsetUpdate)") - } + log.Infof("Detected latest cosmos valset nonce %d, but latest valset on Ethereum is %d. Sending update to Ethereum\n", + latestCosmosConfirmed.Nonce, latestEthereumValsetNonce.Uint64()) + + txHash, err := s.ethereum.SendEthValsetUpdate( + ctx, + currentEthValset, + latestCosmosConfirmed, + latestCosmosSigs, + ) + if err != nil { + metrics.ReportFuncError(s.svcTags) + return err } + log.WithField("tx_hash", txHash.Hex()).Infoln("Sent Ethereum Tx (EthValsetUpdate)") + return nil } @@ -169,7 +155,6 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context) error { defer doneFn() latestBatches, err := s.injective.LatestTransactionBatches(ctx) - //latestBatches, err := s.cosmosQueryClient.LatestTransactionBatches(ctx) if err != nil { metrics.ReportFuncError(s.svcTags) return err @@ -231,12 +216,10 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context) error { if err != nil { return err } + batchCreatedAt := blockResult.Block.Time - //relayBatchOffsetDur, err := time.ParseDuration() - //if err != nil { - // return err - //} customTimeDelay := batchCreatedAt.Add(s.relayBatchOffsetDur) + if time.Now().Sub(customTimeDelay) <= 0 { return nil } @@ -268,7 +251,6 @@ func (s *PeggyOrchestrator) findLatestValset(ctx context.Context) (*types.Valset defer doneFn() latestHeader, err := s.ethereum.HeaderByNumber(ctx, nil) - //latestHeader, err := s.ethProvider.HeaderByNumber(ctx, nil) if err != nil { metrics.ReportFuncError(s.svcTags) err = errors.Wrap(err, "failed to get latest header") @@ -276,15 +258,7 @@ func (s *PeggyOrchestrator) findLatestValset(ctx context.Context) (*types.Valset } currentBlock := latestHeader.Number.Uint64() - //peggyFilterer, err := wrappers.NewPeggyFilterer(s.peggyContract.Address(), s.ethProvider) - //if err != nil { - // metrics.ReportFuncError(s.svcTags) - // err = errors.Wrap(err, "failed to init Peggy events filterer") - // return nil, err - //} - latestEthereumValsetNonce, err := s.ethereum.GetValsetNonce(ctx) - //latestEthereumValsetNonce, err := s.peggyContract.GetValsetNonce(ctx, s.peggyContract.FromAddress()) if err != nil { metrics.ReportFuncError(s.svcTags) err = errors.Wrap(err, "failed to get latest Valset nonce") @@ -316,18 +290,6 @@ func (s *PeggyOrchestrator) findLatestValset(ctx context.Context) (*types.Valset err = errors.Wrap(err, "failed to filter past ValsetUpdated events from Ethereum") return nil, err } - //var valsetUpdatedEvents []*wrappers.PeggyValsetUpdatedEvent - //iter, err := peggyFilterer.FilterValsetUpdatedEvent(&bind.FilterOpts{ - // Start: endSearchBlock, - // End: ¤tBlock, - //}, nil) - //} else { - // for iter.Next() { - // valsetUpdatedEvents = append(valsetUpdatedEvents, iter.Event) - // } - // - // iter.Close() - //} // by default the lowest found valset goes first, we want the highest // diff --git a/orchestrator/relayer/batch_relaying.go b/orchestrator/relayer/batch_relaying.go deleted file mode 100644 index f6ab6dd1..00000000 --- a/orchestrator/relayer/batch_relaying.go +++ /dev/null @@ -1,109 +0,0 @@ -package relayer - -import ( - "context" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" - log "github.com/xlab/suplog" - - "github.com/InjectiveLabs/metrics" - "github.com/InjectiveLabs/sdk-go/chain/peggy/types" -) - -// RelayBatches checks the last validator set on Ethereum, if it's lower than our latest valida -// set then we should package and submit the update as an Ethereum transaction -func (s *peggyRelayer) RelayBatches(ctx context.Context) error { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - latestBatches, err := s.cosmosQueryClient.LatestTransactionBatches(ctx) - if err != nil { - metrics.ReportFuncError(s.svcTags) - return err - } - var oldestSignedBatch *types.OutgoingTxBatch - var oldestSigs []*types.MsgConfirmBatch - for _, batch := range latestBatches { - sigs, err := s.cosmosQueryClient.TransactionBatchSignatures(ctx, batch.BatchNonce, common.HexToAddress(batch.TokenContract)) - if err != nil { - metrics.ReportFuncError(s.svcTags) - return err - } else if len(sigs) == 0 { - continue - } - - oldestSignedBatch = batch - oldestSigs = sigs - } - if oldestSignedBatch == nil { - log.Debugln("could not find batch with signatures, nothing to relay") - return nil - } - - latestEthereumBatch, err := s.peggyContract.GetTxBatchNonce( - ctx, - common.HexToAddress(oldestSignedBatch.TokenContract), - s.peggyContract.FromAddress(), - ) - if err != nil { - metrics.ReportFuncError(s.svcTags) - return err - } - - currentValset, err := s.FindLatestValset(ctx) - if err != nil { - metrics.ReportFuncError(s.svcTags) - return errors.New("failed to find latest valset") - } else if currentValset == nil { - metrics.ReportFuncError(s.svcTags) - return errors.New("latest valset not found") - } - - log.WithFields(log.Fields{"oldestSignedBatchNonce": oldestSignedBatch.BatchNonce, "latestEthereumBatchNonce": latestEthereumBatch.Uint64()}).Debugln("Found Latest valsets") - - if oldestSignedBatch.BatchNonce > latestEthereumBatch.Uint64() { - - latestEthereumBatch, err := s.peggyContract.GetTxBatchNonce( - ctx, - common.HexToAddress(oldestSignedBatch.TokenContract), - s.peggyContract.FromAddress(), - ) - if err != nil { - metrics.ReportFuncError(s.svcTags) - return err - } - // Check if oldestSignedBatch already submitted by other validators in mean time - if oldestSignedBatch.BatchNonce > latestEthereumBatch.Uint64() { - - // Check custom time delay offset - blockResult, err := s.tmClient.GetBlock(ctx, int64(oldestSignedBatch.Block)) - if err != nil { - return err - } - batchCreatedAt := blockResult.Block.Time - relayBatchOffsetDur, err := time.ParseDuration(s.relayBatchOffsetDur) - if err != nil { - return err - } - customTimeDelay := batchCreatedAt.Add(relayBatchOffsetDur) - if time.Now().Sub(customTimeDelay) <= 0 { - return nil - } - - log.Infof("We have detected latest batch %d but latest on Ethereum is %d sending an update!", oldestSignedBatch.BatchNonce, latestEthereumBatch) - - // Send SendTransactionBatch to Ethereum - txHash, err := s.peggyContract.SendTransactionBatch(ctx, currentValset, oldestSignedBatch, oldestSigs) - if err != nil { - metrics.ReportFuncError(s.svcTags) - return err - } - log.WithField("tx_hash", txHash.Hex()).Infoln("Sent Ethereum Tx (TransactionBatch)") - } - } - - return nil -} diff --git a/orchestrator/relayer/find_latest_valset.go b/orchestrator/relayer/find_latest_valset.go deleted file mode 100644 index 5e88825d..00000000 --- a/orchestrator/relayer/find_latest_valset.go +++ /dev/null @@ -1,212 +0,0 @@ -package relayer - -import ( - "context" - "sort" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/pkg/errors" - log "github.com/xlab/suplog" - - "github.com/InjectiveLabs/metrics" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/util" - "github.com/InjectiveLabs/sdk-go/chain/peggy/types" - sdk "github.com/cosmos/cosmos-sdk/types" - - wrappers "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" -) - -const defaultBlocksToSearch = 2000 - -// FindLatestValset finds the latest valset on the Peggy contract by looking back through the event -// history and finding the most recent ValsetUpdatedEvent. Most of the time this will be very fast -// as the latest update will be in recent blockchain history and the search moves from the present -// backwards in time. In the case that the validator set has not been updated for a very long time -// this will take longer. -func (s *peggyRelayer) FindLatestValset(ctx context.Context) (*types.Valset, error) { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - latestHeader, err := s.ethProvider.HeaderByNumber(ctx, nil) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to get latest header") - return nil, err - } - currentBlock := latestHeader.Number.Uint64() - - peggyFilterer, err := wrappers.NewPeggyFilterer(s.peggyContract.Address(), s.ethProvider) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to init Peggy events filterer") - return nil, err - } - - latestEthereumValsetNonce, err := s.peggyContract.GetValsetNonce(ctx, s.peggyContract.FromAddress()) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to get latest Valset nonce") - return nil, err - } - - cosmosValset, err := s.cosmosQueryClient.ValsetAt(ctx, latestEthereumValsetNonce.Uint64()) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to get cosmos Valset") - return nil, err - } - - for currentBlock > 0 { - log.WithField("current_block", currentBlock). - Debugln("About to submit a Valset or Batch looking back into the history to find the last Valset Update") - - var endSearchBlock uint64 - if currentBlock <= defaultBlocksToSearch { - endSearchBlock = 0 - } else { - endSearchBlock = currentBlock - defaultBlocksToSearch - } - - var valsetUpdatedEvents []*wrappers.PeggyValsetUpdatedEvent - iter, err := peggyFilterer.FilterValsetUpdatedEvent(&bind.FilterOpts{ - Start: endSearchBlock, - End: ¤tBlock, - }, nil) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to filter past ValsetUpdated events from Ethereum") - return nil, err - } else { - for iter.Next() { - valsetUpdatedEvents = append(valsetUpdatedEvents, iter.Event) - } - - iter.Close() - } - - // by default the lowest found valset goes first, we want the highest - // - // TODO(xlab): this follows the original impl, but sort might be skipped there: - // we could access just the latest element later. - sort.Sort(sort.Reverse(PeggyValsetUpdatedEvents(valsetUpdatedEvents))) - - log.Debugln("found events", valsetUpdatedEvents) - - // we take only the first event if we find any at all. - if len(valsetUpdatedEvents) > 0 { - event := valsetUpdatedEvents[0] - valset := &types.Valset{ - Nonce: event.NewValsetNonce.Uint64(), - Members: make([]*types.BridgeValidator, 0, len(event.Powers)), - RewardAmount: sdk.NewIntFromBigInt(event.RewardAmount), - RewardToken: event.RewardToken.Hex(), - } - - for idx, p := range event.Powers { - valset.Members = append(valset.Members, &types.BridgeValidator{ - Power: p.Uint64(), - EthereumAddress: event.Validators[idx].Hex(), - }) - } - - s.checkIfValsetsDiffer(cosmosValset, valset) - return valset, nil - } - - currentBlock = endSearchBlock - } - - return nil, ErrNotFound -} - -var ErrNotFound = errors.New("not found") - -type PeggyValsetUpdatedEvents []*wrappers.PeggyValsetUpdatedEvent - -func (a PeggyValsetUpdatedEvents) Len() int { return len(a) } -func (a PeggyValsetUpdatedEvents) Less(i, j int) bool { - return a[i].NewValsetNonce.Cmp(a[j].NewValsetNonce) < 0 -} -func (a PeggyValsetUpdatedEvents) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -// This function exists to provide a warning if Cosmos and Ethereum have different validator sets -// for a given nonce. In the mundane version of this warning the validator sets disagree on sorting order -// which can happen if some relayer uses an unstable sort, or in a case of a mild griefing attack. -// The Peggy contract validates signatures in order of highest to lowest power. That way it can exit -// the loop early once a vote has enough power, if a relayer where to submit things in the reverse order -// they could grief users of the contract into paying more in gas. -// The other (and far worse) way a disagreement here could occur is if validators are colluding to steal -// funds from the Peggy contract and have submitted a hijacking update. If slashing for off Cosmos chain -// Ethereum signatures is implemented you would put that handler here. -func (s *peggyRelayer) checkIfValsetsDiffer(cosmosValset, ethereumValset *types.Valset) { - if cosmosValset == nil && ethereumValset.Nonce == 0 { - // bootstrapping case - return - } else if cosmosValset == nil { - log.WithField( - "eth_valset_nonce", - ethereumValset.Nonce, - ).Errorln("Cosmos does not have a valset for nonce from Ethereum chain. Possible bridge hijacking!") - return - } - - if cosmosValset.Nonce != ethereumValset.Nonce { - log.WithFields(log.Fields{ - "cosmos_valset_nonce": cosmosValset.Nonce, - "eth_valset_nonce": ethereumValset.Nonce, - }).Errorln("Cosmos does have a wrong valset nonce, differs from Ethereum chain. Possible bridge hijacking!") - return - } - - if len(cosmosValset.Members) != len(ethereumValset.Members) { - log.WithFields(log.Fields{ - "cosmos_valset": len(cosmosValset.Members), - "eth_valset": len(ethereumValset.Members), - }).Errorln("Cosmos and Ethereum Valsets have different length. Possible bridge hijacking!") - return - } - - BridgeValidators(cosmosValset.Members).Sort() - BridgeValidators(ethereumValset.Members).Sort() - - for idx, member := range cosmosValset.Members { - if ethereumValset.Members[idx].EthereumAddress != member.EthereumAddress { - log.Errorln("Valsets are different, a sorting error?") - } - if ethereumValset.Members[idx].Power != member.Power { - log.Errorln("Valsets are different, a sorting error?") - } - } -} - -type BridgeValidators []*types.BridgeValidator - -// Sort sorts the validators by power -func (b BridgeValidators) Sort() { - sort.Slice(b, func(i, j int) bool { - if b[i].Power == b[j].Power { - // Secondary sort on eth address in case powers are equal - return util.EthAddrLessThan(b[i].EthereumAddress, b[j].EthereumAddress) - } - return b[i].Power > b[j].Power - }) -} - -// HasDuplicates returns true if there are duplicates in the set -func (b BridgeValidators) HasDuplicates() bool { - m := make(map[string]struct{}, len(b)) - for i := range b { - m[b[i].EthereumAddress] = struct{}{} - } - return len(m) != len(b) -} - -// GetPowers returns only the power values for all members -func (b BridgeValidators) GetPowers() []uint64 { - r := make([]uint64, len(b)) - for i := range b { - r[i] = b[i].Power - } - return r -} diff --git a/orchestrator/relayer/main_loop.go b/orchestrator/relayer/main_loop.go deleted file mode 100644 index 3dcdb46e..00000000 --- a/orchestrator/relayer/main_loop.go +++ /dev/null @@ -1,50 +0,0 @@ -package relayer - -import ( - "context" - "time" - - retry "github.com/avast/retry-go" - log "github.com/xlab/suplog" - - "github.com/InjectiveLabs/peggo/orchestrator/loops" -) - -const defaultLoopDur = 5 * time.Minute - -func (s *peggyRelayer) Start(ctx context.Context) error { - logger := log.WithField("loop", "RelayerMainLoop") - - return loops.RunLoop(ctx, defaultLoopDur, func() error { - var pg loops.ParanoidGroup - if s.valsetRelayEnabled { - logger.Info("Valset Relay Enabled. Starting to relay valsets to Ethereum") - pg.Go(func() error { - return retry.Do(func() error { - return s.RelayValsets(ctx) - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to relay Valsets, will retry (%d)", n) - })) - }) - } - - if s.batchRelayEnabled { - logger.Info("Batch Relay Enabled. Starting to relay batches to Ethereum") - pg.Go(func() error { - return retry.Do(func() error { - return s.RelayBatches(ctx) - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to relay TxBatches, will retry (%d)", n) - })) - }) - } - - if pg.Initialized() { - if err := pg.Wait(); err != nil { - logger.WithError(err).Errorln("got error, loop exits") - return err - } - } - return nil - }) -} diff --git a/orchestrator/relayer/relayer.go b/orchestrator/relayer/relayer.go deleted file mode 100644 index fb4fd042..00000000 --- a/orchestrator/relayer/relayer.go +++ /dev/null @@ -1,57 +0,0 @@ -package relayer - -import ( - "context" - - "github.com/InjectiveLabs/metrics" - "github.com/InjectiveLabs/peggo/orchestrator/cosmos" - "github.com/InjectiveLabs/peggo/orchestrator/cosmos/tmclient" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/peggy" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/provider" - "github.com/InjectiveLabs/sdk-go/chain/peggy/types" -) - -type PeggyRelayer interface { - Start(ctx context.Context) error - - FindLatestValset(ctx context.Context) (*types.Valset, error) - RelayBatches(ctx context.Context) error - RelayValsets(ctx context.Context) error -} - -type peggyRelayer struct { - svcTags metrics.Tags - - tmClient tmclient.TendermintClient - cosmosQueryClient cosmos.PeggyQueryClient - peggyContract peggy.PeggyContract - ethProvider provider.EVMProvider - valsetRelayEnabled bool - relayValsetOffsetDur string - batchRelayEnabled bool - relayBatchOffsetDur string -} - -func NewPeggyRelayer( - cosmosQueryClient cosmos.PeggyQueryClient, - tmClient tmclient.TendermintClient, - peggyContract peggy.PeggyContract, - valsetRelayEnabled bool, - relayValsetOffsetDur string, - batchRelayEnabled bool, - relayBatchOffsetDur string, -) PeggyRelayer { - return &peggyRelayer{ - tmClient: tmClient, - cosmosQueryClient: cosmosQueryClient, - peggyContract: peggyContract, - ethProvider: peggyContract.Provider(), - valsetRelayEnabled: valsetRelayEnabled, - relayValsetOffsetDur: relayValsetOffsetDur, - batchRelayEnabled: batchRelayEnabled, - relayBatchOffsetDur: relayBatchOffsetDur, - svcTags: metrics.Tags{ - "svc": "peggy_relayer", - }, - } -} diff --git a/orchestrator/relayer/valset_relaying.go b/orchestrator/relayer/valset_relaying.go deleted file mode 100644 index 38ede30f..00000000 --- a/orchestrator/relayer/valset_relaying.go +++ /dev/null @@ -1,108 +0,0 @@ -package relayer - -import ( - "context" - "time" - - "github.com/pkg/errors" - log "github.com/xlab/suplog" - - "github.com/InjectiveLabs/metrics" - "github.com/InjectiveLabs/sdk-go/chain/peggy/types" -) - -// RelayValsets checks the last validator set on Ethereum, if it's lower than our latest validator -// set then we should package and submit the update as an Ethereum transaction -func (s *peggyRelayer) RelayValsets(ctx context.Context) error { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - // we should determine if we need to relay one - // to Ethereum for that we will find the latest confirmed valset and compare it to the ethereum chain - latestValsets, err := s.cosmosQueryClient.LatestValsets(ctx) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to fetch latest valsets from cosmos") - return err - } - - var latestCosmosSigs []*types.MsgValsetConfirm - var latestCosmosConfirmed *types.Valset - for _, set := range latestValsets { - sigs, err := s.cosmosQueryClient.AllValsetConfirms(ctx, set.Nonce) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrapf(err, "failed to get valset confirms at nonce %d", set.Nonce) - return err - } else if len(sigs) == 0 { - continue - } - - latestCosmosSigs = sigs - latestCosmosConfirmed = set - break - } - - if latestCosmosConfirmed == nil { - log.Debugln("no confirmed valsets found, nothing to relay") - return nil - } - - currentEthValset, err := s.FindLatestValset(ctx) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "couldn't find latest confirmed valset on Ethereum") - return err - } - log.WithFields(log.Fields{"currentEthValset": currentEthValset, "latestCosmosConfirmed": latestCosmosConfirmed}).Debugln("Found Latest valsets") - - if latestCosmosConfirmed.Nonce > currentEthValset.Nonce { - - latestEthereumValsetNonce, err := s.peggyContract.GetValsetNonce(ctx, s.peggyContract.FromAddress()) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to get latest Valset nonce") - return err - } - - // Check if latestCosmosConfirmed already submitted by other validators in mean time - if latestCosmosConfirmed.Nonce > latestEthereumValsetNonce.Uint64() { - - // Check custom time delay offset - blockResult, err := s.tmClient.GetBlock(ctx, int64(latestCosmosConfirmed.Height)) - if err != nil { - return err - } - valsetCreatedAt := blockResult.Block.Time - relayValsetOffsetDur, err := time.ParseDuration(s.relayValsetOffsetDur) - if err != nil { - return err - } - customTimeDelay := valsetCreatedAt.Add(relayValsetOffsetDur) - if time.Now().Sub(customTimeDelay) <= 0 { - return nil - } - - log.Infof("Detected latest cosmos valset nonce %d, but latest valset on Ethereum is %d. Sending update to Ethereum\n", - latestCosmosConfirmed.Nonce, latestEthereumValsetNonce.Uint64()) - - // Send Valset Update to Ethereum - txHash, err := s.peggyContract.SendEthValsetUpdate( - ctx, - currentEthValset, - latestCosmosConfirmed, - latestCosmosSigs, - ) - if err != nil { - metrics.ReportFuncError(s.svcTags) - return err - } - - log.WithField("tx_hash", txHash.Hex()).Infoln("Sent Ethereum Tx (EthValsetUpdate)") - } - - } - - return nil -} diff --git a/orchestrator/signer.go b/orchestrator/signer.go index 8744df39..202186f1 100644 --- a/orchestrator/signer.go +++ b/orchestrator/signer.go @@ -40,7 +40,7 @@ func (s *PeggyOrchestrator) getPeggyID(ctx context.Context, logger log.Logger) ( if err := retry.Do(retryFn, retry.Context(ctx), - retry.Attempts(s.maxRetries), + retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { logger.WithError(err).Warningf("failed to get PeggyID from Ethereum contract, will retry (%d)", n) }), @@ -85,7 +85,7 @@ func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Lo if err := retry.Do(retryFn, retry.Context(ctx), - retry.Attempts(s.maxRetries), + retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { logger.WithError(err).Warningf("failed to get unsigned Valset for signing, will retry (%d)", n) }), @@ -100,7 +100,7 @@ func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Lo return s.injective.SendValsetConfirm(ctx, peggyID, vs, s.ethereum.FromAddress()) }, retry.Context(ctx), - retry.Attempts(s.maxRetries), + retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { logger.WithError(err).Warningf("failed to sign and send Valset confirmation to Cosmos, will retry (%d)", n) }), @@ -128,7 +128,7 @@ func (s *PeggyOrchestrator) signTransactionBatches(ctx context.Context, logger l oldestUnsignedTransactionBatch = txBatch return nil }, retry.Context(ctx), - retry.Attempts(s.maxRetries), + retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { logger.WithError(err).Warningf("failed to get unsigned TransactionBatch for signing, will retry (%d)", n) })); err != nil { @@ -144,7 +144,7 @@ func (s *PeggyOrchestrator) signTransactionBatches(ctx context.Context, logger l if err := retry.Do(func() error { return s.injective.SendBatchConfirm(ctx, peggyID, oldestUnsignedTransactionBatch, s.ethereum.FromAddress()) }, retry.Context(ctx), - retry.Attempts(s.maxRetries), + retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { logger.WithError(err).Warningf("failed to sign and send TransactionBatch confirmation to Cosmos, will retry (%d)", n) })); err != nil { diff --git a/orchestrator/signer_test.go b/orchestrator/signer_test.go index 5745cfd8..1a0f856e 100644 --- a/orchestrator/signer_test.go +++ b/orchestrator/signer_test.go @@ -18,7 +18,7 @@ func TestEthSignerLoop(t *testing.T) { t.Parallel() orch := &PeggyOrchestrator{ - maxRetries: 1, // todo, hardcode do 10 + maxAttempts: 1, // todo, hardcode do 10 ethereum: mockEthereum{ getPeggyIDFn: func(context.Context) (common.Hash, error) { return [32]byte{}, errors.New("fail") @@ -33,7 +33,7 @@ func TestEthSignerLoop(t *testing.T) { t.Parallel() orch := &PeggyOrchestrator{ - maxRetries: 1, + maxAttempts: 1, injective: &mockInjective{ oldestUnsignedValsetsFn: func(context.Context) ([]*types.Valset, error) { return nil, errors.New("fail") @@ -57,7 +57,7 @@ func TestEthSignerLoop(t *testing.T) { t.Parallel() orch := &PeggyOrchestrator{ - maxRetries: 1, + maxAttempts: 1, injective: &mockInjective{ oldestUnsignedValsetsFn: func(context.Context) ([]*types.Valset, error) { return []*types.Valset{ @@ -94,7 +94,7 @@ func TestEthSignerLoop(t *testing.T) { t.Parallel() orch := &PeggyOrchestrator{ - maxRetries: 1, + maxAttempts: 1, injective: &mockInjective{ oldestUnsignedValsetsFn: func(_ context.Context) ([]*types.Valset, error) { return nil, nil }, sendValsetConfirmFn: func(context.Context, common.Hash, *types.Valset, common.Address) error { return nil }, @@ -111,7 +111,7 @@ func TestEthSignerLoop(t *testing.T) { t.Parallel() orch := &PeggyOrchestrator{ - maxRetries: 1, + maxAttempts: 1, injective: &mockInjective{ oldestUnsignedValsetsFn: func(_ context.Context) ([]*types.Valset, error) { return nil, nil }, sendValsetConfirmFn: func(context.Context, common.Hash, *types.Valset, common.Address) error { return nil }, @@ -137,7 +137,7 @@ func TestEthSignerLoop(t *testing.T) { t.Parallel() orch := &PeggyOrchestrator{ - maxRetries: 1, + maxAttempts: 1, injective: &mockInjective{ oldestUnsignedValsetsFn: func(_ context.Context) ([]*types.Valset, error) { return []*types.Valset{}, nil // non-empty will do From 34e70417e18eea3e44dedd050043d3fa7c9adfdb Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Thu, 15 Jun 2023 16:12:06 +0200 Subject: [PATCH 28/72] improve logging during init --- cmd/peggo/orchestrator.go | 20 +++++++++++++------- cmd/peggo/util.go | 2 +- orchestrator/batch_request_test.go | 26 ++++++++++++++++++++++++++ orchestrator/cosmos/network.go | 8 ++++---- orchestrator/ethereum/network.go | 17 ++++++----------- orchestrator/orchestrator.go | 16 +++++++++++----- 6 files changed, 61 insertions(+), 28 deletions(-) diff --git a/cmd/peggo/orchestrator.go b/cmd/peggo/orchestrator.go index 0e8a44da..5f4e8bf5 100644 --- a/cmd/peggo/orchestrator.go +++ b/cmd/peggo/orchestrator.go @@ -34,7 +34,7 @@ func orchestratorCmd(cmd *cli.Cmd) { defer closer.Close() if *cfg.cosmosUseLedger || *cfg.ethUseLedger { - log.Fatalln("cannot really use Ledger for orchestrator, since signatures msut be realtime") + log.Fatalln("cannot use Ledger for peggo, since signatures must be realtime") } valAddress, cosmosKeyring, err := initCosmosKeyring( @@ -47,7 +47,7 @@ func orchestratorCmd(cmd *cli.Cmd) { cfg.cosmosUseLedger, ) if err != nil { - log.WithError(err).Fatalln("failed to init Cosmos keyring") + log.WithError(err).Fatalln("failed to initialize Injective keyring") } ethKeyFromAddress, signerFn, personalSignFn, err := initEthereumAccountsManager( @@ -59,13 +59,13 @@ func orchestratorCmd(cmd *cli.Cmd) { cfg.ethUseLedger, ) if err != nil { - log.WithError(err).Fatalln("failed to init Ethereum account") + log.WithError(err).Fatalln("failed to initialize Ethereum account") } - log.Infoln("Using Cosmos ValAddress", valAddress.String()) + log.Infoln("Using Injective validator address", valAddress.String()) log.Infoln("Using Ethereum address", ethKeyFromAddress.String()) - // init injective network + // Connect to Injective network injNetwork, err := cosmos.NewNetwork( *cfg.cosmosChainID, valAddress.String(), @@ -78,10 +78,12 @@ func orchestratorCmd(cmd *cli.Cmd) { ) orShutdown(err) + log.Infoln("Connected to Injective network") + // See if the provided ETH address belongs to a validator and determine in which mode peggo should run isValidator, err := isValidatorAddress(injNetwork.PeggyQueryClient, ethKeyFromAddress) if err != nil { - log.WithError(err).Fatalln("failed to query the current validator set from injective") + log.WithError(err).Fatalln("failed to query current validator set on Injective") return } @@ -100,7 +102,7 @@ func orchestratorCmd(cmd *cli.Cmd) { erc20ContractMapping := make(map[ethcmn.Address]string) erc20ContractMapping[injAddress] = ctypes.InjectiveCoin - // init ethereum network + // Connect to ethereum network ethNetwork, err := ethereum.NewNetwork( *cfg.ethNodeRPC, peggyAddress, @@ -113,11 +115,14 @@ func orchestratorCmd(cmd *cli.Cmd) { ) orShutdown(err) + log.Infoln("Connected to Ethereum network") + coingeckoFeed := coingecko.NewCoingeckoPriceFeed(100, &coingecko.Config{BaseURL: *cfg.coingeckoApi}) // make the flag obsolete and hardcode *cfg.minBatchFeeUSD = 49.0 + // Create peggo and run it peggo, err := orchestrator.NewPeggyOrchestrator( injNetwork, ethNetwork, @@ -130,6 +135,7 @@ func orchestratorCmd(cmd *cli.Cmd) { *cfg.relayValsetOffsetDur, *cfg.relayBatchOffsetDur, ) + orShutdown(err) go func() { if err := peggo.Run(ctx, isValidator); err != nil { diff --git a/cmd/peggo/util.go b/cmd/peggo/util.go index 62d44ac0..d6df7c36 100644 --- a/cmd/peggo/util.go +++ b/cmd/peggo/util.go @@ -132,6 +132,6 @@ func hexToBytes(str string) ([]byte, error) { // orShutdown fatals the app if there was an error. func orShutdown(err error) { if err != nil && err != grpc.ErrServerStopped { - log.WithError(err).Fatalln("unable to start peggo orchestrator") + log.WithError(err).Fatalln("unable to start peggo") } } diff --git a/orchestrator/batch_request_test.go b/orchestrator/batch_request_test.go index 7286dd41..bb56bf97 100644 --- a/orchestrator/batch_request_test.go +++ b/orchestrator/batch_request_test.go @@ -13,6 +13,32 @@ import ( cosmtypes "github.com/cosmos/cosmos-sdk/types" ) +func TestLogger(t *testing.T) { + err := errors.New("dusan error") + err2 := errors.New("another dusan error") + + logger := suplog.WithError(err).WithError(errors.New("wqerwerw d")) + + suplog.Infoln("random info line") + suplog.WithFields(suplog.Fields{"field1": 42}).Infoln("info line with field") + + logger.Errorln("descriptive error line") + logger.WithError(err2).Errorln("descriptive error line 2") + + logger = suplog.WithField("dusan", "dusan value") + logger.Errorln("this is an error line") + logger.Infoln("this is an info line") + logger.Info("this is an info log") + num := 10 + logger.Debugln("this", "is", "a", "debug", "log", "with num=", num) + num2 := 11 + logger.WithFields(suplog.Fields{"field1": num, "field2": num2}).Warningln("warning with fields") + + //suplog.WithError(err).Fatalln("failed to initialize Injective keyring") + + suplog.WithFields(suplog.Fields{"chain_id": "888"}).Infoln("Connected to Injective chain") +} + func TestRequestBatches(t *testing.T) { t.Parallel() diff --git a/orchestrator/cosmos/network.go b/orchestrator/cosmos/network.go index 9597cab3..71784961 100644 --- a/orchestrator/cosmos/network.go +++ b/orchestrator/cosmos/network.go @@ -11,6 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/ethereum/go-ethereum/accounts/abi/bind" ethcmn "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" rpchttp "github.com/tendermint/tendermint/rpc/client/http" tmctypes "github.com/tendermint/tendermint/rpc/core/types" log "github.com/xlab/suplog" @@ -39,24 +40,23 @@ func NewNetwork( ) (*Network, error) { clientCtx, err := chainclient.NewClientContext(chainID, validatorAddress, keyring) if err != nil { - log.WithError(err).Fatalln("failed to initialize cosmos client context") + return nil, errors.Wrapf(err, "failed to create client context for Injective chain") } clientCtx = clientCtx.WithNodeURI(tendermintRPC) tmRPC, err := rpchttp.New(tendermintRPC, "/websocket") if err != nil { - log.WithError(err) + return nil, errors.Wrapf(err, "failed to connect to Tendermint RPC %s", tendermintRPC) } clientCtx = clientCtx.WithClient(tmRPC) daemonClient, err := chainclient.NewChainClient(clientCtx, injectiveGRPC, common.OptionGasPrices(injectiveGasPrices)) if err != nil { - log.WithError(err).WithFields(log.Fields{"endpoint": injectiveGRPC}).Fatalln("failed to connect to daemon, is injectived running?") + return nil, errors.Wrapf(err, "failed to connect to Injective GRPC %s", injectiveGRPC) } - log.Infoln("Waiting for injectived GRPC") time.Sleep(1 * time.Second) daemonWaitCtx, cancelWait := context.WithTimeout(context.Background(), time.Minute) diff --git a/orchestrator/ethereum/network.go b/orchestrator/ethereum/network.go index 6820d00d..d0f7492d 100644 --- a/orchestrator/ethereum/network.go +++ b/orchestrator/ethereum/network.go @@ -6,18 +6,16 @@ import ( "strings" "time" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - ethcmn "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/rpc" - "github.com/pkg/errors" - log "github.com/xlab/suplog" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/committer" "github.com/InjectiveLabs/peggo/orchestrator/ethereum/peggy" "github.com/InjectiveLabs/peggo/orchestrator/ethereum/provider" wrappers "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + ethcmn "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" + "github.com/pkg/errors" ) type Network struct { @@ -36,12 +34,9 @@ func NewNetwork( ) (*Network, error) { evmRPC, err := rpc.Dial(ethNodeRPC) if err != nil { - log.WithField("endpoint", ethNodeRPC).WithError(err).Fatalln("Failed to connect to Ethereum RPC") - return nil, err + return nil, errors.Wrapf(err, "failed to connect to ethereum RPC: %s", ethNodeRPC) } - log.Infoln("Connected to Ethereum RPC at", ethNodeRPC) - ethCommitter, err := committer.NewEthCommitter( fromAddr, gasPriceAdjustment, diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index e5c1ae0d..bc8f8910 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -2,6 +2,7 @@ package orchestrator import ( "context" + "github.com/pkg/errors" "math/big" "time" @@ -57,12 +58,14 @@ type EthereumNetwork interface { HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) GetPeggyID(ctx context.Context) (eth.Hash, error) + // events GetSendToCosmosEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) GetSendToInjectiveEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) GetPeggyERC20DeployedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyERC20DeployedEvent, error) GetValsetUpdatedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyValsetUpdatedEvent, error) GetTransactionBatchExecutedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) + // valsets GetValsetNonce(ctx context.Context) (*big.Int, error) SendEthValsetUpdate( ctx context.Context, @@ -71,6 +74,7 @@ type EthereumNetwork interface { confirms []*peggytypes.MsgValsetConfirm, ) (*eth.Hash, error) + // batches GetTxBatchNonce( ctx context.Context, erc20ContractAddress eth.Address, @@ -132,7 +136,7 @@ func NewPeggyOrchestrator( if valsetRelayingEnabled { dur, err := time.ParseDuration(valsetRelayingOffset) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "valset relaying enabled but offset duration is not properly set") } orch.relayValsetOffsetDur = dur @@ -141,7 +145,7 @@ func NewPeggyOrchestrator( if batchRelayingEnabled { dur, err := time.ParseDuration(batchRelayingOffset) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "batch relaying enabled but offset duration is not properly set") } orch.relayBatchOffsetDur = dur @@ -150,21 +154,21 @@ func NewPeggyOrchestrator( return orch, nil } -// Run combines the all major roles required to make +// Run starts all major loops required to make // up the Orchestrator, all of these are async loops. func (s *PeggyOrchestrator) Run(ctx context.Context, validatorMode bool) error { if !validatorMode { - log.Infoln("Starting peggo in relayer (non-validator) mode") return s.startRelayerMode(ctx) } - log.Infoln("Starting peggo in validator mode") return s.startValidatorMode(ctx) } // startValidatorMode runs all orchestrator processes. This is called // when peggo is run alongside a validator injective node. func (s *PeggyOrchestrator) startValidatorMode(ctx context.Context) error { + log.Infoln("Starting peggo in validator mode") + var pg loops.ParanoidGroup pg.Go(func() error { @@ -187,6 +191,8 @@ func (s *PeggyOrchestrator) startValidatorMode(ctx context.Context) error { // messages that do not require a validator's signature. This mode is run // alongside a non-validator injective node func (s *PeggyOrchestrator) startRelayerMode(ctx context.Context) error { + log.Infoln("Starting peggo in relayer mode") + var pg loops.ParanoidGroup pg.Go(func() error { From d39b81fcce6bcdc68dd92a5af718319a20d21dfb Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Mon, 19 Jun 2023 10:55:35 +0200 Subject: [PATCH 29/72] logging --- cmd/peggo/orchestrator.go | 17 ++++++----------- orchestrator/batch_request_test.go | 6 ++++++ orchestrator/cosmos/network.go | 6 ++++++ orchestrator/ethereum/network.go | 9 +++++++++ orchestrator/orchestrator.go | 4 ++-- 5 files changed, 29 insertions(+), 13 deletions(-) diff --git a/cmd/peggo/orchestrator.go b/cmd/peggo/orchestrator.go index 5f4e8bf5..ba06070e 100644 --- a/cmd/peggo/orchestrator.go +++ b/cmd/peggo/orchestrator.go @@ -62,8 +62,8 @@ func orchestratorCmd(cmd *cli.Cmd) { log.WithError(err).Fatalln("failed to initialize Ethereum account") } - log.Infoln("Using Injective validator address", valAddress.String()) - log.Infoln("Using Ethereum address", ethKeyFromAddress.String()) + log.Infoln("using Injective validator address", valAddress.String()) + log.Infoln("using Ethereum address", ethKeyFromAddress.String()) // Connect to Injective network injNetwork, err := cosmos.NewNetwork( @@ -78,13 +78,10 @@ func orchestratorCmd(cmd *cli.Cmd) { ) orShutdown(err) - log.Infoln("Connected to Injective network") - // See if the provided ETH address belongs to a validator and determine in which mode peggo should run isValidator, err := isValidatorAddress(injNetwork.PeggyQueryClient, ethKeyFromAddress) if err != nil { log.WithError(err).Fatalln("failed to query current validator set on Injective") - return } ctx, cancelFn := context.WithCancel(context.Background()) @@ -96,16 +93,16 @@ func orchestratorCmd(cmd *cli.Cmd) { log.WithError(err).Fatalln("failed to query peggy params, is injectived running?") } - peggyAddress := ethcmn.HexToAddress(peggyParams.BridgeEthereumAddress) - injAddress := ethcmn.HexToAddress(peggyParams.CosmosCoinErc20Contract) + peggyContractAddr := ethcmn.HexToAddress(peggyParams.BridgeEthereumAddress) + injTokenAddr := ethcmn.HexToAddress(peggyParams.CosmosCoinErc20Contract) erc20ContractMapping := make(map[ethcmn.Address]string) - erc20ContractMapping[injAddress] = ctypes.InjectiveCoin + erc20ContractMapping[injTokenAddr] = ctypes.InjectiveCoin // Connect to ethereum network ethNetwork, err := ethereum.NewNetwork( *cfg.ethNodeRPC, - peggyAddress, + peggyContractAddr, ethKeyFromAddress, signerFn, *cfg.ethGasPriceAdjustment, @@ -115,8 +112,6 @@ func orchestratorCmd(cmd *cli.Cmd) { ) orShutdown(err) - log.Infoln("Connected to Ethereum network") - coingeckoFeed := coingecko.NewCoingeckoPriceFeed(100, &coingecko.Config{BaseURL: *cfg.coingeckoApi}) // make the flag obsolete and hardcode diff --git a/orchestrator/batch_request_test.go b/orchestrator/batch_request_test.go index bb56bf97..796f7013 100644 --- a/orchestrator/batch_request_test.go +++ b/orchestrator/batch_request_test.go @@ -37,6 +37,12 @@ func TestLogger(t *testing.T) { //suplog.WithError(err).Fatalln("failed to initialize Injective keyring") suplog.WithFields(suplog.Fields{"chain_id": "888"}).Infoln("Connected to Injective chain") + + suplog.WithFields(suplog.Fields{ + "chain_id": "*cfg.cosmosChainID", + "injective_grpc": "*cfg.cosmosGRPC", + "tendermint_rpc": "cfg.tendermintRPC", + }).Infoln("connected to Injective network") } func TestRequestBatches(t *testing.T) { diff --git a/orchestrator/cosmos/network.go b/orchestrator/cosmos/network.go index 71784961..6263c1c4 100644 --- a/orchestrator/cosmos/network.go +++ b/orchestrator/cosmos/network.go @@ -72,6 +72,12 @@ func NewNetwork( PeggyBroadcastClient: NewPeggyBroadcastClient(peggyQuerier, daemonClient, signerFn, personalSignerFn), } + log.WithFields(log.Fields{ + "chain_id": chainID, + "grpc": injectiveGRPC, + "tendermint": tendermintRPC, + }).Infoln("connected to Injective network") + return n, nil } diff --git a/orchestrator/ethereum/network.go b/orchestrator/ethereum/network.go index d0f7492d..561c2932 100644 --- a/orchestrator/ethereum/network.go +++ b/orchestrator/ethereum/network.go @@ -2,6 +2,7 @@ package ethereum import ( "context" + log "github.com/xlab/suplog" "math/big" "strings" "time" @@ -58,8 +59,16 @@ func NewNetwork( return nil, err } + log.WithFields(log.Fields{ + "rpc": ethNodeRPC, + "peggy_contract": peggyContractAddr, + }).Infoln("connected to Ethereum network") + // If Alchemy Websocket URL is set, then Subscribe to Pending Transaction of Peggy Contract. if ethNodeAlchemyWS != "" { + log.WithFields(log.Fields{ + "url": ethNodeAlchemyWS, + }).Infoln("subscribing to Alchemy websocket") go peggyContract.SubscribeToPendingTxs(ethNodeAlchemyWS) } diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index bc8f8910..b691e710 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -167,7 +167,7 @@ func (s *PeggyOrchestrator) Run(ctx context.Context, validatorMode bool) error { // startValidatorMode runs all orchestrator processes. This is called // when peggo is run alongside a validator injective node. func (s *PeggyOrchestrator) startValidatorMode(ctx context.Context) error { - log.Infoln("Starting peggo in validator mode") + log.Infoln("running in validator mode") var pg loops.ParanoidGroup @@ -191,7 +191,7 @@ func (s *PeggyOrchestrator) startValidatorMode(ctx context.Context) error { // messages that do not require a validator's signature. This mode is run // alongside a non-validator injective node func (s *PeggyOrchestrator) startRelayerMode(ctx context.Context) error { - log.Infoln("Starting peggo in relayer mode") + log.Infoln("running in relayer mode") var pg loops.ParanoidGroup From 315d19e01b2be8770f18315b5a7eef35367cd8dd Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Mon, 19 Jun 2023 11:34:17 +0200 Subject: [PATCH 30/72] improve oracle logging --- orchestrator/batch_request_test.go | 4 ++ orchestrator/oracle.go | 111 ++++++++++++++++------------- 2 files changed, 67 insertions(+), 48 deletions(-) diff --git a/orchestrator/batch_request_test.go b/orchestrator/batch_request_test.go index 796f7013..df38f2e1 100644 --- a/orchestrator/batch_request_test.go +++ b/orchestrator/batch_request_test.go @@ -43,6 +43,10 @@ func TestLogger(t *testing.T) { "injective_grpc": "*cfg.cosmosGRPC", "tendermint_rpc": "cfg.tendermintRPC", }).Infoln("connected to Injective network") + + logger = suplog.WithField("loop", "EthOracleMainLoop") + + logger.WithField("lastConfirmedEthHeight", 1212).Infoln("Start scanning for events") } func TestRequestBatches(t *testing.T) { diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index 0a405859..f1ec4343 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -37,7 +37,7 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { if height == 0 { peggyParams, err := s.injective.PeggyParams(ctx) if err != nil { - log.WithError(err).Fatalln("failed to query peggy params, is injectived running?") + logger.WithError(err).Fatalln("failed to query peggy params, is injectived running?") } height = peggyParams.BridgeContractStartHeight } @@ -49,15 +49,16 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { retry.Context(ctx), retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get last checked block, will retry (%d)", n) + logger.WithError(err).Warningf("failed to get last confirmed eth height, will retry (%d)", n) }), ); err != nil { logger.WithError(err).Errorln("got error, loop exits") return err } - logger.WithField("lastConfirmedEthHeight", lastConfirmedEthHeight).Infoln("Start scanning for events") return loops.RunLoop(ctx, defaultLoopDur, func() error { + logger.WithField("last_confirmed_eth_height", lastConfirmedEthHeight).Infoln("scanning for events") + // Relays events from Ethereum -> Cosmos var currentHeight uint64 if err := retry.Do(func() (err error) { @@ -76,13 +77,14 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { lastConfirmedEthHeight = currentHeight - /* + /** Auto re-sync to catch up the nonce. Reasons why event nonce fall behind. 1. It takes some time for events to be indexed on Ethereum. So if peggo queried events immediately as block produced, there is a chance the event is missed. we need to re-scan this block to ensure events are not missed due to indexing delay. 2. if validator was in UnBonding state, the claims broadcasted in last iteration are failed. 3. if infura call failed while filtering events, the peggo missed to broadcast claim events occured in last iteration. **/ + if time.Since(lastResync) >= 48*time.Hour { if err := retry.Do(func() (err error) { lastConfirmedEthHeight, err = s.getLastConfirmedEthHeight(ctx) @@ -91,7 +93,7 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { retry.Context(ctx), retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get last checked block, will retry (%d)", n) + logger.WithError(err).Warningf("failed to get last confirmed eth height, will retry (%d)", n) }), ); err != nil { logger.WithError(err).Errorln("got error, loop exits") @@ -99,7 +101,7 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { } lastResync = time.Now() - logger.WithFields(log.Fields{"lastResync": lastResync, "lastConfirmedEthHeight": lastConfirmedEthHeight}).Infoln("Auto resync") + logger.WithFields(log.Fields{"last_resync": lastResync, "last_confirmed_eth_height": lastConfirmedEthHeight}).Infoln("auto resync") } return nil @@ -134,8 +136,7 @@ func (s *PeggyOrchestrator) relayEthEvents( latestHeader, err := s.ethereum.HeaderByNumber(ctx, nil) if err != nil { metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to get latest header") - return 0, err + return 0, errors.Wrap(err, "failed to get latest ethereum header") } // add delay to ensure minimum confirmations are received and block is finalised @@ -150,59 +151,34 @@ func (s *PeggyOrchestrator) relayEthEvents( legacyDeposits, err := s.ethereum.GetSendToCosmosEvents(startingBlock, currentBlock) if err != nil { - log.WithFields(log.Fields{"start": startingBlock, "end": currentBlock}).Errorln("failed to scan past SendToCosmos events from Ethereum") - return 0, err + metrics.ReportFuncError(s.svcTags) + return 0, errors.Wrap(err, "failed to get SendToCosmos events") } - log.WithFields(log.Fields{ - "start": startingBlock, - "end": currentBlock, - "OldDeposits": legacyDeposits, - }).Debugln("Scanned SendToCosmos events from Ethereum") - deposits, err := s.ethereum.GetSendToInjectiveEvents(startingBlock, currentBlock) if err != nil { - return 0, err + metrics.ReportFuncError(s.svcTags) + return 0, errors.Wrap(err, "failed to get SendToInjective events") } - log.WithFields(log.Fields{ - "start": startingBlock, - "end": currentBlock, - "Deposits": deposits, - }).Debugln("Scanned SendToInjective events from Ethereum") - withdrawals, err := s.ethereum.GetTransactionBatchExecutedEvents(startingBlock, currentBlock) if err != nil { - return 0, err + metrics.ReportFuncError(s.svcTags) + return 0, errors.Wrap(err, "failed to get TransactionBatchExecuted events") } - log.WithFields(log.Fields{ - "start": startingBlock, - "end": currentBlock, - "Withdraws": withdrawals, - }).Debugln("Scanned TransactionBatchExecuted events from Ethereum") - erc20Deployments, err := s.ethereum.GetPeggyERC20DeployedEvents(startingBlock, currentBlock) if err != nil { - return 0, err + metrics.ReportFuncError(s.svcTags) + return 0, errors.Wrap(err, "failed to get ERC20Deployed events") } - log.WithFields(log.Fields{ - "start": startingBlock, - "end": currentBlock, - "erc20Deployed": erc20Deployments, - }).Debugln("Scanned FilterERC20Deployed events from Ethereum") - valsetUpdates, err := s.ethereum.GetValsetUpdatedEvents(startingBlock, currentBlock) if err != nil { + metrics.ReportFuncError(s.svcTags) + return 0, errors.Wrap(err, "failed to get ValsetUpdated events") } - log.WithFields(log.Fields{ - "start": startingBlock, - "end": currentBlock, - "valsetUpdates": valsetUpdates, - }).Debugln("Scanned ValsetUpdatedEvents events from Ethereum") - // note that starting block overlaps with our last checked block, because we have to deal with // the possibility that the relayer was killed after relaying only one of multiple events in a single // block, so we also need this routine so make sure we don't send in the first event in this hypothetical @@ -211,22 +187,61 @@ func (s *PeggyOrchestrator) relayEthEvents( lastClaimEvent, err := s.injective.LastClaimEvent(ctx) if err != nil { metrics.ReportFuncError(s.svcTags) - err = errors.New("failed to query last claim event from backend") - return 0, err + return 0, errors.New("failed to query last claim event from injective") } legacyDeposits = filterSendToCosmosEventsByNonce(legacyDeposits, lastClaimEvent.EthereumEventNonce) + + log.WithFields(log.Fields{ + "start": startingBlock, + "end": currentBlock, + "old_deposits": legacyDeposits, + }).Debugln("scanned SendToCosmos events from Ethereum") + deposits = filterSendToInjectiveEventsByNonce(deposits, lastClaimEvent.EthereumEventNonce) + + log.WithFields(log.Fields{ + "start": startingBlock, + "end": currentBlock, + "deposits": deposits, + }).Debugln("scanned SendToInjective events from Ethereum") + withdrawals = filterTransactionBatchExecutedEventsByNonce(withdrawals, lastClaimEvent.EthereumEventNonce) + + log.WithFields(log.Fields{ + "start": startingBlock, + "end": currentBlock, + "withdrawals": withdrawals, + }).Debugln("scanned TransactionBatchExecuted events from Ethereum") + erc20Deployments = filterERC20DeployedEventsByNonce(erc20Deployments, lastClaimEvent.EthereumEventNonce) + + log.WithFields(log.Fields{ + "start": startingBlock, + "end": currentBlock, + "erc20_deployments": erc20Deployments, + }).Debugln("scanned FilterERC20Deployed events from Ethereum") + valsetUpdates = filterValsetUpdateEventsByNonce(valsetUpdates, lastClaimEvent.EthereumEventNonce) + log.WithFields(log.Fields{ + "start": startingBlock, + "end": currentBlock, + "valset_updates": valsetUpdates, + }).Debugln("scanned ValsetUpdated events from Ethereum") + if len(legacyDeposits) > 0 || len(deposits) > 0 || len(withdrawals) > 0 || len(erc20Deployments) > 0 || len(valsetUpdates) > 0 { // todo get eth chain id from the chain - if err := s.injective.SendEthereumClaims(ctx, lastClaimEvent.EthereumEventNonce, legacyDeposits, deposits, withdrawals, erc20Deployments, valsetUpdates); err != nil { + if err := s.injective.SendEthereumClaims(ctx, + lastClaimEvent.EthereumEventNonce, + legacyDeposits, + deposits, + withdrawals, + erc20Deployments, + valsetUpdates, + ); err != nil { metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to send ethereum claims to Cosmos chain") - return 0, err + return 0, errors.Wrap(err, "failed to send ethereum claims to Injective") } } From bc75629232c338f4c0a186c4d367534214eb3fea Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Mon, 19 Jun 2023 11:47:40 +0200 Subject: [PATCH 31/72] improve batch requester logging --- orchestrator/batch_request.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/orchestrator/batch_request.go b/orchestrator/batch_request.go index 86f18003..277882f6 100644 --- a/orchestrator/batch_request.go +++ b/orchestrator/batch_request.go @@ -25,6 +25,7 @@ func (s *PeggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) mustRequestBatch := false if isInjectiveRelayer && time.Since(startTime) > time.Hour*8 { mustRequestBatch = true + startTime = time.Now() } var pg loops.ParanoidGroup @@ -37,16 +38,17 @@ func (s *PeggyOrchestrator) requestBatches(ctx context.Context, logger log.Logge unbatchedTokensWithFees, err := s.getBatchFeesByToken(ctx, logger) if err != nil { // non-fatal, just alert - logger.Warningln("unable to get UnbatchedTokensWithFees for the token") + // todo dusan: change naming on injective methods + logger.WithError(err).Warningln("unable to get unbatched fees from Injective") return nil } if len(unbatchedTokensWithFees) == 0 { - logger.Debugln("No outgoing withdraw tx or Unbatched token fee less than threshold") + logger.Debugln("no outgoing withdraw txs or the batch fee threshold is not met") return nil } - logger.WithField("unbatchedTokensWithFees", unbatchedTokensWithFees).Debugln("Check if token fees meets set threshold amount and send batch request") + logger.WithField("unbatchedTokensWithFees", unbatchedTokensWithFees).Debugln("check if token fees meet set threshold amount and send batch request") for _, unbatchedToken := range unbatchedTokensWithFees { // check if the token is present in cosmos denom. if so, send batch request with cosmosDenom tokenAddr := eth.HexToAddress(unbatchedToken.Token) @@ -58,7 +60,11 @@ func (s *PeggyOrchestrator) requestBatches(ctx context.Context, logger log.Logge } denom := s.getTokenDenom(tokenAddr) - logger.WithFields(log.Fields{"tokenContract": tokenAddr, "denom": denom}).Infoln("sending batch request") + logger.WithFields(log.Fields{ + "denom": denom, + "token_contract": tokenAddr, + }).Infoln("sending batch request") + _ = s.injective.SendRequestBatch(ctx, denom) } @@ -81,7 +87,7 @@ func (s *PeggyOrchestrator) getBatchFeesByToken(ctx context.Context, log log.Log retry.Context(ctx), retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { - log.WithError(err).Errorf("failed to get UnbatchedTokensWithFees, will retry (%d)", n) + log.WithError(err).Errorf("failed to get unbatched fees, will retry (%d)", n) }), ); err != nil { return nil, err From 7708921cc9f2e8eba4337dac55409c2bd4e4406a Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Mon, 19 Jun 2023 12:12:09 +0200 Subject: [PATCH 32/72] improve signer logging --- orchestrator/signer.go | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/orchestrator/signer.go b/orchestrator/signer.go index 202186f1..f9a56438 100644 --- a/orchestrator/signer.go +++ b/orchestrator/signer.go @@ -42,14 +42,14 @@ func (s *PeggyOrchestrator) getPeggyID(ctx context.Context, logger log.Logger) ( retry.Context(ctx), retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get PeggyID from Ethereum contract, will retry (%d)", n) + logger.WithError(err).Warningf("failed to get peggy ID from Ethereum contract, will retry (%d)", n) }), ); err != nil { logger.WithError(err).Errorln("got error, loop exits") return [32]byte{}, err } - logger.Debugf("received peggyID %s", peggyID.Hex()) + logger.WithField("id", peggyID.Hex()).Debugln("got peggy ID from Ethereum contract") return peggyID, nil } @@ -72,7 +72,7 @@ func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Lo oldestValsets, err := s.injective.OldestUnsignedValsets(ctx) if err != nil { if err == cosmos.ErrNotFound || oldestValsets == nil { - logger.Debugln("no Valset waiting to be signed") + logger.Debugln("no new valset waiting to be signed") return nil } @@ -87,7 +87,7 @@ func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Lo retry.Context(ctx), retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get unsigned Valset for signing, will retry (%d)", n) + logger.WithError(err).Warningf("failed to get unsigned valset, will retry (%d)", n) }), ); err != nil { logger.WithError(err).Errorln("got error, loop exits") @@ -95,14 +95,14 @@ func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Lo } for _, vs := range oldestUnsignedValsets { - logger.Infoln("Sending Valset confirm for %d", vs.Nonce) + logger.Infoln("sending confirm for valset %d", vs.Nonce) if err := retry.Do(func() error { return s.injective.SendValsetConfirm(ctx, peggyID, vs, s.ethereum.FromAddress()) }, retry.Context(ctx), retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to sign and send Valset confirmation to Cosmos, will retry (%d)", n) + logger.WithError(err).Warningf("failed to sign and send valset confirmation to Injective, will retry (%d)", n) }), ); err != nil { logger.WithError(err).Errorln("got error, loop exits") @@ -115,22 +115,25 @@ func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Lo func (s *PeggyOrchestrator) signTransactionBatches(ctx context.Context, logger log.Logger, peggyID common.Hash) error { var oldestUnsignedTransactionBatch *types.OutgoingTxBatch - if err := retry.Do(func() error { + retryFn := func() error { // sign the last unsigned batch, TODO check if we already have signed this txBatch, err := s.injective.OldestUnsignedTransactionBatch(ctx) if err != nil { if err == cosmos.ErrNotFound || txBatch == nil { - logger.Debugln("no TransactionBatch waiting to be signed") + logger.Debugln("no new transaction batch waiting to be signed") return nil } return err } oldestUnsignedTransactionBatch = txBatch return nil - }, retry.Context(ctx), + } + + if err := retry.Do(retryFn, + retry.Context(ctx), retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get unsigned TransactionBatch for signing, will retry (%d)", n) + logger.WithError(err).Warningf("failed to get unsigned transaction batch, will retry (%d)", n) })); err != nil { logger.WithError(err).Errorln("got error, loop exits") return err @@ -140,13 +143,13 @@ func (s *PeggyOrchestrator) signTransactionBatches(ctx context.Context, logger l return nil } - logger.Infoln("Sending TransactionBatch confirm for BatchNonce %d", oldestUnsignedTransactionBatch.BatchNonce) + logger.Infoln("sending confirm for batch %d", oldestUnsignedTransactionBatch.BatchNonce) if err := retry.Do(func() error { return s.injective.SendBatchConfirm(ctx, peggyID, oldestUnsignedTransactionBatch, s.ethereum.FromAddress()) }, retry.Context(ctx), retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to sign and send TransactionBatch confirmation to Cosmos, will retry (%d)", n) + logger.WithError(err).Warningf("failed to sign and send batch confirmation to Injective, will retry (%d)", n) })); err != nil { logger.WithError(err).Errorln("got error, loop exits") return err From b4a39a97203ad87ba40b6608244e0ecc69f7cbd2 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Mon, 19 Jun 2023 12:50:23 +0200 Subject: [PATCH 33/72] improve relayer logging --- orchestrator/relayer.go | 138 ++++++++++++++++++++++------------------ 1 file changed, 76 insertions(+), 62 deletions(-) diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index 7ddc3561..9a3cc8f0 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -22,24 +22,26 @@ func (s *PeggyOrchestrator) RelayerMainLoop(ctx context.Context) (err error) { return loops.RunLoop(ctx, defaultLoopDur, func() error { var pg loops.ParanoidGroup if s.valsetRelayEnabled { - logger.Info("Valset Relay Enabled. Starting to relay valsets to Ethereum") pg.Go(func() error { - return retry.Do(func() error { - return s.relayValsets(ctx) - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to relay Valsets, will retry (%d)", n) - })) + return retry.Do(func() error { return s.relayValsets(ctx, logger) }, + retry.Context(ctx), + retry.Attempts(s.maxAttempts), + retry.OnRetry(func(n uint, err error) { + logger.WithError(err).Warningf("failed to relay valsets, will retry (%d)", n) + }), + ) }) } if s.batchRelayEnabled { - logger.Info("Batch Relay Enabled. Starting to relay batches to Ethereum") pg.Go(func() error { - return retry.Do(func() error { - return s.relayBatches(ctx) - }, retry.Context(ctx), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to relay TxBatches, will retry (%d)", n) - })) + return retry.Do(func() error { return s.relayBatches(ctx, logger) }, + retry.Context(ctx), + retry.Attempts(s.maxAttempts), + retry.OnRetry(func(n uint, err error) { + logger.WithError(err).Warningf("failed to relay batches, will retry (%d)", n) + }), + ) }) } @@ -49,11 +51,12 @@ func (s *PeggyOrchestrator) RelayerMainLoop(ctx context.Context) (err error) { return err } } + return nil }) } -func (s *PeggyOrchestrator) relayValsets(ctx context.Context) error { +func (s *PeggyOrchestrator) relayValsets(ctx context.Context, logger log.Logger) error { metrics.ReportFuncCall(s.svcTags) doneFn := metrics.ReportFuncTiming(s.svcTags) defer doneFn() @@ -64,20 +67,16 @@ func (s *PeggyOrchestrator) relayValsets(ctx context.Context) error { latestValsets, err := s.injective.LatestValsets(ctx) if err != nil { metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to fetch latest valsets from cosmos") - return err + return errors.Wrap(err, "failed to fetch latest valsets from Injective") } var latestCosmosSigs []*types.MsgValsetConfirm var latestCosmosConfirmed *types.Valset for _, set := range latestValsets { - //sigs, err := s.cosmosQueryClient.AllValsetConfirms(ctx, set.Nonce) - sigs, err := s.injective.AllValsetConfirms(ctx, set.Nonce) if err != nil { metrics.ReportFuncError(s.svcTags) - err = errors.Wrapf(err, "failed to get valset confirms at nonce %d", set.Nonce) - return err + return errors.Wrapf(err, "failed to get valset confirms at nonce %d", set.Nonce) } else if len(sigs) == 0 { continue } @@ -88,18 +87,20 @@ func (s *PeggyOrchestrator) relayValsets(ctx context.Context) error { } if latestCosmosConfirmed == nil { - log.Debugln("no confirmed valsets found, nothing to relay") + log.Debugln("no confirmed valsets found on Injective, nothing to relay...") return nil } currentEthValset, err := s.findLatestValset(ctx) if err != nil { metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "couldn't find latest confirmed valset on Ethereum") - return err + return errors.Wrap(err, "failed to find latest confirmed valset on Ethereum") } - log.WithFields(log.Fields{"currentEthValset": currentEthValset, "latestCosmosConfirmed": latestCosmosConfirmed}).Debugln("Found Latest valsets") + logger.WithFields(log.Fields{ + "inj_valset": latestCosmosConfirmed, + "eth_valset": currentEthValset, + }).Debugln("found latest valsets") if latestCosmosConfirmed.Nonce <= currentEthValset.Nonce { return nil @@ -108,10 +109,10 @@ func (s *PeggyOrchestrator) relayValsets(ctx context.Context) error { latestEthereumValsetNonce, err := s.ethereum.GetValsetNonce(ctx) if err != nil { metrics.ReportFuncError(s.svcTags) - return errors.Wrap(err, "failed to get latest Valset nonce") + return errors.Wrap(err, "failed to get latest valset nonce from Ethereum") } - // Check if latestCosmosConfirmed already submitted by other validators in mean time + // Check if other validators already updated the valset on ethereum if latestCosmosConfirmed.Nonce <= latestEthereumValsetNonce.Uint64() { return nil } @@ -119,7 +120,7 @@ func (s *PeggyOrchestrator) relayValsets(ctx context.Context) error { // Check custom time delay offset blockResult, err := s.injective.GetBlock(ctx, int64(latestCosmosConfirmed.Height)) if err != nil { - return err + return errors.Wrapf(err, "failed to get block %d from Injective", latestCosmosConfirmed.Height) } valsetCreatedAt := blockResult.Block.Time @@ -129,8 +130,10 @@ func (s *PeggyOrchestrator) relayValsets(ctx context.Context) error { return nil } - log.Infof("Detected latest cosmos valset nonce %d, but latest valset on Ethereum is %d. Sending update to Ethereum\n", - latestCosmosConfirmed.Nonce, latestEthereumValsetNonce.Uint64()) + logger.WithFields(log.Fields{ + "inj_valset": latestCosmosConfirmed.Nonce, + "eth_valset": latestEthereumValsetNonce.Uint64(), + }).Infoln("detected new valset on Injective. Sending update to Ethereum...") txHash, err := s.ethereum.SendEthValsetUpdate( ctx, @@ -144,12 +147,12 @@ func (s *PeggyOrchestrator) relayValsets(ctx context.Context) error { return err } - log.WithField("tx_hash", txHash.Hex()).Infoln("Sent Ethereum Tx (EthValsetUpdate)") + logger.WithField("tx_hash", txHash.Hex()).Infoln("updated valset on Ethereum") return nil } -func (s *PeggyOrchestrator) relayBatches(ctx context.Context) error { +func (s *PeggyOrchestrator) relayBatches(ctx context.Context, logger log.Logger) error { metrics.ReportFuncCall(s.svcTags) doneFn := metrics.ReportFuncTiming(s.svcTags) defer doneFn() @@ -159,8 +162,12 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context) error { metrics.ReportFuncError(s.svcTags) return err } - var oldestSignedBatch *types.OutgoingTxBatch - var oldestSigs []*types.MsgConfirmBatch + + var ( + oldestSignedBatch *types.OutgoingTxBatch + oldestSigs []*types.MsgConfirmBatch + ) + for _, batch := range latestBatches { sigs, err := s.injective.TransactionBatchSignatures(ctx, batch.BatchNonce, common.HexToAddress(batch.TokenContract)) if err != nil { @@ -173,8 +180,9 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context) error { oldestSignedBatch = batch oldestSigs = sigs } + if oldestSignedBatch == nil { - log.Debugln("could not find batch with signatures, nothing to relay") + logger.Debugln("no confirmed transaction batches on Injective, nothing to relay...") return nil } @@ -187,6 +195,15 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context) error { return err } + logger.WithFields(log.Fields{ + "inj_batch": oldestSignedBatch.BatchNonce, + "eth_batch": latestEthereumBatch.Uint64(), + }).Debugln("found latest batches") + + if oldestSignedBatch.BatchNonce <= latestEthereumBatch.Uint64() { + return nil + } + currentValset, err := s.findLatestValset(ctx) if err != nil { metrics.ReportFuncError(s.svcTags) @@ -196,17 +213,13 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context) error { return errors.New("latest valset not found") } - log.WithFields(log.Fields{"oldestSignedBatchNonce": oldestSignedBatch.BatchNonce, "latestEthereumBatchNonce": latestEthereumBatch.Uint64()}).Debugln("Found Latest valsets") - if oldestSignedBatch.BatchNonce <= latestEthereumBatch.Uint64() { - return nil - } - latestEthereumBatch, err = s.ethereum.GetTxBatchNonce(ctx, common.HexToAddress(oldestSignedBatch.TokenContract)) if err != nil { metrics.ReportFuncError(s.svcTags) return err } - // Check if oldestSignedBatch already submitted by other validators in mean time + + // Check if ethereum batch was updated by other validators if oldestSignedBatch.BatchNonce <= latestEthereumBatch.Uint64() { return nil } @@ -214,7 +227,7 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context) error { // Check custom time delay offset blockResult, err := s.injective.GetBlock(ctx, int64(oldestSignedBatch.Block)) if err != nil { - return err + return errors.Wrapf(err, "failed to get block %d from Injective", oldestSignedBatch.Block) } batchCreatedAt := blockResult.Block.Time @@ -224,7 +237,10 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context) error { return nil } - log.Infof("We have detected latest batch %d but latest on Ethereum is %d sending an update!", oldestSignedBatch.BatchNonce, latestEthereumBatch) + logger.WithFields(log.Fields{ + "inj_batch": oldestSignedBatch.BatchNonce, + "eth_batch": latestEthereumBatch.Uint64(), + }).Infoln("detected new transaction batch on Injective. Sending update to Ethereum...") // Send SendTransactionBatch to Ethereum txHash, err := s.ethereum.SendTransactionBatch(ctx, currentValset, oldestSignedBatch, oldestSigs) @@ -233,7 +249,7 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context) error { return err } - log.WithField("tx_hash", txHash.Hex()).Infoln("Sent Ethereum Tx (TransactionBatch)") + logger.WithField("tx_hash", txHash.Hex()).Infoln("updated transaction batch on Ethereum") return nil } @@ -253,42 +269,40 @@ func (s *PeggyOrchestrator) findLatestValset(ctx context.Context) (*types.Valset latestHeader, err := s.ethereum.HeaderByNumber(ctx, nil) if err != nil { metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to get latest header") - return nil, err + return nil, errors.Wrap(err, "failed to get latest header") } - currentBlock := latestHeader.Number.Uint64() latestEthereumValsetNonce, err := s.ethereum.GetValsetNonce(ctx) if err != nil { metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to get latest Valset nonce") - return nil, err + return nil, errors.Wrap(err, "failed to get latest valset nonce") } cosmosValset, err := s.injective.ValsetAt(ctx, latestEthereumValsetNonce.Uint64()) - //cosmosValset, err := s.cosmosQueryClient.ValsetAt(ctx, latestEthereumValsetNonce.Uint64()) if err != nil { metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to get cosmos Valset") - return nil, err + return nil, errors.Wrap(err, "failed to get Injective valset") } - for currentBlock > 0 { - log.WithField("current_block", currentBlock). - Debugln("About to submit a Valset or Batch looking back into the history to find the last Valset Update") + currentBlock := latestHeader.Number.Uint64() - var endSearchBlock uint64 + for currentBlock > 0 { + var startSearchBlock uint64 if currentBlock <= valsetBlocksToSearch { - endSearchBlock = 0 + startSearchBlock = 0 } else { - endSearchBlock = currentBlock - valsetBlocksToSearch + startSearchBlock = currentBlock - valsetBlocksToSearch } - valsetUpdatedEvents, err := s.ethereum.GetValsetUpdatedEvents(endSearchBlock, currentBlock) + log.WithFields(log.Fields{ + "start_block": startSearchBlock, + "end_block": currentBlock, + }).Debugln("looking back into Ethereum history to find the last valset update") + + valsetUpdatedEvents, err := s.ethereum.GetValsetUpdatedEvents(startSearchBlock, currentBlock) if err != nil { metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to filter past ValsetUpdated events from Ethereum") - return nil, err + return nil, errors.Wrap(err, "failed to filter past ValsetUpdated events from Ethereum") } // by default the lowest found valset goes first, we want the highest @@ -297,13 +311,13 @@ func (s *PeggyOrchestrator) findLatestValset(ctx context.Context) (*types.Valset // we could access just the latest element later. sort.Sort(sort.Reverse(PeggyValsetUpdatedEvents(valsetUpdatedEvents))) - log.Debugln("found events", valsetUpdatedEvents) - if len(valsetUpdatedEvents) == 0 { - currentBlock = endSearchBlock + currentBlock = startSearchBlock continue } + log.Debugln("found events", valsetUpdatedEvents) + // we take only the first event if we find any at all. event := valsetUpdatedEvents[0] valset := &types.Valset{ From 3f27fef16d769ec4a058925fd692e6c5f11e3dde Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Mon, 19 Jun 2023 13:49:36 +0200 Subject: [PATCH 34/72] logging --- orchestrator/batch_request.go | 7 +++---- orchestrator/ethereum/network.go | 2 +- orchestrator/oracle.go | 8 ++++---- orchestrator/orchestrator.go | 4 ++-- orchestrator/relayer.go | 2 +- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/orchestrator/batch_request.go b/orchestrator/batch_request.go index 277882f6..59d5dd38 100644 --- a/orchestrator/batch_request.go +++ b/orchestrator/batch_request.go @@ -38,17 +38,16 @@ func (s *PeggyOrchestrator) requestBatches(ctx context.Context, logger log.Logge unbatchedTokensWithFees, err := s.getBatchFeesByToken(ctx, logger) if err != nil { // non-fatal, just alert - // todo dusan: change naming on injective methods logger.WithError(err).Warningln("unable to get unbatched fees from Injective") return nil } if len(unbatchedTokensWithFees) == 0 { - logger.Debugln("no outgoing withdraw txs or the batch fee threshold is not met") + logger.Debugln("no outgoing withdrawals or minimum batch fee is not met") return nil } - logger.WithField("unbatchedTokensWithFees", unbatchedTokensWithFees).Debugln("check if token fees meet set threshold amount and send batch request") + logger.WithField("unbatched_fees_by_token", unbatchedTokensWithFees).Debugln("check if token fees meet threshold and send batch request") for _, unbatchedToken := range unbatchedTokensWithFees { // check if the token is present in cosmos denom. if so, send batch request with cosmosDenom tokenAddr := eth.HexToAddress(unbatchedToken.Token) @@ -63,7 +62,7 @@ func (s *PeggyOrchestrator) requestBatches(ctx context.Context, logger log.Logge logger.WithFields(log.Fields{ "denom": denom, "token_contract": tokenAddr, - }).Infoln("sending batch request") + }).Infoln("sending batch request to Injective") _ = s.injective.SendRequestBatch(ctx, denom) } diff --git a/orchestrator/ethereum/network.go b/orchestrator/ethereum/network.go index 561c2932..7d05ed31 100644 --- a/orchestrator/ethereum/network.go +++ b/orchestrator/ethereum/network.go @@ -67,7 +67,7 @@ func NewNetwork( // If Alchemy Websocket URL is set, then Subscribe to Pending Transaction of Peggy Contract. if ethNodeAlchemyWS != "" { log.WithFields(log.Fields{ - "url": ethNodeAlchemyWS, + "ws_url": ethNodeAlchemyWS, }).Infoln("subscribing to Alchemy websocket") go peggyContract.SubscribeToPendingTxs(ethNodeAlchemyWS) } diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index f1ec4343..3d61860f 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -31,7 +31,7 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { retryFn := func() error { height, err := s.getLastConfirmedEthHeight(ctx) if err != nil { - logger.WithError(err).Warningf("failed to get last claim. Querying peggy params...") + logger.WithError(err).Warningf("failed to get last claim from Injective. Querying peggy params...") } if height == 0 { @@ -49,7 +49,7 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { retry.Context(ctx), retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get last confirmed eth height, will retry (%d)", n) + logger.WithError(err).Warningf("failed to get last confirmed Ethereum height from Injective, will retry (%d)", n) }), ); err != nil { logger.WithError(err).Errorln("got error, loop exits") @@ -187,7 +187,7 @@ func (s *PeggyOrchestrator) relayEthEvents( lastClaimEvent, err := s.injective.LastClaimEvent(ctx) if err != nil { metrics.ReportFuncError(s.svcTags) - return 0, errors.New("failed to query last claim event from injective") + return 0, errors.New("failed to query last claim event from Injective") } legacyDeposits = filterSendToCosmosEventsByNonce(legacyDeposits, lastClaimEvent.EthereumEventNonce) @@ -241,7 +241,7 @@ func (s *PeggyOrchestrator) relayEthEvents( valsetUpdates, ); err != nil { metrics.ReportFuncError(s.svcTags) - return 0, errors.Wrap(err, "failed to send ethereum claims to Injective") + return 0, errors.Wrap(err, "failed to send event claims to Injective") } } diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index b691e710..d04e7bc7 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -167,7 +167,7 @@ func (s *PeggyOrchestrator) Run(ctx context.Context, validatorMode bool) error { // startValidatorMode runs all orchestrator processes. This is called // when peggo is run alongside a validator injective node. func (s *PeggyOrchestrator) startValidatorMode(ctx context.Context) error { - log.Infoln("running in validator mode") + log.Infoln("running peggo in validator mode") var pg loops.ParanoidGroup @@ -191,7 +191,7 @@ func (s *PeggyOrchestrator) startValidatorMode(ctx context.Context) error { // messages that do not require a validator's signature. This mode is run // alongside a non-validator injective node func (s *PeggyOrchestrator) startRelayerMode(ctx context.Context) error { - log.Infoln("running in relayer mode") + log.Infoln("running peggo in relayer mode") var pg loops.ParanoidGroup diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index 9a3cc8f0..9fd2955c 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -112,7 +112,7 @@ func (s *PeggyOrchestrator) relayValsets(ctx context.Context, logger log.Logger) return errors.Wrap(err, "failed to get latest valset nonce from Ethereum") } - // Check if other validators already updated the valset on ethereum + // Check if other validators already updated the valset if latestCosmosConfirmed.Nonce <= latestEthereumValsetNonce.Uint64() { return nil } From 7424b4402b8a7f73297aed0e323e0010d46387ff Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Mon, 19 Jun 2023 17:57:32 +0200 Subject: [PATCH 35/72] fix tests --- orchestrator/relayer_test.go | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/orchestrator/relayer_test.go b/orchestrator/relayer_test.go index 2bb40e24..caf0f0b0 100644 --- a/orchestrator/relayer_test.go +++ b/orchestrator/relayer_test.go @@ -624,7 +624,12 @@ func TestBatchRelaying(t *testing.T) { orch := &PeggyOrchestrator{ injective: &mockInjective{ latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { - return []*types.OutgoingTxBatch{{TokenContract: "tokenContract"}}, nil + return []*types.OutgoingTxBatch{ + { + TokenContract: "tokenContract", + BatchNonce: 100, + }, + }, nil }, transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { return []*types.MsgConfirmBatch{{}}, nil // non-nil will do @@ -632,7 +637,7 @@ func TestBatchRelaying(t *testing.T) { }, ethereum: mockEthereum{ getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { - return nil, nil + return big.NewInt(99), nil }, headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { return nil, errors.New("fail") @@ -649,7 +654,12 @@ func TestBatchRelaying(t *testing.T) { orch := &PeggyOrchestrator{ injective: &mockInjective{ latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { - return []*types.OutgoingTxBatch{{TokenContract: "tokenContract"}}, nil + return []*types.OutgoingTxBatch{ + { + TokenContract: "tokenContract", + BatchNonce: 100, + }, + }, nil }, transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { return []*types.MsgConfirmBatch{{}}, nil // non-nil will do @@ -657,7 +667,7 @@ func TestBatchRelaying(t *testing.T) { }, ethereum: mockEthereum{ getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { - return nil, nil + return big.NewInt(99), nil }, headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { return &ctypes.Header{Number: big.NewInt(100)}, nil @@ -678,7 +688,12 @@ func TestBatchRelaying(t *testing.T) { orch := &PeggyOrchestrator{ injective: &mockInjective{ latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { - return []*types.OutgoingTxBatch{{TokenContract: "tokenContract"}}, nil + return []*types.OutgoingTxBatch{ + { + TokenContract: "tokenContract", + BatchNonce: 100, + }, + }, nil }, transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { return []*types.MsgConfirmBatch{{}}, nil // non-nil will do @@ -689,7 +704,7 @@ func TestBatchRelaying(t *testing.T) { }, ethereum: mockEthereum{ getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { - return nil, nil + return big.NewInt(99), nil }, headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { return &ctypes.Header{Number: big.NewInt(100)}, nil @@ -710,7 +725,12 @@ func TestBatchRelaying(t *testing.T) { orch := &PeggyOrchestrator{ injective: &mockInjective{ latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { - return []*types.OutgoingTxBatch{{TokenContract: "tokenContract"}}, nil + return []*types.OutgoingTxBatch{ + { + TokenContract: "tokenContract", + BatchNonce: 100, + }, + }, nil }, transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { return []*types.MsgConfirmBatch{{}}, nil // non-nil will do @@ -721,7 +741,7 @@ func TestBatchRelaying(t *testing.T) { }, ethereum: mockEthereum{ getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { - return nil, nil + return big.NewInt(99), nil }, headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { return &ctypes.Header{Number: big.NewInt(100)}, nil From 06fce045d27a8128c16162d6319e6bbb2c790d59 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Tue, 20 Jun 2023 10:34:03 +0200 Subject: [PATCH 36/72] organize imports --- orchestrator/cosmos/network.go | 15 ++++++------ orchestrator/ethereum/network.go | 39 ++++++++++++++++---------------- orchestrator/oracle.go | 10 ++++++++ orchestrator/orchestrator.go | 4 ++-- orchestrator/relayer.go | 16 +++++++------ orchestrator/signer.go | 8 ++++--- 6 files changed, 53 insertions(+), 39 deletions(-) diff --git a/orchestrator/cosmos/network.go b/orchestrator/cosmos/network.go index 3ad15fa7..4f5f9b23 100644 --- a/orchestrator/cosmos/network.go +++ b/orchestrator/cosmos/network.go @@ -2,12 +2,8 @@ package cosmos import ( "context" - "github.com/InjectiveLabs/peggo/orchestrator/cosmos/tmclient" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/keystore" - peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" - peggy "github.com/InjectiveLabs/sdk-go/chain/peggy/types" - chainclient "github.com/InjectiveLabs/sdk-go/client/chain" - "github.com/InjectiveLabs/sdk-go/client/common" + "time" + rpchttp "github.com/cometbft/cometbft/rpc/client/http" tmctypes "github.com/cometbft/cometbft/rpc/core/types" "github.com/cosmos/cosmos-sdk/crypto/keyring" @@ -17,9 +13,14 @@ import ( log "github.com/xlab/suplog" "google.golang.org/grpc" "google.golang.org/grpc/connectivity" - "time" + "github.com/InjectiveLabs/peggo/orchestrator/cosmos/tmclient" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum/keystore" + peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + peggy "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + chainclient "github.com/InjectiveLabs/sdk-go/client/chain" + "github.com/InjectiveLabs/sdk-go/client/common" ) type Network struct { diff --git a/orchestrator/ethereum/network.go b/orchestrator/ethereum/network.go index 7d05ed31..3aae420e 100644 --- a/orchestrator/ethereum/network.go +++ b/orchestrator/ethereum/network.go @@ -2,21 +2,22 @@ package ethereum import ( "context" - log "github.com/xlab/suplog" "math/big" "strings" "time" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/committer" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/peggy" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/provider" - wrappers "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" - peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" "github.com/ethereum/go-ethereum/accounts/abi/bind" ethcmn "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" "github.com/pkg/errors" + log "github.com/xlab/suplog" + + "github.com/InjectiveLabs/peggo/orchestrator/ethereum/committer" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum/peggy" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum/provider" + wrappers "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" + peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" ) type Network struct { @@ -93,10 +94,9 @@ func (n *Network) GetSendToCosmosEvents(startBlock, endBlock uint64) ([]*wrapper return nil, errors.Wrap(err, "failed to init Peggy events filterer") } - endblock := endBlock iter, err := peggyFilterer.FilterSendToCosmosEvent(&bind.FilterOpts{ Start: startBlock, - End: &endblock, + End: &endBlock, }, nil, nil, nil) if err != nil { if !isUnknownBlockErr(err) { @@ -106,13 +106,13 @@ func (n *Network) GetSendToCosmosEvents(startBlock, endBlock uint64) ([]*wrapper } } + defer iter.Close() + var sendToCosmosEvents []*wrappers.PeggySendToCosmosEvent for iter.Next() { sendToCosmosEvents = append(sendToCosmosEvents, iter.Event) } - iter.Close() - return sendToCosmosEvents, nil } @@ -122,10 +122,9 @@ func (n *Network) GetSendToInjectiveEvents(startBlock, endBlock uint64) ([]*wrap return nil, errors.Wrap(err, "failed to init Peggy events filterer") } - endblock := endBlock iter, err := peggyFilterer.FilterSendToInjectiveEvent(&bind.FilterOpts{ Start: startBlock, - End: &endblock, + End: &endBlock, }, nil, nil, nil) if err != nil { if !isUnknownBlockErr(err) { @@ -135,13 +134,13 @@ func (n *Network) GetSendToInjectiveEvents(startBlock, endBlock uint64) ([]*wrap } } + defer iter.Close() + var sendToInjectiveEvents []*wrappers.PeggySendToInjectiveEvent for iter.Next() { sendToInjectiveEvents = append(sendToInjectiveEvents, iter.Event) } - iter.Close() - return sendToInjectiveEvents, nil } @@ -163,13 +162,13 @@ func (n *Network) GetPeggyERC20DeployedEvents(startBlock, endBlock uint64) ([]*w } } + defer iter.Close() + var transactionBatchExecutedEvents []*wrappers.PeggyERC20DeployedEvent for iter.Next() { transactionBatchExecutedEvents = append(transactionBatchExecutedEvents, iter.Event) } - iter.Close() - return transactionBatchExecutedEvents, nil } @@ -191,13 +190,13 @@ func (n *Network) GetValsetUpdatedEvents(startBlock, endBlock uint64) ([]*wrappe } } + defer iter.Close() + var valsetUpdatedEvents []*wrappers.PeggyValsetUpdatedEvent for iter.Next() { valsetUpdatedEvents = append(valsetUpdatedEvents, iter.Event) } - iter.Close() - return valsetUpdatedEvents, nil } @@ -219,13 +218,13 @@ func (n *Network) GetTransactionBatchExecutedEvents(startBlock, endBlock uint64) } } + defer iter.Close() + var transactionBatchExecutedEvents []*wrappers.PeggyTransactionBatchExecutedEvent for iter.Next() { transactionBatchExecutedEvents = append(transactionBatchExecutedEvents, iter.Event) } - iter.Close() - return transactionBatchExecutedEvents, nil } diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index 3d61860f..b507fdcd 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -190,6 +190,8 @@ func (s *PeggyOrchestrator) relayEthEvents( return 0, errors.New("failed to query last claim event from Injective") } + log.Infof("sending event claims. Last event nonce is %d", lastClaimEvent.EthereumEventNonce) + legacyDeposits = filterSendToCosmosEventsByNonce(legacyDeposits, lastClaimEvent.EthereumEventNonce) log.WithFields(log.Fields{ @@ -245,6 +247,14 @@ func (s *PeggyOrchestrator) relayEthEvents( } } + log.WithFields(log.Fields{ + "legacy_deposits": len(legacyDeposits), + "deposits": len(deposits), + "withdrawals": len(withdrawals), + "erc20Deployments": len(erc20Deployments), + "valsetUpdates": len(valsetUpdates), + }).Infoln("sent event claims to Injective") + return currentBlock, nil } diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index 674559a9..db02a423 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -2,13 +2,13 @@ package orchestrator import ( "context" - tmctypes "github.com/cometbft/cometbft/rpc/core/types" - "github.com/pkg/errors" "math/big" "time" + tmctypes "github.com/cometbft/cometbft/rpc/core/types" eth "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/pkg/errors" log "github.com/xlab/suplog" "github.com/InjectiveLabs/metrics" diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index 9fd2955c..3f11d2d5 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -2,18 +2,20 @@ package orchestrator import ( "context" - "github.com/InjectiveLabs/metrics" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/util" - "github.com/InjectiveLabs/peggo/orchestrator/loops" - wrappers "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" - "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + "sort" + "time" + "github.com/avast/retry-go" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" log "github.com/xlab/suplog" - "sort" - "time" + + "github.com/InjectiveLabs/metrics" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum/util" + "github.com/InjectiveLabs/peggo/orchestrator/loops" + wrappers "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" + "github.com/InjectiveLabs/sdk-go/chain/peggy/types" ) func (s *PeggyOrchestrator) RelayerMainLoop(ctx context.Context) (err error) { diff --git a/orchestrator/signer.go b/orchestrator/signer.go index f9a56438..690085c2 100644 --- a/orchestrator/signer.go +++ b/orchestrator/signer.go @@ -2,12 +2,14 @@ package orchestrator import ( "context" - "github.com/InjectiveLabs/peggo/orchestrator/cosmos" - "github.com/InjectiveLabs/peggo/orchestrator/loops" - "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + "github.com/avast/retry-go" "github.com/ethereum/go-ethereum/common" log "github.com/xlab/suplog" + + "github.com/InjectiveLabs/peggo/orchestrator/cosmos" + "github.com/InjectiveLabs/peggo/orchestrator/loops" + "github.com/InjectiveLabs/sdk-go/chain/peggy/types" ) // EthSignerMainLoop simply signs off on any batches or validator sets provided by the validator From 0bb572929ba49e1a39868029c992b35493bb0ec1 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 28 Jun 2023 11:50:34 +0200 Subject: [PATCH 37/72] init eth key config --- cmd/peggo/options.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/cmd/peggo/options.go b/cmd/peggo/options.go index ae3b3a63..4ffabe1b 100644 --- a/cmd/peggo/options.go +++ b/cmd/peggo/options.go @@ -516,6 +516,37 @@ func initConfig(cmd *cli.Cmd) Config { Value: "500gwei", }) + cfg.ethKeystoreDir = cmd.String(cli.StringOpt{ + Name: "eth-keystore-dir", + Desc: "Specify Ethereum keystore dir (Geth-format) prefix.", + EnvVar: "PEGGO_ETH_KEYSTORE_DIR", + }) + + cfg.ethKeyFrom = cmd.String(cli.StringOpt{ + Name: "eth-from", + Desc: "Specify the from address. If specified, must exist in keystore, ledger or match the privkey.", + EnvVar: "PEGGO_ETH_FROM", + }) + + cfg.ethPassphrase = cmd.String(cli.StringOpt{ + Name: "eth-passphrase", + Desc: "Passphrase to unlock the private key from armor, if empty then stdin is used.", + EnvVar: "PEGGO_ETH_PASSPHRASE", + }) + + cfg.ethPrivKey = cmd.String(cli.StringOpt{ + Name: "eth-pk", + Desc: "Provide a raw Ethereum private key of the validator in hex. USE FOR TESTING ONLY!", + EnvVar: "PEGGO_ETH_PK", + }) + + cfg.ethUseLedger = cmd.Bool(cli.BoolOpt{ + Name: "eth-use-ledger", + Desc: "Use the Ethereum app on hardware ledger to sign transactions.", + EnvVar: "PEGGO_ETH_USE_LEDGER", + Value: false, + }) + /** Relayer **/ cfg.relayValsets = cmd.Bool(cli.BoolOpt{ From ce44ee440fd7c7ae02d299399f826b3081fe1207 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 28 Jun 2023 13:06:24 +0200 Subject: [PATCH 38/72] findLatestValsetOnEthereum logging --- orchestrator/relayer.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index 3f11d2d5..18d688f0 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -93,7 +93,7 @@ func (s *PeggyOrchestrator) relayValsets(ctx context.Context, logger log.Logger) return nil } - currentEthValset, err := s.findLatestValset(ctx) + currentEthValset, err := s.findLatestValsetOnEthereum(ctx, logger) if err != nil { metrics.ReportFuncError(s.svcTags) return errors.Wrap(err, "failed to find latest confirmed valset on Ethereum") @@ -206,7 +206,7 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context, logger log.Logger) return nil } - currentValset, err := s.findLatestValset(ctx) + currentValset, err := s.findLatestValsetOnEthereum(ctx, logger) if err != nil { metrics.ReportFuncError(s.svcTags) return errors.New("failed to find latest valset") @@ -263,7 +263,7 @@ const valsetBlocksToSearch = 2000 // as the latest update will be in recent blockchain history and the search moves from the present // backwards in time. In the case that the validator set has not been updated for a very long time // this will take longer. -func (s *PeggyOrchestrator) findLatestValset(ctx context.Context) (*types.Valset, error) { +func (s *PeggyOrchestrator) findLatestValsetOnEthereum(ctx context.Context, logger log.Logger) (*types.Valset, error) { metrics.ReportFuncCall(s.svcTags) doneFn := metrics.ReportFuncTiming(s.svcTags) defer doneFn() @@ -296,7 +296,7 @@ func (s *PeggyOrchestrator) findLatestValset(ctx context.Context) (*types.Valset startSearchBlock = currentBlock - valsetBlocksToSearch } - log.WithFields(log.Fields{ + logger.WithFields(log.Fields{ "start_block": startSearchBlock, "end_block": currentBlock, }).Debugln("looking back into Ethereum history to find the last valset update") @@ -318,7 +318,7 @@ func (s *PeggyOrchestrator) findLatestValset(ctx context.Context) (*types.Valset continue } - log.Debugln("found events", valsetUpdatedEvents) + logger.Debugln("found events", valsetUpdatedEvents) // we take only the first event if we find any at all. event := valsetUpdatedEvents[0] From 135de9c90d0de9a61b695df12cb224a478e0411b Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 28 Jun 2023 13:28:27 +0200 Subject: [PATCH 39/72] improve relayer logging --- orchestrator/relayer.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index 18d688f0..60fd4313 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -25,7 +25,7 @@ func (s *PeggyOrchestrator) RelayerMainLoop(ctx context.Context) (err error) { var pg loops.ParanoidGroup if s.valsetRelayEnabled { pg.Go(func() error { - return retry.Do(func() error { return s.relayValsets(ctx, logger) }, + return retry.Do(func() error { return s.relayValsets(ctx, log.WithField("loop", "ValsetRelaying")) }, retry.Context(ctx), retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { @@ -37,7 +37,7 @@ func (s *PeggyOrchestrator) RelayerMainLoop(ctx context.Context) (err error) { if s.batchRelayEnabled { pg.Go(func() error { - return retry.Do(func() error { return s.relayBatches(ctx, logger) }, + return retry.Do(func() error { return s.relayBatches(ctx, log.WithField("loop", "BatchRelaying")) }, retry.Context(ctx), retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { @@ -297,8 +297,8 @@ func (s *PeggyOrchestrator) findLatestValsetOnEthereum(ctx context.Context, logg } logger.WithFields(log.Fields{ - "start_block": startSearchBlock, - "end_block": currentBlock, + "block_start": startSearchBlock, + "block_end": currentBlock, }).Debugln("looking back into Ethereum history to find the last valset update") valsetUpdatedEvents, err := s.ethereum.GetValsetUpdatedEvents(startSearchBlock, currentBlock) From 19c8ddc6ac358e03fdff4b42de4cd15da9cf3a9d Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 28 Jun 2023 13:40:05 +0200 Subject: [PATCH 40/72] improve error --- orchestrator/relayer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index 60fd4313..7d1d07f7 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -209,10 +209,10 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context, logger log.Logger) currentValset, err := s.findLatestValsetOnEthereum(ctx, logger) if err != nil { metrics.ReportFuncError(s.svcTags) - return errors.New("failed to find latest valset") + return errors.Wrap(err, "failed to find latest valset") } else if currentValset == nil { metrics.ReportFuncError(s.svcTags) - return errors.New("latest valset not found") + return errors.Wrap(err, "latest valset not found") } latestEthereumBatch, err = s.ethereum.GetTxBatchNonce(ctx, common.HexToAddress(oldestSignedBatch.TokenContract)) From d04014cbfccd89f29fe33d4cdcefc42b01039498 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Thu, 29 Jun 2023 15:07:15 +0200 Subject: [PATCH 41/72] patch logs --- orchestrator/batch_request.go | 6 +-- orchestrator/oracle.go | 86 +++++++++++++++++------------------ orchestrator/oracle_test.go | 12 ++--- orchestrator/relayer.go | 4 +- orchestrator/signer.go | 7 +-- 5 files changed, 58 insertions(+), 57 deletions(-) diff --git a/orchestrator/batch_request.go b/orchestrator/batch_request.go index 59d5dd38..91ca11ed 100644 --- a/orchestrator/batch_request.go +++ b/orchestrator/batch_request.go @@ -15,7 +15,7 @@ import ( ) func (s *PeggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) { - logger := log.WithField("loop", "BatchRequesterLoop") + logger := log.WithField("loop", "BatchRequester") startTime := time.Now() // we're the only ones relaying @@ -47,7 +47,7 @@ func (s *PeggyOrchestrator) requestBatches(ctx context.Context, logger log.Logge return nil } - logger.WithField("unbatched_fees_by_token", unbatchedTokensWithFees).Debugln("check if token fees meet threshold and send batch request") + logger.WithField("unbatched_fees_by_token", unbatchedTokensWithFees).Debugln("checking if batch fee is met") for _, unbatchedToken := range unbatchedTokensWithFees { // check if the token is present in cosmos denom. if so, send batch request with cosmosDenom tokenAddr := eth.HexToAddress(unbatchedToken.Token) @@ -62,7 +62,7 @@ func (s *PeggyOrchestrator) requestBatches(ctx context.Context, logger log.Logge logger.WithFields(log.Fields{ "denom": denom, "token_contract": tokenAddr, - }).Infoln("sending batch request to Injective") + }).Infoln("sending MsgRequestBatch to Injective") _ = s.injective.SendRequestBatch(ctx, denom) } diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index b507fdcd..c689aea1 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -24,7 +24,7 @@ const ( // EthOracleMainLoop is responsible for making sure that Ethereum events are retrieved from the Ethereum blockchain // and ferried over to Cosmos where they will be used to issue tokens or process batches. func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { - logger := log.WithField("loop", "EthOracleMainLoop") + logger := log.WithField("loop", "EthOracle") lastResync := time.Now() var lastConfirmedEthHeight uint64 @@ -62,7 +62,7 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { // Relays events from Ethereum -> Cosmos var currentHeight uint64 if err := retry.Do(func() (err error) { - currentHeight, err = s.relayEthEvents(ctx, lastConfirmedEthHeight) + currentHeight, err = s.relayEthEvents(ctx, lastConfirmedEthHeight, logger) return }, retry.Context(ctx), @@ -125,10 +125,7 @@ func (s *PeggyOrchestrator) getLastConfirmedEthHeight(ctx context.Context) (uint // relayEthEvents checks for events such as a deposit to the Peggy Ethereum contract or a validator set update // or a transaction batch update. It then responds to these events by performing actions on the Cosmos chain if required -func (s *PeggyOrchestrator) relayEthEvents( - ctx context.Context, - startingBlock uint64, -) (uint64, error) { +func (s *PeggyOrchestrator) relayEthEvents(ctx context.Context, startingBlock uint64, logger log.Logger) (uint64, error) { metrics.ReportFuncCall(s.svcTags) doneFn := metrics.ReportFuncTiming(s.svcTags) defer doneFn() @@ -140,40 +137,40 @@ func (s *PeggyOrchestrator) relayEthEvents( } // add delay to ensure minimum confirmations are received and block is finalised - currentBlock := latestHeader.Number.Uint64() - ethBlockConfirmationDelay - if currentBlock < startingBlock { - return currentBlock, nil + latestBlock := latestHeader.Number.Uint64() - ethBlockConfirmationDelay + if latestBlock < startingBlock { + return latestBlock, nil } - if currentBlock > startingBlock+defaultBlocksToSearch { - currentBlock = startingBlock + defaultBlocksToSearch + if latestBlock > startingBlock+defaultBlocksToSearch { + latestBlock = startingBlock + defaultBlocksToSearch } - legacyDeposits, err := s.ethereum.GetSendToCosmosEvents(startingBlock, currentBlock) + legacyDeposits, err := s.ethereum.GetSendToCosmosEvents(startingBlock, latestBlock) if err != nil { metrics.ReportFuncError(s.svcTags) return 0, errors.Wrap(err, "failed to get SendToCosmos events") } - deposits, err := s.ethereum.GetSendToInjectiveEvents(startingBlock, currentBlock) + deposits, err := s.ethereum.GetSendToInjectiveEvents(startingBlock, latestBlock) if err != nil { metrics.ReportFuncError(s.svcTags) return 0, errors.Wrap(err, "failed to get SendToInjective events") } - withdrawals, err := s.ethereum.GetTransactionBatchExecutedEvents(startingBlock, currentBlock) + withdrawals, err := s.ethereum.GetTransactionBatchExecutedEvents(startingBlock, latestBlock) if err != nil { metrics.ReportFuncError(s.svcTags) return 0, errors.Wrap(err, "failed to get TransactionBatchExecuted events") } - erc20Deployments, err := s.ethereum.GetPeggyERC20DeployedEvents(startingBlock, currentBlock) + erc20Deployments, err := s.ethereum.GetPeggyERC20DeployedEvents(startingBlock, latestBlock) if err != nil { metrics.ReportFuncError(s.svcTags) return 0, errors.Wrap(err, "failed to get ERC20Deployed events") } - valsetUpdates, err := s.ethereum.GetValsetUpdatedEvents(startingBlock, currentBlock) + valsetUpdates, err := s.ethereum.GetValsetUpdatedEvents(startingBlock, latestBlock) if err != nil { metrics.ReportFuncError(s.svcTags) return 0, errors.Wrap(err, "failed to get ValsetUpdated events") @@ -190,49 +187,51 @@ func (s *PeggyOrchestrator) relayEthEvents( return 0, errors.New("failed to query last claim event from Injective") } - log.Infof("sending event claims. Last event nonce is %d", lastClaimEvent.EthereumEventNonce) - legacyDeposits = filterSendToCosmosEventsByNonce(legacyDeposits, lastClaimEvent.EthereumEventNonce) - log.WithFields(log.Fields{ - "start": startingBlock, - "end": currentBlock, + logger.WithFields(log.Fields{ + "block_start": startingBlock, + "block_end": latestBlock, "old_deposits": legacyDeposits, }).Debugln("scanned SendToCosmos events from Ethereum") deposits = filterSendToInjectiveEventsByNonce(deposits, lastClaimEvent.EthereumEventNonce) - log.WithFields(log.Fields{ - "start": startingBlock, - "end": currentBlock, - "deposits": deposits, + logger.WithFields(log.Fields{ + "block_start": startingBlock, + "block_end": latestBlock, + "deposits": deposits, }).Debugln("scanned SendToInjective events from Ethereum") withdrawals = filterTransactionBatchExecutedEventsByNonce(withdrawals, lastClaimEvent.EthereumEventNonce) - log.WithFields(log.Fields{ - "start": startingBlock, - "end": currentBlock, + logger.WithFields(log.Fields{ + "block_start": startingBlock, + "block_end": latestBlock, "withdrawals": withdrawals, }).Debugln("scanned TransactionBatchExecuted events from Ethereum") erc20Deployments = filterERC20DeployedEventsByNonce(erc20Deployments, lastClaimEvent.EthereumEventNonce) - log.WithFields(log.Fields{ - "start": startingBlock, - "end": currentBlock, + logger.WithFields(log.Fields{ + "block_start": startingBlock, + "block_end": latestBlock, "erc20_deployments": erc20Deployments, }).Debugln("scanned FilterERC20Deployed events from Ethereum") valsetUpdates = filterValsetUpdateEventsByNonce(valsetUpdates, lastClaimEvent.EthereumEventNonce) - log.WithFields(log.Fields{ - "start": startingBlock, - "end": currentBlock, + logger.WithFields(log.Fields{ + "block_start": startingBlock, + "block_end": latestBlock, "valset_updates": valsetUpdates, }).Debugln("scanned ValsetUpdated events from Ethereum") - if len(legacyDeposits) > 0 || len(deposits) > 0 || len(withdrawals) > 0 || len(erc20Deployments) > 0 || len(valsetUpdates) > 0 { + if len(legacyDeposits) > 0 || + len(deposits) > 0 || + len(withdrawals) > 0 || + len(erc20Deployments) > 0 || + len(valsetUpdates) > 0 { // todo get eth chain id from the chain if err := s.injective.SendEthereumClaims(ctx, lastClaimEvent.EthereumEventNonce, @@ -247,15 +246,16 @@ func (s *PeggyOrchestrator) relayEthEvents( } } - log.WithFields(log.Fields{ - "legacy_deposits": len(legacyDeposits), - "deposits": len(deposits), - "withdrawals": len(withdrawals), - "erc20Deployments": len(erc20Deployments), - "valsetUpdates": len(valsetUpdates), - }).Infoln("sent event claims to Injective") + logger.WithFields(log.Fields{ + "last_confirmed_event_nonce": lastClaimEvent.EthereumEventNonce, + "legacy_deposits": len(legacyDeposits), + "deposits": len(deposits), + "withdrawals": len(withdrawals), + "erc20Deployments": len(erc20Deployments), + "valsetUpdates": len(valsetUpdates), + }).Infoln("sent new claims to Injective") - return currentBlock, nil + return latestBlock, nil } func filterSendToCosmosEventsByNonce( diff --git a/orchestrator/oracle_test.go b/orchestrator/oracle_test.go index 91ce2b7b..4e90886f 100644 --- a/orchestrator/oracle_test.go +++ b/orchestrator/oracle_test.go @@ -27,7 +27,7 @@ func TestRelayEvents(t *testing.T) { }, } - _, err := orch.relayEthEvents(context.TODO(), 0) + _, err := orch.relayEthEvents(context.TODO(), 0, nil) assert.Error(t, err) }) @@ -42,7 +42,7 @@ func TestRelayEvents(t *testing.T) { }, } - currentBlock, err := orch.relayEthEvents(context.TODO(), 100) + currentBlock, err := orch.relayEthEvents(context.TODO(), 100, nil) assert.NoError(t, err) assert.Equal(t, currentBlock, 50-ethBlockConfirmationDelay) }) @@ -61,7 +61,7 @@ func TestRelayEvents(t *testing.T) { }, } - _, err := orch.relayEthEvents(context.TODO(), 100) + _, err := orch.relayEthEvents(context.TODO(), 100, nil) assert.Error(t, err) }) @@ -99,7 +99,7 @@ func TestRelayEvents(t *testing.T) { }, } - _, err := orch.relayEthEvents(context.TODO(), 100) + _, err := orch.relayEthEvents(context.TODO(), 100, nil) assert.Error(t, err) }) @@ -149,7 +149,7 @@ func TestRelayEvents(t *testing.T) { }, } - _, err := orch.relayEthEvents(context.TODO(), 100) + _, err := orch.relayEthEvents(context.TODO(), 100, nil) assert.NoError(t, err) assert.Equal(t, inj.sendEthereumClaimsCallCount, 0) }) @@ -200,7 +200,7 @@ func TestRelayEvents(t *testing.T) { }, } - _, err := orch.relayEthEvents(context.TODO(), 100) + _, err := orch.relayEthEvents(context.TODO(), 100, nil) assert.NoError(t, err) assert.Equal(t, inj.sendEthereumClaimsCallCount, 1) }) diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index 7d1d07f7..95405877 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -19,7 +19,7 @@ import ( ) func (s *PeggyOrchestrator) RelayerMainLoop(ctx context.Context) (err error) { - logger := log.WithField("loop", "RelayerMainLoop") + logger := log.WithField("loop", "Relayer") return loops.RunLoop(ctx, defaultLoopDur, func() error { var pg loops.ParanoidGroup @@ -89,7 +89,7 @@ func (s *PeggyOrchestrator) relayValsets(ctx context.Context, logger log.Logger) } if latestCosmosConfirmed == nil { - log.Debugln("no confirmed valsets found on Injective, nothing to relay...") + logger.Debugln("no confirmed valsets found on Injective, nothing to relay...") return nil } diff --git a/orchestrator/signer.go b/orchestrator/signer.go index 690085c2..27c05f10 100644 --- a/orchestrator/signer.go +++ b/orchestrator/signer.go @@ -16,7 +16,7 @@ import ( // since these are provided directly by a trusted Injective node they can simply be assumed to be // valid and signed off on. func (s *PeggyOrchestrator) EthSignerMainLoop(ctx context.Context) error { - logger := log.WithField("loop", "EthSignerMainLoop") + logger := log.WithField("loop", "EthSigner") peggyID, err := s.getPeggyID(ctx, logger) if err != nil { @@ -97,7 +97,7 @@ func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Lo } for _, vs := range oldestUnsignedValsets { - logger.Infoln("sending confirm for valset %d", vs.Nonce) + logger.Infoln("sending MsgValsetConfirm for valset", vs.Nonce) if err := retry.Do(func() error { return s.injective.SendValsetConfirm(ctx, peggyID, vs, s.ethereum.FromAddress()) }, @@ -145,7 +145,8 @@ func (s *PeggyOrchestrator) signTransactionBatches(ctx context.Context, logger l return nil } - logger.Infoln("sending confirm for batch %d", oldestUnsignedTransactionBatch.BatchNonce) + logger.Infoln("sending MsgConfirmBatch for batch", oldestUnsignedTransactionBatch.BatchNonce) + if err := retry.Do(func() error { return s.injective.SendBatchConfirm(ctx, peggyID, oldestUnsignedTransactionBatch, s.ethereum.FromAddress()) }, retry.Context(ctx), From 9e785361820c82700c571a945255cce1bf17b34a Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Fri, 30 Jun 2023 13:16:15 +0200 Subject: [PATCH 42/72] logging --- cmd/peggo/orchestrator.go | 6 ++-- orchestrator/batch_request.go | 12 ++++--- orchestrator/cosmos/network.go | 2 +- orchestrator/ethereum/network.go | 6 ++-- orchestrator/oracle.go | 55 +++++++++++++++++--------------- orchestrator/orchestrator.go | 38 +++++++--------------- orchestrator/relayer.go | 12 +++---- orchestrator/signer.go | 20 +++++++----- 8 files changed, 76 insertions(+), 75 deletions(-) diff --git a/cmd/peggo/orchestrator.go b/cmd/peggo/orchestrator.go index ba06070e..c1fcfa96 100644 --- a/cmd/peggo/orchestrator.go +++ b/cmd/peggo/orchestrator.go @@ -62,8 +62,10 @@ func orchestratorCmd(cmd *cli.Cmd) { log.WithError(err).Fatalln("failed to initialize Ethereum account") } - log.Infoln("using Injective validator address", valAddress.String()) - log.Infoln("using Ethereum address", ethKeyFromAddress.String()) + log.WithFields(log.Fields{ + "injective_addr": valAddress.String(), + "ethereum_addr": ethKeyFromAddress.String(), + }).Infoln("starting peggo service") // Connect to Injective network injNetwork, err := cosmos.NewNetwork( diff --git a/orchestrator/batch_request.go b/orchestrator/batch_request.go index 91ca11ed..7d0fe766 100644 --- a/orchestrator/batch_request.go +++ b/orchestrator/batch_request.go @@ -23,7 +23,7 @@ func (s *PeggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) return loops.RunLoop(ctx, defaultLoopDur, func() error { mustRequestBatch := false - if isInjectiveRelayer && time.Since(startTime) > time.Hour*8 { + if isInjectiveRelayer && time.Since(startTime) >= time.Hour*8 { mustRequestBatch = true startTime = time.Now() } @@ -43,7 +43,7 @@ func (s *PeggyOrchestrator) requestBatches(ctx context.Context, logger log.Logge } if len(unbatchedTokensWithFees) == 0 { - logger.Debugln("no outgoing withdrawals or minimum batch fee is not met") + logger.WithField("min_fee", s.minBatchFeeUSD).Debugln("no outgoing withdrawals or minimum batch fee is not met") return nil } @@ -51,17 +51,21 @@ func (s *PeggyOrchestrator) requestBatches(ctx context.Context, logger log.Logge for _, unbatchedToken := range unbatchedTokensWithFees { // check if the token is present in cosmos denom. if so, send batch request with cosmosDenom tokenAddr := eth.HexToAddress(unbatchedToken.Token) + denom := s.getTokenDenom(tokenAddr) thresholdMet := s.checkFeeThreshold(tokenAddr, unbatchedToken.TotalFees, s.minBatchFeeUSD) if !thresholdMet && !mustRequest { // non-injective relayers only relay when the threshold is met + logger.WithFields(log.Fields{ + "denom": denom, + "token_contract": tokenAddr.String(), + }).Debugln("skipping batch creation") continue } - denom := s.getTokenDenom(tokenAddr) logger.WithFields(log.Fields{ "denom": denom, - "token_contract": tokenAddr, + "token_contract": tokenAddr.String(), }).Infoln("sending MsgRequestBatch to Injective") _ = s.injective.SendRequestBatch(ctx, denom) diff --git a/orchestrator/cosmos/network.go b/orchestrator/cosmos/network.go index 4f5f9b23..cc019a2a 100644 --- a/orchestrator/cosmos/network.go +++ b/orchestrator/cosmos/network.go @@ -75,7 +75,7 @@ func NewNetwork( log.WithFields(log.Fields{ "chain_id": chainID, - "grpc": injectiveGRPC, + "injective": injectiveGRPC, "tendermint": tendermintRPC, }).Infoln("connected to Injective network") diff --git a/orchestrator/ethereum/network.go b/orchestrator/ethereum/network.go index 3aae420e..2d3b72f0 100644 --- a/orchestrator/ethereum/network.go +++ b/orchestrator/ethereum/network.go @@ -61,14 +61,14 @@ func NewNetwork( } log.WithFields(log.Fields{ - "rpc": ethNodeRPC, - "peggy_contract": peggyContractAddr, + "rpc": ethNodeRPC, + "peggy_contract_addr": peggyContractAddr, }).Infoln("connected to Ethereum network") // If Alchemy Websocket URL is set, then Subscribe to Pending Transaction of Peggy Contract. if ethNodeAlchemyWS != "" { log.WithFields(log.Fields{ - "ws_url": ethNodeAlchemyWS, + "url": ethNodeAlchemyWS, }).Infoln("subscribing to Alchemy websocket") go peggyContract.SubscribeToPendingTxs(ethNodeAlchemyWS) } diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index c689aea1..86618957 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -37,7 +37,7 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { if height == 0 { peggyParams, err := s.injective.PeggyParams(ctx) if err != nil { - logger.WithError(err).Fatalln("failed to query peggy params, is injectived running?") + logger.WithError(err).Fatalln("failed to query peggy module params, is injectived running?") } height = peggyParams.BridgeContractStartHeight } @@ -101,7 +101,10 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { } lastResync = time.Now() - logger.WithFields(log.Fields{"last_resync": lastResync, "last_confirmed_eth_height": lastConfirmedEthHeight}).Infoln("auto resync") + logger.WithFields(log.Fields{ + "last_resync": lastResync, + "last_confirmed_eth_height": lastConfirmedEthHeight, + }).Infoln("auto resync") } return nil @@ -227,32 +230,34 @@ func (s *PeggyOrchestrator) relayEthEvents(ctx context.Context, startingBlock ui "valset_updates": valsetUpdates, }).Debugln("scanned ValsetUpdated events from Ethereum") - if len(legacyDeposits) > 0 || - len(deposits) > 0 || - len(withdrawals) > 0 || - len(erc20Deployments) > 0 || - len(valsetUpdates) > 0 { - // todo get eth chain id from the chain - if err := s.injective.SendEthereumClaims(ctx, - lastClaimEvent.EthereumEventNonce, - legacyDeposits, - deposits, - withdrawals, - erc20Deployments, - valsetUpdates, - ); err != nil { - metrics.ReportFuncError(s.svcTags) - return 0, errors.Wrap(err, "failed to send event claims to Injective") - } + if len(legacyDeposits) == 0 && + len(deposits) == 0 && + len(withdrawals) == 0 && + len(erc20Deployments) == 0 && + len(valsetUpdates) == 0 { + return latestBlock, nil + } + + // todo get eth chain id from the chain + if err := s.injective.SendEthereumClaims(ctx, + lastClaimEvent.EthereumEventNonce, + legacyDeposits, + deposits, + withdrawals, + erc20Deployments, + valsetUpdates, + ); err != nil { + metrics.ReportFuncError(s.svcTags) + return 0, errors.Wrap(err, "failed to send event claims to Injective") } logger.WithFields(log.Fields{ - "last_confirmed_event_nonce": lastClaimEvent.EthereumEventNonce, - "legacy_deposits": len(legacyDeposits), - "deposits": len(deposits), - "withdrawals": len(withdrawals), - "erc20Deployments": len(erc20Deployments), - "valsetUpdates": len(valsetUpdates), + "last_claim_event_nonce": lastClaimEvent.EthereumEventNonce, + "legacy_deposits": len(legacyDeposits), + "deposits": len(deposits), + "withdrawals": len(withdrawals), + "erc20Deployments": len(erc20Deployments), + "valsetUpdates": len(valsetUpdates), }).Infoln("sent new claims to Injective") return latestBlock, nil diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index db02a423..8a4c400f 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -90,21 +90,20 @@ type EthereumNetwork interface { const defaultLoopDur = 60 * time.Second type PeggyOrchestrator struct { - svcTags metrics.Tags + svcTags metrics.Tags + injective InjectiveNetwork ethereum EthereumNetwork pricefeed PriceFeed erc20ContractMapping map[eth.Address]string + relayValsetOffsetDur time.Duration + relayBatchOffsetDur time.Duration minBatchFeeUSD float64 maxAttempts uint // max number of times a retry func will be called before exiting - relayValsetOffsetDur, - relayBatchOffsetDur time.Duration - - valsetRelayEnabled bool - batchRelayEnabled bool - + valsetRelayEnabled bool + batchRelayEnabled bool periodicBatchRequesting bool } @@ -171,18 +170,10 @@ func (s *PeggyOrchestrator) startValidatorMode(ctx context.Context) error { var pg loops.ParanoidGroup - pg.Go(func() error { - return s.EthOracleMainLoop(ctx) - }) - pg.Go(func() error { - return s.BatchRequesterLoop(ctx) - }) - pg.Go(func() error { - return s.EthSignerMainLoop(ctx) - }) - pg.Go(func() error { - return s.RelayerMainLoop(ctx) - }) + pg.Go(func() error { return s.EthOracleMainLoop(ctx) }) + pg.Go(func() error { return s.BatchRequesterLoop(ctx) }) + pg.Go(func() error { return s.EthSignerMainLoop(ctx) }) + pg.Go(func() error { return s.RelayerMainLoop(ctx) }) return pg.Wait() } @@ -195,13 +186,8 @@ func (s *PeggyOrchestrator) startRelayerMode(ctx context.Context) error { var pg loops.ParanoidGroup - pg.Go(func() error { - return s.BatchRequesterLoop(ctx) - }) - - pg.Go(func() error { - return s.RelayerMainLoop(ctx) - }) + pg.Go(func() error { return s.BatchRequesterLoop(ctx) }) + pg.Go(func() error { return s.RelayerMainLoop(ctx) }) return pg.Wait() } diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index 95405877..12dd49bc 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -25,7 +25,7 @@ func (s *PeggyOrchestrator) RelayerMainLoop(ctx context.Context) (err error) { var pg loops.ParanoidGroup if s.valsetRelayEnabled { pg.Go(func() error { - return retry.Do(func() error { return s.relayValsets(ctx, log.WithField("loop", "ValsetRelaying")) }, + return retry.Do(func() error { return s.relayValsets(ctx, logger.WithField("Relayer", "Valsets")) }, retry.Context(ctx), retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { @@ -37,7 +37,7 @@ func (s *PeggyOrchestrator) RelayerMainLoop(ctx context.Context) (err error) { if s.batchRelayEnabled { pg.Go(func() error { - return retry.Do(func() error { return s.relayBatches(ctx, log.WithField("loop", "BatchRelaying")) }, + return retry.Do(func() error { return s.relayBatches(ctx, logger.WithField("Relayer", "Batch")) }, retry.Context(ctx), retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { @@ -102,7 +102,7 @@ func (s *PeggyOrchestrator) relayValsets(ctx context.Context, logger log.Logger) logger.WithFields(log.Fields{ "inj_valset": latestCosmosConfirmed, "eth_valset": currentEthValset, - }).Debugln("found latest valsets") + }).Debugln("latest valsets") if latestCosmosConfirmed.Nonce <= currentEthValset.Nonce { return nil @@ -200,7 +200,7 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context, logger log.Logger) logger.WithFields(log.Fields{ "inj_batch": oldestSignedBatch.BatchNonce, "eth_batch": latestEthereumBatch.Uint64(), - }).Debugln("found latest batches") + }).Debugln("latest batches") if oldestSignedBatch.BatchNonce <= latestEthereumBatch.Uint64() { return nil @@ -271,13 +271,13 @@ func (s *PeggyOrchestrator) findLatestValsetOnEthereum(ctx context.Context, logg latestHeader, err := s.ethereum.HeaderByNumber(ctx, nil) if err != nil { metrics.ReportFuncError(s.svcTags) - return nil, errors.Wrap(err, "failed to get latest header") + return nil, errors.Wrap(err, "failed to get latest eth header") } latestEthereumValsetNonce, err := s.ethereum.GetValsetNonce(ctx) if err != nil { metrics.ReportFuncError(s.svcTags) - return nil, errors.Wrap(err, "failed to get latest valset nonce") + return nil, errors.Wrap(err, "failed to get latest valset nonce on Ethereum") } cosmosValset, err := s.injective.ValsetAt(ctx, latestEthereumValsetNonce.Uint64()) diff --git a/orchestrator/signer.go b/orchestrator/signer.go index 27c05f10..23f1bcd7 100644 --- a/orchestrator/signer.go +++ b/orchestrator/signer.go @@ -89,7 +89,7 @@ func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Lo retry.Context(ctx), retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get unsigned valset, will retry (%d)", n) + logger.WithError(err).Warningf("failed to get unsigned valsets, will retry (%d)", n) }), ); err != nil { logger.WithError(err).Errorln("got error, loop exits") @@ -97,7 +97,6 @@ func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Lo } for _, vs := range oldestUnsignedValsets { - logger.Infoln("sending MsgValsetConfirm for valset", vs.Nonce) if err := retry.Do(func() error { return s.injective.SendValsetConfirm(ctx, peggyID, vs, s.ethereum.FromAddress()) }, @@ -110,6 +109,8 @@ func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Lo logger.WithError(err).Errorln("got error, loop exits") return err } + + logger.WithField("valset_nonce", vs.Nonce).Infoln("sent MsgValsetConfirm to Injective") } return nil @@ -145,18 +146,21 @@ func (s *PeggyOrchestrator) signTransactionBatches(ctx context.Context, logger l return nil } - logger.Infoln("sending MsgConfirmBatch for batch", oldestUnsignedTransactionBatch.BatchNonce) - - if err := retry.Do(func() error { - return s.injective.SendBatchConfirm(ctx, peggyID, oldestUnsignedTransactionBatch, s.ethereum.FromAddress()) - }, retry.Context(ctx), + if err := retry.Do( + func() error { + return s.injective.SendBatchConfirm(ctx, peggyID, oldestUnsignedTransactionBatch, s.ethereum.FromAddress()) + }, + retry.Context(ctx), retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { logger.WithError(err).Warningf("failed to sign and send batch confirmation to Injective, will retry (%d)", n) - })); err != nil { + }), + ); err != nil { logger.WithError(err).Errorln("got error, loop exits") return err } + logger.WithField("batch_nonce", oldestUnsignedTransactionBatch.BatchNonce).Infoln("sent MsgConfirmBatch to Injective") + return nil } From 004730497e2b7a8d511e304ec9eac5d6d41838a1 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Fri, 30 Jun 2023 13:17:16 +0200 Subject: [PATCH 43/72] remove unused code --- cmd/peggo/options.go | 123 ------------------------------------------- 1 file changed, 123 deletions(-) diff --git a/cmd/peggo/options.go b/cmd/peggo/options.go index 4ffabe1b..ed554374 100644 --- a/cmd/peggo/options.go +++ b/cmd/peggo/options.go @@ -137,50 +137,6 @@ func initCosmosKeyOptions( }) } -func initEthereumOptions( - cmd *cli.Cmd, - ethChainID **int, - ethNodeRPC **string, - ethNodeAlchemyWS **string, - ethGasPriceAdjustment **float64, - ethMaxGasPrice **string, -) { - *ethChainID = cmd.Int(cli.IntOpt{ - Name: "eth-chain-id", - Desc: "Specify Chain ID of the Ethereum network.", - EnvVar: "PEGGO_ETH_CHAIN_ID", - Value: 42, - }) - - *ethNodeRPC = cmd.String(cli.StringOpt{ - Name: "eth-node-http", - Desc: "Specify HTTP endpoint for an Ethereum node.", - EnvVar: "PEGGO_ETH_RPC", - Value: "http://localhost:1317", - }) - - *ethNodeAlchemyWS = cmd.String(cli.StringOpt{ - Name: "eth-node-alchemy-ws", - Desc: "Specify websocket url for an Alchemy ethereum node.", - EnvVar: "PEGGO_ETH_ALCHEMY_WS", - Value: "", - }) - - *ethGasPriceAdjustment = cmd.Float64(cli.Float64Opt{ - Name: "eth_gas_price_adjustment", - Desc: "gas price adjustment for Ethereum transactions", - EnvVar: "PEGGO_ETH_GAS_PRICE_ADJUSTMENT", - Value: float64(1.3), - }) - - *ethMaxGasPrice = cmd.String(cli.StringOpt{ - Name: "eth-max-gas-price", - Desc: "Specify Max gas price for Ethereum Transactions in GWei", - EnvVar: "PEGGO_ETH_MAX_GAS_PRICE", - Value: "500gwei", - }) -} - func initEthereumKeyOptions( cmd *cli.Cmd, ethKeystoreDir **string, @@ -274,85 +230,6 @@ func initStatsdOptions( }) } -// initRelayerOption sets options for relayer. -func initRelayerOptions( - cmd *cli.Cmd, - relayValsets **bool, - relayValsetOffsetDur **string, - relayBatches **bool, - relayBatchOffsetDur **string, - pendingTxWaitDuration **string, -) { - *relayValsets = cmd.Bool(cli.BoolOpt{ - Name: "relay_valsets", - Desc: "If enabled, relayer will relay valsets to ethereum", - EnvVar: "PEGGO_RELAY_VALSETS", - Value: false, - }) - - *relayValsetOffsetDur = cmd.String(cli.StringOpt{ - Name: "relay_valset_offset_dur", - Desc: "If set, relayer will broadcast valsetUpdate only after relayValsetOffsetDur has passed from time of valsetUpdate creation", - EnvVar: "PEGGO_RELAY_VALSET_OFFSET_DUR", - Value: "5m", - }) - - *relayBatches = cmd.Bool(cli.BoolOpt{ - Name: "relay_batches", - Desc: "If enabled, relayer will relay batches to ethereum", - EnvVar: "PEGGO_RELAY_BATCHES", - Value: false, - }) - - *relayBatchOffsetDur = cmd.String(cli.StringOpt{ - Name: "relay_batch_offset_dur", - Desc: "If set, relayer will broadcast batches only after relayBatchOffsetDur has passed from time of batch creation", - EnvVar: "PEGGO_RELAY_BATCH_OFFSET_DUR", - Value: "5m", - }) - - *pendingTxWaitDuration = cmd.String(cli.StringOpt{ - Name: "relay_pending_tx_wait_duration", - Desc: "If set, relayer will broadcast pending batches/valsetupdate only after pendingTxWaitDuration has passed", - EnvVar: "PEGGO_RELAY_PENDING_TX_WAIT_DURATION", - Value: "20m", - }) -} - -// initBatchRequesterOptions sets options for batch requester. -func initBatchRequesterOptions( - cmd *cli.Cmd, - minBatchFeeUSD **float64, - periodicBatchRequesting **bool, -) { - *minBatchFeeUSD = cmd.Float64(cli.Float64Opt{ - Name: "min_batch_fee_usd", - Desc: "If set, batch request will create batches only if fee threshold exceeds", - EnvVar: "PEGGO_MIN_BATCH_FEE_USD", - Value: float64(23.3), - }) - - *periodicBatchRequesting = cmd.Bool(cli.BoolOpt{ - Name: "periodic_batch_requesting", - Desc: "If set, batches will be requested every 8 hours regardless of the fee", - EnvVar: "PEGGO_PERIODIC_BATCH_REQUESTING", - Value: false, - }) -} - -// initCoingeckoOptions sets options for coingecko. -func initCoingeckoOptions( - cmd *cli.Cmd, - baseUrl **string, -) { - *baseUrl = cmd.String(cli.StringOpt{ - Name: "coingecko_api", - Desc: "Specify HTTP endpoint for coingecko api.", - EnvVar: "PEGGO_COINGECKO_API", - Value: "https://api.coingecko.com/api/v3", - }) -} - type Config struct { // Cosmos params cosmosChainID *string From 822c215ee54a5572c5983070d95a1aa1ec997e30 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Fri, 7 Jul 2023 11:08:41 +0200 Subject: [PATCH 44/72] improve logging in SendTransactionBatch --- orchestrator/ethereum/peggy/submit_batch.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/orchestrator/ethereum/peggy/submit_batch.go b/orchestrator/ethereum/peggy/submit_batch.go index 29d3aeb2..39f42646 100644 --- a/orchestrator/ethereum/peggy/submit_batch.go +++ b/orchestrator/ethereum/peggy/submit_batch.go @@ -25,8 +25,9 @@ func (s *peggyContract) SendTransactionBatch( log.WithFields(log.Fields{ "token_contract": batch.TokenContract, "new_nonce": batch.BatchNonce, + "confirmations": len(confirms), }).Infoln("Checking signatures and submitting TransactionBatch to Ethereum") - log.Debugf("Batch %#v", batch) + log.Debugf("Batch %s", batch.String()) validators, powers, sigV, sigR, sigS, err := checkBatchSigsAndRepack(currentValset, confirms) if err != nil { From f7c002f088aecaef83ff6180af3e03eeb598847c Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Fri, 7 Jul 2023 11:25:45 +0200 Subject: [PATCH 45/72] logging --- orchestrator/oracle.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index 86618957..a52b1c61 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -57,7 +57,7 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { } return loops.RunLoop(ctx, defaultLoopDur, func() error { - logger.WithField("last_confirmed_eth_height", lastConfirmedEthHeight).Infoln("scanning for events") + logger.WithField("last_confirmed_eth_height", lastConfirmedEthHeight).Infoln("scanning for ethereum events") // Relays events from Ethereum -> Cosmos var currentHeight uint64 From 23308ac9698e01124b712c687b503c948c353ed0 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Fri, 7 Jul 2023 12:32:32 +0200 Subject: [PATCH 46/72] better logging --- cmd/peggo/orchestrator.go | 9 +++++++++ orchestrator/batch_request.go | 2 ++ orchestrator/oracle.go | 2 +- orchestrator/orchestrator.go | 16 ++++++++++++++-- orchestrator/relayer.go | 4 +++- orchestrator/signer.go | 4 ++++ 6 files changed, 33 insertions(+), 4 deletions(-) diff --git a/cmd/peggo/orchestrator.go b/cmd/peggo/orchestrator.go index c1fcfa96..9d9b7763 100644 --- a/cmd/peggo/orchestrator.go +++ b/cmd/peggo/orchestrator.go @@ -2,6 +2,7 @@ package main import ( "context" + "github.com/InjectiveLabs/peggo/orchestrator/version" "os" "time" @@ -33,6 +34,14 @@ func orchestratorCmd(cmd *cli.Cmd) { // ensure a clean exit defer closer.Close() + log.WithFields(log.Fields{ + "version": version.AppVersion, + "git": version.GitCommit, + "build_date": version.BuildDate, + "go_version": version.GoVersion, + "go_arch": version.GoArch, + }).Infoln("peggo - injectived ethereum bridge") + if *cfg.cosmosUseLedger || *cfg.ethUseLedger { log.Fatalln("cannot use Ledger for peggo, since signatures must be realtime") } diff --git a/orchestrator/batch_request.go b/orchestrator/batch_request.go index 7d0fe766..5c7f88c7 100644 --- a/orchestrator/batch_request.go +++ b/orchestrator/batch_request.go @@ -35,6 +35,8 @@ func (s *PeggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) } func (s *PeggyOrchestrator) requestBatches(ctx context.Context, logger log.Logger, mustRequest bool) error { + logger.Infoln("scanning injective for potential batches") + unbatchedTokensWithFees, err := s.getBatchFeesByToken(ctx, logger) if err != nil { // non-fatal, just alert diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index a52b1c61..fa7b5089 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -57,7 +57,7 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { } return loops.RunLoop(ctx, defaultLoopDur, func() error { - logger.WithField("last_confirmed_eth_height", lastConfirmedEthHeight).Infoln("scanning for ethereum events") + logger.WithField("last_confirmed_eth_height", lastConfirmedEthHeight).Infoln("scanning ethereum for events") // Relays events from Ethereum -> Cosmos var currentHeight uint64 diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index 8a4c400f..dd724cde 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -166,7 +166,13 @@ func (s *PeggyOrchestrator) Run(ctx context.Context, validatorMode bool) error { // startValidatorMode runs all orchestrator processes. This is called // when peggo is run alongside a validator injective node. func (s *PeggyOrchestrator) startValidatorMode(ctx context.Context) error { - log.Infoln("running peggo in validator mode") + log.WithFields(log.Fields{ + "BatchRequesterEnabled": true, + "EthOracleEnabled": true, + "EthSignerEnabled": true, + "ValsetRelayerEnabled": s.valsetRelayEnabled, + "BatchRelayerEnabled": s.batchRelayEnabled, + }).Infoln("running in validator mode") var pg loops.ParanoidGroup @@ -182,7 +188,13 @@ func (s *PeggyOrchestrator) startValidatorMode(ctx context.Context) error { // messages that do not require a validator's signature. This mode is run // alongside a non-validator injective node func (s *PeggyOrchestrator) startRelayerMode(ctx context.Context) error { - log.Infoln("running peggo in relayer mode") + log.WithFields(log.Fields{ + "BatchRequesterEnabled": true, + "EthOracleEnabled": false, + "EthSignerEnabled": false, + "ValsetRelayerEnabled": s.valsetRelayEnabled, + "BatchRelayerEnabled": s.batchRelayEnabled, + }).Infoln("running in relayer mode") var pg loops.ParanoidGroup diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index 12dd49bc..5033f7e1 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -24,8 +24,9 @@ func (s *PeggyOrchestrator) RelayerMainLoop(ctx context.Context) (err error) { return loops.RunLoop(ctx, defaultLoopDur, func() error { var pg loops.ParanoidGroup if s.valsetRelayEnabled { + logger.Infoln("scanning injective for confirmed valset updates") pg.Go(func() error { - return retry.Do(func() error { return s.relayValsets(ctx, logger.WithField("Relayer", "Valsets")) }, + return retry.Do(func() error { return s.relayValsets(ctx, logger.WithField("Relayer", "Valset")) }, retry.Context(ctx), retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { @@ -36,6 +37,7 @@ func (s *PeggyOrchestrator) RelayerMainLoop(ctx context.Context) (err error) { } if s.batchRelayEnabled { + logger.Infoln("scanning injective for confirmed batches") pg.Go(func() error { return retry.Do(func() error { return s.relayBatches(ctx, logger.WithField("Relayer", "Batch")) }, retry.Context(ctx), diff --git a/orchestrator/signer.go b/orchestrator/signer.go index 23f1bcd7..7971314f 100644 --- a/orchestrator/signer.go +++ b/orchestrator/signer.go @@ -69,6 +69,8 @@ func (s *PeggyOrchestrator) signerLoop(ctx context.Context, logger log.Logger, p } func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Logger, peggyID common.Hash) error { + logger.Infoln("scanning injective for unsigned valset updates") + var oldestUnsignedValsets []*types.Valset retryFn := func() error { oldestValsets, err := s.injective.OldestUnsignedValsets(ctx) @@ -117,6 +119,8 @@ func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Lo } func (s *PeggyOrchestrator) signTransactionBatches(ctx context.Context, logger log.Logger, peggyID common.Hash) error { + logger.Infoln("scanning injective for unsigned batches") + var oldestUnsignedTransactionBatch *types.OutgoingTxBatch retryFn := func() error { // sign the last unsigned batch, TODO check if we already have signed this From a20203d5d9c6020558881bfaac1db709a86e6eb7 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Fri, 7 Jul 2023 12:46:25 +0200 Subject: [PATCH 47/72] add log --- orchestrator/batch_request.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orchestrator/batch_request.go b/orchestrator/batch_request.go index 5c7f88c7..7ebf086b 100644 --- a/orchestrator/batch_request.go +++ b/orchestrator/batch_request.go @@ -35,7 +35,7 @@ func (s *PeggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) } func (s *PeggyOrchestrator) requestBatches(ctx context.Context, logger log.Logger, mustRequest bool) error { - logger.Infoln("scanning injective for potential batches") + logger.WithField("min_batch_fee", s.minBatchFeeUSD).Infoln("scanning injective for potential batches") unbatchedTokensWithFees, err := s.getBatchFeesByToken(ctx, logger) if err != nil { From 5a766585486502951141648439d21637981b4a16 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Fri, 7 Jul 2023 15:06:03 +0200 Subject: [PATCH 48/72] logging --- orchestrator/oracle.go | 4 ++-- orchestrator/relayer.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index fa7b5089..d0d22772 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -57,7 +57,7 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { } return loops.RunLoop(ctx, defaultLoopDur, func() error { - logger.WithField("last_confirmed_eth_height", lastConfirmedEthHeight).Infoln("scanning ethereum for events") + logger.WithField("last_confirmed_eth_height", lastConfirmedEthHeight).Infoln("scanning Ethereum for events") // Relays events from Ethereum -> Cosmos var currentHeight uint64 @@ -68,7 +68,7 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { retry.Context(ctx), retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("error during Eth event checking, will retry (%d)", n) + logger.WithError(err).Warningf("error during Ethereum event checking, will retry (%d)", n) }), ); err != nil { logger.WithError(err).Errorln("got error, loop exits") diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index 5033f7e1..c7847253 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -137,7 +137,7 @@ func (s *PeggyOrchestrator) relayValsets(ctx context.Context, logger log.Logger) logger.WithFields(log.Fields{ "inj_valset": latestCosmosConfirmed.Nonce, "eth_valset": latestEthereumValsetNonce.Uint64(), - }).Infoln("detected new valset on Injective. Sending update to Ethereum...") + }).Infoln("detected new valset on Injective") txHash, err := s.ethereum.SendEthValsetUpdate( ctx, @@ -244,7 +244,7 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context, logger log.Logger) logger.WithFields(log.Fields{ "inj_batch": oldestSignedBatch.BatchNonce, "eth_batch": latestEthereumBatch.Uint64(), - }).Infoln("detected new transaction batch on Injective. Sending update to Ethereum...") + }).Infoln("detected new transaction batch on Injective") // Send SendTransactionBatch to Ethereum txHash, err := s.ethereum.SendTransactionBatch(ctx, currentValset, oldestSignedBatch, oldestSigs) From dc63b5fb5c0cbe7a450d7b3251feb1d4c8f210d7 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Fri, 7 Jul 2023 15:07:51 +0200 Subject: [PATCH 49/72] fix log --- orchestrator/batch_request.go | 2 +- orchestrator/relayer.go | 4 ++-- orchestrator/signer.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/orchestrator/batch_request.go b/orchestrator/batch_request.go index 7ebf086b..b7709d85 100644 --- a/orchestrator/batch_request.go +++ b/orchestrator/batch_request.go @@ -35,7 +35,7 @@ func (s *PeggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) } func (s *PeggyOrchestrator) requestBatches(ctx context.Context, logger log.Logger, mustRequest bool) error { - logger.WithField("min_batch_fee", s.minBatchFeeUSD).Infoln("scanning injective for potential batches") + logger.WithField("min_batch_fee", s.minBatchFeeUSD).Infoln("scanning Injective for potential batches") unbatchedTokensWithFees, err := s.getBatchFeesByToken(ctx, logger) if err != nil { diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index c7847253..778b8b6a 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -24,7 +24,7 @@ func (s *PeggyOrchestrator) RelayerMainLoop(ctx context.Context) (err error) { return loops.RunLoop(ctx, defaultLoopDur, func() error { var pg loops.ParanoidGroup if s.valsetRelayEnabled { - logger.Infoln("scanning injective for confirmed valset updates") + logger.Infoln("scanning Injective for confirmed valset updates") pg.Go(func() error { return retry.Do(func() error { return s.relayValsets(ctx, logger.WithField("Relayer", "Valset")) }, retry.Context(ctx), @@ -37,7 +37,7 @@ func (s *PeggyOrchestrator) RelayerMainLoop(ctx context.Context) (err error) { } if s.batchRelayEnabled { - logger.Infoln("scanning injective for confirmed batches") + logger.Infoln("scanning Injective for confirmed batches") pg.Go(func() error { return retry.Do(func() error { return s.relayBatches(ctx, logger.WithField("Relayer", "Batch")) }, retry.Context(ctx), diff --git a/orchestrator/signer.go b/orchestrator/signer.go index 7971314f..0829ce0f 100644 --- a/orchestrator/signer.go +++ b/orchestrator/signer.go @@ -69,7 +69,7 @@ func (s *PeggyOrchestrator) signerLoop(ctx context.Context, logger log.Logger, p } func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Logger, peggyID common.Hash) error { - logger.Infoln("scanning injective for unsigned valset updates") + logger.Infoln("scanning Injective for unsigned valset updates") var oldestUnsignedValsets []*types.Valset retryFn := func() error { @@ -119,7 +119,7 @@ func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Lo } func (s *PeggyOrchestrator) signTransactionBatches(ctx context.Context, logger log.Logger, peggyID common.Hash) error { - logger.Infoln("scanning injective for unsigned batches") + logger.Infoln("scanning Injective for unsigned batches") var oldestUnsignedTransactionBatch *types.OutgoingTxBatch retryFn := func() error { From cdbab8ee23372dc42cbadffdf5aef3e95a436ab3 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Fri, 7 Jul 2023 15:11:29 +0200 Subject: [PATCH 50/72] fix log --- orchestrator/signer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/orchestrator/signer.go b/orchestrator/signer.go index 0829ce0f..82ecdb11 100644 --- a/orchestrator/signer.go +++ b/orchestrator/signer.go @@ -69,7 +69,7 @@ func (s *PeggyOrchestrator) signerLoop(ctx context.Context, logger log.Logger, p } func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Logger, peggyID common.Hash) error { - logger.Infoln("scanning Injective for unsigned valset updates") + logger.Infoln("scanning Injective for new valset updates") var oldestUnsignedValsets []*types.Valset retryFn := func() error { @@ -119,7 +119,7 @@ func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Lo } func (s *PeggyOrchestrator) signTransactionBatches(ctx context.Context, logger log.Logger, peggyID common.Hash) error { - logger.Infoln("scanning Injective for unsigned batches") + logger.Infoln("scanning Injective for new batches") var oldestUnsignedTransactionBatch *types.OutgoingTxBatch retryFn := func() error { From 2c37f26e5c3942b63f4a6a11c5b6852b43305e2e Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Fri, 7 Jul 2023 16:09:20 +0200 Subject: [PATCH 51/72] refactor signer loop --- orchestrator/signer.go | 209 ++++++++++++++++++++++-------------- orchestrator/signer_test.go | 160 ++++++++++++--------------- 2 files changed, 196 insertions(+), 173 deletions(-) diff --git a/orchestrator/signer.go b/orchestrator/signer.go index 82ecdb11..b8063c9c 100644 --- a/orchestrator/signer.go +++ b/orchestrator/signer.go @@ -16,155 +16,200 @@ import ( // since these are provided directly by a trusted Injective node they can simply be assumed to be // valid and signed off on. func (s *PeggyOrchestrator) EthSignerMainLoop(ctx context.Context) error { - logger := log.WithField("loop", "EthSigner") - - peggyID, err := s.getPeggyID(ctx, logger) + peggyID, err := s.getPeggyID(ctx) if err != nil { return err } - return loops.RunLoop(ctx, defaultLoopDur, func() error { - return s.signerLoop(ctx, logger, peggyID) - }) + signer := ðSigner{ + log: log.WithField("loop", "EthSigner"), + peggyID: peggyID, + ethFrom: s.ethereum.FromAddress(), + retries: s.maxAttempts, + } + + return loops.RunLoop( + ctx, + defaultLoopDur, + func() error { return signer.run(ctx, s.injective) }, + ) } -func (s *PeggyOrchestrator) getPeggyID(ctx context.Context, logger log.Logger) (common.Hash, error) { +func (s *PeggyOrchestrator) getPeggyID(ctx context.Context) (common.Hash, error) { var peggyID common.Hash - retryFn := func() error { - id, err := s.ethereum.GetPeggyID(ctx) - if err != nil { - return err - } - - peggyID = id - return nil + retryFn := func() (err error) { + peggyID, err = s.ethereum.GetPeggyID(ctx) + return err } if err := retry.Do(retryFn, retry.Context(ctx), retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get peggy ID from Ethereum contract, will retry (%d)", n) + log.WithError(err).Warningf("failed to get peggy ID from Ethereum contract, will retry (%d)", n) }), ); err != nil { - logger.WithError(err).Errorln("got error, loop exits") + log.WithError(err).Errorln("got error, loop exits") return [32]byte{}, err } - logger.WithField("id", peggyID.Hex()).Debugln("got peggy ID from Ethereum contract") + log.WithField("id", peggyID.Hex()).Debugln("got peggy ID from Ethereum contract") return peggyID, nil } -func (s *PeggyOrchestrator) signerLoop(ctx context.Context, logger log.Logger, peggyID common.Hash) error { - if err := s.signValsetUpdates(ctx, logger, peggyID); err != nil { +type ethSigner struct { + log log.Logger + peggyID common.Hash + ethFrom common.Address + retries uint +} + +func (s *ethSigner) run(ctx context.Context, injective InjectiveNetwork) error { + if err := s.signNewValsetUpdates(ctx, injective); err != nil { return err } - if err := s.signTransactionBatches(ctx, logger, peggyID); err != nil { + if err := s.signNewBatches(ctx, injective); err != nil { return err } return nil } -func (s *PeggyOrchestrator) signValsetUpdates(ctx context.Context, logger log.Logger, peggyID common.Hash) error { - logger.Infoln("scanning Injective for new valset updates") +func (s *ethSigner) signNewBatches(ctx context.Context, injective InjectiveNetwork) error { + s.log.Infoln("scanning Injective for new batches") - var oldestUnsignedValsets []*types.Valset - retryFn := func() error { - oldestValsets, err := s.injective.OldestUnsignedValsets(ctx) - if err != nil { - if err == cosmos.ErrNotFound || oldestValsets == nil { - logger.Debugln("no new valset waiting to be signed") - return nil - } - - // dusan: this will never really trigger - return err - } - oldestUnsignedValsets = oldestValsets + oldestUnsignedTransactionBatch, err := s.getUnsignedBatch(ctx, injective) + if err != nil { + return err + } + + if oldestUnsignedTransactionBatch == nil { + s.log.Debugln("no new transaction batch waiting to be signed") return nil } + if err := s.signBatch(ctx, injective, oldestUnsignedTransactionBatch); err != nil { + return err + } + + return nil +} + +func (s *ethSigner) getUnsignedBatch(ctx context.Context, injective InjectiveNetwork) (*types.OutgoingTxBatch, error) { + var oldestUnsignedTransactionBatch *types.OutgoingTxBatch + retryFn := func() (err error) { + // sign the last unsigned batch, TODO check if we already have signed this + oldestUnsignedTransactionBatch, err = injective.OldestUnsignedTransactionBatch(ctx) + if err == cosmos.ErrNotFound || oldestUnsignedTransactionBatch == nil { + return nil + } + + return err + } + if err := retry.Do(retryFn, retry.Context(ctx), - retry.Attempts(s.maxAttempts), + retry.Attempts(s.retries), + retry.OnRetry(func(n uint, err error) { + s.log.WithError(err).Warningf("failed to get unsigned transaction batch, will retry (%d)", n) + })); err != nil { + s.log.WithError(err).Errorln("got error, loop exits") + return nil, err + } + + return oldestUnsignedTransactionBatch, nil +} + +func (s *ethSigner) signBatch( + ctx context.Context, + injective InjectiveNetwork, + batch *types.OutgoingTxBatch, +) error { + if err := retry.Do(func() error { return injective.SendBatchConfirm(ctx, s.peggyID, batch, s.ethFrom) }, + retry.Context(ctx), + retry.Attempts(s.retries), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get unsigned valsets, will retry (%d)", n) + s.log.WithError(err).Warningf("failed to sign and send batch confirmation to Injective, will retry (%d)", n) }), ); err != nil { - logger.WithError(err).Errorln("got error, loop exits") + s.log.WithError(err).Errorln("got error, loop exits") + return err + } + + s.log.WithField("batch_nonce", batch.BatchNonce).Infoln("confirmed batch on Injective") + + return nil +} + +func (s *ethSigner) signNewValsetUpdates( + ctx context.Context, + injective InjectiveNetwork, +) error { + s.log.Infoln("scanning Injective for new valset updates") + + oldestUnsignedValsets, err := s.getUnsignedValsets(ctx, injective) + if err != nil { return err } + if len(oldestUnsignedValsets) == 0 { + s.log.Debugln("no new valset updates waiting to be signed") + return nil + } + for _, vs := range oldestUnsignedValsets { - if err := retry.Do(func() error { - return s.injective.SendValsetConfirm(ctx, peggyID, vs, s.ethereum.FromAddress()) - }, - retry.Context(ctx), - retry.Attempts(s.maxAttempts), - retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to sign and send valset confirmation to Injective, will retry (%d)", n) - }), - ); err != nil { - logger.WithError(err).Errorln("got error, loop exits") + if err := s.signValset(ctx, injective, vs); err != nil { return err } - - logger.WithField("valset_nonce", vs.Nonce).Infoln("sent MsgValsetConfirm to Injective") } return nil } -func (s *PeggyOrchestrator) signTransactionBatches(ctx context.Context, logger log.Logger, peggyID common.Hash) error { - logger.Infoln("scanning Injective for new batches") - - var oldestUnsignedTransactionBatch *types.OutgoingTxBatch - retryFn := func() error { - // sign the last unsigned batch, TODO check if we already have signed this - txBatch, err := s.injective.OldestUnsignedTransactionBatch(ctx) - if err != nil { - if err == cosmos.ErrNotFound || txBatch == nil { - logger.Debugln("no new transaction batch waiting to be signed") - return nil - } - return err +func (s *ethSigner) getUnsignedValsets(ctx context.Context, injective InjectiveNetwork) ([]*types.Valset, error) { + var oldestUnsignedValsets []*types.Valset + retryFn := func() (err error) { + oldestUnsignedValsets, err = injective.OldestUnsignedValsets(ctx) + if err == cosmos.ErrNotFound || oldestUnsignedValsets == nil { + return nil } - oldestUnsignedTransactionBatch = txBatch - return nil + + return err } if err := retry.Do(retryFn, retry.Context(ctx), - retry.Attempts(s.maxAttempts), + retry.Attempts(s.retries), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get unsigned transaction batch, will retry (%d)", n) - })); err != nil { - logger.WithError(err).Errorln("got error, loop exits") - return err + s.log.WithError(err).Warningf("failed to get unsigned valsets, will retry (%d)", n) + }), + ); err != nil { + s.log.WithError(err).Errorln("got error, loop exits") + return nil, err } - if oldestUnsignedTransactionBatch == nil { - return nil - } + return oldestUnsignedValsets, nil +} - if err := retry.Do( - func() error { - return s.injective.SendBatchConfirm(ctx, peggyID, oldestUnsignedTransactionBatch, s.ethereum.FromAddress()) - }, +func (s *ethSigner) signValset( + ctx context.Context, + injective InjectiveNetwork, + vs *types.Valset, +) error { + if err := retry.Do(func() error { return injective.SendValsetConfirm(ctx, s.peggyID, vs, s.ethFrom) }, retry.Context(ctx), - retry.Attempts(s.maxAttempts), + retry.Attempts(s.retries), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to sign and send batch confirmation to Injective, will retry (%d)", n) + s.log.WithError(err).Warningf("failed to sign and send valset confirmation to Injective, will retry (%d)", n) }), ); err != nil { - logger.WithError(err).Errorln("got error, loop exits") + s.log.WithError(err).Errorln("got error, loop exits") return err } - logger.WithField("batch_nonce", oldestUnsignedTransactionBatch.BatchNonce).Infoln("sent MsgConfirmBatch to Injective") + s.log.WithField("valset_nonce", vs.Nonce).Infoln("confirmed valset on Injective") return nil } diff --git a/orchestrator/signer_test.go b/orchestrator/signer_test.go index 1a0f856e..c0c8bd05 100644 --- a/orchestrator/signer_test.go +++ b/orchestrator/signer_test.go @@ -2,13 +2,15 @@ package orchestrator import ( "context" - "github.com/InjectiveLabs/sdk-go/chain/peggy/types" - cosmtypes "github.com/cosmos/cosmos-sdk/types" + "testing" + "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" "github.com/stretchr/testify/assert" - "github.com/xlab/suplog" - "testing" + log "github.com/xlab/suplog" + + "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + cosmtypes "github.com/cosmos/cosmos-sdk/types" ) func TestEthSignerLoop(t *testing.T) { @@ -18,7 +20,7 @@ func TestEthSignerLoop(t *testing.T) { t.Parallel() orch := &PeggyOrchestrator{ - maxAttempts: 1, // todo, hardcode do 10 + maxAttempts: 1, ethereum: mockEthereum{ getPeggyIDFn: func(context.Context) (common.Hash, error) { return [32]byte{}, errors.New("fail") @@ -32,130 +34,106 @@ func TestEthSignerLoop(t *testing.T) { t.Run("no valset to sign", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - maxAttempts: 1, - injective: &mockInjective{ - oldestUnsignedValsetsFn: func(context.Context) ([]*types.Valset, error) { - return nil, errors.New("fail") - }, - sendValsetConfirmFn: func(context.Context, common.Hash, *types.Valset, common.Address) error { - return nil - }, - oldestUnsignedTransactionBatchFn: func(context.Context) (*types.OutgoingTxBatch, error) { - return nil, nil - }, - sendBatchConfirmFn: func(context.Context, common.Hash, *types.OutgoingTxBatch, common.Address) error { - return nil - }, + injective := &mockInjective{ + oldestUnsignedValsetsFn: func(context.Context) ([]*types.Valset, error) { + return nil, errors.New("fail") + }, + sendValsetConfirmFn: func(context.Context, common.Hash, *types.Valset, common.Address) error { + return nil + }, + oldestUnsignedTransactionBatchFn: func(context.Context) (*types.OutgoingTxBatch, error) { + return nil, nil + }, + sendBatchConfirmFn: func(context.Context, common.Hash, *types.OutgoingTxBatch, common.Address) error { + return nil }, } - assert.NoError(t, orch.signerLoop(context.TODO(), suplog.DefaultLogger, [32]byte{1, 2, 3})) + sig := ðSigner{log: log.DefaultLogger, retries: 1} + + assert.NoError(t, sig.run(context.TODO(), injective)) }) t.Run("failed to send valset confirm", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - maxAttempts: 1, - injective: &mockInjective{ - oldestUnsignedValsetsFn: func(context.Context) ([]*types.Valset, error) { - return []*types.Valset{ - { - Nonce: 5, - Members: []*types.BridgeValidator{ - { - Power: 100, - EthereumAddress: "abcd", - }, + injective := &mockInjective{ + oldestUnsignedValsetsFn: func(context.Context) ([]*types.Valset, error) { + return []*types.Valset{ + { + Nonce: 5, + Members: []*types.BridgeValidator{ + { + Power: 100, + EthereumAddress: "abcd", }, - Height: 500, - RewardAmount: cosmtypes.NewInt(123), - RewardToken: "dusanToken", }, - }, nil - }, - sendValsetConfirmFn: func(context.Context, common.Hash, *types.Valset, common.Address) error { - return errors.New("fail") - }, + Height: 500, + RewardAmount: cosmtypes.NewInt(123), + RewardToken: "dusanToken", + }, + }, nil }, - ethereum: mockEthereum{ - fromAddressFn: func() common.Address { - return common.Address{} - }, + sendValsetConfirmFn: func(context.Context, common.Hash, *types.Valset, common.Address) error { + return errors.New("fail") }, } - err := orch.signerLoop(context.TODO(), suplog.DefaultLogger, [32]byte{1, 2, 3}) - assert.Error(t, err) + sig := ðSigner{log: log.DefaultLogger, retries: 1} + + assert.Error(t, sig.run(context.TODO(), injective)) }) t.Run("no transaction batch sign", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - maxAttempts: 1, - injective: &mockInjective{ - oldestUnsignedValsetsFn: func(_ context.Context) ([]*types.Valset, error) { return nil, nil }, - sendValsetConfirmFn: func(context.Context, common.Hash, *types.Valset, common.Address) error { return nil }, - oldestUnsignedTransactionBatchFn: func(_ context.Context) (*types.OutgoingTxBatch, error) { return nil, errors.New("fail") }, - sendBatchConfirmFn: func(context.Context, common.Hash, *types.OutgoingTxBatch, common.Address) error { return nil }, - }, + injective := &mockInjective{ + oldestUnsignedValsetsFn: func(_ context.Context) ([]*types.Valset, error) { return nil, nil }, + sendValsetConfirmFn: func(context.Context, common.Hash, *types.Valset, common.Address) error { return nil }, + oldestUnsignedTransactionBatchFn: func(_ context.Context) (*types.OutgoingTxBatch, error) { return nil, errors.New("fail") }, + sendBatchConfirmFn: func(context.Context, common.Hash, *types.OutgoingTxBatch, common.Address) error { return nil }, } - err := orch.signerLoop(context.TODO(), suplog.DefaultLogger, [32]byte{}) - assert.NoError(t, err) + sig := ðSigner{log: log.DefaultLogger, retries: 1} + + assert.NoError(t, sig.run(context.TODO(), injective)) }) t.Run("failed to send batch confirm", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - maxAttempts: 1, - injective: &mockInjective{ - oldestUnsignedValsetsFn: func(_ context.Context) ([]*types.Valset, error) { return nil, nil }, - sendValsetConfirmFn: func(context.Context, common.Hash, *types.Valset, common.Address) error { return nil }, - oldestUnsignedTransactionBatchFn: func(_ context.Context) (*types.OutgoingTxBatch, error) { - return &types.OutgoingTxBatch{}, nil // non-empty will do - }, - sendBatchConfirmFn: func(context.Context, common.Hash, *types.OutgoingTxBatch, common.Address) error { - return errors.New("fail") - }, + injective := &mockInjective{ + oldestUnsignedValsetsFn: func(_ context.Context) ([]*types.Valset, error) { return nil, nil }, + sendValsetConfirmFn: func(context.Context, common.Hash, *types.Valset, common.Address) error { return nil }, + oldestUnsignedTransactionBatchFn: func(_ context.Context) (*types.OutgoingTxBatch, error) { + return &types.OutgoingTxBatch{}, nil // non-empty will do }, - ethereum: mockEthereum{ - fromAddressFn: func() common.Address { - return common.Address{} - }, + sendBatchConfirmFn: func(context.Context, common.Hash, *types.OutgoingTxBatch, common.Address) error { + return errors.New("fail") }, } - err := orch.signerLoop(context.TODO(), suplog.DefaultLogger, [32]byte{}) - assert.Error(t, err) + sig := ðSigner{log: log.DefaultLogger, retries: 1} + + assert.Error(t, sig.run(context.TODO(), injective)) }) t.Run("valset update and transaction batch are confirmed", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - maxAttempts: 1, - injective: &mockInjective{ - oldestUnsignedValsetsFn: func(_ context.Context) ([]*types.Valset, error) { - return []*types.Valset{}, nil // non-empty will do - }, - oldestUnsignedTransactionBatchFn: func(_ context.Context) (*types.OutgoingTxBatch, error) { - return &types.OutgoingTxBatch{}, nil // non-empty will do - }, - sendValsetConfirmFn: func(context.Context, common.Hash, *types.Valset, common.Address) error { return nil }, - sendBatchConfirmFn: func(context.Context, common.Hash, *types.OutgoingTxBatch, common.Address) error { return nil }, + injective := &mockInjective{ + oldestUnsignedValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{}, nil // non-empty will do }, - ethereum: mockEthereum{ - fromAddressFn: func() common.Address { - return common.Address{} - }, + oldestUnsignedTransactionBatchFn: func(_ context.Context) (*types.OutgoingTxBatch, error) { + return &types.OutgoingTxBatch{}, nil // non-empty will do }, + sendValsetConfirmFn: func(context.Context, common.Hash, *types.Valset, common.Address) error { return nil }, + sendBatchConfirmFn: func(context.Context, common.Hash, *types.OutgoingTxBatch, common.Address) error { return nil }, } - err := orch.signerLoop(context.TODO(), suplog.DefaultLogger, [32]byte{}) - assert.NoError(t, err) + sig := ðSigner{log: log.DefaultLogger, retries: 1} + + assert.NoError(t, sig.run(context.TODO(), injective)) }) } From e0fd4771b09b06cf88a6885e5c0ed4e92ebad4d1 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Mon, 10 Jul 2023 11:10:01 +0200 Subject: [PATCH 52/72] refactor batch requester and revert batch relay flow to original --- orchestrator/batch_request.go | 140 +++++++++++++++++++++++++---- orchestrator/batch_request_test.go | 126 +++++++++++--------------- orchestrator/relayer.go | 18 ++-- 3 files changed, 183 insertions(+), 101 deletions(-) diff --git a/orchestrator/batch_request.go b/orchestrator/batch_request.go index b7709d85..f6c629bf 100644 --- a/orchestrator/batch_request.go +++ b/orchestrator/batch_request.go @@ -2,8 +2,6 @@ package orchestrator import ( "context" - "time" - "github.com/avast/retry-go" eth "github.com/ethereum/go-ethereum/common" "github.com/shopspring/decimal" @@ -15,23 +13,133 @@ import ( ) func (s *PeggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) { - logger := log.WithField("loop", "BatchRequester") - startTime := time.Now() + requester := &batchRequester{ + log: log.WithField("loop", "BatchRequester"), + retries: s.maxAttempts, + minBatchFee: s.minBatchFeeUSD, + erc20ContractMapping: s.erc20ContractMapping, + } - // we're the only ones relaying - isInjectiveRelayer := s.periodicBatchRequesting + return loops.RunLoop( + ctx, + defaultLoopDur, + func() error { return requester.run(ctx, s.injective, s.pricefeed) }, + ) +} - return loops.RunLoop(ctx, defaultLoopDur, func() error { - mustRequestBatch := false - if isInjectiveRelayer && time.Since(startTime) >= time.Hour*8 { - mustRequestBatch = true - startTime = time.Now() - } +type batchRequester struct { + log log.Logger + retries uint + minBatchFee float64 + erc20ContractMapping map[eth.Address]string +} + +func (r *batchRequester) run( + ctx context.Context, + injective InjectiveNetwork, + feed PriceFeed, +) error { + r.log.WithField("min_batch_fee", r.minBatchFee).Infoln("scanning Injective for potential batches") + + unbatchedTokensWithFees, err := r.getBatchFeesByToken(ctx, injective) + if err != nil { + // non-fatal, just alert + r.log.WithError(err).Warningln("unable to get unbatched fees from Injective") + return nil + } + + if len(unbatchedTokensWithFees) == 0 { + r.log.Debugln("no outgoing withdrawals or minimum batch fee is not met") + return nil + } + + for _, unbatchedToken := range unbatchedTokensWithFees { + r.requestBatchCreation(ctx, injective, feed, unbatchedToken) + } + + return nil +} + +func (r *batchRequester) getBatchFeesByToken(ctx context.Context, injective InjectiveNetwork) ([]*types.BatchFees, error) { + var unbatchedTokensWithFees []*types.BatchFees + retryFn := func() (err error) { + unbatchedTokensWithFees, err = injective.UnbatchedTokenFees(ctx) + return err + } + + if err := retry.Do(retryFn, + retry.Context(ctx), + retry.Attempts(r.retries), + retry.OnRetry(func(n uint, err error) { + log.WithError(err).Errorf("failed to get unbatched fees, will retry (%d)", n) + }), + ); err != nil { + return nil, err + } + + return unbatchedTokensWithFees, nil +} + +func (r *batchRequester) requestBatchCreation( + ctx context.Context, + injective InjectiveNetwork, + feed PriceFeed, + batchFee *types.BatchFees, +) { + var ( + tokenAddr = eth.HexToAddress(batchFee.Token) + denom = r.tokenDenom(tokenAddr) + ) + + if thresholdMet := r.checkFeeThreshold(feed, tokenAddr, batchFee.TotalFees); !thresholdMet { + r.log.WithFields(log.Fields{ + "denom": denom, + "token_contract": tokenAddr.String(), + "total_fees": batchFee.TotalFees.String(), + }).Debugln("skipping underpriced batch") + return + } + + r.log.WithFields(log.Fields{ + "denom": denom, + "token_contract": tokenAddr.String(), + }).Infoln("requesting batch creation on Injective") + + _ = injective.SendRequestBatch(ctx, denom) +} + +func (r *batchRequester) tokenDenom(tokenAddr eth.Address) string { + if cosmosDenom, ok := r.erc20ContractMapping[tokenAddr]; ok { + return cosmosDenom + } + + // peggy denom + return types.PeggyDenomString(tokenAddr) +} + +func (r *batchRequester) checkFeeThreshold( + feed PriceFeed, + tokenAddr eth.Address, + totalFees cosmtypes.Int, +) bool { + if r.minBatchFee == 0 { + return true + } + + tokenPriceInUSD, err := feed.QueryUSDPrice(tokenAddr) + if err != nil { + return false + } + + tokenPriceInUSDDec := decimal.NewFromFloat(tokenPriceInUSD) + totalFeeInUSDDec := decimal.NewFromBigInt(totalFees.BigInt(), -18).Mul(tokenPriceInUSDDec) + minFeeInUSDDec := decimal.NewFromFloat(r.minBatchFee) + + if totalFeeInUSDDec.GreaterThan(minFeeInUSDDec) { + return true + } - var pg loops.ParanoidGroup - pg.Go(func() error { return s.requestBatches(ctx, logger, mustRequestBatch) }) - return pg.Wait() - }) + return false } func (s *PeggyOrchestrator) requestBatches(ctx context.Context, logger log.Logger, mustRequest bool) error { diff --git a/orchestrator/batch_request_test.go b/orchestrator/batch_request_test.go index df38f2e1..eed250fb 100644 --- a/orchestrator/batch_request_test.go +++ b/orchestrator/batch_request_test.go @@ -13,135 +13,109 @@ import ( cosmtypes "github.com/cosmos/cosmos-sdk/types" ) -func TestLogger(t *testing.T) { - err := errors.New("dusan error") - err2 := errors.New("another dusan error") - - logger := suplog.WithError(err).WithError(errors.New("wqerwerw d")) - - suplog.Infoln("random info line") - suplog.WithFields(suplog.Fields{"field1": 42}).Infoln("info line with field") - - logger.Errorln("descriptive error line") - logger.WithError(err2).Errorln("descriptive error line 2") - - logger = suplog.WithField("dusan", "dusan value") - logger.Errorln("this is an error line") - logger.Infoln("this is an info line") - logger.Info("this is an info log") - num := 10 - logger.Debugln("this", "is", "a", "debug", "log", "with num=", num) - num2 := 11 - logger.WithFields(suplog.Fields{"field1": num, "field2": num2}).Warningln("warning with fields") - - //suplog.WithError(err).Fatalln("failed to initialize Injective keyring") - - suplog.WithFields(suplog.Fields{"chain_id": "888"}).Infoln("Connected to Injective chain") - - suplog.WithFields(suplog.Fields{ - "chain_id": "*cfg.cosmosChainID", - "injective_grpc": "*cfg.cosmosGRPC", - "tendermint_rpc": "cfg.tendermintRPC", - }).Infoln("connected to Injective network") - - logger = suplog.WithField("loop", "EthOracleMainLoop") - - logger.WithField("lastConfirmedEthHeight", 1212).Infoln("Start scanning for events") -} - func TestRequestBatches(t *testing.T) { t.Parallel() t.Run("failed to get unbatched tokens from injective", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - maxAttempts: 1, - injective: &mockInjective{ - unbatchedTokenFeesFn: func(context.Context) ([]*peggy.BatchFees, error) { - return nil, errors.New("fail") - }, + r := &batchRequester{ + log: suplog.DefaultLogger, + retries: 1, + } + + inj := &mockInjective{ + unbatchedTokenFeesFn: func(context.Context) ([]*peggy.BatchFees, error) { + return nil, errors.New("fail") }, } + feed := mockPriceFeed{} - assert.NoError(t, orch.requestBatches(context.TODO(), suplog.DefaultLogger, false)) + assert.NoError(t, r.run(context.TODO(), inj, feed)) }) t.Run("no unbatched tokens", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - maxAttempts: 1, - injective: &mockInjective{ - unbatchedTokenFeesFn: func(context.Context) ([]*peggy.BatchFees, error) { - return nil, nil - }, + r := &batchRequester{ + log: suplog.DefaultLogger, + retries: 1, + } + + inj := &mockInjective{ + unbatchedTokenFeesFn: func(context.Context) ([]*peggy.BatchFees, error) { + return nil, nil }, } + feed := mockPriceFeed{} - assert.NoError(t, orch.requestBatches(context.TODO(), suplog.DefaultLogger, false)) + assert.NoError(t, r.run(context.TODO(), inj, feed)) }) t.Run("batch does not meet fee threshold", func(t *testing.T) { t.Parallel() - tokenAddr := eth.HexToAddress("0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30") + tokenAddr := "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30" + + r := &batchRequester{ + log: suplog.DefaultLogger, + minBatchFee: 51.0, + retries: 1, + erc20ContractMapping: map[eth.Address]string{ + eth.HexToAddress(tokenAddr): "inj", + }, + } - injective := &mockInjective{ + inj := &mockInjective{ sendRequestBatchFn: func(context.Context, string) error { return nil }, unbatchedTokenFeesFn: func(context.Context) ([]*peggy.BatchFees, error) { fees, _ := cosmtypes.NewIntFromString("50000000000000000000") return []*peggy.BatchFees{ { - Token: tokenAddr.String(), + Token: eth.HexToAddress(tokenAddr).String(), TotalFees: fees, }, }, nil }, } - orch := &PeggyOrchestrator{ - maxAttempts: 1, - minBatchFeeUSD: 51.0, - erc20ContractMapping: map[eth.Address]string{tokenAddr: "inj"}, - pricefeed: mockPriceFeed{queryFn: func(_ eth.Address) (float64, error) { return 1, nil }}, - injective: injective, - } + feed := mockPriceFeed{queryFn: func(_ eth.Address) (float64, error) { return 1, nil }} - assert.NoError(t, orch.requestBatches(context.TODO(), suplog.DefaultLogger, false)) - assert.Equal(t, injective.sendRequestBatchCallCount, 0) + assert.NoError(t, r.run(context.TODO(), inj, feed)) + assert.Equal(t, inj.sendRequestBatchCallCount, 0) }) t.Run("batch meets threshold and a request is sent", func(t *testing.T) { t.Parallel() - tokenAddr := eth.HexToAddress("0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30") + tokenAddr := "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30" + + r := &batchRequester{ + log: suplog.DefaultLogger, + minBatchFee: 49.0, + retries: 1, + erc20ContractMapping: map[eth.Address]string{ + eth.HexToAddress(tokenAddr): "inj", + }, + } - injective := &mockInjective{ + inj := &mockInjective{ sendRequestBatchFn: func(context.Context, string) error { return nil }, unbatchedTokenFeesFn: func(_ context.Context) ([]*peggy.BatchFees, error) { fees, _ := cosmtypes.NewIntFromString("50000000000000000000") return []*peggy.BatchFees{ { - Token: tokenAddr.String(), + Token: eth.HexToAddress(tokenAddr).String(), TotalFees: fees, }, }, nil }, } - orch := &PeggyOrchestrator{ - maxAttempts: 1, - minBatchFeeUSD: 49.0, - erc20ContractMapping: map[eth.Address]string{tokenAddr: "inj"}, - pricefeed: mockPriceFeed{queryFn: func(_ eth.Address) (float64, error) { - return 1, nil - }}, - injective: injective, - } + feed := mockPriceFeed{queryFn: func(_ eth.Address) (float64, error) { return 1, nil }} - assert.NoError(t, orch.requestBatches(context.TODO(), suplog.DefaultLogger, false)) - assert.Equal(t, injective.sendRequestBatchCallCount, 1) + assert.NoError(t, r.run(context.TODO(), inj, feed)) + assert.Equal(t, inj.sendRequestBatchCallCount, 1) }) } diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index 778b8b6a..1e4a321f 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -199,15 +199,6 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context, logger log.Logger) return err } - logger.WithFields(log.Fields{ - "inj_batch": oldestSignedBatch.BatchNonce, - "eth_batch": latestEthereumBatch.Uint64(), - }).Debugln("latest batches") - - if oldestSignedBatch.BatchNonce <= latestEthereumBatch.Uint64() { - return nil - } - currentValset, err := s.findLatestValsetOnEthereum(ctx, logger) if err != nil { metrics.ReportFuncError(s.svcTags) @@ -217,6 +208,15 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context, logger log.Logger) return errors.Wrap(err, "latest valset not found") } + logger.WithFields(log.Fields{ + "inj_batch": oldestSignedBatch.BatchNonce, + "eth_batch": latestEthereumBatch.Uint64(), + }).Debugln("latest batches") + + if oldestSignedBatch.BatchNonce <= latestEthereumBatch.Uint64() { + return nil + } + latestEthereumBatch, err = s.ethereum.GetTxBatchNonce(ctx, common.HexToAddress(oldestSignedBatch.TokenContract)) if err != nil { metrics.ReportFuncError(s.svcTags) From 64112dc89e9477208a9d75b5b6503f7168353ea9 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Mon, 10 Jul 2023 11:13:53 +0200 Subject: [PATCH 53/72] fix oracle tests --- orchestrator/oracle_test.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/orchestrator/oracle_test.go b/orchestrator/oracle_test.go index 4e90886f..a225b4fa 100644 --- a/orchestrator/oracle_test.go +++ b/orchestrator/oracle_test.go @@ -3,6 +3,7 @@ package orchestrator import ( "context" "errors" + "github.com/xlab/suplog" "math/big" "testing" @@ -27,7 +28,7 @@ func TestRelayEvents(t *testing.T) { }, } - _, err := orch.relayEthEvents(context.TODO(), 0, nil) + _, err := orch.relayEthEvents(context.TODO(), 0, suplog.DefaultLogger) assert.Error(t, err) }) @@ -42,7 +43,7 @@ func TestRelayEvents(t *testing.T) { }, } - currentBlock, err := orch.relayEthEvents(context.TODO(), 100, nil) + currentBlock, err := orch.relayEthEvents(context.TODO(), 100, suplog.DefaultLogger) assert.NoError(t, err) assert.Equal(t, currentBlock, 50-ethBlockConfirmationDelay) }) @@ -61,7 +62,7 @@ func TestRelayEvents(t *testing.T) { }, } - _, err := orch.relayEthEvents(context.TODO(), 100, nil) + _, err := orch.relayEthEvents(context.TODO(), 100, suplog.DefaultLogger) assert.Error(t, err) }) @@ -99,7 +100,7 @@ func TestRelayEvents(t *testing.T) { }, } - _, err := orch.relayEthEvents(context.TODO(), 100, nil) + _, err := orch.relayEthEvents(context.TODO(), 100, suplog.DefaultLogger) assert.Error(t, err) }) @@ -149,7 +150,7 @@ func TestRelayEvents(t *testing.T) { }, } - _, err := orch.relayEthEvents(context.TODO(), 100, nil) + _, err := orch.relayEthEvents(context.TODO(), 100, suplog.DefaultLogger) assert.NoError(t, err) assert.Equal(t, inj.sendEthereumClaimsCallCount, 0) }) @@ -200,7 +201,7 @@ func TestRelayEvents(t *testing.T) { }, } - _, err := orch.relayEthEvents(context.TODO(), 100, nil) + _, err := orch.relayEthEvents(context.TODO(), 100, suplog.DefaultLogger) assert.NoError(t, err) assert.Equal(t, inj.sendEthereumClaimsCallCount, 1) }) From 48f9208fe237bb9f9a6de27254f8ad97e74e1b23 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Mon, 10 Jul 2023 11:14:43 +0200 Subject: [PATCH 54/72] remove redundant code --- orchestrator/batch_request.go | 96 ----------------------------------- 1 file changed, 96 deletions(-) diff --git a/orchestrator/batch_request.go b/orchestrator/batch_request.go index f6c629bf..24118ce9 100644 --- a/orchestrator/batch_request.go +++ b/orchestrator/batch_request.go @@ -141,99 +141,3 @@ func (r *batchRequester) checkFeeThreshold( return false } - -func (s *PeggyOrchestrator) requestBatches(ctx context.Context, logger log.Logger, mustRequest bool) error { - logger.WithField("min_batch_fee", s.minBatchFeeUSD).Infoln("scanning Injective for potential batches") - - unbatchedTokensWithFees, err := s.getBatchFeesByToken(ctx, logger) - if err != nil { - // non-fatal, just alert - logger.WithError(err).Warningln("unable to get unbatched fees from Injective") - return nil - } - - if len(unbatchedTokensWithFees) == 0 { - logger.WithField("min_fee", s.minBatchFeeUSD).Debugln("no outgoing withdrawals or minimum batch fee is not met") - return nil - } - - logger.WithField("unbatched_fees_by_token", unbatchedTokensWithFees).Debugln("checking if batch fee is met") - for _, unbatchedToken := range unbatchedTokensWithFees { - // check if the token is present in cosmos denom. if so, send batch request with cosmosDenom - tokenAddr := eth.HexToAddress(unbatchedToken.Token) - denom := s.getTokenDenom(tokenAddr) - - thresholdMet := s.checkFeeThreshold(tokenAddr, unbatchedToken.TotalFees, s.minBatchFeeUSD) - if !thresholdMet && !mustRequest { - // non-injective relayers only relay when the threshold is met - logger.WithFields(log.Fields{ - "denom": denom, - "token_contract": tokenAddr.String(), - }).Debugln("skipping batch creation") - continue - } - - logger.WithFields(log.Fields{ - "denom": denom, - "token_contract": tokenAddr.String(), - }).Infoln("sending MsgRequestBatch to Injective") - - _ = s.injective.SendRequestBatch(ctx, denom) - } - - return nil -} - -func (s *PeggyOrchestrator) getBatchFeesByToken(ctx context.Context, log log.Logger) ([]*types.BatchFees, error) { - var unbatchedTokensWithFees []*types.BatchFees - retryFn := func() error { - fees, err := s.injective.UnbatchedTokenFees(ctx) - if err != nil { - return err - } - - unbatchedTokensWithFees = fees - return nil - } - - if err := retry.Do(retryFn, - retry.Context(ctx), - retry.Attempts(s.maxAttempts), - retry.OnRetry(func(n uint, err error) { - log.WithError(err).Errorf("failed to get unbatched fees, will retry (%d)", n) - }), - ); err != nil { - return nil, err - } - - return unbatchedTokensWithFees, nil -} - -func (s *PeggyOrchestrator) getTokenDenom(tokenAddr eth.Address) string { - if cosmosDenom, ok := s.erc20ContractMapping[tokenAddr]; ok { - return cosmosDenom - } - - // peggy denom - return types.PeggyDenomString(tokenAddr) -} - -func (s *PeggyOrchestrator) checkFeeThreshold(erc20Contract eth.Address, totalFee cosmtypes.Int, minFeeInUSD float64) bool { - if minFeeInUSD == 0 { - return true - } - - tokenPriceInUSD, err := s.pricefeed.QueryUSDPrice(erc20Contract) - if err != nil { - return false - } - - tokenPriceInUSDDec := decimal.NewFromFloat(tokenPriceInUSD) - totalFeeInUSDDec := decimal.NewFromBigInt(totalFee.BigInt(), -18).Mul(tokenPriceInUSDDec) - minFeeInUSDDec := decimal.NewFromFloat(minFeeInUSD) - - if totalFeeInUSDDec.GreaterThan(minFeeInUSDDec) { - return true - } - return false -} From 271c79405f7088b7878083a4c58d362dd67a8e8f Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Mon, 10 Jul 2023 14:21:54 +0200 Subject: [PATCH 55/72] refactor oracle and tests --- orchestrator/oracle.go | 398 +++++++++++++++++++----------------- orchestrator/oracle_test.go | 245 +++++++++++++--------- 2 files changed, 361 insertions(+), 282 deletions(-) diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index d0d22772..babff904 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -8,7 +8,6 @@ import ( "github.com/pkg/errors" log "github.com/xlab/suplog" - "github.com/InjectiveLabs/metrics" "github.com/InjectiveLabs/peggo/orchestrator/loops" wrappers "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" ) @@ -24,24 +23,44 @@ const ( // EthOracleMainLoop is responsible for making sure that Ethereum events are retrieved from the Ethereum blockchain // and ferried over to Cosmos where they will be used to issue tokens or process batches. func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { - logger := log.WithField("loop", "EthOracle") - lastResync := time.Now() + lastConfirmedEthHeight, err := s.getLastConfirmedEthHeightOnInjective(ctx) + if err != nil { + return err + } + + oracle := ðOracle{ + log: log.WithField("loop", "EthOracle"), + retries: s.maxAttempts, + lastResyncWithInjective: time.Now(), + lastCheckedEthHeight: lastConfirmedEthHeight, + } + + return loops.RunLoop( + ctx, + defaultLoopDur, + func() error { return oracle.run(ctx, s.injective, s.ethereum) }, + ) +} +func (s *PeggyOrchestrator) getLastConfirmedEthHeightOnInjective(ctx context.Context) (uint64, error) { var lastConfirmedEthHeight uint64 retryFn := func() error { - height, err := s.getLastConfirmedEthHeight(ctx) - if err != nil { - logger.WithError(err).Warningf("failed to get last claim from Injective. Querying peggy params...") + lastClaimEvent, err := s.injective.LastClaimEvent(ctx) + if err == nil && lastClaimEvent != nil && lastClaimEvent.EthereumEventHeight != 0 { + lastConfirmedEthHeight = lastClaimEvent.EthereumEventHeight + return nil + } - if height == 0 { - peggyParams, err := s.injective.PeggyParams(ctx) - if err != nil { - logger.WithError(err).Fatalln("failed to query peggy module params, is injectived running?") - } - height = peggyParams.BridgeContractStartHeight + log.WithError(err).Warningf("failed to get last claim from Injective. Querying peggy params...") + + peggyParams, err := s.injective.PeggyParams(ctx) + if err != nil { + log.WithError(err).Fatalln("failed to query peggy module params, is injectived running?") + return err } - lastConfirmedEthHeight = height + + lastConfirmedEthHeight = peggyParams.BridgeContractStartHeight return nil } @@ -49,34 +68,39 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { retry.Context(ctx), retry.Attempts(s.maxAttempts), retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get last confirmed Ethereum height from Injective, will retry (%d)", n) + log.WithError(err).Warningf("failed to get last confirmed Ethereum height on Injective, will retry (%d)", n) }), ); err != nil { - logger.WithError(err).Errorln("got error, loop exits") - return err + log.WithError(err).Errorln("got error, loop exits") + return 0, err } - return loops.RunLoop(ctx, defaultLoopDur, func() error { - logger.WithField("last_confirmed_eth_height", lastConfirmedEthHeight).Infoln("scanning Ethereum for events") - - // Relays events from Ethereum -> Cosmos - var currentHeight uint64 - if err := retry.Do(func() (err error) { - currentHeight, err = s.relayEthEvents(ctx, lastConfirmedEthHeight, logger) - return - }, - retry.Context(ctx), - retry.Attempts(s.maxAttempts), - retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("error during Ethereum event checking, will retry (%d)", n) - }), - ); err != nil { - logger.WithError(err).Errorln("got error, loop exits") - return err - } + return lastConfirmedEthHeight, nil +} + +type ethOracle struct { + log log.Logger + retries uint + lastResyncWithInjective time.Time + lastCheckedEthHeight uint64 +} + +func (o *ethOracle) run( + ctx context.Context, + injective InjectiveNetwork, + ethereum EthereumNetwork, +) error { + o.log.WithField("last_checked_eth_height", o.lastCheckedEthHeight).Infoln("scanning Ethereum for events") + + // Relays events from Ethereum -> Cosmos + newHeight, err := o.relayEvents(ctx, injective, ethereum) + if err != nil { + return err + } - lastConfirmedEthHeight = currentHeight + o.lastCheckedEthHeight = newHeight + if time.Since(o.lastResyncWithInjective) >= 48*time.Hour { /** Auto re-sync to catch up the nonce. Reasons why event nonce fall behind. 1. It takes some time for events to be indexed on Ethereum. So if peggo queried events immediately as block produced, there is a chance the event is missed. @@ -84,183 +108,189 @@ func (s *PeggyOrchestrator) EthOracleMainLoop(ctx context.Context) error { 2. if validator was in UnBonding state, the claims broadcasted in last iteration are failed. 3. if infura call failed while filtering events, the peggo missed to broadcast claim events occured in last iteration. **/ - - if time.Since(lastResync) >= 48*time.Hour { - if err := retry.Do(func() (err error) { - lastConfirmedEthHeight, err = s.getLastConfirmedEthHeight(ctx) - return - }, - retry.Context(ctx), - retry.Attempts(s.maxAttempts), - retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to get last confirmed eth height, will retry (%d)", n) - }), - ); err != nil { - logger.WithError(err).Errorln("got error, loop exits") - return err - } - - lastResync = time.Now() - logger.WithFields(log.Fields{ - "last_resync": lastResync, - "last_confirmed_eth_height": lastConfirmedEthHeight, - }).Infoln("auto resync") + if err := o.autoResync(ctx, injective); err != nil { + return err } - - return nil - }) -} - -// getLastConfirmedEthHeight retrieves the last claim event this oracle has relayed to Cosmos. -func (s *PeggyOrchestrator) getLastConfirmedEthHeight(ctx context.Context) (uint64, error) { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - lastClaimEvent, err := s.injective.LastClaimEvent(ctx) - if err != nil { - metrics.ReportFuncError(s.svcTags) - return uint64(0), err } - return lastClaimEvent.EthereumEventHeight, nil + return nil } -// relayEthEvents checks for events such as a deposit to the Peggy Ethereum contract or a validator set update -// or a transaction batch update. It then responds to these events by performing actions on the Cosmos chain if required -func (s *PeggyOrchestrator) relayEthEvents(ctx context.Context, startingBlock uint64, logger log.Logger) (uint64, error) { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() +func (o *ethOracle) relayEvents( + ctx context.Context, + injective InjectiveNetwork, + ethereum EthereumNetwork, +) (uint64, error) { + // Relays events from Ethereum -> Cosmos + var ( + latestHeight uint64 + currentHeight = o.lastCheckedEthHeight + ) - latestHeader, err := s.ethereum.HeaderByNumber(ctx, nil) - if err != nil { - metrics.ReportFuncError(s.svcTags) - return 0, errors.Wrap(err, "failed to get latest ethereum header") - } - - // add delay to ensure minimum confirmations are received and block is finalised - latestBlock := latestHeader.Number.Uint64() - ethBlockConfirmationDelay - if latestBlock < startingBlock { - return latestBlock, nil - } - - if latestBlock > startingBlock+defaultBlocksToSearch { - latestBlock = startingBlock + defaultBlocksToSearch - } - - legacyDeposits, err := s.ethereum.GetSendToCosmosEvents(startingBlock, latestBlock) - if err != nil { - metrics.ReportFuncError(s.svcTags) - return 0, errors.Wrap(err, "failed to get SendToCosmos events") - } + retryFn := func() error { + latestHeader, err := ethereum.HeaderByNumber(ctx, nil) + if err != nil { + return errors.Wrap(err, "failed to get latest ethereum header") + } - deposits, err := s.ethereum.GetSendToInjectiveEvents(startingBlock, latestBlock) - if err != nil { - metrics.ReportFuncError(s.svcTags) - return 0, errors.Wrap(err, "failed to get SendToInjective events") - } + // add delay to ensure minimum confirmations are received and block is finalised + latestHeight = latestHeader.Number.Uint64() - ethBlockConfirmationDelay + if latestHeight < currentHeight { + println(latestHeight) + return nil + } - withdrawals, err := s.ethereum.GetTransactionBatchExecutedEvents(startingBlock, latestBlock) - if err != nil { - metrics.ReportFuncError(s.svcTags) - return 0, errors.Wrap(err, "failed to get TransactionBatchExecuted events") - } + if latestHeight > currentHeight+defaultBlocksToSearch { + latestHeight = currentHeight + defaultBlocksToSearch + } - erc20Deployments, err := s.ethereum.GetPeggyERC20DeployedEvents(startingBlock, latestBlock) - if err != nil { - metrics.ReportFuncError(s.svcTags) - return 0, errors.Wrap(err, "failed to get ERC20Deployed events") - } + legacyDeposits, err := ethereum.GetSendToCosmosEvents(currentHeight, latestHeight) + if err != nil { + return errors.Wrap(err, "failed to get SendToCosmos events") + } - valsetUpdates, err := s.ethereum.GetValsetUpdatedEvents(startingBlock, latestBlock) - if err != nil { - metrics.ReportFuncError(s.svcTags) - return 0, errors.Wrap(err, "failed to get ValsetUpdated events") - } + deposits, err := ethereum.GetSendToInjectiveEvents(currentHeight, latestHeight) + if err != nil { + return errors.Wrap(err, "failed to get SendToInjective events") + } - // note that starting block overlaps with our last checked block, because we have to deal with - // the possibility that the relayer was killed after relaying only one of multiple events in a single - // block, so we also need this routine so make sure we don't send in the first event in this hypothetical - // multi event block again. In theory we only send all events for every block and that will pass of fail - // atomically but lets not take that risk. - lastClaimEvent, err := s.injective.LastClaimEvent(ctx) - if err != nil { - metrics.ReportFuncError(s.svcTags) - return 0, errors.New("failed to query last claim event from Injective") - } + withdrawals, err := ethereum.GetTransactionBatchExecutedEvents(currentHeight, latestHeight) + if err != nil { + return errors.Wrap(err, "failed to get TransactionBatchExecuted events") + } - legacyDeposits = filterSendToCosmosEventsByNonce(legacyDeposits, lastClaimEvent.EthereumEventNonce) + erc20Deployments, err := ethereum.GetPeggyERC20DeployedEvents(currentHeight, latestHeight) + if err != nil { + return errors.Wrap(err, "failed to get ERC20Deployed events") + } - logger.WithFields(log.Fields{ - "block_start": startingBlock, - "block_end": latestBlock, - "old_deposits": legacyDeposits, - }).Debugln("scanned SendToCosmos events from Ethereum") + valsetUpdates, err := ethereum.GetValsetUpdatedEvents(currentHeight, latestHeight) + if err != nil { + return errors.Wrap(err, "failed to get ValsetUpdated events") + } - deposits = filterSendToInjectiveEventsByNonce(deposits, lastClaimEvent.EthereumEventNonce) + // note that starting block overlaps with our last checked block, because we have to deal with + // the possibility that the relayer was killed after relaying only one of multiple events in a single + // block, so we also need this routine so make sure we don't send in the first event in this hypothetical + // multi event block again. In theory we only send all events for every block and that will pass of fail + // atomically but lets not take that risk. + lastClaimEvent, err := injective.LastClaimEvent(ctx) + if err != nil { + return errors.New("failed to query last claim event from Injective") + } - logger.WithFields(log.Fields{ - "block_start": startingBlock, - "block_end": latestBlock, - "deposits": deposits, - }).Debugln("scanned SendToInjective events from Ethereum") + legacyDeposits = filterSendToCosmosEventsByNonce(legacyDeposits, lastClaimEvent.EthereumEventNonce) + o.log.WithFields(log.Fields{ + "block_start": currentHeight, + "block_end": latestHeight, + "old_deposits": legacyDeposits, + }).Debugln("scanned SendToCosmos events from Ethereum") + + deposits = filterSendToInjectiveEventsByNonce(deposits, lastClaimEvent.EthereumEventNonce) + o.log.WithFields(log.Fields{ + "block_start": currentHeight, + "block_end": latestHeight, + "deposits": deposits, + }).Debugln("scanned SendToInjective events from Ethereum") + + withdrawals = filterTransactionBatchExecutedEventsByNonce(withdrawals, lastClaimEvent.EthereumEventNonce) + o.log.WithFields(log.Fields{ + "block_start": currentHeight, + "block_end": latestHeight, + "withdrawals": withdrawals, + }).Debugln("scanned TransactionBatchExecuted events from Ethereum") + + erc20Deployments = filterERC20DeployedEventsByNonce(erc20Deployments, lastClaimEvent.EthereumEventNonce) + o.log.WithFields(log.Fields{ + "block_start": currentHeight, + "block_end": latestHeight, + "erc20_deployments": erc20Deployments, + }).Debugln("scanned FilterERC20Deployed events from Ethereum") + + valsetUpdates = filterValsetUpdateEventsByNonce(valsetUpdates, lastClaimEvent.EthereumEventNonce) + o.log.WithFields(log.Fields{ + "block_start": currentHeight, + "block_end": latestHeight, + "valset_updates": valsetUpdates, + }).Debugln("scanned ValsetUpdated events from Ethereum") + + if len(legacyDeposits) == 0 && + len(deposits) == 0 && + len(withdrawals) == 0 && + len(erc20Deployments) == 0 && + len(valsetUpdates) == 0 { + return nil + } - withdrawals = filterTransactionBatchExecutedEventsByNonce(withdrawals, lastClaimEvent.EthereumEventNonce) + if err := injective.SendEthereumClaims(ctx, + lastClaimEvent.EthereumEventNonce, + legacyDeposits, + deposits, + withdrawals, + erc20Deployments, + valsetUpdates, + ); err != nil { + return errors.Wrap(err, "failed to send event claims to Injective") + } - logger.WithFields(log.Fields{ - "block_start": startingBlock, - "block_end": latestBlock, - "withdrawals": withdrawals, - }).Debugln("scanned TransactionBatchExecuted events from Ethereum") + o.log.WithFields(log.Fields{ + "last_claim_event_nonce": lastClaimEvent.EthereumEventNonce, + "legacy_deposits": len(legacyDeposits), + "deposits": len(deposits), + "withdrawals": len(withdrawals), + "erc20Deployments": len(erc20Deployments), + "valsetUpdates": len(valsetUpdates), + }).Infoln("sent new claims to Injective") - erc20Deployments = filterERC20DeployedEventsByNonce(erc20Deployments, lastClaimEvent.EthereumEventNonce) + return nil + } - logger.WithFields(log.Fields{ - "block_start": startingBlock, - "block_end": latestBlock, - "erc20_deployments": erc20Deployments, - }).Debugln("scanned FilterERC20Deployed events from Ethereum") + if err := retry.Do(retryFn, + retry.Context(ctx), + retry.Attempts(o.retries), + retry.OnRetry(func(n uint, err error) { + o.log.WithError(err).Warningf("error during Ethereum event checking, will retry (%d)", n) + }), + ); err != nil { + o.log.WithError(err).Errorln("got error, loop exits") + return 0, err + } - valsetUpdates = filterValsetUpdateEventsByNonce(valsetUpdates, lastClaimEvent.EthereumEventNonce) + return latestHeight, nil +} - logger.WithFields(log.Fields{ - "block_start": startingBlock, - "block_end": latestBlock, - "valset_updates": valsetUpdates, - }).Debugln("scanned ValsetUpdated events from Ethereum") +func (o *ethOracle) autoResync(ctx context.Context, injective InjectiveNetwork) error { + var latestHeight uint64 + retryFn := func() error { + lastClaimEvent, err := injective.LastClaimEvent(ctx) + if err != nil { + return err + } - if len(legacyDeposits) == 0 && - len(deposits) == 0 && - len(withdrawals) == 0 && - len(erc20Deployments) == 0 && - len(valsetUpdates) == 0 { - return latestBlock, nil + latestHeight = lastClaimEvent.EthereumEventHeight + return nil } - // todo get eth chain id from the chain - if err := s.injective.SendEthereumClaims(ctx, - lastClaimEvent.EthereumEventNonce, - legacyDeposits, - deposits, - withdrawals, - erc20Deployments, - valsetUpdates, + if err := retry.Do(retryFn, + retry.Context(ctx), + retry.Attempts(o.retries), + retry.OnRetry(func(n uint, err error) { + o.log.WithError(err).Warningf("failed to get last confirmed eth height, will retry (%d)", n) + }), ); err != nil { - metrics.ReportFuncError(s.svcTags) - return 0, errors.Wrap(err, "failed to send event claims to Injective") + o.log.WithError(err).Errorln("got error, loop exits") + return err } - logger.WithFields(log.Fields{ - "last_claim_event_nonce": lastClaimEvent.EthereumEventNonce, - "legacy_deposits": len(legacyDeposits), - "deposits": len(deposits), - "withdrawals": len(withdrawals), - "erc20Deployments": len(erc20Deployments), - "valsetUpdates": len(valsetUpdates), - }).Infoln("sent new claims to Injective") + o.lastCheckedEthHeight = latestHeight + o.lastResyncWithInjective = time.Now() + + o.log.WithFields(log.Fields{ + "last_resync": o.lastResyncWithInjective.String(), + "last_confirmed_eth_height": o.lastCheckedEthHeight, + }).Infoln("auto resync") - return latestBlock, nil + return nil } func filterSendToCosmosEventsByNonce( diff --git a/orchestrator/oracle_test.go b/orchestrator/oracle_test.go index a225b4fa..c4f8ffed 100644 --- a/orchestrator/oracle_test.go +++ b/orchestrator/oracle_test.go @@ -3,18 +3,19 @@ package orchestrator import ( "context" "errors" - "github.com/xlab/suplog" "math/big" "testing" + "time" + peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/assert" + "github.com/xlab/suplog" wrappers "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" - peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" ) -func TestRelayEvents(t *testing.T) { +func TestEthOracle(t *testing.T) { t.Parallel() t.Run("failed to get latest header from ethereum", func(t *testing.T) { @@ -28,80 +29,93 @@ func TestRelayEvents(t *testing.T) { }, } - _, err := orch.relayEthEvents(context.TODO(), 0, suplog.DefaultLogger) - assert.Error(t, err) + assert.Error(t, orch.EthOracleMainLoop(context.TODO())) }) t.Run("latest ethereum header is old", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - ethereum: mockEthereum{ - headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { - return &types.Header{Number: big.NewInt(50)}, nil - }, + ethereum := mockEthereum{ + headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return &types.Header{Number: big.NewInt(50)}, nil }, } - currentBlock, err := orch.relayEthEvents(context.TODO(), 100, suplog.DefaultLogger) - assert.NoError(t, err) - assert.Equal(t, currentBlock, 50-ethBlockConfirmationDelay) + o := ðOracle{ + log: suplog.DefaultLogger, + retries: 1, + lastResyncWithInjective: time.Now(), + lastCheckedEthHeight: 100, + } + + assert.NoError(t, o.run(context.TODO(), nil, ethereum)) + assert.Equal(t, o.lastCheckedEthHeight, uint64(38)) }) t.Run("failed to get SendToCosmos events", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - ethereum: mockEthereum{ - headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { - return &types.Header{Number: big.NewInt(200)}, nil - }, - getSendToCosmosEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToCosmosEvent, error) { - return nil, errors.New("fail") - }, + ethereum := mockEthereum{ + headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return &types.Header{Number: big.NewInt(200)}, nil }, + getSendToCosmosEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToCosmosEvent, error) { + return nil, errors.New("fail") + }, + } + + o := ðOracle{ + log: suplog.DefaultLogger, + retries: 1, + lastResyncWithInjective: time.Now(), + lastCheckedEthHeight: 100, } - _, err := orch.relayEthEvents(context.TODO(), 100, suplog.DefaultLogger) - assert.Error(t, err) + assert.Error(t, o.run(context.TODO(), nil, ethereum)) + assert.Equal(t, o.lastCheckedEthHeight, uint64(100)) }) t.Run("failed to get last claim event from injective", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - ethereum: mockEthereum{ - headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { - return &types.Header{Number: big.NewInt(200)}, nil - }, - getSendToCosmosEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToCosmosEvent, error) { - return []*wrappers.PeggySendToCosmosEvent{}, nil // empty slice will do - }, + ethereum := mockEthereum{ + headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return &types.Header{Number: big.NewInt(200)}, nil + }, - // no-ops - getTransactionBatchExecutedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyTransactionBatchExecutedEvent, error) { - return nil, nil - }, - getValsetUpdatedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { - return nil, nil - }, - getPeggyERC20DeployedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyERC20DeployedEvent, error) { - return nil, nil - }, - getSendToInjectiveEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToInjectiveEvent, error) { - return nil, nil - }, + // no-ops + getSendToCosmosEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToCosmosEvent, error) { + return nil, nil + }, + getTransactionBatchExecutedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyTransactionBatchExecutedEvent, error) { + return nil, nil + }, + getValsetUpdatedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return nil, nil + }, + getPeggyERC20DeployedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyERC20DeployedEvent, error) { + return nil, nil }, + getSendToInjectiveEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToInjectiveEvent, error) { + return nil, nil + }, + } - injective: &mockInjective{ - lastClaimEventFn: func(context.Context) (*peggytypes.LastClaimEvent, error) { - return nil, errors.New("fail") - }, + injective := &mockInjective{ + lastClaimEventFn: func(context.Context) (*peggytypes.LastClaimEvent, error) { + return nil, errors.New("fail") }, } - _, err := orch.relayEthEvents(context.TODO(), 100, suplog.DefaultLogger) - assert.Error(t, err) + o := ðOracle{ + log: suplog.DefaultLogger, + retries: 1, + lastResyncWithInjective: time.Now(), + lastCheckedEthHeight: 100, + } + + assert.Error(t, o.run(context.TODO(), injective, ethereum)) + assert.Equal(t, o.lastCheckedEthHeight, uint64(100)) }) t.Run("old events are pruned", func(t *testing.T) { @@ -124,34 +138,38 @@ func TestRelayEvents(t *testing.T) { }, } - orch := &PeggyOrchestrator{ - injective: inj, - ethereum: mockEthereum{ - headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { - return &types.Header{Number: big.NewInt(200)}, nil - }, - getSendToCosmosEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToCosmosEvent, error) { - return []*wrappers.PeggySendToCosmosEvent{{EventNonce: big.NewInt(5)}}, nil - }, + eth := mockEthereum{ + headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return &types.Header{Number: big.NewInt(200)}, nil + }, + getSendToCosmosEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToCosmosEvent, error) { + return []*wrappers.PeggySendToCosmosEvent{{EventNonce: big.NewInt(5)}}, nil + }, - // no-ops - getTransactionBatchExecutedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyTransactionBatchExecutedEvent, error) { - return nil, nil - }, - getValsetUpdatedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { - return nil, nil - }, - getPeggyERC20DeployedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyERC20DeployedEvent, error) { - return nil, nil - }, - getSendToInjectiveEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToInjectiveEvent, error) { - return nil, nil - }, + // no-ops + getTransactionBatchExecutedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyTransactionBatchExecutedEvent, error) { + return nil, nil + }, + getValsetUpdatedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return nil, nil + }, + getPeggyERC20DeployedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyERC20DeployedEvent, error) { + return nil, nil + }, + getSendToInjectiveEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToInjectiveEvent, error) { + return nil, nil }, } - _, err := orch.relayEthEvents(context.TODO(), 100, suplog.DefaultLogger) - assert.NoError(t, err) + o := ðOracle{ + log: suplog.DefaultLogger, + retries: 1, + lastResyncWithInjective: time.Now(), + lastCheckedEthHeight: 100, + } + + assert.NoError(t, o.run(context.TODO(), inj, eth)) + assert.Equal(t, o.lastCheckedEthHeight, uint64(120)) assert.Equal(t, inj.sendEthereumClaimsCallCount, 0) }) @@ -175,34 +193,65 @@ func TestRelayEvents(t *testing.T) { }, } - orch := &PeggyOrchestrator{ - injective: inj, - ethereum: mockEthereum{ - headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { - return &types.Header{Number: big.NewInt(200)}, nil - }, - getSendToCosmosEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToCosmosEvent, error) { - return []*wrappers.PeggySendToCosmosEvent{{EventNonce: big.NewInt(10)}}, nil - }, + eth := mockEthereum{ + headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return &types.Header{Number: big.NewInt(200)}, nil + }, + getSendToCosmosEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToCosmosEvent, error) { + return []*wrappers.PeggySendToCosmosEvent{{EventNonce: big.NewInt(10)}}, nil + }, - // no-ops - getTransactionBatchExecutedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyTransactionBatchExecutedEvent, error) { - return nil, nil - }, - getValsetUpdatedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { - return nil, nil - }, - getPeggyERC20DeployedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyERC20DeployedEvent, error) { - return nil, nil - }, - getSendToInjectiveEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToInjectiveEvent, error) { - return nil, nil - }, + // no-ops + getTransactionBatchExecutedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyTransactionBatchExecutedEvent, error) { + return nil, nil + }, + getValsetUpdatedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return nil, nil + }, + getPeggyERC20DeployedEventsFn: func(uint64, uint64) ([]*wrappers.PeggyERC20DeployedEvent, error) { + return nil, nil + }, + getSendToInjectiveEventsFn: func(uint64, uint64) ([]*wrappers.PeggySendToInjectiveEvent, error) { + return nil, nil }, } - _, err := orch.relayEthEvents(context.TODO(), 100, suplog.DefaultLogger) - assert.NoError(t, err) + o := ðOracle{ + log: suplog.DefaultLogger, + retries: 1, + lastResyncWithInjective: time.Now(), + lastCheckedEthHeight: 100, + } + + assert.NoError(t, o.run(context.TODO(), inj, eth)) + assert.Equal(t, o.lastCheckedEthHeight, uint64(120)) assert.Equal(t, inj.sendEthereumClaimsCallCount, 1) }) + + t.Run("auto resync", func(t *testing.T) { + t.Parallel() + + inj := &mockInjective{ + lastClaimEventFn: func(_ context.Context) (*peggytypes.LastClaimEvent, error) { + return &peggytypes.LastClaimEvent{EthereumEventHeight: 101}, nil + }, + } + + eth := mockEthereum{ + headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return &types.Header{Number: big.NewInt(50)}, nil + }, + } + + o := ðOracle{ + log: suplog.DefaultLogger, + retries: 1, + lastResyncWithInjective: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), + lastCheckedEthHeight: 100, + } + + assert.NoError(t, o.run(context.TODO(), inj, eth)) + assert.Equal(t, o.lastCheckedEthHeight, uint64(101)) + assert.True(t, time.Since(o.lastResyncWithInjective) < 1*time.Second) + }) } From c2992404f8c05caa59cb835d486f8bb9c4ae40e1 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Mon, 10 Jul 2023 18:20:58 +0200 Subject: [PATCH 56/72] refactor relaying and tests --- orchestrator/relayer.go | 223 ++--- orchestrator/relayer_test.go | 1678 ++++++++++++++++++---------------- 2 files changed, 1016 insertions(+), 885 deletions(-) diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index 1e4a321f..45fd2f0a 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -11,7 +11,6 @@ import ( "github.com/pkg/errors" log "github.com/xlab/suplog" - "github.com/InjectiveLabs/metrics" "github.com/InjectiveLabs/peggo/orchestrator/ethereum/util" "github.com/InjectiveLabs/peggo/orchestrator/loops" wrappers "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" @@ -19,67 +18,96 @@ import ( ) func (s *PeggyOrchestrator) RelayerMainLoop(ctx context.Context) (err error) { - logger := log.WithField("loop", "Relayer") - - return loops.RunLoop(ctx, defaultLoopDur, func() error { - var pg loops.ParanoidGroup - if s.valsetRelayEnabled { - logger.Infoln("scanning Injective for confirmed valset updates") - pg.Go(func() error { - return retry.Do(func() error { return s.relayValsets(ctx, logger.WithField("Relayer", "Valset")) }, - retry.Context(ctx), - retry.Attempts(s.maxAttempts), - retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to relay valsets, will retry (%d)", n) - }), - ) - }) - } + rel := &relayer{ + log: log.WithField("loop", "Relayer"), + retries: s.maxAttempts, + relayValsetOffsetDur: s.relayValsetOffsetDur, + relayBatchOffsetDur: s.relayBatchOffsetDur, + valsetRelaying: s.valsetRelayEnabled, + batchRelaying: s.batchRelayEnabled, + } - if s.batchRelayEnabled { - logger.Infoln("scanning Injective for confirmed batches") - pg.Go(func() error { - return retry.Do(func() error { return s.relayBatches(ctx, logger.WithField("Relayer", "Batch")) }, - retry.Context(ctx), - retry.Attempts(s.maxAttempts), - retry.OnRetry(func(n uint, err error) { - logger.WithError(err).Warningf("failed to relay batches, will retry (%d)", n) - }), - ) - }) - } + return loops.RunLoop( + ctx, + defaultLoopDur, + func() error { return rel.run(ctx, s.injective, s.ethereum) }, + ) +} - if pg.Initialized() { - if err := pg.Wait(); err != nil { - logger.WithError(err).Errorln("got error, loop exits") - return err - } +type relayer struct { + log log.Logger + retries uint + relayValsetOffsetDur time.Duration + relayBatchOffsetDur time.Duration + valsetRelaying bool + batchRelaying bool +} + +func (r *relayer) run( + ctx context.Context, + injective InjectiveNetwork, + ethereum EthereumNetwork, +) error { + var pg loops.ParanoidGroup + + if r.valsetRelaying { + r.log.Infoln("scanning Injective for confirmed valset updates") + pg.Go(func() error { + return retry.Do( + func() error { return r.relayValsets(ctx, injective, ethereum) }, + retry.Context(ctx), + retry.Attempts(r.retries), + retry.OnRetry(func(n uint, err error) { + r.log.WithError(err).Warningf("failed to relay valsets, will retry (%d)", n) + }), + ) + }) + } + + if r.batchRelaying { + r.log.Infoln("scanning Injective for confirmed batches") + pg.Go(func() error { + return retry.Do( + func() error { return r.relayBatches(ctx, injective, ethereum) }, + retry.Context(ctx), + retry.Attempts(r.retries), + retry.OnRetry(func(n uint, err error) { + r.log.WithError(err).Warningf("failed to relay batches, will retry (%d)", n) + }), + ) + }) + } + + if pg.Initialized() { + if err := pg.Wait(); err != nil { + r.log.WithError(err).Errorln("got error, loop exits") + return err } + } - return nil - }) + return nil } -func (s *PeggyOrchestrator) relayValsets(ctx context.Context, logger log.Logger) error { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - +func (r *relayer) relayValsets( + ctx context.Context, + injective InjectiveNetwork, + ethereum EthereumNetwork, +) error { // we should determine if we need to relay one // to Ethereum for that we will find the latest confirmed valset and compare it to the ethereum chain - - latestValsets, err := s.injective.LatestValsets(ctx) + latestValsets, err := injective.LatestValsets(ctx) if err != nil { - metrics.ReportFuncError(s.svcTags) return errors.Wrap(err, "failed to fetch latest valsets from Injective") } - var latestCosmosSigs []*types.MsgValsetConfirm - var latestCosmosConfirmed *types.Valset + var ( + latestCosmosSigs []*types.MsgValsetConfirm + latestCosmosConfirmed *types.Valset + ) + for _, set := range latestValsets { - sigs, err := s.injective.AllValsetConfirms(ctx, set.Nonce) + sigs, err := injective.AllValsetConfirms(ctx, set.Nonce) if err != nil { - metrics.ReportFuncError(s.svcTags) return errors.Wrapf(err, "failed to get valset confirms at nonce %d", set.Nonce) } else if len(sigs) == 0 { continue @@ -91,17 +119,16 @@ func (s *PeggyOrchestrator) relayValsets(ctx context.Context, logger log.Logger) } if latestCosmosConfirmed == nil { - logger.Debugln("no confirmed valsets found on Injective, nothing to relay...") + r.log.Debugln("no confirmed valsets found on Injective, nothing to relay...") return nil } - currentEthValset, err := s.findLatestValsetOnEthereum(ctx, logger) + currentEthValset, err := r.findLatestValsetOnEth(ctx, injective, ethereum) if err != nil { - metrics.ReportFuncError(s.svcTags) return errors.Wrap(err, "failed to find latest confirmed valset on Ethereum") } - logger.WithFields(log.Fields{ + r.log.WithFields(log.Fields{ "inj_valset": latestCosmosConfirmed, "eth_valset": currentEthValset, }).Debugln("latest valsets") @@ -110,9 +137,8 @@ func (s *PeggyOrchestrator) relayValsets(ctx context.Context, logger log.Logger) return nil } - latestEthereumValsetNonce, err := s.ethereum.GetValsetNonce(ctx) + latestEthereumValsetNonce, err := ethereum.GetValsetNonce(ctx) if err != nil { - metrics.ReportFuncError(s.svcTags) return errors.Wrap(err, "failed to get latest valset nonce from Ethereum") } @@ -122,24 +148,21 @@ func (s *PeggyOrchestrator) relayValsets(ctx context.Context, logger log.Logger) } // Check custom time delay offset - blockResult, err := s.injective.GetBlock(ctx, int64(latestCosmosConfirmed.Height)) + blockResult, err := injective.GetBlock(ctx, int64(latestCosmosConfirmed.Height)) if err != nil { return errors.Wrapf(err, "failed to get block %d from Injective", latestCosmosConfirmed.Height) } - valsetCreatedAt := blockResult.Block.Time - customTimeDelay := valsetCreatedAt.Add(s.relayValsetOffsetDur) - - if time.Now().Sub(customTimeDelay) <= 0 { + if time.Since(blockResult.Block.Time) <= r.relayValsetOffsetDur { return nil } - logger.WithFields(log.Fields{ + r.log.WithFields(log.Fields{ "inj_valset": latestCosmosConfirmed.Nonce, "eth_valset": latestEthereumValsetNonce.Uint64(), }).Infoln("detected new valset on Injective") - txHash, err := s.ethereum.SendEthValsetUpdate( + txHash, err := ethereum.SendEthValsetUpdate( ctx, currentEthValset, latestCosmosConfirmed, @@ -147,23 +170,21 @@ func (s *PeggyOrchestrator) relayValsets(ctx context.Context, logger log.Logger) ) if err != nil { - metrics.ReportFuncError(s.svcTags) return err } - logger.WithField("tx_hash", txHash.Hex()).Infoln("updated valset on Ethereum") + r.log.WithField("tx_hash", txHash.Hex()).Infoln("updated valset on Ethereum") return nil } -func (s *PeggyOrchestrator) relayBatches(ctx context.Context, logger log.Logger) error { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - latestBatches, err := s.injective.LatestTransactionBatches(ctx) +func (r *relayer) relayBatches( + ctx context.Context, + injective InjectiveNetwork, + ethereum EthereumNetwork, +) error { + latestBatches, err := injective.LatestTransactionBatches(ctx) if err != nil { - metrics.ReportFuncError(s.svcTags) return err } @@ -173,9 +194,8 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context, logger log.Logger) ) for _, batch := range latestBatches { - sigs, err := s.injective.TransactionBatchSignatures(ctx, batch.BatchNonce, common.HexToAddress(batch.TokenContract)) + sigs, err := injective.TransactionBatchSignatures(ctx, batch.BatchNonce, common.HexToAddress(batch.TokenContract)) if err != nil { - metrics.ReportFuncError(s.svcTags) return err } else if len(sigs) == 0 { continue @@ -186,29 +206,26 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context, logger log.Logger) } if oldestSignedBatch == nil { - logger.Debugln("no confirmed transaction batches on Injective, nothing to relay...") + r.log.Debugln("no confirmed transaction batches on Injective, nothing to relay...") return nil } - latestEthereumBatch, err := s.ethereum.GetTxBatchNonce( + latestEthereumBatch, err := ethereum.GetTxBatchNonce( ctx, common.HexToAddress(oldestSignedBatch.TokenContract), ) if err != nil { - metrics.ReportFuncError(s.svcTags) return err } - currentValset, err := s.findLatestValsetOnEthereum(ctx, logger) + currentValset, err := r.findLatestValsetOnEth(ctx, injective, ethereum) if err != nil { - metrics.ReportFuncError(s.svcTags) return errors.Wrap(err, "failed to find latest valset") } else if currentValset == nil { - metrics.ReportFuncError(s.svcTags) return errors.Wrap(err, "latest valset not found") } - logger.WithFields(log.Fields{ + r.log.WithFields(log.Fields{ "inj_batch": oldestSignedBatch.BatchNonce, "eth_batch": latestEthereumBatch.Uint64(), }).Debugln("latest batches") @@ -217,9 +234,8 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context, logger log.Logger) return nil } - latestEthereumBatch, err = s.ethereum.GetTxBatchNonce(ctx, common.HexToAddress(oldestSignedBatch.TokenContract)) + latestEthereumBatch, err = ethereum.GetTxBatchNonce(ctx, common.HexToAddress(oldestSignedBatch.TokenContract)) if err != nil { - metrics.ReportFuncError(s.svcTags) return err } @@ -229,31 +245,27 @@ func (s *PeggyOrchestrator) relayBatches(ctx context.Context, logger log.Logger) } // Check custom time delay offset - blockResult, err := s.injective.GetBlock(ctx, int64(oldestSignedBatch.Block)) + blockResult, err := injective.GetBlock(ctx, int64(oldestSignedBatch.Block)) if err != nil { return errors.Wrapf(err, "failed to get block %d from Injective", oldestSignedBatch.Block) } - batchCreatedAt := blockResult.Block.Time - customTimeDelay := batchCreatedAt.Add(s.relayBatchOffsetDur) - - if time.Now().Sub(customTimeDelay) <= 0 { + if time.Since(blockResult.Block.Time) <= r.relayBatchOffsetDur { return nil } - logger.WithFields(log.Fields{ + r.log.WithFields(log.Fields{ "inj_batch": oldestSignedBatch.BatchNonce, "eth_batch": latestEthereumBatch.Uint64(), }).Infoln("detected new transaction batch on Injective") // Send SendTransactionBatch to Ethereum - txHash, err := s.ethereum.SendTransactionBatch(ctx, currentValset, oldestSignedBatch, oldestSigs) + txHash, err := ethereum.SendTransactionBatch(ctx, currentValset, oldestSignedBatch, oldestSigs) if err != nil { - metrics.ReportFuncError(s.svcTags) return err } - logger.WithField("tx_hash", txHash.Hex()).Infoln("updated transaction batch on Ethereum") + r.log.WithField("tx_hash", txHash.Hex()).Infoln("updated transaction batch on Ethereum") return nil } @@ -265,26 +277,23 @@ const valsetBlocksToSearch = 2000 // as the latest update will be in recent blockchain history and the search moves from the present // backwards in time. In the case that the validator set has not been updated for a very long time // this will take longer. -func (s *PeggyOrchestrator) findLatestValsetOnEthereum(ctx context.Context, logger log.Logger) (*types.Valset, error) { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - latestHeader, err := s.ethereum.HeaderByNumber(ctx, nil) +func (r *relayer) findLatestValsetOnEth( + ctx context.Context, + injective InjectiveNetwork, + ethereum EthereumNetwork, +) (*types.Valset, error) { + latestHeader, err := ethereum.HeaderByNumber(ctx, nil) if err != nil { - metrics.ReportFuncError(s.svcTags) return nil, errors.Wrap(err, "failed to get latest eth header") } - latestEthereumValsetNonce, err := s.ethereum.GetValsetNonce(ctx) + latestEthereumValsetNonce, err := ethereum.GetValsetNonce(ctx) if err != nil { - metrics.ReportFuncError(s.svcTags) return nil, errors.Wrap(err, "failed to get latest valset nonce on Ethereum") } - cosmosValset, err := s.injective.ValsetAt(ctx, latestEthereumValsetNonce.Uint64()) + cosmosValset, err := injective.ValsetAt(ctx, latestEthereumValsetNonce.Uint64()) if err != nil { - metrics.ReportFuncError(s.svcTags) return nil, errors.Wrap(err, "failed to get Injective valset") } @@ -298,14 +307,13 @@ func (s *PeggyOrchestrator) findLatestValsetOnEthereum(ctx context.Context, logg startSearchBlock = currentBlock - valsetBlocksToSearch } - logger.WithFields(log.Fields{ + r.log.WithFields(log.Fields{ "block_start": startSearchBlock, "block_end": currentBlock, }).Debugln("looking back into Ethereum history to find the last valset update") - valsetUpdatedEvents, err := s.ethereum.GetValsetUpdatedEvents(startSearchBlock, currentBlock) + valsetUpdatedEvents, err := ethereum.GetValsetUpdatedEvents(startSearchBlock, currentBlock) if err != nil { - metrics.ReportFuncError(s.svcTags) return nil, errors.Wrap(err, "failed to filter past ValsetUpdated events from Ethereum") } @@ -320,7 +328,7 @@ func (s *PeggyOrchestrator) findLatestValsetOnEthereum(ctx context.Context, logg continue } - logger.Debugln("found events", valsetUpdatedEvents) + r.log.Debugln("found events", valsetUpdatedEvents) // we take only the first event if we find any at all. event := valsetUpdatedEvents[0] @@ -338,7 +346,8 @@ func (s *PeggyOrchestrator) findLatestValsetOnEthereum(ctx context.Context, logg }) } - s.checkIfValsetsDiffer(cosmosValset, valset) + checkIfValsetsDiffer(cosmosValset, valset) + return valset, nil } @@ -365,7 +374,7 @@ func (a PeggyValsetUpdatedEvents) Swap(i, j int) { a[i], a[j] = a[j], a[i] } // The other (and far worse) way a disagreement here could occur is if validators are colluding to steal // funds from the Peggy contract and have submitted a hijacking update. If slashing for off Cosmos chain // Ethereum signatures is implemented you would put that handler here. -func (s *PeggyOrchestrator) checkIfValsetsDiffer(cosmosValset, ethereumValset *types.Valset) { +func checkIfValsetsDiffer(cosmosValset, ethereumValset *types.Valset) { if cosmosValset == nil && ethereumValset.Nonce == 0 { // bootstrapping case return diff --git a/orchestrator/relayer_test.go b/orchestrator/relayer_test.go index caf0f0b0..43043ab2 100644 --- a/orchestrator/relayer_test.go +++ b/orchestrator/relayer_test.go @@ -25,523 +25,583 @@ func TestValsetRelaying(t *testing.T) { t.Run("failed to fetch latest valsets from injective", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { - return nil, errors.New("fail") - }, + injective := &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return nil, errors.New("fail") }, } - assert.Error(t, orch.relayValsets(context.TODO(), suplog.DefaultLogger)) + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + valsetRelaying: true, + } + + assert.Error(t, rel.relayValsets(context.TODO(), injective, nil)) }) t.Run("failed to fetch confirms for a valset", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { - return []*types.Valset{{}}, nil // non-empty will do - }, - allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { - return nil, errors.New("fail") - }, + inj := &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{{}}, nil // non-empty will do + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return nil, errors.New("fail") }, } - assert.Error(t, orch.relayValsets(context.TODO(), suplog.DefaultLogger)) + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + valsetRelaying: true, + } + + assert.Error(t, rel.relayValsets(context.TODO(), inj, nil)) }) t.Run("no confirms for valset", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { - return []*types.Valset{{}}, nil // non-empty will do - }, - allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { - return nil, nil - }, + inj := &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{{}}, nil // non-empty will do }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return nil, nil + }, + } + + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + valsetRelaying: true, } - assert.NoError(t, orch.relayValsets(context.TODO(), suplog.DefaultLogger)) + assert.NoError(t, rel.relayValsets(context.TODO(), inj, nil)) }) t.Run("failed to get latest ethereum header", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { - return []*types.Valset{{}}, nil // non-empty will do - }, - allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { - return []*types.MsgValsetConfirm{ - { - Nonce: 5, - Orchestrator: "orch", - EthAddress: "eth", - Signature: "sig", - }, - }, nil - }, + inj := &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{{}}, nil // non-empty will do }, - ethereum: mockEthereum{ - headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { - return nil, errors.New("fail") - }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil }, } - assert.Error(t, orch.relayValsets(context.TODO(), suplog.DefaultLogger)) + eth := mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return nil, errors.New("fail") + }, + } + + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + valsetRelaying: true, + } + + assert.Error(t, rel.relayValsets(context.TODO(), inj, eth)) }) t.Run("failed to get latest ethereum header", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { - return []*types.Valset{{}}, nil // non-empty will do - }, - allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { - return []*types.MsgValsetConfirm{ - { - Nonce: 5, - Orchestrator: "orch", - EthAddress: "eth", - Signature: "sig", - }, - }, nil - }, + inj := &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{{}}, nil // non-empty will do }, - ethereum: mockEthereum{ - headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { - return nil, errors.New("fail") - }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil }, } - assert.Error(t, orch.relayValsets(context.TODO(), suplog.DefaultLogger)) + eth := mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return nil, errors.New("fail") + }, + } + + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + valsetRelaying: true, + } + + assert.Error(t, rel.relayValsets(context.TODO(), inj, eth)) }) t.Run("failed to get valset nonce from peggy contract", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { - return []*types.Valset{{}}, nil // non-empty will do - }, - allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { - return []*types.MsgValsetConfirm{ - { - Nonce: 5, - Orchestrator: "orch", - EthAddress: "eth", - Signature: "sig", - }, - }, nil - }, + inj := &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{{}}, nil // non-empty will do + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil + }, + } + + eth := mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(123)}, nil }, - ethereum: mockEthereum{ - headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { - return &ctypes.Header{Number: big.NewInt(123)}, nil - }, - getValsetNonceFn: func(_ context.Context) (*big.Int, error) { - return nil, errors.New("fail") - }, + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return nil, errors.New("fail") }, } - assert.Error(t, orch.relayValsets(context.TODO(), suplog.DefaultLogger)) + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + valsetRelaying: true, + } + + assert.Error(t, rel.relayValsets(context.TODO(), inj, eth)) }) t.Run("failed to get specific valset from injective", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { - return []*types.Valset{{}}, nil // non-empty will do - }, - allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { - return []*types.MsgValsetConfirm{ - { - Nonce: 5, - Orchestrator: "orch", - EthAddress: "eth", - Signature: "sig", - }, - }, nil - }, - valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { - return nil, errors.New("fail") - }, + inj := &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{{}}, nil // non-empty will do + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return nil, errors.New("fail") + }, + } + + eth := mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(123)}, nil }, - ethereum: mockEthereum{ - headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { - return &ctypes.Header{Number: big.NewInt(123)}, nil - }, - getValsetNonceFn: func(_ context.Context) (*big.Int, error) { - return big.NewInt(100), nil - }, + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil }, } - assert.Error(t, orch.relayValsets(context.TODO(), suplog.DefaultLogger)) + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + valsetRelaying: true, + } + + assert.Error(t, rel.relayValsets(context.TODO(), inj, eth)) }) t.Run("failed to get valset update events from ethereum", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { - return []*types.Valset{{}}, nil // non-empty will do - }, - allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { - return []*types.MsgValsetConfirm{ - { - Nonce: 5, - Orchestrator: "orch", - EthAddress: "eth", - Signature: "sig", - }, - }, nil - }, - valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { - return &types.Valset{}, nil // non-empty will do - }, - }, - ethereum: mockEthereum{ - headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { - return &ctypes.Header{Number: big.NewInt(123)}, nil - }, - getValsetNonceFn: func(_ context.Context) (*big.Int, error) { - return big.NewInt(100), nil - }, - getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { - return nil, errors.New("fail") - }, - }, - } - - assert.Error(t, orch.relayValsets(context.TODO(), suplog.DefaultLogger)) + inj := &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{{}}, nil // non-empty will do + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{}, nil // non-empty will do + }, + } + + eth := mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(123)}, nil + }, + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return nil, errors.New("fail") + }, + } + + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + valsetRelaying: true, + } + + assert.Error(t, rel.relayValsets(context.TODO(), inj, eth)) }) t.Run("ethereum valset is not higher than injective valset", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { - return []*types.Valset{ - { - Nonce: 333, - RewardAmount: cosmtypes.NewInt(1000), - RewardToken: "0xfafafafafafafafa", - }, - }, nil - }, - allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { - return []*types.MsgValsetConfirm{ - { - Nonce: 5, - Orchestrator: "orch", - EthAddress: "eth", - Signature: "sig", - }, - }, nil - }, - valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { - return &types.Valset{ + inj := &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{ + { Nonce: 333, RewardAmount: cosmtypes.NewInt(1000), RewardToken: "0xfafafafafafafafa", - }, nil // non-empty will do - }, - }, - ethereum: mockEthereum{ - headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { - return &ctypes.Header{Number: big.NewInt(123)}, nil - }, - getValsetNonceFn: func(_ context.Context) (*big.Int, error) { - return big.NewInt(100), nil - }, - getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { - return []*wrappers.PeggyValsetUpdatedEvent{ - { - NewValsetNonce: big.NewInt(333), - RewardAmount: big.NewInt(1000), - RewardToken: common.HexToAddress("0xfafafafafafafafa"), - }, - }, nil - }, + }, + }, nil + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{ + Nonce: 333, + RewardAmount: cosmtypes.NewInt(1000), + RewardToken: "0xfafafafafafafafa", + }, nil // non-empty will do + }, + } + + eth := mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(123)}, nil + }, + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(333), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xfafafafafafafafa"), + }, + }, nil + }, + } + + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + valsetRelaying: true, } - assert.NoError(t, orch.relayValsets(context.TODO(), suplog.DefaultLogger)) + assert.NoError(t, rel.relayValsets(context.TODO(), inj, eth)) }) t.Run("injective valset is higher than ethereum but failed to get block from injective", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { - return []*types.Valset{ - { - Nonce: 444, - RewardAmount: cosmtypes.NewInt(1000), - RewardToken: "0xfafafafafafafafa", - }, - }, nil - }, - allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { - return []*types.MsgValsetConfirm{ - { - Nonce: 5, - Orchestrator: "orch", - EthAddress: "eth", - Signature: "sig", - }, - }, nil - }, - valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { - return &types.Valset{ - Nonce: 333, + inj := &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{ + { + Nonce: 444, RewardAmount: cosmtypes.NewInt(1000), RewardToken: "0xfafafafafafafafa", - }, nil // non-empty will do - }, - getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { - return nil, errors.New("fail") - }, - }, - ethereum: mockEthereum{ - headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { - return &ctypes.Header{Number: big.NewInt(123)}, nil - }, - getValsetNonceFn: func(_ context.Context) (*big.Int, error) { - return big.NewInt(100), nil - }, - getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { - return []*wrappers.PeggyValsetUpdatedEvent{ - { - NewValsetNonce: big.NewInt(333), - RewardAmount: big.NewInt(1000), - RewardToken: common.HexToAddress("0xfafafafafafafafa"), - }, - }, nil - }, + }, + }, nil + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{ + Nonce: 333, + RewardAmount: cosmtypes.NewInt(1000), + RewardToken: "0xfafafafafafafafa", + }, nil // non-empty will do + }, + getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { + return nil, errors.New("fail") + }, + } + + eth := mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(123)}, nil + }, + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(333), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xfafafafafafafafa"), + }, + }, nil + }, + } + + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + valsetRelaying: true, } - assert.Error(t, orch.relayValsets(context.TODO(), suplog.DefaultLogger)) + assert.Error(t, rel.relayValsets(context.TODO(), inj, eth)) }) t.Run("injective valset is higher than ethereum but valsetOffsetDur has not expired", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - relayValsetOffsetDur: time.Second * 5, - injective: &mockInjective{ - latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { - return []*types.Valset{ - { - Nonce: 444, - RewardAmount: cosmtypes.NewInt(1000), - RewardToken: "0xfafafafafafafafa", - }, - }, nil - }, - allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { - return []*types.MsgValsetConfirm{ - { - Nonce: 5, - Orchestrator: "orch", - EthAddress: "eth", - Signature: "sig", - }, - }, nil - }, - valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { - return &types.Valset{ - Nonce: 333, + inj := &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{ + { + Nonce: 444, RewardAmount: cosmtypes.NewInt(1000), RewardToken: "0xfafafafafafafafa", - }, nil // non-empty will do - }, - getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { - return &tmctypes.ResultBlock{ - Block: &tmtypes.Block{ - Header: tmtypes.Header{ - Time: time.Now().Add(time.Hour), - }, - }, - }, nil - }, - }, - ethereum: mockEthereum{ - headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { - return &ctypes.Header{Number: big.NewInt(123)}, nil - }, - getValsetNonceFn: func(_ context.Context) (*big.Int, error) { - return big.NewInt(100), nil - }, - getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { - return []*wrappers.PeggyValsetUpdatedEvent{ - { - NewValsetNonce: big.NewInt(333), - RewardAmount: big.NewInt(1000), - RewardToken: common.HexToAddress("0xfafafafafafafafa"), + }, + }, nil + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{ + Nonce: 333, + RewardAmount: cosmtypes.NewInt(1000), + RewardToken: "0xfafafafafafafafa", + }, nil // non-empty will do + }, + getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { + return &tmctypes.ResultBlock{ + Block: &tmtypes.Block{ + Header: tmtypes.Header{ + Time: time.Now().Add(time.Hour), }, - }, nil - }, + }, + }, nil + }, + } + + eth := mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(123)}, nil }, + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(333), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xfafafafafafafafa"), + }, + }, nil + }, + } + + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + valsetRelaying: true, + relayValsetOffsetDur: time.Second * 5, } - assert.NoError(t, orch.relayValsets(context.TODO(), suplog.DefaultLogger)) + assert.NoError(t, rel.relayValsets(context.TODO(), inj, eth)) }) t.Run("injective valset is higher than ethereum but failed to send update tx to ethereum", func(t *testing.T) { t.Parallel() - oldTime := time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC) - orch := &PeggyOrchestrator{ - relayValsetOffsetDur: time.Second * 5, - injective: &mockInjective{ - latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { - return []*types.Valset{ - { - Nonce: 444, - RewardAmount: cosmtypes.NewInt(1000), - RewardToken: "0xfafafafafafafafa", - }, - }, nil - }, - allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { - return []*types.MsgValsetConfirm{ - { - Nonce: 5, - Orchestrator: "orch", - EthAddress: "eth", - Signature: "sig", - }, - }, nil - }, - valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { - return &types.Valset{ - Nonce: 333, + inj := &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{ + { + Nonce: 444, RewardAmount: cosmtypes.NewInt(1000), RewardToken: "0xfafafafafafafafa", - }, nil // non-empty will do - }, - getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { - return &tmctypes.ResultBlock{ - Block: &tmtypes.Block{ - Header: tmtypes.Header{ - Time: oldTime, - }, - }, - }, nil - }, - }, - ethereum: mockEthereum{ - headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { - return &ctypes.Header{Number: big.NewInt(123)}, nil - }, - getValsetNonceFn: func(_ context.Context) (*big.Int, error) { - return big.NewInt(100), nil - }, - getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { - return []*wrappers.PeggyValsetUpdatedEvent{ - { - NewValsetNonce: big.NewInt(333), - RewardAmount: big.NewInt(1000), - RewardToken: common.HexToAddress("0xfafafafafafafafa"), + }, + }, nil + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{ + Nonce: 333, + RewardAmount: cosmtypes.NewInt(1000), + RewardToken: "0xfafafafafafafafa", + }, nil // non-empty will do + }, + getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { + return &tmctypes.ResultBlock{ + Block: &tmtypes.Block{ + Header: tmtypes.Header{ + Time: time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC), }, - }, nil - }, - sendEthValsetUpdateFn: func(_ context.Context, _ *types.Valset, _ *types.Valset, _ []*types.MsgValsetConfirm) (*common.Hash, error) { - return nil, errors.New("fail") - }, + }, + }, nil + }, + } + + eth := mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(123)}, nil }, + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(333), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xfafafafafafafafa"), + }, + }, nil + }, + sendEthValsetUpdateFn: func(_ context.Context, _ *types.Valset, _ *types.Valset, _ []*types.MsgValsetConfirm) (*common.Hash, error) { + return nil, errors.New("fail") + }, + } + + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + valsetRelaying: true, + relayValsetOffsetDur: time.Second * 5, } - assert.Error(t, orch.relayValsets(context.TODO(), suplog.DefaultLogger)) + assert.Error(t, rel.relayValsets(context.TODO(), inj, eth)) }) t.Run("new valset update is sent to ethereum", func(t *testing.T) { t.Parallel() - oldTime := time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC) - orch := &PeggyOrchestrator{ - relayValsetOffsetDur: time.Second * 5, - injective: &mockInjective{ - latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { - return []*types.Valset{ - { - Nonce: 444, - RewardAmount: cosmtypes.NewInt(1000), - RewardToken: "0xfafafafafafafafa", - }, - }, nil - }, - allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { - return []*types.MsgValsetConfirm{ - { - Nonce: 5, - Orchestrator: "orch", - EthAddress: "eth", - Signature: "sig", - }, - }, nil - }, - valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { - return &types.Valset{ - Nonce: 333, + inj := &mockInjective{ + latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { + return []*types.Valset{ + { + Nonce: 444, RewardAmount: cosmtypes.NewInt(1000), RewardToken: "0xfafafafafafafafa", - }, nil // non-empty will do - }, - getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { - return &tmctypes.ResultBlock{ - Block: &tmtypes.Block{ - Header: tmtypes.Header{ - Time: oldTime, - }, - }, - }, nil - }, - }, - ethereum: mockEthereum{ - headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { - return &ctypes.Header{Number: big.NewInt(123)}, nil - }, - getValsetNonceFn: func(_ context.Context) (*big.Int, error) { - return big.NewInt(100), nil - }, - getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { - return []*wrappers.PeggyValsetUpdatedEvent{ - { - NewValsetNonce: big.NewInt(333), - RewardAmount: big.NewInt(1000), - RewardToken: common.HexToAddress("0xfafafafafafafafa"), + }, + }, nil + }, + allValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*types.MsgValsetConfirm, error) { + return []*types.MsgValsetConfirm{ + { + Nonce: 5, + Orchestrator: "orch", + EthAddress: "eth", + Signature: "sig", + }, + }, nil + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{ + Nonce: 333, + RewardAmount: cosmtypes.NewInt(1000), + RewardToken: "0xfafafafafafafafa", + }, nil + }, + getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { + return &tmctypes.ResultBlock{ + Block: &tmtypes.Block{ + Header: tmtypes.Header{ + Time: time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC), }, - }, nil - }, - sendEthValsetUpdateFn: func(_ context.Context, _ *types.Valset, _ *types.Valset, _ []*types.MsgValsetConfirm) (*common.Hash, error) { - return &common.Hash{}, nil - }, + }, + }, nil + }, + } + + eth := mockEthereum{ + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(123)}, nil + }, + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(333), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xfafafafafafafafa"), + }, + }, nil + }, + sendEthValsetUpdateFn: func(_ context.Context, _ *types.Valset, _ *types.Valset, _ []*types.MsgValsetConfirm) (*common.Hash, error) { + return &common.Hash{}, nil }, } - assert.NoError(t, orch.relayValsets(context.TODO(), suplog.DefaultLogger)) + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + valsetRelaying: true, + relayValsetOffsetDur: time.Second * 5, + } + + assert.NoError(t, rel.relayValsets(context.TODO(), inj, eth)) }) } @@ -551,480 +611,542 @@ func TestBatchRelaying(t *testing.T) { t.Run("failed to get latest batches from injective", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { - return nil, errors.New("fail") - }, + inj := &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return nil, errors.New("fail") }, } - assert.Error(t, orch.relayBatches(context.TODO(), suplog.DefaultLogger)) + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + batchRelaying: true, + } + + assert.Error(t, rel.relayBatches(context.TODO(), inj, nil)) }) t.Run("failed to get latest batches from injective", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { - return []*types.OutgoingTxBatch{{}}, nil // non-empty will do - }, - transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { - return nil, errors.New("fail") - }, + inj := &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{{}}, nil // non-empty will do + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return nil, errors.New("fail") }, } - assert.Error(t, orch.relayBatches(context.TODO(), suplog.DefaultLogger)) + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + batchRelaying: true, + } + + assert.Error(t, rel.relayBatches(context.TODO(), inj, nil)) }) t.Run("no batch confirms", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { - return []*types.OutgoingTxBatch{{}}, nil // non-empty will do - }, - transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { - return nil, nil - }, + inj := &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{{}}, nil // non-empty will do }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return nil, nil + }, + } + + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + batchRelaying: true, } - assert.NoError(t, orch.relayBatches(context.TODO(), suplog.DefaultLogger)) + assert.NoError(t, rel.relayBatches(context.TODO(), inj, nil)) }) t.Run("failed to get batch nonce from ethereum", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { - return []*types.OutgoingTxBatch{{}}, nil // non-empty will do - }, - transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { - return []*types.MsgConfirmBatch{{}}, nil // non-nil will do - }, + inj := &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{{}}, nil // non-empty will do }, - ethereum: mockEthereum{ - getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { - return nil, errors.New("fail") - }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do }, } - assert.Error(t, orch.relayBatches(context.TODO(), suplog.DefaultLogger)) + eth := mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return nil, errors.New("fail") + }, + } + + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + batchRelaying: true, + } + + assert.Error(t, rel.relayBatches(context.TODO(), inj, eth)) }) t.Run("failed to get latest ethereum header", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { - return []*types.OutgoingTxBatch{ - { - TokenContract: "tokenContract", - BatchNonce: 100, - }, - }, nil - }, - transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { - return []*types.MsgConfirmBatch{{}}, nil // non-nil will do - }, + inj := &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{ + { + TokenContract: "tokenContract", + BatchNonce: 100, + }, + }, nil }, - ethereum: mockEthereum{ - getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { - return big.NewInt(99), nil - }, - headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { - return nil, errors.New("fail") - }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do }, } - assert.Error(t, orch.relayBatches(context.TODO(), suplog.DefaultLogger)) + eth := mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return big.NewInt(99), nil + }, + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return nil, errors.New("fail") + }, + } + + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + batchRelaying: true, + } + + assert.Error(t, rel.relayBatches(context.TODO(), inj, eth)) }) t.Run("failed to get valset nonce from ethereum", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { - return []*types.OutgoingTxBatch{ - { - TokenContract: "tokenContract", - BatchNonce: 100, - }, - }, nil - }, - transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { - return []*types.MsgConfirmBatch{{}}, nil // non-nil will do - }, + inj := &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{ + { + TokenContract: "tokenContract", + BatchNonce: 100, + }, + }, nil + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do + }, + } + + eth := mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return big.NewInt(99), nil + }, + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(100)}, nil }, - ethereum: mockEthereum{ - getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { - return big.NewInt(99), nil - }, - headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { - return &ctypes.Header{Number: big.NewInt(100)}, nil - }, - getValsetNonceFn: func(_ context.Context) (*big.Int, error) { - return nil, errors.New("fail") - }, + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return nil, errors.New("fail") }, } - assert.Error(t, orch.relayBatches(context.TODO(), suplog.DefaultLogger)) + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + batchRelaying: true, + } + + assert.Error(t, rel.relayBatches(context.TODO(), inj, eth)) }) t.Run("failed to get specific valset from injective", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { - return []*types.OutgoingTxBatch{ - { - TokenContract: "tokenContract", - BatchNonce: 100, - }, - }, nil - }, - transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { - return []*types.MsgConfirmBatch{{}}, nil // non-nil will do - }, - valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { - return nil, errors.New("fail") - }, - }, - ethereum: mockEthereum{ - getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { - return big.NewInt(99), nil - }, - headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { - return &ctypes.Header{Number: big.NewInt(100)}, nil - }, - - getValsetNonceFn: func(_ context.Context) (*big.Int, error) { - return big.NewInt(100), nil - }, - }, - } - - assert.Error(t, orch.relayBatches(context.TODO(), suplog.DefaultLogger)) + inj := &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{ + { + TokenContract: "tokenContract", + BatchNonce: 100, + }, + }, nil + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return nil, errors.New("fail") + }, + } + + eth := mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return big.NewInt(99), nil + }, + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(100)}, nil + }, + + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + } + + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + batchRelaying: true, + } + + assert.Error(t, rel.relayBatches(context.TODO(), inj, eth)) }) t.Run("failed to get valset updated events from ethereum", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { - return []*types.OutgoingTxBatch{ - { - TokenContract: "tokenContract", - BatchNonce: 100, - }, - }, nil - }, - transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { - return []*types.MsgConfirmBatch{{}}, nil // non-nil will do - }, - valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { - return &types.Valset{}, nil - }, - }, - ethereum: mockEthereum{ - getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { - return big.NewInt(99), nil - }, - headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { - return &ctypes.Header{Number: big.NewInt(100)}, nil - }, - - getValsetNonceFn: func(_ context.Context) (*big.Int, error) { - return big.NewInt(100), nil - }, - getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { - return nil, errors.New("fail") - }, - }, - } - - assert.Error(t, orch.relayBatches(context.TODO(), suplog.DefaultLogger)) + inj := &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{ + { + TokenContract: "tokenContract", + BatchNonce: 100, + }, + }, nil + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{}, nil + }, + } + + eth := mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return big.NewInt(99), nil + }, + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(100)}, nil + }, + + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return nil, errors.New("fail") + }, + } + + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + batchRelaying: true, + } + + assert.Error(t, rel.relayBatches(context.TODO(), inj, eth)) }) t.Run("ethereum batch is not lower than injective batch", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { - return []*types.OutgoingTxBatch{ - { - TokenContract: "tokenContract", - BatchNonce: 202, - }, - }, nil - }, - transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { - return []*types.MsgConfirmBatch{{}}, nil // non-nil will do - }, - valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { - return &types.Valset{Nonce: 202}, nil - }, - }, - ethereum: mockEthereum{ - getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { - return big.NewInt(202), nil - }, - headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { - return &ctypes.Header{Number: big.NewInt(100)}, nil - }, - - getValsetNonceFn: func(_ context.Context) (*big.Int, error) { - return big.NewInt(100), nil - }, - getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { - return []*wrappers.PeggyValsetUpdatedEvent{ - { - NewValsetNonce: big.NewInt(202), - RewardAmount: big.NewInt(1000), - RewardToken: common.HexToAddress("0xcafecafecafecafe"), - }, - }, nil - }, + inj := &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{ + { + TokenContract: "tokenContract", + BatchNonce: 202, + }, + }, nil + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{Nonce: 202}, nil + }, + } + + eth := mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return big.NewInt(202), nil + }, + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(100)}, nil + }, + + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(202), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xcafecafecafecafe"), + }, + }, nil }, } - assert.NoError(t, orch.relayBatches(context.TODO(), suplog.DefaultLogger)) + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + batchRelaying: true, + } + + assert.NoError(t, rel.relayBatches(context.TODO(), inj, eth)) }) t.Run("ethereum batch is lower than injective batch but failed to get block from injhective", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - injective: &mockInjective{ - latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { - return []*types.OutgoingTxBatch{ - { - TokenContract: "tokenContract", - BatchNonce: 202, - }, - }, nil - }, - transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { - return []*types.MsgConfirmBatch{{}}, nil // non-nil will do - }, - valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { - return &types.Valset{Nonce: 202}, nil - }, - getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { - return nil, errors.New("fail") - }, - }, - ethereum: mockEthereum{ - getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { - return big.NewInt(201), nil - }, - headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { - return &ctypes.Header{Number: big.NewInt(100)}, nil - }, - - getValsetNonceFn: func(_ context.Context) (*big.Int, error) { - return big.NewInt(100), nil - }, - getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { - return []*wrappers.PeggyValsetUpdatedEvent{ - { - NewValsetNonce: big.NewInt(202), - RewardAmount: big.NewInt(1000), - RewardToken: common.HexToAddress("0xcafecafecafecafe"), - }, - }, nil - }, + inj := &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{ + { + TokenContract: "tokenContract", + BatchNonce: 202, + }, + }, nil + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{Nonce: 202}, nil + }, + getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { + return nil, errors.New("fail") + }, + } + + eth := mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return big.NewInt(201), nil + }, + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(100)}, nil + }, + + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(202), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xcafecafecafecafe"), + }, + }, nil }, } - assert.Error(t, orch.relayBatches(context.TODO(), suplog.DefaultLogger)) + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + batchRelaying: true, + } + + assert.Error(t, rel.relayBatches(context.TODO(), inj, eth)) }) t.Run("ethereum batch is lower than injective batch but relayBatchOffsetDur has not expired", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - relayBatchOffsetDur: 5 * time.Second, - injective: &mockInjective{ - latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { - return []*types.OutgoingTxBatch{ - { - TokenContract: "tokenContract", - BatchNonce: 202, - }, - }, nil - }, - transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { - return []*types.MsgConfirmBatch{{}}, nil // non-nil will do - }, - valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { - return &types.Valset{Nonce: 202}, nil - }, - getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { - return &tmctypes.ResultBlock{ - Block: &tmtypes.Block{ - Header: tmtypes.Header{ - Time: time.Now().Add(time.Hour), - }, - }, - }, nil - }, - }, - ethereum: mockEthereum{ - getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { - return big.NewInt(201), nil - }, - headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { - return &ctypes.Header{Number: big.NewInt(100)}, nil - }, - - getValsetNonceFn: func(_ context.Context) (*big.Int, error) { - return big.NewInt(100), nil - }, - getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { - return []*wrappers.PeggyValsetUpdatedEvent{ - { - NewValsetNonce: big.NewInt(202), - RewardAmount: big.NewInt(1000), - RewardToken: common.HexToAddress("0xcafecafecafecafe"), + inj := &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{ + { + TokenContract: "tokenContract", + BatchNonce: 202, + }, + }, nil + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{Nonce: 202}, nil + }, + getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { + return &tmctypes.ResultBlock{ + Block: &tmtypes.Block{ + Header: tmtypes.Header{ + Time: time.Now().Add(time.Hour), }, - }, nil - }, + }, + }, nil + }, + } + + eth := mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return big.NewInt(201), nil + }, + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(100)}, nil }, + + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(202), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xcafecafecafecafe"), + }, + }, nil + }, + } + + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + batchRelaying: true, + relayBatchOffsetDur: 5 * time.Second, } - assert.NoError(t, orch.relayBatches(context.TODO(), suplog.DefaultLogger)) + assert.NoError(t, rel.relayBatches(context.TODO(), inj, eth)) }) t.Run("ethereum batch is lower than injective batch but failed to send batch update", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - relayBatchOffsetDur: 5 * time.Second, - injective: &mockInjective{ - latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { - return []*types.OutgoingTxBatch{ - { - TokenContract: "tokenContract", - BatchNonce: 202, - }, - }, nil - }, - transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { - return []*types.MsgConfirmBatch{{}}, nil // non-nil will do - }, - valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { - return &types.Valset{Nonce: 202}, nil - }, - getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { - return &tmctypes.ResultBlock{ - Block: &tmtypes.Block{ - Header: tmtypes.Header{ - Time: time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC), - }, - }, - }, nil - }, - }, - ethereum: mockEthereum{ - getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { - return big.NewInt(201), nil - }, - headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { - return &ctypes.Header{Number: big.NewInt(100)}, nil - }, - - getValsetNonceFn: func(_ context.Context) (*big.Int, error) { - return big.NewInt(100), nil - }, - getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { - return []*wrappers.PeggyValsetUpdatedEvent{ - { - NewValsetNonce: big.NewInt(202), - RewardAmount: big.NewInt(1000), - RewardToken: common.HexToAddress("0xcafecafecafecafe"), + inj := &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{ + { + TokenContract: "tokenContract", + BatchNonce: 202, + }, + }, nil + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{Nonce: 202}, nil + }, + getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { + return &tmctypes.ResultBlock{ + Block: &tmtypes.Block{ + Header: tmtypes.Header{ + Time: time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC), }, - }, nil - }, - sendTransactionBatchFn: func(_ context.Context, _ *types.Valset, _ *types.OutgoingTxBatch, _ []*types.MsgConfirmBatch) (*common.Hash, error) { - return nil, errors.New("fail") - }, + }, + }, nil + }, + } + + eth := mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return big.NewInt(201), nil + }, + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(100)}, nil + }, + + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(202), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xcafecafecafecafe"), + }, + }, nil + }, + sendTransactionBatchFn: func(_ context.Context, _ *types.Valset, _ *types.OutgoingTxBatch, _ []*types.MsgConfirmBatch) (*common.Hash, error) { + return nil, errors.New("fail") }, } - assert.Error(t, orch.relayBatches(context.TODO(), suplog.DefaultLogger)) + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + batchRelaying: true, + relayBatchOffsetDur: 5 * time.Second, + } + + assert.Error(t, rel.relayBatches(context.TODO(), inj, eth)) }) t.Run("sending a batch update to ethereum", func(t *testing.T) { t.Parallel() - orch := &PeggyOrchestrator{ - relayBatchOffsetDur: 5 * time.Second, - injective: &mockInjective{ - latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { - return []*types.OutgoingTxBatch{ - { - TokenContract: "tokenContract", - BatchNonce: 202, - }, - }, nil - }, - transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { - return []*types.MsgConfirmBatch{{}}, nil // non-nil will do - }, - valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { - return &types.Valset{Nonce: 202}, nil - }, - getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { - return &tmctypes.ResultBlock{ - Block: &tmtypes.Block{ - Header: tmtypes.Header{ - Time: time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC), - }, - }, - }, nil - }, - }, - ethereum: mockEthereum{ - getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { - return big.NewInt(201), nil - }, - headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { - return &ctypes.Header{Number: big.NewInt(100)}, nil - }, - - getValsetNonceFn: func(_ context.Context) (*big.Int, error) { - return big.NewInt(100), nil - }, - getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { - return []*wrappers.PeggyValsetUpdatedEvent{ - { - NewValsetNonce: big.NewInt(202), - RewardAmount: big.NewInt(1000), - RewardToken: common.HexToAddress("0xcafecafecafecafe"), + inj := &mockInjective{ + latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { + return []*types.OutgoingTxBatch{ + { + TokenContract: "tokenContract", + BatchNonce: 202, + }, + }, nil + }, + transactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ common.Address) ([]*types.MsgConfirmBatch, error) { + return []*types.MsgConfirmBatch{{}}, nil // non-nil will do + }, + valsetAtFn: func(_ context.Context, _ uint64) (*types.Valset, error) { + return &types.Valset{Nonce: 202}, nil + }, + getBlockFn: func(_ context.Context, _ int64) (*tmctypes.ResultBlock, error) { + return &tmctypes.ResultBlock{ + Block: &tmtypes.Block{ + Header: tmtypes.Header{ + Time: time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC), }, - }, nil - }, - sendTransactionBatchFn: func(_ context.Context, _ *types.Valset, _ *types.OutgoingTxBatch, _ []*types.MsgConfirmBatch) (*common.Hash, error) { - return &common.Hash{}, nil - }, + }, + }, nil + }, + } + + eth := mockEthereum{ + getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { + return big.NewInt(201), nil }, + headerByNumberFn: func(_ context.Context, _ *big.Int) (*ctypes.Header, error) { + return &ctypes.Header{Number: big.NewInt(100)}, nil + }, + + getValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(100), nil + }, + getValsetUpdatedEventsFn: func(_ uint64, _ uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { + return []*wrappers.PeggyValsetUpdatedEvent{ + { + NewValsetNonce: big.NewInt(202), + RewardAmount: big.NewInt(1000), + RewardToken: common.HexToAddress("0xcafecafecafecafe"), + }, + }, nil + }, + sendTransactionBatchFn: func(_ context.Context, _ *types.Valset, _ *types.OutgoingTxBatch, _ []*types.MsgConfirmBatch) (*common.Hash, error) { + return &common.Hash{}, nil + }, + } + + rel := &relayer{ + log: suplog.DefaultLogger, + retries: 1, + batchRelaying: true, + relayBatchOffsetDur: 5 * time.Second, } - assert.NoError(t, orch.relayBatches(context.TODO(), suplog.DefaultLogger)) + assert.NoError(t, rel.relayBatches(context.TODO(), inj, eth)) }) } From 87d0e7a311ce3a493dfdb51d25c1dc3c83855dd7 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Tue, 11 Jul 2023 11:39:02 +0200 Subject: [PATCH 57/72] remove min fee hardcode --- cmd/peggo/orchestrator.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/cmd/peggo/orchestrator.go b/cmd/peggo/orchestrator.go index 9d9b7763..e8c111e4 100644 --- a/cmd/peggo/orchestrator.go +++ b/cmd/peggo/orchestrator.go @@ -40,7 +40,7 @@ func orchestratorCmd(cmd *cli.Cmd) { "build_date": version.BuildDate, "go_version": version.GoVersion, "go_arch": version.GoArch, - }).Infoln("peggo - injectived ethereum bridge") + }).Infoln("peggo - injectived bridge binary for Ethereum") if *cfg.cosmosUseLedger || *cfg.ethUseLedger { log.Fatalln("cannot use Ledger for peggo, since signatures must be realtime") @@ -72,8 +72,8 @@ func orchestratorCmd(cmd *cli.Cmd) { } log.WithFields(log.Fields{ - "injective_addr": valAddress.String(), - "ethereum_addr": ethKeyFromAddress.String(), + "inj_addr": valAddress.String(), + "eth_addr": ethKeyFromAddress.String(), }).Infoln("starting peggo service") // Connect to Injective network @@ -125,9 +125,6 @@ func orchestratorCmd(cmd *cli.Cmd) { coingeckoFeed := coingecko.NewCoingeckoPriceFeed(100, &coingecko.Config{BaseURL: *cfg.coingeckoApi}) - // make the flag obsolete and hardcode - *cfg.minBatchFeeUSD = 49.0 - // Create peggo and run it peggo, err := orchestrator.NewPeggyOrchestrator( injNetwork, From 6e6cf2b7e5c8de82c2dd9fa5e573d797626e9bcf Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Tue, 11 Jul 2023 11:47:16 +0200 Subject: [PATCH 58/72] rename symbol --- orchestrator/batch_request.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/orchestrator/batch_request.go b/orchestrator/batch_request.go index 24118ce9..7537c2f4 100644 --- a/orchestrator/batch_request.go +++ b/orchestrator/batch_request.go @@ -41,29 +41,29 @@ func (r *batchRequester) run( ) error { r.log.WithField("min_batch_fee", r.minBatchFee).Infoln("scanning Injective for potential batches") - unbatchedTokensWithFees, err := r.getBatchFeesByToken(ctx, injective) + unbatchedFees, err := r.getUnbatchedFeesByToken(ctx, injective) if err != nil { // non-fatal, just alert r.log.WithError(err).Warningln("unable to get unbatched fees from Injective") return nil } - if len(unbatchedTokensWithFees) == 0 { + if len(unbatchedFees) == 0 { r.log.Debugln("no outgoing withdrawals or minimum batch fee is not met") return nil } - for _, unbatchedToken := range unbatchedTokensWithFees { - r.requestBatchCreation(ctx, injective, feed, unbatchedToken) + for _, tokenFee := range unbatchedFees { + r.requestBatchCreation(ctx, injective, feed, tokenFee) } return nil } -func (r *batchRequester) getBatchFeesByToken(ctx context.Context, injective InjectiveNetwork) ([]*types.BatchFees, error) { - var unbatchedTokensWithFees []*types.BatchFees +func (r *batchRequester) getUnbatchedFeesByToken(ctx context.Context, injective InjectiveNetwork) ([]*types.BatchFees, error) { + var unbatchedFees []*types.BatchFees retryFn := func() (err error) { - unbatchedTokensWithFees, err = injective.UnbatchedTokenFees(ctx) + unbatchedFees, err = injective.UnbatchedTokenFees(ctx) return err } @@ -77,7 +77,7 @@ func (r *batchRequester) getBatchFeesByToken(ctx context.Context, injective Inje return nil, err } - return unbatchedTokensWithFees, nil + return unbatchedFees, nil } func (r *batchRequester) requestBatchCreation( From 2a95f20169dd54843a429808eca1826e17dd4210 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Tue, 11 Jul 2023 11:59:01 +0200 Subject: [PATCH 59/72] log/rename --- orchestrator/relayer.go | 38 +++++++++++++++++++++----------------- orchestrator/signer.go | 21 ++++++++++++--------- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index 45fd2f0a..c4bd83aa 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -97,28 +97,28 @@ func (r *relayer) relayValsets( // to Ethereum for that we will find the latest confirmed valset and compare it to the ethereum chain latestValsets, err := injective.LatestValsets(ctx) if err != nil { - return errors.Wrap(err, "failed to fetch latest valsets from Injective") + return errors.Wrap(err, "failed to get latest valsets from Injective") } var ( - latestCosmosSigs []*types.MsgValsetConfirm - latestCosmosConfirmed *types.Valset + latestInjectiveConfirmed *types.Valset + latestInjectiveConfirmedSigs []*types.MsgValsetConfirm ) for _, set := range latestValsets { sigs, err := injective.AllValsetConfirms(ctx, set.Nonce) if err != nil { - return errors.Wrapf(err, "failed to get valset confirms at nonce %d", set.Nonce) + return errors.Wrapf(err, "failed to get valset confirmations for nonce %d", set.Nonce) } else if len(sigs) == 0 { continue } - latestCosmosSigs = sigs - latestCosmosConfirmed = set + latestInjectiveConfirmedSigs = sigs + latestInjectiveConfirmed = set break } - if latestCosmosConfirmed == nil { + if latestInjectiveConfirmed == nil { r.log.Debugln("no confirmed valsets found on Injective, nothing to relay...") return nil } @@ -129,11 +129,11 @@ func (r *relayer) relayValsets( } r.log.WithFields(log.Fields{ - "inj_valset": latestCosmosConfirmed, + "inj_valset": latestInjectiveConfirmed, "eth_valset": currentEthValset, }).Debugln("latest valsets") - if latestCosmosConfirmed.Nonce <= currentEthValset.Nonce { + if latestInjectiveConfirmed.Nonce <= currentEthValset.Nonce { return nil } @@ -143,30 +143,32 @@ func (r *relayer) relayValsets( } // Check if other validators already updated the valset - if latestCosmosConfirmed.Nonce <= latestEthereumValsetNonce.Uint64() { + if latestInjectiveConfirmed.Nonce <= latestEthereumValsetNonce.Uint64() { return nil } // Check custom time delay offset - blockResult, err := injective.GetBlock(ctx, int64(latestCosmosConfirmed.Height)) + blockResult, err := injective.GetBlock(ctx, int64(latestInjectiveConfirmed.Height)) if err != nil { - return errors.Wrapf(err, "failed to get block %d from Injective", latestCosmosConfirmed.Height) + return errors.Wrapf(err, "failed to get block %d from Injective", latestInjectiveConfirmed.Height) } - if time.Since(blockResult.Block.Time) <= r.relayValsetOffsetDur { + if timeElapsed := time.Since(blockResult.Block.Time); timeElapsed <= r.relayValsetOffsetDur { + timeRemaining := time.Duration(int64(r.relayBatchOffsetDur) - int64(timeElapsed)) + r.log.WithField("time_remaining", timeRemaining.String()).Debugln("valset relay offset duration not expired") return nil } r.log.WithFields(log.Fields{ - "inj_valset": latestCosmosConfirmed.Nonce, + "inj_valset": latestInjectiveConfirmed.Nonce, "eth_valset": latestEthereumValsetNonce.Uint64(), }).Infoln("detected new valset on Injective") txHash, err := ethereum.SendEthValsetUpdate( ctx, currentEthValset, - latestCosmosConfirmed, - latestCosmosSigs, + latestInjectiveConfirmed, + latestInjectiveConfirmedSigs, ) if err != nil { @@ -250,7 +252,9 @@ func (r *relayer) relayBatches( return errors.Wrapf(err, "failed to get block %d from Injective", oldestSignedBatch.Block) } - if time.Since(blockResult.Block.Time) <= r.relayBatchOffsetDur { + if timeElapsed := time.Since(blockResult.Block.Time); timeElapsed <= r.relayValsetOffsetDur { + timeRemaining := time.Duration(int64(r.relayBatchOffsetDur) - int64(timeElapsed)) + r.log.WithField("time_remaining", timeRemaining.String()).Debugln("batch relay offset duration not expired") return nil } diff --git a/orchestrator/signer.go b/orchestrator/signer.go index b8063c9c..873d9636 100644 --- a/orchestrator/signer.go +++ b/orchestrator/signer.go @@ -86,7 +86,7 @@ func (s *ethSigner) signNewBatches(ctx context.Context, injective InjectiveNetwo } if oldestUnsignedTransactionBatch == nil { - s.log.Debugln("no new transaction batch waiting to be signed") + s.log.Debugln("no batch waiting to be confirmed") return nil } @@ -113,8 +113,9 @@ func (s *ethSigner) getUnsignedBatch(ctx context.Context, injective InjectiveNet retry.Context(ctx), retry.Attempts(s.retries), retry.OnRetry(func(n uint, err error) { - s.log.WithError(err).Warningf("failed to get unsigned transaction batch, will retry (%d)", n) - })); err != nil { + s.log.WithError(err).Warningf("failed to get unconfirmed batch, will retry (%d)", n) + }), + ); err != nil { s.log.WithError(err).Errorln("got error, loop exits") return nil, err } @@ -127,11 +128,12 @@ func (s *ethSigner) signBatch( injective InjectiveNetwork, batch *types.OutgoingTxBatch, ) error { - if err := retry.Do(func() error { return injective.SendBatchConfirm(ctx, s.peggyID, batch, s.ethFrom) }, + if err := retry.Do( + func() error { return injective.SendBatchConfirm(ctx, s.peggyID, batch, s.ethFrom) }, retry.Context(ctx), retry.Attempts(s.retries), retry.OnRetry(func(n uint, err error) { - s.log.WithError(err).Warningf("failed to sign and send batch confirmation to Injective, will retry (%d)", n) + s.log.WithError(err).Warningf("failed to confirm batch on Injective, will retry (%d)", n) }), ); err != nil { s.log.WithError(err).Errorln("got error, loop exits") @@ -155,7 +157,7 @@ func (s *ethSigner) signNewValsetUpdates( } if len(oldestUnsignedValsets) == 0 { - s.log.Debugln("no new valset updates waiting to be signed") + s.log.Debugln("no valset updates waiting to be confirmed") return nil } @@ -183,7 +185,7 @@ func (s *ethSigner) getUnsignedValsets(ctx context.Context, injective InjectiveN retry.Context(ctx), retry.Attempts(s.retries), retry.OnRetry(func(n uint, err error) { - s.log.WithError(err).Warningf("failed to get unsigned valsets, will retry (%d)", n) + s.log.WithError(err).Warningf("failed to get unconfirmed valsets, will retry (%d)", n) }), ); err != nil { s.log.WithError(err).Errorln("got error, loop exits") @@ -198,11 +200,12 @@ func (s *ethSigner) signValset( injective InjectiveNetwork, vs *types.Valset, ) error { - if err := retry.Do(func() error { return injective.SendValsetConfirm(ctx, s.peggyID, vs, s.ethFrom) }, + if err := retry.Do( + func() error { return injective.SendValsetConfirm(ctx, s.peggyID, vs, s.ethFrom) }, retry.Context(ctx), retry.Attempts(s.retries), retry.OnRetry(func(n uint, err error) { - s.log.WithError(err).Warningf("failed to sign and send valset confirmation to Injective, will retry (%d)", n) + s.log.WithError(err).Warningf("failed to confirm valset update on Injective, will retry (%d)", n) }), ); err != nil { s.log.WithError(err).Errorln("got error, loop exits") From 541632fc99e90308bc066de8ac4567d93677cb72 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Tue, 11 Jul 2023 11:59:01 +0200 Subject: [PATCH 60/72] log/rename --- orchestrator/relayer.go | 66 ++++++++++++++++++++++------------------- orchestrator/signer.go | 21 +++++++------ 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index 45fd2f0a..5c3b2824 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -97,28 +97,28 @@ func (r *relayer) relayValsets( // to Ethereum for that we will find the latest confirmed valset and compare it to the ethereum chain latestValsets, err := injective.LatestValsets(ctx) if err != nil { - return errors.Wrap(err, "failed to fetch latest valsets from Injective") + return errors.Wrap(err, "failed to get latest valsets from Injective") } var ( - latestCosmosSigs []*types.MsgValsetConfirm - latestCosmosConfirmed *types.Valset + oldestConfirmedValset *types.Valset + oldestConfirmedValsetSigs []*types.MsgValsetConfirm ) for _, set := range latestValsets { sigs, err := injective.AllValsetConfirms(ctx, set.Nonce) if err != nil { - return errors.Wrapf(err, "failed to get valset confirms at nonce %d", set.Nonce) + return errors.Wrapf(err, "failed to get valset confirmations for nonce %d", set.Nonce) } else if len(sigs) == 0 { continue } - latestCosmosSigs = sigs - latestCosmosConfirmed = set + oldestConfirmedValsetSigs = sigs + oldestConfirmedValset = set break } - if latestCosmosConfirmed == nil { + if oldestConfirmedValset == nil { r.log.Debugln("no confirmed valsets found on Injective, nothing to relay...") return nil } @@ -129,11 +129,11 @@ func (r *relayer) relayValsets( } r.log.WithFields(log.Fields{ - "inj_valset": latestCosmosConfirmed, + "inj_valset": oldestConfirmedValset, "eth_valset": currentEthValset, }).Debugln("latest valsets") - if latestCosmosConfirmed.Nonce <= currentEthValset.Nonce { + if oldestConfirmedValset.Nonce <= currentEthValset.Nonce { return nil } @@ -143,30 +143,32 @@ func (r *relayer) relayValsets( } // Check if other validators already updated the valset - if latestCosmosConfirmed.Nonce <= latestEthereumValsetNonce.Uint64() { + if oldestConfirmedValset.Nonce <= latestEthereumValsetNonce.Uint64() { return nil } // Check custom time delay offset - blockResult, err := injective.GetBlock(ctx, int64(latestCosmosConfirmed.Height)) + blockResult, err := injective.GetBlock(ctx, int64(oldestConfirmedValset.Height)) if err != nil { - return errors.Wrapf(err, "failed to get block %d from Injective", latestCosmosConfirmed.Height) + return errors.Wrapf(err, "failed to get block %d from Injective", oldestConfirmedValset.Height) } - if time.Since(blockResult.Block.Time) <= r.relayValsetOffsetDur { + if timeElapsed := time.Since(blockResult.Block.Time); timeElapsed <= r.relayValsetOffsetDur { + timeRemaining := time.Duration(int64(r.relayBatchOffsetDur) - int64(timeElapsed)) + r.log.WithField("time_remaining", timeRemaining.String()).Debugln("valset relay offset duration not expired") return nil } r.log.WithFields(log.Fields{ - "inj_valset": latestCosmosConfirmed.Nonce, + "inj_valset": oldestConfirmedValset.Nonce, "eth_valset": latestEthereumValsetNonce.Uint64(), }).Infoln("detected new valset on Injective") txHash, err := ethereum.SendEthValsetUpdate( ctx, currentEthValset, - latestCosmosConfirmed, - latestCosmosSigs, + oldestConfirmedValset, + oldestConfirmedValsetSigs, ) if err != nil { @@ -189,8 +191,8 @@ func (r *relayer) relayBatches( } var ( - oldestSignedBatch *types.OutgoingTxBatch - oldestSigs []*types.MsgConfirmBatch + oldestConfirmedBatch *types.OutgoingTxBatch + oldestConfirmedBatchSigs []*types.MsgConfirmBatch ) for _, batch := range latestBatches { @@ -201,18 +203,18 @@ func (r *relayer) relayBatches( continue } - oldestSignedBatch = batch - oldestSigs = sigs + oldestConfirmedBatch = batch + oldestConfirmedBatchSigs = sigs } - if oldestSignedBatch == nil { + if oldestConfirmedBatch == nil { r.log.Debugln("no confirmed transaction batches on Injective, nothing to relay...") return nil } latestEthereumBatch, err := ethereum.GetTxBatchNonce( ctx, - common.HexToAddress(oldestSignedBatch.TokenContract), + common.HexToAddress(oldestConfirmedBatch.TokenContract), ) if err != nil { return err @@ -226,41 +228,43 @@ func (r *relayer) relayBatches( } r.log.WithFields(log.Fields{ - "inj_batch": oldestSignedBatch.BatchNonce, + "inj_batch": oldestConfirmedBatch.BatchNonce, "eth_batch": latestEthereumBatch.Uint64(), }).Debugln("latest batches") - if oldestSignedBatch.BatchNonce <= latestEthereumBatch.Uint64() { + if oldestConfirmedBatch.BatchNonce <= latestEthereumBatch.Uint64() { return nil } - latestEthereumBatch, err = ethereum.GetTxBatchNonce(ctx, common.HexToAddress(oldestSignedBatch.TokenContract)) + latestEthereumBatch, err = ethereum.GetTxBatchNonce(ctx, common.HexToAddress(oldestConfirmedBatch.TokenContract)) if err != nil { return err } // Check if ethereum batch was updated by other validators - if oldestSignedBatch.BatchNonce <= latestEthereumBatch.Uint64() { + if oldestConfirmedBatch.BatchNonce <= latestEthereumBatch.Uint64() { return nil } // Check custom time delay offset - blockResult, err := injective.GetBlock(ctx, int64(oldestSignedBatch.Block)) + blockResult, err := injective.GetBlock(ctx, int64(oldestConfirmedBatch.Block)) if err != nil { - return errors.Wrapf(err, "failed to get block %d from Injective", oldestSignedBatch.Block) + return errors.Wrapf(err, "failed to get block %d from Injective", oldestConfirmedBatch.Block) } - if time.Since(blockResult.Block.Time) <= r.relayBatchOffsetDur { + if timeElapsed := time.Since(blockResult.Block.Time); timeElapsed <= r.relayValsetOffsetDur { + timeRemaining := time.Duration(int64(r.relayBatchOffsetDur) - int64(timeElapsed)) + r.log.WithField("time_remaining", timeRemaining.String()).Debugln("batch relay offset duration not expired") return nil } r.log.WithFields(log.Fields{ - "inj_batch": oldestSignedBatch.BatchNonce, + "inj_batch": oldestConfirmedBatch.BatchNonce, "eth_batch": latestEthereumBatch.Uint64(), }).Infoln("detected new transaction batch on Injective") // Send SendTransactionBatch to Ethereum - txHash, err := ethereum.SendTransactionBatch(ctx, currentValset, oldestSignedBatch, oldestSigs) + txHash, err := ethereum.SendTransactionBatch(ctx, currentValset, oldestConfirmedBatch, oldestConfirmedBatchSigs) if err != nil { return err } diff --git a/orchestrator/signer.go b/orchestrator/signer.go index b8063c9c..873d9636 100644 --- a/orchestrator/signer.go +++ b/orchestrator/signer.go @@ -86,7 +86,7 @@ func (s *ethSigner) signNewBatches(ctx context.Context, injective InjectiveNetwo } if oldestUnsignedTransactionBatch == nil { - s.log.Debugln("no new transaction batch waiting to be signed") + s.log.Debugln("no batch waiting to be confirmed") return nil } @@ -113,8 +113,9 @@ func (s *ethSigner) getUnsignedBatch(ctx context.Context, injective InjectiveNet retry.Context(ctx), retry.Attempts(s.retries), retry.OnRetry(func(n uint, err error) { - s.log.WithError(err).Warningf("failed to get unsigned transaction batch, will retry (%d)", n) - })); err != nil { + s.log.WithError(err).Warningf("failed to get unconfirmed batch, will retry (%d)", n) + }), + ); err != nil { s.log.WithError(err).Errorln("got error, loop exits") return nil, err } @@ -127,11 +128,12 @@ func (s *ethSigner) signBatch( injective InjectiveNetwork, batch *types.OutgoingTxBatch, ) error { - if err := retry.Do(func() error { return injective.SendBatchConfirm(ctx, s.peggyID, batch, s.ethFrom) }, + if err := retry.Do( + func() error { return injective.SendBatchConfirm(ctx, s.peggyID, batch, s.ethFrom) }, retry.Context(ctx), retry.Attempts(s.retries), retry.OnRetry(func(n uint, err error) { - s.log.WithError(err).Warningf("failed to sign and send batch confirmation to Injective, will retry (%d)", n) + s.log.WithError(err).Warningf("failed to confirm batch on Injective, will retry (%d)", n) }), ); err != nil { s.log.WithError(err).Errorln("got error, loop exits") @@ -155,7 +157,7 @@ func (s *ethSigner) signNewValsetUpdates( } if len(oldestUnsignedValsets) == 0 { - s.log.Debugln("no new valset updates waiting to be signed") + s.log.Debugln("no valset updates waiting to be confirmed") return nil } @@ -183,7 +185,7 @@ func (s *ethSigner) getUnsignedValsets(ctx context.Context, injective InjectiveN retry.Context(ctx), retry.Attempts(s.retries), retry.OnRetry(func(n uint, err error) { - s.log.WithError(err).Warningf("failed to get unsigned valsets, will retry (%d)", n) + s.log.WithError(err).Warningf("failed to get unconfirmed valsets, will retry (%d)", n) }), ); err != nil { s.log.WithError(err).Errorln("got error, loop exits") @@ -198,11 +200,12 @@ func (s *ethSigner) signValset( injective InjectiveNetwork, vs *types.Valset, ) error { - if err := retry.Do(func() error { return injective.SendValsetConfirm(ctx, s.peggyID, vs, s.ethFrom) }, + if err := retry.Do( + func() error { return injective.SendValsetConfirm(ctx, s.peggyID, vs, s.ethFrom) }, retry.Context(ctx), retry.Attempts(s.retries), retry.OnRetry(func(n uint, err error) { - s.log.WithError(err).Warningf("failed to sign and send valset confirmation to Injective, will retry (%d)", n) + s.log.WithError(err).Warningf("failed to confirm valset update on Injective, will retry (%d)", n) }), ); err != nil { s.log.WithError(err).Errorln("got error, loop exits") From fce55d6479f7f843d994ffa68e0e73b5d1fc2713 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Tue, 11 Jul 2023 13:08:45 +0200 Subject: [PATCH 61/72] remvove periodic batch requiesting feature --- cmd/peggo/options.go | 9 --------- cmd/peggo/orchestrator.go | 3 +-- orchestrator/orchestrator.go | 20 +++++++++----------- 3 files changed, 10 insertions(+), 22 deletions(-) diff --git a/cmd/peggo/options.go b/cmd/peggo/options.go index ed554374..60e05e69 100644 --- a/cmd/peggo/options.go +++ b/cmd/peggo/options.go @@ -271,8 +271,6 @@ type Config struct { // Batch requester config minBatchFeeUSD *float64 - periodicBatchRequesting *bool - coingeckoApi *string } @@ -470,13 +468,6 @@ func initConfig(cmd *cli.Cmd) Config { Value: float64(23.3), }) - cfg.periodicBatchRequesting = cmd.Bool(cli.BoolOpt{ - Name: "periodic_batch_requesting", - Desc: "If set, batches will be requested every 8 hours regardless of the fee", - EnvVar: "PEGGO_PERIODIC_BATCH_REQUESTING", - Value: false, - }) - /** Coingecko **/ cfg.coingeckoApi = cmd.String(cli.StringOpt{ diff --git a/cmd/peggo/orchestrator.go b/cmd/peggo/orchestrator.go index e8c111e4..7aac5956 100644 --- a/cmd/peggo/orchestrator.go +++ b/cmd/peggo/orchestrator.go @@ -40,7 +40,7 @@ func orchestratorCmd(cmd *cli.Cmd) { "build_date": version.BuildDate, "go_version": version.GoVersion, "go_arch": version.GoArch, - }).Infoln("peggo - injectived bridge binary for Ethereum") + }).Infoln("peggo - peggy binary for Ethereum bridge") if *cfg.cosmosUseLedger || *cfg.ethUseLedger { log.Fatalln("cannot use Ledger for peggo, since signatures must be realtime") @@ -132,7 +132,6 @@ func orchestratorCmd(cmd *cli.Cmd) { coingeckoFeed, erc20ContractMapping, *cfg.minBatchFeeUSD, - *cfg.periodicBatchRequesting, *cfg.relayValsets, *cfg.relayBatches, *cfg.relayValsetOffsetDur, diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index dd724cde..bff2bad2 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -113,23 +113,21 @@ func NewPeggyOrchestrator( priceFeed PriceFeed, erc20ContractMapping map[eth.Address]string, minBatchFeeUSD float64, - periodicBatchRequesting, valsetRelayingEnabled, batchRelayingEnabled bool, valsetRelayingOffset, batchRelayingOffset string, ) (*PeggyOrchestrator, error) { orch := &PeggyOrchestrator{ - svcTags: metrics.Tags{"svc": "peggy_orchestrator"}, - injective: injective, - ethereum: ethereum, - pricefeed: priceFeed, - erc20ContractMapping: erc20ContractMapping, - minBatchFeeUSD: minBatchFeeUSD, - periodicBatchRequesting: periodicBatchRequesting, - valsetRelayEnabled: valsetRelayingEnabled, - batchRelayEnabled: batchRelayingEnabled, - maxAttempts: 10, // default is 10 for retry pkg + svcTags: metrics.Tags{"svc": "peggy_orchestrator"}, + injective: injective, + ethereum: ethereum, + pricefeed: priceFeed, + erc20ContractMapping: erc20ContractMapping, + minBatchFeeUSD: minBatchFeeUSD, + valsetRelayEnabled: valsetRelayingEnabled, + batchRelayEnabled: batchRelayingEnabled, + maxAttempts: 10, // default is 10 for retry pkg } if valsetRelayingEnabled { From b74245e2e825c3bbea2e86d36d85d9196a7ce63c Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 12 Jul 2023 12:59:11 +0200 Subject: [PATCH 62/72] improve logs --- orchestrator/oracle.go | 32 ++++++++++++++++---------------- orchestrator/relayer.go | 8 ++++---- orchestrator/signer.go | 12 ++++++------ 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index babff904..9fd07bd0 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -181,38 +181,38 @@ func (o *ethOracle) relayEvents( legacyDeposits = filterSendToCosmosEventsByNonce(legacyDeposits, lastClaimEvent.EthereumEventNonce) o.log.WithFields(log.Fields{ - "block_start": currentHeight, - "block_end": latestHeight, - "old_deposits": legacyDeposits, - }).Debugln("scanned SendToCosmos events from Ethereum") + "block_start": currentHeight, + "block_end": latestHeight, + "events": legacyDeposits, + }).Debugln("scanned SendToCosmos events") deposits = filterSendToInjectiveEventsByNonce(deposits, lastClaimEvent.EthereumEventNonce) o.log.WithFields(log.Fields{ "block_start": currentHeight, "block_end": latestHeight, - "deposits": deposits, - }).Debugln("scanned SendToInjective events from Ethereum") + "events": deposits, + }).Debugln("scanned SendToInjective events") withdrawals = filterTransactionBatchExecutedEventsByNonce(withdrawals, lastClaimEvent.EthereumEventNonce) o.log.WithFields(log.Fields{ "block_start": currentHeight, "block_end": latestHeight, - "withdrawals": withdrawals, - }).Debugln("scanned TransactionBatchExecuted events from Ethereum") + "events": withdrawals, + }).Debugln("scanned TransactionBatchExecuted events") erc20Deployments = filterERC20DeployedEventsByNonce(erc20Deployments, lastClaimEvent.EthereumEventNonce) o.log.WithFields(log.Fields{ - "block_start": currentHeight, - "block_end": latestHeight, - "erc20_deployments": erc20Deployments, - }).Debugln("scanned FilterERC20Deployed events from Ethereum") + "block_start": currentHeight, + "block_end": latestHeight, + "events": erc20Deployments, + }).Debugln("scanned FilterERC20Deployed events") valsetUpdates = filterValsetUpdateEventsByNonce(valsetUpdates, lastClaimEvent.EthereumEventNonce) o.log.WithFields(log.Fields{ - "block_start": currentHeight, - "block_end": latestHeight, - "valset_updates": valsetUpdates, - }).Debugln("scanned ValsetUpdated events from Ethereum") + "block_start": currentHeight, + "block_end": latestHeight, + "events": valsetUpdates, + }).Debugln("scanned ValsetUpdated events") if len(legacyDeposits) == 0 && len(deposits) == 0 && diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index 5c3b2824..0fa4f45f 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -97,7 +97,7 @@ func (r *relayer) relayValsets( // to Ethereum for that we will find the latest confirmed valset and compare it to the ethereum chain latestValsets, err := injective.LatestValsets(ctx) if err != nil { - return errors.Wrap(err, "failed to get latest valsets from Injective") + return errors.Wrap(err, "failed to get latest valset updates from Injective") } var ( @@ -119,19 +119,19 @@ func (r *relayer) relayValsets( } if oldestConfirmedValset == nil { - r.log.Debugln("no confirmed valsets found on Injective, nothing to relay...") + r.log.Debugln("no confirmed valset updates to relay") return nil } currentEthValset, err := r.findLatestValsetOnEth(ctx, injective, ethereum) if err != nil { - return errors.Wrap(err, "failed to find latest confirmed valset on Ethereum") + return errors.Wrap(err, "failed to find latest confirmed valset update on Ethereum") } r.log.WithFields(log.Fields{ "inj_valset": oldestConfirmedValset, "eth_valset": currentEthValset, - }).Debugln("latest valsets") + }).Debugln("latest valset updates") if oldestConfirmedValset.Nonce <= currentEthValset.Nonce { return nil diff --git a/orchestrator/signer.go b/orchestrator/signer.go index 873d9636..c27c53f1 100644 --- a/orchestrator/signer.go +++ b/orchestrator/signer.go @@ -78,7 +78,7 @@ func (s *ethSigner) run(ctx context.Context, injective InjectiveNetwork) error { } func (s *ethSigner) signNewBatches(ctx context.Context, injective InjectiveNetwork) error { - s.log.Infoln("scanning Injective for new batches") + s.log.Infoln("scanning Injective for unconfirmed batch") oldestUnsignedTransactionBatch, err := s.getUnsignedBatch(ctx, injective) if err != nil { @@ -86,7 +86,7 @@ func (s *ethSigner) signNewBatches(ctx context.Context, injective InjectiveNetwo } if oldestUnsignedTransactionBatch == nil { - s.log.Debugln("no batch waiting to be confirmed") + s.log.Debugln("no batch to confirm") return nil } @@ -149,7 +149,7 @@ func (s *ethSigner) signNewValsetUpdates( ctx context.Context, injective InjectiveNetwork, ) error { - s.log.Infoln("scanning Injective for new valset updates") + s.log.Infoln("scanning Injective for unconfirmed valset updates") oldestUnsignedValsets, err := s.getUnsignedValsets(ctx, injective) if err != nil { @@ -157,7 +157,7 @@ func (s *ethSigner) signNewValsetUpdates( } if len(oldestUnsignedValsets) == 0 { - s.log.Debugln("no valset updates waiting to be confirmed") + s.log.Debugln("no valset updates to confirm") return nil } @@ -185,7 +185,7 @@ func (s *ethSigner) getUnsignedValsets(ctx context.Context, injective InjectiveN retry.Context(ctx), retry.Attempts(s.retries), retry.OnRetry(func(n uint, err error) { - s.log.WithError(err).Warningf("failed to get unconfirmed valsets, will retry (%d)", n) + s.log.WithError(err).Warningf("failed to get unconfirmed valset updates, will retry (%d)", n) }), ); err != nil { s.log.WithError(err).Errorln("got error, loop exits") @@ -212,7 +212,7 @@ func (s *ethSigner) signValset( return err } - s.log.WithField("valset_nonce", vs.Nonce).Infoln("confirmed valset on Injective") + s.log.WithField("valset_nonce", vs.Nonce).Infoln("confirmed valset update on Injective") return nil } From 598c44ee27af8c5a02eb9e66f989dddf890ae187 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 12 Jul 2023 13:15:53 +0200 Subject: [PATCH 63/72] tmp: increase defaultBlocksToSearch to 1000 --- orchestrator/oracle.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index 9fd07bd0..88e64218 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -17,7 +17,7 @@ import ( // So better to search only 20 blocks to ensure all the events are broadcast to Injective Chain without misses. const ( ethBlockConfirmationDelay uint64 = 12 - defaultBlocksToSearch uint64 = 20 + defaultBlocksToSearch uint64 = 1000 ) // EthOracleMainLoop is responsible for making sure that Ethereum events are retrieved from the Ethereum blockchain From 2b8c260106ce74c4d5889906be6407c4d87c5f35 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 12 Jul 2023 13:37:01 +0200 Subject: [PATCH 64/72] imrpove log --- orchestrator/ethereum/peggy/submit_batch.go | 6 +++--- orchestrator/relayer.go | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/orchestrator/ethereum/peggy/submit_batch.go b/orchestrator/ethereum/peggy/submit_batch.go index 39f42646..df1f1a3f 100644 --- a/orchestrator/ethereum/peggy/submit_batch.go +++ b/orchestrator/ethereum/peggy/submit_batch.go @@ -24,10 +24,10 @@ func (s *peggyContract) SendTransactionBatch( log.WithFields(log.Fields{ "token_contract": batch.TokenContract, - "new_nonce": batch.BatchNonce, + "nonce": batch.BatchNonce, + "transactions": len(batch.Transactions), "confirmations": len(confirms), - }).Infoln("Checking signatures and submitting TransactionBatch to Ethereum") - log.Debugf("Batch %s", batch.String()) + }).Infoln("checking signatures and submitting batch to Ethereum") validators, powers, sigV, sigR, sigS, err := checkBatchSigsAndRepack(currentValset, confirms) if err != nil { diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index 0fa4f45f..d8ed2e97 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -314,7 +314,7 @@ func (r *relayer) findLatestValsetOnEth( r.log.WithFields(log.Fields{ "block_start": startSearchBlock, "block_end": currentBlock, - }).Debugln("looking back into Ethereum history to find the last valset update") + }).Debugln("looking for the most recent ValsetUpdatedEvent on Ethereum") valsetUpdatedEvents, err := ethereum.GetValsetUpdatedEvents(startSearchBlock, currentBlock) if err != nil { @@ -332,8 +332,6 @@ func (r *relayer) findLatestValsetOnEth( continue } - r.log.Debugln("found events", valsetUpdatedEvents) - // we take only the first event if we find any at all. event := valsetUpdatedEvents[0] valset := &types.Valset{ From d74af9f21af871eb0db4aabaf3f26a0647af1a35 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 12 Jul 2023 17:23:52 +0200 Subject: [PATCH 65/72] revert defaultBlocksToSearch to 20 --- orchestrator/oracle.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index 88e64218..9fd07bd0 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -17,7 +17,7 @@ import ( // So better to search only 20 blocks to ensure all the events are broadcast to Injective Chain without misses. const ( ethBlockConfirmationDelay uint64 = 12 - defaultBlocksToSearch uint64 = 1000 + defaultBlocksToSearch uint64 = 20 ) // EthOracleMainLoop is responsible for making sure that Ethereum events are retrieved from the Ethereum blockchain From df7206ae1586b6c13e1261f3dc95bc8b78ff2e4d Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 12 Jul 2023 17:24:32 +0200 Subject: [PATCH 66/72] ethBlockConfirmationDelay to 96 --- orchestrator/oracle.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index 9fd07bd0..78ed7dec 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -16,7 +16,7 @@ import ( // we broadcast only 20 events in each iteration. // So better to search only 20 blocks to ensure all the events are broadcast to Injective Chain without misses. const ( - ethBlockConfirmationDelay uint64 = 12 + ethBlockConfirmationDelay uint64 = 96 defaultBlocksToSearch uint64 = 20 ) From d8f44e370508bf26ae08c0ce79a6fb4384ae1fb7 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Wed, 19 Jul 2023 23:42:04 +0200 Subject: [PATCH 67/72] safeguard QueryUSDPrice --- orchestrator/coingecko/coingecko.go | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/orchestrator/coingecko/coingecko.go b/orchestrator/coingecko/coingecko.go index fde0206e..f10f69c1 100644 --- a/orchestrator/coingecko/coingecko.go +++ b/orchestrator/coingecko/coingecko.go @@ -92,11 +92,32 @@ func (cp *CoingeckoPriceFeed) QueryUSDPrice(erc20Contract common.Address) (float _ = resp.Body.Close() var f interface{} - err = json.Unmarshal(respBody, &f) - m := f.(map[string]interface{}) + if err := json.Unmarshal(respBody, &f); err != nil { + metrics.ReportFuncError(cp.svcTags) + cp.logger.WithError(err).Errorln("failed to unmarshal response") + return zeroPrice, err + } + + m, ok := f.(map[string]interface{}) + if !ok { + metrics.ReportFuncError(cp.svcTags) + cp.logger.WithError(err).Errorln("failed to cast response type: map[string]interface{}") + return zeroPrice, err + } v := m[strings.ToLower(erc20Contract.String())] - n := v.(map[string]interface{}) + if v == nil { + metrics.ReportFuncError(cp.svcTags) + cp.logger.WithError(err).Errorln("failed to get contract address") + return zeroPrice, err + } + + n, ok := v.(map[string]interface{}) + if !ok { + metrics.ReportFuncError(cp.svcTags) + cp.logger.WithError(err).Errorln("failed to cast value type: map[string]interface{}") + return zeroPrice, err + } tokenPriceInUSD := n["usd"].(float64) return tokenPriceInUSD, nil From feaa38b4732a8e5ccf24b972a532a5355edca509 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Thu, 20 Jul 2023 16:21:56 +0200 Subject: [PATCH 68/72] better relayer logs --- orchestrator/ethereum/peggy/submit_batch.go | 4 +--- orchestrator/relayer.go | 9 +++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/orchestrator/ethereum/peggy/submit_batch.go b/orchestrator/ethereum/peggy/submit_batch.go index df1f1a3f..09b7d17b 100644 --- a/orchestrator/ethereum/peggy/submit_batch.go +++ b/orchestrator/ethereum/peggy/submit_batch.go @@ -27,7 +27,7 @@ func (s *peggyContract) SendTransactionBatch( "nonce": batch.BatchNonce, "transactions": len(batch.Transactions), "confirmations": len(confirms), - }).Infoln("checking signatures and submitting batch to Ethereum") + }).Debugln("checking signatures and submitting batch to Ethereum") validators, powers, sigV, sigR, sigS, err := checkBatchSigsAndRepack(currentValset, confirms) if err != nil { @@ -97,8 +97,6 @@ func (s *peggyContract) SendTransactionBatch( return nil, err } - log.Infoln("Sent Tx (Peggy submitBatch):", txHash.Hex()) - // let before_nonce = get_tx_batch_nonce( // peggy_contract_address, // batch.token_contract, diff --git a/orchestrator/relayer.go b/orchestrator/relayer.go index d8ed2e97..04c97e64 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/relayer.go @@ -259,9 +259,10 @@ func (r *relayer) relayBatches( } r.log.WithFields(log.Fields{ - "inj_batch": oldestConfirmedBatch.BatchNonce, - "eth_batch": latestEthereumBatch.Uint64(), - }).Infoln("detected new transaction batch on Injective") + "inj_batch": oldestConfirmedBatch.BatchNonce, + "eth_batch": latestEthereumBatch.Uint64(), + "token_contract": common.HexToAddress(oldestConfirmedBatch.TokenContract), + }).Infoln("detected new batch on Injective") // Send SendTransactionBatch to Ethereum txHash, err := ethereum.SendTransactionBatch(ctx, currentValset, oldestConfirmedBatch, oldestConfirmedBatchSigs) @@ -269,7 +270,7 @@ func (r *relayer) relayBatches( return err } - r.log.WithField("tx_hash", txHash.Hex()).Infoln("updated transaction batch on Ethereum") + r.log.WithField("tx_hash", txHash.Hex()).Infoln("sent batch tx to Ethereum") return nil } From 54de1c1999e672f03d9e5899b3b3a05f8314c955 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Thu, 20 Jul 2023 16:23:06 +0200 Subject: [PATCH 69/72] bump oracle defaultBlocksToSearch to 2k --- orchestrator/oracle.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go index 78ed7dec..9ac8976d 100644 --- a/orchestrator/oracle.go +++ b/orchestrator/oracle.go @@ -17,7 +17,7 @@ import ( // So better to search only 20 blocks to ensure all the events are broadcast to Injective Chain without misses. const ( ethBlockConfirmationDelay uint64 = 96 - defaultBlocksToSearch uint64 = 20 + defaultBlocksToSearch uint64 = 2000 ) // EthOracleMainLoop is responsible for making sure that Ethereum events are retrieved from the Ethereum blockchain From 799fc4162b3e71014d1789381ab62b705c4f7bc1 Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Fri, 21 Jul 2023 14:37:17 +0200 Subject: [PATCH 70/72] log --- orchestrator/ethereum/committer/eth_committer.go | 7 ++++--- orchestrator/signer.go | 6 ++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/orchestrator/ethereum/committer/eth_committer.go b/orchestrator/ethereum/committer/eth_committer.go index ca29d9ca..b231f1b8 100644 --- a/orchestrator/ethereum/committer/eth_committer.go +++ b/orchestrator/ethereum/committer/eth_committer.go @@ -150,9 +150,8 @@ func (e *ethCommitter) SendTx( return nil } else { log.WithFields(log.Fields{ - "txHash": txHash.Hex(), - "txHashRet": txHashRet.Hex(), - }).WithError(err).Warningln("SendTransaction failed with error") + "tx_hash": txHash.Hex(), + }).WithError(err).Warningln("failed to send tx") } switch { @@ -199,6 +198,8 @@ func (e *ethCommitter) SendTx( }); err != nil { metrics.ReportFuncError(e.svcTags) + log.WithError(err).Errorln("SendTx serialize failed") + return common.Hash{}, err } diff --git a/orchestrator/signer.go b/orchestrator/signer.go index c27c53f1..a2d376b2 100644 --- a/orchestrator/signer.go +++ b/orchestrator/signer.go @@ -66,6 +66,8 @@ type ethSigner struct { } func (s *ethSigner) run(ctx context.Context, injective InjectiveNetwork) error { + s.log.Infoln("scanning Injective for unconfirmed batches and valset updates") + if err := s.signNewValsetUpdates(ctx, injective); err != nil { return err } @@ -78,8 +80,6 @@ func (s *ethSigner) run(ctx context.Context, injective InjectiveNetwork) error { } func (s *ethSigner) signNewBatches(ctx context.Context, injective InjectiveNetwork) error { - s.log.Infoln("scanning Injective for unconfirmed batch") - oldestUnsignedTransactionBatch, err := s.getUnsignedBatch(ctx, injective) if err != nil { return err @@ -149,8 +149,6 @@ func (s *ethSigner) signNewValsetUpdates( ctx context.Context, injective InjectiveNetwork, ) error { - s.log.Infoln("scanning Injective for unconfirmed valset updates") - oldestUnsignedValsets, err := s.getUnsignedValsets(ctx, injective) if err != nil { return err From 6ac04bcb5bc3fff247a35ada862aaf766a3fab1e Mon Sep 17 00:00:00 2001 From: Albert Chon Date: Tue, 22 Aug 2023 19:01:22 -0400 Subject: [PATCH 71/72] Delete todo --- todo | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 todo diff --git a/todo b/todo deleted file mode 100644 index e69de29b..00000000 From 887c5d83cb98e7bb0fae364a1e902ef86581c98b Mon Sep 17 00:00:00 2001 From: Dusan Brajovic Date: Fri, 25 Aug 2023 16:04:10 +0200 Subject: [PATCH 72/72] add TestCheckFeeThreshold --- orchestrator/batch_request_test.go | 36 ++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/orchestrator/batch_request_test.go b/orchestrator/batch_request_test.go index eed250fb..29fb387a 100644 --- a/orchestrator/batch_request_test.go +++ b/orchestrator/batch_request_test.go @@ -119,3 +119,39 @@ func TestRequestBatches(t *testing.T) { }) } + +func TestCheckFeeThreshold(t *testing.T) { + t.Parallel() + + t.Run("fee threshold is met", func(t *testing.T) { + t.Parallel() + + var ( + requester = &batchRequester{minBatchFee: 21} + tokenAddr = eth.HexToAddress("0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30") + totalFees, _ = cosmtypes.NewIntFromString("10000000000000000000") // 10inj + feed = mockPriceFeed{queryFn: func(_ eth.Address) (float64, error) { + return 2.5, nil + }} + ) + + // 2.5 * 10 > 21 + assert.True(t, requester.checkFeeThreshold(feed, tokenAddr, totalFees)) + }) + + t.Run("fee threshold is met", func(t *testing.T) { + t.Parallel() + + var ( + requester = &batchRequester{minBatchFee: 333.333} + tokenAddr = eth.HexToAddress("0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30") + totalFees, _ = cosmtypes.NewIntFromString("100000000000000000000") // 10inj + feed = mockPriceFeed{queryFn: func(_ eth.Address) (float64, error) { + return 2.5, nil + }} + ) + + // 2.5 * 100 < 333.333 + assert.False(t, requester.checkFeeThreshold(feed, tokenAddr, totalFees)) + }) +}