diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 46eb5b4f..53a92c60 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -51,15 +51,7 @@ jobs: - name: Build image and push run: | cd peggo/ - TAG=$(echo ${GITHUB_REF#refs/heads/} | cut -d '/' -f 2) + TAG=${{ github.ref_name }} echo $TAG [[ $ECR_ENABLED == "false" ]] || docker buildx build --tag $ECR_REPO:$TAG --platform linux/amd64,linux/arm64 --push . [[ $GHCR_ENABLED == "false" ]] || docker buildx build --tag $GHCR_REPO:$TAG --platform linux/amd64,linux/arm64 --push . - - - name: NONROOT Build image and push - run: | - cd peggo/ - TAG=$(echo ${GITHUB_REF#refs/heads/} | cut -d '/' -f 2)-nonroot - echo $TAG - [[ $ECR_ENABLED == "false" ]] || docker buildx build -f Dockerfile.nonroot --tag $ECR_REPO:$TAG --platform linux/amd64,linux/arm64 --push . - [[ $GHCR_ENABLED == "false" ]] || docker buildx build -f Dockerfile.nonroot --tag $GHCR_REPO:$TAG --platform linux/amd64,linux/arm64 --push . diff --git a/cmd/peggo/keys.go b/cmd/peggo/keys.go index 744e3fd2..18cfa754 100644 --- a/cmd/peggo/keys.go +++ b/cmd/peggo/keys.go @@ -2,20 +2,13 @@ package main import ( "bytes" - "crypto/rand" "fmt" "io" - "log" "math/big" "os" - "path/filepath" "strings" "syscall" - cosmcrypto "github.com/cosmos/cosmos-sdk/crypto" - "github.com/cosmos/cosmos-sdk/crypto/keyring" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - cosmtypes "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/usbwallet" @@ -27,154 +20,9 @@ import ( "github.com/pkg/errors" "golang.org/x/crypto/ssh/terminal" - "github.com/InjectiveLabs/sdk-go/chain/crypto/ethsecp256k1" - "github.com/InjectiveLabs/sdk-go/chain/crypto/hd" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/keystore" ) -const defaultKeyringKeyName = "validator" - -var emptyCosmosAddress = cosmtypes.AccAddress{} -var cdc = MakeEncodingConfig().Marshaler - -func initCosmosKeyring( - cosmosKeyringDir *string, - cosmosKeyringAppName *string, - cosmosKeyringBackend *string, - cosmosKeyFrom *string, - cosmosKeyPassphrase *string, - cosmosPrivKey *string, - cosmosUseLedger *bool, -) (cosmtypes.AccAddress, keyring.Keyring, error) { - - switch { - case len(*cosmosPrivKey) > 0: - if *cosmosUseLedger { - err := errors.New("cannot combine ledger and privkey options") - return emptyCosmosAddress, nil, err - } - - pkBytes, err := hexToBytes(*cosmosPrivKey) - if err != nil { - err = errors.Wrap(err, "failed to hex-decode cosmos account privkey") - return emptyCosmosAddress, nil, err - } - - // Specfic to Injective chain with Ethermint keys - // Should be secp256k1.PrivKey for generic Cosmos chain - cosmosAccPk := ðsecp256k1.PrivKey{ - Key: pkBytes, - } - - addressFromPk := cosmtypes.AccAddress(cosmosAccPk.PubKey().Address().Bytes()) - - var keyName string - - // check that if cosmos 'From' specified separately, it must match the provided privkey, - if len(*cosmosKeyFrom) > 0 { - addressFrom, err := cosmtypes.AccAddressFromBech32(*cosmosKeyFrom) - if err == nil { - if !bytes.Equal(addressFrom.Bytes(), addressFromPk.Bytes()) { - err = errors.Errorf("expected account address %s but got %s from the private key", addressFrom.String(), addressFromPk.String()) - return emptyCosmosAddress, nil, err - } - } else { - // use it as a name then - keyName = *cosmosKeyFrom - } - } - - if len(keyName) == 0 { - keyName = defaultKeyringKeyName - } - - // wrap a PK into a Keyring - kb, err := KeyringForPrivKey(keyName, cosmosAccPk) - return addressFromPk, kb, err - - case len(*cosmosKeyFrom) > 0: - var fromIsAddress bool - addressFrom, err := cosmtypes.AccAddressFromBech32(*cosmosKeyFrom) - if err == nil { - fromIsAddress = true - } - - var passReader io.Reader = os.Stdin - if len(*cosmosKeyPassphrase) > 0 { - passReader = newPassReader(*cosmosKeyPassphrase) - } - - var absoluteKeyringDir string - if filepath.IsAbs(*cosmosKeyringDir) { - absoluteKeyringDir = *cosmosKeyringDir - } else { - absoluteKeyringDir, _ = filepath.Abs(*cosmosKeyringDir) - } - - kb, err := keyring.New( - *cosmosKeyringAppName, - *cosmosKeyringBackend, - absoluteKeyringDir, - passReader, - cdc, - hd.EthSecp256k1Option(), - ) - if err != nil { - err = errors.Wrap(err, "failed to init keyring") - return emptyCosmosAddress, nil, err - } - - var keyInfo *keyring.Record - if fromIsAddress { - if keyInfo, err = kb.KeyByAddress(addressFrom); err != nil { - err = errors.Wrapf(err, "couldn't find an entry for the key %s in keybase", addressFrom.String()) - return emptyCosmosAddress, nil, err - } - } else { - if keyInfo, err = kb.Key(*cosmosKeyFrom); err != nil { - err = errors.Wrapf(err, "could not find an entry for the key '%s' in keybase", *cosmosKeyFrom) - return emptyCosmosAddress, nil, err - } - } - - switch keyType := keyInfo.GetType(); keyType { - case keyring.TypeLocal: - // kb has a key and it's totally usable - addr, err := keyInfo.GetAddress() - if err != nil { - return emptyCosmosAddress, nil, err - } - return addr, kb, nil - case keyring.TypeLedger: - // the kb stores references to ledger keys, so we must explicitly - // check that. kb doesn't know how to scan HD keys - they must be added manually before - if *cosmosUseLedger { - addr, err := keyInfo.GetAddress() - if err != nil { - return emptyCosmosAddress, nil, err - } - return addr, kb, nil - } - err := errors.Errorf("'%s' key is a ledger reference, enable ledger option", keyInfo.Name) - return emptyCosmosAddress, nil, err - case keyring.TypeOffline: - err := errors.Errorf("'%s' key is an offline key, not supported yet", keyInfo.Name) - return emptyCosmosAddress, nil, err - case keyring.TypeMulti: - err := errors.Errorf("'%s' key is an multisig key, not supported yet", keyInfo.Name) - return emptyCosmosAddress, nil, err - default: - err := errors.Errorf("'%s' key has unsupported type: %s", keyInfo.Name, keyType) - return emptyCosmosAddress, nil, err - } - - default: - err := errors.New("insufficient cosmos key details provided") - return emptyCosmosAddress, nil, err - } -} - var emptyEthAddress = ethcmn.Address{} func initEthereumAccountsManager( @@ -392,32 +240,3 @@ func (r *passReader) Read(p []byte) (n int, err error) { return } - -// KeyringForPrivKey creates a temporary in-mem keyring for a PrivKey. -// Allows to init Context when the key has been provided in plaintext and parsed. -func KeyringForPrivKey(name string, privKey cryptotypes.PrivKey) (keyring.Keyring, error) { - kb := keyring.NewInMemory(cdc, hd.EthSecp256k1Option()) - tmpPhrase := randPhrase(64) - armored := cosmcrypto.EncryptArmorPrivKey(privKey, tmpPhrase, privKey.Type()) - err := kb.ImportPrivKey(name, armored, tmpPhrase) - if err != nil { - err = errors.Wrap(err, "failed to import privkey") - return nil, err - } - - return kb, nil -} - -func randPhrase(size int) string { - buf := make([]byte, size) - _, err := rand.Read(buf) - orPanic(err) - - return string(buf) -} - -func orPanic(err error) { - if err != nil { - log.Panicln() - } -} diff --git a/cmd/peggo/orchestrator.go b/cmd/peggo/orchestrator.go index 3eac2716..727b45b4 100644 --- a/cmd/peggo/orchestrator.go +++ b/cmd/peggo/orchestrator.go @@ -6,6 +6,7 @@ import ( gethcommon "github.com/ethereum/go-ethereum/common" cli "github.com/jawher/mow.cli" + "github.com/pkg/errors" "github.com/xlab/closer" log "github.com/xlab/suplog" @@ -22,8 +23,6 @@ import ( // // $ peggo orchestrator func orchestratorCmd(cmd *cli.Cmd) { - // orchestrator-specific CLI options - cmd.Before = func() { initMetrics(cmd) } @@ -40,24 +39,24 @@ func orchestratorCmd(cmd *cli.Cmd) { "build_date": version.BuildDate, "go_version": version.GoVersion, "go_arch": version.GoArch, - }).Infoln("peggo - peggy binary for Ethereum bridge") + }).Infoln("Peggo - Peggy module companion binary used to bridge assets between Injective and Ethereum") if *cfg.cosmosUseLedger || *cfg.ethUseLedger { - log.Fatalln("cannot use Ledger for peggo, since signatures must be realtime") + log.Fatalln("cannot use Ledger for orchestrator, since signatures must be realtime") } - valAddress, cosmosKeyring, err := initCosmosKeyring( - cfg.cosmosKeyringDir, - cfg.cosmosKeyringAppName, - cfg.cosmosKeyringBackend, - cfg.cosmosKeyFrom, - cfg.cosmosKeyPassphrase, - cfg.cosmosPrivKey, - cfg.cosmosUseLedger, - ) - if err != nil { - log.WithError(err).Fatalln("failed to initialize Injective keyring") - } + cosmosKeyring, err := cosmos.NewKeyring(cosmos.KeyringConfig{ + KeyringDir: *cfg.cosmosKeyringDir, + KeyringAppName: *cfg.cosmosKeyringAppName, + KeyringBackend: *cfg.cosmosKeyringBackend, + KeyFrom: *cfg.cosmosKeyFrom, + KeyPassphrase: *cfg.cosmosKeyPassphrase, + PrivateKey: *cfg.cosmosPrivKey, + UseLedger: *cfg.cosmosUseLedger, + }) + orShutdown(errors.Wrap(err, "failed to initialize Injective keyring")) + + log.Infoln("initialized Injective keyring", cosmosKeyring.Addr.String()) ethKeyFromAddress, signerFn, personalSignFn, err := initEthereumAccountsManager( uint64(*cfg.ethChainID), @@ -67,86 +66,77 @@ func orchestratorCmd(cmd *cli.Cmd) { cfg.ethPrivKey, cfg.ethUseLedger, ) - if err != nil { - log.WithError(err).Fatalln("failed to initialize Ethereum account") - } - - log.WithFields(log.Fields{"inj_addr": valAddress.String(), "eth_addr": ethKeyFromAddress.String()}).Infoln("starting peggo service") - - var ( - injectiveNet orchestrator.InjectiveNetwork - customEndpointRPCs = *cfg.cosmosGRPC != "" && *cfg.tendermintRPC != "" - ) + orShutdown(errors.Wrap(err, "failed to initialize Ethereum keyring")) - if customEndpointRPCs { - injectiveNet, err = cosmos.NewCustomRPCNetwork( - *cfg.cosmosChainID, - valAddress.String(), - *cfg.cosmosGRPC, - *cfg.cosmosGasPrices, - *cfg.tendermintRPC, - cosmosKeyring, - personalSignFn, - ) - } else { - // load balanced connection - injectiveNet, err = cosmos.NewLoadBalancedNetwork( - *cfg.cosmosChainID, - valAddress.String(), - *cfg.cosmosGasPrices, - cosmosKeyring, - personalSignFn, - ) - } + log.Infoln("initialized Ethereum keyring", ethKeyFromAddress.String()) + cosmosNetwork, err := cosmos.NewNetwork(cosmosKeyring, personalSignFn, cosmos.NetworkConfig{ + ChainID: *cfg.cosmosChainID, + ValidatorAddress: cosmosKeyring.Addr.String(), + CosmosGRPC: *cfg.cosmosGRPC, + TendermintRPC: *cfg.tendermintRPC, + GasPrice: *cfg.cosmosGasPrices, + }) orShutdown(err) + log.WithFields(log.Fields{"chain_id": *cfg.cosmosChainID, "gas_price": *cfg.cosmosGasPrices}).Infoln("connected to Injective network") + ctx, cancelFn := context.WithCancel(context.Background()) closer.Bind(cancelFn) // Construct erc20 token mapping - peggyParams, err := injectiveNet.PeggyParams(ctx) - if err != nil { - log.WithError(err).Fatalln("failed to query peggy params, is injectived running?") - } + peggyParams, err := cosmosNetwork.PeggyParams(ctx) + orShutdown(errors.Wrap(err, "failed to query peggy params, is injectived running?")) - peggyContractAddr := gethcommon.HexToAddress(peggyParams.BridgeEthereumAddress) - injTokenAddr := gethcommon.HexToAddress(peggyParams.CosmosCoinErc20Contract) + var ( + peggyContractAddr = gethcommon.HexToAddress(peggyParams.BridgeEthereumAddress) + injTokenAddr = gethcommon.HexToAddress(peggyParams.CosmosCoinErc20Contract) + erc20ContractMapping = map[gethcommon.Address]string{injTokenAddr: chaintypes.InjectiveCoin} + ) - erc20ContractMapping := make(map[gethcommon.Address]string) - erc20ContractMapping[injTokenAddr] = chaintypes.InjectiveCoin + log.WithFields(log.Fields{"peggy_contract": peggyContractAddr.String(), "inj_token_contract": injTokenAddr.String()}).Debugln("loaded Peggy module params") // Connect to ethereum network - ethereumNet, err := ethereum.NewNetwork( - *cfg.ethNodeRPC, - peggyContractAddr, - ethKeyFromAddress, - signerFn, - *cfg.ethGasPriceAdjustment, - *cfg.ethMaxGasPrice, - *cfg.pendingTxWaitDuration, - *cfg.ethNodeAlchemyWS, - ) + ethereumNetwork, err := ethereum.NewNetwork(peggyContractAddr, ethKeyFromAddress, signerFn, ethereum.NetworkConfig{ + EthNodeRPC: *cfg.ethNodeRPC, + GasPriceAdjustment: *cfg.ethGasPriceAdjustment, + MaxGasPrice: *cfg.ethMaxGasPrice, + PendingTxWaitDuration: *cfg.pendingTxWaitDuration, + EthNodeAlchemyWS: *cfg.ethNodeAlchemyWS, + }) orShutdown(err) - coingeckoFeed := coingecko.NewCoingeckoPriceFeed(100, &coingecko.Config{BaseURL: *cfg.coingeckoApi}) + log.WithFields(log.Fields{ + "chain_id": *cfg.ethChainID, + "rpc": *cfg.ethNodeRPC, + "max_gas_price": *cfg.ethMaxGasPrice, + "gas_price_adjustment": *cfg.ethGasPriceAdjustment, + }).Infoln("connected to Ethereum network") + + addr, isValidator := cosmos.HasRegisteredOrchestrator(cosmosNetwork, ethKeyFromAddress) + if isValidator { + log.Debugln("provided ETH address is registered with an orchestrator", addr.String()) + } // Create peggo and run it peggo, err := orchestrator.NewPeggyOrchestrator( - injectiveNet, - ethereumNet, - coingeckoFeed, - erc20ContractMapping, - *cfg.minBatchFeeUSD, - *cfg.relayValsets, - *cfg.relayBatches, - *cfg.relayValsetOffsetDur, - *cfg.relayBatchOffsetDur, + cosmosKeyring.Addr, + ethKeyFromAddress, + coingecko.NewPriceFeed(100, &coingecko.Config{BaseURL: *cfg.coingeckoApi}), + orchestrator.Config{ + MinBatchFeeUSD: *cfg.minBatchFeeUSD, + ERC20ContractMapping: erc20ContractMapping, + RelayValsetOffsetDur: *cfg.relayValsetOffsetDur, + RelayBatchOffsetDur: *cfg.relayBatchOffsetDur, + RelayValsets: *cfg.relayValsets, + RelayBatches: *cfg.relayBatches, + RelayerMode: !isValidator, + }, ) orShutdown(err) go func() { - if err := peggo.Run(ctx); err != nil { + if err := peggo.Run(ctx, cosmosNetwork, ethereumNetwork); err != nil { log.Errorln(err) os.Exit(1) } diff --git a/cmd/peggo/tx.go b/cmd/peggo/tx.go index c69c41ff..379e6919 100644 --- a/cmd/peggo/tx.go +++ b/cmd/peggo/tx.go @@ -2,6 +2,7 @@ package main import ( "context" + "github.com/InjectiveLabs/peggo/orchestrator/cosmos/peggy" "time" cli "github.com/jawher/mow.cli" @@ -93,19 +94,19 @@ func registerEthKeyCmd(cmd *cli.Cmd) { log.Warningln("beware: you cannot really use Ledger for orchestrator, so make sure the Ethereum key is accessible outside of it") } - valAddress, cosmosKeyring, err := initCosmosKeyring( - cosmosKeyringDir, - cosmosKeyringAppName, - cosmosKeyringBackend, - cosmosKeyFrom, - cosmosKeyPassphrase, - cosmosPrivKey, - cosmosUseLedger, - ) - if err != nil { - log.WithError(err).Fatalln("failed to init Cosmos keyring") + keyringCfg := cosmos.KeyringConfig{ + KeyringDir: *cosmosKeyringDir, + KeyringAppName: *cosmosKeyringAppName, + KeyringBackend: *cosmosKeyringBackend, + KeyFrom: *cosmosKeyFrom, + KeyPassphrase: *cosmosKeyPassphrase, + PrivateKey: *cosmosPrivKey, + UseLedger: *cosmosUseLedger, } + keyring, err := cosmos.NewKeyring(keyringCfg) + orShutdown(err) + ethKeyFromAddress, _, personalSignFn, err := initEthereumAccountsManager( 0, ethKeystoreDir, @@ -118,7 +119,7 @@ func registerEthKeyCmd(cmd *cli.Cmd) { log.WithError(err).Fatalln("failed to init Ethereum account") } - log.Infoln("Using Cosmos ValAddress", valAddress.String()) + log.Infoln("Using Cosmos ValAddress", keyring.Addr.String()) log.Infoln("Using Ethereum address", ethKeyFromAddress.String()) actionConfirmed := *alwaysAutoConfirm || stdinConfirm("Confirm UpdatePeggyOrchestratorAddresses transaction? [y/N]: ") @@ -126,53 +127,28 @@ func registerEthKeyCmd(cmd *cli.Cmd) { return } - var ( - peggyBroadcastClient cosmos.PeggyBroadcastClient - customCosmosRPC = *cosmosGRPC != "" && *tendermintRPC != "" - ) + net, err := cosmos.NewNetwork(keyring, personalSignFn, cosmos.NetworkConfig{ + ChainID: *cosmosChainID, + ValidatorAddress: keyring.Addr.String(), + CosmosGRPC: *cosmosGRPC, + TendermintRPC: *cosmosGasPrices, + GasPrice: *tendermintRPC, + }) - if customCosmosRPC { - net, err := cosmos.NewCustomRPCNetwork( - *cosmosChainID, - valAddress.String(), - *cosmosGRPC, - *cosmosGasPrices, - *tendermintRPC, - cosmosKeyring, - personalSignFn, - ) - - if err != nil { - log.Fatalln("failed to connect to Injective network") - } - - peggyBroadcastClient = net.PeggyBroadcastClient - } else { - net, err := cosmos.NewLoadBalancedNetwork( - *cosmosChainID, - valAddress.String(), - *cosmosGasPrices, - cosmosKeyring, - personalSignFn, - ) - - if err != nil { - log.Fatalln("failed to connect to Injective network") - } - - peggyBroadcastClient = net.PeggyBroadcastClient + if err != nil { + log.Fatalln("failed to connect to Injective network") } broadcastCtx, cancelFn := context.WithTimeout(context.Background(), 15*time.Second) defer cancelFn() - if err = peggyBroadcastClient.UpdatePeggyOrchestratorAddresses(broadcastCtx, ethKeyFromAddress, valAddress); err != nil { + if err = peggy.BroadcastClient(net).UpdatePeggyOrchestratorAddresses(broadcastCtx, ethKeyFromAddress, keyring.Addr); err != nil { log.WithError(err).Errorln("failed to broadcast Tx") time.Sleep(time.Second) return } log.Infof("Registered Ethereum address %s for validator address %s", - ethKeyFromAddress, valAddress.String()) + ethKeyFromAddress, keyring.Addr.String()) } } diff --git a/go.mod b/go.mod index 54a7d699..1dc2e099 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,10 @@ go 1.19 require ( github.com/InjectiveLabs/etherman v1.7.0 github.com/InjectiveLabs/metrics v0.0.1 - github.com/InjectiveLabs/sdk-go v1.48.16 + github.com/InjectiveLabs/sdk-go v1.50.0 github.com/avast/retry-go v3.0.0+incompatible github.com/cometbft/cometbft v0.37.2 - github.com/cosmos/cosmos-sdk v0.47.3 + github.com/cosmos/cosmos-sdk v0.47.5 github.com/ethereum/go-ethereum v1.11.5 github.com/hashicorp/go-multierror v1.1.1 github.com/jawher/mow.cli v1.2.0 @@ -19,22 +19,22 @@ require ( github.com/stretchr/testify v1.8.4 github.com/xlab/closer v0.0.0-20190328110542-03326addb7c2 github.com/xlab/suplog v1.3.1 - golang.org/x/crypto v0.9.0 - google.golang.org/grpc v1.55.0 + golang.org/x/crypto v0.11.0 + google.golang.org/grpc v1.56.2 ) require ( cosmossdk.io/api v0.3.1 // indirect - cosmossdk.io/core v0.5.1 // indirect - cosmossdk.io/depinject v1.0.0-alpha.3 // indirect - cosmossdk.io/errors v1.0.0-beta.7 // indirect - cosmossdk.io/math v1.0.1 // indirect + cosmossdk.io/core v0.6.1 // indirect + cosmossdk.io/depinject v1.0.0-alpha.4 // indirect + cosmossdk.io/errors v1.0.0 // indirect + cosmossdk.io/math v1.1.2 // indirect filippo.io/edwards25519 v1.0.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect - github.com/99designs/keyring v1.2.1 // indirect + github.com/99designs/keyring v1.2.2 // indirect github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect - github.com/CosmWasm/wasmd v0.40.0 // indirect - github.com/CosmWasm/wasmvm v1.2.4 // indirect + github.com/CosmWasm/wasmd v0.40.2 // indirect + github.com/CosmWasm/wasmvm v1.5.0 // indirect github.com/DataDog/datadog-go/v5 v5.1.0 // indirect github.com/InjectiveLabs/suplog v1.3.3 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect @@ -42,7 +42,7 @@ require ( github.com/alexcesaro/statsd v2.0.0+incompatible // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/aws/aws-sdk-go v1.44.203 // indirect - github.com/bandprotocol/bandchain-packet v0.0.2 // indirect + github.com/bandprotocol/bandchain-packet v0.0.4 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect github.com/btcsuite/btcd v0.23.4 // indirect @@ -54,16 +54,19 @@ require ( github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chzyer/readline v1.5.1 // indirect + github.com/cockroachdb/errors v1.10.0 // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/redact v1.1.5 // indirect github.com/cometbft/cometbft-db v0.8.0 // indirect github.com/confio/ics23/go v0.9.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect - github.com/cosmos/cosmos-proto v1.0.0-beta.2 // indirect + github.com/cosmos/cosmos-proto v1.0.0-beta.3 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogoproto v1.4.10 // indirect - github.com/cosmos/iavl v0.20.0 // indirect - github.com/cosmos/ibc-go/v7 v7.0.1 // indirect + github.com/cosmos/iavl v0.20.1 // indirect + github.com/cosmos/ibc-go/v7 v7.3.0 // indirect github.com/cosmos/ics23/go v0.10.0 // indirect - github.com/cosmos/ledger-cosmos-go v0.12.1 // indirect + github.com/cosmos/ledger-cosmos-go v0.12.2 // indirect github.com/danieljoos/wincred v1.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect @@ -75,6 +78,7 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/getsentry/sentry-go v0.23.0 // indirect github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect @@ -88,7 +92,7 @@ require ( github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.5.9 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.4.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect @@ -111,11 +115,13 @@ require ( github.com/karalabe/usb v0.0.2 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/klauspost/compress v1.16.3 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/linxGnu/grocksdb v1.7.16 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/manifoldco/promptui v0.9.0 // indirect - github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect @@ -128,12 +134,13 @@ require ( github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.15.0 // indirect + github.com/prometheus/client_golang v1.16.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/shirou/gopsutil v3.21.6+incompatible // indirect github.com/sirupsen/logrus v1.9.0 // indirect @@ -157,15 +164,17 @@ require ( github.com/zondax/hid v0.9.1 // indirect github.com/zondax/ledger-go v0.14.1 // indirect go.etcd.io/bbolt v1.3.7 // indirect - golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect - golang.org/x/mod v0.9.0 // indirect - golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/term v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb // indirect + golang.org/x/mod v0.11.0 // indirect + golang.org/x/net v0.12.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/term v0.10.0 // indirect + golang.org/x/text v0.12.0 // indirect golang.org/x/tools v0.7.0 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect @@ -176,9 +185,9 @@ require ( ) replace ( - github.com/CosmWasm/wasmd => github.com/InjectiveLabs/wasmd v0.40.0-inj + github.com/CosmWasm/wasmd => github.com/InjectiveLabs/wasmd v0.45.0-inj github.com/bandprotocol/bandchain-packet => github.com/InjectiveLabs/bandchain-packet v0.0.4-0.20230327115226-35199d4659d5 github.com/cometbft/cometbft => github.com/InjectiveLabs/cometbft v0.37.1-inj - github.com/cosmos/cosmos-sdk => github.com/InjectiveLabs/cosmos-sdk v0.47.3-inj-5 + github.com/cosmos/cosmos-sdk => github.com/InjectiveLabs/cosmos-sdk v0.47.3-inj-9 github.com/ethereum/go-ethereum => github.com/ethereum/go-ethereum v1.12.0 ) diff --git a/go.sum b/go.sum index 890cccac..e1589e66 100644 --- a/go.sum +++ b/go.sum @@ -47,8 +47,8 @@ cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34h cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= -cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk= cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= @@ -159,7 +159,7 @@ cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARy cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= -cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ= +cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg= cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= @@ -283,7 +283,7 @@ cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQE cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= -cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k= +cloud.google.com/go/iam v1.1.0 h1:67gSqaPukx7O8WLLHMa0PNs3EBGd2eE4d+psbO/CO94= cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= @@ -474,8 +474,8 @@ cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= -cloud.google.com/go/storage v1.29.0 h1:6weCgzRvMg7lzuUurI4697AqIRPU1SvzHhynwpW31jI= cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM= cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= @@ -544,19 +544,23 @@ cosmossdk.io/api v0.3.0/go.mod h1:2HDRQHwVIyklENrrXko0E/waZrRFZWHhPyhcBO4qHq4= cosmossdk.io/api v0.3.1 h1:NNiOclKRR0AOlO4KIqeaG6PS6kswOMhHD0ir0SscNXE= cosmossdk.io/api v0.3.1/go.mod h1:DfHfMkiNA2Uhy8fj0JJlOCYOBp4eWUUJ1te5zBGNyIw= cosmossdk.io/core v0.3.2/go.mod h1:CO7vbe+evrBvHc0setFHL/u7nlY7HJGzdRSBkT/sirc= -cosmossdk.io/core v0.5.1 h1:vQVtFrIYOQJDV3f7rw4pjjVqc1id4+mE0L9hHP66pyI= cosmossdk.io/core v0.5.1/go.mod h1:KZtwHCLjcFuo0nmDc24Xy6CRNEL9Vl/MeimQ2aC7NLE= -cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw= +cosmossdk.io/core v0.6.1 h1:OBy7TI2W+/gyn2z40vVvruK3di+cAluinA6cybFbE7s= +cosmossdk.io/core v0.6.1/go.mod h1:g3MMBCBXtxbDWBURDVnJE7XML4BG5qENhs0gzkcpuFA= cosmossdk.io/depinject v1.0.0-alpha.3/go.mod h1:eRbcdQ7MRpIPEM5YUJh8k97nxHpYbc3sMUnEtt8HPWU= -cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w= +cosmossdk.io/depinject v1.0.0-alpha.4 h1:PLNp8ZYAMPTUKyG9IK2hsbciDWqna2z1Wsl98okJopc= +cosmossdk.io/depinject v1.0.0-alpha.4/go.mod h1:HeDk7IkR5ckZ3lMGs/o91AVUc7E596vMaOmslGFM3yU= cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE= -cosmossdk.io/log v1.1.0 h1:v0ogPHYeTzPcBTcPR1A3j1hkei4pZama8kz8LKlCMv0= +cosmossdk.io/errors v1.0.0 h1:nxF07lmlBbB8NKQhtJ+sJm6ef5uV1XkvPXG2bUntb04= +cosmossdk.io/errors v1.0.0/go.mod h1:+hJZLuhdDE0pYN8HkOrVNwrIOYvUGnn6+4fjnJs/oV0= cosmossdk.io/log v1.1.0/go.mod h1:6zjroETlcDs+mm62gd8Ig7mZ+N+fVOZS91V17H+M4N4= +cosmossdk.io/log v1.2.1 h1:Xc1GgTCicniwmMiKwDxUjO4eLhPxoVdI9vtMW8Ti/uk= cosmossdk.io/math v1.0.0-beta.3/go.mod h1:3LYasri3Zna4XpbrTNdKsWmD5fHHkaNAod/mNT9XdE4= cosmossdk.io/math v1.0.0-beta.4/go.mod h1:An0MllWJY6PxibUpnwGk8jOm+a/qIxlKmL5Zyp9NnaM= cosmossdk.io/math v1.0.0-beta.6/go.mod h1:gUVtWwIzfSXqcOT+lBVz2jyjfua8DoBdzRsIyaUAT/8= -cosmossdk.io/math v1.0.1 h1:Qx3ifyOPaMLNH/89WeZFH268yCvU4xEcnPLu3sJqPPg= cosmossdk.io/math v1.0.1/go.mod h1:Ygz4wBHrgc7g0N+8+MrnTfS9LLn9aaTGa9hKopuym5k= +cosmossdk.io/math v1.1.2 h1:ORZetZCTyWkI5GlZ6CZS28fMHi83ZYf+A2vVnHNzZBM= +cosmossdk.io/math v1.1.2/go.mod h1:l2Gnda87F0su8a/7FEKJfFdJrM0JZRXQaohlgJeyQh0= cosmossdk.io/tools/rosetta v0.2.1 h1:ddOMatOH+pbxWbrGJKRAawdBkPYLfKXutK9IETnjYxw= cosmossdk.io/tools/rosetta v0.2.1/go.mod h1:Pqdc1FdvkNV3LcNIkYWt2RQY6IP1ge6YWZk8MhhO9Hw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -569,8 +573,9 @@ git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqbl git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= -github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o= github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= +github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0= +github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk= github.com/Abirdcfly/dupword v0.0.7/go.mod h1:K/4M1kj+Zh39d2aotRwypvasonOyAMH1c/IZJzE0dmk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= github.com/AkihiroSuda/containerd-fuse-overlayfs v1.0.0/go.mod h1:0mMDvQFeLbbn1Wy8P2j3hwFhqBq+FKn8OZPno8WLmp8= @@ -649,8 +654,8 @@ github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3 github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w= github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/CloudyKit/jet/v6 v6.1.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= -github.com/CosmWasm/wasmvm v1.2.4 h1:6OfeZuEcEH/9iqwrg2pkeVtDCkMoj9U6PpKtcrCyVrQ= -github.com/CosmWasm/wasmvm v1.2.4/go.mod h1:vW/E3h8j9xBQs9bCoijDuawKo9kCtxOaS8N8J7KFtkc= +github.com/CosmWasm/wasmvm v1.5.0 h1:3hKeT9SfwfLhxTGKH3vXaKFzBz1yuvP8SlfwfQXbQfw= +github.com/CosmWasm/wasmvm v1.5.0/go.mod h1:fXB+m2gyh4v9839zlIXdMZGeLAxqUdYdFQqYsTha2hc= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/datadog-go/v5 v5.1.0 h1:Zmq3tCk9+Tdq8Du73M71Zo6Dyx+cEo9QkCSCqQlHFaQ= @@ -670,18 +675,18 @@ github.com/InjectiveLabs/bandchain-packet v0.0.4-0.20230327115226-35199d4659d5 h github.com/InjectiveLabs/bandchain-packet v0.0.4-0.20230327115226-35199d4659d5/go.mod h1:VoNDHSybdPQ35/3zxNwjewaGpzWHhYyTgV7cJzFglEE= github.com/InjectiveLabs/cometbft v0.37.1-inj h1:mNSorEwP72ovlb2HrYsH3L+uIgNgrUj5rI4DDXhatxk= github.com/InjectiveLabs/cometbft v0.37.1-inj/go.mod h1:Y2MMMN//O5K4YKd8ze4r9jmk4Y7h0ajqILXbH5JQFVs= -github.com/InjectiveLabs/cosmos-sdk v0.47.3-inj-5 h1:WbNT7o2AbXBqOvmPptAd1nVmx67l1LtXIs4GjR0gvXo= -github.com/InjectiveLabs/cosmos-sdk v0.47.3-inj-5/go.mod h1:c4OfLdAykA9zsj1CqrxBRqXzVz48I++JSvIMPSPcEmk= +github.com/InjectiveLabs/cosmos-sdk v0.47.3-inj-9 h1:/x20L4ZfYIhGu/wsmXb2nV6DF6t5QVNq7ecAFqAcrMU= +github.com/InjectiveLabs/cosmos-sdk v0.47.3-inj-9/go.mod h1:c4OfLdAykA9zsj1CqrxBRqXzVz48I++JSvIMPSPcEmk= github.com/InjectiveLabs/etherman v1.7.0 h1:ryYW87fqpnhRP33x+ykDhy6Lx9215Fl5T5LIiA78W88= github.com/InjectiveLabs/etherman v1.7.0/go.mod h1:7unNlA/9UVNZhneEWtNnFWfGDJ5KijkGprkpEF9I1BA= github.com/InjectiveLabs/metrics v0.0.1 h1:MXNj8JWOdIqiGZw83JdUTR+i6hgBrb12HatIUvaly9I= github.com/InjectiveLabs/metrics v0.0.1/go.mod h1:Dmgd60Z0pfi7uOGSUzyqZ00tbMYmZK25u8Sjgk3Ay4A= -github.com/InjectiveLabs/sdk-go v1.48.16 h1:OAraiGNKHk/O1scFIjUh6CtgnjpYDnC/jsqK8OKDuZU= -github.com/InjectiveLabs/sdk-go v1.48.16/go.mod h1:IPc0yp3d0++M7b/c+Frfb3PpFQz8kMF4rb49IXj3Drw= +github.com/InjectiveLabs/sdk-go v1.50.0 h1:dKHUZ1AC7BLxP5srUFsdRjGaP3cyo//Okr8KRW06tDk= +github.com/InjectiveLabs/sdk-go v1.50.0/go.mod h1:ecmY701q1cAZaNdeuzs08x7diCC8z5qjvcpbETR81oY= github.com/InjectiveLabs/suplog v1.3.3 h1:ARIR3lWD9BxcrmqTwgcGBt8t7e10gwOqllUAXa/MfxI= github.com/InjectiveLabs/suplog v1.3.3/go.mod h1:+I9WRgUhzmo1V/n7IkW24kFBFB9ZTPAiXXXCogWxmTM= -github.com/InjectiveLabs/wasmd v0.40.0-inj h1:HBienMKEZufMHIK8gaqBukzPNmIzj4NQcLL7YbZm7No= -github.com/InjectiveLabs/wasmd v0.40.0-inj/go.mod h1:GKEn2k43oSu/WX4hd9tMGZi3ykgfcn01rLa116B/Dhs= +github.com/InjectiveLabs/wasmd v0.45.0-inj h1:hYRzxEiBNJqJA7EoE9kMTFpWayjT2ICo/FsmtAf8A+o= +github.com/InjectiveLabs/wasmd v0.45.0-inj/go.mod h1:M8hxvHoOzZkzdrFNqDYcPPcQw7EmJwYNZkslhHbCj0I= github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM= @@ -762,7 +767,6 @@ github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3 github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= github.com/alecthomas/kingpin/v2 v2.3.1/go.mod h1:oYL5vtsvEHZGHxU7DMp32Dvx+qL+ptGn6lWaot2vCNE= -github.com/alecthomas/participle/v2 v2.0.0-alpha7 h1:cK4vjj0VSgb3lN1nuKA5F7dw+1s1pWBe5bx7nNCnN+c= github.com/alecthomas/participle/v2 v2.0.0-alpha7/go.mod h1:NumScqsC42o9x+dGj8/YqsIfhrIQjFEOFovxotbBirA= github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -862,8 +866,8 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 h1:41iFGWnSlI2gVpmOtVTJZNodLdLQLn/KsJqFvXwnd/s= github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= @@ -1004,7 +1008,6 @@ github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= -github.com/cockroachdb/apd/v3 v3.1.0 h1:MK3Ow7LH0W8zkd5GMKA1PvS9qG3bWFI95WaVNfyZJ/w= github.com/cockroachdb/apd/v3 v3.1.0/go.mod h1:6qgPBMXjATAdD/VefbRP9NoSLKjbB4LCoA7gN4LpHs4= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= @@ -1013,18 +1016,20 @@ github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSU github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/errors v1.6.1/go.mod h1:tm6FTP5G81vwJ5lC0SizQo374JNCOPrHyXGitRJoDqM= github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac= -github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= +github.com/cockroachdb/errors v1.10.0 h1:lfxS8zZz1+OjtV4MtNWgboi/W5tyLEB6VQZBXN+0VUU= +github.com/cockroachdb/errors v1.10.0/go.mod h1:lknhIsEVQ9Ss/qKDBQS/UqFSvPQjOwNq2qyKAxtHRqE= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/pebble v0.0.0-20220817183557-09c6e030a677/go.mod h1:890yq1fUb9b6dGNwssgeUO5vQV9qfXnCPxAJhBQfXw0= -github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk= github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lgvnQ2+oGlE/EyZy4+2/CxRh9KfvCXnag1vtpxVM= +github.com/cockroachdb/pebble v0.0.0-20230226194802-02d779ffbc46 h1:yMaoO76pV9knZ6bzEwzPSHnPSCTnrJohwkIQirmii70= github.com/cockroachdb/redact v1.0.8/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ= github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= @@ -1181,8 +1186,9 @@ github.com/cosmos/cosmos-db v0.0.0-20221226095112-f3c38ecb5e32/go.mod h1:kwMlEC4 github.com/cosmos/cosmos-proto v1.0.0-alpha7/go.mod h1:dosO4pSAbJF8zWCzCoTWP7nNsjcvSUBQmniFxDg5daw= github.com/cosmos/cosmos-proto v1.0.0-alpha8/go.mod h1:6/p+Bc4O8JKeZqe0VqUGTX31eoYqemTT4C1hLCWsO7I= github.com/cosmos/cosmos-proto v1.0.0-beta.1/go.mod h1:8k2GNZghi5sDRFw/scPL8gMSowT1vDA+5ouxL8GjaUE= -github.com/cosmos/cosmos-proto v1.0.0-beta.2 h1:X3OKvWgK9Gsejo0F1qs5l8Qn6xJV/AzgIWR2wZ8Nua8= github.com/cosmos/cosmos-proto v1.0.0-beta.2/go.mod h1:+XRCLJ14pr5HFEHIUcn51IKXD1Fy3rkEQqt4WqmN4V0= +github.com/cosmos/cosmos-proto v1.0.0-beta.3 h1:VitvZ1lPORTVxkmF2fAp3IiA61xVwArQYKXTdEcpW6o= +github.com/cosmos/cosmos-proto v1.0.0-beta.3/go.mod h1:t8IASdLaAq+bbHbjq4p960BvcTqtwuAxid3b/2rOD6I= github.com/cosmos/cosmos-sdk/db v1.0.0-beta.1.0.20220726092710-f848e4300a8a/go.mod h1:c8IO23vgNxueCCJlSI9awQtcxsvc+buzaeThB85qfBU= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= @@ -1198,14 +1204,16 @@ github.com/cosmos/gogoproto v1.4.10 h1:QH/yT8X+c0F4ZDacDv3z+xE3WU1P1Z3wQoLMBRJoK github.com/cosmos/gogoproto v1.4.10/go.mod h1:3aAZzeRWpAwr+SS/LLkICX2/kDFyaYVzckBDzygIxek= github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw= github.com/cosmos/iavl v0.20.0-alpha4/go.mod h1:WO7FyvaZJoH65+HFOsDir7xU9FWk2w9cHXNW1XHcl7A= -github.com/cosmos/iavl v0.20.0 h1:fTVznVlepH0KK8NyKq8w+U7c2L6jofa27aFX6YGlm38= github.com/cosmos/iavl v0.20.0/go.mod h1:WO7FyvaZJoH65+HFOsDir7xU9FWk2w9cHXNW1XHcl7A= -github.com/cosmos/ibc-go/v7 v7.0.1 h1:NIBNRWjlOoFvFQu1ZlgwkaSeHO5avf4C1YQiWegt8jw= -github.com/cosmos/ibc-go/v7 v7.0.1/go.mod h1:vEaapV6nuLPQlS+g8IKmxMo6auPi0i7HMv1PhViht/E= +github.com/cosmos/iavl v0.20.1 h1:rM1kqeG3/HBT85vsZdoSNsehciqUQPWrR4BYmqE2+zg= +github.com/cosmos/iavl v0.20.1/go.mod h1:WO7FyvaZJoH65+HFOsDir7xU9FWk2w9cHXNW1XHcl7A= +github.com/cosmos/ibc-go/v7 v7.3.0 h1:QtGeVMi/3JeLWuvEuC60sBHpAF40Oenx/y+bP8+wRRw= +github.com/cosmos/ibc-go/v7 v7.3.0/go.mod h1:mUmaHFXpXrEdcxfdXyau+utZf14pGKVUiXwYftRZZfQ= github.com/cosmos/ics23/go v0.10.0 h1:iXqLLgp2Lp+EdpIuwXTYIQU+AiHj9mOC2X9ab++bZDM= github.com/cosmos/ics23/go v0.10.0/go.mod h1:ZfJSmng/TBNTBkFemHHHj5YY7VAU/MBU980F4VU1NG0= -github.com/cosmos/ledger-cosmos-go v0.12.1 h1:sMBxza5p/rNK/06nBSNmsI/WDqI0pVJFVNihy1Y984w= github.com/cosmos/ledger-cosmos-go v0.12.1/go.mod h1:dhO6kj+Y+AHIOgAe4L9HL/6NDdyyth4q238I9yFpD2g= +github.com/cosmos/ledger-cosmos-go v0.12.2 h1:/XYaBlE2BJxtvpkHiBm97gFGSGmYGKunKyF3nNqAXZA= +github.com/cosmos/ledger-cosmos-go v0.12.2/go.mod h1:ZcqYgnfNJ6lAXe4HPtWgarNEY+B74i+2/8MhZw4ziiI= github.com/cosmos/rosetta-sdk-go v0.10.0 h1:E5RhTruuoA7KTIXUcMicL76cffyeoyvNybzUGSKFTcM= github.com/cosmos/rosetta-sdk-go v0.10.0/go.mod h1:SImAZkb96YbwvoRkzSMQB6noNJXFgWl/ENIznEoYQI4= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= @@ -1223,9 +1231,7 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cristalhq/acmd v0.8.1/go.mod h1:LG5oa43pE/BbxtfMoImHCQN++0Su7dzipdgBjMCBVDQ= -github.com/cucumber/common/gherkin/go/v22 v22.0.0 h1:4K8NqptbvdOrjL9DEea6HFjSpbdT9+Q5kgLpmmsHYl0= github.com/cucumber/common/gherkin/go/v22 v22.0.0/go.mod h1:3mJT10B2GGn3MvVPd3FwR7m2u4tLhSRhWUqJU4KN4Fg= -github.com/cucumber/common/messages/go/v17 v17.1.1 h1:RNqopvIFyLWnKv0LfATh34SWBhXeoFTJnSrgm9cT/Ts= github.com/cucumber/common/messages/go/v17 v17.1.1/go.mod h1:bpGxb57tDE385Rb2EohgUadLkAbhoC4IyCFi89u/JQI= github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= @@ -1427,8 +1433,9 @@ github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSy github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= github.com/getsentry/sentry-go v0.17.0/go.mod h1:B82dxtBvxG0KaPD8/hfSV+VcHD+Lg/xUS4JuQn1P4cM= -github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= +github.com/getsentry/sentry-go v0.23.0 h1:dn+QRCeJv4pPt9OjVXiMcGIBIefaTJPw/h0bZWO05nE= +github.com/getsentry/sentry-go v0.23.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -1446,6 +1453,7 @@ github.com/go-critic/go-critic v0.4.1/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E github.com/go-critic/go-critic v0.4.3/go.mod h1:j4O3D4RoIwRqlZw5jJpx0BNfXWWbpcJoKu5cYSe4YmQ= github.com/go-critic/go-critic v0.6.5/go.mod h1:ezfP/Lh7MA6dBNn4c6ab5ALv3sKnZVLx37tr00uuaOY= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= @@ -1745,7 +1753,7 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg= -github.com/google/s2a-go v0.1.3 h1:FAgZmpLl/SXurPEZyCMPBIiiYeTbqfjlbdnCNTAkbGE= +github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= @@ -1755,8 +1763,9 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s= github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= @@ -1778,7 +1787,7 @@ github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= -github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK67vJZc= +github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= @@ -2255,8 +2264,9 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -2562,6 +2572,7 @@ github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= @@ -2604,8 +2615,8 @@ github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrb github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM= -github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -2651,8 +2662,9 @@ github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7/go.mod h1:IToEjHuttnUzwZI5KBSM/LOOW3qLbbrHOEfp3SbECGY= github.com/pseudomuto/protoc-gen-doc v1.3.2/go.mod h1:y5+P6n3iGrbKG+9O04V5ld71in3v/bX88wUwgt+U8EA= @@ -2673,7 +2685,6 @@ github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Ung github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/regen-network/gocuke v0.6.2 h1:pHviZ0kKAq2U2hN2q3smKNxct6hS0mGByFMHGnWA97M= github.com/regen-network/gocuke v0.6.2/go.mod h1:zYaqIHZobHyd0xOrHGPQjbhGJsuZ1oElx150u2o1xuk= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= @@ -2690,8 +2701,9 @@ github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo= @@ -2699,8 +2711,8 @@ github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U= -github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= +github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c= github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= @@ -3237,8 +3249,8 @@ golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80 golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -3264,8 +3276,9 @@ golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZ golang.org/x/exp v0.0.0-20230131160201-f062dba9d201/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb h1:xIApU0ow1zwMa2uL1VDNeQlNVFTWMQxZUZCMDy0Q4Us= +golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20220613132600-b0d781184e0d/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= @@ -3316,8 +3329,9 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -3429,8 +3443,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -3468,7 +3482,7 @@ golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= -golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -3487,8 +3501,8 @@ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -3680,8 +3694,8 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -3696,8 +3710,8 @@ golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -3714,8 +3728,9 @@ golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -3729,8 +3744,8 @@ golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -3961,7 +3976,7 @@ google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/ google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= -google.golang.org/api v0.122.0 h1:zDobeejm3E7pEG1mNHvdxvjs5XJoCMzyNH+CmwL94Es= +google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -4128,8 +4143,12 @@ google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 h1:Au6te5hbKUV8pIYWHqOUZ1pva5qK/rwbIhoXEUB9Lu8= +google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y= +google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 h1:s5YSX+ZH5b5vS9rnpGymvIyMpLRJizowqDlOuyjXnTk= +google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -4185,8 +4204,9 @@ google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsA google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= -google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= +google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -4207,8 +4227,9 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.2-0.20230208135220-49eaa78c6c9c/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.2-0.20230222093303-bc1253ad3743/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/statsd.v2 v2.0.0 h1:FXkZSCZIH17vLCO5sO2UucTHsH9pc+17F6pl3JVCwMc= @@ -4279,8 +4300,8 @@ gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ= gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= -gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= +gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/orchestrator/batch_request.go b/orchestrator/batch_request.go deleted file mode 100644 index 7ee1e01d..00000000 --- a/orchestrator/batch_request.go +++ /dev/null @@ -1,129 +0,0 @@ -package orchestrator - -import ( - "context" - "time" - - "github.com/avast/retry-go" - cosmtypes "github.com/cosmos/cosmos-sdk/types" - eth "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" -) - -func (s *PeggyOrchestrator) BatchRequesterLoop(ctx context.Context) (err error) { - loop := batchRequestLoop{ - PeggyOrchestrator: s, - loopDuration: defaultLoopDur, - } - - return loop.Run(ctx) -} - -type batchRequestLoop struct { - *PeggyOrchestrator - loopDuration time.Duration -} - -func (l *batchRequestLoop) Logger() log.Logger { - return l.logger.WithField("loop", "BatchRequest") -} - -func (l *batchRequestLoop) Run(ctx context.Context) error { - return loops.RunLoop(ctx, l.loopDuration, func() error { - return l.requestBatches(ctx) - }) -} - -func (l *batchRequestLoop) requestBatches(ctx context.Context) error { - fees, err := l.getUnbatchedTokenFees(ctx) - if err != nil { - // non-fatal, just alert - l.Logger().WithError(err).Warningln("unable to get outgoing withdrawal fees") - return nil - } - - if len(fees) == 0 { - l.Logger().Debugln("no withdrawals to batch") - return nil - } - - for _, fee := range fees { - l.requestBatch(ctx, fee) - - // todo: in case of multiple requests, we should sleep in between (non-continuous nonce) - } - - return nil -} - -func (l *batchRequestLoop) getUnbatchedTokenFees(ctx context.Context) ([]*types.BatchFees, error) { - var unbatchedFees []*types.BatchFees - getUnbatchedTokenFeesFn := func() (err error) { - unbatchedFees, err = l.inj.UnbatchedTokensWithFees(ctx) - return err - } - - if err := retry.Do(getUnbatchedTokenFeesFn, - retry.Context(ctx), - retry.Attempts(l.maxAttempts), - retry.OnRetry(func(n uint, err error) { - l.Logger().WithError(err).Errorf("failed to get outgoing withdrawal fees, will retry (%d)", n) - }), - ); err != nil { - return nil, err - } - - return unbatchedFees, nil -} - -func (l *batchRequestLoop) requestBatch(ctx context.Context, fee *types.BatchFees) { - var ( - tokenAddr = eth.HexToAddress(fee.Token) - tokenDenom = l.tokenDenom(tokenAddr) - ) - - if thresholdMet := l.checkFeeThreshold(tokenAddr, fee.TotalFees); !thresholdMet { - return - } - - l.Logger().WithFields(log.Fields{"denom": tokenDenom, "token_contract": tokenAddr.String()}).Infoln("requesting batch on Injective") - - _ = l.inj.SendRequestBatch(ctx, tokenDenom) -} - -func (l *batchRequestLoop) tokenDenom(tokenAddr eth.Address) string { - if cosmosDenom, ok := l.erc20ContractMapping[tokenAddr]; ok { - return cosmosDenom - } - - // peggy denom - // todo: in reality, peggy denominators don't have an actual price listing - // So it seems that bridge fee must always be inj - return types.PeggyDenomString(tokenAddr) -} - -func (l *batchRequestLoop) checkFeeThreshold(tokenAddr eth.Address, fees cosmtypes.Int) bool { - if l.minBatchFeeUSD == 0 { - return true - } - - tokenPriceInUSD, err := l.pricefeed.QueryUSDPrice(tokenAddr) - if err != nil { - return false - } - - minFeeInUSDDec := decimal.NewFromFloat(l.minBatchFeeUSD) - tokenPriceInUSDDec := decimal.NewFromFloat(tokenPriceInUSD) - totalFeeInUSDDec := decimal.NewFromBigInt(fees.BigInt(), -18).Mul(tokenPriceInUSDDec) - - if totalFeeInUSDDec.LessThan(minFeeInUSDDec) { - l.Logger().WithFields(log.Fields{"token_contract": tokenAddr.String(), "batch_fee": totalFeeInUSDDec.String(), "min_fee": minFeeInUSDDec.String()}).Debugln("insufficient token batch fee") - return false - } - - return true -} diff --git a/orchestrator/batch_request_test.go b/orchestrator/batch_request_test.go deleted file mode 100644 index 3ae46e36..00000000 --- a/orchestrator/batch_request_test.go +++ /dev/null @@ -1,202 +0,0 @@ -package orchestrator - -import ( - "context" - "errors" - "testing" - - cosmtypes "github.com/cosmos/cosmos-sdk/types" - 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" -) - -func TestRequestBatches(t *testing.T) { - t.Parallel() - - t.Run("failed to get unbatched tokens from injective", func(t *testing.T) { - t.Parallel() - - inj := &mockInjective{ - unbatchedTokenFeesFn: func(context.Context) ([]*peggytypes.BatchFees, error) { - return nil, errors.New("fail") - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - maxAttempts: 1, - } - - loop := batchRequestLoop{ - PeggyOrchestrator: o, - } - - assert.NoError(t, loop.requestBatches(context.TODO())) - }) - - t.Run("no unbatched tokens", func(t *testing.T) { - t.Parallel() - - inj := &mockInjective{ - unbatchedTokenFeesFn: func(context.Context) ([]*peggytypes.BatchFees, error) { - return nil, nil - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - maxAttempts: 1, - } - - loop := batchRequestLoop{ - PeggyOrchestrator: o, - } - - assert.NoError(t, loop.requestBatches(context.TODO())) - - }) - - t.Run("batch does not meet fee threshold", func(t *testing.T) { - t.Parallel() - - tokenAddr := "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30" - - inj := &mockInjective{ - sendRequestBatchFn: func(context.Context, string) error { return nil }, - unbatchedTokenFeesFn: func(context.Context) ([]*peggytypes.BatchFees, error) { - fees, _ := cosmtypes.NewIntFromString("50000000000000000000") - return []*peggytypes.BatchFees{ - { - Token: eth.HexToAddress(tokenAddr).String(), - TotalFees: fees, - }, - }, nil - }, - } - - feed := mockPriceFeed{queryFn: func(_ eth.Address) (float64, error) { return 1, nil }} - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - pricefeed: feed, - maxAttempts: 1, - minBatchFeeUSD: 51.0, - erc20ContractMapping: map[eth.Address]string{ - eth.HexToAddress(tokenAddr): "inj", - }, - } - - loop := batchRequestLoop{ - PeggyOrchestrator: o, - } - - assert.NoError(t, loop.requestBatches(context.TODO())) - assert.Equal(t, inj.sendRequestBatchCallCount, 0) - }) - - t.Run("batch meets threshold and a request is sent", func(t *testing.T) { - t.Parallel() - - tokenAddr := "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30" - - inj := &mockInjective{ - sendRequestBatchFn: func(context.Context, string) error { return nil }, - unbatchedTokenFeesFn: func(_ context.Context) ([]*peggytypes.BatchFees, error) { - fees, _ := cosmtypes.NewIntFromString("50000000000000000000") - return []*peggytypes.BatchFees{ - { - Token: eth.HexToAddress(tokenAddr).String(), - TotalFees: fees, - }, - }, nil - }, - } - - feed := mockPriceFeed{queryFn: func(_ eth.Address) (float64, error) { return 1, nil }} - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - pricefeed: feed, - maxAttempts: 1, - minBatchFeeUSD: 49.0, - erc20ContractMapping: map[eth.Address]string{ - eth.HexToAddress(tokenAddr): "inj", - }, - } - - loop := batchRequestLoop{ - PeggyOrchestrator: o, - } - - assert.NoError(t, loop.requestBatches(context.TODO())) - assert.Equal(t, inj.sendRequestBatchCallCount, 1) - }) - -} - -func TestCheckFeeThreshold(t *testing.T) { - t.Parallel() - - t.Run("fee threshold is met", func(t *testing.T) { - t.Parallel() - - var ( - totalFees, _ = cosmtypes.NewIntFromString("10000000000000000000") // 10inj - tokenAddr = eth.HexToAddress("0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30") - feed = mockPriceFeed{queryFn: func(_ eth.Address) (float64, error) { - return 2.5, nil - }} - ) - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - pricefeed: feed, - minBatchFeeUSD: 21, - erc20ContractMapping: map[eth.Address]string{ - tokenAddr: "inj", - }, - } - - loop := batchRequestLoop{ - PeggyOrchestrator: o, - } - - // 2.5 * 10 > 21 - assert.True(t, loop.checkFeeThreshold(tokenAddr, totalFees)) - }) - - t.Run("fee threshold is met", func(t *testing.T) { - t.Parallel() - - var ( - tokenAddr = eth.HexToAddress("0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30") - totalFees, _ = cosmtypes.NewIntFromString("100000000000000000000") // 10inj - feed = mockPriceFeed{queryFn: func(_ eth.Address) (float64, error) { - return 2.5, nil - }} - ) - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - pricefeed: feed, - minBatchFeeUSD: 333.333, - erc20ContractMapping: map[eth.Address]string{ - tokenAddr: "inj", - }, - } - - loop := batchRequestLoop{ - PeggyOrchestrator: o, - } - - // 2.5 * 100 < 333.333 - assert.False(t, loop.checkFeeThreshold(tokenAddr, totalFees)) - }) -} diff --git a/orchestrator/batch_requester.go b/orchestrator/batch_requester.go new file mode 100644 index 00000000..1d098308 --- /dev/null +++ b/orchestrator/batch_requester.go @@ -0,0 +1,128 @@ +package orchestrator + +import ( + "context" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/shopspring/decimal" + log "github.com/xlab/suplog" + + peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + + "github.com/InjectiveLabs/peggo/orchestrator/cosmos" + "github.com/InjectiveLabs/peggo/orchestrator/loops" +) + +func (s *Orchestrator) runBatchRequester(ctx context.Context, inj cosmos.Network, eth ethereum.Network) (err error) { + requester := batchRequester{ + Orchestrator: s, + Injective: inj, + Ethereum: eth, + } + + s.logger.WithField("loop_duration", defaultLoopDur.String()).Debugln("starting BatchRequester...") + + return loops.RunLoop(ctx, defaultLoopDur, func() error { + return requester.RequestBatches(ctx) + }) +} + +type batchRequester struct { + *Orchestrator + Injective cosmos.Network + Ethereum ethereum.Network +} + +func (l *batchRequester) Logger() log.Logger { + return l.logger.WithField("loop", "BatchRequest") +} + +func (l *batchRequester) RequestBatches(ctx context.Context) error { + fees, err := l.GetUnbatchedTokenFees(ctx) + if err != nil { + l.Logger().WithError(err).Warningln("unable to get outgoing withdrawal fees") + return nil + } + + if len(fees) == 0 { + l.Logger().Infoln("no withdrawals to batch") + return nil + } + + for _, fee := range fees { + l.RequestTokenBatch(ctx, fee) + } + + return nil +} + +func (l *batchRequester) GetUnbatchedTokenFees(ctx context.Context) ([]*peggytypes.BatchFees, error) { + var unbatchedFees []*peggytypes.BatchFees + fn := func() error { + fees, err := l.Injective.UnbatchedTokensWithFees(ctx) + if err != nil { + return err + } + + unbatchedFees = fees + + return nil + } + + if err := retryFnOnErr(ctx, l.Logger(), fn); err != nil { + return nil, err + } + + return unbatchedFees, nil +} + +func (l *batchRequester) RequestTokenBatch(ctx context.Context, fee *peggytypes.BatchFees) { + tokenContract := gethcommon.HexToAddress(fee.Token) + tokenPriceInUSD, err := l.priceFeed.QueryUSDPrice(tokenContract) + if err != nil { + l.Logger().WithError(err).Warningln("failed to query oracle for token price") + return + } + + tokenDecimals, err := l.Ethereum.TokenDecimals(ctx, tokenContract) + if err != nil { + l.Logger().WithError(err).Warningln("failed to query decimals from token contract") + return + } + + if l.CheckMinBatchFee(fee, tokenPriceInUSD, tokenDecimals) { + return + } + + tokenDenom := l.GetTokenDenom(tokenContract) + l.Logger().WithFields(log.Fields{"token_denom": tokenDenom, "token_contract": tokenContract.String()}).Infoln("requesting new token batch on Injective") + + _ = l.Injective.SendRequestBatch(ctx, tokenDenom) +} + +func (l *batchRequester) GetTokenDenom(tokenAddr gethcommon.Address) string { + if cosmosDenom, ok := l.erc20ContractMapping[tokenAddr]; ok { + return cosmosDenom + } + + return peggytypes.PeggyDenomString(tokenAddr) +} + +func (l *batchRequester) CheckMinBatchFee(fee *peggytypes.BatchFees, tokenPriceInUSD float64, tokenDecimals uint8) bool { + if l.minBatchFeeUSD == 0 { + return true + } + + var ( + minFeeInUSDDec = decimal.NewFromFloat(l.minBatchFeeUSD) + tokenPriceInUSDDec = decimal.NewFromFloat(tokenPriceInUSD) + totalFeeInUSDDec = decimal.NewFromBigInt(fee.TotalFees.BigInt(), -1*int32(tokenDecimals)).Mul(tokenPriceInUSDDec) + ) + + if totalFeeInUSDDec.LessThan(minFeeInUSDDec) { + l.Logger().WithFields(log.Fields{"token_contract": fee.Token, "total_fee": totalFeeInUSDDec.String(), "min_fee": minFeeInUSDDec.String()}).Debugln("insufficient fee for token batch request, skipping...") + return false + } + + return true +} diff --git a/orchestrator/coingecko/coingecko.go b/orchestrator/coingecko/coingecko.go index f10f69c1..9f2e7289 100644 --- a/orchestrator/coingecko/coingecko.go +++ b/orchestrator/coingecko/coingecko.go @@ -4,7 +4,6 @@ import ( "encoding/json" "io" "io/ioutil" - "net/http" "net/url" "path" @@ -27,7 +26,7 @@ const ( var zeroPrice = float64(0) -type CoingeckoPriceFeed struct { +type PriceFeed struct { client *http.Client config *Config @@ -51,7 +50,7 @@ func urlJoin(baseURL string, segments ...string) string { } -func (cp *CoingeckoPriceFeed) QueryUSDPrice(erc20Contract common.Address) (float64, error) { +func (cp *PriceFeed) QueryUSDPrice(erc20Contract common.Address) (float64, error) { metrics.ReportFuncCall(cp.svcTags) doneFn := metrics.ReportFuncTiming(cp.svcTags) defer doneFn() @@ -123,10 +122,10 @@ func (cp *CoingeckoPriceFeed) QueryUSDPrice(erc20Contract common.Address) (float return tokenPriceInUSD, nil } -// NewCoingeckoPriceFeed returns price puller for given symbol. The price will be pulled +// NewPriceFeed returns price puller for given symbol. The price will be pulled // from endpoint and divided by scaleFactor. Symbol name (if reported by endpoint) must match. -func NewCoingeckoPriceFeed(interval time.Duration, endpointConfig *Config) *CoingeckoPriceFeed { - return &CoingeckoPriceFeed{ +func NewPriceFeed(interval time.Duration, endpointConfig *Config) *PriceFeed { + return &PriceFeed{ client: &http.Client{ Transport: &http.Transport{ ResponseHeaderTimeout: maxRespHeadersTime, @@ -159,7 +158,7 @@ func checkCoingeckoConfig(cfg *Config) *Config { return cfg } -func (cp *CoingeckoPriceFeed) CheckFeeThreshold(erc20Contract common.Address, totalFee cosmtypes.Int, minFeeInUSD float64) bool { +func (cp *PriceFeed) CheckFeeThreshold(erc20Contract common.Address, totalFee cosmtypes.Int, minFeeInUSD float64) bool { metrics.ReportFuncCall(cp.svcTags) doneFn := metrics.ReportFuncTiming(cp.svcTags) defer doneFn() diff --git a/orchestrator/coingecko/coingecko_test.go b/orchestrator/coingecko/coingecko_test.go index 1de34652..1ae706b4 100644 --- a/orchestrator/coingecko/coingecko_test.go +++ b/orchestrator/coingecko/coingecko_test.go @@ -13,7 +13,7 @@ func TestFeeThresholdTwoDecimals(t *testing.T) { // https://api.coingecko.com/api/v3/simple/token_price/ethereum?contract_addresses=0xe28b3b32b6c345a34ff64674606124dd5aceca30&vs_currencies=usd injTokenContract := common.HexToAddress("0xe28b3b32b6c345a34ff64674606124dd5aceca30") - coingeckoFeed := NewCoingeckoPriceFeed(100, &Config{}) + coingeckoFeed := NewPriceFeed(100, &Config{}) currentTokenPrice, _ := coingeckoFeed.QueryUSDPrice(injTokenContract) // "usd":9.35 minFeeInUSD := float64(23.5) // 23.5 USD to submit batch tx @@ -34,7 +34,7 @@ func TestFeeThresholdTwoDecimals(t *testing.T) { func TestFeeThresholdNineDecimals(t *testing.T) { // https://api.coingecko.com/api/v3/simple/token_price/ethereum?contract_addresses=0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce&vs_currencies=usd shibTokenContract := common.HexToAddress("0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce") - coingeckoFeed := NewCoingeckoPriceFeed(100, &Config{}) + coingeckoFeed := NewPriceFeed(100, &Config{}) currentTokenPrice, _ := coingeckoFeed.QueryUSDPrice(shibTokenContract) // "usd":0.000008853 minFeeInUSD := float64(23.5) // 23.5 USD to submit batch tx diff --git a/orchestrator/cosmos/broadcast.go b/orchestrator/cosmos/broadcast.go deleted file mode 100644 index d88a229e..00000000 --- a/orchestrator/cosmos/broadcast.go +++ /dev/null @@ -1,594 +0,0 @@ -package cosmos - -import ( - "context" - "fmt" - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - ethcmn "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" - log "github.com/xlab/suplog" - - "github.com/InjectiveLabs/sdk-go/chain/peggy/types" - chainclient "github.com/InjectiveLabs/sdk-go/client/chain" - - "github.com/InjectiveLabs/metrics" - - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/keystore" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/peggy" - - wrappers "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" -) - -type PeggyBroadcastClient interface { - ValFromAddress() sdk.ValAddress - AccFromAddress() sdk.AccAddress - - /// Send a transaction updating the eth address for the sending - /// Cosmos address. The sending Cosmos address should be a validator - UpdatePeggyOrchestratorAddresses( - ctx context.Context, - ethFrom ethcmn.Address, - orchAddr sdk.AccAddress, - ) error - - // SendValsetConfirm broadcasts in a confirmation for a specific validator set for a specific block height. - SendValsetConfirm( - ctx context.Context, - ethFrom ethcmn.Address, - peggyID ethcmn.Hash, - valset *types.Valset, - ) error - - // SendBatchConfirm broadcasts in a confirmation for a specific transaction batch set for a specific block height - // since transaction batches also include validator sets this has all the arguments - SendBatchConfirm( - ctx context.Context, - ethFrom ethcmn.Address, - peggyID ethcmn.Hash, - batch *types.OutgoingTxBatch, - ) error - - SendEthereumClaims( - ctx context.Context, - lastClaimEvent uint64, - oldDeposits []*wrappers.PeggySendToCosmosEvent, - deposits []*wrappers.PeggySendToInjectiveEvent, - withdraws []*wrappers.PeggyTransactionBatchExecutedEvent, - erc20Deployed []*wrappers.PeggyERC20DeployedEvent, - valsetUpdates []*wrappers.PeggyValsetUpdatedEvent, - ) error - - // SendToEth broadcasts a Tx that tokens from Cosmos to Ethereum. - // These tokens will not be sent immediately. Instead, they will require - // some time to be included in a batch. - SendToEth( - ctx context.Context, - destination ethcmn.Address, - amount, fee sdk.Coin, - ) error - - // SendRequestBatch broadcasts a requests a batch of withdrawal transactions to be generated on the chain. - SendRequestBatch( - ctx context.Context, - denom string, - ) error -} - -func NewPeggyBroadcastClient( - queryClient types.QueryClient, - broadcastClient chainclient.ChainClient, - ethPersonalSignFn keystore.PersonalSignFn, -) PeggyBroadcastClient { - return &peggyBroadcastClient{ - daemonQueryClient: queryClient, - broadcastClient: broadcastClient, - ethPersonalSignFn: ethPersonalSignFn, - - svcTags: metrics.Tags{ - "svc": "peggy_broadcast", - }, - } -} - -func (s *peggyBroadcastClient) ValFromAddress() sdk.ValAddress { - return sdk.ValAddress(s.broadcastClient.FromAddress().Bytes()) -} - -func (s *peggyBroadcastClient) AccFromAddress() sdk.AccAddress { - return s.broadcastClient.FromAddress() -} - -type peggyBroadcastClient struct { - daemonQueryClient types.QueryClient - broadcastClient chainclient.ChainClient - ethSignerFn keystore.SignerFn - ethPersonalSignFn keystore.PersonalSignFn - - svcTags metrics.Tags -} - -func (s *peggyBroadcastClient) UpdatePeggyOrchestratorAddresses( - ctx context.Context, - ethFrom ethcmn.Address, - orchestratorAddr sdk.AccAddress, -) error { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - // SetOrchestratorAddresses - - // This message allows validators to delegate their voting responsibilities - // to a given key. This key is then used as an optional authentication method - // for sigining oracle claims - // This is used by the validators to set the Ethereum address that represents - // them on the Ethereum side of the bridge. They must sign their Cosmos address - // using the Ethereum address they have submitted. Like ValsetResponse this - // message can in theory be submitted by anyone, but only the current validator - // sets submissions carry any weight. - - // ------------- - msg := &types.MsgSetOrchestratorAddresses{ - Sender: s.AccFromAddress().String(), - EthAddress: ethFrom.Hex(), - Orchestrator: orchestratorAddr.String(), - } - - res, err := s.broadcastClient.SyncBroadcastMsg(msg) - fmt.Println("Response of set eth address", "res", res) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "broadcasting MsgSetOrchestratorAddresses failed") - return err - } - - return nil -} - -func (s *peggyBroadcastClient) SendValsetConfirm( - ctx context.Context, - ethFrom ethcmn.Address, - peggyID ethcmn.Hash, - valset *types.Valset, -) error { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - confirmHash := peggy.EncodeValsetConfirm(peggyID, valset) - signature, err := s.ethPersonalSignFn(ethFrom, confirmHash.Bytes()) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.New("failed to sign validator address") - return err - } - // MsgValsetConfirm - // this is the message sent by the validators when they wish to submit their - // signatures over the validator set at a given block height. A validator must - // first call MsgSetEthAddress to set their Ethereum address to be used for - // signing. Then someone (anyone) must make a ValsetRequest the request is - // essentially a messaging mechanism to determine which block all validators - // should submit signatures over. Finally validators sign the validator set, - // powers, and Ethereum addresses of the entire validator set at the height of a - // ValsetRequest and submit that signature with this message. - // - // If a sufficient number of validators (66% of voting power) (A) have set - // Ethereum addresses and (B) submit ValsetConfirm messages with their - // signatures it is then possible for anyone to view these signatures in the - // chain store and submit them to Ethereum to update the validator set - // ------------- - msg := &types.MsgValsetConfirm{ - Orchestrator: s.AccFromAddress().String(), - EthAddress: ethFrom.Hex(), - Nonce: valset.Nonce, - Signature: ethcmn.Bytes2Hex(signature), - } - if err = s.broadcastClient.QueueBroadcastMsg(msg); err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "broadcasting MsgValsetConfirm failed") - return err - } - - return nil -} - -func (s *peggyBroadcastClient) SendBatchConfirm( - ctx context.Context, - ethFrom ethcmn.Address, - peggyID ethcmn.Hash, - batch *types.OutgoingTxBatch, -) error { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - confirmHash := peggy.EncodeTxBatchConfirm(peggyID, batch) - signature, err := s.ethPersonalSignFn(ethFrom, confirmHash.Bytes()) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.New("failed to sign validator address") - return err - } - - // MsgConfirmBatch - // When validators observe a MsgRequestBatch they form a batch by ordering - // transactions currently in the txqueue in order of highest to lowest fee, - // cutting off when the batch either reaches a hardcoded maximum size (to be - // decided, probably around 100) or when transactions stop being profitable - // (TODO determine this without nondeterminism) This message includes the batch - // as well as an Ethereum signature over this batch by the validator - // ------------- - msg := &types.MsgConfirmBatch{ - Orchestrator: s.AccFromAddress().String(), - Nonce: batch.BatchNonce, - Signature: ethcmn.Bytes2Hex(signature), - EthSigner: ethFrom.Hex(), - TokenContract: batch.TokenContract, - } - if err = s.broadcastClient.QueueBroadcastMsg(msg); err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "broadcasting MsgConfirmBatch failed") - return err - } - - return nil -} - -func (s *peggyBroadcastClient) sendOldDepositClaims( - ctx context.Context, - oldDeposit *wrappers.PeggySendToCosmosEvent, -) error { - // EthereumBridgeDepositClaim - // When more than 66% of the active validator set has - // claimed to have seen the deposit enter the ethereum blockchain coins are - // issued to the Cosmos address in question - // ------------- - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - log.WithFields(log.Fields{ - "sender": oldDeposit.Sender.Hex(), - "destination": sdk.AccAddress(oldDeposit.Destination[12:32]).String(), - "amount": oldDeposit.Amount.String(), - "event_nonce": oldDeposit.EventNonce.String(), - }).Debugln("observed SendToCosmosEvent") - - msg := &types.MsgDepositClaim{ - EventNonce: oldDeposit.EventNonce.Uint64(), - BlockHeight: oldDeposit.Raw.BlockNumber, - TokenContract: oldDeposit.TokenContract.Hex(), - Amount: sdk.NewIntFromBigInt(oldDeposit.Amount), - EthereumSender: oldDeposit.Sender.Hex(), - CosmosReceiver: sdk.AccAddress(oldDeposit.Destination[12:32]).String(), - Orchestrator: s.broadcastClient.FromAddress().String(), - Data: "", - } - - if txResponse, err := s.broadcastClient.SyncBroadcastMsg(msg); err != nil { - metrics.ReportFuncError(s.svcTags) - log.WithError(err).Errorln("broadcasting MsgDepositClaim failed") - return err - } else { - log.WithFields(log.Fields{ - "event_nonce": oldDeposit.EventNonce.String(), - "tx_hash": txResponse.TxResponse.TxHash, - }).Debugln("Oracle sent MsgDepositClaim") - } - - return nil -} - -func (s *peggyBroadcastClient) sendDepositClaims( - ctx context.Context, - deposit *wrappers.PeggySendToInjectiveEvent, -) error { - // EthereumBridgeDepositClaim - // When more than 66% of the active validator set has - // claimed to have seen the deposit enter the ethereum blockchain coins are - // issued to the Cosmos address in question - // ------------- - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - log.WithFields(log.Fields{ - "sender": deposit.Sender.Hex(), - "destination": sdk.AccAddress(deposit.Destination[12:32]).String(), - "amount": deposit.Amount.String(), - "event_nonce": deposit.EventNonce.String(), - "data": deposit.Data, - }).Debugln("observed SendToInjectiveEvent") - - msg := &types.MsgDepositClaim{ - EventNonce: deposit.EventNonce.Uint64(), - BlockHeight: deposit.Raw.BlockNumber, - TokenContract: deposit.TokenContract.Hex(), - Amount: sdk.NewIntFromBigInt(deposit.Amount), - EthereumSender: deposit.Sender.Hex(), - CosmosReceiver: sdk.AccAddress(deposit.Destination[12:32]).String(), - Orchestrator: s.broadcastClient.FromAddress().String(), - Data: deposit.Data, - } - - if txResponse, err := s.broadcastClient.SyncBroadcastMsg(msg); err != nil { - metrics.ReportFuncError(s.svcTags) - log.WithError(err).Errorln("broadcasting MsgDepositClaim failed") - return err - } else { - log.WithFields(log.Fields{ - "event_nonce": deposit.EventNonce.String(), - "tx_hash": txResponse.TxResponse.TxHash, - }).Debugln("Oracle sent MsgDepositClaim") - } - - return nil -} - -func (s *peggyBroadcastClient) sendWithdrawClaims( - ctx context.Context, - withdraw *wrappers.PeggyTransactionBatchExecutedEvent, -) error { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - log.WithFields(log.Fields{ - "nonce": withdraw.BatchNonce.String(), - "token_contract": withdraw.Token.Hex(), - "event_nonce": withdraw.EventNonce.String(), - }).Debugln("observed TransactionBatchExecutedEvent") - - // WithdrawClaim claims that a batch of withdrawal - // operations on the bridge contract was executed. - msg := &types.MsgWithdrawClaim{ - EventNonce: withdraw.EventNonce.Uint64(), - BatchNonce: withdraw.BatchNonce.Uint64(), - BlockHeight: withdraw.Raw.BlockNumber, - TokenContract: withdraw.Token.Hex(), - Orchestrator: s.AccFromAddress().String(), - } - - if txResponse, err := s.broadcastClient.SyncBroadcastMsg(msg); err != nil { - metrics.ReportFuncError(s.svcTags) - log.WithError(err).Errorln("broadcasting MsgWithdrawClaim failed") - return err - } else { - log.WithFields(log.Fields{ - "event_nonce": withdraw.EventNonce.String(), - "tx_hash": txResponse.TxResponse.TxHash, - }).Debugln("Oracle sent MsgWithdrawClaim") - } - - return nil -} - -func (s *peggyBroadcastClient) sendValsetUpdateClaims( - ctx context.Context, - valsetUpdate *wrappers.PeggyValsetUpdatedEvent, -) error { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - log.WithFields(log.Fields{ - "event_nonce": valsetUpdate.EventNonce.Uint64(), - "valset_nonce": valsetUpdate.NewValsetNonce.Uint64(), - "validators": valsetUpdate.Validators, - "powers": valsetUpdate.Powers, - "reward_amount": valsetUpdate.RewardAmount, - "reward_token": valsetUpdate.RewardToken.Hex(), - }).Debugln("observed ValsetUpdatedEvent") - - members := make([]*types.BridgeValidator, len(valsetUpdate.Validators)) - for i, val := range valsetUpdate.Validators { - members[i] = &types.BridgeValidator{ - EthereumAddress: val.Hex(), - Power: valsetUpdate.Powers[i].Uint64(), - } - } - - msg := &types.MsgValsetUpdatedClaim{ - EventNonce: valsetUpdate.EventNonce.Uint64(), - ValsetNonce: valsetUpdate.NewValsetNonce.Uint64(), - BlockHeight: valsetUpdate.Raw.BlockNumber, - RewardAmount: sdk.NewIntFromBigInt(valsetUpdate.RewardAmount), - RewardToken: valsetUpdate.RewardToken.Hex(), - Members: members, - Orchestrator: s.AccFromAddress().String(), - } - - if txResponse, err := s.broadcastClient.SyncBroadcastMsg(msg); err != nil { - metrics.ReportFuncError(s.svcTags) - log.WithError(err).Errorln("broadcasting MsgValsetUpdatedClaim failed") - return err - } else { - log.WithFields(log.Fields{ - "event_nonce": valsetUpdate.EventNonce.String(), - "tx_hash": txResponse.TxResponse.TxHash, - }).Debugln("Oracle sent MsgValsetUpdatedClaim") - } - - return nil -} - -func (s *peggyBroadcastClient) sendErc20DeployedClaims( - ctx context.Context, - erc20Deployed *wrappers.PeggyERC20DeployedEvent, -) error { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - log.WithFields(log.Fields{ - "event_nonce": erc20Deployed.EventNonce.Uint64(), - "cosmos_denom": erc20Deployed.CosmosDenom, - "token_contract": erc20Deployed.TokenContract.Hex(), - "name": erc20Deployed.Name, - "symbol": erc20Deployed.Symbol, - "decimals": erc20Deployed.Decimals, - }).Debugln("observed ERC20DeployedEvent") - - msg := &types.MsgERC20DeployedClaim{ - EventNonce: erc20Deployed.EventNonce.Uint64(), - BlockHeight: erc20Deployed.Raw.BlockNumber, - CosmosDenom: erc20Deployed.CosmosDenom, - TokenContract: erc20Deployed.TokenContract.Hex(), - Name: erc20Deployed.Name, - Symbol: erc20Deployed.Symbol, - Decimals: uint64(erc20Deployed.Decimals), - Orchestrator: s.AccFromAddress().String(), - } - - if txResponse, err := s.broadcastClient.SyncBroadcastMsg(msg); err != nil { - metrics.ReportFuncError(s.svcTags) - log.WithError(err).Errorln("broadcasting MsgERC20DeployedClaim failed") - return err - } else { - log.WithFields(log.Fields{ - "event_nonce": erc20Deployed.EventNonce.String(), - "tx_hash": txResponse.TxResponse.TxHash, - }).Debugln("Oracle sent MsgERC20DeployedClaim") - } - - return nil -} - -func (s *peggyBroadcastClient) SendEthereumClaims( - ctx context.Context, - lastClaimEvent uint64, - oldDeposits []*wrappers.PeggySendToCosmosEvent, - deposits []*wrappers.PeggySendToInjectiveEvent, - withdraws []*wrappers.PeggyTransactionBatchExecutedEvent, - erc20Deployed []*wrappers.PeggyERC20DeployedEvent, - valsetUpdates []*wrappers.PeggyValsetUpdatedEvent, -) error { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - totalClaimEvents := len(oldDeposits) + len(deposits) + len(withdraws) + len(erc20Deployed) + len(valsetUpdates) - var count, h, i, j, k, l int - - // Individual arrays (oldDeposits, deposits, withdraws, valsetUpdates) are sorted. - // Broadcast claim events sequentially starting with eventNonce = lastClaimEvent + 1. - for count < totalClaimEvents { - if h < len(oldDeposits) && oldDeposits[h].EventNonce.Uint64() == lastClaimEvent+1 { - // send old deposit - if err := s.sendOldDepositClaims(ctx, oldDeposits[h]); err != nil { - metrics.ReportFuncError(s.svcTags) - log.WithError(err).Errorln("broadcasting MsgDepositClaim failed") - return err - } - h++ - } - if i < len(deposits) && deposits[i].EventNonce.Uint64() == lastClaimEvent+1 { - // send deposit - if err := s.sendDepositClaims(ctx, deposits[i]); err != nil { - metrics.ReportFuncError(s.svcTags) - log.WithError(err).Errorln("broadcasting MsgDepositClaim failed") - return err - } - i++ - } else if j < len(withdraws) && withdraws[j].EventNonce.Uint64() == lastClaimEvent+1 { - // send withdraw claim - if err := s.sendWithdrawClaims(ctx, withdraws[j]); err != nil { - metrics.ReportFuncError(s.svcTags) - log.WithError(err).Errorln("broadcasting MsgWithdrawClaim failed") - return err - } - j++ - } else if k < len(valsetUpdates) && valsetUpdates[k].EventNonce.Uint64() == lastClaimEvent+1 { - // send valset update claim - if err := s.sendValsetUpdateClaims(ctx, valsetUpdates[k]); err != nil { - metrics.ReportFuncError(s.svcTags) - log.WithError(err).Errorln("broadcasting MsgValsetUpdateClaim failed") - return err - } - k++ - } else if l < len(erc20Deployed) && erc20Deployed[l].EventNonce.Uint64() == lastClaimEvent+1 { - // send erc20 deployed claim - if err := s.sendErc20DeployedClaims(ctx, erc20Deployed[l]); err != nil { - metrics.ReportFuncError(s.svcTags) - log.WithError(err).Errorln("broadcasting MsgERC20DeployedClaim failed") - return err - } - l++ - } - count = count + 1 - lastClaimEvent = lastClaimEvent + 1 - - // Considering blockTime=1s on Injective chain, Adding Sleep to make sure new event is - // sent only after previous event is executed successfully. - // Otherwise it will through `non contiguous event nonce` failing CheckTx. - time.Sleep(1200 * time.Millisecond) - } - return nil -} - -func (s *peggyBroadcastClient) SendToEth( - ctx context.Context, - destination ethcmn.Address, - amount, fee sdk.Coin, -) error { - // MsgSendToEth - // This is the message that a user calls when they want to bridge an asset - // it will later be removed when it is included in a batch and successfully - // submitted tokens are removed from the users balance immediately - // ------------- - // AMOUNT: - // the coin to send across the bridge, note the restriction that this is a - // single coin not a set of coins that is normal in other Cosmos messages - // FEE: - // the fee paid for the bridge, distinct from the fee paid to the chain to - // actually send this message in the first place. So a successful send has - // two layers of fees for the user - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - msg := &types.MsgSendToEth{ - Sender: s.AccFromAddress().String(), - EthDest: destination.Hex(), - Amount: amount, - BridgeFee: fee, // TODO: use exactly that fee for transaction - } - if err := s.broadcastClient.QueueBroadcastMsg(msg); err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "broadcasting MsgSendToEth failed") - return err - } - - return nil -} - -func (s *peggyBroadcastClient) SendRequestBatch( - ctx context.Context, - denom string, -) error { - // MsgRequestBatch - // this is a message anyone can send that requests a batch of transactions to - // send across the bridge be created for whatever block height this message is - // included in. This acts as a coordination point, the handler for this message - // looks at the AddToOutgoingPool tx's in the store and generates a batch, also - // available in the store tied to this message. The validators then grab this - // batch, sign it, submit the signatures with a MsgConfirmBatch before a relayer - // can finally submit the batch - // ------------- - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - msg := &types.MsgRequestBatch{ - Denom: denom, - Orchestrator: s.AccFromAddress().String(), - } - if err := s.broadcastClient.QueueBroadcastMsg(msg); err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "broadcasting MsgRequestBatch failed") - return err - } - - return nil -} diff --git a/orchestrator/cosmos/custom_rpc.go b/orchestrator/cosmos/custom_rpc.go deleted file mode 100644 index 079a4d8e..00000000 --- a/orchestrator/cosmos/custom_rpc.go +++ /dev/null @@ -1,112 +0,0 @@ -package cosmos - -import ( - "context" - "time" - - "github.com/InjectiveLabs/sdk-go/client/common" - rpchttp "github.com/cometbft/cometbft/rpc/client/http" - "github.com/cosmos/cosmos-sdk/crypto/keyring" - "github.com/pkg/errors" - log "github.com/xlab/suplog" - - "github.com/InjectiveLabs/peggo/orchestrator/cosmos/tmclient" - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/keystore" - peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" - chainclient "github.com/InjectiveLabs/sdk-go/client/chain" -) - -type CustomRPCNetwork struct { - tmclient.TendermintClient - PeggyQueryClient - PeggyBroadcastClient -} - -func loadCustomNetworkConfig(chainID, feeDenom, cosmosGRPC, tendermintRPC string) common.Network { - cfg := common.LoadNetwork("devnet", "") - cfg.Name = "custom" - cfg.ChainId = chainID - cfg.Fee_denom = feeDenom - cfg.TmEndpoint = tendermintRPC - cfg.ChainGrpcEndpoint = cosmosGRPC - cfg.ExplorerGrpcEndpoint = "" - cfg.LcdEndpoint = "" - cfg.ExplorerGrpcEndpoint = "" - - return cfg -} - -// NewCustomRPCNetwork creates a single endpoint connection to the Injective network -func NewCustomRPCNetwork( - chainID, - validatorAddress, - injectiveGRPC, - injectiveGasPrices, - tendermintRPC string, - keyring keyring.Keyring, - personalSignerFn keystore.PersonalSignFn, -) (*CustomRPCNetwork, error) { - clientCtx, err := chainclient.NewClientContext(chainID, validatorAddress, keyring) - if err != nil { - return nil, errors.Wrapf(err, "failed to create client context for Injective chain") - } - - tmRPC, err := rpchttp.New(tendermintRPC, "/websocket") - if err != nil { - return nil, errors.Wrapf(err, "failed to connect to Tendermint RPC %s", tendermintRPC) - } - - clientCtx = clientCtx.WithNodeURI(tendermintRPC) - clientCtx = clientCtx.WithClient(tmRPC) - - netCfg := loadCustomNetworkConfig(chainID, "inj", injectiveGRPC, tendermintRPC) - daemonClient, err := chainclient.NewChainClient(clientCtx, netCfg, common.OptionGasPrices(injectiveGasPrices)) - if err != nil { - return nil, errors.Wrapf(err, "failed to connect to Injective GRPC %s", injectiveGRPC) - } - - time.Sleep(1 * time.Second) - - daemonWaitCtx, cancelWait := context.WithTimeout(context.Background(), time.Minute) - defer cancelWait() - - grpcConn := daemonClient.QueryClient() - waitForService(daemonWaitCtx, grpcConn) - peggyQuerier := peggytypes.NewQueryClient(grpcConn) - - n := &CustomRPCNetwork{ - TendermintClient: tmclient.NewRPCClient(tendermintRPC), - PeggyQueryClient: NewPeggyQueryClient(peggyQuerier), - PeggyBroadcastClient: NewPeggyBroadcastClient(peggyQuerier, daemonClient, personalSignerFn), - } - - log.WithFields(log.Fields{ - "chain_id": chainID, - "conn": "custom", - "injective": injectiveGRPC, - "tendermint": tendermintRPC, - }).Infoln("connected to Injective network") - - return n, nil -} - -func (n *CustomRPCNetwork) GetBlockCreationTime(ctx context.Context, height int64) (time.Time, error) { - block, err := n.TendermintClient.GetBlock(ctx, height) - if err != nil { - return time.Time{}, err - } - - return block.Block.Time, nil -} - -func (n *CustomRPCNetwork) LastClaimEvent(ctx context.Context) (*peggytypes.LastClaimEvent, error) { - return n.LastClaimEventByAddr(ctx, n.AccFromAddress()) -} - -func (n *CustomRPCNetwork) OldestUnsignedValsets(ctx context.Context) ([]*peggytypes.Valset, error) { - return n.PeggyQueryClient.OldestUnsignedValsets(ctx, n.AccFromAddress()) -} - -func (n *CustomRPCNetwork) OldestUnsignedTransactionBatch(ctx context.Context) (*peggytypes.OutgoingTxBatch, error) { - return n.PeggyQueryClient.OldestUnsignedTransactionBatch(ctx, n.AccFromAddress()) -} diff --git a/orchestrator/cosmos/keyring.go b/orchestrator/cosmos/keyring.go new file mode 100644 index 00000000..b022b405 --- /dev/null +++ b/orchestrator/cosmos/keyring.go @@ -0,0 +1,236 @@ +package cosmos + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "io" + "os" + "path/filepath" + "strings" + + cosmoscodec "github.com/cosmos/cosmos-sdk/codec" + cosmoscdctypes "github.com/cosmos/cosmos-sdk/codec/types" + cosmoscrypto "github.com/cosmos/cosmos-sdk/crypto" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + cosmostypes "github.com/cosmos/cosmos-sdk/types" + "github.com/pkg/errors" + + "github.com/InjectiveLabs/sdk-go/chain/codec" + "github.com/InjectiveLabs/sdk-go/chain/crypto/ethsecp256k1" + "github.com/InjectiveLabs/sdk-go/chain/crypto/hd" +) + +const ( + DefaultKeyName = "validator" +) + +type KeyringConfig struct { + KeyringDir, + KeyringAppName, + KeyringBackend, + KeyFrom, + KeyPassphrase, + PrivateKey string + UseLedger bool +} + +func (cfg KeyringConfig) withPrivateKey() bool { + return len(cfg.PrivateKey) > 0 +} + +type Keyring struct { + keyring.Keyring + + Addr cosmostypes.AccAddress +} + +func NewKeyring(cfg KeyringConfig) (Keyring, error) { + if cfg.withPrivateKey() { + return newInMemoryKeyring(cfg) + } + + return newKeyringFromDir(cfg) +} + +func newInMemoryKeyring(cfg KeyringConfig) (Keyring, error) { + if cfg.UseLedger { + return Keyring{}, errors.New("cannot use both private key and Ledger") + } + + pk := cfg.PrivateKey + if strings.HasPrefix(pk, "0x") { + pk = pk[2:] + } + + pkRaw, err := hex.DecodeString(pk) + if err != nil { + return Keyring{}, errors.Wrap(err, "invalid private key") + } + + var ( + cosmosPK = ðsecp256k1.PrivKey{Key: pkRaw} + cosmosAddr = cosmostypes.AccAddress(cosmosPK.PubKey().Address()) + keyName = DefaultKeyName + ) + + if len(cfg.KeyFrom) > 0 { + from, err := cosmostypes.AccAddressFromBech32(cfg.KeyFrom) + if err != nil { + keyName = cfg.KeyFrom // use it as key name + } else if !bytes.Equal(from.Bytes(), cosmosAddr.Bytes()) { + return Keyring{}, errors.Errorf("expected account address %s but got %s from the private key", from.String(), cosmosAddr.String()) + } + } + + // Create a temporary in-mem keyring for cosmosPK. + // Allows to init Context when the key has been provided in plaintext and parsed. + tmpPhrase := randPhrase(64) + armored := cosmoscrypto.EncryptArmorPrivKey(cosmosPK, tmpPhrase, cosmosPK.Type()) + + kr := keyring.NewInMemory(Codec(), hd.EthSecp256k1Option()) + if err := kr.ImportPrivKey(keyName, armored, tmpPhrase); err != nil { + return Keyring{}, errors.Wrap(err, "failed to import private key") + } + + if err != nil { + return Keyring{}, errors.Wrap(err, "failed to initialize cosmos keyring") + } + + k := Keyring{ + Keyring: kr, + Addr: cosmosAddr, + } + + return k, nil +} + +func newKeyringFromDir(cfg KeyringConfig) (Keyring, error) { + if len(cfg.KeyFrom) == 0 { + return Keyring{}, errors.New("insufficient cosmos details provided") + } + + keyringDir := cfg.KeyringDir + if !filepath.IsAbs(keyringDir) { + dir, err := filepath.Abs(keyringDir) + if err != nil { + return Keyring{}, errors.Wrap(err, "failed to get absolute path of keyring dir") + } + + keyringDir = dir + } + + var reader io.Reader = os.Stdin + if len(cfg.KeyPassphrase) > 0 { + reader = newPassReader(cfg.KeyPassphrase) + } + + kr, err := keyring.New( + cfg.KeyringAppName, + cfg.KeyringBackend, + keyringDir, + reader, + Codec(), + hd.EthSecp256k1Option(), + ) + + if err != nil { + return Keyring{}, errors.Wrap(err, "failed to initialize cosmos keyring") + } + + var keyRecord *keyring.Record + if cosmosAddr, err := cosmostypes.AccAddressFromBech32(cfg.KeyFrom); err != nil { + r, err := kr.Key(cfg.KeyFrom) + if err != nil { + return Keyring{}, err + } + + keyRecord = r + } else { + r, err := kr.KeyByAddress(cosmosAddr) + if err != nil { + return Keyring{}, err + } + + keyRecord = r + } + + switch keyRecord.GetType() { + case keyring.TypeLocal: + // kb has a key and it's totally usable + addr, err := keyRecord.GetAddress() + if err != nil { + return Keyring{}, errors.Wrap(err, "failed to get address from key record") + } + + k := Keyring{ + Keyring: kr, + Addr: addr, + } + + return k, nil + case keyring.TypeLedger: + // the kb stores references to ledger keys, so we must explicitly + // check that. kb doesn't know how to scan HD keys - they must be added manually before + if !cfg.UseLedger { + return Keyring{}, errors.Errorf("key %s is a Ledger reference, enable Ledger option", keyRecord.Name) + } + + addr, err := keyRecord.GetAddress() + if err != nil { + return Keyring{}, errors.Wrap(err, "failed to get address from key record") + + } + + k := Keyring{ + Keyring: kr, + Addr: addr, + } + + return k, nil + default: + return Keyring{}, errors.Errorf("unsupported key type: %s", keyRecord.GetType()) + } +} + +func Codec() cosmoscodec.Codec { + iRegistry := cosmoscdctypes.NewInterfaceRegistry() + codec.RegisterInterfaces(iRegistry) + codec.RegisterLegacyAminoCodec(cosmoscodec.NewLegacyAmino()) + + return cosmoscodec.NewProtoCodec(iRegistry) +} + +func randPhrase(size int) string { + buf := make([]byte, size) + if _, err := rand.Read(buf); err != nil { + panic("rand failed") + } + + return string(buf) +} + +var _ io.Reader = &passReader{} + +type passReader struct { + pass string + buf *bytes.Buffer +} + +func newPassReader(pass string) io.Reader { + return &passReader{ + pass: pass, + buf: new(bytes.Buffer), + } +} + +func (r *passReader) Read(p []byte) (n int, err error) { + n, err = r.buf.Read(p) + if err == io.EOF || n == 0 { + r.buf.WriteString(r.pass + "\n") + + n, err = r.buf.Read(p) + } + + return +} diff --git a/orchestrator/cosmos/load_balanced.go b/orchestrator/cosmos/load_balanced.go deleted file mode 100644 index 620da606..00000000 --- a/orchestrator/cosmos/load_balanced.go +++ /dev/null @@ -1,132 +0,0 @@ -package cosmos - -import ( - "context" - "strconv" - "time" - - "github.com/cosmos/cosmos-sdk/crypto/keyring" - "github.com/pkg/errors" - log "github.com/xlab/suplog" - "google.golang.org/grpc" - "google.golang.org/grpc/connectivity" - - "github.com/InjectiveLabs/peggo/orchestrator/ethereum/keystore" - "github.com/InjectiveLabs/sdk-go/chain/peggy/types" - peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" - chainclient "github.com/InjectiveLabs/sdk-go/client/chain" - "github.com/InjectiveLabs/sdk-go/client/common" - explorerclient "github.com/InjectiveLabs/sdk-go/client/explorer" -) - -type LoadBalancedNetwork struct { - PeggyQueryClient - PeggyBroadcastClient - explorerclient.ExplorerClient -} - -// NewLoadBalancedNetwork creates a load balanced connection to the Injective network. -// The chainID argument decides which network Peggo will be connecting to: -// - injective-1 (mainnet) -// - injective-777 (devnet) -// - injective-888 (testnet) -func NewLoadBalancedNetwork( - chainID, - validatorAddress, - injectiveGasPrices string, - keyring keyring.Keyring, - personalSignerFn keystore.PersonalSignFn, -) (*LoadBalancedNetwork, error) { - clientCtx, err := chainclient.NewClientContext(chainID, validatorAddress, keyring) - if err != nil { - return nil, errors.Wrapf(err, "failed to create client context for Injective chain") - } - - var networkName string - switch chainID { - case "injective-1": - networkName = "mainnet" - case "injective-777": - networkName = "devnet" - case "injective-888": - networkName = "testnet" - default: - return nil, errors.Errorf("provided chain id %v does not belong to any known Injective network", chainID) - } - - netCfg := common.LoadNetwork(networkName, "lb") - explorer, err := explorerclient.NewExplorerClient(netCfg) - if err != nil { - return nil, err - } - - daemonClient, err := chainclient.NewChainClient(clientCtx, netCfg, common.OptionGasPrices(injectiveGasPrices)) - if err != nil { - return nil, errors.Wrapf(err, "failed to connect to Injective network: %s", networkName) - } - - 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 := &LoadBalancedNetwork{ - PeggyQueryClient: NewPeggyQueryClient(peggyQuerier), - PeggyBroadcastClient: NewPeggyBroadcastClient(peggyQuerier, daemonClient, personalSignerFn), - ExplorerClient: explorer, - } - - log.WithFields(log.Fields{"chain_id": chainID, "conn": "load_balanced"}).Infoln("connected to Injective network") - - return n, nil -} - -func (n *LoadBalancedNetwork) GetBlockCreationTime(ctx context.Context, height int64) (time.Time, error) { - block, err := n.ExplorerClient.GetBlock(ctx, strconv.FormatInt(height, 10)) - if err != nil { - return time.Time{}, err - } - - blockTime, err := time.Parse("2006-01-02 15:04:05.999 -0700 MST", block.Data.Timestamp) - if err != nil { - return time.Time{}, errors.Wrap(err, "failed to parse timestamp from block") - } - - return blockTime, nil -} - -func (n *LoadBalancedNetwork) LastClaimEvent(ctx context.Context) (*peggytypes.LastClaimEvent, error) { - return n.LastClaimEventByAddr(ctx, n.AccFromAddress()) -} - -func (n *LoadBalancedNetwork) OldestUnsignedValsets(ctx context.Context) ([]*peggytypes.Valset, error) { - return n.PeggyQueryClient.OldestUnsignedValsets(ctx, n.AccFromAddress()) -} - -func (n *LoadBalancedNetwork) OldestUnsignedTransactionBatch(ctx context.Context) (*peggytypes.OutgoingTxBatch, error) { - return n.PeggyQueryClient.OldestUnsignedTransactionBatch(ctx, n.AccFromAddress()) -} - -// 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/cosmos/net.go b/orchestrator/cosmos/net.go deleted file mode 100644 index cda0becc..00000000 --- a/orchestrator/cosmos/net.go +++ /dev/null @@ -1,31 +0,0 @@ -package cosmos - -import ( - "context" - "net" - "strings" -) - -func DialerFunc(ctx context.Context, addr string) (net.Conn, error) { - return Connect(addr) -} - -// Connect dials the given address and returns a net.Conn. The protoAddr argument should be prefixed with the protocol, -// eg. "tcp://127.0.0.1:8080" or "unix:///tmp/test.sock" -func Connect(protoAddr string) (net.Conn, error) { - proto, address := ProtocolAndAddress(protoAddr) - conn, err := net.Dial(proto, address) - return conn, err -} - -// ProtocolAndAddress splits an address into the protocol and address components. -// For instance, "tcp://127.0.0.1:8080" will be split into "tcp" and "127.0.0.1:8080". -// If the address has no protocol prefix, the default is "tcp". -func ProtocolAndAddress(listenAddr string) (string, string) { - protocol, address := "tcp", listenAddr - parts := strings.SplitN(address, "://", 2) - if len(parts) == 2 { - protocol, address = parts[0], parts[1] - } - return protocol, address -} diff --git a/orchestrator/cosmos/network.go b/orchestrator/cosmos/network.go new file mode 100644 index 00000000..43d5f712 --- /dev/null +++ b/orchestrator/cosmos/network.go @@ -0,0 +1,153 @@ +package cosmos + +import ( + "context" + "fmt" + cosmostypes "github.com/cosmos/cosmos-sdk/types" + gethcommon "github.com/ethereum/go-ethereum/common" + "time" + + comethttp "github.com/cometbft/cometbft/rpc/client/http" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + log "github.com/xlab/suplog" + "google.golang.org/grpc" + "google.golang.org/grpc/connectivity" + + peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + "github.com/InjectiveLabs/sdk-go/client/chain" + clientcommon "github.com/InjectiveLabs/sdk-go/client/common" + + "github.com/InjectiveLabs/peggo/orchestrator/cosmos/peggy" + "github.com/InjectiveLabs/peggo/orchestrator/cosmos/tendermint" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum/keystore" +) + +type NetworkConfig struct { + ChainID, + ValidatorAddress, + CosmosGRPC, + TendermintRPC, + GasPrice string +} + +type Network interface { + peggy.QueryClient + peggy.BroadcastClient + tendermint.Client +} + +func NewNetwork(k keyring.Keyring, ethSignFn keystore.PersonalSignFn, cfg NetworkConfig) (Network, error) { + clientCfg := cfg.loadClientConfig() + + clientCtx, err := chain.NewClientContext(clientCfg.ChainId, cfg.ValidatorAddress, k) + if err != nil { + return nil, err + } + + clientCtx.WithNodeURI(clientCfg.TmEndpoint) + + tmRPC, err := comethttp.New(clientCfg.TmEndpoint, "/websocket") + if err != nil { + return nil, err + } + + clientCtx = clientCtx.WithClient(tmRPC) + + chainClient, err := chain.NewChainClient(clientCtx, clientCfg, clientcommon.OptionGasPrices(cfg.GasPrice)) + if err != nil { + return nil, err + } + + time.Sleep(1 * time.Second) + + conn := awaitConnection(chainClient, 1*time.Minute) + + net := struct { + peggy.QueryClient + peggy.BroadcastClient + tendermint.Client + }{ + peggy.NewQueryClient(peggytypes.NewQueryClient(conn)), + peggy.NewBroadcastClient(chainClient, ethSignFn), + tendermint.NewRPCClient(clientCfg.TmEndpoint), + } + + return net, nil +} + +func awaitConnection(client chain.ChainClient, timeout time.Duration) *grpc.ClientConn { + ctx, cancelWait := context.WithTimeout(context.Background(), timeout) + defer cancelWait() + + grpcConn := client.QueryClient() + + for { + select { + case <-ctx.Done(): + log.Fatalln("GRPC service wait timed out") + default: + state := grpcConn.GetState() + if state != connectivity.Ready { + log.WithField("state", state.String()).Warningln("state of GRPC connection not ready") + time.Sleep(5 * time.Second) + continue + } + + return grpcConn + } + } +} + +func (cfg NetworkConfig) loadClientConfig() clientcommon.Network { + if custom := cfg.CosmosGRPC != "" && cfg.TendermintRPC != ""; custom { + log.WithFields(log.Fields{"cosmos_grpc": cfg.CosmosGRPC, "tendermint_rpc": cfg.TendermintRPC}).Debugln("using custom endpoints for Injective") + return customEndpoints(cfg) + } + + c := loadBalancedEndpoints(cfg) + log.WithFields(log.Fields{"cosmos_grpc": c.ChainGrpcEndpoint, "tendermint_rpc": c.TmEndpoint}).Debugln("using load balanced endpoints for Injective") + + return c +} + +func customEndpoints(cfg NetworkConfig) clientcommon.Network { + c := clientcommon.LoadNetwork("devnet", "") + c.Name = "custom" + c.ChainId = cfg.ChainID + c.Fee_denom = "inj" + c.TmEndpoint = cfg.TendermintRPC + c.ChainGrpcEndpoint = cfg.CosmosGRPC + c.ExplorerGrpcEndpoint = "" + c.LcdEndpoint = "" + c.ExplorerGrpcEndpoint = "" + + return c +} + +func loadBalancedEndpoints(cfg NetworkConfig) clientcommon.Network { + var networkName string + switch cfg.ChainID { + case "injective-1": + networkName = "mainnet" + case "injective-777": + networkName = "devnet" + case "injective-888": + networkName = "testnet" + default: + panic(fmt.Errorf("no provider for chain id %s", cfg.ChainID)) + } + + return clientcommon.LoadNetwork(networkName, "lb") +} + +func HasRegisteredOrchestrator(n Network, ethAddr gethcommon.Address) (cosmostypes.AccAddress, bool) { + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + validator, err := n.GetValidatorAddress(ctx, ethAddr) + if err != nil { + return nil, false + } + + return validator, true +} diff --git a/orchestrator/cosmos/peggy/broadcast.go b/orchestrator/cosmos/peggy/broadcast.go new file mode 100644 index 00000000..152224cf --- /dev/null +++ b/orchestrator/cosmos/peggy/broadcast.go @@ -0,0 +1,422 @@ +package peggy + +import ( + "context" + "fmt" + + cosmostypes "github.com/cosmos/cosmos-sdk/types" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + log "github.com/xlab/suplog" + + "github.com/InjectiveLabs/metrics" + peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + "github.com/InjectiveLabs/sdk-go/client/chain" + + "github.com/InjectiveLabs/peggo/orchestrator/ethereum/keystore" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum/peggy" + peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" +) + +type BroadcastClient interface { + UpdatePeggyOrchestratorAddresses(ctx context.Context, ethFrom gethcommon.Address, orchAddr cosmostypes.AccAddress) error + SendValsetConfirm(ctx context.Context, ethFrom gethcommon.Address, peggyID gethcommon.Hash, valset *peggytypes.Valset) error + SendBatchConfirm(ctx context.Context, ethFrom gethcommon.Address, peggyID gethcommon.Hash, batch *peggytypes.OutgoingTxBatch) error + SendRequestBatch(ctx context.Context, denom string) error + SendToEth(ctx context.Context, destination gethcommon.Address, amount, fee cosmostypes.Coin) error + SendOldDepositClaim(ctx context.Context, deposit *peggyevents.PeggySendToCosmosEvent) error + SendDepositClaim(ctx context.Context, deposit *peggyevents.PeggySendToInjectiveEvent) error + SendWithdrawalClaim(ctx context.Context, withdrawal *peggyevents.PeggyTransactionBatchExecutedEvent) error + SendValsetClaim(ctx context.Context, vs *peggyevents.PeggyValsetUpdatedEvent) error + SendERC20DeployedClaim(ctx context.Context, erc20 *peggyevents.PeggyERC20DeployedEvent) error +} + +type broadcastClient struct { + chain.ChainClient + + ethSignFn keystore.PersonalSignFn + svcTags metrics.Tags +} + +func NewBroadcastClient(client chain.ChainClient, signFn keystore.PersonalSignFn) BroadcastClient { + return broadcastClient{ + ChainClient: client, + ethSignFn: signFn, + svcTags: metrics.Tags{"svc": "peggy_broadcast"}, + } +} + +func (c broadcastClient) UpdatePeggyOrchestratorAddresses(_ context.Context, ethFrom gethcommon.Address, orchAddr cosmostypes.AccAddress) error { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + // SetOrchestratorAddresses + + // This message allows validators to delegate their voting responsibilities + // to a given key. This key is then used as an optional authentication method + // for sigining oracle claims + // This is used by the validators to set the Ethereum address that represents + // them on the Ethereum side of the bridge. They must sign their Cosmos address + // using the Ethereum address they have submitted. Like ValsetResponse this + // message can in theory be submitted by anyone, but only the current validator + // sets submissions carry any weight. + // ------------- + msg := &peggytypes.MsgSetOrchestratorAddresses{ + Sender: c.ChainClient.FromAddress().String(), + EthAddress: ethFrom.Hex(), + Orchestrator: orchAddr.String(), + } + + res, err := c.ChainClient.SyncBroadcastMsg(msg) + fmt.Println("Response of set eth address", "res", res) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return errors.Wrap(err, "broadcasting MsgSetOrchestratorAddresses failed") + } + + return nil +} + +func (c broadcastClient) SendValsetConfirm(_ context.Context, ethFrom gethcommon.Address, peggyID gethcommon.Hash, valset *peggytypes.Valset) error { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + confirmHash := peggy.EncodeValsetConfirm(peggyID, valset) + signature, err := c.ethSignFn(ethFrom, confirmHash.Bytes()) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return errors.New("failed to sign validator address") + } + + // MsgValsetConfirm + // this is the message sent by the validators when they wish to submit their + // signatures over the validator set at a given block height. A validator must + // first call MsgSetEthAddress to set their Ethereum address to be used for + // signing. Then someone (anyone) must make a ValsetRequest the request is + // essentially a messaging mechanism to determine which block all validators + // should submit signatures over. Finally validators sign the validator set, + // powers, and Ethereum addresses of the entire validator set at the height of a + // ValsetRequest and submit that signature with this message. + // + // If a sufficient number of validators (66% of voting power) (A) have set + // Ethereum addresses and (B) submit ValsetConfirm messages with their + // signatures it is then possible for anyone to view these signatures in the + // chain store and submit them to Ethereum to update the validator set + // ------------- + msg := &peggytypes.MsgValsetConfirm{ + Orchestrator: c.FromAddress().String(), + EthAddress: ethFrom.Hex(), + Nonce: valset.Nonce, + Signature: gethcommon.Bytes2Hex(signature), + } + + if err = c.ChainClient.QueueBroadcastMsg(msg); err != nil { + metrics.ReportFuncError(c.svcTags) + return errors.Wrap(err, "broadcasting MsgValsetConfirm failed") + } + + return nil +} + +func (c broadcastClient) SendBatchConfirm(_ context.Context, ethFrom gethcommon.Address, peggyID gethcommon.Hash, batch *peggytypes.OutgoingTxBatch) error { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + confirmHash := peggy.EncodeTxBatchConfirm(peggyID, batch) + signature, err := c.ethSignFn(ethFrom, confirmHash.Bytes()) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return errors.New("failed to sign validator address") + } + + // MsgConfirmBatch + // When validators observe a MsgRequestBatch they form a batch by ordering + // transactions currently in the txqueue in order of highest to lowest fee, + // cutting off when the batch either reaches a hardcoded maximum size (to be + // decided, probably around 100) or when transactions stop being profitable + // (TODO determine this without nondeterminism) This message includes the batch + // as well as an Ethereum signature over this batch by the validator + // ------------- + msg := &peggytypes.MsgConfirmBatch{ + Orchestrator: c.FromAddress().String(), + Nonce: batch.BatchNonce, + Signature: gethcommon.Bytes2Hex(signature), + EthSigner: ethFrom.Hex(), + TokenContract: batch.TokenContract, + } + + if err = c.ChainClient.QueueBroadcastMsg(msg); err != nil { + metrics.ReportFuncError(c.svcTags) + return errors.Wrap(err, "broadcasting MsgConfirmBatch failed") + } + + return nil +} + +func (c broadcastClient) SendToEth(ctx context.Context, destination gethcommon.Address, amount, fee cosmostypes.Coin) error { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + // MsgSendToEth + // This is the message that a user calls when they want to bridge an asset + // it will later be removed when it is included in a batch and successfully + // submitted tokens are removed from the users balance immediately + // ------------- + // AMOUNT: + // the coin to send across the bridge, note the restriction that this is a + // single coin not a set of coins that is normal in other Cosmos messages + // FEE: + // the fee paid for the bridge, distinct from the fee paid to the chain to + // actually send this message in the first place. So a successful send has + // two layers of fees for the user + // ------------- + msg := &peggytypes.MsgSendToEth{ + Sender: c.FromAddress().String(), + EthDest: destination.Hex(), + Amount: amount, + BridgeFee: fee, // TODO: use exactly that fee for transaction + } + + if err := c.ChainClient.QueueBroadcastMsg(msg); err != nil { + metrics.ReportFuncError(c.svcTags) + return errors.Wrap(err, "broadcasting MsgSendToEth failed") + } + + return nil +} + +func (c broadcastClient) SendRequestBatch(ctx context.Context, denom string) error { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + // MsgRequestBatch + // this is a message anyone can send that requests a batch of transactions to + // send across the bridge be created for whatever block height this message is + // included in. This acts as a coordination point, the handler for this message + // looks at the AddToOutgoingPool tx's in the store and generates a batch, also + // available in the store tied to this message. The validators then grab this + // batch, sign it, submit the signatures with a MsgConfirmBatch before a relayer + // can finally submit the batch + // ------------- + msg := &peggytypes.MsgRequestBatch{ + Denom: denom, + Orchestrator: c.FromAddress().String(), + } + if err := c.ChainClient.QueueBroadcastMsg(msg); err != nil { + metrics.ReportFuncError(c.svcTags) + return errors.Wrap(err, "broadcasting MsgRequestBatch failed") + } + + return nil +} + +func (c broadcastClient) SendOldDepositClaim(_ context.Context, deposit *peggyevents.PeggySendToCosmosEvent) error { + // EthereumBridgeDepositClaim + // When more than 66% of the active validator set has + // claimed to have seen the deposit enter the ethereum blockchain coins are + // issued to the Cosmos address in question + // ------------- + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + log.WithFields(log.Fields{ + "sender": deposit.Sender.Hex(), + "destination": cosmostypes.AccAddress(deposit.Destination[12:32]).String(), + "amount": deposit.Amount.String(), + "event_nonce": deposit.EventNonce.String(), + }).Debugln("observed SendToCosmosEvent") + + msg := &peggytypes.MsgDepositClaim{ + EventNonce: deposit.EventNonce.Uint64(), + BlockHeight: deposit.Raw.BlockNumber, + TokenContract: deposit.TokenContract.Hex(), + Amount: cosmostypes.NewIntFromBigInt(deposit.Amount), + EthereumSender: deposit.Sender.Hex(), + CosmosReceiver: cosmostypes.AccAddress(deposit.Destination[12:32]).String(), + Orchestrator: c.ChainClient.FromAddress().String(), + Data: "", + } + + resp, err := c.ChainClient.SyncBroadcastMsg(msg) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return errors.Wrap(err, "broadcasting MsgDepositClaim failed") + } + + log.WithFields(log.Fields{ + "event_height": msg.BlockHeight, + "event_nonce": msg.EventNonce, + "tx_hash": resp.TxResponse.TxHash, + }).Infoln("Oracle sent MsgDepositClaim") + + return nil +} + +func (c broadcastClient) SendDepositClaim(_ context.Context, deposit *peggyevents.PeggySendToInjectiveEvent) error { + // EthereumBridgeDepositClaim + // When more than 66% of the active validator set has + // claimed to have seen the deposit enter the ethereum blockchain coins are + // issued to the Cosmos address in question + // ------------- + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + log.WithFields(log.Fields{ + "sender": deposit.Sender.Hex(), + "destination": cosmostypes.AccAddress(deposit.Destination[12:32]).String(), + "amount": deposit.Amount.String(), + "data": deposit.Data, + "token_contract": deposit.TokenContract.Hex(), + }).Debugln("observed SendToInjectiveEvent") + + msg := &peggytypes.MsgDepositClaim{ + EventNonce: deposit.EventNonce.Uint64(), + BlockHeight: deposit.Raw.BlockNumber, + TokenContract: deposit.TokenContract.Hex(), + Amount: cosmostypes.NewIntFromBigInt(deposit.Amount), + EthereumSender: deposit.Sender.Hex(), + CosmosReceiver: cosmostypes.AccAddress(deposit.Destination[12:32]).String(), + Orchestrator: c.ChainClient.FromAddress().String(), + Data: deposit.Data, + } + + resp, err := c.ChainClient.SyncBroadcastMsg(msg) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return errors.Wrap(err, "broadcasting MsgDepositClaim failed") + } + + log.WithFields(log.Fields{ + "event_nonce": msg.EventNonce, + "event_height": msg.BlockHeight, + "tx_hash": resp.TxResponse.TxHash, + }).Infoln("EthOracle sent MsgDepositClaim") + + return nil +} + +func (c broadcastClient) SendWithdrawalClaim(_ context.Context, withdrawal *peggyevents.PeggyTransactionBatchExecutedEvent) error { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + log.WithFields(log.Fields{ + "batch_nonce": withdrawal.BatchNonce.String(), + "token_contract": withdrawal.Token.Hex(), + }).Debugln("observed TransactionBatchExecutedEvent") + + // WithdrawClaim claims that a batch of withdrawal + // operations on the bridge contract was executed. + msg := &peggytypes.MsgWithdrawClaim{ + EventNonce: withdrawal.EventNonce.Uint64(), + BatchNonce: withdrawal.BatchNonce.Uint64(), + BlockHeight: withdrawal.Raw.BlockNumber, + TokenContract: withdrawal.Token.Hex(), + Orchestrator: c.FromAddress().String(), + } + + resp, err := c.ChainClient.SyncBroadcastMsg(msg) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return errors.Wrap(err, "broadcasting MsgWithdrawClaim failed") + } + + log.WithFields(log.Fields{ + "event_height": msg.BlockHeight, + "event_nonce": msg.EventNonce, + "tx_hash": resp.TxResponse.TxHash, + }).Infoln("EthOracle sent MsgWithdrawClaim") + + return nil +} + +func (c broadcastClient) SendValsetClaim(_ context.Context, vs *peggyevents.PeggyValsetUpdatedEvent) error { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + log.WithFields(log.Fields{ + "valset_nonce": vs.NewValsetNonce.Uint64(), + "validators": vs.Validators, + "powers": vs.Powers, + "reward_amount": vs.RewardAmount, + "reward_token": vs.RewardToken.Hex(), + }).Debugln("observed ValsetUpdatedEvent") + + members := make([]*peggytypes.BridgeValidator, len(vs.Validators)) + for i, val := range vs.Validators { + members[i] = &peggytypes.BridgeValidator{ + EthereumAddress: val.Hex(), + Power: vs.Powers[i].Uint64(), + } + } + + msg := &peggytypes.MsgValsetUpdatedClaim{ + EventNonce: vs.EventNonce.Uint64(), + ValsetNonce: vs.NewValsetNonce.Uint64(), + BlockHeight: vs.Raw.BlockNumber, + RewardAmount: cosmostypes.NewIntFromBigInt(vs.RewardAmount), + RewardToken: vs.RewardToken.Hex(), + Members: members, + Orchestrator: c.FromAddress().String(), + } + + resp, err := c.ChainClient.SyncBroadcastMsg(msg) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return errors.Wrap(err, "broadcasting MsgValsetUpdatedClaim failed") + } + + log.WithFields(log.Fields{ + "event_nonce": msg.EventNonce, + "event_height": msg.BlockHeight, + "tx_hash": resp.TxResponse.TxHash, + }).Infoln("Oracle sent MsgValsetUpdatedClaim") + + return nil +} + +func (c broadcastClient) SendERC20DeployedClaim(_ context.Context, erc20 *peggyevents.PeggyERC20DeployedEvent) error { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + log.WithFields(log.Fields{ + "cosmos_denom": erc20.CosmosDenom, + "token_contract": erc20.TokenContract.Hex(), + "name": erc20.Name, + "symbol": erc20.Symbol, + "decimals": erc20.Decimals, + }).Debugln("observed ERC20DeployedEvent") + + msg := &peggytypes.MsgERC20DeployedClaim{ + EventNonce: erc20.EventNonce.Uint64(), + BlockHeight: erc20.Raw.BlockNumber, + CosmosDenom: erc20.CosmosDenom, + TokenContract: erc20.TokenContract.Hex(), + Name: erc20.Name, + Symbol: erc20.Symbol, + Decimals: uint64(erc20.Decimals), + Orchestrator: c.FromAddress().String(), + } + + resp, err := c.ChainClient.SyncBroadcastMsg(msg) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return errors.Wrap(err, "broadcasting MsgERC20DeployedClaim failed") + } + + log.WithFields(log.Fields{ + "event_nonce": msg.EventNonce, + "event_height": msg.BlockHeight, + "tx_hash": resp.TxResponse.TxHash, + }).Infoln("Oracle sent MsgERC20DeployedClaim") + + return nil +} diff --git a/orchestrator/cosmos/peggy/query.go b/orchestrator/cosmos/peggy/query.go new file mode 100644 index 00000000..22af057e --- /dev/null +++ b/orchestrator/cosmos/peggy/query.go @@ -0,0 +1,300 @@ +package peggy + +import ( + "context" + + cosmostypes "github.com/cosmos/cosmos-sdk/types" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + + "github.com/InjectiveLabs/metrics" + peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" +) + +var ErrNotFound = errors.New("not found") + +type QueryClient interface { + PeggyParams(ctx context.Context) (*peggytypes.Params, error) + LastClaimEventByAddr(ctx context.Context, validatorAccountAddress cosmostypes.AccAddress) (*peggytypes.LastClaimEvent, error) + GetValidatorAddress(ctx context.Context, addr gethcommon.Address) (cosmostypes.AccAddress, error) + + ValsetAt(ctx context.Context, nonce uint64) (*peggytypes.Valset, error) + CurrentValset(ctx context.Context) (*peggytypes.Valset, error) + OldestUnsignedValsets(ctx context.Context, valAccountAddress cosmostypes.AccAddress) ([]*peggytypes.Valset, error) + LatestValsets(ctx context.Context) ([]*peggytypes.Valset, error) + AllValsetConfirms(ctx context.Context, nonce uint64) ([]*peggytypes.MsgValsetConfirm, error) + + OldestUnsignedTransactionBatch(ctx context.Context, valAccountAddress cosmostypes.AccAddress) (*peggytypes.OutgoingTxBatch, error) + LatestTransactionBatches(ctx context.Context) ([]*peggytypes.OutgoingTxBatch, error) + UnbatchedTokensWithFees(ctx context.Context) ([]*peggytypes.BatchFees, error) + TransactionBatchSignatures(ctx context.Context, nonce uint64, tokenContract gethcommon.Address) ([]*peggytypes.MsgConfirmBatch, error) +} + +type queryClient struct { + peggytypes.QueryClient + + svcTags metrics.Tags +} + +func NewQueryClient(client peggytypes.QueryClient) QueryClient { + return queryClient{ + QueryClient: client, + svcTags: metrics.Tags{"svc": "peggy_query"}, + } +} + +func (c queryClient) ValsetAt(ctx context.Context, nonce uint64) (*peggytypes.Valset, error) { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + req := &peggytypes.QueryValsetRequestRequest{Nonce: nonce} + + resp, err := c.QueryClient.ValsetRequest(ctx, req) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return nil, errors.Wrap(err, "failed to query ValsetRequest from client") + } + + if resp == nil { + metrics.ReportFuncError(c.svcTags) + return nil, ErrNotFound + } + + return resp.Valset, nil +} + +func (c queryClient) CurrentValset(ctx context.Context) (*peggytypes.Valset, error) { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + resp, err := c.QueryClient.CurrentValset(ctx, &peggytypes.QueryCurrentValsetRequest{}) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return nil, errors.Wrap(err, "failed to query CurrentValset from client") + } + + if resp == nil { + metrics.ReportFuncError(c.svcTags) + return nil, ErrNotFound + } + + return resp.Valset, nil +} + +func (c queryClient) OldestUnsignedValsets(ctx context.Context, valAccountAddress cosmostypes.AccAddress) ([]*peggytypes.Valset, error) { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + req := &peggytypes.QueryLastPendingValsetRequestByAddrRequest{ + Address: valAccountAddress.String(), + } + + resp, err := c.QueryClient.LastPendingValsetRequestByAddr(ctx, req) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return nil, errors.Wrap(err, "failed to query LastPendingValsetRequestByAddr from client") + } + + if resp == nil { + metrics.ReportFuncError(c.svcTags) + return nil, ErrNotFound + } + + return resp.Valsets, nil +} + +func (c queryClient) LatestValsets(ctx context.Context) ([]*peggytypes.Valset, error) { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + resp, err := c.QueryClient.LastValsetRequests(ctx, &peggytypes.QueryLastValsetRequestsRequest{}) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return nil, errors.Wrap(err, "failed to query LastValsetRequests from daemon") + } + + if resp == nil { + metrics.ReportFuncError(c.svcTags) + return nil, ErrNotFound + } + + return resp.Valsets, nil +} + +func (c queryClient) AllValsetConfirms(ctx context.Context, nonce uint64) ([]*peggytypes.MsgValsetConfirm, error) { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + resp, err := c.QueryClient.ValsetConfirmsByNonce(ctx, &peggytypes.QueryValsetConfirmsByNonceRequest{Nonce: nonce}) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return nil, errors.Wrap(err, "failed to query ValsetConfirmsByNonce from daemon") + } + + if resp == nil { + metrics.ReportFuncError(c.svcTags) + return nil, ErrNotFound + } + + return resp.Confirms, nil +} + +func (c queryClient) OldestUnsignedTransactionBatch(ctx context.Context, valAccountAddress cosmostypes.AccAddress) (*peggytypes.OutgoingTxBatch, error) { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + req := &peggytypes.QueryLastPendingBatchRequestByAddrRequest{ + Address: valAccountAddress.String(), + } + + resp, err := c.QueryClient.LastPendingBatchRequestByAddr(ctx, req) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return nil, errors.Wrap(err, "failed to query LastPendingBatchRequestByAddr from daemon") + } + + if resp == nil { + metrics.ReportFuncError(c.svcTags) + return nil, ErrNotFound + } + + return resp.Batch, nil +} + +func (c queryClient) LatestTransactionBatches(ctx context.Context) ([]*peggytypes.OutgoingTxBatch, error) { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + resp, err := c.QueryClient.OutgoingTxBatches(ctx, &peggytypes.QueryOutgoingTxBatchesRequest{}) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return nil, errors.Wrap(err, "failed to query OutgoingTxBatches from daemon") + } + + if resp == nil { + metrics.ReportFuncError(c.svcTags) + return nil, ErrNotFound + } + + return resp.Batches, nil +} + +func (c queryClient) UnbatchedTokensWithFees(ctx context.Context) ([]*peggytypes.BatchFees, error) { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + resp, err := c.QueryClient.BatchFees(ctx, &peggytypes.QueryBatchFeeRequest{}) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return nil, errors.Wrap(err, "failed to query BatchFees from daemon") + } + + if resp == nil { + metrics.ReportFuncError(c.svcTags) + return nil, ErrNotFound + } + + return resp.BatchFees, nil +} + +func (c queryClient) TransactionBatchSignatures(ctx context.Context, nonce uint64, tokenContract gethcommon.Address) ([]*peggytypes.MsgConfirmBatch, error) { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + req := &peggytypes.QueryBatchConfirmsRequest{ + Nonce: nonce, + ContractAddress: tokenContract.String(), + } + + resp, err := c.QueryClient.BatchConfirms(ctx, req) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return nil, errors.Wrap(err, "failed to query BatchConfirms from daemon") + } + + if resp == nil { + metrics.ReportFuncError(c.svcTags) + return nil, ErrNotFound + } + + return resp.Confirms, nil +} + +func (c queryClient) LastClaimEventByAddr(ctx context.Context, validatorAccountAddress cosmostypes.AccAddress) (*peggytypes.LastClaimEvent, error) { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + req := &peggytypes.QueryLastEventByAddrRequest{ + Address: validatorAccountAddress.String(), + } + + resp, err := c.QueryClient.LastEventByAddr(ctx, req) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return nil, errors.Wrap(err, "failed to query LastEventByAddr from daemon") + } + + if resp == nil { + metrics.ReportFuncError(c.svcTags) + return nil, ErrNotFound + } + + return resp.LastClaimEvent, nil +} + +func (c queryClient) PeggyParams(ctx context.Context) (*peggytypes.Params, error) { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + resp, err := c.QueryClient.Params(ctx, &peggytypes.QueryParamsRequest{}) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return nil, errors.Wrap(err, "failed to query PeggyParams from daemon") + } + + if resp == nil { + metrics.ReportFuncError(c.svcTags) + return nil, ErrNotFound + } + + return &resp.Params, nil +} + +func (c queryClient) GetValidatorAddress(ctx context.Context, addr gethcommon.Address) (cosmostypes.AccAddress, error) { + metrics.ReportFuncCall(c.svcTags) + doneFn := metrics.ReportFuncTiming(c.svcTags) + defer doneFn() + + req := &peggytypes.QueryDelegateKeysByEthAddress{ + EthAddress: addr.Hex(), + } + + resp, err := c.QueryClient.GetDelegateKeyByEth(ctx, req) + if err != nil { + metrics.ReportFuncError(c.svcTags) + return nil, errors.Wrap(err, "failed to query GetDelegateKeyByEth from client") + } + + if resp == nil { + metrics.ReportFuncError(c.svcTags) + return nil, ErrNotFound + } + + valAddr, err := cosmostypes.AccAddressFromBech32(resp.ValidatorAddress) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode validator address: %v", resp.ValidatorAddress) + } + + return valAddr, nil +} diff --git a/orchestrator/cosmos/query.go b/orchestrator/cosmos/query.go deleted file mode 100644 index f37b6879..00000000 --- a/orchestrator/cosmos/query.go +++ /dev/null @@ -1,281 +0,0 @@ -package cosmos - -import ( - "context" - - sdk "github.com/cosmos/cosmos-sdk/types" - ethcmn "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" - - "github.com/InjectiveLabs/metrics" - "github.com/InjectiveLabs/sdk-go/chain/peggy/types" -) - -type PeggyQueryClient interface { - ValsetAt(ctx context.Context, nonce uint64) (*types.Valset, error) - CurrentValset(ctx context.Context) (*types.Valset, error) - OldestUnsignedValsets(ctx context.Context, valAccountAddress sdk.AccAddress) ([]*types.Valset, error) - LatestValsets(ctx context.Context) ([]*types.Valset, error) - AllValsetConfirms(ctx context.Context, nonce uint64) ([]*types.MsgValsetConfirm, error) - OldestUnsignedTransactionBatch(ctx context.Context, valAccountAddress sdk.AccAddress) (*types.OutgoingTxBatch, error) - LatestTransactionBatches(ctx context.Context) ([]*types.OutgoingTxBatch, error) - UnbatchedTokensWithFees(ctx context.Context) ([]*types.BatchFees, error) - - TransactionBatchSignatures(ctx context.Context, nonce uint64, tokenContract ethcmn.Address) ([]*types.MsgConfirmBatch, error) - LastClaimEventByAddr(ctx context.Context, validatorAccountAddress sdk.AccAddress) (*types.LastClaimEvent, error) - - PeggyParams(ctx context.Context) (*types.Params, error) - HasRegisteredEthAddress(ctx context.Context, addr ethcmn.Address) (bool, error) -} - -func NewPeggyQueryClient(client types.QueryClient) PeggyQueryClient { - return &peggyQueryClient{ - daemonQueryClient: client, - svcTags: metrics.Tags{ - "svc": "peggy_query", - }, - } -} - -type peggyQueryClient struct { - daemonQueryClient types.QueryClient - svcTags metrics.Tags -} - -var ErrNotFound = errors.New("not found") - -func (s *peggyQueryClient) HasRegisteredEthAddress(ctx context.Context, addr ethcmn.Address) (bool, error) { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - req := &types.QueryDelegateKeysByEthAddress{EthAddress: addr.Hex()} - resp, err := s.daemonQueryClient.GetDelegateKeyByEth(ctx, req) - - if errors.Is(err, types.ErrInvalid) { - // no record found - return false, nil - } - - if err != nil { - metrics.ReportFuncError(s.svcTags) - return false, errors.Wrap(err, "failed to query GetDelegateKeyByEth from daemon") - } - - if resp == nil { - return false, nil - } - - return true, nil -} - -func (s *peggyQueryClient) ValsetAt(ctx context.Context, nonce uint64) (*types.Valset, error) { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - daemonResp, err := s.daemonQueryClient.ValsetRequest(ctx, &types.QueryValsetRequestRequest{ - Nonce: nonce, - }) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to query ValsetRequest from daemon") - return nil, err - } else if daemonResp == nil { - metrics.ReportFuncError(s.svcTags) - return nil, ErrNotFound - } - - return daemonResp.Valset, nil -} - -func (s *peggyQueryClient) CurrentValset(ctx context.Context) (*types.Valset, error) { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - daemonResp, err := s.daemonQueryClient.CurrentValset(ctx, &types.QueryCurrentValsetRequest{}) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to query CurrentValset from daemon") - return nil, err - } else if daemonResp == nil { - metrics.ReportFuncError(s.svcTags) - return nil, ErrNotFound - } - - return daemonResp.Valset, nil -} - -func (s *peggyQueryClient) OldestUnsignedValsets(ctx context.Context, valAccountAddress sdk.AccAddress) ([]*types.Valset, error) { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - daemonResp, err := s.daemonQueryClient.LastPendingValsetRequestByAddr(ctx, &types.QueryLastPendingValsetRequestByAddrRequest{ - Address: valAccountAddress.String(), - }) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to query LastPendingValsetRequestByAddr from daemon") - return nil, err - } else if daemonResp == nil { - metrics.ReportFuncError(s.svcTags) - return nil, ErrNotFound - } - - return daemonResp.Valsets, nil -} - -func (s *peggyQueryClient) LatestValsets(ctx context.Context) ([]*types.Valset, error) { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - daemonResp, err := s.daemonQueryClient.LastValsetRequests(ctx, &types.QueryLastValsetRequestsRequest{}) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to query LastValsetRequests from daemon") - return nil, err - } else if daemonResp == nil { - metrics.ReportFuncError(s.svcTags) - return nil, ErrNotFound - } - - return daemonResp.Valsets, nil -} - -func (s *peggyQueryClient) AllValsetConfirms(ctx context.Context, nonce uint64) ([]*types.MsgValsetConfirm, error) { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - daemonResp, err := s.daemonQueryClient.ValsetConfirmsByNonce(ctx, &types.QueryValsetConfirmsByNonceRequest{ - Nonce: nonce, - }) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to query ValsetConfirmsByNonce from daemon") - return nil, err - } else if daemonResp == nil { - metrics.ReportFuncError(s.svcTags) - return nil, ErrNotFound - } - - return daemonResp.Confirms, nil -} - -func (s *peggyQueryClient) OldestUnsignedTransactionBatch(ctx context.Context, valAccountAddress sdk.AccAddress) (*types.OutgoingTxBatch, error) { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - daemonResp, err := s.daemonQueryClient.LastPendingBatchRequestByAddr(ctx, &types.QueryLastPendingBatchRequestByAddrRequest{ - Address: valAccountAddress.String(), - }) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to query LastPendingBatchRequestByAddr from daemon") - return nil, err - } else if daemonResp == nil { - metrics.ReportFuncError(s.svcTags) - return nil, ErrNotFound - } - - return daemonResp.Batch, nil -} - -func (s *peggyQueryClient) LatestTransactionBatches(ctx context.Context) ([]*types.OutgoingTxBatch, error) { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - daemonResp, err := s.daemonQueryClient.OutgoingTxBatches(ctx, &types.QueryOutgoingTxBatchesRequest{}) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to query OutgoingTxBatches from daemon") - return nil, err - } else if daemonResp == nil { - metrics.ReportFuncError(s.svcTags) - return nil, ErrNotFound - } - - return daemonResp.Batches, nil -} - -func (s *peggyQueryClient) UnbatchedTokensWithFees(ctx context.Context) ([]*types.BatchFees, error) { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - daemonResp, err := s.daemonQueryClient.BatchFees(ctx, &types.QueryBatchFeeRequest{}) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to query BatchFees from daemon") - return nil, err - } else if daemonResp == nil { - metrics.ReportFuncError(s.svcTags) - return nil, ErrNotFound - } - - return daemonResp.BatchFees, nil -} - -func (s *peggyQueryClient) TransactionBatchSignatures(ctx context.Context, nonce uint64, tokenContract ethcmn.Address) ([]*types.MsgConfirmBatch, error) { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - daemonResp, err := s.daemonQueryClient.BatchConfirms(ctx, &types.QueryBatchConfirmsRequest{ - Nonce: nonce, - ContractAddress: tokenContract.String(), - }) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to query BatchConfirms from daemon") - return nil, err - } else if daemonResp == nil { - metrics.ReportFuncError(s.svcTags) - return nil, ErrNotFound - } - - return daemonResp.Confirms, nil -} - -func (s *peggyQueryClient) LastClaimEventByAddr(ctx context.Context, validatorAccountAddress sdk.AccAddress) (*types.LastClaimEvent, error) { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - daemonResp, err := s.daemonQueryClient.LastEventByAddr(ctx, &types.QueryLastEventByAddrRequest{ - Address: validatorAccountAddress.String(), - }) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to query LastEventByAddr from daemon") - return nil, err - } else if daemonResp == nil { - metrics.ReportFuncError(s.svcTags) - return nil, ErrNotFound - } - - return daemonResp.LastClaimEvent, nil -} - -func (s *peggyQueryClient) PeggyParams(ctx context.Context) (*types.Params, error) { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - daemonResp, err := s.daemonQueryClient.Params(ctx, &types.QueryParamsRequest{}) - if err != nil { - metrics.ReportFuncError(s.svcTags) - err = errors.Wrap(err, "failed to query PeggyParams from daemon") - return nil, err - } else if daemonResp == nil { - metrics.ReportFuncError(s.svcTags) - return nil, ErrNotFound - } - - return &daemonResp.Params, nil -} diff --git a/orchestrator/cosmos/tmclient/tmclient.go b/orchestrator/cosmos/tendermint/client.go similarity index 74% rename from orchestrator/cosmos/tmclient/tmclient.go rename to orchestrator/cosmos/tendermint/client.go index aaf753d1..80804732 100644 --- a/orchestrator/cosmos/tmclient/tmclient.go +++ b/orchestrator/cosmos/tendermint/client.go @@ -1,24 +1,21 @@ -package tmclient +package tendermint import ( "context" "strings" "github.com/InjectiveLabs/metrics" - - log "github.com/xlab/suplog" - rpcclient "github.com/cometbft/cometbft/rpc/client" rpchttp "github.com/cometbft/cometbft/rpc/client/http" - ctypes "github.com/cometbft/cometbft/rpc/core/types" - tmctypes "github.com/cometbft/cometbft/rpc/core/types" + comettypes "github.com/cometbft/cometbft/rpc/core/types" + log "github.com/xlab/suplog" ) -type TendermintClient interface { - GetBlock(ctx context.Context, height int64) (*tmctypes.ResultBlock, error) +type Client interface { + GetBlock(ctx context.Context, height int64) (*comettypes.ResultBlock, error) GetLatestBlockHeight(ctx context.Context) (int64, error) - GetTxs(ctx context.Context, block *tmctypes.ResultBlock) ([]*ctypes.ResultTx, error) - GetValidatorSet(ctx context.Context, height int64) (*tmctypes.ResultValidators, error) + GetTxs(ctx context.Context, block *comettypes.ResultBlock) ([]*comettypes.ResultTx, error) + GetValidatorSet(ctx context.Context, height int64) (*comettypes.ResultValidators, error) } type tmClient struct { @@ -26,7 +23,7 @@ type tmClient struct { svcTags metrics.Tags } -func NewRPCClient(rpcNodeAddr string) TendermintClient { +func NewRPCClient(rpcNodeAddr string) Client { rpcClient, err := rpchttp.NewWithTimeout(rpcNodeAddr, "/websocket", 10) if err != nil { log.WithError(err).Fatalln("failed to init rpcClient") @@ -35,13 +32,13 @@ func NewRPCClient(rpcNodeAddr string) TendermintClient { return &tmClient{ rpcClient: rpcClient, svcTags: metrics.Tags{ - "svc": string("tmclient"), + "svc": string("tendermint"), }, } } // GetBlock queries for a block by height. An error is returned if the query fails. -func (c *tmClient) GetBlock(ctx context.Context, height int64) (*tmctypes.ResultBlock, error) { +func (c *tmClient) GetBlock(ctx context.Context, height int64) (*comettypes.ResultBlock, error) { return c.rpcClient.Block(ctx, &height) } @@ -64,12 +61,12 @@ func (c *tmClient) GetLatestBlockHeight(ctx context.Context) (int64, error) { // GetTxs queries for all the transactions in a block height. // It uses `Tx` RPC method to query for the transaction. -func (c *tmClient) GetTxs(ctx context.Context, block *tmctypes.ResultBlock) ([]*ctypes.ResultTx, error) { +func (c *tmClient) GetTxs(ctx context.Context, block *comettypes.ResultBlock) ([]*comettypes.ResultTx, error) { metrics.ReportFuncCall(c.svcTags) doneFn := metrics.ReportFuncTiming(c.svcTags) defer doneFn() - txs := make([]*ctypes.ResultTx, 0, len(block.Block.Txs)) + txs := make([]*comettypes.ResultTx, 0, len(block.Block.Txs)) for _, tmTx := range block.Block.Txs { tx, err := c.rpcClient.Tx(ctx, tmTx.Hash(), true) if err != nil { @@ -90,7 +87,7 @@ func (c *tmClient) GetTxs(ctx context.Context, block *tmctypes.ResultBlock) ([]* // GetValidatorSet returns all the known Tendermint validators for a given block // height. An error is returned if the query fails. -func (c *tmClient) GetValidatorSet(ctx context.Context, height int64) (*tmctypes.ResultValidators, error) { +func (c *tmClient) GetValidatorSet(ctx context.Context, height int64) (*comettypes.ResultValidators, error) { metrics.ReportFuncCall(c.svcTags) doneFn := metrics.ReportFuncTiming(c.svcTags) defer doneFn() diff --git a/orchestrator/eth_oracle.go b/orchestrator/eth_oracle.go new file mode 100644 index 00000000..0e30e636 --- /dev/null +++ b/orchestrator/eth_oracle.go @@ -0,0 +1,384 @@ +package orchestrator + +import ( + "context" + "sort" + "time" + + "github.com/pkg/errors" + log "github.com/xlab/suplog" + + "github.com/InjectiveLabs/peggo/orchestrator/cosmos" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum" + "github.com/InjectiveLabs/peggo/orchestrator/loops" + peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" + peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" +) + +const ( + // Minimum number of confirmations for an Ethereum block to be considered valid + ethBlockConfirmationDelay uint64 = 12 + + // Maximum block range for Ethereum event query. If the orchestrator has been offline for a long time, + // the oracle loop can potentially run longer than defaultLoopDur due to a surge of events. This usually happens + // when there are more than ~50 events to claim in a single run. + defaultBlocksToSearch uint64 = 2000 + + // Auto re-sync to catch up the validator's last observed event 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. + resyncInterval = 24 * time.Hour +) + +// runEthOracle 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 *Orchestrator) runEthOracle( + ctx context.Context, + inj cosmos.Network, + eth ethereum.Network, + lastObservedBlock uint64, +) error { + oracle := ethOracle{ + Orchestrator: s, + Injective: inj, + Ethereum: eth, + LastObservedEthHeight: lastObservedBlock, + LastResyncWithInjective: time.Now(), + } + + s.logger.WithField("loop_duration", defaultLoopDur.String()).Debugln("starting EthOracle...") + + return loops.RunLoop(ctx, defaultLoopDur, func() error { + return oracle.ObserveEthEvents(ctx) + }) +} + +type ethOracle struct { + *Orchestrator + Injective cosmos.Network + Ethereum ethereum.Network + LastResyncWithInjective time.Time + LastObservedEthHeight uint64 +} + +func (l *ethOracle) Logger() log.Logger { + return l.logger.WithField("loop", "EthOracle") +} + +func (l *ethOracle) ObserveEthEvents(ctx context.Context) error { + // check if validator is in the active set since claims will fail otherwise + vs, err := l.Injective.CurrentValset(ctx) + if err != nil { + return errors.Wrap(err, "failed to get current valset on Injective") + } + + bonded := false + for _, v := range vs.Members { + if l.ethAddr.Hex() == v.EthereumAddress { + bonded = true + } + } + + if !bonded { + l.Logger().WithFields(log.Fields{"latest_inj_block": vs.Height}).Infoln("validator not in active set, cannot make claims...") + return nil + } + + latestHeight, err := l.getLatestEthHeight(ctx) + if err != nil { + return err + } + + // not enough blocks on ethereum yet + if latestHeight <= ethBlockConfirmationDelay { + l.Logger().Debugln("not enough blocks on Ethereum") + return nil + } + + // ensure that latest block has minimum confirmations + latestHeight = latestHeight - ethBlockConfirmationDelay + if latestHeight <= l.LastObservedEthHeight { + l.Logger().WithFields(log.Fields{"latest": latestHeight, "observed": l.LastObservedEthHeight}).Debugln("latest Ethereum height already observed") + return nil + } + + // ensure the block range is within defaultBlocksToSearch + if latestHeight > l.LastObservedEthHeight+defaultBlocksToSearch { + latestHeight = l.LastObservedEthHeight + defaultBlocksToSearch + } + + events, err := l.getEthEvents(ctx, l.LastObservedEthHeight, latestHeight) + if err != nil { + return err + } + + lastClaim, err := l.getLastClaimEvent(ctx) + if err != nil { + return err + } + + newEvents := filterEvents(events, lastClaim.EthereumEventNonce) + sort.Slice(newEvents, func(i, j int) bool { + return newEvents[i].Nonce() < newEvents[j].Nonce() + }) + + if len(newEvents) == 0 { + l.Logger().WithFields(log.Fields{"last_claimed_event_nonce": lastClaim.EthereumEventNonce, "eth_block_start": l.LastObservedEthHeight, "eth_block_end": latestHeight}).Infoln("no new events on Ethereum") + l.LastObservedEthHeight = latestHeight + + return nil + } + + if expected, actual := lastClaim.EthereumEventNonce+1, newEvents[0].Nonce(); expected != actual { + l.Logger().WithFields(log.Fields{"expected_nonce": expected, "actual_nonce": actual, "last_claimed_event_nonce": lastClaim.EthereumEventNonce}).Debugln("orchestrator missed an Ethereum event. Resyncing event nonce with last claimed event...") + l.LastObservedEthHeight = lastClaim.EthereumEventHeight + + return nil + } + + if err := l.sendNewEventClaims(ctx, newEvents); err != nil { + return err + } + + l.Logger().WithFields(log.Fields{"claims": len(newEvents), "eth_block_start": l.LastObservedEthHeight, "eth_block_end": latestHeight}).Infoln("sent new event claims to Injective") + l.LastObservedEthHeight = latestHeight + + if time.Since(l.LastResyncWithInjective) >= resyncInterval { + if err := l.autoResync(ctx); err != nil { + return err + } + } + + return nil +} + +func (l *ethOracle) getEthEvents(ctx context.Context, startBlock, endBlock uint64) ([]event, error) { + var events []event + scanEthEventsFn := func() error { + events = nil // clear previous result in case a retry happens + + oldDepositEvents, err := l.Ethereum.GetSendToCosmosEvents(startBlock, endBlock) + if err != nil { + return errors.Wrap(err, "failed to get SendToCosmos events") + } + + depositEvents, err := l.Ethereum.GetSendToInjectiveEvents(startBlock, endBlock) + if err != nil { + return errors.Wrap(err, "failed to get SendToInjective events") + } + + withdrawalEvents, err := l.Ethereum.GetTransactionBatchExecutedEvents(startBlock, endBlock) + if err != nil { + return errors.Wrap(err, "failed to get TransactionBatchExecuted events") + } + + erc20DeploymentEvents, err := l.Ethereum.GetPeggyERC20DeployedEvents(startBlock, endBlock) + if err != nil { + return errors.Wrap(err, "failed to get ERC20Deployed events") + } + + valsetUpdateEvents, err := l.Ethereum.GetValsetUpdatedEvents(startBlock, endBlock) + if err != nil { + return errors.Wrap(err, "failed to get ValsetUpdated events") + } + + for _, e := range oldDepositEvents { + ev := oldDeposit(*e) + events = append(events, &ev) + } + + for _, e := range depositEvents { + ev := deposit(*e) + events = append(events, &ev) + } + + for _, e := range withdrawalEvents { + ev := withdrawal(*e) + events = append(events, &ev) + } + + for _, e := range valsetUpdateEvents { + ev := valsetUpdate(*e) + events = append(events, &ev) + } + + for _, e := range erc20DeploymentEvents { + ev := erc20Deployment(*e) + events = append(events, &ev) + } + + return nil + } + + if err := retryFnOnErr(ctx, l.Logger(), scanEthEventsFn); err != nil { + l.Logger().WithError(err).Errorln("got error, loop exits") + return nil, err + } + + return events, nil +} + +func (l *ethOracle) getLatestEthHeight(ctx context.Context) (uint64, error) { + latestHeight := uint64(0) + fn := func() error { + h, err := l.Ethereum.GetHeaderByNumber(ctx, nil) + if err != nil { + return errors.Wrap(err, "failed to get latest ethereum header") + } + + latestHeight = h.Number.Uint64() + return nil + } + + if err := retryFnOnErr(ctx, l.Logger(), fn); err != nil { + l.Logger().WithError(err).Errorln("got error, loop exits") + return 0, err + } + + return latestHeight, nil +} + +func (l *ethOracle) getLastClaimEvent(ctx context.Context) (*peggytypes.LastClaimEvent, error) { + var claim *peggytypes.LastClaimEvent + fn := func() error { + c, err := l.Injective.LastClaimEventByAddr(ctx, l.injAddr) + if err != nil { + return err + } + + claim = c + return nil + } + + if err := retryFnOnErr(ctx, l.Logger(), fn); err != nil { + l.Logger().WithError(err).Errorln("got error, loop exits") + return nil, err + } + + return claim, nil +} + +func (l *ethOracle) sendNewEventClaims(ctx context.Context, events []event) error { + sendEventsFn := func() error { + // in case sending one of more claims fails, we reload the latest claimed nonce to filter processed events + lastClaim, err := l.Injective.LastClaimEventByAddr(ctx, l.injAddr) + if err != nil { + return err + } + + newEvents := filterEvents(events, lastClaim.EthereumEventNonce) + if len(newEvents) == 0 { + return nil + } + + for _, event := range newEvents { + if err := l.sendEthEventClaim(ctx, event); err != nil { + return err + } + + // Considering blockTime=1s on Injective chain, adding Sleep to make sure new event is sent + // only after previous event is executed successfully. Otherwise it will through `non contiguous event nonce` failing CheckTx. + time.Sleep(1200 * time.Millisecond) + } + + return nil + } + + if err := retryFnOnErr(ctx, l.Logger(), sendEventsFn); err != nil { + l.Logger().WithError(err).Errorln("got error, loop exits") + return err + } + + return nil +} + +func (l *ethOracle) autoResync(ctx context.Context) error { + latestHeight := uint64(0) + fn := func() error { + h, err := l.getLastClaimBlockHeight(ctx, l.Injective) + if err != nil { + return err + } + + latestHeight = h + return nil + } + + if err := retryFnOnErr(ctx, l.Logger(), fn); err != nil { + l.Logger().WithError(err).Errorln("got error, loop exits") + return err + } + + l.Logger().WithFields(log.Fields{"last_resync": l.LastResyncWithInjective.String(), "last_claimed_eth_height": latestHeight}).Infoln("auto resyncing with last claimed event on Injective") + + l.LastObservedEthHeight = latestHeight + l.LastResyncWithInjective = time.Now() + + return nil +} + +func (l *ethOracle) sendEthEventClaim(ctx context.Context, ev event) error { + switch e := ev.(type) { + case *oldDeposit: + ev := peggyevents.PeggySendToCosmosEvent(*e) + return l.Injective.SendOldDepositClaim(ctx, &ev) + case *deposit: + ev := peggyevents.PeggySendToInjectiveEvent(*e) + return l.Injective.SendDepositClaim(ctx, &ev) + case *valsetUpdate: + ev := peggyevents.PeggyValsetUpdatedEvent(*e) + return l.Injective.SendValsetClaim(ctx, &ev) + case *withdrawal: + ev := peggyevents.PeggyTransactionBatchExecutedEvent(*e) + return l.Injective.SendWithdrawalClaim(ctx, &ev) + case *erc20Deployment: + ev := peggyevents.PeggyERC20DeployedEvent(*e) + return l.Injective.SendERC20DeployedClaim(ctx, &ev) + default: + panic(errors.Errorf("unknown ev type %T", e)) + } +} + +type ( + oldDeposit peggyevents.PeggySendToCosmosEvent + deposit peggyevents.PeggySendToInjectiveEvent + valsetUpdate peggyevents.PeggyValsetUpdatedEvent + withdrawal peggyevents.PeggyTransactionBatchExecutedEvent + erc20Deployment peggyevents.PeggyERC20DeployedEvent + + event interface { + Nonce() uint64 + } +) + +func filterEvents(events []event, nonce uint64) (filtered []event) { + for _, e := range events { + if e.Nonce() > nonce { + filtered = append(filtered, e) + } + } + + return +} + +func (o *oldDeposit) Nonce() uint64 { + return o.EventNonce.Uint64() +} + +func (o *deposit) Nonce() uint64 { + return o.EventNonce.Uint64() +} + +func (o *valsetUpdate) Nonce() uint64 { + return o.EventNonce.Uint64() +} + +func (o *withdrawal) Nonce() uint64 { + return o.EventNonce.Uint64() +} + +func (o *erc20Deployment) Nonce() uint64 { + return o.EventNonce.Uint64() +} diff --git a/orchestrator/ethereum.go b/orchestrator/ethereum.go deleted file mode 100644 index c35ad852..00000000 --- a/orchestrator/ethereum.go +++ /dev/null @@ -1,39 +0,0 @@ -package orchestrator - -import ( - "context" - "math/big" - - eth "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" - - peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" - peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" -) - -// EthereumNetwork is the orchestrator's reference endpoint to the Ethereum network -type EthereumNetwork interface { - FromAddress() eth.Address - HeaderByNumber(ctx context.Context, number *big.Int) (*ethtypes.Header, error) - GetPeggyID(ctx context.Context) (eth.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, - ) (*eth.Hash, error) - - GetTxBatchNonce(ctx context.Context, erc20ContractAddress eth.Address) (*big.Int, error) - SendTransactionBatch(ctx context.Context, - currentValset *peggytypes.Valset, - batch *peggytypes.OutgoingTxBatch, - confirms []*peggytypes.MsgConfirmBatch, - ) (*eth.Hash, error) -} diff --git a/orchestrator/ethereum/committer/eth_committer.go b/orchestrator/ethereum/committer/eth_committer.go index 19444bed..499bc090 100644 --- a/orchestrator/ethereum/committer/eth_committer.go +++ b/orchestrator/ethereum/committer/eth_committer.go @@ -2,10 +2,11 @@ package committer import ( "context" - "github.com/ethereum/go-ethereum" "math/big" "strings" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -13,6 +14,7 @@ import ( log "github.com/xlab/suplog" "github.com/InjectiveLabs/metrics" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum/provider" "github.com/InjectiveLabs/peggo/orchestrator/ethereum/util" ) diff --git a/orchestrator/ethereum/network.go b/orchestrator/ethereum/network.go index 2d3b72f0..6aec8945 100644 --- a/orchestrator/ethereum/network.go +++ b/orchestrator/ethereum/network.go @@ -2,13 +2,14 @@ package ethereum import ( "context" + "github.com/ethereum/go-ethereum" "math/big" "strings" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" - ethcmn "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" + gethcommon "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" "github.com/pkg/errors" log "github.com/xlab/suplog" @@ -16,33 +17,67 @@ import ( "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" + peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" ) -type Network struct { +type NetworkConfig struct { + EthNodeRPC string + GasPriceAdjustment float64 + MaxGasPrice string + PendingTxWaitDuration string + EthNodeAlchemyWS string +} + +// Network is the orchestrator's reference endpoint to the Ethereum network +type Network interface { + GetHeaderByNumber(ctx context.Context, number *big.Int) (*gethtypes.Header, error) + GetPeggyID(ctx context.Context) (gethcommon.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, + ) (*gethcommon.Hash, error) + + GetTxBatchNonce(ctx context.Context, erc20ContractAddress gethcommon.Address) (*big.Int, error) + SendTransactionBatch(ctx context.Context, + currentValset *peggytypes.Valset, + batch *peggytypes.OutgoingTxBatch, + confirms []*peggytypes.MsgConfirmBatch, + ) (*gethcommon.Hash, error) + + TokenDecimals(ctx context.Context, tokenContract gethcommon.Address) (uint8, error) +} + +type network struct { peggy.PeggyContract + + FromAddr gethcommon.Address } func NewNetwork( - ethNodeRPC string, peggyContractAddr, - fromAddr ethcmn.Address, + fromAddr gethcommon.Address, signerFn bind.SignerFn, - gasPriceAdjustment float64, - maxGasPrice string, - pendingTxWaitDuration string, - ethNodeAlchemyWS string, -) (*Network, error) { - evmRPC, err := rpc.Dial(ethNodeRPC) + cfg NetworkConfig, +) (Network, error) { + evmRPC, err := rpc.Dial(cfg.EthNodeRPC) if err != nil { - return nil, errors.Wrapf(err, "failed to connect to ethereum RPC: %s", ethNodeRPC) + return nil, errors.Wrapf(err, "failed to connect to ethereum RPC: %s", cfg.EthNodeRPC) } ethCommitter, err := committer.NewEthCommitter( fromAddr, - gasPriceAdjustment, - maxGasPrice, + cfg.GasPriceAdjustment, + cfg.MaxGasPrice, signerFn, provider.NewEVMProvider(evmRPC), ) @@ -50,7 +85,7 @@ func NewNetwork( return nil, err } - pendingTxDuration, err := time.ParseDuration(pendingTxWaitDuration) + pendingTxDuration, err := time.ParseDuration(cfg.PendingTxWaitDuration) if err != nil { return nil, err } @@ -60,36 +95,59 @@ func NewNetwork( return nil, err } - log.WithFields(log.Fields{ - "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 != "" { + if cfg.EthNodeAlchemyWS != "" { log.WithFields(log.Fields{ - "url": ethNodeAlchemyWS, + "url": cfg.EthNodeAlchemyWS, }).Infoln("subscribing to Alchemy websocket") - go peggyContract.SubscribeToPendingTxs(ethNodeAlchemyWS) + go peggyContract.SubscribeToPendingTxs(cfg.EthNodeAlchemyWS) + } + + n := &network{ + PeggyContract: peggyContract, + FromAddr: fromAddr, } - return &Network{PeggyContract: peggyContract}, nil + return n, nil } -func (n *Network) FromAddress() ethcmn.Address { - return n.PeggyContract.FromAddress() +func (n *network) TokenDecimals(ctx context.Context, tokenContract gethcommon.Address) (uint8, error) { + msg := ethereum.CallMsg{ + //From: gethcommon.Address{}, + To: &tokenContract, + Data: gethcommon.Hex2Bytes("313ce567"), // Function signature for decimals(), + } + + res, err := n.Provider().CallContract(ctx, msg, nil) + if err != nil { + return 0, err + } + + if len(res) == 0 { + return 0, errors.Errorf("no decimals found for token contract %s", tokenContract.Hex()) + } + + return uint8(big.NewInt(0).SetBytes(res).Uint64()), nil } -func (n *Network) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { +func (n *network) GetHeaderByNumber(ctx context.Context, number *big.Int) (*gethtypes.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) GetPeggyID(ctx context.Context) (gethcommon.Hash, error) { + return n.PeggyContract.GetPeggyID(ctx, n.FromAddr) +} + +func (n *network) GetValsetNonce(ctx context.Context) (*big.Int, error) { + return n.PeggyContract.GetValsetNonce(ctx, n.FromAddr) } -func (n *Network) GetSendToCosmosEvents(startBlock, endBlock uint64) ([]*wrappers.PeggySendToCosmosEvent, error) { - peggyFilterer, err := wrappers.NewPeggyFilterer(n.Address(), n.Provider()) +func (n *network) GetTxBatchNonce(ctx context.Context, erc20ContractAddress gethcommon.Address) (*big.Int, error) { + return n.PeggyContract.GetTxBatchNonce(ctx, erc20ContractAddress, n.FromAddr) +} + +func (n *network) GetSendToCosmosEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) { + peggyFilterer, err := peggyevents.NewPeggyFilterer(n.Address(), n.Provider()) if err != nil { return nil, errors.Wrap(err, "failed to init Peggy events filterer") } @@ -108,7 +166,7 @@ func (n *Network) GetSendToCosmosEvents(startBlock, endBlock uint64) ([]*wrapper defer iter.Close() - var sendToCosmosEvents []*wrappers.PeggySendToCosmosEvent + var sendToCosmosEvents []*peggyevents.PeggySendToCosmosEvent for iter.Next() { sendToCosmosEvents = append(sendToCosmosEvents, iter.Event) } @@ -116,8 +174,8 @@ func (n *Network) GetSendToCosmosEvents(startBlock, endBlock uint64) ([]*wrapper return sendToCosmosEvents, nil } -func (n *Network) GetSendToInjectiveEvents(startBlock, endBlock uint64) ([]*wrappers.PeggySendToInjectiveEvent, error) { - peggyFilterer, err := wrappers.NewPeggyFilterer(n.Address(), n.Provider()) +func (n *network) GetSendToInjectiveEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) { + peggyFilterer, err := peggyevents.NewPeggyFilterer(n.Address(), n.Provider()) if err != nil { return nil, errors.Wrap(err, "failed to init Peggy events filterer") } @@ -136,7 +194,7 @@ func (n *Network) GetSendToInjectiveEvents(startBlock, endBlock uint64) ([]*wrap defer iter.Close() - var sendToInjectiveEvents []*wrappers.PeggySendToInjectiveEvent + var sendToInjectiveEvents []*peggyevents.PeggySendToInjectiveEvent for iter.Next() { sendToInjectiveEvents = append(sendToInjectiveEvents, iter.Event) } @@ -144,8 +202,8 @@ func (n *Network) GetSendToInjectiveEvents(startBlock, endBlock uint64) ([]*wrap return sendToInjectiveEvents, nil } -func (n *Network) GetPeggyERC20DeployedEvents(startBlock, endBlock uint64) ([]*wrappers.PeggyERC20DeployedEvent, error) { - peggyFilterer, err := wrappers.NewPeggyFilterer(n.Address(), n.Provider()) +func (n *network) GetPeggyERC20DeployedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyERC20DeployedEvent, error) { + peggyFilterer, err := peggyevents.NewPeggyFilterer(n.Address(), n.Provider()) if err != nil { return nil, errors.Wrap(err, "failed to init Peggy events filterer") } @@ -164,7 +222,7 @@ func (n *Network) GetPeggyERC20DeployedEvents(startBlock, endBlock uint64) ([]*w defer iter.Close() - var transactionBatchExecutedEvents []*wrappers.PeggyERC20DeployedEvent + var transactionBatchExecutedEvents []*peggyevents.PeggyERC20DeployedEvent for iter.Next() { transactionBatchExecutedEvents = append(transactionBatchExecutedEvents, iter.Event) } @@ -172,8 +230,8 @@ func (n *Network) GetPeggyERC20DeployedEvents(startBlock, endBlock uint64) ([]*w return transactionBatchExecutedEvents, nil } -func (n *Network) GetValsetUpdatedEvents(startBlock, endBlock uint64) ([]*wrappers.PeggyValsetUpdatedEvent, error) { - peggyFilterer, err := wrappers.NewPeggyFilterer(n.Address(), n.Provider()) +func (n *network) GetValsetUpdatedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyValsetUpdatedEvent, error) { + peggyFilterer, err := peggyevents.NewPeggyFilterer(n.Address(), n.Provider()) if err != nil { return nil, errors.Wrap(err, "failed to init Peggy events filterer") } @@ -192,7 +250,7 @@ func (n *Network) GetValsetUpdatedEvents(startBlock, endBlock uint64) ([]*wrappe defer iter.Close() - var valsetUpdatedEvents []*wrappers.PeggyValsetUpdatedEvent + var valsetUpdatedEvents []*peggyevents.PeggyValsetUpdatedEvent for iter.Next() { valsetUpdatedEvents = append(valsetUpdatedEvents, iter.Event) } @@ -200,8 +258,8 @@ func (n *Network) GetValsetUpdatedEvents(startBlock, endBlock uint64) ([]*wrappe return valsetUpdatedEvents, nil } -func (n *Network) GetTransactionBatchExecutedEvents(startBlock, endBlock uint64) ([]*wrappers.PeggyTransactionBatchExecutedEvent, error) { - peggyFilterer, err := wrappers.NewPeggyFilterer(n.Address(), n.Provider()) +func (n *network) GetTransactionBatchExecutedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) { + peggyFilterer, err := peggyevents.NewPeggyFilterer(n.Address(), n.Provider()) if err != nil { return nil, errors.Wrap(err, "failed to init Peggy events filterer") } @@ -220,7 +278,7 @@ func (n *Network) GetTransactionBatchExecutedEvents(startBlock, endBlock uint64) defer iter.Close() - var transactionBatchExecutedEvents []*wrappers.PeggyTransactionBatchExecutedEvent + var transactionBatchExecutedEvents []*peggyevents.PeggyTransactionBatchExecutedEvent for iter.Next() { transactionBatchExecutedEvents = append(transactionBatchExecutedEvents, iter.Event) } @@ -228,32 +286,6 @@ func (n *Network) GetTransactionBatchExecutedEvents(startBlock, endBlock uint64) return transactionBatchExecutedEvents, nil } -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/ethereum/peggy/message_signatures.go b/orchestrator/ethereum/peggy/message_signatures.go index 14ca91ff..a3b85707 100644 --- a/orchestrator/ethereum/peggy/message_signatures.go +++ b/orchestrator/ethereum/peggy/message_signatures.go @@ -14,9 +14,9 @@ import ( "github.com/InjectiveLabs/sdk-go/chain/peggy/types" ) -/// EncodeValsetConfirm takes the required input data and produces the required signature to confirm a validator -/// set update on the Peggy Ethereum contract. This value will then be signed before being -/// submitted to Cosmos, verified, and then relayed to Ethereum +// EncodeValsetConfirm takes the required input data and produces the required signature to confirm a validator +// set update on the Peggy Ethereum contract. This value will then be signed before being +// submitted to Cosmos, verified, and then relayed to Ethereum func EncodeValsetConfirm(peggyID common.Hash, valset *types.Valset) common.Hash { // error case here should not occur outside of testing since the above is a constant contractAbi, abiErr := abi.JSON(strings.NewReader(ValsetCheckpointABIJSON)) diff --git a/orchestrator/ethereum/peggy/peggy_contract.go b/orchestrator/ethereum/peggy/peggy_contract.go index a243dd0d..36e92c97 100644 --- a/orchestrator/ethereum/peggy/peggy_contract.go +++ b/orchestrator/ethereum/peggy/peggy_contract.go @@ -14,10 +14,11 @@ import ( "github.com/shopspring/decimal" "github.com/InjectiveLabs/metrics" + "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum/committer" "github.com/InjectiveLabs/peggo/orchestrator/ethereum/provider" wrappers "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" - "github.com/InjectiveLabs/sdk-go/chain/peggy/types" ) type PeggyContract interface { diff --git a/orchestrator/ethereum/peggy/pending_transactions.go b/orchestrator/ethereum/peggy/pending_transactions.go index c9f45f00..60c6fffc 100644 --- a/orchestrator/ethereum/peggy/pending_transactions.go +++ b/orchestrator/ethereum/peggy/pending_transactions.go @@ -3,7 +3,6 @@ package peggy import ( "bytes" "context" - "time" log "github.com/xlab/suplog" diff --git a/orchestrator/ethereum/peggy/send_to_cosmos.go b/orchestrator/ethereum/peggy/send_to_cosmos.go index e780da8d..ea64d8b6 100644 --- a/orchestrator/ethereum/peggy/send_to_cosmos.go +++ b/orchestrator/ethereum/peggy/send_to_cosmos.go @@ -11,6 +11,7 @@ import ( log "github.com/xlab/suplog" "github.com/InjectiveLabs/metrics" + wrappers "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" ) diff --git a/orchestrator/relayer.go b/orchestrator/inj_relayer.go similarity index 52% rename from orchestrator/relayer.go rename to orchestrator/inj_relayer.go index 17e5b9d2..9136b639 100644 --- a/orchestrator/relayer.go +++ b/orchestrator/inj_relayer.go @@ -5,17 +5,19 @@ import ( "sort" "time" - "github.com/avast/retry-go" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" + cosmostypes "github.com/cosmos/cosmos-sdk/types" + gethcommon "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" log "github.com/xlab/suplog" "github.com/InjectiveLabs/metrics" + peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + + "github.com/InjectiveLabs/peggo/orchestrator/cosmos" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum" "github.com/InjectiveLabs/peggo/orchestrator/ethereum/util" "github.com/InjectiveLabs/peggo/orchestrator/loops" peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" - "github.com/InjectiveLabs/sdk-go/chain/peggy/types" ) const ( @@ -23,54 +25,65 @@ const ( findValsetBlocksToSearch = 2000 ) -func (s *PeggyOrchestrator) RelayerMainLoop(ctx context.Context) (err error) { - loop := relayerLoop{ - PeggyOrchestrator: s, - loopDuration: defaultRelayerLoopDur, +func (s *Orchestrator) runRelayer(ctx context.Context, inj cosmos.Network, eth ethereum.Network) (err error) { + rel := relayer{ + Orchestrator: s, + Injective: inj, + Ethereum: eth, } - return loop.Run(ctx) + relayingBatches := rel.IsRelayingBatches() + relayingValsets := rel.IsRelayingValsets() + if noRelay := !relayingBatches && !relayingValsets; noRelay { + return nil + } + + s.logger.WithFields(log.Fields{"loop_duration": defaultRelayerLoopDur.String(), "relay_batches": relayingBatches, "relay_valsets": relayingValsets}).Debugln("starting Relayer...") + + return loops.RunLoop(ctx, defaultRelayerLoopDur, func() error { + return rel.RelayValsetsAndBatches(ctx) + }) } -type relayerLoop struct { - *PeggyOrchestrator - loopDuration time.Duration +type relayer struct { + *Orchestrator + Injective cosmos.Network + Ethereum ethereum.Network } -func (l *relayerLoop) Logger() log.Logger { +func (l *relayer) Logger() log.Logger { return l.logger.WithField("loop", "Relayer") } -func (l *relayerLoop) Run(ctx context.Context) error { - return loops.RunLoop(ctx, l.loopDuration, func() error { - return l.relayValsetsAndBatches(ctx) - }) +func (l *relayer) IsRelayingBatches() bool { + return l.relayBatchOffsetDur != 0 } -func (l *relayerLoop) relayValsetsAndBatches(ctx context.Context) error { +func (l *relayer) IsRelayingValsets() bool { + return l.relayValsetOffsetDur != 0 +} + +func (l *relayer) RelayValsetsAndBatches(ctx context.Context) error { + ethValset, err := l.GetLatestEthValset(ctx) + if err != nil { + return err + } + var pg loops.ParanoidGroup - if l.valsetRelayEnabled { + if l.relayValsetOffsetDur != 0 { pg.Go(func() error { - return retry.Do(func() error { return l.relayValset(ctx) }, - retry.Context(ctx), - retry.Attempts(l.maxAttempts), - retry.OnRetry(func(n uint, err error) { - l.Logger().WithError(err).Warningf("failed to relay valsets, will retry (%d)", n) - }), - ) + return retryFnOnErr(ctx, l.Logger(), func() error { + return l.relayValset(ctx, ethValset) + }) }) } - if l.batchRelayEnabled { + if l.relayBatchOffsetDur != 0 { pg.Go(func() error { - return retry.Do(func() error { return l.relayBatch(ctx) }, - retry.Context(ctx), - retry.Attempts(l.maxAttempts), - retry.OnRetry(func(n uint, err error) { - l.Logger().WithError(err).Warningf("failed to relay batches, will retry (%d)", n) - }), - ) + return retryFnOnErr(ctx, l.Logger(), func() error { + return l.relayBatch(ctx, ethValset) + }) }) } @@ -82,176 +95,205 @@ func (l *relayerLoop) relayValsetsAndBatches(ctx context.Context) error { } return nil + } -func (l *relayerLoop) relayValset(ctx context.Context) error { +func (l *relayer) GetLatestEthValset(ctx context.Context) (*peggytypes.Valset, error) { + var latestEthValset *peggytypes.Valset + fn := func() error { + vs, err := l.findLatestValsetOnEth(ctx) + if err != nil { + return err + } + + latestEthValset = vs + return nil + } + + if err := retryFnOnErr(ctx, l.Logger(), fn); err != nil { + l.Logger().WithError(err).Errorln("got error, loop exits") + return nil, err + } + + return latestEthValset, nil +} + +func (l *relayer) relayValset(ctx context.Context, latestEthValset *peggytypes.Valset) error { metrics.ReportFuncCall(l.svcTags) doneFn := metrics.ReportFuncTiming(l.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 := l.inj.LatestValsets(ctx) + latestInjectiveValsets, err := l.Injective.LatestValsets(ctx) if err != nil { return errors.Wrap(err, "failed to get latest valset updates from Injective") } var ( - oldestConfirmedValset *types.Valset - oldestConfirmedValsetSigs []*types.MsgValsetConfirm + latestConfirmedValset *peggytypes.Valset + confirmations []*peggytypes.MsgValsetConfirm ) - for _, set := range latestValsets { - sigs, err := l.inj.AllValsetConfirms(ctx, set.Nonce) + for _, set := range latestInjectiveValsets { + sigs, err := l.Injective.AllValsetConfirms(ctx, set.Nonce) if err != nil { return errors.Wrapf(err, "failed to get valset confirmations for nonce %d", set.Nonce) - } else if len(sigs) == 0 { + } + + if len(sigs) == 0 { continue } - oldestConfirmedValsetSigs = sigs - oldestConfirmedValset = set + confirmations = sigs + latestConfirmedValset = set break } - if oldestConfirmedValset == nil { - l.Logger().Debugln("no valset update to relay") + if latestConfirmedValset == nil { + l.Logger().Infoln("no valset to relay") return nil } - currentEthValset, err := l.findLatestValsetOnEth(ctx, l.inj, l.eth) - if err != nil { - return errors.Wrap(err, "failed to find latest confirmed valset update on Ethereum") + if !l.shouldRelayValset(ctx, latestConfirmedValset) { + return nil } - if oldestConfirmedValset.Nonce <= currentEthValset.Nonce { - l.Logger().WithFields(log.Fields{"eth_nonce": currentEthValset.Nonce, "inj_nonce": currentEthValset.Nonce}).Debugln("valset already updated on Ethereum") - return nil + txHash, err := l.Ethereum.SendEthValsetUpdate(ctx, latestEthValset, latestConfirmedValset, confirmations) + if err != nil { + return err } - latestEthereumValsetNonce, err := l.eth.GetValsetNonce(ctx) + l.Logger().WithField("tx_hash", txHash.Hex()).Infoln("sent validator set update to Ethereum") + + return nil +} + +func (l *relayer) shouldRelayValset(ctx context.Context, vs *peggytypes.Valset) bool { + latestEthereumValsetNonce, err := l.Ethereum.GetValsetNonce(ctx) if err != nil { - return errors.Wrap(err, "failed to get latest valset nonce from Ethereum") + l.Logger().WithError(err).Warningln("failed to get latest valset nonce from Ethereum") + return false } // Check if other validators already updated the valset - if oldestConfirmedValset.Nonce <= latestEthereumValsetNonce.Uint64() { - l.Logger().WithFields(log.Fields{"eth_nonce": latestEthereumValsetNonce, "inj_nonce": currentEthValset.Nonce}).Debugln("valset already updated on Ethereum") - return nil + if vs.Nonce <= latestEthereumValsetNonce.Uint64() { + l.Logger().WithFields(log.Fields{"eth_nonce": latestEthereumValsetNonce, "inj_nonce": vs.Nonce}).Debugln("valset already updated on Ethereum") + return false } - l.Logger().WithFields(log.Fields{"inj_nonce": oldestConfirmedValset.Nonce, "eth_nonce": latestEthereumValsetNonce.Uint64()}).Debugln("latest valset updates") - // Check custom time delay offset - blockTime, err := l.inj.GetBlockCreationTime(ctx, int64(oldestConfirmedValset.Height)) + block, err := l.Injective.GetBlock(ctx, int64(vs.Height)) if err != nil { - return errors.Wrap(err, "failed to parse timestamp from block") + l.Logger().WithError(err).Warningln("unable to get latest block from Injective") + return false } - if timeElapsed := time.Since(blockTime); timeElapsed <= l.relayValsetOffsetDur { + if timeElapsed := time.Since(block.Block.Time); timeElapsed <= l.relayValsetOffsetDur { timeRemaining := time.Duration(int64(l.relayValsetOffsetDur) - int64(timeElapsed)) l.Logger().WithField("time_remaining", timeRemaining.String()).Debugln("valset relay offset not reached yet") - return nil - } - - txHash, err := l.eth.SendEthValsetUpdate( - ctx, - currentEthValset, - oldestConfirmedValset, - oldestConfirmedValsetSigs, - ) - if err != nil { - return err + return false } - l.Logger().WithField("tx_hash", txHash.Hex()).Infoln("sent valset tx to Ethereum") + l.Logger().WithFields(log.Fields{"inj_nonce": vs.Nonce, "eth_nonce": latestEthereumValsetNonce.Uint64()}).Debugln("new valset update") - return nil + return true } -func (l *relayerLoop) relayBatch(ctx context.Context) error { +func (l *relayer) relayBatch(ctx context.Context, latestEthValset *peggytypes.Valset) error { metrics.ReportFuncCall(l.svcTags) doneFn := metrics.ReportFuncTiming(l.svcTags) defer doneFn() - latestBatches, err := l.inj.LatestTransactionBatches(ctx) + latestBatches, err := l.Injective.LatestTransactionBatches(ctx) + if err != nil { + return err + } + + latestEthHeight, err := l.Ethereum.GetHeaderByNumber(ctx, nil) if err != nil { return err } var ( - oldestConfirmedBatch *types.OutgoingTxBatch - oldestConfirmedBatchSigs []*types.MsgConfirmBatch + oldestConfirmedBatch *peggytypes.OutgoingTxBatch + confirmations []*peggytypes.MsgConfirmBatch ) for _, batch := range latestBatches { - sigs, err := l.inj.TransactionBatchSignatures(ctx, batch.BatchNonce, common.HexToAddress(batch.TokenContract)) + // temporary + if _, err := l.Ethereum.TokenDecimals(ctx, gethcommon.HexToAddress(batch.TokenContract)); err != nil { + l.Logger().Warningln("skipping batch for non existent token contract", batch.TokenContract) + continue + } + + if batch.BatchTimeout <= latestEthHeight.Number.Uint64() { + l.Logger().WithFields(log.Fields{"batch_nonce": batch.BatchNonce, "batch_timeout_height": batch.BatchTimeout, "latest_eth_height": latestEthHeight.Number.Uint64()}).Debugln("skipping timed out batch") + continue + } + + sigs, err := l.Injective.TransactionBatchSignatures(ctx, batch.BatchNonce, gethcommon.HexToAddress(batch.TokenContract)) if err != nil { return err - } else if len(sigs) == 0 { + } + + if len(sigs) == 0 { continue } oldestConfirmedBatch = batch - oldestConfirmedBatchSigs = sigs + confirmations = sigs } if oldestConfirmedBatch == nil { - l.Logger().Debugln("no batch to relay") + l.Logger().Infoln("no batch to relay") return nil } - latestEthereumBatch, err := l.eth.GetTxBatchNonce(ctx, common.HexToAddress(oldestConfirmedBatch.TokenContract)) - if err != nil { - return err + if !l.shouldRelayBatch(ctx, oldestConfirmedBatch) { + return nil } - currentValset, err := l.findLatestValsetOnEth(ctx, l.inj, l.eth) + txHash, err := l.Ethereum.SendTransactionBatch(ctx, latestEthValset, oldestConfirmedBatch, confirmations) if err != nil { - return errors.Wrap(err, "failed to find latest valset") - } else if currentValset == nil { - return errors.Wrap(err, "latest valset not found") - } - - if oldestConfirmedBatch.BatchNonce <= latestEthereumBatch.Uint64() { - l.Logger().WithFields(log.Fields{"eth_nonce": latestEthereumBatch.Uint64(), "inj_nonce": oldestConfirmedBatch.BatchNonce}).Debugln("batch already updated on Ethereum") + // Returning an error here triggers retries which don't help much except risk a binary crash + // Better to warn the user and try again in the next loop interval + log.WithError(err).Warningln("failed to send outgoing tx batch to Ethereum") return nil } - latestEthereumBatch, err = l.eth.GetTxBatchNonce(ctx, common.HexToAddress(oldestConfirmedBatch.TokenContract)) + l.Logger().WithField("tx_hash", txHash.Hex()).Infoln("sent outgoing tx batch to Ethereum") + + return nil +} + +func (l *relayer) shouldRelayBatch(ctx context.Context, batch *peggytypes.OutgoingTxBatch) bool { + latestEthBatch, err := l.Ethereum.GetTxBatchNonce(ctx, gethcommon.HexToAddress(batch.TokenContract)) if err != nil { - return err + l.Logger().WithError(err).Warningf("unable to get latest batch nonce from Ethereum: token_contract=%s", gethcommon.HexToAddress(batch.TokenContract)) + return false } // Check if ethereum batch was updated by other validators - if oldestConfirmedBatch.BatchNonce <= latestEthereumBatch.Uint64() { - l.Logger().WithFields(log.Fields{"eth_nonce": latestEthereumBatch.Uint64(), "inj_nonce": oldestConfirmedBatch.BatchNonce}).Debugln("batch already updated on Ethereum") - return nil + if batch.BatchNonce <= latestEthBatch.Uint64() { + l.Logger().WithFields(log.Fields{"eth_nonce": latestEthBatch.Uint64(), "inj_nonce": batch.BatchNonce}).Debugln("batch already updated on Ethereum") + return false } - l.Logger().WithFields(log.Fields{"inj_nonce": oldestConfirmedBatch.BatchNonce, "eth_nonce": latestEthereumBatch.Uint64()}).Debugln("latest batch updates") - // Check custom time delay offset - blockTime, err := l.inj.GetBlockCreationTime(ctx, int64(oldestConfirmedBatch.Block)) + blockTime, err := l.Injective.GetBlock(ctx, int64(batch.Block)) if err != nil { - return errors.Wrap(err, "failed to parse timestamp from block") + l.Logger().WithError(err).Warningln("unable to get latest block from Injective") + return false } - if timeElapsed := time.Since(blockTime); timeElapsed <= l.relayBatchOffsetDur { + if timeElapsed := time.Since(blockTime.Block.Time); timeElapsed <= l.relayBatchOffsetDur { timeRemaining := time.Duration(int64(l.relayBatchOffsetDur) - int64(timeElapsed)) l.Logger().WithField("time_remaining", timeRemaining.String()).Debugln("batch relay offset not reached yet") - return nil + return false } - // Send SendTransactionBatch to Ethereum - txHash, err := l.eth.SendTransactionBatch(ctx, currentValset, oldestConfirmedBatch, oldestConfirmedBatchSigs) - if err != nil { - return err - } + l.Logger().WithFields(log.Fields{"inj_nonce": batch.BatchNonce, "eth_nonce": latestEthBatch.Uint64()}).Debugln("new batch update") - l.Logger().WithField("tx_hash", txHash.Hex()).Infoln("sent batch tx to Ethereum") - - return nil + return true } // FindLatestValset finds the latest valset on the Peggy contract by looking back through the event @@ -259,18 +301,18 @@ func (l *relayerLoop) relayBatch(ctx context.Context) error { // 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 (l *relayerLoop) findLatestValsetOnEth(ctx context.Context, injective InjectiveNetwork, ethereum EthereumNetwork) (*types.Valset, error) { - latestHeader, err := ethereum.HeaderByNumber(ctx, nil) +func (l *relayer) findLatestValsetOnEth(ctx context.Context) (*peggytypes.Valset, error) { + latestHeader, err := l.Ethereum.GetHeaderByNumber(ctx, nil) if err != nil { return nil, errors.Wrap(err, "failed to get latest eth header") } - latestEthereumValsetNonce, err := ethereum.GetValsetNonce(ctx) + latestEthereumValsetNonce, err := l.Ethereum.GetValsetNonce(ctx) if err != nil { return nil, errors.Wrap(err, "failed to get latest valset nonce on Ethereum") } - cosmosValset, err := injective.ValsetAt(ctx, latestEthereumValsetNonce.Uint64()) + cosmosValset, err := l.Injective.ValsetAt(ctx, latestEthereumValsetNonce.Uint64()) if err != nil { return nil, errors.Wrap(err, "failed to get Injective valset") } @@ -285,7 +327,7 @@ func (l *relayerLoop) findLatestValsetOnEth(ctx context.Context, injective Injec startSearchBlock = currentBlock - findValsetBlocksToSearch } - valsetUpdatedEvents, err := ethereum.GetValsetUpdatedEvents(startSearchBlock, currentBlock) + valsetUpdatedEvents, err := l.Ethereum.GetValsetUpdatedEvents(startSearchBlock, currentBlock) if err != nil { return nil, errors.Wrap(err, "failed to filter past ValsetUpdated events from Ethereum") } @@ -303,15 +345,15 @@ func (l *relayerLoop) findLatestValsetOnEth(ctx context.Context, injective Injec // we take only the first event if we find any at all. event := valsetUpdatedEvents[0] - valset := &types.Valset{ + valset := &peggytypes.Valset{ Nonce: event.NewValsetNonce.Uint64(), - Members: make([]*types.BridgeValidator, 0, len(event.Powers)), - RewardAmount: sdk.NewIntFromBigInt(event.RewardAmount), + Members: make([]*peggytypes.BridgeValidator, 0, len(event.Powers)), + RewardAmount: cosmostypes.NewIntFromBigInt(event.RewardAmount), RewardToken: event.RewardToken.Hex(), } for idx, p := range event.Powers { - valset.Members = append(valset.Members, &types.BridgeValidator{ + valset.Members = append(valset.Members, &peggytypes.BridgeValidator{ Power: p.Uint64(), EthereumAddress: event.Validators[idx].Hex(), }) @@ -345,7 +387,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 checkIfValsetsDiffer(cosmosValset, ethereumValset *types.Valset) { +func checkIfValsetsDiffer(cosmosValset, ethereumValset *peggytypes.Valset) { if cosmosValset == nil && ethereumValset.Nonce == 0 { // bootstrapping case return @@ -386,7 +428,7 @@ func checkIfValsetsDiffer(cosmosValset, ethereumValset *types.Valset) { } } -type BridgeValidators []*types.BridgeValidator +type BridgeValidators []*peggytypes.BridgeValidator // Sort sorts the validators by power func (b BridgeValidators) Sort() { diff --git a/orchestrator/inj_signer.go b/orchestrator/inj_signer.go new file mode 100644 index 00000000..d656d5e0 --- /dev/null +++ b/orchestrator/inj_signer.go @@ -0,0 +1,113 @@ +package orchestrator + +import ( + "context" + gethcommon "github.com/ethereum/go-ethereum/common" + log "github.com/xlab/suplog" + + peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + + "github.com/InjectiveLabs/peggo/orchestrator/cosmos" + "github.com/InjectiveLabs/peggo/orchestrator/loops" +) + +// runEthSigner 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 *Orchestrator) runEthSigner(ctx context.Context, inj cosmos.Network, peggyID gethcommon.Hash) error { + signer := ethSigner{ + Orchestrator: s, + Injective: inj, + PeggyID: peggyID, + } + + s.logger.WithField("loop_duration", defaultLoopDur.String()).Debugln("starting Signer...") + + return loops.RunLoop(ctx, defaultLoopDur, func() error { + return signer.SignValsetsAndBatches(ctx) + }) +} + +type ethSigner struct { + *Orchestrator + Injective cosmos.Network + PeggyID gethcommon.Hash +} + +func (l *ethSigner) Logger() log.Logger { + return l.logger.WithField("loop", "Signer") +} + +func (l *ethSigner) SignValsetsAndBatches(ctx context.Context) error { + if err := l.signNewValsetUpdates(ctx); err != nil { + return err + } + + if err := l.signNewBatch(ctx); err != nil { + return err + } + + return nil +} + +func (l *ethSigner) signNewValsetUpdates(ctx context.Context) error { + var oldestUnsignedValsets []*peggytypes.Valset + getUnsignedValsetsFn := func() error { + oldestUnsignedValsets, _ = l.Injective.OldestUnsignedValsets(ctx, l.injAddr) + return nil + } + + if err := retryFnOnErr(ctx, l.Logger(), getUnsignedValsetsFn); err != nil { + l.Logger().WithError(err).Errorln("got error, loop exits") + return err + } + + if len(oldestUnsignedValsets) == 0 { + l.Logger().Infoln("no valset updates to confirm") + return nil + } + + for _, vs := range oldestUnsignedValsets { + if err := retryFnOnErr(ctx, l.Logger(), func() error { + return l.Injective.SendValsetConfirm(ctx, l.ethAddr, l.PeggyID, vs) + }); err != nil { + l.Logger().WithError(err).Errorln("got error, loop exits") + return err + } + + l.Logger().WithFields(log.Fields{"valset_nonce": vs.Nonce, "validators": len(vs.Members)}).Infoln("confirmed valset update on Injective") + } + + return nil +} + +func (l *ethSigner) signNewBatch(ctx context.Context) error { + var oldestUnsignedBatch *peggytypes.OutgoingTxBatch + getBatchFn := func() error { + oldestUnsignedBatch, _ = l.Injective.OldestUnsignedTransactionBatch(ctx, l.injAddr) + return nil + } + + if err := retryFnOnErr(ctx, l.Logger(), getBatchFn); err != nil { + l.Logger().WithError(err).Errorln("got error, loop exits") + return err + } + + if oldestUnsignedBatch == nil { + l.Logger().Infoln("no batch to confirm") + return nil + } + + confirmBatchFn := func() error { + return l.Injective.SendBatchConfirm(ctx, l.ethAddr, l.PeggyID, oldestUnsignedBatch) + } + + if err := retryFnOnErr(ctx, l.Logger(), confirmBatchFn); err != nil { + l.Logger().WithError(err).Errorln("got error, loop exits") + return err + } + + l.Logger().WithFields(log.Fields{"token_contract": oldestUnsignedBatch.TokenContract, "batch_nonce": oldestUnsignedBatch.BatchNonce, "txs": len(oldestUnsignedBatch.Transactions)}).Infoln("confirmed batch on Injective") + + return nil +} diff --git a/orchestrator/injective.go b/orchestrator/injective.go deleted file mode 100644 index 6e6fb25d..00000000 --- a/orchestrator/injective.go +++ /dev/null @@ -1,41 +0,0 @@ -package orchestrator - -import ( - "context" - "time" - - gethcommon "github.com/ethereum/go-ethereum/common" - - peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" - peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" -) - -// InjectiveNetwork is the orchestrator's reference endpoint to the Injective network -type InjectiveNetwork interface { - PeggyParams(ctx context.Context) (*peggytypes.Params, error) - GetBlockCreationTime(ctx context.Context, height int64) (time.Time, error) - HasRegisteredEthAddress(ctx context.Context, addr gethcommon.Address) (bool, 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 - - UnbatchedTokensWithFees(ctx context.Context) ([]*peggytypes.BatchFees, error) - SendRequestBatch(ctx context.Context, denom string) error - OldestUnsignedTransactionBatch(ctx context.Context) (*peggytypes.OutgoingTxBatch, error) - SendBatchConfirm(ctx context.Context, ethFrom gethcommon.Address, peggyID gethcommon.Hash, batch *peggytypes.OutgoingTxBatch) error - LatestTransactionBatches(ctx context.Context) ([]*peggytypes.OutgoingTxBatch, error) - TransactionBatchSignatures(ctx context.Context, nonce uint64, tokenContract gethcommon.Address) ([]*peggytypes.MsgConfirmBatch, error) - - OldestUnsignedValsets(ctx context.Context) ([]*peggytypes.Valset, error) - SendValsetConfirm(ctx context.Context, ethFrom gethcommon.Address, peggyID gethcommon.Hash, valset *peggytypes.Valset) 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) -} diff --git a/orchestrator/loops/paranoid_group.go b/orchestrator/loops/paranoid_group.go index c0c4f261..d8f50454 100644 --- a/orchestrator/loops/paranoid_group.go +++ b/orchestrator/loops/paranoid_group.go @@ -4,7 +4,7 @@ import ( "sync" "time" - multierror "github.com/hashicorp/go-multierror" + "github.com/hashicorp/go-multierror" ) // ParanoidGroup is a special primitive to run groups of goroutines, e.g. loops. diff --git a/orchestrator/mocks_test.go b/orchestrator/mocks_test.go index 1011dcf4..e549411c 100644 --- a/orchestrator/mocks_test.go +++ b/orchestrator/mocks_test.go @@ -2,216 +2,325 @@ 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" + log "github.com/xlab/suplog" "math/big" "time" + comettypes "github.com/cometbft/cometbft/rpc/core/types" + sdktypes "github.com/cosmos/cosmos-sdk/types" + gethcommon "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + + peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" ) -type mockPriceFeed struct { - queryFn func(eth.Address) (float64, error) +type MockPriceFeed struct { + QueryUSDPriceFn func(gethcommon.Address) (float64, error) +} + +func (p MockPriceFeed) QueryUSDPrice(address gethcommon.Address) (float64, error) { + return p.QueryUSDPriceFn(address) +} + +type MockCosmosNetwork struct { + PeggyParamsFn func(ctx context.Context) (*peggytypes.Params, error) + LastClaimEventByAddrFn func(ctx context.Context, address sdktypes.AccAddress) (*peggytypes.LastClaimEvent, error) + GetValidatorAddressFn func(ctx context.Context, address gethcommon.Address) (sdktypes.AccAddress, error) + CurrentValsetFn func(ctx context.Context) (*peggytypes.Valset, error) + ValsetAtFn func(ctx context.Context, uint642 uint64) (*peggytypes.Valset, error) + OldestUnsignedValsetsFn func(ctx context.Context, address sdktypes.AccAddress) ([]*peggytypes.Valset, error) + LatestValsetsFn func(ctx context.Context) ([]*peggytypes.Valset, error) + AllValsetConfirmsFn func(ctx context.Context, uint642 uint64) ([]*peggytypes.MsgValsetConfirm, error) + OldestUnsignedTransactionBatchFn func(ctx context.Context, address sdktypes.AccAddress) (*peggytypes.OutgoingTxBatch, error) + LatestTransactionBatchesFn func(ctx context.Context) ([]*peggytypes.OutgoingTxBatch, error) + UnbatchedTokensWithFeesFn func(ctx context.Context) ([]*peggytypes.BatchFees, error) + TransactionBatchSignaturesFn func(ctx context.Context, uint642 uint64, address gethcommon.Address) ([]*peggytypes.MsgConfirmBatch, error) + UpdatePeggyOrchestratorAddressesFn func(ctx context.Context, address gethcommon.Address, address2 sdktypes.Address) error + SendValsetConfirmFn func(ctx context.Context, address gethcommon.Address, hash gethcommon.Hash, valset *peggytypes.Valset) error + SendBatchConfirmFn func(ctx context.Context, ethFrom gethcommon.Address, peggyID gethcommon.Hash, batch *peggytypes.OutgoingTxBatch) error + SendRequestBatchFn func(ctx context.Context, denom string) error + SendToEthFn func(ctx context.Context, destination gethcommon.Address, amount, fee sdktypes.Coin) error + SendOldDepositClaimFn func(ctx context.Context, deposit *peggyevents.PeggySendToCosmosEvent) error + SendDepositClaimFn func(ctx context.Context, deposit *peggyevents.PeggySendToInjectiveEvent) error + SendWithdrawalClaimFn func(ctx context.Context, withdrawal *peggyevents.PeggyTransactionBatchExecutedEvent) error + SendValsetClaimFn func(ctx context.Context, vs *peggyevents.PeggyValsetUpdatedEvent) error + SendERC20DeployedClaimFn func(ctx context.Context, erc20 *peggyevents.PeggyERC20DeployedEvent) error + GetBlockFn func(ctx context.Context, height int64) (*comettypes.ResultBlock, error) + GetLatestBlockHeightFn func(ctx context.Context) (int64, error) +} + +func (n MockCosmosNetwork) PeggyParams(ctx context.Context) (*peggytypes.Params, error) { + return n.PeggyParamsFn(ctx) +} + +func (n MockCosmosNetwork) LastClaimEventByAddr(ctx context.Context, validatorAccountAddress sdktypes.AccAddress) (*peggytypes.LastClaimEvent, error) { + return n.LastClaimEventByAddrFn(ctx, validatorAccountAddress) +} + +func (n MockCosmosNetwork) GetValidatorAddress(ctx context.Context, addr gethcommon.Address) (sdktypes.AccAddress, error) { + return n.GetValidatorAddressFn(ctx, addr) +} + +func (n MockCosmosNetwork) ValsetAt(ctx context.Context, nonce uint64) (*peggytypes.Valset, error) { + return n.ValsetAtFn(ctx, nonce) +} + +func (n MockCosmosNetwork) CurrentValset(ctx context.Context) (*peggytypes.Valset, error) { + return n.CurrentValsetFn(ctx) +} + +func (n MockCosmosNetwork) OldestUnsignedValsets(ctx context.Context, valAccountAddress sdktypes.AccAddress) ([]*peggytypes.Valset, error) { + return n.OldestUnsignedValsetsFn(ctx, valAccountAddress) +} + +func (n MockCosmosNetwork) LatestValsets(ctx context.Context) ([]*peggytypes.Valset, error) { + return n.LatestValsetsFn(ctx) +} + +func (n MockCosmosNetwork) AllValsetConfirms(ctx context.Context, nonce uint64) ([]*peggytypes.MsgValsetConfirm, error) { + return n.AllValsetConfirmsFn(ctx, nonce) +} + +func (n MockCosmosNetwork) OldestUnsignedTransactionBatch(ctx context.Context, valAccountAddress sdktypes.AccAddress) (*peggytypes.OutgoingTxBatch, error) { + return n.OldestUnsignedTransactionBatchFn(ctx, valAccountAddress) +} + +func (n MockCosmosNetwork) LatestTransactionBatches(ctx context.Context) ([]*peggytypes.OutgoingTxBatch, error) { + return n.LatestTransactionBatchesFn(ctx) +} + +func (n MockCosmosNetwork) UnbatchedTokensWithFees(ctx context.Context) ([]*peggytypes.BatchFees, error) { + return n.UnbatchedTokensWithFeesFn(ctx) +} + +func (n MockCosmosNetwork) TransactionBatchSignatures(ctx context.Context, nonce uint64, tokenContract gethcommon.Address) ([]*peggytypes.MsgConfirmBatch, error) { + return n.TransactionBatchSignaturesFn(ctx, nonce, tokenContract) +} + +func (n MockCosmosNetwork) UpdatePeggyOrchestratorAddresses(ctx context.Context, ethFrom gethcommon.Address, orchAddr sdktypes.AccAddress) error { + return n.UpdatePeggyOrchestratorAddressesFn(ctx, ethFrom, orchAddr) +} + +func (n MockCosmosNetwork) SendValsetConfirm(ctx context.Context, ethFrom gethcommon.Address, peggyID gethcommon.Hash, valset *peggytypes.Valset) error { + return n.SendValsetConfirmFn(ctx, ethFrom, peggyID, valset) +} + +func (n MockCosmosNetwork) SendBatchConfirm(ctx context.Context, ethFrom gethcommon.Address, peggyID gethcommon.Hash, batch *peggytypes.OutgoingTxBatch) error { + return n.SendBatchConfirmFn(ctx, ethFrom, peggyID, batch) +} + +func (n MockCosmosNetwork) SendRequestBatch(ctx context.Context, denom string) error { + return n.SendRequestBatchFn(ctx, denom) +} + +func (n MockCosmosNetwork) SendToEth(ctx context.Context, destination gethcommon.Address, amount, fee sdktypes.Coin) error { + return n.SendToEthFn(ctx, destination, amount, fee) } -func (p mockPriceFeed) QueryUSDPrice(address eth.Address) (float64, error) { - return p.queryFn(address) +func (n MockCosmosNetwork) SendOldDepositClaim(ctx context.Context, deposit *peggyevents.PeggySendToCosmosEvent) error { + return n.SendOldDepositClaimFn(ctx, deposit) } -type mockInjective struct { - unbatchedTokenFeesFn func(context.Context) ([]*peggytypes.BatchFees, error) - unbatchedTokenFeesCallCount int +func (n MockCosmosNetwork) SendDepositClaim(ctx context.Context, deposit *peggyevents.PeggySendToInjectiveEvent) error { + return n.SendDepositClaimFn(ctx, deposit) +} - sendRequestBatchFn func(context.Context, string) error - sendRequestBatchCallCount int +func (n MockCosmosNetwork) SendWithdrawalClaim(ctx context.Context, withdrawal *peggyevents.PeggyTransactionBatchExecutedEvent) error { + return n.SendWithdrawalClaimFn(ctx, withdrawal) +} - 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 (n MockCosmosNetwork) SendValsetClaim(ctx context.Context, vs *peggyevents.PeggyValsetUpdatedEvent) error { + return n.SendValsetClaimFn(ctx, vs) +} - oldestUnsignedValsetsFn func(context.Context) ([]*peggytypes.Valset, error) - sendValsetConfirmFn func(context.Context, eth.Hash, *peggytypes.Valset, eth.Address) error +func (n MockCosmosNetwork) SendERC20DeployedClaim(ctx context.Context, erc20 *peggyevents.PeggyERC20DeployedEvent) error { + return n.SendERC20DeployedClaimFn(ctx, erc20) +} - oldestUnsignedTransactionBatchFn func(context.Context) (*peggytypes.OutgoingTxBatch, error) - sendBatchConfirmFn func(context.Context, eth.Hash, *peggytypes.OutgoingTxBatch, eth.Address) error +func (n MockCosmosNetwork) GetBlock(ctx context.Context, height int64) (*comettypes.ResultBlock, error) { + return n.GetBlockFn(ctx, height) +} - latestValsetsFn func(context.Context) ([]*peggytypes.Valset, error) - getBlockCreationTimeFn func(context.Context, int64) (time.Time, error) +func (n MockCosmosNetwork) GetLatestBlockHeight(ctx context.Context) (int64, error) { + //TODO implement me + panic("implement me") +} - allValsetConfirmsFn func(context.Context, uint64) ([]*peggytypes.MsgValsetConfirm, error) - valsetAtFn func(context.Context, uint64) (*peggytypes.Valset, error) +func (n MockCosmosNetwork) GetTxs(ctx context.Context, block *comettypes.ResultBlock) ([]*comettypes.ResultTx, error) { + //TODO implement me + panic("implement me") +} + +func (n MockCosmosNetwork) GetValidatorSet(ctx context.Context, height int64) (*comettypes.ResultValidators, error) { + //TODO implement me + panic("implement me") +} + +type MockEthereumNetwork struct { + GetHeaderByNumberFn func(ctx context.Context, number *big.Int) (*gethtypes.Header, error) + GetPeggyIDFn func(ctx context.Context) (gethcommon.Hash, error) + GetSendToCosmosEventsFn func(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) + GetSendToInjectiveEventsFn func(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) + GetPeggyERC20DeployedEventsFn func(startBlock, endBlock uint64) ([]*peggyevents.PeggyERC20DeployedEvent, error) + GetValsetUpdatedEventsFn func(startBlock, endBlock uint64) ([]*peggyevents.PeggyValsetUpdatedEvent, error) + GetTransactionBatchExecutedEventsFn func(startBlock, endBlock uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) + GetValsetNonceFn func(ctx context.Context) (*big.Int, error) + SendEthValsetUpdateFn func(ctx context.Context, oldValset *peggytypes.Valset, newValset *peggytypes.Valset, confirms []*peggytypes.MsgValsetConfirm) (*gethcommon.Hash, error) + GetTxBatchNonceFn func(ctx context.Context, erc20ContractAddress gethcommon.Address) (*big.Int, error) + SendTransactionBatchFn func(ctx context.Context, currentValset *peggytypes.Valset, batch *peggytypes.OutgoingTxBatch, confirms []*peggytypes.MsgConfirmBatch) (*gethcommon.Hash, error) +} + +func (n MockEthereumNetwork) GetHeaderByNumber(ctx context.Context, number *big.Int) (*gethtypes.Header, error) { + return n.GetHeaderByNumberFn(ctx, number) +} + +func (n MockEthereumNetwork) GetPeggyID(ctx context.Context) (gethcommon.Hash, error) { + return n.GetPeggyIDFn(ctx) +} + +func (n MockEthereumNetwork) GetSendToCosmosEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) { + return n.GetSendToCosmosEventsFn(startBlock, endBlock) +} + +func (n MockEthereumNetwork) GetSendToInjectiveEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) { + return n.GetSendToInjectiveEventsFn(startBlock, endBlock) +} + +func (n MockEthereumNetwork) GetPeggyERC20DeployedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyERC20DeployedEvent, error) { + return n.GetPeggyERC20DeployedEventsFn(startBlock, endBlock) +} - latestTransactionBatchesFn func(context.Context) ([]*peggytypes.OutgoingTxBatch, error) - transactionBatchSignaturesFn func(context.Context, uint64, eth.Address) ([]*peggytypes.MsgConfirmBatch, error) +func (n MockEthereumNetwork) GetValsetUpdatedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyValsetUpdatedEvent, error) { + return n.GetValsetUpdatedEventsFn(startBlock, endBlock) +} + +func (n MockEthereumNetwork) GetTransactionBatchExecutedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) { + return n.GetTransactionBatchExecutedEventsFn(startBlock, endBlock) +} + +func (n MockEthereumNetwork) GetValsetNonce(ctx context.Context) (*big.Int, error) { + return n.GetValsetNonceFn(ctx) +} + +func (n MockEthereumNetwork) SendEthValsetUpdate(ctx context.Context, oldValset *peggytypes.Valset, newValset *peggytypes.Valset, confirms []*peggytypes.MsgValsetConfirm) (*gethcommon.Hash, error) { + return n.SendEthValsetUpdateFn(ctx, oldValset, newValset, confirms) +} + +func (n MockEthereumNetwork) GetTxBatchNonce(ctx context.Context, erc20ContractAddress gethcommon.Address) (*big.Int, error) { + return n.GetTxBatchNonceFn(ctx, erc20ContractAddress) +} + +func (n MockEthereumNetwork) SendTransactionBatch(ctx context.Context, currentValset *peggytypes.Valset, batch *peggytypes.OutgoingTxBatch, confirms []*peggytypes.MsgConfirmBatch) (*gethcommon.Hash, error) { + return n.SendTransactionBatchFn(ctx, currentValset, batch, confirms) +} + +var ( + DummyLog = DummyLogger{} +) + +type DummyLogger struct{} + +func (l DummyLogger) Success(format string, args ...interface{}) { +} + +func (l DummyLogger) Warning(format string, args ...interface{}) { +} + +func (l DummyLogger) Error(format string, args ...interface{}) { +} - hasRegisteredEthAddress func(ctx context.Context, address eth.Address) (bool, error) +func (l DummyLogger) Debug(format string, args ...interface{}) { } -func (i *mockInjective) HasRegisteredEthAddress(ctx context.Context, addr eth.Address) (bool, error) { - return i.hasRegisteredEthAddress(ctx, addr) +func (l DummyLogger) WithField(key string, value interface{}) log.Logger { + return l } -func (i *mockInjective) UnbatchedTokensWithFees(ctx context.Context) ([]*peggytypes.BatchFees, error) { - i.unbatchedTokenFeesCallCount++ - return i.unbatchedTokenFeesFn(ctx) +func (l DummyLogger) WithFields(fields log.Fields) log.Logger { + return l } -func (i *mockInjective) SendRequestBatch(ctx context.Context, denom string) error { - i.sendRequestBatchCallCount++ - return i.sendRequestBatchFn(ctx, denom) +func (l DummyLogger) WithError(err error) log.Logger { + return l } -func (i *mockInjective) PeggyParams(ctx context.Context) (*peggytypes.Params, error) { - return i.peggyParamsFn(ctx) +func (l DummyLogger) WithContext(ctx context.Context) log.Logger { + return l } -func (i *mockInjective) LastClaimEvent(ctx context.Context) (*peggytypes.LastClaimEvent, error) { - return i.lastClaimEventFn(ctx) +func (l DummyLogger) WithTime(t time.Time) log.Logger { + return l } -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, - ) +func (l DummyLogger) Logf(level log.Level, format string, args ...interface{}) { } -func (i *mockInjective) OldestUnsignedValsets(ctx context.Context) ([]*peggytypes.Valset, error) { - return i.oldestUnsignedValsetsFn(ctx) +func (l DummyLogger) Tracef(format string, args ...interface{}) { } -func (i *mockInjective) SendValsetConfirm(ctx context.Context, ethFrom eth.Address, peggyID eth.Hash, valset *peggytypes.Valset) error { - return i.sendValsetConfirmFn(ctx, peggyID, valset, ethFrom) +func (l DummyLogger) Debugf(format string, args ...interface{}) { } -func (i *mockInjective) OldestUnsignedTransactionBatch(ctx context.Context) (*peggytypes.OutgoingTxBatch, error) { - return i.oldestUnsignedTransactionBatchFn(ctx) +func (l DummyLogger) Infof(format string, args ...interface{}) { } -func (i *mockInjective) LatestValsets(ctx context.Context) ([]*peggytypes.Valset, error) { - return i.latestValsetsFn(ctx) +func (l DummyLogger) Printf(format string, args ...interface{}) { } -func (i *mockInjective) AllValsetConfirms(ctx context.Context, nonce uint64) ([]*peggytypes.MsgValsetConfirm, error) { - return i.allValsetConfirmsFn(ctx, nonce) +func (l DummyLogger) Warningf(format string, args ...interface{}) { } -func (i *mockInjective) SendBatchConfirm(ctx context.Context, ethFrom eth.Address, peggyID eth.Hash, batch *peggytypes.OutgoingTxBatch) error { - return i.sendBatchConfirmFn(ctx, peggyID, batch, ethFrom) +func (l DummyLogger) Errorf(format string, args ...interface{}) { } -func (i *mockInjective) ValsetAt(ctx context.Context, nonce uint64) (*peggytypes.Valset, error) { - return i.valsetAtFn(ctx, nonce) +func (l DummyLogger) Fatalf(format string, args ...interface{}) { } -func (i *mockInjective) LatestTransactionBatches(ctx context.Context) ([]*peggytypes.OutgoingTxBatch, error) { - return i.latestTransactionBatchesFn(ctx) +func (l DummyLogger) Panicf(format string, args ...interface{}) { } -func (i *mockInjective) TransactionBatchSignatures(ctx context.Context, nonce uint64, tokenContract eth.Address) ([]*peggytypes.MsgConfirmBatch, error) { - return i.transactionBatchSignaturesFn(ctx, nonce, tokenContract) +func (l DummyLogger) Log(level log.Level, args ...interface{}) { } -func (i *mockInjective) GetBlockCreationTime(ctx context.Context, height int64) (time.Time, error) { - return i.getBlockCreationTimeFn(ctx, height) +func (l DummyLogger) Trace(args ...interface{}) { } -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) - 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) - 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 (l DummyLogger) Info(args ...interface{}) { } -func (e mockEthereum) FromAddress() eth.Address { - return e.fromAddressFn() +func (l DummyLogger) Print(args ...interface{}) { } -func (e mockEthereum) HeaderByNumber(ctx context.Context, number *big.Int) (*ethtypes.Header, error) { - return e.headerByNumberFn(ctx, number) +func (l DummyLogger) Fatal(args ...interface{}) { } -func (e mockEthereum) GetSendToCosmosEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) { - return e.getSendToCosmosEventsFn(startBlock, endBlock) +func (l DummyLogger) Panic(args ...interface{}) { } -func (e mockEthereum) GetSendToInjectiveEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) { - return e.getSendToInjectiveEventsFn(startBlock, endBlock) +func (l DummyLogger) Logln(level log.Level, args ...interface{}) { } -func (e mockEthereum) GetPeggyERC20DeployedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyERC20DeployedEvent, error) { - return e.getPeggyERC20DeployedEventsFn(startBlock, endBlock) +func (l DummyLogger) Traceln(args ...interface{}) { } -func (e mockEthereum) GetValsetUpdatedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyValsetUpdatedEvent, error) { - return e.getValsetUpdatedEventsFn(startBlock, endBlock) +func (l DummyLogger) Debugln(args ...interface{}) { } -func (e mockEthereum) GetTransactionBatchExecutedEvents(startBlock, endBlock uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) { - return e.getTransactionBatchExecutedEventsFn(startBlock, endBlock) +func (l DummyLogger) Infoln(args ...interface{}) { } -func (e mockEthereum) GetPeggyID(ctx context.Context) (eth.Hash, error) { - return e.getPeggyIDFn(ctx) +func (l DummyLogger) Println(args ...interface{}) { } -func (e mockEthereum) GetValsetNonce(ctx context.Context) (*big.Int, error) { - return e.getValsetNonceFn(ctx) +func (l DummyLogger) Warningln(args ...interface{}) { } -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) +func (l DummyLogger) Errorln(args ...interface{}) { } -func (e mockEthereum) GetTxBatchNonce( - ctx context.Context, - erc20ContractAddress eth.Address, -) (*big.Int, error) { - return e.getTxBatchNonceFn(ctx, erc20ContractAddress) +func (l DummyLogger) Fatalln(args ...interface{}) { } -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) +func (l DummyLogger) Panicln(args ...interface{}) { } diff --git a/orchestrator/oracle.go b/orchestrator/oracle.go deleted file mode 100644 index b6b6a108..00000000 --- a/orchestrator/oracle.go +++ /dev/null @@ -1,343 +0,0 @@ -package orchestrator - -import ( - "context" - "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" - peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" -) - -// todo: this is outdated, need to update -// 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 ( - ethBlockConfirmationDelay uint64 = 12 - defaultBlocksToSearch uint64 = 2000 -) - -// 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 { - lastConfirmedEthHeight, err := s.getLastConfirmedEthHeightOnInjective(ctx) - if err != nil { - return err - } - - s.logger.Debugln("last observed Ethereum block", lastConfirmedEthHeight) - - loop := ethOracleLoop{ - PeggyOrchestrator: s, - loopDuration: defaultLoopDur, - lastCheckedEthHeight: lastConfirmedEthHeight, - lastResyncWithInjective: time.Now(), - } - - return loop.Run(ctx) -} - -func (s *PeggyOrchestrator) getLastConfirmedEthHeightOnInjective(ctx context.Context) (uint64, error) { - var lastConfirmedEthHeight uint64 - getLastConfirmedEthHeightFn := func() (err error) { - lastConfirmedEthHeight, err = s.getLastClaimBlockHeight(ctx) - if lastConfirmedEthHeight == 0 { - peggyParams, err := s.inj.PeggyParams(ctx) - if err != nil { - s.logger.WithError(err).Fatalln("unable to query peggy module params, is injectived running?") - return err - } - - lastConfirmedEthHeight = peggyParams.BridgeContractStartHeight - } - return - } - - if err := retry.Do(getLastConfirmedEthHeightFn, - retry.Context(ctx), - retry.Attempts(s.maxAttempts), - retry.OnRetry(func(n uint, err error) { - s.logger.WithError(err).Warningf("failed to get last confirmed Ethereum height on Injective, will retry (%d)", n) - }), - ); err != nil { - s.logger.WithError(err).Errorln("got error, loop exits") - return 0, err - } - - return lastConfirmedEthHeight, nil -} - -func (s *PeggyOrchestrator) getLastClaimBlockHeight(ctx context.Context) (uint64, error) { - metrics.ReportFuncCall(s.svcTags) - doneFn := metrics.ReportFuncTiming(s.svcTags) - defer doneFn() - - claim, err := s.inj.LastClaimEvent(ctx) - if err != nil { - return 0, err - } - - return claim.EthereumEventHeight, nil -} - -type ethOracleLoop struct { - *PeggyOrchestrator - loopDuration time.Duration - lastResyncWithInjective time.Time - lastCheckedEthHeight uint64 -} - -func (l *ethOracleLoop) Logger() log.Logger { - return l.logger.WithField("loop", "EthOracle") -} - -func (l *ethOracleLoop) Run(ctx context.Context) error { - return loops.RunLoop(ctx, l.loopDuration, func() error { - return l.observeEthEvents(ctx) - }) -} - -func (l *ethOracleLoop) observeEthEvents(ctx context.Context) error { - newHeight, err := l.relayEvents(ctx) - if err != nil { - return err - } - - l.Logger().WithFields(log.Fields{"block_start": l.lastCheckedEthHeight, "block_end": newHeight}).Debugln("scanned Ethereum blocks for events") - l.lastCheckedEthHeight = newHeight - - if time.Since(l.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. - 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 err := l.autoResync(ctx); err != nil { - return err - } - } - - return nil -} - -func (l *ethOracleLoop) relayEvents(ctx context.Context) (uint64, error) { - var ( - currentHeight = l.lastCheckedEthHeight - latestHeight uint64 - ) - - scanEthBlocksAndRelayEventsFn := func() error { - metrics.ReportFuncCall(l.svcTags) - doneFn := metrics.ReportFuncTiming(l.svcTags) - defer doneFn() - - latestHeader, err := l.eth.HeaderByNumber(ctx, nil) - if err != nil { - return errors.Wrap(err, "failed to get latest ethereum header") - } - - // add delay to ensure minimum confirmations are received and block is finalised - latestHeight = latestHeader.Number.Uint64() - ethBlockConfirmationDelay - if latestHeight < currentHeight { - return nil - } - - if latestHeight > currentHeight+defaultBlocksToSearch { - latestHeight = currentHeight + defaultBlocksToSearch - } - - legacyDeposits, err := l.eth.GetSendToCosmosEvents(currentHeight, latestHeight) - if err != nil { - return errors.Wrap(err, "failed to get SendToCosmos events") - } - - deposits, err := l.eth.GetSendToInjectiveEvents(currentHeight, latestHeight) - if err != nil { - return errors.Wrap(err, "failed to get SendToInjective events") - } - - withdrawals, err := l.eth.GetTransactionBatchExecutedEvents(currentHeight, latestHeight) - if err != nil { - return errors.Wrap(err, "failed to get TransactionBatchExecuted events") - } - - erc20Deployments, err := l.eth.GetPeggyERC20DeployedEvents(currentHeight, latestHeight) - if err != nil { - return errors.Wrap(err, "failed to get ERC20Deployed events") - } - - valsetUpdates, err := l.eth.GetValsetUpdatedEvents(currentHeight, latestHeight) - if err != nil { - return errors.Wrap(err, "failed to get ValsetUpdated 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 := l.inj.LastClaimEvent(ctx) - if err != nil { - return errors.Wrap(err, "failed to query last claim event from Injective") - } - - l.Logger().WithFields(log.Fields{ - "event_nonce": lastClaimEvent.EthereumEventNonce, - "event_height": lastClaimEvent.EthereumEventHeight, - }).Debugln("last Ethereum claim event on Injective") - - 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 noEvents := len(legacyDeposits) == 0 && len(deposits) == 0 && len(withdrawals) == 0 && - len(erc20Deployments) == 0 && len(valsetUpdates) == 0; noEvents { - l.Logger().Debugln("no new events on Ethereum") - return nil - } - - if err := l.inj.SendEthereumClaims(ctx, - lastClaimEvent.EthereumEventNonce, - legacyDeposits, - deposits, - withdrawals, - erc20Deployments, - valsetUpdates, - ); err != nil { - return errors.Wrap(err, "failed to send event claims to Injective") - } - - l.Logger().WithFields(log.Fields{ - "legacy_deposits": len(legacyDeposits), - "deposits": len(deposits), - "withdrawals": len(withdrawals), - "erc20_deployments": len(erc20Deployments), - "valset_updates": len(valsetUpdates), - }).Infoln("sent new claims to Injective") - - return nil - } - - if err := retry.Do(scanEthBlocksAndRelayEventsFn, - retry.Context(ctx), - retry.Attempts(l.maxAttempts), - retry.OnRetry(func(n uint, err error) { - l.Logger().WithError(err).Warningf("error during Ethereum event checking, will retry (%d)", n) - }), - ); err != nil { - l.Logger().WithError(err).Errorln("got error, loop exits") - return 0, err - } - - return latestHeight, nil -} - -func (l *ethOracleLoop) autoResync(ctx context.Context) error { - var latestHeight uint64 - getLastClaimEventFn := func() (err error) { - latestHeight, err = l.getLastClaimBlockHeight(ctx) - return - } - - if err := retry.Do(getLastClaimEventFn, - retry.Context(ctx), - retry.Attempts(l.maxAttempts), - retry.OnRetry(func(n uint, err error) { - l.Logger().WithError(err).Warningf("failed to get last confirmed eth height, will retry (%d)", n) - }), - ); err != nil { - l.Logger().WithError(err).Errorln("got error, loop exits") - return err - } - - l.lastCheckedEthHeight = latestHeight - l.lastResyncWithInjective = time.Now() - - l.Logger().WithFields(log.Fields{"last_resync_time": l.lastResyncWithInjective.String(), "last_confirmed_eth_height": l.lastCheckedEthHeight}).Infoln("auto resync event nonce with Injective") - - return nil -} - -func filterSendToCosmosEventsByNonce( - events []*peggyevents.PeggySendToCosmosEvent, - nonce uint64, -) []*peggyevents.PeggySendToCosmosEvent { - res := make([]*peggyevents.PeggySendToCosmosEvent, 0, len(events)) - - for _, ev := range events { - if ev.EventNonce.Uint64() > nonce { - res = append(res, ev) - } - } - - return res -} - -func filterSendToInjectiveEventsByNonce( - events []*peggyevents.PeggySendToInjectiveEvent, - nonce uint64, -) []*peggyevents.PeggySendToInjectiveEvent { - res := make([]*peggyevents.PeggySendToInjectiveEvent, 0, len(events)) - - for _, ev := range events { - if ev.EventNonce.Uint64() > nonce { - res = append(res, ev) - } - } - - return res -} - -func filterTransactionBatchExecutedEventsByNonce( - events []*peggyevents.PeggyTransactionBatchExecutedEvent, - nonce uint64, -) []*peggyevents.PeggyTransactionBatchExecutedEvent { - res := make([]*peggyevents.PeggyTransactionBatchExecutedEvent, 0, len(events)) - - for _, ev := range events { - if ev.EventNonce.Uint64() > nonce { - res = append(res, ev) - } - } - - return res -} - -func filterERC20DeployedEventsByNonce( - events []*peggyevents.PeggyERC20DeployedEvent, - nonce uint64, -) []*peggyevents.PeggyERC20DeployedEvent { - res := make([]*peggyevents.PeggyERC20DeployedEvent, 0, len(events)) - - for _, ev := range events { - if ev.EventNonce.Uint64() > nonce { - res = append(res, ev) - } - } - - return res -} - -func filterValsetUpdateEventsByNonce( - events []*peggyevents.PeggyValsetUpdatedEvent, - nonce uint64, -) []*peggyevents.PeggyValsetUpdatedEvent { - res := make([]*peggyevents.PeggyValsetUpdatedEvent, 0, len(events)) - - for _, ev := range events { - if ev.EventNonce.Uint64() > nonce { - res = append(res, ev) - } - } - return res -} diff --git a/orchestrator/oracle_test.go b/orchestrator/oracle_test.go deleted file mode 100644 index 2d24fa87..00000000 --- a/orchestrator/oracle_test.go +++ /dev/null @@ -1,292 +0,0 @@ -package orchestrator - -import ( - "context" - "errors" - "math/big" - "testing" - "time" - - "github.com/ethereum/go-ethereum/core/types" - "github.com/stretchr/testify/assert" - "github.com/xlab/suplog" - - peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" - peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" -) - -func TestEthOracle(t *testing.T) { - t.Parallel() - - t.Run("failed to get latest header from ethereum", func(t *testing.T) { - t.Parallel() - - orch := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - eth: mockEthereum{ - headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { - return nil, errors.New("fail") - }, - }, - } - - assert.Error(t, orch.EthOracleMainLoop(context.TODO())) - }) - - t.Run("latest ethereum header is old", func(t *testing.T) { - t.Parallel() - - ethereum := mockEthereum{ - headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { - return &types.Header{Number: big.NewInt(50)}, nil - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - eth: ethereum, - maxAttempts: 1, - } - - loop := ethOracleLoop{ - PeggyOrchestrator: o, - lastResyncWithInjective: time.Now(), - lastCheckedEthHeight: 100, - } - - assert.NoError(t, loop.observeEthEvents(context.TODO())) - assert.Equal(t, loop.lastCheckedEthHeight, uint64(100)) - }) - - t.Run("failed to get SendToCosmos events", func(t *testing.T) { - t.Parallel() - - ethereum := mockEthereum{ - headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { - return &types.Header{Number: big.NewInt(200)}, nil - }, - getSendToCosmosEventsFn: func(uint64, uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) { - return nil, errors.New("fail") - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - eth: ethereum, - maxAttempts: 1, - } - - loop := ethOracleLoop{ - PeggyOrchestrator: o, - lastResyncWithInjective: time.Now(), - lastCheckedEthHeight: 100, - } - - assert.Error(t, loop.observeEthEvents(context.TODO())) - assert.Equal(t, loop.lastCheckedEthHeight, uint64(100)) - }) - - t.Run("failed to get last claim event from injective", func(t *testing.T) { - t.Parallel() - - ethereum := mockEthereum{ - headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { - return &types.Header{Number: big.NewInt(200)}, nil - }, - - // no-ops - getSendToCosmosEventsFn: func(uint64, uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) { - return nil, nil - }, - getTransactionBatchExecutedEventsFn: func(uint64, uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) { - return nil, nil - }, - getValsetUpdatedEventsFn: func(uint64, uint64) ([]*peggyevents.PeggyValsetUpdatedEvent, error) { - return nil, nil - }, - getPeggyERC20DeployedEventsFn: func(uint64, uint64) ([]*peggyevents.PeggyERC20DeployedEvent, error) { - return nil, nil - }, - getSendToInjectiveEventsFn: func(uint64, uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) { - return nil, nil - }, - } - - injective := &mockInjective{ - lastClaimEventFn: func(context.Context) (*peggytypes.LastClaimEvent, error) { - return nil, errors.New("fail") - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - eth: ethereum, - inj: injective, - maxAttempts: 1, - } - - loop := ethOracleLoop{ - PeggyOrchestrator: o, - lastResyncWithInjective: time.Now(), - lastCheckedEthHeight: 100, - } - - assert.Error(t, loop.observeEthEvents(context.TODO())) - assert.Equal(t, loop.lastCheckedEthHeight, uint64(100)) - }) - - 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, - []*peggyevents.PeggySendToCosmosEvent, - []*peggyevents.PeggySendToInjectiveEvent, - []*peggyevents.PeggyTransactionBatchExecutedEvent, - []*peggyevents.PeggyERC20DeployedEvent, - []*peggyevents.PeggyValsetUpdatedEvent, - ) error { - return nil - }, - } - - eth := mockEthereum{ - headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { - return &types.Header{Number: big.NewInt(200)}, nil - }, - getSendToCosmosEventsFn: func(uint64, uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) { - return []*peggyevents.PeggySendToCosmosEvent{{EventNonce: big.NewInt(5)}}, nil - }, - - // no-ops - getTransactionBatchExecutedEventsFn: func(uint64, uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) { - return nil, nil - }, - getValsetUpdatedEventsFn: func(uint64, uint64) ([]*peggyevents.PeggyValsetUpdatedEvent, error) { - return nil, nil - }, - getPeggyERC20DeployedEventsFn: func(uint64, uint64) ([]*peggyevents.PeggyERC20DeployedEvent, error) { - return nil, nil - }, - getSendToInjectiveEventsFn: func(uint64, uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) { - return nil, nil - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - eth: eth, - inj: inj, - maxAttempts: 1, - } - - loop := ethOracleLoop{ - PeggyOrchestrator: o, - lastResyncWithInjective: time.Now(), - lastCheckedEthHeight: 100, - } - - assert.NoError(t, loop.observeEthEvents(context.TODO())) - assert.Equal(t, loop.lastCheckedEthHeight, uint64(104)) - 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, - []*peggyevents.PeggySendToCosmosEvent, - []*peggyevents.PeggySendToInjectiveEvent, - []*peggyevents.PeggyTransactionBatchExecutedEvent, - []*peggyevents.PeggyERC20DeployedEvent, - []*peggyevents.PeggyValsetUpdatedEvent, - ) error { - return nil - }, - } - - eth := mockEthereum{ - headerByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { - return &types.Header{Number: big.NewInt(200)}, nil - }, - getSendToCosmosEventsFn: func(uint64, uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) { - return []*peggyevents.PeggySendToCosmosEvent{{EventNonce: big.NewInt(10)}}, nil - }, - - // no-ops - getTransactionBatchExecutedEventsFn: func(uint64, uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) { - return nil, nil - }, - getValsetUpdatedEventsFn: func(uint64, uint64) ([]*peggyevents.PeggyValsetUpdatedEvent, error) { - return nil, nil - }, - getPeggyERC20DeployedEventsFn: func(uint64, uint64) ([]*peggyevents.PeggyERC20DeployedEvent, error) { - return nil, nil - }, - getSendToInjectiveEventsFn: func(uint64, uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) { - return nil, nil - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - eth: eth, - inj: inj, - maxAttempts: 1, - } - - loop := ethOracleLoop{ - PeggyOrchestrator: o, - lastResyncWithInjective: time.Now(), - lastCheckedEthHeight: 100, - } - - assert.NoError(t, loop.observeEthEvents(context.TODO())) - assert.Equal(t, loop.lastCheckedEthHeight, uint64(104)) - 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 := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - eth: eth, - inj: inj, - maxAttempts: 1, - } - - loop := ethOracleLoop{ - PeggyOrchestrator: o, - lastResyncWithInjective: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), - lastCheckedEthHeight: 100, - } - - assert.NoError(t, loop.observeEthEvents(context.TODO())) - assert.Equal(t, loop.lastCheckedEthHeight, uint64(101)) - assert.True(t, time.Since(loop.lastResyncWithInjective) < 1*time.Second) - }) -} diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go deleted file mode 100644 index 0b0582ae..00000000 --- a/orchestrator/orchestrator.go +++ /dev/null @@ -1,137 +0,0 @@ -package orchestrator - -import ( - "context" - "time" - - eth "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" - log "github.com/xlab/suplog" - - "github.com/InjectiveLabs/metrics" - "github.com/InjectiveLabs/peggo/orchestrator/loops" -) - -const defaultLoopDur = 60 * time.Second - -type PeggyOrchestrator struct { - logger log.Logger - svcTags metrics.Tags - - inj InjectiveNetwork - eth 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 - - valsetRelayEnabled bool - batchRelayEnabled bool - periodicBatchRequesting bool -} - -func NewPeggyOrchestrator( - injective InjectiveNetwork, - ethereum EthereumNetwork, - priceFeed PriceFeed, - erc20ContractMapping map[eth.Address]string, - minBatchFeeUSD float64, - valsetRelayingEnabled, - batchRelayingEnabled bool, - valsetRelayingOffset, - batchRelayingOffset string, -) (*PeggyOrchestrator, error) { - o := &PeggyOrchestrator{ - logger: log.DefaultLogger, - svcTags: metrics.Tags{"svc": "peggy_orchestrator"}, - inj: injective, - eth: ethereum, - pricefeed: priceFeed, - erc20ContractMapping: erc20ContractMapping, - minBatchFeeUSD: minBatchFeeUSD, - valsetRelayEnabled: valsetRelayingEnabled, - batchRelayEnabled: batchRelayingEnabled, - maxAttempts: 10, // default is 10 for retry pkg - } - - if valsetRelayingEnabled { - dur, err := time.ParseDuration(valsetRelayingOffset) - if err != nil { - return nil, errors.Wrapf(err, "valset relaying enabled but offset duration is not properly set") - } - - o.relayValsetOffsetDur = dur - } - - if batchRelayingEnabled { - dur, err := time.ParseDuration(batchRelayingOffset) - if err != nil { - return nil, errors.Wrapf(err, "batch relaying enabled but offset duration is not properly set") - } - - o.relayBatchOffsetDur = dur - } - - return o, nil -} - -// Run starts all major loops required to make -// up the Orchestrator, all of these are async loops. -func (s *PeggyOrchestrator) Run(ctx context.Context) error { - if !s.hasRegisteredETHAddress(ctx) { - return s.startRelayerMode(ctx) - } - - return s.startValidatorMode(ctx) -} - -func (s *PeggyOrchestrator) hasRegisteredETHAddress(ctx context.Context) bool { - subCtx, cancelFn := context.WithTimeout(ctx, 5*time.Second) - defer cancelFn() - - ok, _ := s.inj.HasRegisteredEthAddress(subCtx, s.eth.FromAddress()) - return ok -} - -// 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.WithFields(log.Fields{ - "batch_requesting": true, - "eth_event_tracking": true, - "batch_signing": true, - "valset_signing": true, - "valset_relaying": s.valsetRelayEnabled, - "batch_relaying": s.batchRelayEnabled, - }).Infoln("running in validator mode") - - 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 { - log.WithFields(log.Fields{ - "batch_requesting": true, - "valset_relaying": s.valsetRelayEnabled, - "batch_relaying": s.batchRelayEnabled, - }).Infoln("running in relayer mode") - - 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/peggy_orchestrator.go b/orchestrator/peggy_orchestrator.go new file mode 100644 index 00000000..2562fa24 --- /dev/null +++ b/orchestrator/peggy_orchestrator.go @@ -0,0 +1,173 @@ +package orchestrator + +import ( + "context" + "time" + + "github.com/avast/retry-go" + cosmostypes "github.com/cosmos/cosmos-sdk/types" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + log "github.com/xlab/suplog" + + "github.com/InjectiveLabs/metrics" + + "github.com/InjectiveLabs/peggo/orchestrator/cosmos" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum" + "github.com/InjectiveLabs/peggo/orchestrator/loops" +) + +const ( + defaultLoopDur = 60 * time.Second +) + +var ( + maxRetryAttempts uint = 10 +) + +// PriceFeed provides token price for a given contract address +type PriceFeed interface { + QueryUSDPrice(address gethcommon.Address) (float64, error) +} + +type Config struct { + MinBatchFeeUSD float64 + ERC20ContractMapping map[gethcommon.Address]string + RelayValsetOffsetDur string + RelayBatchOffsetDur string + RelayValsets bool + RelayBatches bool + RelayerMode bool +} + +type Orchestrator struct { + logger log.Logger + svcTags metrics.Tags + + injAddr cosmostypes.AccAddress + ethAddr gethcommon.Address + + priceFeed PriceFeed + erc20ContractMapping map[gethcommon.Address]string + relayValsetOffsetDur time.Duration + relayBatchOffsetDur time.Duration + minBatchFeeUSD float64 + isRelayer bool +} + +func NewPeggyOrchestrator( + orchestratorAddr cosmostypes.AccAddress, + ethAddr gethcommon.Address, + priceFeed PriceFeed, + cfg Config, +) (*Orchestrator, error) { + o := &Orchestrator{ + logger: log.DefaultLogger, + svcTags: metrics.Tags{"svc": "peggy_orchestrator"}, + injAddr: orchestratorAddr, + ethAddr: ethAddr, + priceFeed: priceFeed, + erc20ContractMapping: cfg.ERC20ContractMapping, + minBatchFeeUSD: cfg.MinBatchFeeUSD, + isRelayer: cfg.RelayerMode, + } + + if cfg.RelayValsets { + dur, err := time.ParseDuration(cfg.RelayValsetOffsetDur) + if err != nil { + return nil, errors.Wrapf(err, "valset relaying enabled but offset duration is not properly set") + } + + o.relayValsetOffsetDur = dur + } + + if cfg.RelayBatches { + dur, err := time.ParseDuration(cfg.RelayBatchOffsetDur) + if err != nil { + return nil, errors.Wrapf(err, "batch relaying enabled but offset duration is not properly set") + } + + o.relayBatchOffsetDur = dur + } + + return o, nil +} + +// Run starts all major loops required to make +// up the Orchestrator, all of these are async loops. +func (s *Orchestrator) Run(ctx context.Context, inj cosmos.Network, eth ethereum.Network) error { + if s.isRelayer { + return s.startRelayerMode(ctx, inj, eth) + } + + return s.startValidatorMode(ctx, inj, eth) +} + +// startValidatorMode runs all orchestrator processes. This is called +// when peggo is run alongside a validator injective node. +func (s *Orchestrator) startValidatorMode(ctx context.Context, inj cosmos.Network, eth ethereum.Network) error { + log.Infoln("running orchestrator in validator mode") + + // get gethcommon block observed by this validator + lastObservedEthBlock, _ := s.getLastClaimBlockHeight(ctx, inj) + if lastObservedEthBlock == 0 { + peggyParams, err := inj.PeggyParams(ctx) + if err != nil { + s.logger.WithError(err).Fatalln("unable to query peggy module params, is injectived running?") + } + + lastObservedEthBlock = peggyParams.BridgeContractStartHeight + } + + // get peggy ID from contract + peggyContractID, err := eth.GetPeggyID(ctx) + if err != nil { + s.logger.WithError(err).Fatalln("unable to query peggy ID from contract") + } + + var pg loops.ParanoidGroup + + pg.Go(func() error { return s.runEthOracle(ctx, inj, eth, lastObservedEthBlock) }) + pg.Go(func() error { return s.runEthSigner(ctx, inj, peggyContractID) }) + pg.Go(func() error { return s.runBatchRequester(ctx, inj, eth) }) + pg.Go(func() error { return s.runRelayer(ctx, inj, eth) }) + + 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 *Orchestrator) startRelayerMode(ctx context.Context, inj cosmos.Network, eth ethereum.Network) error { + log.Infoln("running orchestrator in relayer mode") + + var pg loops.ParanoidGroup + + pg.Go(func() error { return s.runBatchRequester(ctx, inj, eth) }) + pg.Go(func() error { return s.runRelayer(ctx, inj, eth) }) + + return pg.Wait() +} + +func (s *Orchestrator) getLastClaimBlockHeight(ctx context.Context, inj cosmos.Network) (uint64, error) { + metrics.ReportFuncCall(s.svcTags) + doneFn := metrics.ReportFuncTiming(s.svcTags) + defer doneFn() + + claim, err := inj.LastClaimEventByAddr(ctx, s.injAddr) + if err != nil { + return 0, err + } + + return claim.EthereumEventHeight, nil +} + +func retryFnOnErr(ctx context.Context, log log.Logger, fn func() error) error { + return retry.Do(fn, + retry.Context(ctx), + retry.Attempts(maxRetryAttempts), + retry.OnRetry(func(n uint, err error) { + log.WithError(err).Warningf("encountered error, retrying (%d)", n) + }), + ) +} diff --git a/orchestrator/peggy_orchestrator_test.go b/orchestrator/peggy_orchestrator_test.go new file mode 100644 index 00000000..5320af6e --- /dev/null +++ b/orchestrator/peggy_orchestrator_test.go @@ -0,0 +1,1230 @@ +package orchestrator + +import ( + "context" + "errors" + "github.com/InjectiveLabs/metrics" + "github.com/InjectiveLabs/peggo/orchestrator/cosmos" + "github.com/InjectiveLabs/peggo/orchestrator/ethereum" + peggyevents "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" + peggytypes "github.com/InjectiveLabs/sdk-go/chain/peggy/types" + comettypes "github.com/cometbft/cometbft/rpc/core/types" + comet "github.com/cometbft/cometbft/types" + cosmtypes "github.com/cosmos/cosmos-sdk/types" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/assert" + "math/big" + "testing" + "time" +) + +func Test_Orchestrator_Loops(t *testing.T) { + t.Parallel() + + // faster test runs + maxRetryAttempts = 1 + + t.Run("batch requester", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + expected error + orch *Orchestrator + inj cosmos.Network + eth ethereum.Network + }{ + { + name: "failed to get token fees", + expected: nil, + orch: &Orchestrator{logger: DummyLog}, + inj: MockCosmosNetwork{ + UnbatchedTokensWithFeesFn: func(_ context.Context) ([]*peggytypes.BatchFees, error) { + return nil, errors.New("oops") + }, + }, + }, + + { + name: "no unbatched tokens", + expected: nil, + orch: &Orchestrator{logger: DummyLog}, + inj: MockCosmosNetwork{ + UnbatchedTokensWithFeesFn: func(context.Context) ([]*peggytypes.BatchFees, error) { + return nil, nil + }, + }, + }, + + { + name: "batch does not meet fee threshold", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + priceFeed: MockPriceFeed{QueryUSDPriceFn: func(_ gethcommon.Address) (float64, error) { return 1, nil }}, + minBatchFeeUSD: 51.0, + erc20ContractMapping: map[gethcommon.Address]string{ + gethcommon.HexToAddress("0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30"): "inj", + }, + }, + inj: MockCosmosNetwork{ + SendRequestBatchFn: func(context.Context, string) error { return nil }, + UnbatchedTokensWithFeesFn: func(context.Context) ([]*peggytypes.BatchFees, error) { + fees, _ := cosmtypes.NewIntFromString("50000000000000000000") + return []*peggytypes.BatchFees{ + { + Token: gethcommon.HexToAddress("0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30").String(), + TotalFees: fees, + }, + }, nil + }, + }, + }, + + { + name: "batch meets threshold and a request is sent", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + priceFeed: MockPriceFeed{QueryUSDPriceFn: func(_ gethcommon.Address) (float64, error) { return 1, nil }}, + minBatchFeeUSD: 49.0, + erc20ContractMapping: map[gethcommon.Address]string{ + gethcommon.HexToAddress("0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30"): "inj", + }, + }, + inj: MockCosmosNetwork{ + SendRequestBatchFn: func(context.Context, string) error { return nil }, + UnbatchedTokensWithFeesFn: func(_ context.Context) ([]*peggytypes.BatchFees, error) { + fees, _ := cosmtypes.NewIntFromString("50000000000000000000") + return []*peggytypes.BatchFees{{ + Token: gethcommon.HexToAddress("0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30").String(), + TotalFees: fees, + }}, nil + }, + }, + }, + } + + for _, tt := range testTable { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + r := batchRequester{ + Orchestrator: tt.orch, + Injective: tt.inj, + } + + assert.ErrorIs(t, r.RequestBatches(context.Background()), tt.expected) + }) + } + }) + + t.Run("oracle", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + expected error + orch *Orchestrator + inj cosmos.Network + eth ethereum.Network + lastResyncWithInjective time.Time + lastObservedEthHeight uint64 + }{ + { + name: "failed to get current valset", + expected: errors.New("oops"), + orch: &Orchestrator{ + logger: DummyLog, + }, + inj: MockCosmosNetwork{ + CurrentValsetFn: func(_ context.Context) (*peggytypes.Valset, error) { + return nil, errors.New("oops") + }, + }, + }, + + { + name: "orchestrator not bonded", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + ethAddr: gethcommon.HexToAddress("0x76D2dDbb89C36FA39FAa5c5e7C61ee95AC4D76C4"), + }, + inj: MockCosmosNetwork{ + CurrentValsetFn: func(_ context.Context) (*peggytypes.Valset, error) { + return &peggytypes.Valset{ + Members: []*peggytypes.BridgeValidator{ + { + EthereumAddress: "0x3959f5246c452463279F690301D923D5a75bbD88", + }, + }, + }, nil + }, + }, + }, + + { + name: "failed to get latest eth height", + expected: errors.New("oops"), + orch: &Orchestrator{ + logger: DummyLog, + ethAddr: gethcommon.HexToAddress("0x3959f5246c452463279F690301D923D5a75bbD88"), + }, + inj: MockCosmosNetwork{ + CurrentValsetFn: func(_ context.Context) (*peggytypes.Valset, error) { + return &peggytypes.Valset{ + Members: []*peggytypes.BridgeValidator{ + { + EthereumAddress: "0x3959f5246c452463279F690301D923D5a75bbD88", + }, + }, + }, nil + }, + }, + eth: MockEthereumNetwork{ + GetHeaderByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return nil, errors.New("fail") + }, + }, + }, + + { + name: "not enough block on ethereum", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + ethAddr: gethcommon.HexToAddress("0x3959f5246c452463279F690301D923D5a75bbD88"), + }, + inj: MockCosmosNetwork{ + CurrentValsetFn: func(_ context.Context) (*peggytypes.Valset, error) { + return &peggytypes.Valset{ + Members: []*peggytypes.BridgeValidator{ + { + EthereumAddress: "0x3959f5246c452463279F690301D923D5a75bbD88", + }, + }, + }, nil + }, + }, + eth: MockEthereumNetwork{ + GetHeaderByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return &types.Header{Number: big.NewInt(10)}, nil // minimum is 12 + }, + }, + }, + + { + name: "failed to get ethereum events", + expected: errors.New("oops"), + orch: &Orchestrator{ + logger: DummyLog, + ethAddr: gethcommon.HexToAddress("0x3959f5246c452463279F690301D923D5a75bbD88"), + }, + inj: MockCosmosNetwork{ + CurrentValsetFn: func(_ context.Context) (*peggytypes.Valset, error) { + return &peggytypes.Valset{ + Members: []*peggytypes.BridgeValidator{ + { + EthereumAddress: "0x3959f5246c452463279F690301D923D5a75bbD88", + }, + }, + }, nil + }, + }, + eth: MockEthereumNetwork{ + GetHeaderByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return &types.Header{Number: big.NewInt(2100)}, nil + }, + GetSendToCosmosEventsFn: func(_, _ uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) { + return nil, errors.New("oops") + }, + }, + lastObservedEthHeight: 100, + }, + + { + name: "failed to get last claim event", + expected: errors.New("oops"), + orch: &Orchestrator{ + logger: DummyLog, + ethAddr: gethcommon.HexToAddress("0x3959f5246c452463279F690301D923D5a75bbD88"), + }, + inj: MockCosmosNetwork{ + CurrentValsetFn: func(_ context.Context) (*peggytypes.Valset, error) { + return &peggytypes.Valset{ + Members: []*peggytypes.BridgeValidator{ + { + EthereumAddress: "0x3959f5246c452463279F690301D923D5a75bbD88", + }, + }, + }, nil + }, + + LastClaimEventByAddrFn: func(_ context.Context, _ cosmtypes.AccAddress) (*peggytypes.LastClaimEvent, error) { + return nil, errors.New("oops") + }, + }, + eth: MockEthereumNetwork{ + GetHeaderByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return &types.Header{Number: big.NewInt(2100)}, nil + }, + GetSendToCosmosEventsFn: func(_, _ uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) { + return []*peggyevents.PeggySendToCosmosEvent{ + { + EventNonce: big.NewInt(100), + }, + }, nil + }, + + GetValsetUpdatedEventsFn: func(_, _ uint64) ([]*peggyevents.PeggyValsetUpdatedEvent, error) { + return nil, nil + }, + GetSendToInjectiveEventsFn: func(_, _ uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) { + return nil, nil + }, + GetTransactionBatchExecutedEventsFn: func(_, _ uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) { + return nil, nil + }, + GetPeggyERC20DeployedEventsFn: func(_, _ uint64) ([]*peggyevents.PeggyERC20DeployedEvent, error) { + return nil, nil + }, + }, + lastObservedEthHeight: 100, + }, + + { + name: "no new events", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + ethAddr: gethcommon.HexToAddress("0x3959f5246c452463279F690301D923D5a75bbD88"), + }, + inj: MockCosmosNetwork{ + CurrentValsetFn: func(_ context.Context) (*peggytypes.Valset, error) { + return &peggytypes.Valset{ + Members: []*peggytypes.BridgeValidator{ + { + EthereumAddress: "0x3959f5246c452463279F690301D923D5a75bbD88", + }, + }, + }, nil + }, + + LastClaimEventByAddrFn: func(_ context.Context, _ cosmtypes.AccAddress) (*peggytypes.LastClaimEvent, error) { + return &peggytypes.LastClaimEvent{ + EthereumEventNonce: 101, + }, nil + }, + }, + eth: MockEthereumNetwork{ + GetHeaderByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return &types.Header{Number: big.NewInt(2100)}, nil + }, + GetSendToCosmosEventsFn: func(_, _ uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) { + return []*peggyevents.PeggySendToCosmosEvent{ + { + EventNonce: big.NewInt(100), + }, + }, nil + }, + + GetValsetUpdatedEventsFn: func(_, _ uint64) ([]*peggyevents.PeggyValsetUpdatedEvent, error) { + return nil, nil + }, + GetSendToInjectiveEventsFn: func(_, _ uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) { + return nil, nil + }, + GetTransactionBatchExecutedEventsFn: func(_, _ uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) { + return nil, nil + }, + GetPeggyERC20DeployedEventsFn: func(_, _ uint64) ([]*peggyevents.PeggyERC20DeployedEvent, error) { + return nil, nil + }, + }, + lastObservedEthHeight: 100, + }, + + { + name: "missed events triggers resync", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + ethAddr: gethcommon.HexToAddress("0x3959f5246c452463279F690301D923D5a75bbD88"), + }, + inj: MockCosmosNetwork{ + CurrentValsetFn: func(_ context.Context) (*peggytypes.Valset, error) { + return &peggytypes.Valset{ + Members: []*peggytypes.BridgeValidator{ + { + EthereumAddress: "0x3959f5246c452463279F690301D923D5a75bbD88", + }, + }, + }, nil + }, + + LastClaimEventByAddrFn: func(_ context.Context, _ cosmtypes.AccAddress) (*peggytypes.LastClaimEvent, error) { + return &peggytypes.LastClaimEvent{ + EthereumEventNonce: 102, + EthereumEventHeight: 1000, + }, nil + }, + }, + eth: MockEthereumNetwork{ + GetHeaderByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return &types.Header{Number: big.NewInt(2100)}, nil + }, + GetSendToCosmosEventsFn: func(_, _ uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) { + return []*peggyevents.PeggySendToCosmosEvent{ + { + EventNonce: big.NewInt(104), + }, + }, nil + }, + + GetValsetUpdatedEventsFn: func(_, _ uint64) ([]*peggyevents.PeggyValsetUpdatedEvent, error) { + return nil, nil + }, + GetSendToInjectiveEventsFn: func(_, _ uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) { + return nil, nil + }, + GetTransactionBatchExecutedEventsFn: func(_, _ uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) { + return nil, nil + }, + GetPeggyERC20DeployedEventsFn: func(_, _ uint64) ([]*peggyevents.PeggyERC20DeployedEvent, error) { + return nil, nil + }, + }, + lastObservedEthHeight: 100, + }, + + { + name: "sent new event claim", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + ethAddr: gethcommon.HexToAddress("0x3959f5246c452463279F690301D923D5a75bbD88"), + }, + inj: MockCosmosNetwork{ + CurrentValsetFn: func(_ context.Context) (*peggytypes.Valset, error) { + return &peggytypes.Valset{ + Members: []*peggytypes.BridgeValidator{ + { + EthereumAddress: "0x3959f5246c452463279F690301D923D5a75bbD88", + }, + }, + }, nil + }, + + LastClaimEventByAddrFn: func(_ context.Context, _ cosmtypes.AccAddress) (*peggytypes.LastClaimEvent, error) { + return &peggytypes.LastClaimEvent{ + EthereumEventNonce: 102, + EthereumEventHeight: 1000, + }, nil + }, + + SendOldDepositClaimFn: func(_ context.Context, _ *peggyevents.PeggySendToCosmosEvent) error { + return nil + }, + }, + eth: MockEthereumNetwork{ + GetHeaderByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return &types.Header{Number: big.NewInt(2100)}, nil + }, + GetSendToCosmosEventsFn: func(_, _ uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) { + return []*peggyevents.PeggySendToCosmosEvent{ + { + EventNonce: big.NewInt(103), + }, + }, nil + }, + + GetValsetUpdatedEventsFn: func(_, _ uint64) ([]*peggyevents.PeggyValsetUpdatedEvent, error) { + return nil, nil + }, + GetSendToInjectiveEventsFn: func(_, _ uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) { + return nil, nil + }, + GetTransactionBatchExecutedEventsFn: func(_, _ uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) { + return nil, nil + }, + GetPeggyERC20DeployedEventsFn: func(_, _ uint64) ([]*peggyevents.PeggyERC20DeployedEvent, error) { + return nil, nil + }, + }, + lastObservedEthHeight: 100, + lastResyncWithInjective: time.Now(), // skip auto resync + }, + + { + name: "auto resync", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + ethAddr: gethcommon.HexToAddress("0x3959f5246c452463279F690301D923D5a75bbD88"), + }, + inj: MockCosmosNetwork{ + CurrentValsetFn: func(_ context.Context) (*peggytypes.Valset, error) { + return &peggytypes.Valset{ + Members: []*peggytypes.BridgeValidator{ + { + EthereumAddress: "0x3959f5246c452463279F690301D923D5a75bbD88", + }, + }, + }, nil + }, + + LastClaimEventByAddrFn: func(_ context.Context, _ cosmtypes.AccAddress) (*peggytypes.LastClaimEvent, error) { + return &peggytypes.LastClaimEvent{ + EthereumEventNonce: 102, + EthereumEventHeight: 1000, + }, nil + }, + + SendOldDepositClaimFn: func(_ context.Context, _ *peggyevents.PeggySendToCosmosEvent) error { + return nil + }, + }, + eth: MockEthereumNetwork{ + GetHeaderByNumberFn: func(context.Context, *big.Int) (*types.Header, error) { + return &types.Header{Number: big.NewInt(2100)}, nil + }, + GetSendToCosmosEventsFn: func(_, _ uint64) ([]*peggyevents.PeggySendToCosmosEvent, error) { + return []*peggyevents.PeggySendToCosmosEvent{ + { + EventNonce: big.NewInt(103), + }, + }, nil + }, + + GetValsetUpdatedEventsFn: func(_, _ uint64) ([]*peggyevents.PeggyValsetUpdatedEvent, error) { + return nil, nil + }, + GetSendToInjectiveEventsFn: func(_, _ uint64) ([]*peggyevents.PeggySendToInjectiveEvent, error) { + return nil, nil + }, + GetTransactionBatchExecutedEventsFn: func(_, _ uint64) ([]*peggyevents.PeggyTransactionBatchExecutedEvent, error) { + return nil, nil + }, + GetPeggyERC20DeployedEventsFn: func(_, _ uint64) ([]*peggyevents.PeggyERC20DeployedEvent, error) { + return nil, nil + }, + }, + lastObservedEthHeight: 100, + }, + } + + for _, tt := range testTable { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + o := ethOracle{ + Orchestrator: tt.orch, + Injective: tt.inj, + Ethereum: tt.eth, + LastResyncWithInjective: tt.lastResyncWithInjective, + LastObservedEthHeight: tt.lastObservedEthHeight, + } + + err := o.ObserveEthEvents(context.Background()) + if tt.expected == nil { + assert.NoError(t, err) + } else { + assert.Error(t, err) + } + }) + } + }) + + t.Run("relayer valset", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + expected error + orch *Orchestrator + inj cosmos.Network + eth ethereum.Network + }{ + { + name: "failed to get latest valset updates", + expected: errors.New("oops"), + orch: &Orchestrator{svcTags: metrics.Tags{"svc": "relayer"}}, + inj: MockCosmosNetwork{ + LatestValsetsFn: func(_ context.Context) ([]*peggytypes.Valset, error) { + return nil, errors.New("oops") + }, + }, + }, + + { + name: "failed to get valset confirmations", + expected: errors.New("oops"), + orch: &Orchestrator{svcTags: metrics.Tags{"svc": "relayer"}}, + inj: MockCosmosNetwork{ + LatestValsetsFn: func(_ context.Context) ([]*peggytypes.Valset, error) { + return []*peggytypes.Valset{{}}, nil // non-empty will do + }, + + AllValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*peggytypes.MsgValsetConfirm, error) { + return nil, errors.New("oops") + }, + }, + }, + + { + name: "no new valset to relay", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + svcTags: metrics.Tags{"svc": "relayer"}, + }, + inj: MockCosmosNetwork{ + LatestValsetsFn: func(_ context.Context) ([]*peggytypes.Valset, error) { + return nil, nil + }, + + AllValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*peggytypes.MsgValsetConfirm, error) { + return nil, nil + }, + }, + }, + + { + name: "no new valset to relay", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + svcTags: metrics.Tags{"svc": "relayer"}, + }, + eth: MockEthereumNetwork{ + GetValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return nil, errors.New("oops") + }, + }, + + inj: MockCosmosNetwork{ + LatestValsetsFn: func(_ context.Context) ([]*peggytypes.Valset, error) { + return []*peggytypes.Valset{{}}, nil // non-empty will do + }, + + AllValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*peggytypes.MsgValsetConfirm, error) { + return []*peggytypes.MsgValsetConfirm{{}}, nil // non-empty will do + }, + }, + }, + + { + name: "valset already updated", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + svcTags: metrics.Tags{"svc": "relayer"}, + }, + eth: MockEthereumNetwork{ + GetValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(101), nil + }, + }, + + inj: MockCosmosNetwork{ + LatestValsetsFn: func(_ context.Context) ([]*peggytypes.Valset, error) { + return []*peggytypes.Valset{{}}, nil // non-empty will do + }, + + AllValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*peggytypes.MsgValsetConfirm, error) { + return []*peggytypes.MsgValsetConfirm{{}}, nil // non-empty will do + }, + }, + }, + + { + name: "failed to get injective block", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + svcTags: metrics.Tags{"svc": "relayer"}, + }, + eth: MockEthereumNetwork{ + GetValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(99), nil + }, + }, + + inj: MockCosmosNetwork{ + GetBlockFn: func(_ context.Context, _ int64) (*comettypes.ResultBlock, error) { + return nil, errors.New("oops") + }, + + LatestValsetsFn: func(_ context.Context) ([]*peggytypes.Valset, error) { + return []*peggytypes.Valset{{}}, nil // non-empty will do + }, + + AllValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*peggytypes.MsgValsetConfirm, error) { + return []*peggytypes.MsgValsetConfirm{{}}, nil // non-empty will do + }, + }, + }, + + { + name: "relay valset offser duration not expired", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + svcTags: metrics.Tags{"svc": "relayer"}, + relayValsetOffsetDur: 10 * time.Second, + }, + eth: MockEthereumNetwork{ + GetValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(99), nil + }, + }, + + inj: MockCosmosNetwork{ + GetBlockFn: func(_ context.Context, _ int64) (*comettypes.ResultBlock, error) { + return &comettypes.ResultBlock{ + Block: &comet.Block{ + Header: comet.Header{Time: time.Now()}, + }, + }, nil + }, + + LatestValsetsFn: func(_ context.Context) ([]*peggytypes.Valset, error) { + return []*peggytypes.Valset{{}}, nil // non-empty will do + }, + + AllValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*peggytypes.MsgValsetConfirm, error) { + return []*peggytypes.MsgValsetConfirm{{}}, nil // non-empty will do + }, + }, + }, + + { + name: "failed to send valset update", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + svcTags: metrics.Tags{"svc": "relayer"}, + relayValsetOffsetDur: 0, + }, + eth: MockEthereumNetwork{ + GetValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(99), nil + }, + + SendEthValsetUpdateFn: func(_ context.Context, _ *peggytypes.Valset, _ *peggytypes.Valset, _ []*peggytypes.MsgValsetConfirm) (*gethcommon.Hash, error) { + return nil, errors.New("oops") + }, + }, + + inj: MockCosmosNetwork{ + GetBlockFn: func(_ context.Context, _ int64) (*comettypes.ResultBlock, error) { + return &comettypes.ResultBlock{ + Block: &comet.Block{ + Header: comet.Header{Time: time.Now()}, + }, + }, nil + }, + + LatestValsetsFn: func(_ context.Context) ([]*peggytypes.Valset, error) { + return []*peggytypes.Valset{{}}, nil // non-empty will do + }, + + AllValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*peggytypes.MsgValsetConfirm, error) { + return []*peggytypes.MsgValsetConfirm{{}}, nil // non-empty will do + }, + }, + }, + + { + name: "sent valset update", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + svcTags: metrics.Tags{"svc": "relayer"}, + relayValsetOffsetDur: 0, + }, + eth: MockEthereumNetwork{ + GetValsetNonceFn: func(_ context.Context) (*big.Int, error) { + return big.NewInt(99), nil + }, + + SendEthValsetUpdateFn: func(_ context.Context, _ *peggytypes.Valset, _ *peggytypes.Valset, _ []*peggytypes.MsgValsetConfirm) (*gethcommon.Hash, error) { + return &gethcommon.Hash{}, nil + }, + }, + + inj: MockCosmosNetwork{ + GetBlockFn: func(_ context.Context, _ int64) (*comettypes.ResultBlock, error) { + return &comettypes.ResultBlock{ + Block: &comet.Block{ + Header: comet.Header{Time: time.Now()}, + }, + }, nil + }, + + LatestValsetsFn: func(_ context.Context) ([]*peggytypes.Valset, error) { + return []*peggytypes.Valset{{}}, nil // non-empty will do + }, + + AllValsetConfirmsFn: func(_ context.Context, _ uint64) ([]*peggytypes.MsgValsetConfirm, error) { + return []*peggytypes.MsgValsetConfirm{{}}, nil // non-empty will do + }, + }, + }, + } + + for _, tt := range testTable { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + r := relayer{ + Orchestrator: tt.orch, + Injective: tt.inj, + Ethereum: tt.eth, + } + + latestEthValset := &peggytypes.Valset{ + Nonce: 101, + } + + err := r.relayValset(context.Background(), latestEthValset) + if tt.expected == nil { + assert.NoError(t, err) + } else { + assert.Error(t, err) + } + }) + } + }) + + t.Run("relayer batches", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + expected error + orch *Orchestrator + inj cosmos.Network + eth ethereum.Network + }{ + { + name: "failed to get latest batches", + expected: errors.New("oops"), + orch: &Orchestrator{ + logger: DummyLog, + svcTags: metrics.Tags{"svc": "relayer"}, + }, + inj: MockCosmosNetwork{ + LatestTransactionBatchesFn: func(_ context.Context) ([]*peggytypes.OutgoingTxBatch, error) { + return nil, errors.New("oops") + }, + }, + }, + + { + name: "failed to get batch confirmations", + expected: errors.New("oops"), + orch: &Orchestrator{ + logger: DummyLog, + svcTags: metrics.Tags{"svc": "relayer"}, + }, + inj: MockCosmosNetwork{ + LatestTransactionBatchesFn: func(_ context.Context) ([]*peggytypes.OutgoingTxBatch, error) { + return []*peggytypes.OutgoingTxBatch{{}}, nil + }, + + TransactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ gethcommon.Address) ([]*peggytypes.MsgConfirmBatch, error) { + return nil, errors.New("oops") + }, + }, + }, + + { + name: "no batch to relay", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + svcTags: metrics.Tags{"svc": "relayer"}, + }, + inj: MockCosmosNetwork{ + LatestTransactionBatchesFn: func(_ context.Context) ([]*peggytypes.OutgoingTxBatch, error) { + return nil, nil + }, + + TransactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ gethcommon.Address) ([]*peggytypes.MsgConfirmBatch, error) { + return []*peggytypes.MsgConfirmBatch{{}}, nil + }, + }, + }, + + { + name: "failed to get latest batch nonce", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + svcTags: metrics.Tags{"svc": "relayer"}, + }, + inj: MockCosmosNetwork{ + LatestTransactionBatchesFn: func(_ context.Context) ([]*peggytypes.OutgoingTxBatch, error) { + return []*peggytypes.OutgoingTxBatch{{}}, nil + }, + + TransactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ gethcommon.Address) ([]*peggytypes.MsgConfirmBatch, error) { + return []*peggytypes.MsgConfirmBatch{{}}, nil + }, + }, + eth: MockEthereumNetwork{ + GetTxBatchNonceFn: func(_ context.Context, _ gethcommon.Address) (*big.Int, error) { + return nil, errors.New("oops") + }, + }, + }, + + { + name: "batch already updated", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + svcTags: metrics.Tags{"svc": "relayer"}, + }, + inj: MockCosmosNetwork{ + LatestTransactionBatchesFn: func(_ context.Context) ([]*peggytypes.OutgoingTxBatch, error) { + return []*peggytypes.OutgoingTxBatch{{ + BatchNonce: 100, + }}, nil + }, + + TransactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ gethcommon.Address) ([]*peggytypes.MsgConfirmBatch, error) { + return []*peggytypes.MsgConfirmBatch{{}}, nil + }, + }, + eth: MockEthereumNetwork{ + GetTxBatchNonceFn: func(_ context.Context, _ gethcommon.Address) (*big.Int, error) { + return big.NewInt(100), nil + }, + }, + }, + + { + name: "failed to get injective block", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + svcTags: metrics.Tags{"svc": "relayer"}, + }, + inj: MockCosmosNetwork{ + LatestTransactionBatchesFn: func(_ context.Context) ([]*peggytypes.OutgoingTxBatch, error) { + return []*peggytypes.OutgoingTxBatch{{ + BatchNonce: 101, + }}, nil + }, + + TransactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ gethcommon.Address) ([]*peggytypes.MsgConfirmBatch, error) { + return []*peggytypes.MsgConfirmBatch{{}}, nil + }, + + GetBlockFn: func(_ context.Context, _ int64) (*comettypes.ResultBlock, error) { + return nil, errors.New("oops") + }, + }, + eth: MockEthereumNetwork{ + GetTxBatchNonceFn: func(_ context.Context, _ gethcommon.Address) (*big.Int, error) { + return big.NewInt(100), nil + }, + }, + }, + + { + name: "batch relay offset not expired", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + svcTags: metrics.Tags{"svc": "relayer"}, + relayBatchOffsetDur: 10 * time.Second, + }, + inj: MockCosmosNetwork{ + LatestTransactionBatchesFn: func(_ context.Context) ([]*peggytypes.OutgoingTxBatch, error) { + return []*peggytypes.OutgoingTxBatch{{ + BatchNonce: 101, + }}, nil + }, + + TransactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ gethcommon.Address) ([]*peggytypes.MsgConfirmBatch, error) { + return []*peggytypes.MsgConfirmBatch{{}}, nil + }, + + GetBlockFn: func(_ context.Context, _ int64) (*comettypes.ResultBlock, error) { + return &comettypes.ResultBlock{ + Block: &comet.Block{ + Header: comet.Header{Time: time.Now()}, + }, + }, nil + }, + }, + eth: MockEthereumNetwork{ + GetTxBatchNonceFn: func(_ context.Context, _ gethcommon.Address) (*big.Int, error) { + return big.NewInt(100), nil + }, + }, + }, + + { + name: "failed to send batch update", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + svcTags: metrics.Tags{"svc": "relayer"}, + relayBatchOffsetDur: 0, + }, + inj: MockCosmosNetwork{ + LatestTransactionBatchesFn: func(_ context.Context) ([]*peggytypes.OutgoingTxBatch, error) { + return []*peggytypes.OutgoingTxBatch{{ + BatchNonce: 101, + }}, nil + }, + + TransactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ gethcommon.Address) ([]*peggytypes.MsgConfirmBatch, error) { + return []*peggytypes.MsgConfirmBatch{{}}, nil + }, + + GetBlockFn: func(_ context.Context, _ int64) (*comettypes.ResultBlock, error) { + return &comettypes.ResultBlock{ + Block: &comet.Block{ + Header: comet.Header{Time: time.Now()}, + }, + }, nil + }, + }, + eth: MockEthereumNetwork{ + GetTxBatchNonceFn: func(_ context.Context, _ gethcommon.Address) (*big.Int, error) { + return big.NewInt(100), nil + }, + + SendTransactionBatchFn: func(_ context.Context, _ *peggytypes.Valset, _ *peggytypes.OutgoingTxBatch, _ []*peggytypes.MsgConfirmBatch) (*gethcommon.Hash, error) { + return nil, errors.New("oops") + }, + }, + }, + + { + name: "sent batch update", + expected: nil, + orch: &Orchestrator{ + logger: DummyLog, + svcTags: metrics.Tags{"svc": "relayer"}, + relayBatchOffsetDur: 0, + }, + inj: MockCosmosNetwork{ + LatestTransactionBatchesFn: func(_ context.Context) ([]*peggytypes.OutgoingTxBatch, error) { + return []*peggytypes.OutgoingTxBatch{{ + BatchNonce: 101, + }}, nil + }, + + TransactionBatchSignaturesFn: func(_ context.Context, _ uint64, _ gethcommon.Address) ([]*peggytypes.MsgConfirmBatch, error) { + return []*peggytypes.MsgConfirmBatch{{}}, nil + }, + + GetBlockFn: func(_ context.Context, _ int64) (*comettypes.ResultBlock, error) { + return &comettypes.ResultBlock{ + Block: &comet.Block{ + Header: comet.Header{Time: time.Now()}, + }, + }, nil + }, + }, + eth: MockEthereumNetwork{ + GetTxBatchNonceFn: func(_ context.Context, _ gethcommon.Address) (*big.Int, error) { + return big.NewInt(100), nil + }, + + SendTransactionBatchFn: func(_ context.Context, _ *peggytypes.Valset, _ *peggytypes.OutgoingTxBatch, _ []*peggytypes.MsgConfirmBatch) (*gethcommon.Hash, error) { + return &gethcommon.Hash{}, nil + }, + }, + }, + } + + for _, tt := range testTable { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + r := relayer{ + Orchestrator: tt.orch, + Injective: tt.inj, + Ethereum: tt.eth, + } + + latestEthValset := &peggytypes.Valset{ + Nonce: 101, + } + + err := r.relayBatch(context.Background(), latestEthValset) + if tt.expected == nil { + assert.NoError(t, err) + } else { + assert.Error(t, err) + } + }) + } + }) + + t.Run("signer valsets", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + expected error + orch *Orchestrator + inj cosmos.Network + }{ + { + name: "failed to get unsigned valsets", + expected: nil, + orch: &Orchestrator{logger: DummyLog}, + inj: MockCosmosNetwork{ + OldestUnsignedValsetsFn: func(_ context.Context, _ cosmtypes.AccAddress) ([]*peggytypes.Valset, error) { + return nil, errors.New("oops") + }, + }, + }, + + { + name: "no valset updates to sign", + expected: nil, + orch: &Orchestrator{logger: DummyLog}, + inj: MockCosmosNetwork{ + OldestUnsignedValsetsFn: func(_ context.Context, _ cosmtypes.AccAddress) ([]*peggytypes.Valset, error) { + return nil, nil + }, + }, + }, + + { + name: "failed to send valset confirm", + expected: errors.New("oops"), + orch: &Orchestrator{logger: DummyLog}, + inj: MockCosmosNetwork{ + OldestUnsignedValsetsFn: func(_ context.Context, _ cosmtypes.AccAddress) ([]*peggytypes.Valset, error) { + return []*peggytypes.Valset{{}}, nil + }, + + SendValsetConfirmFn: func(_ context.Context, _ gethcommon.Address, _ gethcommon.Hash, _ *peggytypes.Valset) error { + return errors.New("oops") + }, + }, + }, + + { + name: "sent valset confirm", + expected: nil, + orch: &Orchestrator{logger: DummyLog}, + inj: MockCosmosNetwork{ + OldestUnsignedValsetsFn: func(_ context.Context, _ cosmtypes.AccAddress) ([]*peggytypes.Valset, error) { + return []*peggytypes.Valset{{}}, nil + }, + + SendValsetConfirmFn: func(_ context.Context, _ gethcommon.Address, _ gethcommon.Hash, _ *peggytypes.Valset) error { + return nil + }, + }, + }, + } + + for _, tt := range testTable { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + s := ethSigner{ + Orchestrator: tt.orch, + Injective: tt.inj, + } + + err := s.signNewValsetUpdates(context.Background()) + if tt.expected == nil { + assert.NoError(t, err) + } else { + assert.Error(t, err) + } + }) + } + }) + + t.Run("signer batches", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + expected error + orch *Orchestrator + inj cosmos.Network + }{ + { + name: "failed to get unsigned batches/no batch to confirm", + expected: nil, + orch: &Orchestrator{logger: DummyLog}, + inj: MockCosmosNetwork{ + OldestUnsignedTransactionBatchFn: func(_ context.Context, _ cosmtypes.AccAddress) (*peggytypes.OutgoingTxBatch, error) { + return nil, errors.New("ooops") + }, + }, + }, + + { + name: "failed to send batch confirm", + expected: errors.New("oops"), + orch: &Orchestrator{logger: DummyLog}, + inj: MockCosmosNetwork{ + OldestUnsignedTransactionBatchFn: func(_ context.Context, _ cosmtypes.AccAddress) (*peggytypes.OutgoingTxBatch, error) { + return &peggytypes.OutgoingTxBatch{}, nil + }, + + SendBatchConfirmFn: func(_ context.Context, _ gethcommon.Address, _ gethcommon.Hash, _ *peggytypes.OutgoingTxBatch) error { + return errors.New("oops") + }, + }, + }, + + { + name: "sent batch confirm", + expected: nil, + orch: &Orchestrator{logger: DummyLog}, + inj: MockCosmosNetwork{ + OldestUnsignedTransactionBatchFn: func(_ context.Context, _ cosmtypes.AccAddress) (*peggytypes.OutgoingTxBatch, error) { + return &peggytypes.OutgoingTxBatch{}, nil + }, + + SendBatchConfirmFn: func(_ context.Context, _ gethcommon.Address, _ gethcommon.Hash, _ *peggytypes.OutgoingTxBatch) error { + return nil + }, + }, + }, + } + + for _, tt := range testTable { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + s := ethSigner{ + Orchestrator: tt.orch, + Injective: tt.inj, + } + + err := s.signNewBatch(context.Background()) + if tt.expected == nil { + assert.NoError(t, err) + } else { + assert.Error(t, err) + } + }) + } + }) +} diff --git a/orchestrator/price_feed.go b/orchestrator/price_feed.go deleted file mode 100644 index 2ff81c8c..00000000 --- a/orchestrator/price_feed.go +++ /dev/null @@ -1,8 +0,0 @@ -package orchestrator - -import eth "github.com/ethereum/go-ethereum/common" - -// PriceFeed provides token price for a given contract address -type PriceFeed interface { - QueryUSDPrice(address eth.Address) (float64, error) -} diff --git a/orchestrator/relayer_test.go b/orchestrator/relayer_test.go deleted file mode 100644 index ea0d2f50..00000000 --- a/orchestrator/relayer_test.go +++ /dev/null @@ -1,1290 +0,0 @@ -package orchestrator - -import ( - "context" - wrappers "github.com/InjectiveLabs/peggo/solidity/wrappers/Peggy.sol" - 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" - "github.com/xlab/suplog" - "math/big" - "testing" - "time" - - "github.com/InjectiveLabs/sdk-go/chain/peggy/types" -) - -func TestValsetRelaying(t *testing.T) { - t.Parallel() - - t.Run("failed to fetch latest valsets from injective", func(t *testing.T) { - t.Parallel() - - injective := &mockInjective{ - latestValsetsFn: func(_ context.Context) ([]*types.Valset, error) { - return nil, errors.New("fail") - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: injective, - maxAttempts: 1, - valsetRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.Error(t, l.relayValset(context.TODO())) - }) - - t.Run("failed to fetch confirms for a valset", func(t *testing.T) { - t.Parallel() - - 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") - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - maxAttempts: 1, - valsetRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.Error(t, l.relayValset(context.TODO())) - }) - - t.Run("no confirms for valset", func(t *testing.T) { - t.Parallel() - - 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 - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - maxAttempts: 1, - valsetRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.NoError(t, l.relayValset(context.TODO())) - }) - - t.Run("failed to get latest ethereum header", func(t *testing.T) { - t.Parallel() - - 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 nil, errors.New("fail") - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - valsetRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.Error(t, l.relayValset(context.TODO())) - }) - - t.Run("failed to get latest ethereum header", func(t *testing.T) { - t.Parallel() - - 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 nil, errors.New("fail") - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - valsetRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.Error(t, l.relayValset(context.TODO())) - }) - - t.Run("failed to get valset nonce from peggy contract", func(t *testing.T) { - t.Parallel() - - 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 - }, - getValsetNonceFn: func(_ context.Context) (*big.Int, error) { - return nil, errors.New("fail") - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - valsetRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.Error(t, l.relayValset(context.TODO())) - }) - - t.Run("failed to get specific valset from injective", func(t *testing.T) { - t.Parallel() - - 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 - }, - getValsetNonceFn: func(_ context.Context) (*big.Int, error) { - return big.NewInt(100), nil - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - valsetRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.Error(t, l.relayValset(context.TODO())) - }) - - t.Run("failed to get valset update events from ethereum", func(t *testing.T) { - t.Parallel() - - 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") - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - valsetRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.Error(t, l.relayValset(context.TODO())) - }) - - t.Run("ethereum valset is not higher than injective valset", func(t *testing.T) { - t.Parallel() - - inj := &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 - }, - } - - 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 - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - valsetRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.NoError(t, l.relayValset(context.TODO())) - }) - - t.Run("injective valset is higher than ethereum but failed to get block from injective", func(t *testing.T) { - t.Parallel() - - inj := &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 - }, - getBlockCreationTimeFn: func(_ context.Context, _ int64) (time.Time, error) { - return time.Time{}, 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 - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - valsetRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.Error(t, l.relayValset(context.TODO())) - }) - - t.Run("injective valset is higher than ethereum but valsetOffsetDur has not expired", func(t *testing.T) { - t.Parallel() - - inj := &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 - }, - getBlockCreationTimeFn: func(_ context.Context, _ int64) (time.Time, error) { - return time.Now().Add(time.Hour), 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 - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - valsetRelayEnabled: true, - relayValsetOffsetDur: time.Second * 5, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.NoError(t, l.relayValset(context.TODO())) - }) - - t.Run("injective valset is higher than ethereum but failed to send update tx to ethereum", func(t *testing.T) { - t.Parallel() - - inj := &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 - }, - getBlockCreationTimeFn: func(_ context.Context, _ int64) (time.Time, error) { - return time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC), 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") - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - valsetRelayEnabled: true, - relayValsetOffsetDur: time.Second * 5, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.Error(t, l.relayValset(context.TODO())) - }) - - t.Run("new valset update is sent to ethereum", func(t *testing.T) { - t.Parallel() - - inj := &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 - }, - getBlockCreationTimeFn: func(_ context.Context, _ int64) (time.Time, error) { - return time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC), 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 - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - valsetRelayEnabled: true, - relayValsetOffsetDur: time.Second * 5, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.NoError(t, l.relayValset(context.TODO())) - }) -} - -func TestBatchRelaying(t *testing.T) { - t.Parallel() - - t.Run("failed to get latest batches from injective", func(t *testing.T) { - t.Parallel() - - inj := &mockInjective{ - latestTransactionBatchesFn: func(_ context.Context) ([]*types.OutgoingTxBatch, error) { - return nil, errors.New("fail") - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - maxAttempts: 1, - batchRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.Error(t, l.relayBatch(context.TODO())) - }) - - t.Run("failed to get latest batches from injective", func(t *testing.T) { - t.Parallel() - - 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") - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - maxAttempts: 1, - batchRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.Error(t, l.relayBatch(context.TODO())) - }) - - t.Run("no batch confirms", func(t *testing.T) { - t.Parallel() - - 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 - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - maxAttempts: 1, - batchRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.NoError(t, l.relayBatch(context.TODO())) - }) - - t.Run("failed to get batch nonce from ethereum", func(t *testing.T) { - t.Parallel() - - 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 []*types.MsgConfirmBatch{{}}, nil // non-nil will do - }, - } - - eth := mockEthereum{ - getTxBatchNonceFn: func(_ context.Context, _ common.Address) (*big.Int, error) { - return nil, errors.New("fail") - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - batchRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.Error(t, l.relayBatch(context.TODO())) - }) - - t.Run("failed to get latest ethereum header", func(t *testing.T) { - t.Parallel() - - 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 nil, errors.New("fail") - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - batchRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.Error(t, l.relayBatch(context.TODO())) - }) - - t.Run("failed to get valset nonce from ethereum", func(t *testing.T) { - t.Parallel() - - 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 - }, - - getValsetNonceFn: func(_ context.Context) (*big.Int, error) { - return nil, errors.New("fail") - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - batchRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.Error(t, l.relayBatch(context.TODO())) - }) - - t.Run("failed to get specific valset from injective", func(t *testing.T) { - t.Parallel() - - 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 - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - batchRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.Error(t, l.relayBatch(context.TODO())) - }) - - t.Run("failed to get valset updated events from ethereum", func(t *testing.T) { - t.Parallel() - - 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") - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - batchRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.Error(t, l.relayBatch(context.TODO())) - }) - - t.Run("ethereum batch is not lower than injective batch", func(t *testing.T) { - t.Parallel() - - 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 - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - batchRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.NoError(t, l.relayBatch(context.TODO())) - }) - - t.Run("ethereum batch is lower than injective batch but failed to get block from injhective", func(t *testing.T) { - t.Parallel() - - 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 - }, - getBlockCreationTimeFn: func(_ context.Context, _ int64) (time.Time, error) { - return time.Time{}, 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 - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - batchRelayEnabled: true, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.Error(t, l.relayBatch(context.TODO())) - }) - - t.Run("ethereum batch is lower than injective batch but relayBatchOffsetDur has not expired", func(t *testing.T) { - t.Parallel() - - 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 - }, - getBlockCreationTimeFn: func(_ context.Context, _ int64) (time.Time, error) { - return time.Now().Add(time.Hour), 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 - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - batchRelayEnabled: true, - relayBatchOffsetDur: time.Second * 5, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.NoError(t, l.relayBatch(context.TODO())) - }) - - t.Run("ethereum batch is lower than injective batch but failed to send batch update", func(t *testing.T) { - t.Parallel() - - 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 - }, - getBlockCreationTimeFn: func(_ context.Context, _ int64) (time.Time, error) { - return time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC), 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") - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - batchRelayEnabled: true, - relayBatchOffsetDur: time.Second * 5, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.Error(t, l.relayBatch(context.TODO())) - - }) - - t.Run("sending a batch update to ethereum", func(t *testing.T) { - t.Parallel() - - 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 - }, - getBlockCreationTimeFn: func(_ context.Context, _ int64) (time.Time, error) { - return time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC), 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 - }, - } - - o := &PeggyOrchestrator{ - logger: suplog.DefaultLogger, - inj: inj, - eth: eth, - maxAttempts: 1, - batchRelayEnabled: true, - relayBatchOffsetDur: time.Second * 5, - } - - l := relayerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultRelayerLoopDur, - } - - assert.NoError(t, l.relayBatch(context.TODO())) - }) -} diff --git a/orchestrator/signer.go b/orchestrator/signer.go deleted file mode 100644 index 9b429f0d..00000000 --- a/orchestrator/signer.go +++ /dev/null @@ -1,215 +0,0 @@ -package orchestrator - -import ( - "context" - "time" - - "github.com/avast/retry-go" - "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" - 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 -// 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 { - peggyID, err := s.getPeggyID(ctx) - if err != nil { - return err - } - - loop := ethSignerLoop{ - PeggyOrchestrator: s, - loopDuration: defaultLoopDur, - peggyID: peggyID, - ethFrom: s.eth.FromAddress(), - } - - return loop.Run(ctx) -} - -func (s *PeggyOrchestrator) getPeggyID(ctx context.Context) (common.Hash, error) { - var peggyID common.Hash - getPeggyIDFn := func() (err error) { - peggyID, err = s.eth.GetPeggyID(ctx) - return err - } - - if err := retry.Do(getPeggyIDFn, - retry.Context(ctx), - retry.Attempts(s.maxAttempts), - retry.OnRetry(func(n uint, err error) { - log.WithError(err).Warningf("failed to get Peggy ID from Ethereum contract, will retry (%d)", n) - }), - ); err != nil { - log.WithError(err).Errorln("got error, loop exits") - return [32]byte{}, err - } - - log.WithField("id", peggyID.Hex()).Debugln("got peggy ID from Ethereum contract") - - return peggyID, nil -} - -type ethSignerLoop struct { - *PeggyOrchestrator - loopDuration time.Duration - peggyID common.Hash - ethFrom common.Address -} - -func (l *ethSignerLoop) Logger() log.Logger { - return l.logger.WithField("loop", "EthSigner") -} - -func (l *ethSignerLoop) Run(ctx context.Context) error { - return loops.RunLoop(ctx, l.loopDuration, func() error { - if err := l.signNewValsetUpdates(ctx); err != nil { - return err - } - - if err := l.signNewBatch(ctx); err != nil { - return err - } - - return nil - }) -} - -func (l *ethSignerLoop) signNewValsetUpdates(ctx context.Context) error { - oldestUnsignedValsets, err := l.getUnsignedValsets(ctx) - if err != nil { - return err - } - - if len(oldestUnsignedValsets) == 0 { - l.Logger().Debugln("no valset updates to confirm") - return nil - } - - for _, vs := range oldestUnsignedValsets { - if err := l.signValset(ctx, vs); err != nil { - return err - } - - // todo: in case of multiple updates, we should sleep in between tx (non-continuous nonce) - } - - return nil -} - -func (l *ethSignerLoop) signNewBatch(ctx context.Context) error { - oldestUnsignedTransactionBatch, err := l.getUnsignedBatch(ctx) - if err != nil { - return err - } - - if oldestUnsignedTransactionBatch == nil { - l.Logger().Debugln("no batch to confirm") - return nil - } - - if err := l.signBatch(ctx, oldestUnsignedTransactionBatch); err != nil { - return err - } - - return nil -} - -func (l *ethSignerLoop) getUnsignedBatch(ctx context.Context) (*types.OutgoingTxBatch, error) { - var oldestUnsignedBatch *types.OutgoingTxBatch - getOldestUnsignedBatchFn := func() (err error) { - // sign the last unsigned batch, TODO check if we already have signed this - oldestUnsignedBatch, err = l.inj.OldestUnsignedTransactionBatch(ctx) - if errors.Is(err, cosmos.ErrNotFound) || oldestUnsignedBatch == nil { - return nil - } - - return err - } - - if err := retry.Do(getOldestUnsignedBatchFn, - retry.Context(ctx), - retry.Attempts(l.maxAttempts), - retry.OnRetry(func(n uint, err error) { - l.Logger().WithError(err).Warningf("failed to get unconfirmed batch, will retry (%d)", n) - }), - ); err != nil { - l.Logger().WithError(err).Errorln("got error, loop exits") - return nil, err - } - - return oldestUnsignedBatch, nil -} - -func (l *ethSignerLoop) signBatch(ctx context.Context, batch *types.OutgoingTxBatch) error { - signFn := func() error { - return l.inj.SendBatchConfirm(ctx, l.ethFrom, l.peggyID, batch) - } - - if err := retry.Do(signFn, - retry.Context(ctx), - retry.Attempts(l.maxAttempts), - retry.OnRetry(func(n uint, err error) { - l.Logger().WithError(err).Warningf("failed to confirm batch on Injective, will retry (%d)", n) - }), - ); err != nil { - l.Logger().WithError(err).Errorln("got error, loop exits") - return err - } - - l.Logger().WithFields(log.Fields{"token_contract": batch.TokenContract, "batch_nonce": batch.BatchNonce, "txs": len(batch.Transactions)}).Infoln("confirmed batch on Injective") - - return nil -} - -func (l *ethSignerLoop) getUnsignedValsets(ctx context.Context) ([]*types.Valset, error) { - var oldestUnsignedValsets []*types.Valset - getOldestUnsignedValsetsFn := func() (err error) { - oldestUnsignedValsets, err = l.inj.OldestUnsignedValsets(ctx) - if errors.Is(err, cosmos.ErrNotFound) || oldestUnsignedValsets == nil { - return nil - } - - return err - } - - if err := retry.Do(getOldestUnsignedValsetsFn, - retry.Context(ctx), - retry.Attempts(l.maxAttempts), - retry.OnRetry(func(n uint, err error) { - l.Logger().WithError(err).Warningf("failed to get unconfirmed valset updates, will retry (%d)", n) - }), - ); err != nil { - l.Logger().WithError(err).Errorln("got error, loop exits") - return nil, err - } - - return oldestUnsignedValsets, nil -} - -func (l *ethSignerLoop) signValset(ctx context.Context, vs *types.Valset) error { - signFn := func() error { - return l.inj.SendValsetConfirm(ctx, l.ethFrom, l.peggyID, vs) - } - - if err := retry.Do(signFn, - retry.Context(ctx), - retry.Attempts(l.maxAttempts), - retry.OnRetry(func(n uint, err error) { - l.Logger().WithError(err).Warningf("failed to confirm valset update on Injective, will retry (%d)", n) - }), - ); err != nil { - l.Logger().WithError(err).Errorln("got error, loop exits") - return err - } - - l.Logger().WithFields(log.Fields{"valset_nonce": vs.Nonce, "validators": len(vs.Members)}).Infoln("confirmed valset update on Injective") - - return nil -} diff --git a/orchestrator/signer_test.go b/orchestrator/signer_test.go deleted file mode 100644 index bf530ce6..00000000 --- a/orchestrator/signer_test.go +++ /dev/null @@ -1,184 +0,0 @@ -package orchestrator - -import ( - "context" - "testing" - - cosmtypes "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - log "github.com/xlab/suplog" - - "github.com/InjectiveLabs/sdk-go/chain/peggy/types" -) - -func TestEthSignerLoop(t *testing.T) { - t.Parallel() - - t.Run("failed to fetch peggy id from contract", func(t *testing.T) { - t.Parallel() - - orch := &PeggyOrchestrator{ - maxAttempts: 1, - eth: mockEthereum{ - getPeggyIDFn: func(context.Context) (common.Hash, error) { - return [32]byte{}, errors.New("fail") - }, - }, - } - - assert.Error(t, orch.EthSignerMainLoop(context.TODO())) - }) - - t.Run("no valset to sign", func(t *testing.T) { - t.Parallel() - - 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 - }, - } - - o := &PeggyOrchestrator{ - logger: log.DefaultLogger, - inj: injective, - maxAttempts: 1, - } - - l := ethSignerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultLoopDur, - } - - assert.NoError(t, l.signBatchesAndValsets(context.TODO())) - }) - - t.Run("failed to send valset confirm", func(t *testing.T) { - t.Parallel() - - 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") - }, - } - - o := &PeggyOrchestrator{ - logger: log.DefaultLogger, - inj: injective, - maxAttempts: 1, - } - - l := ethSignerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultLoopDur, - } - - assert.Error(t, l.signBatchesAndValsets(context.TODO())) - }) - - t.Run("no transaction batch sign", func(t *testing.T) { - t.Parallel() - - 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 }, - } - - o := &PeggyOrchestrator{ - logger: log.DefaultLogger, - inj: injective, - maxAttempts: 1, - } - - l := ethSignerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultLoopDur, - } - - assert.NoError(t, l.signBatchesAndValsets(context.TODO())) - }) - - t.Run("failed to send batch confirm", func(t *testing.T) { - t.Parallel() - - 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") - }, - } - - o := &PeggyOrchestrator{ - logger: log.DefaultLogger, - inj: injective, - maxAttempts: 1, - } - - l := ethSignerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultLoopDur, - } - - assert.Error(t, l.signBatchesAndValsets(context.TODO())) - }) - - t.Run("valset update and transaction batch are confirmed", func(t *testing.T) { - t.Parallel() - - 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 }, - } - - o := &PeggyOrchestrator{ - logger: log.DefaultLogger, - inj: injective, - maxAttempts: 1, - } - - l := ethSignerLoop{ - PeggyOrchestrator: o, - loopDuration: defaultLoopDur, - } - - assert.NoError(t, l.signBatchesAndValsets(context.TODO())) - }) -}