diff --git a/Makefile b/Makefile index b5cf22b39f..9a4ccfe3fa 100644 --- a/Makefile +++ b/Makefile @@ -98,6 +98,10 @@ install-zetaclient-race-test-only-build: go.sum @echo "--> Installing zetaclientd" @go install -race -mod=readonly $(BUILD_FLAGS) ./cmd/zetaclientd +install-zetatool: go.sum + @echo "--> Installing zetatool" + @go install -mod=readonly $(BUILD_FLAGS) ./cmd/zetatool + ############################################################################### ### Local network ### ############################################################################### @@ -286,4 +290,17 @@ mainnet-bitcoind-node: cd contrib/mainnet/bitcoind && DOCKER_TAG=$(DOCKER_TAG) docker-compose up athens3-zetarpc-node: - cd contrib/athens3/zetacored && DOCKER_TAG=$(DOCKER_TAG) docker-compose up \ No newline at end of file + cd contrib/athens3/zetacored && DOCKER_TAG=$(DOCKER_TAG) docker-compose up + +############################################################################### +### Debug Tools ### +############################################################################### + +filter-missed-btc: install-zetatool + zetatool filterdeposit btc --config ./tool/filter_missed_deposits/zetatool_config.json + +filter-missed-eth: install-zetatool + zetatool filterdeposit eth \ + --config ./tool/filter_missed_deposits/zetatool_config.json \ + --evm-max-range 1000 \ + --evm-start-block 19464041 \ No newline at end of file diff --git a/changelog.md b/changelog.md index b5325bf873..6efe4a3c45 100644 --- a/changelog.md +++ b/changelog.md @@ -15,14 +15,17 @@ * [1783](https://github.com/zeta-chain/node/pull/1783) - refactor zetaclient metrics naming and structure * [1774](https://github.com/zeta-chain/node/pull/1774) - split params and config in zetaclient * [1831](https://github.com/zeta-chain/node/pull/1831) - removing unnecessary pointers in context structure +* [1864](https://github.com/zeta-chain/node/pull/1864) - prevent panic in param management * [1848](https://github.com/zeta-chain/node/issues/1848) - create a method to observe deposits to tss address in one evm block * [1885](https://github.com/zeta-chain/node/pull/1885) - change important metrics on port 8123 to be prometheus compatible +* [1863](https://github.com/zeta-chain/node/pull/1863) - remove duplicate ValidateChainParams function ### Features * [1789](https://github.com/zeta-chain/node/issues/1789) - block cross-chain transactions that involve restricted addresses * [1755](https://github.com/zeta-chain/node/issues/1755) - use evm JSON RPC for inbound tx (including blob tx) observation. * [1815](https://github.com/zeta-chain/node/pull/1815) - add authority module for authorized actions +* [1884](https://github.com/zeta-chain/node/pull/1884) - added zetatool cmd, added subcommand to filter deposits ### Tests @@ -31,15 +34,19 @@ * [1791](https://github.com/zeta-chain/node/pull/1791) - add e2e tests for feature of restricted address * [1787](https://github.com/zeta-chain/node/pull/1787) - add unit tests for cross-chain evm hooks and e2e test failed withdraw to BTC legacy address * [1840](https://github.com/zeta-chain/node/pull/1840) - fix code coverage test failures ignored in CI +* [1870](https://github.com/zeta-chain/node/pull/1870) - enable emissions pool in local e2e testing * [1868](https://github.com/zeta-chain/node/pull/1868) - run e2e btc tests locally * [1851](https://github.com/zeta-chain/node/pull/1851) - rename usdt to erc20 in e2e tests * [1872](https://github.com/zeta-chain/node/pull/1872) - remove usage of RPC in unit test * [1805](https://github.com/zeta-chain/node/pull/1805) - add admin and performance test and fix upgrade test * [1879](https://github.com/zeta-chain/node/pull/1879) - full coverage for messages in types packages +* [1899](https://github.com/zeta-chain/node/pull/1899) - add empty test files so packages are included in coverage ### Fixes * [1861](https://github.com/zeta-chain/node/pull/1861) - fix `ObserverSlashAmount` invalid read +* [1880](https://github.com/zeta-chain/node/issues/1880) - lower the gas price multiplier for EVM chains. +* [1633](https://github.com/zeta-chain/node/issues/1633) - zetaclient should be able to pick up new connector and erc20Custody addresses ### Chores @@ -51,7 +58,13 @@ * [1891](https://github.com/zeta-chain/node/pull/1891) - fix typo that was introduced to docker-compose and a typo in start.sh for the docker start script for full nodes. * [1894](https://github.com/zeta-chain/node/pull/1894) - added download binaries and configs to the start sequence so it will download binaries that don't exist -## Version: v14 +## Version: v15.0.0 + +### Features + +*[1912](https://github.com/zeta-chain/node/pull/1912) - add reset chain nonces msg + +## Version: v14.0.1 - [1817](https://github.com/zeta-chain/node/pull/1817) - Add migration script to fix pending and chain nonces on testnet diff --git a/cmd/zetaclientd/utils.go b/cmd/zetaclientd/utils.go index f926f8118e..ca9a259594 100644 --- a/cmd/zetaclientd/utils.go +++ b/cmd/zetaclientd/utils.go @@ -3,7 +3,6 @@ package main import ( sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/common/cosmos" appcontext "github.com/zeta-chain/zetacore/zetaclient/app_context" "github.com/zeta-chain/zetacore/zetaclient/authz" @@ -55,8 +54,8 @@ func CreateSignerMap( tss interfaces.TSSSigner, loggers clientcommon.ClientLogger, ts *metrics.TelemetryServer, -) (map[common.Chain]interfaces.ChainSigner, error) { - signerMap := make(map[common.Chain]interfaces.ChainSigner) +) (map[int64]interfaces.ChainSigner, error) { + signerMap := make(map[int64]interfaces.ChainSigner) // EVM signers for _, evmConfig := range appContext.Config().GetAllEVMConfigs() { if evmConfig.Chain.IsZetaChain() { @@ -77,7 +76,7 @@ func CreateSignerMap( loggers.Std.Error().Err(err).Msgf("NewEVMSigner error for chain %s", evmConfig.Chain.String()) continue } - signerMap[evmConfig.Chain] = signer + signerMap[evmConfig.Chain.ChainId] = signer } // BTC signer btcChain, btcConfig, enabled := appContext.GetBTCChainAndConfig() @@ -86,7 +85,7 @@ func CreateSignerMap( if err != nil { loggers.Std.Error().Err(err).Msgf("NewBTCSigner error for chain %s", btcChain.String()) } else { - signerMap[btcChain] = signer + signerMap[btcChain.ChainId] = signer } } @@ -100,8 +99,8 @@ func CreateChainClientMap( dbpath string, loggers clientcommon.ClientLogger, ts *metrics.TelemetryServer, -) (map[common.Chain]interfaces.ChainClient, error) { - clientMap := make(map[common.Chain]interfaces.ChainClient) +) (map[int64]interfaces.ChainClient, error) { + clientMap := make(map[int64]interfaces.ChainClient) // EVM clients for _, evmConfig := range appContext.Config().GetAllEVMConfigs() { if evmConfig.Chain.IsZetaChain() { @@ -120,17 +119,17 @@ func CreateChainClientMap( loggers.Std.Error().Err(err).Msgf("NewEVMChainClient error for chain %s", evmConfig.Chain.String()) continue } - clientMap[evmConfig.Chain] = co + clientMap[evmConfig.Chain.ChainId] = co } // BTC client - btcChain, _, enabled := appContext.GetBTCChainAndConfig() + btcChain, btcConfig, enabled := appContext.GetBTCChainAndConfig() if enabled { - co, err := bitcoin.NewBitcoinClient(appContext, btcChain, bridge, tss, dbpath, loggers, ts) + co, err := bitcoin.NewBitcoinClient(appContext, btcChain, bridge, tss, dbpath, loggers, btcConfig, ts) if err != nil { loggers.Std.Error().Err(err).Msgf("NewBitcoinClient error for chain %s", btcChain.String()) } else { - clientMap[btcChain] = co + clientMap[btcChain.ChainId] = co } } diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index ddbc7323ec..c700285ae6 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -44,71 +44,19 @@ func NewLocalCmd() *cobra.Command { Short: "Run Local E2E tests", Run: localE2ETest, } - cmd.Flags().Bool( - flagContractsDeployed, - false, - "set to to true if running tests again with existing state", - ) - cmd.Flags().Int64( - flagWaitForHeight, - 0, - "block height for tests to begin, ex. --wait-for 100", - ) - cmd.Flags().String( - FlagConfigFile, - "", - "config file to use for the tests", - ) - cmd.Flags().String( - flagConfigOut, - "", - "config file to write the deployed contracts from the setup", - ) - cmd.Flags().Bool( - flagVerbose, - false, - "set to true to enable verbose logging", - ) - cmd.Flags().Bool( - flagTestAdmin, - false, - "set to true to run admin tests", - ) - cmd.Flags().Bool( - flagTestPerformance, - false, - "set to true to run performance tests", - ) - cmd.Flags().Bool( - flagTestCustom, - false, - "set to true to run custom tests", - ) - cmd.Flags().Bool( - flagSkipRegular, - false, - "set to true to skip regular tests", - ) - cmd.Flags().Bool( - flagLight, - false, - "run the most basic regular tests, useful for quick checks", - ) - cmd.Flags().Bool( - flagSetupOnly, - false, - "set to true to only setup the networks", - ) - cmd.Flags().Bool( - flagSkipSetup, - false, - "set to true to skip setup", - ) - cmd.Flags().Bool( - flagSkipBitcoinSetup, - false, - "set to true to skip bitcoin wallet setup", - ) + cmd.Flags().Bool(flagContractsDeployed, false, "set to to true if running tests again with existing state") + cmd.Flags().Int64(flagWaitForHeight, 0, "block height for tests to begin, ex. --wait-for 100") + cmd.Flags().String(FlagConfigFile, "", "config file to use for the tests") + cmd.Flags().Bool(flagVerbose, false, "set to true to enable verbose logging") + cmd.Flags().Bool(flagTestAdmin, false, "set to true to run admin tests") + cmd.Flags().Bool(flagTestPerformance, false, "set to true to run performance tests") + cmd.Flags().Bool(flagTestCustom, false, "set to true to run custom tests") + cmd.Flags().Bool(flagSkipRegular, false, "set to true to skip regular tests") + cmd.Flags().Bool(flagLight, false, "run the most basic regular tests, useful for quick checks") + cmd.Flags().Bool(flagSetupOnly, false, "set to true to only setup the networks") + cmd.Flags().String(flagConfigOut, "", "config file to write the deployed contracts from the setup") + cmd.Flags().Bool(flagSkipSetup, false, "set to true to skip setup") + cmd.Flags().Bool(flagSkipBitcoinSetup, false, "set to true to skip bitcoin wallet setup") return cmd } @@ -242,7 +190,16 @@ func localE2ETest(cmd *cobra.Command, _ []string) { startTime := time.Now() deployerRunner.SetupEVM(contractsDeployed) deployerRunner.SetZEVMContracts() + + // NOTE: this method return an error so we handle it and panic if it occurs unlike other method that panics directly + // TODO: all methods should return errors instead of panicking and this current function should also return an error + // https://github.com/zeta-chain/node/issues/1500 + if err := deployerRunner.FundEmissionsPool(); err != nil { + panic(err) + } + deployerRunner.MintERC20OnEvm(10000) + logger.Print("✅ setup completed in %s", time.Since(startTime)) } @@ -347,4 +304,17 @@ func localE2ETest(cmd *cobra.Command, _ []string) { } logger.Print("✅ e2e tests completed in %s", time.Since(testStartTime).String()) + + // print and validate report + networkReport, err := deployerRunner.GenerateNetworkReport() + if err != nil { + logger.Print("❌ failed to generate network report %v", err) + } + deployerRunner.PrintNetworkReport(networkReport) + if err := networkReport.Validate(); err != nil { + logger.Print("❌ network report validation failed %v", err) + os.Exit(1) + } + + os.Exit(0) } diff --git a/cmd/zetatool/config/config.go b/cmd/zetatool/config/config.go new file mode 100644 index 0000000000..6f3face04d --- /dev/null +++ b/cmd/zetatool/config/config.go @@ -0,0 +1,71 @@ +package config + +import ( + "encoding/json" + + "github.com/spf13/afero" +) + +var AppFs = afero.NewOsFs() + +const ( + FlagConfig = "config" + defaultCfgFileName = "zetatool_config.json" + ZetaURL = "127.0.0.1:1317" + BtcExplorerURL = "https://blockstream.info/api/" + EthRPCURL = "https://ethereum-rpc.publicnode.com" + ConnectorAddress = "0x000007Cf399229b2f5A4D043F20E90C9C98B7C6a" + CustodyAddress = "0x0000030Ec64DF25301d8414eE5a29588C4B0dE10" +) + +// Config is a struct the defines the configuration fields used by zetatool +type Config struct { + ZetaURL string + BtcExplorerURL string + EthRPCURL string + EtherscanAPIkey string + ConnectorAddress string + CustodyAddress string +} + +func DefaultConfig() *Config { + return &Config{ + ZetaURL: ZetaURL, + BtcExplorerURL: BtcExplorerURL, + EthRPCURL: EthRPCURL, + ConnectorAddress: ConnectorAddress, + CustodyAddress: CustodyAddress, + } +} + +func (c *Config) Save() error { + file, err := json.MarshalIndent(c, "", " ") + if err != nil { + return err + } + err = afero.WriteFile(AppFs, defaultCfgFileName, file, 0600) + return err +} + +func (c *Config) Read(filename string) error { + data, err := afero.ReadFile(AppFs, filename) + if err != nil { + return err + } + err = json.Unmarshal(data, c) + return err +} + +func GetConfig(filename string) (*Config, error) { + //Check if cfgFile is empty, if so return default Config and save to file + if filename == "" { + cfg := DefaultConfig() + err := cfg.Save() + return cfg, err + } + + //if file is specified, open file and return struct + cfg := &Config{} + err := cfg.Read(filename) + return cfg, err +} diff --git a/cmd/zetatool/config/config_test.go b/cmd/zetatool/config/config_test.go new file mode 100644 index 0000000000..dd56604d5f --- /dev/null +++ b/cmd/zetatool/config/config_test.go @@ -0,0 +1,77 @@ +package config + +import ( + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/require" +) + +func TestDefaultConfig(t *testing.T) { + cfg := DefaultConfig() + require.Equal(t, cfg.EthRPCURL, EthRPCURL) + require.Equal(t, cfg.ZetaURL, ZetaURL) + require.Equal(t, cfg.BtcExplorerURL, BtcExplorerURL) + require.Equal(t, cfg.ConnectorAddress, ConnectorAddress) + require.Equal(t, cfg.CustodyAddress, CustodyAddress) +} + +func TestGetConfig(t *testing.T) { + AppFs = afero.NewMemMapFs() + defaultCfg := DefaultConfig() + + t.Run("No config file specified", func(t *testing.T) { + cfg, err := GetConfig("") + require.NoError(t, err) + require.Equal(t, cfg, defaultCfg) + + exists, err := afero.Exists(AppFs, defaultCfgFileName) + require.NoError(t, err) + require.True(t, exists) + }) + + t.Run("config file specified", func(t *testing.T) { + cfg, err := GetConfig(defaultCfgFileName) + require.NoError(t, err) + require.Equal(t, cfg, defaultCfg) + }) +} + +func TestConfig_Read(t *testing.T) { + AppFs = afero.NewMemMapFs() + cfg, err := GetConfig("") + require.NoError(t, err) + + t.Run("read existing file", func(t *testing.T) { + c := &Config{} + err := c.Read(defaultCfgFileName) + require.NoError(t, err) + require.Equal(t, c, cfg) + }) + + t.Run("read non-existent file", func(t *testing.T) { + err := AppFs.Remove(defaultCfgFileName) + require.NoError(t, err) + c := &Config{} + err = c.Read(defaultCfgFileName) + require.ErrorContains(t, err, "file does not exist") + require.NotEqual(t, c, cfg) + }) +} + +func TestConfig_Save(t *testing.T) { + AppFs = afero.NewMemMapFs() + cfg := DefaultConfig() + cfg.EtherscanAPIkey = "DIFFERENTAPIKEY" + + t.Run("save modified cfg", func(t *testing.T) { + err := cfg.Save() + require.NoError(t, err) + + newCfg, err := GetConfig(defaultCfgFileName) + require.NoError(t, err) + require.Equal(t, cfg, newCfg) + }) + + // Should test invalid json encoding but currently not able to without interface +} diff --git a/cmd/zetatool/filterdeposit/btc.go b/cmd/zetatool/filterdeposit/btc.go new file mode 100644 index 0000000000..bc2397523d --- /dev/null +++ b/cmd/zetatool/filterdeposit/btc.go @@ -0,0 +1,188 @@ +package filterdeposit + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/spf13/cobra" + "github.com/zeta-chain/zetacore/cmd/zetatool/config" + "github.com/zeta-chain/zetacore/common" +) + +func NewBtcCmd() *cobra.Command { + return &cobra.Command{ + Use: "btc", + Short: "Filter inbound btc deposits", + RunE: FilterBTCTransactions, + } +} + +// FilterBTCTransactions is a command that queries the bitcoin explorer for inbound transactions that qualify for +// cross chain transactions. +func FilterBTCTransactions(cmd *cobra.Command, _ []string) error { + configFile, err := cmd.Flags().GetString(config.FlagConfig) + fmt.Println("config file name: ", configFile) + if err != nil { + return err + } + btcChainID, err := cmd.Flags().GetString(BTCChainIDFlag) + if err != nil { + return err + } + cfg, err := config.GetConfig(configFile) + if err != nil { + return err + } + fmt.Println("getting tss Address") + res, err := GetTssAddress(cfg, btcChainID) + if err != nil { + return err + } + fmt.Println("got tss Address") + list, err := getHashList(cfg, res.Btc) + if err != nil { + return err + } + + _, err = CheckForCCTX(list, cfg) + return err +} + +// getHashList is called by FilterBTCTransactions to help query and filter inbound transactions on btc +func getHashList(cfg *config.Config, tssAddress string) ([]Deposit, error) { + var list []Deposit + lastHash := "" + + // Setup URL for query + btcURL, err := url.JoinPath(cfg.BtcExplorerURL, "address", tssAddress, "txs") + if err != nil { + return list, err + } + + // This loop will query the bitcoin explorer for transactions associated with the TSS address. Since the api only + // allows a response of 25 transactions per request, several requests will be required in order to retrieve a + // complete list. + for { + // The Next Query is determined by the last transaction hash provided by the previous response. + nextQuery := btcURL + if lastHash != "" { + nextQuery, err = url.JoinPath(btcURL, "chain", lastHash) + if err != nil { + return list, err + } + } + // #nosec G107 url must be variable + res, getErr := http.Get(nextQuery) + if getErr != nil { + return list, getErr + } + + body, readErr := ioutil.ReadAll(res.Body) + if readErr != nil { + return list, readErr + } + closeErr := res.Body.Close() + if closeErr != nil { + return list, closeErr + } + + // NOTE: decoding json from request dynamically is not ideal, however there isn't a detailed, defined data structure + // provided by blockstream. Will need to create one in the future using following definition: + // https://github.com/Blockstream/esplora/blob/master/API.md#transaction-format + var txns []map[string]interface{} + err := json.Unmarshal(body, &txns) + if err != nil { + return list, err + } + + if len(txns) == 0 { + break + } + + fmt.Println("Length of txns: ", len(txns)) + + // The "/address" blockstream api provides a maximum of 25 transactions associated with a given address. This + // loop will iterate over that list of transactions to determine whether each transaction can be considered + // a deposit to ZetaChain. + for _, txn := range txns { + // Get tx hash of the current transaction + hash := txn["txid"].(string) + + // Read the first output of the transaction and parse the destination address. + // This address should be the TSS address. + vout := txn["vout"].([]interface{}) + vout0 := vout[0].(map[string]interface{}) + var vout1 map[string]interface{} + if len(vout) > 1 { + vout1 = vout[1].(map[string]interface{}) + } else { + continue + } + _, found := vout0["scriptpubkey"] + scriptpubkey := "" + if found { + scriptpubkey = vout0["scriptpubkey"].(string) + } + _, found = vout0["scriptpubkey_address"] + targetAddr := "" + if found { + targetAddr = vout0["scriptpubkey_address"].(string) + } + + //Check if txn is confirmed + status := txn["status"].(map[string]interface{}) + confirmed := status["confirmed"].(bool) + if !confirmed { + continue + } + + //Filter out deposits less than min base fee + if vout0["value"].(float64) < 1360 { + continue + } + + //Check if Deposit is a donation + scriptpubkey1 := vout1["scriptpubkey"].(string) + if len(scriptpubkey1) >= 4 && scriptpubkey1[:2] == "6a" { + memoSize, err := strconv.ParseInt(scriptpubkey1[2:4], 16, 32) + if err != nil { + continue + } + if int(memoSize) != (len(scriptpubkey1)-4)/2 { + continue + } + memoBytes, err := hex.DecodeString(scriptpubkey1[4:]) + if err != nil { + continue + } + if bytes.Equal(memoBytes, []byte(common.DonationMessage)) { + continue + } + } else { + continue + } + + //Make sure Deposit is sent to correct tss address + if strings.Compare("0014", scriptpubkey[:4]) == 0 && targetAddr == tssAddress { + entry := Deposit{ + hash, + // #nosec G701 parsing json requires float64 type from blockstream + uint64(vout0["value"].(float64)), + } + list = append(list, entry) + } + } + + lastTxn := txns[len(txns)-1] + lastHash = lastTxn["txid"].(string) + } + + return list, nil +} diff --git a/cmd/zetatool/filterdeposit/evm.go b/cmd/zetatool/filterdeposit/evm.go new file mode 100644 index 0000000000..73e1f8b78f --- /dev/null +++ b/cmd/zetatool/filterdeposit/evm.go @@ -0,0 +1,240 @@ +package filterdeposit + +import ( + "context" + "fmt" + "log" + "math/big" + "strings" + + "github.com/zeta-chain/zetacore/zetaclient/evm" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/nanmu42/etherscan-api" + "github.com/spf13/cobra" + "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol" + "github.com/zeta-chain/zetacore/cmd/zetatool/config" + zetacommon "github.com/zeta-chain/zetacore/common" +) + +const ( + EvmMaxRangeFlag = "evm-max-range" + EvmStartBlockFlag = "evm-start-block" +) + +func NewEvmCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "eth", + Short: "Filter inbound eth deposits", + RunE: FilterEVMTransactions, + } + + cmd.Flags().Uint64(EvmMaxRangeFlag, 1000, "number of blocks to scan per iteration") + cmd.Flags().Uint64(EvmStartBlockFlag, 19463725, "block height to start scanning from") + + return cmd +} + +// FilterEVMTransactions is a command that queries an EVM explorer and Contracts for inbound transactions that qualify +// for cross chain transactions. +func FilterEVMTransactions(cmd *cobra.Command, _ []string) error { + // Get flags + configFile, err := cmd.Flags().GetString(config.FlagConfig) + if err != nil { + return err + } + startBlock, err := cmd.Flags().GetUint64(EvmStartBlockFlag) + if err != nil { + return err + } + blockRange, err := cmd.Flags().GetUint64(EvmMaxRangeFlag) + if err != nil { + return err + } + btcChainID, err := cmd.Flags().GetString(BTCChainIDFlag) + if err != nil { + return err + } + // Scan for deposits + cfg, err := config.GetConfig(configFile) + if err != nil { + log.Fatal(err) + } + res, err := GetTssAddress(cfg, btcChainID) + if err != nil { + return err + } + list, err := GetEthHashList(cfg, res.Eth, startBlock, blockRange) + if err != nil { + return err + } + _, err = CheckForCCTX(list, cfg) + return err +} + +// GetEthHashList is a helper function querying total inbound txns by segments of blocks in ranges defined by the config +func GetEthHashList(cfg *config.Config, tssAddress string, startBlock uint64, blockRange uint64) ([]Deposit, error) { + client, err := ethclient.Dial(cfg.EthRPCURL) + if err != nil { + return []Deposit{}, err + } + fmt.Println("Connection successful") + + header, err := client.HeaderByNumber(context.Background(), nil) + if err != nil { + return []Deposit{}, err + } + latestBlock := header.Number.Uint64() + fmt.Println("latest Block: ", latestBlock) + + endBlock := startBlock + blockRange + deposits := make([]Deposit, 0) + segment := 0 + for startBlock < latestBlock { + fmt.Printf("adding segment: %d, startblock: %d\n", segment, startBlock) + segmentRes, err := GetHashListSegment(client, startBlock, endBlock, tssAddress, cfg) + if err != nil { + fmt.Println(err.Error()) + continue + } + deposits = append(deposits, segmentRes...) + startBlock = endBlock + endBlock = endBlock + blockRange + if endBlock > latestBlock { + endBlock = latestBlock + } + segment++ + } + return deposits, nil +} + +// GetHashListSegment queries and filters deposits for a given range +func GetHashListSegment( + client *ethclient.Client, + startBlock uint64, + endBlock uint64, + tssAddress string, + cfg *config.Config) ([]Deposit, error) { + + deposits := make([]Deposit, 0) + connectorAddress := common.HexToAddress(cfg.ConnectorAddress) + connectorContract, err := zetaconnector.NewZetaConnectorNonEth(connectorAddress, client) + if err != nil { + return deposits, err + } + erc20CustodyAddress := common.HexToAddress(cfg.CustodyAddress) + erc20CustodyContract, err := erc20custody.NewERC20Custody(erc20CustodyAddress, client) + if err != nil { + return deposits, err + } + + custodyIter, err := erc20CustodyContract.FilterDeposited(&bind.FilterOpts{ + Start: startBlock, + End: &endBlock, + Context: context.TODO(), + }, []common.Address{}) + if err != nil { + return deposits, err + } + + connectorIter, err := connectorContract.FilterZetaSent(&bind.FilterOpts{ + Start: startBlock, + End: &endBlock, + Context: context.TODO(), + }, []common.Address{}, []*big.Int{}) + if err != nil { + return deposits, err + } + + // Get ERC20 Custody Deposit events + for custodyIter.Next() { + // sanity check tx event + err := CheckEvmTxLog(&custodyIter.Event.Raw, erc20CustodyAddress, "", evm.TopicsDeposited) + if err == nil { + deposits = append(deposits, Deposit{ + TxID: custodyIter.Event.Raw.TxHash.Hex(), + Amount: custodyIter.Event.Amount.Uint64(), + }) + } + } + + // Get Connector ZetaSent events + for connectorIter.Next() { + // sanity check tx event + err := CheckEvmTxLog(&connectorIter.Event.Raw, connectorAddress, "", evm.TopicsZetaSent) + if err == nil { + deposits = append(deposits, Deposit{ + TxID: connectorIter.Event.Raw.TxHash.Hex(), + Amount: connectorIter.Event.ZetaValueAndGas.Uint64(), + }) + } + } + + // Get Transactions sent directly to TSS address + tssDeposits, err := getTSSDeposits(tssAddress, startBlock, endBlock, cfg.EtherscanAPIkey) + if err != nil { + return deposits, err + } + deposits = append(deposits, tssDeposits...) + + return deposits, nil +} + +// getTSSDeposits more specifically queries and filters deposits based on direct transfers the TSS address. +func getTSSDeposits(tssAddress string, startBlock uint64, endBlock uint64, apiKey string) ([]Deposit, error) { + client := etherscan.New(etherscan.Mainnet, apiKey) + deposits := make([]Deposit, 0) + + // #nosec G701 these block numbers need to be *int for this particular client package + startInt := int(startBlock) + // #nosec G701 + endInt := int(endBlock) + txns, err := client.NormalTxByAddress(tssAddress, &startInt, &endInt, 0, 0, true) + if err != nil { + return deposits, err + } + + fmt.Println("getTSSDeposits - Num of transactions: ", len(txns)) + + for _, tx := range txns { + if tx.To == tssAddress { + if strings.Compare(tx.Input, zetacommon.DonationMessage) == 0 { + continue // skip donation tx + } + if tx.TxReceiptStatus != "1" { + continue + } + //fmt.Println("getTSSDeposits - adding Deposit") + deposits = append(deposits, Deposit{ + TxID: tx.Hash, + Amount: tx.Value.Int().Uint64(), + }) + } + } + + return deposits, nil +} + +// CheckEvmTxLog is a helper function used to validate receipts, logic is taken from zetaclient. +func CheckEvmTxLog(vLog *ethtypes.Log, wantAddress common.Address, wantHash string, wantTopics int) error { + if vLog.Removed { + return fmt.Errorf("log is removed, chain reorg?") + } + if vLog.Address != wantAddress { + return fmt.Errorf("log emitter address mismatch: want %s got %s", wantAddress.Hex(), vLog.Address.Hex()) + } + if vLog.TxHash.Hex() == "" { + return fmt.Errorf("log tx hash is empty: %d %s", vLog.BlockNumber, vLog.TxHash.Hex()) + } + if wantHash != "" && vLog.TxHash.Hex() != wantHash { + return fmt.Errorf("log tx hash mismatch: want %s got %s", wantHash, vLog.TxHash.Hex()) + } + if len(vLog.Topics) != wantTopics { + return fmt.Errorf("number of topics mismatch: want %d got %d", wantTopics, len(vLog.Topics)) + } + return nil +} diff --git a/cmd/zetatool/filterdeposit/filterdeposit.go b/cmd/zetatool/filterdeposit/filterdeposit.go new file mode 100644 index 0000000000..73a2601cd8 --- /dev/null +++ b/cmd/zetatool/filterdeposit/filterdeposit.go @@ -0,0 +1,122 @@ +package filterdeposit + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + + "github.com/spf13/cobra" + "github.com/zeta-chain/zetacore/cmd/zetatool/config" + "github.com/zeta-chain/zetacore/x/observer/types" +) + +const ( + BTCChainIDFlag = "btc-chain-id" +) + +func NewFilterDepositCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "filterdeposit", + Short: "filter missing inbound deposits", + } + + cmd.AddCommand(NewBtcCmd()) + cmd.AddCommand(NewEvmCmd()) + + // Required for TSS address query + cmd.PersistentFlags().String(BTCChainIDFlag, "8332", "chain id used on zetachain to identify bitcoin - default: 8332") + + return cmd +} + +// Deposit is a data structure for keeping track of inbound transactions +type Deposit struct { + TxID string + Amount uint64 +} + +// CheckForCCTX is querying zeta core for a cctx associated with a confirmed transaction hash. If the cctx is not found, +// then the transaction hash is added to the list of missed inbound transactions. +func CheckForCCTX(list []Deposit, cfg *config.Config) ([]Deposit, error) { + var missedList []Deposit + + fmt.Println("Going through list, num of transactions: ", len(list)) + for _, entry := range list { + zetaURL, err := url.JoinPath(cfg.ZetaURL, "zeta-chain", "crosschain", "in_tx_hash_to_cctx_data", entry.TxID) + if err != nil { + return missedList, err + } + + request, err := http.NewRequest(http.MethodGet, zetaURL, nil) + if err != nil { + return missedList, err + } + request.Header.Add("Accept", "application/json") + client := &http.Client{} + + response, getErr := client.Do(request) + if getErr != nil { + return missedList, getErr + } + + data, readErr := ioutil.ReadAll(response.Body) + if readErr != nil { + return missedList, readErr + } + closeErr := response.Body.Close() + if closeErr != nil { + return missedList, closeErr + } + + var cctx map[string]interface{} + err = json.Unmarshal(data, &cctx) + if err != nil { + return missedList, err + } + + // successful query of the given cctx will not contain a "message" field with value "not found", if it was not + // found then it is added to the missing list. + if _, ok := cctx["message"]; ok { + if strings.Compare(cctx["message"].(string), "not found") == 0 { + missedList = append(missedList, entry) + } + } + } + + fmt.Printf("Found %d missed transactions.\n", len(missedList)) + for _, entry := range missedList { + fmt.Printf("%s, amount: %d\n", entry.TxID, entry.Amount) + } + return missedList, nil +} + +func GetTssAddress(cfg *config.Config, btcChainID string) (*types.QueryGetTssAddressResponse, error) { + res := &types.QueryGetTssAddressResponse{} + requestURL, err := url.JoinPath(cfg.ZetaURL, "zeta-chain", "observer", "get_tss_address", btcChainID) + if err != nil { + return res, err + } + request, err := http.NewRequest(http.MethodGet, requestURL, nil) + if err != nil { + return res, err + } + request.Header.Add("Accept", "application/json") + zetacoreHTTPClient := &http.Client{} + response, getErr := zetacoreHTTPClient.Do(request) + if getErr != nil { + return res, err + } + data, readErr := ioutil.ReadAll(response.Body) + if readErr != nil { + return res, err + } + closeErr := response.Body.Close() + if closeErr != nil { + return res, closeErr + } + err = json.Unmarshal(data, res) + return res, err +} diff --git a/cmd/zetatool/filterdeposit/filterdeposit_test.go b/cmd/zetatool/filterdeposit/filterdeposit_test.go new file mode 100644 index 0000000000..2140a43c08 --- /dev/null +++ b/cmd/zetatool/filterdeposit/filterdeposit_test.go @@ -0,0 +1,97 @@ +package filterdeposit + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/cmd/zetatool/config" + "github.com/zeta-chain/zetacore/x/crosschain/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" +) + +func TestCheckForCCTX(t *testing.T) { + t.Run("no missed inbound txns found", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/zeta-chain/crosschain/in_tx_hash_to_cctx_data/0x093f4ca4c1884df0fd9dd59b75979342ded29d3c9b6861644287a2e1417b9a39" { + t.Errorf("Expected to request '/zeta-chain', got: %s", r.URL.Path) + } + w.WriteHeader(http.StatusOK) + //Return CCtx + cctx := types.CrossChainTx{} + bytes, err := json.Marshal(cctx) + require.NoError(t, err) + _, err = w.Write(bytes) + require.NoError(t, err) + })) + defer server.Close() + + deposits := []Deposit{{ + TxID: "0x093f4ca4c1884df0fd9dd59b75979342ded29d3c9b6861644287a2e1417b9a39", + Amount: uint64(657177295293237048), + }} + cfg := config.DefaultConfig() + cfg.ZetaURL = server.URL + missedInbounds, err := CheckForCCTX(deposits, cfg) + require.NoError(t, err) + require.Equal(t, 0, len(missedInbounds)) + }) + + t.Run("1 missed inbound txn found", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("{\n \"code\": 5,\n \"message\": \"not found\",\n \"details\": [\n ]\n}")) + require.NoError(t, err) + })) + defer server.Close() + + deposits := []Deposit{{ + TxID: "0x093f4ca4c1884df0fd9dd59b75979342ded29d3c9b6861644287a2e1417b9a39", + Amount: uint64(657177295293237048), + }} + cfg := config.DefaultConfig() + cfg.ZetaURL = server.URL + missedInbounds, err := CheckForCCTX(deposits, cfg) + require.NoError(t, err) + require.Equal(t, 1, len(missedInbounds)) + }) +} + +func TestGetTssAddress(t *testing.T) { + t.Run("should run successfully", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/zeta-chain/observer/get_tss_address/8332" { + t.Errorf("Expected to request '/zeta-chain', got: %s", r.URL.Path) + } + w.WriteHeader(http.StatusOK) + response := observertypes.QueryGetTssAddressResponse{} + bytes, err := json.Marshal(response) + require.NoError(t, err) + _, err = w.Write(bytes) + require.NoError(t, err) + })) + cfg := config.DefaultConfig() + cfg.ZetaURL = server.URL + _, err := GetTssAddress(cfg, "8332") + require.NoError(t, err) + }) + + t.Run("bad request", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/zeta-chain/observer/get_tss_address/8332" { + w.WriteHeader(http.StatusBadRequest) + response := observertypes.QueryGetTssAddressResponse{} + bytes, err := json.Marshal(response) + require.NoError(t, err) + _, err = w.Write(bytes) + require.NoError(t, err) + } + })) + cfg := config.DefaultConfig() + cfg.ZetaURL = server.URL + _, err := GetTssAddress(cfg, "8332") + require.Error(t, err) + }) +} diff --git a/cmd/zetatool/main.go b/cmd/zetatool/main.go new file mode 100644 index 0000000000..584d21b747 --- /dev/null +++ b/cmd/zetatool/main.go @@ -0,0 +1,28 @@ +package main + +import ( + "fmt" + "os" + + "github.com/zeta-chain/zetacore/cmd/zetatool/filterdeposit" + + "github.com/spf13/cobra" + "github.com/zeta-chain/zetacore/cmd/zetatool/config" +) + +var rootCmd = &cobra.Command{ + Use: "zetatool", + Short: "utility tool for zeta-chain", +} + +func init() { + rootCmd.AddCommand(filterdeposit.NewFilterDepositCmd()) + rootCmd.PersistentFlags().String(config.FlagConfig, "", "custom config file: --config filename.json") +} + +func main() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/codecov.yml b/codecov.yml index 948e5aef5f..93d974911b 100644 --- a/codecov.yml +++ b/codecov.yml @@ -68,4 +68,6 @@ ignore: - "scripts/" - "server/" - "testutil/" + - "testutils/" + - "errors/" - "typescript/" \ No newline at end of file diff --git a/common/bitcoin/proof_test.go b/common/bitcoin/proof_test.go new file mode 100644 index 0000000000..7e3ae1ff51 --- /dev/null +++ b/common/bitcoin/proof_test.go @@ -0,0 +1 @@ +package bitcoin_test diff --git a/common/constant.go b/common/constant.go index 2e10d21b87..8280246a80 100644 --- a/common/constant.go +++ b/common/constant.go @@ -1,9 +1,6 @@ package common const ( - // DefaultGasPriceMultiplier is the default gas price multiplier for outbond txs - DefaultGasPriceMultiplier = 2 - // DonationMessage is the message for donation transactions // Transaction sent to the TSS or ERC20 Custody address containing this message are considered as a donation DonationMessage = "I am rich!" diff --git a/common/cosmos/cosmos_test.go b/common/cosmos/cosmos_test.go new file mode 100644 index 0000000000..712dd321d1 --- /dev/null +++ b/common/cosmos/cosmos_test.go @@ -0,0 +1 @@ +package cosmos_test diff --git a/docs/cli/zetatool/filterdeposit.md b/docs/cli/zetatool/filterdeposit.md new file mode 100644 index 0000000000..cec577a1e1 --- /dev/null +++ b/docs/cli/zetatool/filterdeposit.md @@ -0,0 +1,29 @@ +# filterdeposit + +Filter missing inbound deposits + +### Synopsis + +Filters relevant inbound transactions for a given network and attempts to find an associated cctx from zetacore. If a +cctx is not found, the associated transaction hash and amount is added to a list and displayed. + +``` +zetatool filterdeposit [command] +``` +### Options + +``` +Available Commands: +btc Filter inbound btc deposits +eth Filter inbound eth deposits +``` + +### Flags +``` +--btc-chain-id string chain id used on zetachain to identify bitcoin - default: 8332 (default "8332") +``` + +### Options inherited from parent commands +``` +--config string custom config file: --config filename.json +``` \ No newline at end of file diff --git a/docs/cli/zetatool/readme.md b/docs/cli/zetatool/readme.md new file mode 100644 index 0000000000..c35d5009a7 --- /dev/null +++ b/docs/cli/zetatool/readme.md @@ -0,0 +1,55 @@ +# Zeta Tool + +Currently, has only one subcommand which finds inbound transactions or deposits that weren't observed on a particular +network. `filterdeposit` + +## Configuring + +#### RPC endpoints +Configuring the tool for specific networks will require different reliable endpoints. For example, if you wanted to +configure an ethereum rpc endpoint, then you will have to find an evm rpc endpoint for eth mainnet and set the field: +`EthRPCURL` + +#### Zeta URL +You will need to find an enpoint for zetachain and set the field: `ZetaURL` + +#### Contract Addresses +Depending on the network, connector and custody contract addresses must be set using these fields: `ConnectorAddress`, +`CustodyAddress` + +If a configuration file is not provided, a default config will be generated under the name +`zetatool_config.json`. Below is an example of a configuration file used for mainnet: + +#### Etherscan API Key +In order to make requests to etherscan, an api key will need to be configured. + +``` +{ + "ZetaURL": "", + "BtcExplorerURL": "https://blockstream.info/api/", + "EthRPCURL": "https://ethereum-rpc.publicnode.com", + "EtherscanAPIkey": "", + "ConnectorAddress": "0x000007Cf399229b2f5A4D043F20E90C9C98B7C6a", + "CustodyAddress": "0x0000030Ec64DF25301d8414eE5a29588C4B0dE10" +} +``` + +## Running Tool + +There are two targets available: + +``` +filter-missed-btc: install-zetatool + ./tool/filter_missed_deposits/filter_missed_btc.sh + +filter-missed-eth: install-zetatool + ./tool/filter_missed_deposits/filter_missed_eth.sh +``` + +Running the commands can be simply done through the makefile in the node repo: + +``` +make filter-missed-btc +or ... +make filter-missed-eth +``` \ No newline at end of file diff --git a/docs/spec/observer/messages.md b/docs/spec/observer/messages.md index 508225f6af..0f784a7831 100644 --- a/docs/spec/observer/messages.md +++ b/docs/spec/observer/messages.md @@ -111,14 +111,13 @@ message MsgAddBlockHeader { ## MsgResetChainNonces ResetChainNonces handles resetting chain nonces -Authorized: policy group admin ```proto message MsgResetChainNonces { string creator = 1; int64 chain_id = 2; - uint64 chain_nonce_low = 3; - uint64 chain_nonce_high = 4; + int64 chain_nonce_low = 3; + int64 chain_nonce_high = 4; } ``` diff --git a/e2e/runner/e2etest.go b/e2e/runner/e2etest.go new file mode 100644 index 0000000000..499d45144d --- /dev/null +++ b/e2e/runner/e2etest.go @@ -0,0 +1,105 @@ +package runner + +import "fmt" + +// E2ETestFunc is a function representing a E2E test +// It takes a E2ERunner as an argument +type E2ETestFunc func(*E2ERunner, []string) + +// E2ETest represents a E2E test with a name, args, description and test func +type E2ETest struct { + Name string + Description string + Args []string + ArgsDefinition []ArgDefinition + E2ETest E2ETestFunc +} + +// NewE2ETest creates a new instance of E2ETest with specified parameters. +func NewE2ETest(name, description string, argsDefinition []ArgDefinition, e2eTestFunc E2ETestFunc) E2ETest { + return E2ETest{ + Name: name, + Description: description, + ArgsDefinition: argsDefinition, + E2ETest: e2eTestFunc, + Args: []string{}, + } +} + +// ArgDefinition defines a structure for holding an argument's description along with it's default value. +type ArgDefinition struct { + Description string + DefaultValue string +} + +// DefaultArgs extracts and returns array of default arguments from the ArgsDefinition. +func (e E2ETest) DefaultArgs() []string { + defaultArgs := make([]string, len(e.ArgsDefinition)) + for i, spec := range e.ArgsDefinition { + defaultArgs[i] = spec.DefaultValue + } + return defaultArgs +} + +// ArgsDescription returns a string representing the arguments description in a readable format. +func (e E2ETest) ArgsDescription() string { + argsDescription := "" + for _, def := range e.ArgsDefinition { + argDesc := fmt.Sprintf("%s (%s)", def.Description, def.DefaultValue) + if argsDescription != "" { + argsDescription += ", " + } + argsDescription += argDesc + } + return argsDescription +} + +// E2ETestRunConfig defines the basic configuration for initiating an E2E test, including its name and optional runtime arguments. +type E2ETestRunConfig struct { + Name string + Args []string +} + +// GetE2ETestsToRunByName prepares a list of E2ETests to run based on given test names without arguments +func (runner *E2ERunner) GetE2ETestsToRunByName(availableTests []E2ETest, testNames ...string) ([]E2ETest, error) { + tests := []E2ETestRunConfig{} + for _, testName := range testNames { + tests = append(tests, E2ETestRunConfig{ + Name: testName, + Args: []string{}, + }) + } + return runner.GetE2ETestsToRunByConfig(availableTests, tests) +} + +// GetE2ETestsToRunByConfig prepares a list of E2ETests to run based on provided test names and their corresponding arguments +func (runner *E2ERunner) GetE2ETestsToRunByConfig(availableTests []E2ETest, testConfigs []E2ETestRunConfig) ([]E2ETest, error) { + tests := []E2ETest{} + for _, testSpec := range testConfigs { + e2eTest, found := findE2ETestByName(availableTests, testSpec.Name) + if !found { + return nil, fmt.Errorf("e2e test %s not found", testSpec.Name) + } + e2eTestToRun := NewE2ETest( + e2eTest.Name, + e2eTest.Description, + e2eTest.ArgsDefinition, + e2eTest.E2ETest, + ) + // update e2e test args + e2eTestToRun.Args = testSpec.Args + tests = append(tests, e2eTestToRun) + } + + return tests, nil +} + +// findE2ETest finds a e2e test by name +func findE2ETestByName(e2eTests []E2ETest, e2eTestName string) (E2ETest, bool) { + for _, test := range e2eTests { + if test.Name == e2eTestName { + return test, true + } + } + return E2ETest{}, false +} diff --git a/e2e/runner/report.go b/e2e/runner/report.go index a502c442dd..e798eadd4b 100644 --- a/e2e/runner/report.go +++ b/e2e/runner/report.go @@ -5,9 +5,16 @@ import ( "strings" "text/tabwriter" "time" + + sdkmath "cosmossdk.io/math" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/zeta-chain/zetacore/cmd/zetacored/config" + "github.com/zeta-chain/zetacore/e2e/txserver" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" ) -// TestReport is a struct that contains the test report +// TestReport is a struct that contains the report for a specific e2e test +// It can be generated with the RunE2ETestsIntoReport method type TestReport struct { Name string Success bool @@ -53,3 +60,70 @@ func (runner *E2ERunner) PrintTestReports(tr TestReports) { } runner.Logger.PrintNoPrefix(table) } + +// NetworkReport is a struct that contains the report for the network used after running e2e tests +// This report has been initialized to check the emissions pool balance and if the pool is decreasing +// TODO: add more complete data and validation to the network +// https://github.com/zeta-chain/node/issues/1873 +type NetworkReport struct { + EmissionsPoolBalance sdkmath.Int + Height uint64 + CctxCount int +} + +// Validate validates the network report +// This method is used to validate the network after running e2e tests +// It checks the emissions pool balance and if the pool is decreasing +func (nr NetworkReport) Validate() error { + if nr.EmissionsPoolBalance.GTE(sdkmath.NewIntFromBigInt(EmissionsPoolFunding)) { + return fmt.Errorf( + "emissions pool balance is not decreasing, expected less than %s, got %s", + EmissionsPoolFunding, + nr.EmissionsPoolBalance, + ) + } + return nil +} + +// GenerateNetworkReport generates a report for the network used after running e2e tests +func (runner *E2ERunner) GenerateNetworkReport() (NetworkReport, error) { + // get the emissions pool balance + balanceRes, err := runner.BankClient.Balance(runner.Ctx, &banktypes.QueryBalanceRequest{ + Address: txserver.EmissionsPoolAddress, + Denom: config.BaseDenom, + }) + if err != nil { + return NetworkReport{}, err + } + emissionsPoolBalance := balanceRes.Balance + + // fetch the height and number of cctxs, this gives a better idea on the activity of the network + + // get the block height + blockRes, err := runner.ZEVMClient.BlockNumber(runner.Ctx) + if err != nil { + return NetworkReport{}, err + } + + // get the number of cctxs + cctxsRes, err := runner.CctxClient.CctxAll(runner.Ctx, &crosschaintypes.QueryAllCctxRequest{}) + if err != nil { + return NetworkReport{}, err + } + cctxCount := len(cctxsRes.CrossChainTx) + + return NetworkReport{ + EmissionsPoolBalance: emissionsPoolBalance.Amount, + Height: blockRes, + CctxCount: cctxCount, + }, nil +} + +// PrintNetworkReport prints the network report +func (runner *E2ERunner) PrintNetworkReport(nr NetworkReport) { + runner.Logger.Print(" ---📈 Network Report ---") + runner.Logger.Print("Block Height: %d", nr.Height) + runner.Logger.Print("CCTX Processed: %d", nr.CctxCount) + runner.Logger.Print("Emissions Pool Balance: %sZETA", nr.EmissionsPoolBalance.Quo(sdkmath.NewIntFromUint64(1e18))) + +} diff --git a/e2e/runner/run.go b/e2e/runner/run.go new file mode 100644 index 0000000000..f24ff9d9f8 --- /dev/null +++ b/e2e/runner/run.go @@ -0,0 +1,94 @@ +package runner + +import ( + "fmt" + "runtime" + "time" +) + +// RunE2ETests runs a list of e2e tests +func (runner *E2ERunner) RunE2ETests(e2eTests []E2ETest) (err error) { + for _, e2eTest := range e2eTests { + if err := runner.RunE2ETest(e2eTest, true); err != nil { + return err + } + } + return nil +} + +// RunE2ETest runs a e2e test +func (runner *E2ERunner) RunE2ETest(e2eTest E2ETest, checkAccounting bool) (err error) { + // return an error on panic + // https://github.com/zeta-chain/node/issues/1500 + defer func() { + if r := recover(); r != nil { + // print stack trace + stack := make([]byte, 4096) + n := runtime.Stack(stack, false) + err = fmt.Errorf("%s failed: %v, stack trace %s", e2eTest.Name, r, stack[:n]) + } + }() + + startTime := time.Now() + runner.Logger.Print("⏳running - %s", e2eTest.Description) + + // run e2e test, if args are not provided, use default args + args := e2eTest.Args + if len(args) == 0 { + args = e2eTest.DefaultArgs() + } + e2eTest.E2ETest(runner, args) + + //check supplies + if checkAccounting { + if err := runner.CheckZRC20ReserveAndSupply(); err != nil { + return err + } + } + + runner.Logger.Print("✅ completed in %s - %s", time.Since(startTime), e2eTest.Description) + + return err +} + +// RunE2ETestsIntoReport runs a list of e2e tests by name in a list of e2e tests and returns a report +// The function doesn't return an error, it returns a report with the error +func (runner *E2ERunner) RunE2ETestsIntoReport(e2eTests []E2ETest) (TestReports, error) { + // go through all tests + reports := make(TestReports, 0, len(e2eTests)) + for _, test := range e2eTests { + // get info before test + balancesBefore, err := runner.GetAccountBalances(true) + if err != nil { + return nil, err + } + timeBefore := time.Now() + + // run test + testErr := runner.RunE2ETest(test, false) + if testErr != nil { + runner.Logger.Print("test %s failed: %s", test.Name, testErr.Error()) + } + + // wait 5 sec to make sure we get updated balances + time.Sleep(5 * time.Second) + + // get info after test + balancesAfter, err := runner.GetAccountBalances(true) + if err != nil { + return nil, err + } + timeAfter := time.Now() + + // create report + report := TestReport{ + Name: test.Name, + Success: testErr == nil, + Time: timeAfter.Sub(timeBefore), + GasSpent: GetAccountBalancesDiff(balancesBefore, balancesAfter), + } + reports = append(reports, report) + } + + return reports, nil +} diff --git a/e2e/runner/runner.go b/e2e/runner/runner.go index 79f95e9709..f66af62ff0 100644 --- a/e2e/runner/runner.go +++ b/e2e/runner/runner.go @@ -2,16 +2,9 @@ package runner import ( "context" - "fmt" - "runtime" "sync" "time" - "github.com/zeta-chain/zetacore/e2e/contracts/contextapp" - "github.com/zeta-chain/zetacore/e2e/contracts/erc20" - "github.com/zeta-chain/zetacore/e2e/contracts/zevmswap" - "github.com/zeta-chain/zetacore/e2e/txserver" - "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcutil" @@ -29,6 +22,10 @@ import ( "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-core/contracts/uniswapv2factory.sol" uniswapv2router "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" + "github.com/zeta-chain/zetacore/e2e/contracts/contextapp" + "github.com/zeta-chain/zetacore/e2e/contracts/erc20" + "github.com/zeta-chain/zetacore/e2e/contracts/zevmswap" + "github.com/zeta-chain/zetacore/e2e/txserver" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" @@ -160,195 +157,6 @@ func NewE2ERunner( } } -// E2ETestFunc is a function representing a E2E test -// It takes a E2ERunner as an argument -type E2ETestFunc func(*E2ERunner, []string) - -// E2ETest represents a E2E test with a name, args, description and test func -type E2ETest struct { - Name string - Description string - Args []string - ArgsDefinition []ArgDefinition - E2ETest E2ETestFunc -} - -// NewE2ETest creates a new instance of E2ETest with specified parameters. -func NewE2ETest(name, description string, argsDefinition []ArgDefinition, e2eTestFunc E2ETestFunc) E2ETest { - return E2ETest{ - Name: name, - Description: description, - ArgsDefinition: argsDefinition, - E2ETest: e2eTestFunc, - Args: []string{}, - } -} - -// ArgDefinition defines a structure for holding an argument's description along with it's default value. -type ArgDefinition struct { - Description string - DefaultValue string -} - -// DefaultArgs extracts and returns array of default arguments from the ArgsDefinition. -func (e E2ETest) DefaultArgs() []string { - defaultArgs := make([]string, len(e.ArgsDefinition)) - for i, spec := range e.ArgsDefinition { - defaultArgs[i] = spec.DefaultValue - } - return defaultArgs -} - -// ArgsDescription returns a string representing the arguments description in a readable format. -func (e E2ETest) ArgsDescription() string { - argsDescription := "" - for _, def := range e.ArgsDefinition { - argDesc := fmt.Sprintf("%s (%s)", def.Description, def.DefaultValue) - if argsDescription != "" { - argsDescription += ", " - } - argsDescription += argDesc - } - return argsDescription -} - -// E2ETestRunConfig defines the basic configuration for initiating an E2E test, including its name and optional runtime arguments. -type E2ETestRunConfig struct { - Name string - Args []string -} - -// GetE2ETestsToRunByName prepares a list of E2ETests to run based on given test names without arguments -func (runner *E2ERunner) GetE2ETestsToRunByName(availableTests []E2ETest, testNames ...string) ([]E2ETest, error) { - tests := []E2ETestRunConfig{} - for _, testName := range testNames { - tests = append(tests, E2ETestRunConfig{ - Name: testName, - Args: []string{}, - }) - } - return runner.GetE2ETestsToRunByConfig(availableTests, tests) -} - -// GetE2ETestsToRunByConfig prepares a list of E2ETests to run based on provided test names and their corresponding arguments -func (runner *E2ERunner) GetE2ETestsToRunByConfig(availableTests []E2ETest, testConfigs []E2ETestRunConfig) ([]E2ETest, error) { - tests := []E2ETest{} - for _, testSpec := range testConfigs { - e2eTest, found := findE2ETestByName(availableTests, testSpec.Name) - if !found { - return nil, fmt.Errorf("e2e test %s not found", testSpec.Name) - } - e2eTestToRun := NewE2ETest( - e2eTest.Name, - e2eTest.Description, - e2eTest.ArgsDefinition, - e2eTest.E2ETest, - ) - // update e2e test args - e2eTestToRun.Args = testSpec.Args - tests = append(tests, e2eTestToRun) - } - - return tests, nil -} - -// RunE2ETests runs a list of e2e tests -func (runner *E2ERunner) RunE2ETests(e2eTests []E2ETest) (err error) { - for _, e2eTest := range e2eTests { - if err := runner.RunE2ETest(e2eTest, true); err != nil { - return err - } - } - return nil -} - -// RunE2ETestsIntoReport runs a list of e2e tests by name in a list of e2e tests and returns a report -// The function doesn't return an error, it returns a report with the error -func (runner *E2ERunner) RunE2ETestsIntoReport(e2eTests []E2ETest) (TestReports, error) { - // go through all tests - reports := make(TestReports, 0, len(e2eTests)) - for _, test := range e2eTests { - // get info before test - balancesBefore, err := runner.GetAccountBalances(true) - if err != nil { - return nil, err - } - timeBefore := time.Now() - - // run test - testErr := runner.RunE2ETest(test, false) - if testErr != nil { - runner.Logger.Print("test %s failed: %s", test.Name, testErr.Error()) - } - - // wait 5 sec to make sure we get updated balances - time.Sleep(5 * time.Second) - - // get info after test - balancesAfter, err := runner.GetAccountBalances(true) - if err != nil { - return nil, err - } - timeAfter := time.Now() - - // create report - report := TestReport{ - Name: test.Name, - Success: testErr == nil, - Time: timeAfter.Sub(timeBefore), - GasSpent: GetAccountBalancesDiff(balancesBefore, balancesAfter), - } - reports = append(reports, report) - } - - return reports, nil -} - -// RunE2ETest runs a e2e test -func (runner *E2ERunner) RunE2ETest(e2eTest E2ETest, checkAccounting bool) (err error) { - // return an error on panic - // https://github.com/zeta-chain/node/issues/1500 - defer func() { - if r := recover(); r != nil { - // print stack trace - stack := make([]byte, 4096) - n := runtime.Stack(stack, false) - err = fmt.Errorf("%s failed: %v, stack trace %s", e2eTest.Name, r, stack[:n]) - } - }() - - startTime := time.Now() - runner.Logger.Print("⏳running - %s", e2eTest.Description) - - // run e2e test, if args are not provided, use default args - args := e2eTest.Args - if len(args) == 0 { - args = e2eTest.DefaultArgs() - } - e2eTest.E2ETest(runner, args) - - //check supplies - if checkAccounting { - if err := runner.CheckZRC20ReserveAndSupply(); err != nil { - return err - } - } - - runner.Logger.Print("✅ completed in %s - %s", time.Since(startTime), e2eTest.Description) - - return err -} - -// findE2ETest finds a e2e test by name -func findE2ETestByName(e2eTests []E2ETest, e2eTestName string) (E2ETest, bool) { - for _, test := range e2eTests { - if test.Name == e2eTestName { - return test, true - } - } - return E2ETest{}, false -} - // CopyAddressesFrom copies addresses from another E2ETestRunner that initialized the contracts func (runner *E2ERunner) CopyAddressesFrom(other *E2ERunner) (err error) { // copy TSS address diff --git a/e2e/runner/setup_zeta.go b/e2e/runner/setup_zeta.go index 74bbd6df21..05fa2a6e78 100644 --- a/e2e/runner/setup_zeta.go +++ b/e2e/runner/setup_zeta.go @@ -16,11 +16,16 @@ import ( "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/e2e/contracts/contextapp" "github.com/zeta-chain/zetacore/e2e/contracts/zevmswap" - "github.com/zeta-chain/zetacore/e2e/utils" + "github.com/zeta-chain/zetacore/e2e/txserver" + e2eutils "github.com/zeta-chain/zetacore/e2e/utils" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) +// EmissionsPoolFunding represents the amount of ZETA to fund the emissions pool with +// This is the same value as used originally on mainnet (20M ZETA) +var EmissionsPoolFunding = big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(2e7)) + // SetTSSAddresses set TSS addresses from information queried from ZetaChain func (runner *E2ERunner) SetTSSAddresses() error { runner.Logger.Print("⚙️ setting up TSS address") @@ -69,7 +74,7 @@ func (runner *E2ERunner) SetZEVMContracts() { // deploy system contracts and ZRC20 contracts on ZetaChain uniswapV2FactoryAddr, uniswapV2RouterAddr, zevmConnectorAddr, wzetaAddr, erc20zrc20Addr, err := runner.ZetaTxServer.DeploySystemContractsAndZRC20( - utils.FungibleAdminName, + e2eutils.FungibleAdminName, runner.ERC20Addr.Hex(), ) if err != nil { @@ -152,14 +157,14 @@ func (runner *E2ERunner) SetZEVMContracts() { panic(err) } - receipt := utils.MustWaitForTxReceipt(runner.Ctx, runner.ZEVMClient, txZEVMSwapApp, runner.Logger, runner.ReceiptTimeout) + receipt := e2eutils.MustWaitForTxReceipt(runner.Ctx, runner.ZEVMClient, txZEVMSwapApp, runner.Logger, runner.ReceiptTimeout) if receipt.Status != 1 { panic("ZEVMSwapApp deployment failed") } runner.ZEVMSwapAppAddr = zevmSwapAppAddr runner.ZEVMSwapApp = zevmSwapApp - receipt = utils.MustWaitForTxReceipt(runner.Ctx, runner.ZEVMClient, txContextApp, runner.Logger, runner.ReceiptTimeout) + receipt = e2eutils.MustWaitForTxReceipt(runner.Ctx, runner.ZEVMClient, txContextApp, runner.Logger, runner.ReceiptTimeout) if receipt.Status != 1 { panic("ContextApp deployment failed") } @@ -167,6 +172,7 @@ func (runner *E2ERunner) SetZEVMContracts() { runner.ContextApp = contextApp } +// SetupETHZRC20 sets up the ETH ZRC20 in the runner from the values queried from the chain func (runner *E2ERunner) SetupETHZRC20() { ethZRC20Addr, err := runner.SystemContract.GasCoinZRC20ByChainId(&bind.CallOpts{}, big.NewInt(common.GoerliLocalnetChain().ChainId)) if err != nil { @@ -183,6 +189,7 @@ func (runner *E2ERunner) SetupETHZRC20() { runner.ETHZRC20 = ethZRC20 } +// SetupBTCZRC20 sets up the BTC ZRC20 in the runner from the values queried from the chain func (runner *E2ERunner) SetupBTCZRC20() { BTCZRC20Addr, err := runner.SystemContract.GasCoinZRC20ByChainId(&bind.CallOpts{}, big.NewInt(common.BtcRegtestChain().ChainId)) if err != nil { @@ -196,3 +203,10 @@ func (runner *E2ERunner) SetupBTCZRC20() { } runner.BTCZRC20 = BTCZRC20 } + +// FundEmissionsPool funds the emissions pool on ZetaChain with the same value as used originally on mainnet (20M ZETA) +func (runner *E2ERunner) FundEmissionsPool() error { + runner.Logger.Print("⚙️ funding the emissions pool on ZetaChain with 20M ZETA (%s)", txserver.EmissionsPoolAddress) + + return runner.ZetaTxServer.FundEmissionsPool(e2eutils.FungibleAdminName, EmissionsPoolFunding) +} diff --git a/e2e/txserver/zeta_tx_server.go b/e2e/txserver/zeta_tx_server.go index 2c5af55a2f..88aa7add33 100644 --- a/e2e/txserver/zeta_tx_server.go +++ b/e2e/txserver/zeta_tx_server.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "math/big" "os" "strings" @@ -32,6 +33,7 @@ import ( etherminttypes "github.com/evmos/ethermint/types" evmtypes "github.com/evmos/ethermint/x/evm/types" rpchttp "github.com/tendermint/tendermint/rpc/client/http" + "github.com/zeta-chain/zetacore/cmd/zetacored/config" "github.com/zeta-chain/zetacore/common" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" emissionstypes "github.com/zeta-chain/zetacore/x/emissions/types" @@ -39,6 +41,10 @@ import ( observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) +// EmissionsPoolAddress is the address of the emissions pool +// This address is constant for all networks because it is derived from emissions name +const EmissionsPoolAddress = "zeta1w43fn2ze2wyhu5hfmegr6vp52c3dgn0srdgymy" + // ZetaTxServer is a ZetaChain tx server for E2E test type ZetaTxServer struct { clientCtx client.Context @@ -293,6 +299,36 @@ func (zts ZetaTxServer) DeploySystemContractsAndZRC20(account, erc20Addr string) return uniswapV2FactoryAddr, uniswapV2RouterAddr, zevmConnectorAddr, wzetaAddr, erc20zrc20Addr, nil } +// FundEmissionsPool funds the emissions pool with the given amount +func (zts ZetaTxServer) FundEmissionsPool(account string, amount *big.Int) error { + // retrieve account + acc, err := zts.clientCtx.Keyring.Key(account) + if err != nil { + return err + } + addr, err := acc.GetAddress() + if err != nil { + return err + } + + // retrieve account address + emissionPoolAccAddr, err := sdktypes.AccAddressFromBech32(EmissionsPoolAddress) + if err != nil { + return err + } + + // convert amount + amountInt := sdktypes.NewIntFromBigInt(amount) + + // fund emissions pool + _, err = zts.BroadcastTx(account, banktypes.NewMsgSend( + addr, + emissionPoolAccAddr, + sdktypes.NewCoins(sdktypes.NewCoin(config.BaseDenom, amountInt)), + )) + return err +} + // newCodec returns the codec for msg server func newCodec() (*codec.ProtoCodec, codectypes.InterfaceRegistry) { interfaceRegistry := codectypes.NewInterfaceRegistry() diff --git a/go.mod b/go.mod index 6669039f30..9560f12c9e 100644 --- a/go.mod +++ b/go.mod @@ -59,6 +59,7 @@ require ( require ( github.com/binance-chain/tss-lib v0.0.0-20201118045712-70b2cb4bf916 + github.com/nanmu42/etherscan-api v1.10.0 github.com/onrik/ethrpc v1.2.0 ) diff --git a/go.sum b/go.sum index 7b2dc1998c..96e3c53ae9 100644 --- a/go.sum +++ b/go.sum @@ -2314,6 +2314,8 @@ github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76/go.mod h1:x5OoJ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= +github.com/nanmu42/etherscan-api v1.10.0 h1:8lAwKbaHEVzXK+cbLaApxbmp4Kai12WKEcY9CxqxKbY= +github.com/nanmu42/etherscan-api v1.10.0/go.mod h1:P8oAUxbYfsdfGXQnHCgjTDs4YbmasUVCtYAYc4rrZ5w= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= diff --git a/proto/observer/tx.proto b/proto/observer/tx.proto index c1e3b49d9e..85e0ffe9ef 100644 --- a/proto/observer/tx.proto +++ b/proto/observer/tx.proto @@ -93,8 +93,8 @@ message MsgUpdateKeygenResponse {} message MsgResetChainNonces { string creator = 1; int64 chain_id = 2; - uint64 chain_nonce_low = 3; - uint64 chain_nonce_high = 4; + int64 chain_nonce_low = 3; + int64 chain_nonce_high = 4; } message MsgResetChainNoncesResponse {} diff --git a/testutil/keeper/emissions.go b/testutil/keeper/emissions.go index a359d93249..e61f6cd76e 100644 --- a/testutil/keeper/emissions.go +++ b/testutil/keeper/emissions.go @@ -15,18 +15,15 @@ import ( ) type EmissionMockOptions struct { - UseBankMock bool - UseStakingMock bool - UseObserverMock bool - UseAccountMock bool + UseBankMock bool + UseStakingMock bool + UseObserverMock bool + UseAccountMock bool + UseParamStoreMock bool } func EmissionsKeeper(t testing.TB) (*keeper.Keeper, sdk.Context, SDKKeepers, ZetaKeepers) { - return EmissionKeeperWithMockOptions(t, EmissionMockOptions{ - UseBankMock: false, - UseStakingMock: false, - UseObserverMock: false, - }) + return EmissionKeeperWithMockOptions(t, EmissionMockOptions{}) } func EmissionKeeperWithMockOptions( t testing.TB, @@ -91,18 +88,31 @@ func EmissionKeeperWithMockOptions( observerKeeper = emissionsmocks.NewEmissionObserverKeeper(t) } + var paramStore types.ParamStore + if mockOptions.UseParamStoreMock { + mock := emissionsmocks.NewEmissionParamStore(t) + // mock this method for the keeper constructor + mock.On("HasKeyTable").Maybe().Return(true) + paramStore = mock + } else { + paramStore = sdkKeepers.ParamsKeeper.Subspace(types.ModuleName) + } + k := keeper.NewKeeper( cdc, storeKey, memStoreKey, - sdkKeepers.ParamsKeeper.Subspace(types.ModuleName), + paramStore, authtypes.FeeCollectorName, bankKeeper, stakingKeeper, observerKeeper, authKeeper, ) - k.SetParams(ctx, types.DefaultParams()) + + if !mockOptions.UseParamStoreMock { + k.SetParams(ctx, types.DefaultParams()) + } return k, ctx, sdkKeepers, zetaKeepers } @@ -112,3 +122,9 @@ func GetEmissionsBankMock(t testing.TB, keeper *keeper.Keeper) *emissionsmocks.E require.True(t, ok) return cbk } + +func GetEmissionsParamStoreMock(t testing.TB, keeper *keeper.Keeper) *emissionsmocks.EmissionParamStore { + m, ok := keeper.GetParamStore().(*emissionsmocks.EmissionParamStore) + require.True(t, ok) + return m +} diff --git a/testutil/keeper/mocks/crosschain/observer.go b/testutil/keeper/mocks/crosschain/observer.go index 8e2a40b9cb..d100894b02 100644 --- a/testutil/keeper/mocks/crosschain/observer.go +++ b/testutil/keeper/mocks/crosschain/observer.go @@ -555,24 +555,6 @@ func (_m *CrosschainObserverKeeper) GetObserverSet(ctx types.Context) (observert return r0, r1 } -// GetParams provides a mock function with given fields: ctx -func (_m *CrosschainObserverKeeper) GetParams(ctx types.Context) observertypes.Params { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetParams") - } - - var r0 observertypes.Params - if rf, ok := ret.Get(0).(func(types.Context) observertypes.Params); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(observertypes.Params) - } - - return r0 -} - // GetPendingNonces provides a mock function with given fields: ctx, tss, chainID func (_m *CrosschainObserverKeeper) GetPendingNonces(ctx types.Context, tss string, chainID int64) (observertypes.PendingNonces, bool) { ret := _m.Called(ctx, tss, chainID) diff --git a/testutil/keeper/mocks/emissions/param_store.go b/testutil/keeper/mocks/emissions/param_store.go new file mode 100644 index 0000000000..f9923f3d94 --- /dev/null +++ b/testutil/keeper/mocks/emissions/param_store.go @@ -0,0 +1,76 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + + types "github.com/cosmos/cosmos-sdk/types" +) + +// EmissionParamStore is an autogenerated mock type for the EmissionParamStore type +type EmissionParamStore struct { + mock.Mock +} + +// GetParamSetIfExists provides a mock function with given fields: ctx, ps +func (_m *EmissionParamStore) GetParamSetIfExists(ctx types.Context, ps paramstypes.ParamSet) { + _m.Called(ctx, ps) +} + +// HasKeyTable provides a mock function with given fields: +func (_m *EmissionParamStore) HasKeyTable() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for HasKeyTable") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// SetParamSet provides a mock function with given fields: ctx, ps +func (_m *EmissionParamStore) SetParamSet(ctx types.Context, ps paramstypes.ParamSet) { + _m.Called(ctx, ps) +} + +// WithKeyTable provides a mock function with given fields: table +func (_m *EmissionParamStore) WithKeyTable(table paramstypes.KeyTable) paramstypes.Subspace { + ret := _m.Called(table) + + if len(ret) == 0 { + panic("no return value specified for WithKeyTable") + } + + var r0 paramstypes.Subspace + if rf, ok := ret.Get(0).(func(paramstypes.KeyTable) paramstypes.Subspace); ok { + r0 = rf(table) + } else { + r0 = ret.Get(0).(paramstypes.Subspace) + } + + return r0 +} + +// NewEmissionParamStore creates a new instance of EmissionParamStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewEmissionParamStore(t interface { + mock.TestingT + Cleanup(func()) +}) *EmissionParamStore { + mock := &EmissionParamStore{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/testutil/keeper/mocks/fungible/observer.go b/testutil/keeper/mocks/fungible/observer.go index 3010f8faaf..83cdb37bcc 100644 --- a/testutil/keeper/mocks/fungible/observer.go +++ b/testutil/keeper/mocks/fungible/observer.go @@ -6,8 +6,6 @@ import ( mock "github.com/stretchr/testify/mock" common "github.com/zeta-chain/zetacore/common" - observertypes "github.com/zeta-chain/zetacore/x/observer/types" - types "github.com/cosmos/cosmos-sdk/types" ) @@ -16,24 +14,6 @@ type FungibleObserverKeeper struct { mock.Mock } -// GetParams provides a mock function with given fields: ctx -func (_m *FungibleObserverKeeper) GetParams(ctx types.Context) observertypes.Params { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetParams") - } - - var r0 observertypes.Params - if rf, ok := ret.Get(0).(func(types.Context) observertypes.Params); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(observertypes.Params) - } - - return r0 -} - // GetSupportedChains provides a mock function with given fields: ctx func (_m *FungibleObserverKeeper) GetSupportedChains(ctx types.Context) []*common.Chain { ret := _m.Called(ctx) diff --git a/testutil/keeper/mocks/mocks.go b/testutil/keeper/mocks/mocks.go index 0da231a7e5..1180fca27a 100644 --- a/testutil/keeper/mocks/mocks.go +++ b/testutil/keeper/mocks/mocks.go @@ -94,6 +94,11 @@ type EmissionObserverKeeper interface { emissionstypes.ObserverKeeper } +//go:generate mockery --name EmissionParamStore --filename param_store.go --case underscore --output ./emissions +type EmissionParamStore interface { + emissionstypes.ParamStore +} + /** * Observer Mocks */ diff --git a/tool/filter_missed_deposits/zetatool_config.json b/tool/filter_missed_deposits/zetatool_config.json new file mode 100644 index 0000000000..f3189e52e0 --- /dev/null +++ b/tool/filter_missed_deposits/zetatool_config.json @@ -0,0 +1,8 @@ +{ + "ZetaURL": "127.0.0.1:1317", + "BtcExplorerURL": "https://blockstream.info/api/", + "EthRPCURL": "https://ethereum-rpc.publicnode.com", + "EtherscanAPIkey": "", + "ConnectorAddress": "0x000007Cf399229b2f5A4D043F20E90C9C98B7C6a", + "CustodyAddress": "0x0000030Ec64DF25301d8414eE5a29588C4B0dE10" +} \ No newline at end of file diff --git a/typescript/observer/tx_pb.d.ts b/typescript/observer/tx_pb.d.ts index 3543427ae3..4f7a346cc2 100644 --- a/typescript/observer/tx_pb.d.ts +++ b/typescript/observer/tx_pb.d.ts @@ -465,12 +465,12 @@ export declare class MsgResetChainNonces extends Message { chainId: bigint; /** - * @generated from field: uint64 chain_nonce_low = 3; + * @generated from field: int64 chain_nonce_low = 3; */ chainNonceLow: bigint; /** - * @generated from field: uint64 chain_nonce_high = 4; + * @generated from field: int64 chain_nonce_high = 4; */ chainNonceHigh: bigint; diff --git a/x/crosschain/keeper/msg_server_add_to_intx_tracker_test.go b/x/crosschain/keeper/msg_server_add_to_intx_tracker_test.go index 013f992fd9..50e8164eb7 100644 --- a/x/crosschain/keeper/msg_server_add_to_intx_tracker_test.go +++ b/x/crosschain/keeper/msg_server_add_to_intx_tracker_test.go @@ -16,7 +16,7 @@ import ( ) func setupVerificationParams(zk keepertest.ZetaKeepers, ctx sdk.Context, tx_index int64, chainID int64, header ethtypes.Header, headerRLP []byte, block *ethtypes.Block) { - params := zk.ObserverKeeper.GetParams(ctx) + params := zk.ObserverKeeper.GetParamsIfExists(ctx) zk.ObserverKeeper.SetParams(ctx, params) zk.ObserverKeeper.SetBlockHeader(ctx, common.BlockHeader{ Height: block.Number().Int64(), diff --git a/x/crosschain/types/expected_keepers.go b/x/crosschain/types/expected_keepers.go index 4286bbf48b..6f77d4c2dc 100644 --- a/x/crosschain/types/expected_keepers.go +++ b/x/crosschain/types/expected_keepers.go @@ -33,7 +33,6 @@ type BankKeeper interface { type ObserverKeeper interface { GetObserverSet(ctx sdk.Context) (val observertypes.ObserverSet, found bool) GetBallot(ctx sdk.Context, index string) (val observertypes.Ballot, found bool) - GetParams(ctx sdk.Context) (params observertypes.Params) GetChainParamsByChainID(ctx sdk.Context, chainID int64) (params *observertypes.ChainParams, found bool) GetNodeAccount(ctx sdk.Context, address string) (nodeAccount observertypes.NodeAccount, found bool) GetAllNodeAccount(ctx sdk.Context) (nodeAccounts []observertypes.NodeAccount) diff --git a/x/emissions/abci.go b/x/emissions/abci.go index 96be4ae613..6472ae9e20 100644 --- a/x/emissions/abci.go +++ b/x/emissions/abci.go @@ -18,9 +18,10 @@ func BeginBlocker(ctx sdk.Context, keeper keeper.Keeper) { ctx.Logger().Info(fmt.Sprintf("Block rewards %s are greater than emission pool balance %s", blockRewards.String(), emissionPoolBalance.String())) return } - validatorRewards := sdk.MustNewDecFromStr(keeper.GetParams(ctx).ValidatorEmissionPercentage).Mul(blockRewards).TruncateInt() - observerRewards := sdk.MustNewDecFromStr(keeper.GetParams(ctx).ObserverEmissionPercentage).Mul(blockRewards).TruncateInt() - tssSignerRewards := sdk.MustNewDecFromStr(keeper.GetParams(ctx).TssSignerEmissionPercentage).Mul(blockRewards).TruncateInt() + + // Get the distribution of rewards + params := keeper.GetParamsIfExists(ctx) + validatorRewards, observerRewards, tssSignerRewards := types.GetRewardsDistributions(params) // TODO : Replace hardcoded slash amount with a parameter // https://github.com/zeta-chain/node/pull/1861 @@ -70,7 +71,6 @@ func DistributeValidatorRewards(ctx sdk.Context, amount sdkmath.Int, bankKeeper // The total rewards are distributed equally among all Successful votes // NotVoted or Unsuccessful votes are slashed // rewards given or slashed amounts are in azeta - func DistributeObserverRewards( ctx sdk.Context, amount sdkmath.Int, @@ -126,7 +126,6 @@ func DistributeObserverRewards( continue } if observerRewardUnits < 0 { - keeper.SlashObserverEmission(ctx, observerAddress.String(), slashAmount) finalDistributionList = append(finalDistributionList, &types.ObserverEmission{ EmissionType: types.EmissionType_Slash, diff --git a/x/emissions/abci_test.go b/x/emissions/abci_test.go index a4a66b34a0..207840f79b 100644 --- a/x/emissions/abci_test.go +++ b/x/emissions/abci_test.go @@ -102,9 +102,9 @@ func TestBeginBlocker(t *testing.T) { emissionPool := sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.ModuleName).GetAddress() blockRewards := emissionstypes.BlockReward - observerRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParams(ctx).ObserverEmissionPercentage)).TruncateInt() - validatorRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParams(ctx).ValidatorEmissionPercentage)).TruncateInt() - tssSignerRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParams(ctx).TssSignerEmissionPercentage)).TruncateInt() + observerRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).ObserverEmissionPercentage)).TruncateInt() + validatorRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).ValidatorEmissionPercentage)).TruncateInt() + tssSignerRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).TssSignerEmissionPercentage)).TruncateInt() distributedRewards := observerRewardsForABlock.Add(validatorRewardsForABlock).Add(tssSignerRewardsForABlock) require.True(t, blockRewards.TruncateInt().GT(distributedRewards)) diff --git a/x/emissions/genesis.go b/x/emissions/genesis.go index 4805bec1ef..b278330eeb 100644 --- a/x/emissions/genesis.go +++ b/x/emissions/genesis.go @@ -19,7 +19,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) // ExportGenesis returns the emissions module's exported genesis. func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { var genesis types.GenesisState - genesis.Params = k.GetParams(ctx) + genesis.Params = k.GetParamsIfExists(ctx) genesis.WithdrawableEmissions = k.GetAllWithdrawableEmission(ctx) return &genesis diff --git a/x/emissions/keeper/block_rewards_components.go b/x/emissions/keeper/block_rewards_components.go index ab70e13de5..fe4140805d 100644 --- a/x/emissions/keeper/block_rewards_components.go +++ b/x/emissions/keeper/block_rewards_components.go @@ -17,9 +17,9 @@ func (k Keeper) GetBlockRewardComponents(ctx sdk.Context) (sdk.Dec, sdk.Dec, sdk return reservesFactor, bondFactor, durationFactor } func (k Keeper) GetBondFactor(ctx sdk.Context, stakingKeeper types.StakingKeeper) sdk.Dec { - targetBondRatio := sdk.MustNewDecFromStr(k.GetParams(ctx).TargetBondRatio) - maxBondFactor := sdk.MustNewDecFromStr(k.GetParams(ctx).MaxBondFactor) - minBondFactor := sdk.MustNewDecFromStr(k.GetParams(ctx).MinBondFactor) + targetBondRatio := sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).TargetBondRatio) + maxBondFactor := sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).MaxBondFactor) + minBondFactor := sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).MinBondFactor) currentBondedRatio := stakingKeeper.BondedRatio(ctx) // Bond factor ranges between minBondFactor (0.75) to maxBondFactor (1.25) @@ -37,10 +37,10 @@ func (k Keeper) GetBondFactor(ctx sdk.Context, stakingKeeper types.StakingKeeper } func (k Keeper) GetDurationFactor(ctx sdk.Context) sdk.Dec { - avgBlockTime := sdk.MustNewDecFromStr(k.GetParams(ctx).AvgBlockTime) + avgBlockTime := sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).AvgBlockTime) NumberOfBlocksInAMonth := sdk.NewDec(types.SecsInMonth).Quo(avgBlockTime) monthFactor := sdk.NewDec(ctx.BlockHeight()).Quo(NumberOfBlocksInAMonth) - logValueDec := sdk.MustNewDecFromStr(k.GetParams(ctx).DurationFactorConstant) + logValueDec := sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).DurationFactorConstant) // month * log(1 + 0.02 / 12) fractionNumerator := monthFactor.Mul(logValueDec) // (month * log(1 + 0.02 / 12) ) + 1 diff --git a/x/emissions/keeper/grpc_query_params.go b/x/emissions/keeper/grpc_query_params.go index 7b6b16965a..acc804124d 100644 --- a/x/emissions/keeper/grpc_query_params.go +++ b/x/emissions/keeper/grpc_query_params.go @@ -15,5 +15,5 @@ func (k Keeper) Params(c context.Context, req *types.QueryParamsRequest) (*types } ctx := sdk.UnwrapSDKContext(c) - return &types.QueryParamsResponse{Params: k.GetParams(ctx)}, nil + return &types.QueryParamsResponse{Params: k.GetParamsIfExists(ctx)}, nil } diff --git a/x/emissions/keeper/keeper.go b/x/emissions/keeper/keeper.go index b0fe4d26ee..aadabb61f7 100644 --- a/x/emissions/keeper/keeper.go +++ b/x/emissions/keeper/keeper.go @@ -9,7 +9,6 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" ) type ( @@ -17,7 +16,7 @@ type ( cdc codec.BinaryCodec storeKey storetypes.StoreKey memKey storetypes.StoreKey - paramstore paramtypes.Subspace + paramStore types.ParamStore feeCollectorName string bankKeeper types.BankKeeper stakingKeeper types.StakingKeeper @@ -30,7 +29,7 @@ func NewKeeper( cdc codec.BinaryCodec, storeKey, memKey storetypes.StoreKey, - ps paramtypes.Subspace, + ps types.ParamStore, feeCollectorName string, bankKeeper types.BankKeeper, stakingKeeper types.StakingKeeper, @@ -46,7 +45,7 @@ func NewKeeper( cdc: cdc, storeKey: storeKey, memKey: memKey, - paramstore: ps, + paramStore: ps, feeCollectorName: feeCollectorName, bankKeeper: bankKeeper, stakingKeeper: stakingKeeper, @@ -78,3 +77,7 @@ func (k Keeper) GetObserverKeeper() types.ObserverKeeper { func (k Keeper) GetAuthKeeper() types.AccountKeeper { return k.authKeeper } + +func (k Keeper) GetParamStore() types.ParamStore { + return k.paramStore +} diff --git a/x/emissions/keeper/params.go b/x/emissions/keeper/params.go index 17d506bbca..5369e0953a 100644 --- a/x/emissions/keeper/params.go +++ b/x/emissions/keeper/params.go @@ -5,13 +5,14 @@ import ( "github.com/zeta-chain/zetacore/x/emissions/types" ) -// GetParams get all parameters as types.Params -func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { - k.paramstore.GetParamSet(ctx, ¶ms) +// GetParamsIfExists get all parameters as types.Params if they exist +// non existent parameters will return zero values +func (k Keeper) GetParamsIfExists(ctx sdk.Context) (params types.Params) { + k.paramStore.GetParamSetIfExists(ctx, ¶ms) return } // SetParams set the params func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { - k.paramstore.SetParamSet(ctx, ¶ms) + k.paramStore.SetParamSet(ctx, ¶ms) } diff --git a/x/emissions/keeper/params_test.go b/x/emissions/keeper/params_test.go index e73031b824..6b85f76444 100644 --- a/x/emissions/keeper/params_test.go +++ b/x/emissions/keeper/params_test.go @@ -219,9 +219,9 @@ func TestKeeper_GetParams(t *testing.T) { }, tt.isPanic) if tt.isPanic != "" { - require.Equal(t, emissionstypes.DefaultParams(), k.GetParams(ctx)) + require.Equal(t, emissionstypes.DefaultParams(), k.GetParamsIfExists(ctx)) } else { - require.Equal(t, tt.params, k.GetParams(ctx)) + require.Equal(t, tt.params, k.GetParamsIfExists(ctx)) } }) } diff --git a/x/emissions/types/distributions.go b/x/emissions/types/distributions.go new file mode 100644 index 0000000000..fb67135ea9 --- /dev/null +++ b/x/emissions/types/distributions.go @@ -0,0 +1,34 @@ +package types + +import ( + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GetRewardsDistributions returns the current distribution of rewards +// for validators, observers and TSS signers +// If the percentage is not set, it returns 0 +func GetRewardsDistributions(params Params) (sdkmath.Int, sdkmath.Int, sdkmath.Int) { + // Fetch the validator rewards, use 0 if the percentage is not set + validatorRewards := sdk.NewInt(0) + validatorRewardsDec, err := sdk.NewDecFromStr(params.ValidatorEmissionPercentage) + if err == nil { + validatorRewards = validatorRewardsDec.Mul(BlockReward).TruncateInt() + } + + // Fetch the observer rewards, use 0 if the percentage is not set + observerRewards := sdk.NewInt(0) + observerRewardsDec, err := sdk.NewDecFromStr(params.ObserverEmissionPercentage) + if err == nil { + observerRewards = observerRewardsDec.Mul(BlockReward).TruncateInt() + } + + // Fetch the TSS signer rewards, use 0 if the percentage is not set + tssSignerRewards := sdk.NewInt(0) + tssSignerRewardsDec, err := sdk.NewDecFromStr(params.TssSignerEmissionPercentage) + if err == nil { + tssSignerRewards = tssSignerRewardsDec.Mul(BlockReward).TruncateInt() + } + + return validatorRewards, observerRewards, tssSignerRewards +} diff --git a/x/emissions/types/distributions_test.go b/x/emissions/types/distributions_test.go new file mode 100644 index 0000000000..76d2534583 --- /dev/null +++ b/x/emissions/types/distributions_test.go @@ -0,0 +1,34 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/x/emissions/types" +) + +func TestKeeper_GetRewardsDistributions(t *testing.T) { + t.Run("Return fractions of block reward", func(t *testing.T) { + val, obs, tss := types.GetRewardsDistributions(types.Params{ + ValidatorEmissionPercentage: "0.5", + ObserverEmissionPercentage: "0.25", + TssSignerEmissionPercentage: "0.25", + }) + + require.EqualValues(t, "4810474537037037037", val.String()) // 0.5 * block reward + require.EqualValues(t, "2405237268518518518", obs.String()) // 0.25 * block reward + require.EqualValues(t, "2405237268518518518", tss.String()) // 0.25 * block reward + }) + + t.Run("Return zero in case of invalid string", func(t *testing.T) { + val, obs, tss := types.GetRewardsDistributions(types.Params{ + ValidatorEmissionPercentage: "invalid", + ObserverEmissionPercentage: "invalid", + TssSignerEmissionPercentage: "invalid", + }) + + require.True(t, val.IsZero()) + require.True(t, obs.IsZero()) + require.True(t, tss.IsZero()) + }) +} diff --git a/x/emissions/types/expected_keepers.go b/x/emissions/types/expected_keepers.go index 286490e7c5..298a0caf42 100644 --- a/x/emissions/types/expected_keepers.go +++ b/x/emissions/types/expected_keepers.go @@ -3,6 +3,7 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/types" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) @@ -28,3 +29,11 @@ type BankKeeper interface { type StakingKeeper interface { BondedRatio(ctx sdk.Context) sdk.Dec } + +// ParamStore defines the expected paramstore methods to store and load Params (noalias) +type ParamStore interface { + GetParamSetIfExists(ctx sdk.Context, ps paramstypes.ParamSet) + SetParamSet(ctx sdk.Context, ps paramstypes.ParamSet) + WithKeyTable(table paramstypes.KeyTable) paramstypes.Subspace + HasKeyTable() bool +} diff --git a/x/fungible/types/expected_keepers.go b/x/fungible/types/expected_keepers.go index feddedf4b7..5ffc04c723 100644 --- a/x/fungible/types/expected_keepers.go +++ b/x/fungible/types/expected_keepers.go @@ -13,7 +13,6 @@ import ( evmtypes "github.com/evmos/ethermint/x/evm/types" "github.com/zeta-chain/zetacore/common" authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" - observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) // AccountKeeper defines the expected account keeper used for simulations (noalias) @@ -32,7 +31,6 @@ type BankKeeper interface { } type ObserverKeeper interface { - GetParams(ctx sdk.Context) (params observertypes.Params) GetSupportedChains(ctx sdk.Context) []*common.Chain } diff --git a/x/observer/client/cli/tx_reset_chain_nonces.go b/x/observer/client/cli/tx_reset_chain_nonces.go new file mode 100644 index 0000000000..01a992bd9e --- /dev/null +++ b/x/observer/client/cli/tx_reset_chain_nonces.go @@ -0,0 +1,58 @@ +package cli + +import ( + "strconv" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/spf13/cobra" + "github.com/zeta-chain/zetacore/x/observer/types" +) + +func CmdResetChainNonces() *cobra.Command { + cmd := &cobra.Command{ + Use: "reset-chain-nonces [chain-id] [chain-nonce-low] [chain-nonce-high]", + Short: "Broadcast message to reset chain nonces", + Args: cobra.ExactArgs(3), + RunE: func(cmd *cobra.Command, args []string) (err error) { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + // get chainID as int64 + chainID, err := strconv.ParseInt(args[0], 10, 64) + if err != nil { + return err + } + + // get chainNonceLow as int64 + chainNonceLow, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return err + } + + // get chainNonceHigh as int64 + chainNonceHigh, err := strconv.ParseInt(args[2], 10, 64) + if err != nil { + return err + } + + msg := types.NewMsgResetChainNonces( + clientCtx.GetFromAddress().String(), + chainID, + chainNonceLow, + chainNonceHigh, + ) + if err := msg.ValidateBasic(); err != nil { + return err + } + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} diff --git a/x/observer/genesis.go b/x/observer/genesis.go index 8098394f65..1c1b10ef26 100644 --- a/x/observer/genesis.go +++ b/x/observer/genesis.go @@ -141,7 +141,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) // ExportGenesis returns the observer module's exported genesis. func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { - params := k.GetParams(ctx) + params := k.GetParamsIfExists(ctx) chainParams, found := k.GetChainParamsList(ctx) if !found { diff --git a/x/observer/keeper/ballot.go b/x/observer/keeper/ballot.go index 787afd7831..4c960743ac 100644 --- a/x/observer/keeper/ballot.go +++ b/x/observer/keeper/ballot.go @@ -63,7 +63,7 @@ func (k Keeper) AddBallotToList(ctx sdk.Context, ballot types.Ballot) { // GetMaturedBallotList Returns a list of ballots which are matured at current height func (k Keeper) GetMaturedBallotList(ctx sdk.Context) []string { - maturityBlocks := k.GetParams(ctx).BallotMaturityBlocks + maturityBlocks := k.GetParamsIfExists(ctx).BallotMaturityBlocks list, found := k.GetBallotList(ctx, ctx.BlockHeight()-maturityBlocks) if !found { return []string{} diff --git a/x/observer/keeper/grpc_query_params.go b/x/observer/keeper/grpc_query_params.go index f02060de25..ef095420af 100644 --- a/x/observer/keeper/grpc_query_params.go +++ b/x/observer/keeper/grpc_query_params.go @@ -15,5 +15,5 @@ func (k Keeper) Params(c context.Context, req *types.QueryParamsRequest) (*types } ctx := sdk.UnwrapSDKContext(c) return &types.QueryParamsResponse{ - Params: k.GetParams(ctx)}, nil + Params: k.GetParamsIfExists(ctx)}, nil } diff --git a/x/observer/keeper/msg_server_reset_chain_nonces.go b/x/observer/keeper/msg_server_reset_chain_nonces.go index 84a5aac743..6a5230b0cc 100644 --- a/x/observer/keeper/msg_server_reset_chain_nonces.go +++ b/x/observer/keeper/msg_server_reset_chain_nonces.go @@ -3,19 +3,15 @@ package keeper import ( "context" - authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/zeta-chain/zetacore/common" + authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" "github.com/zeta-chain/zetacore/x/observer/types" ) // ResetChainNonces handles resetting chain nonces -// Authorized: policy group admin func (k msgServer) ResetChainNonces(goCtx context.Context, msg *types.MsgResetChainNonces) (*types.MsgResetChainNoncesResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - - // check permission if !k.GetAuthorityKeeper().IsAuthorized(ctx, msg.Creator, authoritytypes.PolicyType_groupAdmin) { return &types.MsgResetChainNoncesResponse{}, types.ErrNotAuthorizedPolicy } @@ -30,20 +26,21 @@ func (k msgServer) ResetChainNonces(goCtx context.Context, msg *types.MsgResetCh return nil, types.ErrSupportedChains } - // reset chain nonces + // set chain nonces chainNonce := types.ChainNonces{ Index: chain.ChainName.String(), ChainId: chain.ChainId, - Nonce: msg.ChainNonceHigh, + // #nosec G701 always positive + Nonce: uint64(msg.ChainNonceHigh), // #nosec G701 always positive FinalizedHeight: uint64(ctx.BlockHeight()), } k.SetChainNonces(ctx, chainNonce) - // reset pending nonces + // set pending nonces p := types.PendingNonces{ - NonceLow: int64(msg.ChainNonceLow), - NonceHigh: int64(msg.ChainNonceHigh), + NonceLow: msg.ChainNonceLow, + NonceHigh: msg.ChainNonceHigh, ChainId: chain.ChainId, Tss: tss.TssPubkey, } diff --git a/x/observer/keeper/msg_server_reset_chain_nonces_test.go b/x/observer/keeper/msg_server_reset_chain_nonces_test.go new file mode 100644 index 0000000000..46da328fe8 --- /dev/null +++ b/x/observer/keeper/msg_server_reset_chain_nonces_test.go @@ -0,0 +1,148 @@ +package keeper_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/common" + keepertest "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" + "github.com/zeta-chain/zetacore/x/observer/keeper" + "github.com/zeta-chain/zetacore/x/observer/types" +) + +func TestMsgServer_ResetChainNonces(t *testing.T) { + t.Run("cannot reset chain nonces if not authorized", func(t *testing.T) { + k, ctx, _, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMockOptions{ + UseAuthorityMock: true, + }) + srv := keeper.NewMsgServerImpl(*k) + chainId := common.GoerliLocalnetChain().ChainId + + admin := sample.AccAddress() + authorityMock := keepertest.GetObserverAuthorityMock(t, k) + keepertest.MockIsAuthorized(&authorityMock.Mock, admin, authoritytypes.PolicyType_groupAdmin, false) + + _, err := srv.ResetChainNonces(sdk.WrapSDKContext(ctx), &types.MsgResetChainNonces{ + Creator: admin, + ChainId: chainId, + ChainNonceLow: 1, + ChainNonceHigh: 5, + }) + require.ErrorIs(t, err, types.ErrNotAuthorizedPolicy) + }) + + t.Run("cannot reset chain nonces if tss not found", func(t *testing.T) { + k, ctx, _, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMockOptions{ + UseAuthorityMock: true, + }) + srv := keeper.NewMsgServerImpl(*k) + + admin := sample.AccAddress() + authorityMock := keepertest.GetObserverAuthorityMock(t, k) + keepertest.MockIsAuthorized(&authorityMock.Mock, admin, authoritytypes.PolicyType_groupAdmin, true) + + chainId := common.GoerliLocalnetChain().ChainId + _, err := srv.ResetChainNonces(sdk.WrapSDKContext(ctx), &types.MsgResetChainNonces{ + Creator: admin, + ChainId: chainId, + ChainNonceLow: 1, + ChainNonceHigh: 5, + }) + require.ErrorIs(t, err, types.ErrTssNotFound) + }) + + t.Run("cannot reset chain nonces if chain not supported", func(t *testing.T) { + k, ctx, _, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMockOptions{ + UseAuthorityMock: true, + }) + srv := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + k.SetTSS(ctx, tss) + + admin := sample.AccAddress() + authorityMock := keepertest.GetObserverAuthorityMock(t, k) + keepertest.MockIsAuthorized(&authorityMock.Mock, admin, authoritytypes.PolicyType_groupAdmin, true) + + _, err := srv.ResetChainNonces(sdk.WrapSDKContext(ctx), &types.MsgResetChainNonces{ + Creator: admin, + ChainId: 999, + ChainNonceLow: 1, + ChainNonceHigh: 5, + }) + require.ErrorIs(t, err, types.ErrSupportedChains) + }) + + t.Run("can reset chain nonces", func(t *testing.T) { + k, ctx, _, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMockOptions{ + UseAuthorityMock: true, + }) + srv := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + k.SetTSS(ctx, tss) + + admin := sample.AccAddress() + authorityMock := keepertest.GetObserverAuthorityMock(t, k) + keepertest.MockIsAuthorized(&authorityMock.Mock, admin, authoritytypes.PolicyType_groupAdmin, true) + keepertest.MockIsAuthorized(&authorityMock.Mock, admin, authoritytypes.PolicyType_groupAdmin, true) + + chainId := common.GoerliLocalnetChain().ChainId + index := common.GoerliLocalnetChain().ChainName.String() + + // check existing chain nonces + _, found := k.GetChainNonces(ctx, index) + require.False(t, found) + _, found = k.GetPendingNonces(ctx, tss.TssPubkey, chainId) + require.False(t, found) + + // reset chain nonces + nonceLow := 1 + nonceHigh := 5 + _, err := srv.ResetChainNonces(sdk.WrapSDKContext(ctx), &types.MsgResetChainNonces{ + Creator: admin, + ChainId: chainId, + ChainNonceLow: int64(nonceLow), + ChainNonceHigh: int64(nonceHigh), + }) + require.NoError(t, err) + + // check updated chain nonces + chainNonces, found := k.GetChainNonces(ctx, index) + require.True(t, found) + require.Equal(t, chainId, chainNonces.ChainId) + require.Equal(t, index, chainNonces.Index) + require.Equal(t, uint64(nonceHigh), chainNonces.Nonce) + + pendingNonces, found := k.GetPendingNonces(ctx, tss.TssPubkey, chainId) + require.True(t, found) + require.Equal(t, chainId, pendingNonces.ChainId) + require.Equal(t, tss.TssPubkey, pendingNonces.Tss) + require.Equal(t, int64(nonceLow), pendingNonces.NonceLow) + require.Equal(t, int64(nonceHigh), pendingNonces.NonceHigh) + + // reset nonces back to 0 + _, err = srv.ResetChainNonces(sdk.WrapSDKContext(ctx), &types.MsgResetChainNonces{ + Creator: admin, + ChainId: chainId, + ChainNonceLow: 0, + ChainNonceHigh: 0, + }) + require.NoError(t, err) + + // check updated chain nonces + chainNonces, found = k.GetChainNonces(ctx, index) + require.True(t, found) + require.Equal(t, chainId, chainNonces.ChainId) + require.Equal(t, index, chainNonces.Index) + require.Equal(t, uint64(0), chainNonces.Nonce) + + pendingNonces, found = k.GetPendingNonces(ctx, tss.TssPubkey, chainId) + require.True(t, found) + require.Equal(t, chainId, pendingNonces.ChainId) + require.Equal(t, tss.TssPubkey, pendingNonces.Tss) + require.Equal(t, int64(0), pendingNonces.NonceLow) + require.Equal(t, int64(0), pendingNonces.NonceHigh) + }) +} diff --git a/x/observer/keeper/params.go b/x/observer/keeper/params.go index 3c570c9cf0..f137d04a18 100644 --- a/x/observer/keeper/params.go +++ b/x/observer/keeper/params.go @@ -5,12 +5,6 @@ import ( "github.com/zeta-chain/zetacore/x/observer/types" ) -// GetParams get all parameters as types.Params -func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { - k.paramstore.GetParamSet(ctx, ¶ms) - return -} - func (k Keeper) GetParamsIfExists(ctx sdk.Context) (params types.Params) { k.paramstore.GetParamSetIfExists(ctx, ¶ms) return diff --git a/x/observer/keeper/params_test.go b/x/observer/keeper/params_test.go index 5ec4c260a3..c487ea0f73 100644 --- a/x/observer/keeper/params_test.go +++ b/x/observer/keeper/params_test.go @@ -18,7 +18,7 @@ func TestGetParams(t *testing.T) { k.SetParams(ctx, params) - require.EqualValues(t, params, k.GetParams(ctx)) + require.EqualValues(t, params, k.GetParamsIfExists(ctx)) } func TestGenerateAddress(t *testing.T) { diff --git a/x/observer/migrations/v3/migrate_test.go b/x/observer/migrations/v3/migrate_test.go index 81e3747517..2ff0b36255 100644 --- a/x/observer/migrations/v3/migrate_test.go +++ b/x/observer/migrations/v3/migrate_test.go @@ -19,7 +19,7 @@ func TestMigrateStore(t *testing.T) { k.SetParams(ctx, params) err := v3.MigrateStore(ctx, k) require.NoError(t, err) - params = k.GetParams(ctx) + params = k.GetParamsIfExists(ctx) require.Len(t, params.AdminPolicy, 0) // update admin policy @@ -42,7 +42,7 @@ func TestMigrateStore(t *testing.T) { k.SetParams(ctx, params) err = v3.MigrateStore(ctx, k) require.NoError(t, err) - params = k.GetParams(ctx) + params = k.GetParamsIfExists(ctx) require.Len(t, params.AdminPolicy, 2) require.Equal(t, params.AdminPolicy[0].PolicyType, types.Policy_Type_group1) require.Equal(t, params.AdminPolicy[1].PolicyType, types.Policy_Type_group2) diff --git a/x/observer/migrations/v4/migrate.go b/x/observer/migrations/v4/migrate.go index 7444c46233..9ca22467a2 100644 --- a/x/observer/migrations/v4/migrate.go +++ b/x/observer/migrations/v4/migrate.go @@ -10,7 +10,6 @@ import ( // observerKeeper prevents circular dependency type observerKeeper interface { - GetParams(ctx sdk.Context) types.Params SetParams(ctx sdk.Context, params types.Params) GetChainParamsList(ctx sdk.Context) (params types.ChainParamsList, found bool) SetChainParamsList(ctx sdk.Context, params types.ChainParamsList) diff --git a/x/observer/migrations/v5/migrate.go b/x/observer/migrations/v5/migrate.go index a626ce192b..2eb261a3f4 100644 --- a/x/observer/migrations/v5/migrate.go +++ b/x/observer/migrations/v5/migrate.go @@ -10,7 +10,7 @@ import ( // observerKeeper prevents circular dependency type observerKeeper interface { - GetParams(ctx sdk.Context) types.Params + GetParamsIfExists(ctx sdk.Context) types.Params SetParams(ctx sdk.Context, params types.Params) GetChainParamsList(ctx sdk.Context) (params types.ChainParamsList, found bool) SetChainParamsList(ctx sdk.Context, params types.ChainParamsList) @@ -64,7 +64,7 @@ func MigrateObserverParams(ctx sdk.Context, observerKeeper observerKeeper) error } // search for the observer params with chain params entry - observerParams := observerKeeper.GetParams(ctx).ObserverParams + observerParams := observerKeeper.GetParamsIfExists(ctx).ObserverParams for _, observerParam := range observerParams { for i := range chainParamsList.ChainParams { // if the chain is found, update the chain params with the observer params diff --git a/x/observer/migrations/v7/migrate.go b/x/observer/migrations/v7/migrate.go index 2b2c759e09..414a87a38e 100644 --- a/x/observer/migrations/v7/migrate.go +++ b/x/observer/migrations/v7/migrate.go @@ -8,7 +8,7 @@ import ( // observerKeeper prevents circular dependency type observerKeeper interface { - GetParams(ctx sdk.Context) (params types.Params) + GetParamsIfExists(ctx sdk.Context) (params types.Params) GetAuthorityKeeper() types.AuthorityKeeper } @@ -20,7 +20,7 @@ func MigrateStore(ctx sdk.Context, observerKeeper observerKeeper) error { // MigratePolicies migrates policies from observer to authority func MigratePolicies(ctx sdk.Context, observerKeeper observerKeeper) error { - params := observerKeeper.GetParams(ctx) + params := observerKeeper.GetParamsIfExists(ctx) authorityKeeper := observerKeeper.GetAuthorityKeeper() var policies authoritytypes.Policies diff --git a/x/observer/types/message_reset_chain_nonces.go b/x/observer/types/message_reset_chain_nonces.go new file mode 100644 index 0000000000..71e438d103 --- /dev/null +++ b/x/observer/types/message_reset_chain_nonces.go @@ -0,0 +1,72 @@ +package types + +import ( + "errors" + + cosmoserrors "cosmossdk.io/errors" + "github.com/zeta-chain/zetacore/common" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +const TypeMsgResetChainNonces = "reset_chain_nonces" + +var _ sdk.Msg = &MsgResetChainNonces{} + +func NewMsgResetChainNonces(creator string, chainID int64, chainNonceLow int64, chainNonceHigh int64) *MsgResetChainNonces { + return &MsgResetChainNonces{ + Creator: creator, + ChainId: chainID, + ChainNonceLow: chainNonceLow, + ChainNonceHigh: chainNonceHigh, + } +} + +func (msg *MsgResetChainNonces) Route() string { + return RouterKey +} + +func (msg *MsgResetChainNonces) Type() string { + return TypeMsgResetChainNonces +} + +func (msg *MsgResetChainNonces) GetSigners() []sdk.AccAddress { + creator, err := sdk.AccAddressFromBech32(msg.Creator) + if err != nil { + panic(err) + } + return []sdk.AccAddress{creator} +} + +func (msg *MsgResetChainNonces) GetSignBytes() []byte { + bz := ModuleCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) +} + +func (msg *MsgResetChainNonces) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Creator) + if err != nil { + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + } + + // Check if chain exists + chain := common.GetChainFromChainID(msg.ChainId) + if chain == nil { + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidChainID, "invalid chain id (%d)", msg.ChainId) + } + + if msg.ChainNonceLow < 0 { + return errors.New("chain nonce low must be greater or equal 0") + } + + if msg.ChainNonceHigh < 0 { + return errors.New("chain nonce high must be greater or equal 0") + } + + if msg.ChainNonceLow > msg.ChainNonceHigh { + return errors.New("chain nonce low must be less or equal than chain nonce high") + } + + return nil +} diff --git a/x/observer/types/message_reset_chain_nonces_test.go b/x/observer/types/message_reset_chain_nonces_test.go new file mode 100644 index 0000000000..7043432952 --- /dev/null +++ b/x/observer/types/message_reset_chain_nonces_test.go @@ -0,0 +1,145 @@ +package types_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/common" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/observer/types" +) + +func TestMsgResetChainNonces_ValidateBasic(t *testing.T) { + tests := []struct { + name string + msg types.MsgResetChainNonces + wantErr bool + }{ + { + name: "valid message chain nonce high greater than nonce low", + msg: types.MsgResetChainNonces{ + Creator: sample.AccAddress(), + ChainId: common.ExternalChainList()[0].ChainId, + ChainNonceLow: 1, + ChainNonceHigh: 5, + }, + wantErr: false, + }, + { + name: "valid message chain nonce high same as nonce low", + msg: types.MsgResetChainNonces{ + Creator: sample.AccAddress(), + ChainId: common.ExternalChainList()[0].ChainId, + ChainNonceLow: 1, + ChainNonceHigh: 1, + }, + wantErr: false, + }, + { + name: "invalid address", + msg: types.MsgResetChainNonces{ + Creator: "invalid_address", + ChainId: common.ExternalChainList()[0].ChainId, + }, + wantErr: true, + }, + { + name: "invalid chain ID", + msg: types.MsgResetChainNonces{ + Creator: sample.AccAddress(), + ChainId: 999, + }, + wantErr: true, + }, + { + name: "invalid chain nonce low", + msg: types.MsgResetChainNonces{ + Creator: sample.AccAddress(), + ChainId: common.ExternalChainList()[0].ChainId, + ChainNonceLow: -1, + }, + wantErr: true, + }, + { + name: "invalid chain nonce high", + msg: types.MsgResetChainNonces{ + Creator: sample.AccAddress(), + ChainId: common.ExternalChainList()[0].ChainId, + ChainNonceLow: 1, + ChainNonceHigh: -1, + }, + wantErr: true, + }, + { + name: "invalid chain nonce low greater than chain nonce high", + msg: types.MsgResetChainNonces{ + Creator: sample.AccAddress(), + ChainId: common.ExternalChainList()[0].ChainId, + ChainNonceLow: 1, + ChainNonceHigh: 0, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.msg.ValidateBasic() + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + }) + } +} + +func TestMsgResetChainNonces_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg *types.MsgResetChainNonces + panics bool + }{ + { + name: "valid signer", + msg: types.NewMsgResetChainNonces(signer, 5, 1, 5), + panics: false, + }, + { + name: "invalid signer", + msg: types.NewMsgResetChainNonces("invalid", 5, 1, 5), + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgResetChainNonces_Type(t *testing.T) { + msg := types.NewMsgResetChainNonces(sample.AccAddress(), 5, 1, 5) + require.Equal(t, types.TypeMsgResetChainNonces, msg.Type()) +} + +func TestMsgResetChainNonces_Route(t *testing.T) { + msg := types.NewMsgResetChainNonces(sample.AccAddress(), 5, 1, 5) + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgResetChainNonces_GetSignBytes(t *testing.T) { + msg := types.NewMsgResetChainNonces(sample.AccAddress(), 5, 1, 5) + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/observer/types/tx.pb.go b/x/observer/types/tx.pb.go index 6c73455ae6..481efd0716 100644 --- a/x/observer/types/tx.pb.go +++ b/x/observer/types/tx.pb.go @@ -825,8 +825,8 @@ var xxx_messageInfo_MsgUpdateKeygenResponse proto.InternalMessageInfo type MsgResetChainNonces struct { Creator string `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty"` ChainId int64 `protobuf:"varint,2,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` - ChainNonceLow uint64 `protobuf:"varint,3,opt,name=chain_nonce_low,json=chainNonceLow,proto3" json:"chain_nonce_low,omitempty"` - ChainNonceHigh uint64 `protobuf:"varint,4,opt,name=chain_nonce_high,json=chainNonceHigh,proto3" json:"chain_nonce_high,omitempty"` + ChainNonceLow int64 `protobuf:"varint,3,opt,name=chain_nonce_low,json=chainNonceLow,proto3" json:"chain_nonce_low,omitempty"` + ChainNonceHigh int64 `protobuf:"varint,4,opt,name=chain_nonce_high,json=chainNonceHigh,proto3" json:"chain_nonce_high,omitempty"` } func (m *MsgResetChainNonces) Reset() { *m = MsgResetChainNonces{} } @@ -876,14 +876,14 @@ func (m *MsgResetChainNonces) GetChainId() int64 { return 0 } -func (m *MsgResetChainNonces) GetChainNonceLow() uint64 { +func (m *MsgResetChainNonces) GetChainNonceLow() int64 { if m != nil { return m.ChainNonceLow } return 0 } -func (m *MsgResetChainNonces) GetChainNonceHigh() uint64 { +func (m *MsgResetChainNonces) GetChainNonceHigh() int64 { if m != nil { return m.ChainNonceHigh } @@ -950,71 +950,71 @@ func init() { func init() { proto.RegisterFile("observer/tx.proto", fileDescriptor_1bcd40fa296a2b1d) } var fileDescriptor_1bcd40fa296a2b1d = []byte{ - // 1022 bytes of a gzipped FileDescriptorProto + // 1021 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0xcb, 0x6e, 0xdb, 0x46, - 0x14, 0x35, 0x63, 0xc7, 0xb1, 0xaf, 0x1c, 0x3f, 0x26, 0x76, 0x2c, 0xcb, 0xb1, 0x62, 0x70, 0x51, - 0xa8, 0xad, 0x2b, 0xd9, 0x4a, 0x5b, 0x34, 0x05, 0xba, 0xb0, 0xfb, 0xb0, 0xd5, 0x34, 0xb1, 0x41, - 0xa0, 0x5e, 0x74, 0x43, 0x8c, 0x38, 0x63, 0x92, 0x08, 0x35, 0x23, 0x70, 0xa8, 0xd8, 0x2a, 0xda, - 0x02, 0xfd, 0x80, 0xa2, 0xfd, 0x80, 0xfe, 0x44, 0xff, 0xa1, 0x8b, 0x2c, 0xb3, 0xec, 0xaa, 0x28, - 0xec, 0x55, 0xfb, 0x03, 0xdd, 0x06, 0x1c, 0x92, 0x23, 0x51, 0x94, 0x28, 0xc9, 0x2b, 0x72, 0xe6, - 0x9e, 0x7b, 0xee, 0x63, 0xce, 0x3c, 0x60, 0x8d, 0x37, 0x05, 0xf5, 0x5f, 0x51, 0xbf, 0x16, 0x5c, - 0x55, 0xdb, 0x3e, 0x0f, 0x38, 0xda, 0xfe, 0x9e, 0x06, 0xd8, 0x72, 0xb0, 0xcb, 0xaa, 0xf2, 0x8f, - 0xfb, 0xb4, 0x9a, 0xa0, 0x4a, 0x0f, 0x2c, 0xde, 0x6a, 0x71, 0x56, 0x8b, 0x3e, 0x91, 0x47, 0x69, - 0xdd, 0xe6, 0x36, 0x97, 0xbf, 0xb5, 0xf0, 0x2f, 0x99, 0x55, 0xd4, 0x4d, 0x0f, 0xb7, 0x68, 0x3c, - 0xfb, 0x58, 0xcd, 0x5a, 0x3e, 0x17, 0x42, 0xc6, 0x31, 0x2f, 0x3c, 0x6c, 0x8b, 0x18, 0xb0, 0xa9, - 0x00, 0xc9, 0x4f, 0x6c, 0xd8, 0x50, 0x86, 0x36, 0xf6, 0x71, 0x2b, 0xc1, 0xef, 0xf4, 0xa6, 0x29, - 0x23, 0x2e, 0xb3, 0x4d, 0xc6, 0x99, 0x45, 0x13, 0x33, 0xea, 0x15, 0x28, 0xe2, 0x39, 0xfd, 0x5f, - 0x0d, 0xd6, 0x9e, 0x0b, 0xfb, 0xdb, 0x36, 0xc1, 0x01, 0x3d, 0x8d, 0xed, 0xa8, 0x08, 0xf7, 0x2c, - 0x9f, 0xe2, 0x80, 0xfb, 0x45, 0x6d, 0x57, 0xab, 0x2c, 0x1a, 0xc9, 0x10, 0xed, 0xc3, 0x3a, 0xf7, - 0x88, 0x99, 0x30, 0x99, 0x98, 0x10, 0x9f, 0x0a, 0x51, 0xbc, 0x23, 0x61, 0x88, 0x7b, 0x24, 0x21, - 0x39, 0x8c, 0x2c, 0xa1, 0x07, 0xa3, 0x97, 0x59, 0x8f, 0xd9, 0xc8, 0x83, 0xd1, 0xcb, 0x41, 0x8f, - 0x73, 0xb8, 0xdf, 0x91, 0xf9, 0x98, 0x3e, 0xc5, 0x82, 0xb3, 0xe2, 0xdc, 0xae, 0x56, 0x59, 0xae, - 0x1f, 0x54, 0x73, 0x56, 0xa3, 0x9a, 0x90, 0x44, 0x95, 0x18, 0xd2, 0xd1, 0x58, 0xea, 0xf4, 0x8d, - 0xf4, 0x6d, 0xd8, 0xca, 0x94, 0x6a, 0x50, 0xd1, 0xe6, 0x4c, 0x50, 0xfd, 0x8f, 0xa8, 0x11, 0x87, - 0x84, 0x1c, 0x79, 0xdc, 0x7a, 0x79, 0x42, 0x31, 0xc9, 0x6d, 0xc4, 0x16, 0x2c, 0x44, 0x0b, 0xe6, - 0x12, 0x59, 0xfc, 0xac, 0x71, 0x4f, 0x8e, 0x1b, 0x04, 0xed, 0x00, 0x34, 0x43, 0x0e, 0xd3, 0xc1, - 0xc2, 0x91, 0x75, 0x2e, 0x19, 0x8b, 0x72, 0xe6, 0x04, 0x0b, 0x07, 0x3d, 0x84, 0x79, 0x87, 0xba, - 0xb6, 0x13, 0xc8, 0xba, 0x66, 0x8d, 0x78, 0x84, 0xf6, 0xc3, 0xf9, 0x30, 0x6a, 0xf1, 0xee, 0xae, - 0x56, 0x29, 0xd4, 0x51, 0x35, 0x56, 0x56, 0x94, 0xcb, 0x17, 0x38, 0xc0, 0x47, 0x73, 0xaf, 0xff, - 0x7e, 0x3c, 0x63, 0xc4, 0xb8, 0xb8, 0xa0, 0x74, 0xca, 0xaa, 0xa0, 0x1f, 0x60, 0x5d, 0x55, 0xfb, - 0x79, 0x98, 0xd9, 0x99, 0x94, 0x4a, 0x4e, 0x49, 0x5f, 0x43, 0xc1, 0xea, 0x01, 0x65, 0x55, 0x85, - 0x7a, 0x25, 0xb7, 0xeb, 0x7d, 0xc4, 0x46, 0xbf, 0xb3, 0x5e, 0x86, 0x47, 0xc3, 0xa2, 0xab, 0xec, - 0x9e, 0xc9, 0xec, 0x0c, 0xda, 0xe2, 0xaf, 0x26, 0xcc, 0x6e, 0x74, 0xc3, 0xe3, 0x60, 0x19, 0x32, - 0x15, 0xec, 0x4f, 0x0d, 0x96, 0xa3, 0x46, 0x4d, 0xa0, 0xf0, 0x77, 0x61, 0x75, 0x84, 0xba, 0x57, - 0xf8, 0x80, 0x50, 0x3f, 0x85, 0x2d, 0xd9, 0x12, 0xcf, 0xa5, 0x2c, 0x30, 0x6d, 0x1f, 0xb3, 0x80, - 0x52, 0xb3, 0xdd, 0x69, 0xbe, 0xa4, 0xdd, 0x58, 0xdf, 0x9b, 0x3d, 0xc0, 0x71, 0x64, 0x3f, 0x93, - 0x66, 0x74, 0x00, 0x1b, 0x98, 0x10, 0x93, 0x71, 0x42, 0x4d, 0x6c, 0x59, 0xbc, 0xc3, 0x02, 0x93, - 0x33, 0xaf, 0x2b, 0x45, 0xb1, 0x60, 0x20, 0x4c, 0xc8, 0x0b, 0x4e, 0xe8, 0x61, 0x64, 0x3a, 0x65, - 0x5e, 0x57, 0x2f, 0xc2, 0xc3, 0x74, 0x15, 0xaa, 0xc0, 0x5f, 0x35, 0x58, 0x49, 0x94, 0x80, 0x5b, - 0xf4, 0x9c, 0x07, 0xf4, 0x76, 0xd2, 0x3d, 0x0e, 0xa5, 0x8b, 0x5b, 0xd4, 0x74, 0xd9, 0x05, 0x97, - 0x25, 0x14, 0xea, 0x7a, 0xae, 0x02, 0x64, 0xc0, 0x58, 0x97, 0x8b, 0xd2, 0xb7, 0xc1, 0x2e, 0xb8, - 0xbe, 0x05, 0x9b, 0x03, 0x09, 0xa9, 0x64, 0xff, 0xbf, 0x03, 0xc5, 0x9e, 0x36, 0xd4, 0xc9, 0xf7, - 0x55, 0x78, 0xf0, 0xe5, 0x64, 0xfd, 0x1e, 0xac, 0xba, 0xa2, 0xc1, 0x9a, 0xbc, 0xc3, 0xc8, 0x97, - 0x0c, 0x37, 0x3d, 0x4a, 0x64, 0x82, 0x0b, 0x46, 0x66, 0x1e, 0xed, 0xc1, 0x9a, 0x2b, 0x4e, 0x3b, - 0x41, 0x0a, 0x1c, 0x35, 0x36, 0x6b, 0x40, 0x0e, 0x6c, 0xd8, 0x58, 0x9c, 0xf9, 0xae, 0x45, 0x1b, - 0x2c, 0x0c, 0x27, 0xa8, 0x4c, 0x26, 0xde, 0x87, 0xf5, 0xdc, 0xfa, 0x8f, 0x87, 0x79, 0x1a, 0xc3, - 0x09, 0xd1, 0x8f, 0xf0, 0xa8, 0xd9, 0xdb, 0xaa, 0xe7, 0xd4, 0x77, 0x2f, 0x5c, 0x0b, 0x07, 0x2e, - 0x8f, 0xaa, 0x2f, 0xce, 0xcb, 0x80, 0x4f, 0xc7, 0x34, 0x7c, 0x34, 0x81, 0x91, 0x4b, 0xaf, 0xeb, - 0xb0, 0x3b, 0xaa, 0xf1, 0x6a, 0x75, 0x0e, 0xa5, 0x92, 0x22, 0xcc, 0x33, 0xda, 0xb5, 0x29, 0xcb, - 0x59, 0x93, 0x75, 0xb8, 0x2b, 0x03, 0xc6, 0x32, 0x8a, 0x06, 0xf1, 0xda, 0xf7, 0x53, 0x28, 0xf6, - 0xdf, 0x35, 0x78, 0x20, 0xb7, 0xaa, 0xa0, 0x81, 0xdc, 0xa9, 0x2f, 0xe4, 0x05, 0x75, 0x3b, 0xb1, - 0xbe, 0x03, 0x2b, 0x91, 0x49, 0xde, 0x72, 0xa6, 0xc7, 0x2f, 0xa5, 0x20, 0xe6, 0x8c, 0xfb, 0x96, - 0xa2, 0xfe, 0x86, 0x5f, 0xa2, 0x0a, 0xac, 0xf6, 0xe3, 0x1c, 0xd7, 0x76, 0xa4, 0x18, 0xe6, 0x8c, - 0xe5, 0x1e, 0xf0, 0xc4, 0xb5, 0x1d, 0x7d, 0x07, 0xb6, 0x87, 0x64, 0x97, 0x64, 0x5f, 0xff, 0x6f, - 0x01, 0x66, 0x9f, 0x0b, 0x1b, 0x71, 0x28, 0xf4, 0x9f, 0x25, 0xef, 0xe7, 0xae, 0x57, 0x7a, 0xcb, - 0x96, 0x9e, 0x4c, 0x01, 0x4e, 0x02, 0xa3, 0x2b, 0x58, 0x1e, 0xb8, 0xa1, 0xab, 0xe3, 0x68, 0xd2, - 0xf8, 0xd2, 0xc7, 0xd3, 0xe1, 0x55, 0xe4, 0x9f, 0x35, 0x58, 0xcb, 0xde, 0x21, 0x07, 0x93, 0xb1, - 0xf5, 0xb9, 0x94, 0x9e, 0x4e, 0xed, 0x92, 0xca, 0x21, 0x7b, 0x53, 0x8c, 0xcd, 0x21, 0xe3, 0x32, - 0x3e, 0x87, 0x91, 0x57, 0x08, 0xf2, 0x61, 0x29, 0x75, 0xba, 0xee, 0x4d, 0xb0, 0x8c, 0x0a, 0x5d, - 0xfa, 0x70, 0x1a, 0xb4, 0x8a, 0xf9, 0x8b, 0x06, 0x1b, 0xc3, 0x4f, 0xc9, 0x8f, 0x26, 0x6c, 0x66, - 0xda, 0xad, 0xf4, 0xd9, 0xad, 0xdc, 0xfa, 0x7b, 0x90, 0x3a, 0x17, 0xf6, 0x26, 0xa3, 0x8b, 0xd0, - 0xe3, 0x7b, 0x30, 0xec, 0xc0, 0x08, 0x95, 0x3f, 0xf0, 0x24, 0xab, 0x4e, 0xd4, 0x4b, 0x85, 0x1f, - 0xaf, 0xfc, 0xe1, 0xef, 0x27, 0xf4, 0x13, 0xac, 0x66, 0x8e, 0xa9, 0xfd, 0xf1, 0x02, 0x4a, 0x7b, - 0x94, 0x3e, 0x99, 0xd6, 0x23, 0x89, 0x7f, 0xd4, 0x78, 0x7d, 0x5d, 0xd6, 0xde, 0x5c, 0x97, 0xb5, - 0x7f, 0xae, 0xcb, 0xda, 0x6f, 0x37, 0xe5, 0x99, 0x37, 0x37, 0xe5, 0x99, 0xbf, 0x6e, 0xca, 0x33, - 0xdf, 0xd5, 0x6c, 0x37, 0x70, 0x3a, 0xcd, 0xf0, 0x79, 0x58, 0x0b, 0x39, 0x3f, 0x90, 0xf4, 0xb5, - 0x84, 0xbe, 0x76, 0x55, 0xeb, 0x3d, 0xf4, 0xbb, 0x6d, 0x2a, 0x9a, 0xf3, 0xf2, 0xad, 0xff, 0xe4, - 0x6d, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5b, 0x00, 0x12, 0xf4, 0xe2, 0x0c, 0x00, 0x00, + 0x17, 0x36, 0xa3, 0xc4, 0xb1, 0x8f, 0x1c, 0x5f, 0x26, 0x76, 0x2c, 0xcb, 0xb1, 0x62, 0x70, 0xf1, + 0x43, 0x7f, 0xeb, 0x4a, 0xb6, 0xd2, 0x16, 0x4d, 0x81, 0x2e, 0xec, 0x5e, 0x6c, 0x35, 0x4d, 0x6c, + 0x10, 0xa8, 0x17, 0xdd, 0x10, 0x23, 0xce, 0x98, 0x24, 0x42, 0xcd, 0x08, 0x1c, 0x2a, 0xb6, 0x8a, + 0xb6, 0x40, 0x1f, 0xa0, 0x68, 0x1f, 0xa0, 0x2f, 0xd1, 0x77, 0xe8, 0x22, 0xcb, 0x2c, 0xbb, 0x2a, + 0x0a, 0x7b, 0xd5, 0xbe, 0x40, 0xb7, 0x05, 0x87, 0xe4, 0x48, 0x14, 0x25, 0x4a, 0xf2, 0x8a, 0x9c, + 0x39, 0xdf, 0xf9, 0xce, 0x65, 0xbe, 0xb9, 0xc0, 0x1a, 0x6f, 0x09, 0xea, 0xbf, 0xa6, 0x7e, 0x3d, + 0xb8, 0xaa, 0x75, 0x7c, 0x1e, 0x70, 0xb4, 0xfd, 0x2d, 0x0d, 0xb0, 0xe5, 0x60, 0x97, 0xd5, 0xe4, + 0x1f, 0xf7, 0x69, 0x2d, 0x41, 0x95, 0x1f, 0x5a, 0xbc, 0xdd, 0xe6, 0xac, 0x1e, 0x7d, 0x22, 0x8f, + 0xf2, 0xba, 0xcd, 0x6d, 0x2e, 0x7f, 0xeb, 0xe1, 0x5f, 0x32, 0xab, 0xa8, 0x5b, 0x1e, 0x6e, 0xd3, + 0x78, 0xf6, 0x89, 0x9a, 0xb5, 0x7c, 0x2e, 0x84, 0x8c, 0x63, 0x5e, 0x78, 0xd8, 0x16, 0x31, 0x60, + 0x53, 0x01, 0x92, 0x9f, 0xd8, 0xb0, 0xa1, 0x0c, 0x1d, 0xec, 0xe3, 0x76, 0x82, 0xdf, 0xe9, 0x4f, + 0x53, 0x46, 0x5c, 0x66, 0x9b, 0x8c, 0x33, 0x8b, 0x26, 0x66, 0xd4, 0x2f, 0x50, 0xc4, 0x73, 0xfa, + 0xdf, 0x1a, 0xac, 0xbd, 0x10, 0xf6, 0xd7, 0x1d, 0x82, 0x03, 0x7a, 0x1a, 0xdb, 0x51, 0x09, 0xee, + 0x5b, 0x3e, 0xc5, 0x01, 0xf7, 0x4b, 0xda, 0xae, 0x56, 0x5d, 0x34, 0x92, 0x21, 0xda, 0x87, 0x75, + 0xee, 0x11, 0x33, 0x61, 0x32, 0x31, 0x21, 0x3e, 0x15, 0xa2, 0x74, 0x47, 0xc2, 0x10, 0xf7, 0x48, + 0x42, 0x72, 0x18, 0x59, 0x42, 0x0f, 0x46, 0x2f, 0xb3, 0x1e, 0x85, 0xc8, 0x83, 0xd1, 0xcb, 0x61, + 0x8f, 0x73, 0x78, 0xd0, 0x95, 0xf9, 0x98, 0x3e, 0xc5, 0x82, 0xb3, 0xd2, 0xdd, 0x5d, 0xad, 0xba, + 0xdc, 0x38, 0xa8, 0xe5, 0xac, 0x46, 0x2d, 0x21, 0x89, 0x2a, 0x31, 0xa4, 0xa3, 0xb1, 0xd4, 0x1d, + 0x18, 0xe9, 0xdb, 0xb0, 0x95, 0x29, 0xd5, 0xa0, 0xa2, 0xc3, 0x99, 0xa0, 0xfa, 0x6f, 0x51, 0x23, + 0x0e, 0x09, 0x39, 0xf2, 0xb8, 0xf5, 0xea, 0x84, 0x62, 0x92, 0xdb, 0x88, 0x2d, 0x58, 0x88, 0x16, + 0xcc, 0x25, 0xb2, 0xf8, 0x82, 0x71, 0x5f, 0x8e, 0x9b, 0x04, 0xed, 0x00, 0xb4, 0x42, 0x0e, 0xd3, + 0xc1, 0xc2, 0x91, 0x75, 0x2e, 0x19, 0x8b, 0x72, 0xe6, 0x04, 0x0b, 0x07, 0x3d, 0x82, 0x79, 0x87, + 0xba, 0xb6, 0x13, 0xc8, 0xba, 0x0a, 0x46, 0x3c, 0x42, 0xfb, 0xe1, 0x7c, 0x18, 0xb5, 0x74, 0x6f, + 0x57, 0xab, 0x16, 0x1b, 0xa8, 0x16, 0x2b, 0x2b, 0xca, 0xe5, 0x33, 0x1c, 0xe0, 0xa3, 0xbb, 0x6f, + 0xfe, 0x7c, 0x32, 0x67, 0xc4, 0xb8, 0xb8, 0xa0, 0x74, 0xca, 0xaa, 0xa0, 0xef, 0x60, 0x5d, 0x55, + 0xfb, 0x69, 0x98, 0xd9, 0x99, 0x94, 0x4a, 0x4e, 0x49, 0x5f, 0x42, 0xd1, 0xea, 0x03, 0x65, 0x55, + 0xc5, 0x46, 0x35, 0xb7, 0xeb, 0x03, 0xc4, 0xc6, 0xa0, 0xb3, 0x5e, 0x81, 0xc7, 0xa3, 0xa2, 0xab, + 0xec, 0x9e, 0xcb, 0xec, 0x0c, 0xda, 0xe6, 0xaf, 0xa7, 0xcc, 0x6e, 0x7c, 0xc3, 0xe3, 0x60, 0x19, + 0x32, 0x15, 0xec, 0x77, 0x0d, 0x96, 0xa3, 0x46, 0x4d, 0xa1, 0xf0, 0xff, 0xc3, 0xea, 0x18, 0x75, + 0xaf, 0xf0, 0x21, 0xa1, 0x7e, 0x0c, 0x5b, 0xb2, 0x25, 0x9e, 0x4b, 0x59, 0x60, 0xda, 0x3e, 0x66, + 0x01, 0xa5, 0x66, 0xa7, 0xdb, 0x7a, 0x45, 0x7b, 0xb1, 0xbe, 0x37, 0xfb, 0x80, 0xe3, 0xc8, 0x7e, + 0x26, 0xcd, 0xe8, 0x00, 0x36, 0x30, 0x21, 0x26, 0xe3, 0x84, 0x9a, 0xd8, 0xb2, 0x78, 0x97, 0x05, + 0x26, 0x67, 0x5e, 0x4f, 0x8a, 0x62, 0xc1, 0x40, 0x98, 0x90, 0x97, 0x9c, 0xd0, 0xc3, 0xc8, 0x74, + 0xca, 0xbc, 0x9e, 0x5e, 0x82, 0x47, 0xe9, 0x2a, 0x54, 0x81, 0x3f, 0x6b, 0xb0, 0x92, 0x28, 0x01, + 0xb7, 0xe9, 0x39, 0x0f, 0xe8, 0xed, 0xa4, 0x7b, 0x1c, 0x4a, 0x17, 0xb7, 0xa9, 0xe9, 0xb2, 0x0b, + 0x2e, 0x4b, 0x28, 0x36, 0xf4, 0x5c, 0x05, 0xc8, 0x80, 0xb1, 0x2e, 0x17, 0xa5, 0x6f, 0x93, 0x5d, + 0x70, 0x7d, 0x0b, 0x36, 0x87, 0x12, 0x52, 0xc9, 0xfe, 0x7b, 0x07, 0x4a, 0x7d, 0x6d, 0xa8, 0x93, + 0xef, 0x8b, 0xf0, 0xe0, 0xcb, 0xc9, 0xfa, 0x1d, 0x58, 0x75, 0x45, 0x93, 0xb5, 0x78, 0x97, 0x91, + 0xcf, 0x19, 0x6e, 0x79, 0x94, 0xc8, 0x04, 0x17, 0x8c, 0xcc, 0x3c, 0xda, 0x83, 0x35, 0x57, 0x9c, + 0x76, 0x83, 0x14, 0x38, 0x6a, 0x6c, 0xd6, 0x80, 0x1c, 0xd8, 0xb0, 0xb1, 0x38, 0xf3, 0x5d, 0x8b, + 0x36, 0x59, 0x18, 0x4e, 0x50, 0x99, 0x4c, 0xbc, 0x0f, 0x1b, 0xb9, 0xf5, 0x1f, 0x8f, 0xf2, 0x34, + 0x46, 0x13, 0xa2, 0xef, 0xe1, 0x71, 0xab, 0xbf, 0x55, 0xcf, 0xa9, 0xef, 0x5e, 0xb8, 0x16, 0x0e, + 0x5c, 0x1e, 0x55, 0x5f, 0x9a, 0x97, 0x01, 0x9f, 0x4d, 0x68, 0xf8, 0x78, 0x02, 0x23, 0x97, 0x5e, + 0xd7, 0x61, 0x77, 0x5c, 0xe3, 0xd5, 0xea, 0x1c, 0x4a, 0x25, 0x45, 0x98, 0xe7, 0xb4, 0x67, 0x53, + 0x96, 0xb3, 0x26, 0xeb, 0x70, 0x4f, 0x06, 0x8c, 0x65, 0x14, 0x0d, 0xe2, 0xb5, 0x1f, 0xa4, 0x50, + 0xec, 0xbf, 0x6a, 0xf0, 0x50, 0x6e, 0x55, 0x41, 0x03, 0xb9, 0x53, 0x5f, 0xca, 0x0b, 0xea, 0x76, + 0x62, 0xfd, 0x1f, 0xac, 0x44, 0x26, 0x79, 0xcb, 0x99, 0x1e, 0xbf, 0x94, 0x82, 0x28, 0x18, 0x0f, + 0x2c, 0x45, 0xfd, 0x15, 0xbf, 0x44, 0x55, 0x58, 0x1d, 0xc4, 0x39, 0xae, 0xed, 0xc4, 0x47, 0xef, + 0x72, 0x1f, 0x78, 0xe2, 0xda, 0x8e, 0xbe, 0x03, 0xdb, 0x23, 0xb2, 0x4b, 0xb2, 0x6f, 0xfc, 0xb3, + 0x00, 0x85, 0x17, 0xc2, 0x46, 0x1c, 0x8a, 0x83, 0x67, 0xc9, 0xbb, 0xb9, 0xeb, 0x95, 0xde, 0xb2, + 0xe5, 0xa7, 0x33, 0x80, 0x93, 0xc0, 0xe8, 0x0a, 0x96, 0x87, 0x6e, 0xe8, 0xda, 0x24, 0x9a, 0x34, + 0xbe, 0xfc, 0xe1, 0x6c, 0x78, 0x15, 0xf9, 0x47, 0x0d, 0xd6, 0xb2, 0x77, 0xc8, 0xc1, 0x74, 0x6c, + 0x03, 0x2e, 0xe5, 0x67, 0x33, 0xbb, 0xa4, 0x72, 0xc8, 0xde, 0x14, 0x13, 0x73, 0xc8, 0xb8, 0x4c, + 0xce, 0x61, 0xec, 0x15, 0x82, 0x7c, 0x58, 0x4a, 0x9d, 0xae, 0x7b, 0x53, 0x2c, 0xa3, 0x42, 0x97, + 0xdf, 0x9f, 0x05, 0xad, 0x62, 0xfe, 0xa4, 0xc1, 0xc6, 0xe8, 0x53, 0xf2, 0x83, 0x29, 0x9b, 0x99, + 0x76, 0x2b, 0x7f, 0x72, 0x2b, 0xb7, 0xc1, 0x1e, 0xa4, 0xce, 0x85, 0xbd, 0xe9, 0xe8, 0x22, 0xf4, + 0xe4, 0x1e, 0x8c, 0x3a, 0x30, 0x42, 0xe5, 0x0f, 0x3d, 0xc9, 0x6a, 0x53, 0xf5, 0x52, 0xe1, 0x27, + 0x2b, 0x7f, 0xf4, 0xfb, 0x09, 0xfd, 0x00, 0xab, 0x99, 0x63, 0x6a, 0x7f, 0xb2, 0x80, 0xd2, 0x1e, + 0xe5, 0x8f, 0x66, 0xf5, 0x48, 0xe2, 0x1f, 0x35, 0xdf, 0x5c, 0x57, 0xb4, 0xb7, 0xd7, 0x15, 0xed, + 0xaf, 0xeb, 0x8a, 0xf6, 0xcb, 0x4d, 0x65, 0xee, 0xed, 0x4d, 0x65, 0xee, 0x8f, 0x9b, 0xca, 0xdc, + 0x37, 0x75, 0xdb, 0x0d, 0x9c, 0x6e, 0x2b, 0x7c, 0x1e, 0xd6, 0x43, 0xce, 0xf7, 0x24, 0x7d, 0x3d, + 0xa1, 0xaf, 0x5f, 0xd5, 0xfb, 0x0f, 0xfd, 0x5e, 0x87, 0x8a, 0xd6, 0xbc, 0x7c, 0xeb, 0x3f, 0xfd, + 0x2f, 0x00, 0x00, 0xff, 0xff, 0xd0, 0xc2, 0x64, 0xda, 0xe2, 0x0c, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -3975,7 +3975,7 @@ func (m *MsgResetChainNonces) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.ChainNonceLow |= uint64(b&0x7F) << shift + m.ChainNonceLow |= int64(b&0x7F) << shift if b < 0x80 { break } @@ -3994,7 +3994,7 @@ func (m *MsgResetChainNonces) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.ChainNonceHigh |= uint64(b&0x7F) << shift + m.ChainNonceHigh |= int64(b&0x7F) << shift if b < 0x80 { break } diff --git a/zetaclient/app_context/app_context_test.go b/zetaclient/app_context/app_context_test.go new file mode 100644 index 0000000000..9669dfdedb --- /dev/null +++ b/zetaclient/app_context/app_context_test.go @@ -0,0 +1 @@ +package appcontext_test diff --git a/zetaclient/authz/authz_signer_test.go b/zetaclient/authz/authz_signer_test.go new file mode 100644 index 0000000000..00a25722c4 --- /dev/null +++ b/zetaclient/authz/authz_signer_test.go @@ -0,0 +1 @@ +package authz_test diff --git a/zetaclient/bitcoin/bitcoin_client.go b/zetaclient/bitcoin/bitcoin_client.go index 34ddc580ab..fd894ba201 100644 --- a/zetaclient/bitcoin/bitcoin_client.go +++ b/zetaclient/bitcoin/bitcoin_client.go @@ -15,6 +15,7 @@ import ( appcontext "github.com/zeta-chain/zetacore/zetaclient/app_context" clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" + "github.com/zeta-chain/zetacore/zetaclient/compliance" "github.com/zeta-chain/zetacore/zetaclient/config" "github.com/zeta-chain/zetacore/zetaclient/interfaces" "github.com/zeta-chain/zetacore/zetaclient/metrics" @@ -140,6 +141,7 @@ func NewBitcoinClient( tss interfaces.TSSSigner, dbpath string, loggers clientcommon.ClientLogger, + btcCfg config.BTCConfig, ts *metrics.TelemetryServer, ) (*BTCChainClient, error) { ob := BTCChainClient{ @@ -174,7 +176,6 @@ func NewBitcoinClient( } ob.params = *chainParams // initialize the Client - btcCfg := appcontext.Config().BitcoinConfig ob.logger.ChainLogger.Info().Msgf("Chain %s endpoint %s", ob.chain.String(), btcCfg.RPCHost) connCfg := &rpcclient.ConnConfig{ Host: btcCfg.RPCHost, @@ -712,7 +713,7 @@ func (ob *BTCChainClient) IsInTxRestricted(inTx *BTCInTxEvnet) bool { receiver = parsedAddress.Hex() } if config.ContainRestrictedAddress(inTx.FromAddress, receiver) { - clientcommon.PrintComplianceLog(ob.logger.WatchInTx, ob.logger.Compliance, + compliance.PrintComplianceLog(ob.logger.WatchInTx, ob.logger.Compliance, false, ob.chain.ChainId, inTx.TxHash, inTx.FromAddress, receiver, "BTC") return true } @@ -1230,7 +1231,7 @@ func (ob *BTCChainClient) checkTssOutTxResult(cctx *types.CrossChainTx, hash *ch } // differentiate between normal and restricted cctx - if clientcommon.IsCctxRestricted(cctx) { + if compliance.IsCctxRestricted(cctx) { err = ob.checkTSSVoutCancelled(params, rawResult.Vout) if err != nil { return errors.Wrapf(err, "checkTssOutTxResult: invalid TSS Vout in cancelled outTx %s nonce %d", hash, nonce) diff --git a/zetaclient/bitcoin/bitcoin_client_rpc_test.go b/zetaclient/bitcoin/bitcoin_client_rpc_test.go index 7f8c060dce..0f9acc6a3d 100644 --- a/zetaclient/bitcoin/bitcoin_client_rpc_test.go +++ b/zetaclient/bitcoin/bitcoin_client_rpc_test.go @@ -47,7 +47,8 @@ func (suite *BitcoinClientTestSuite) SetupTest() { PrivKey: privateKey, } appContext := appcontext.NewAppContext(&corecontext.ZetaCoreContext{}, config.Config{}) - client, err := NewBitcoinClient(appContext, common.BtcRegtestChain(), nil, tss, "/tmp", clientcommon.DefaultLoggers(), nil) + client, err := NewBitcoinClient(appContext, common.BtcRegtestChain(), nil, tss, "/tmp", + clientcommon.DefaultLoggers(), config.BTCConfig{}, nil) suite.Require().NoError(err) suite.BitcoinChainClient = client skBytes, err := hex.DecodeString(skHex) diff --git a/zetaclient/bitcoin/bitcoin_client_test.go b/zetaclient/bitcoin/bitcoin_client_test.go index 68cb840e0f..9f50f9e608 100644 --- a/zetaclient/bitcoin/bitcoin_client_test.go +++ b/zetaclient/bitcoin/bitcoin_client_test.go @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/common" observertypes "github.com/zeta-chain/zetacore/x/observer/types" + clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" "github.com/zeta-chain/zetacore/zetaclient/testutils" "github.com/zeta-chain/zetacore/zetaclient/testutils/stub" ) @@ -155,7 +156,10 @@ func TestCalcDepositorFee828440(t *testing.T) { var blockVb btcjson.GetBlockVerboseTxResult err := testutils.LoadObjectFromJSONFile(&blockVb, path.Join("../", testutils.TestDataPathBTC, "block_trimmed_8332_828440.json")) require.NoError(t, err) - dynamicFee828440 := DepositorFee(32 * common.DefaultGasPriceMultiplier) + avgGasRate := float64(32.0) + // #nosec G701 test - always in range + gasRate := int64(avgGasRate * clientcommon.BTCOuttxGasPriceMultiplier) + dynamicFee828440 := DepositorFee(gasRate) // should return default fee if it's a regtest block fee := CalcDepositorFee(&blockVb, 18444, &chaincfg.RegressionNetParams, log.Logger) diff --git a/zetaclient/bitcoin/bitcoin_signer.go b/zetaclient/bitcoin/bitcoin_signer.go index 05106e9a48..f665027f3a 100644 --- a/zetaclient/bitcoin/bitcoin_signer.go +++ b/zetaclient/bitcoin/bitcoin_signer.go @@ -8,7 +8,9 @@ import ( "math/rand" "time" + ethcommon "github.com/ethereum/go-ethereum/common" clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" + "github.com/zeta-chain/zetacore/zetaclient/compliance" "github.com/zeta-chain/zetacore/zetaclient/interfaces" "github.com/zeta-chain/zetacore/zetaclient/metrics" "github.com/zeta-chain/zetacore/zetaclient/outtxprocessor" @@ -34,6 +36,7 @@ const ( outTxBytesMax = uint64(1531) // 1531v == EstimateSegWitTxSize(21, 3) ) +// BTCSigner deals with signing BTC transactions and implements the ChainSigner interface type BTCSigner struct { tssSigner interfaces.TSSSigner rpcClient interfaces.BTCRPCClient @@ -71,6 +74,24 @@ func NewBTCSigner( }, nil } +// SetZetaConnectorAddress does nothing for BTC +func (signer *BTCSigner) SetZetaConnectorAddress(_ ethcommon.Address) { +} + +// SetERC20CustodyAddress does nothing for BTC +func (signer *BTCSigner) SetERC20CustodyAddress(_ ethcommon.Address) { +} + +// GetZetaConnectorAddress returns dummy address +func (signer *BTCSigner) GetZetaConnectorAddress() ethcommon.Address { + return ethcommon.Address{} +} + +// GetERC20CustodyAddress returns dummy address +func (signer *BTCSigner) GetERC20CustodyAddress() ethcommon.Address { + return ethcommon.Address{} +} + // SignWithdrawTx receives utxos sorted by value, amount in BTC, feeRate in BTC per Kb func (signer *BTCSigner) SignWithdrawTx( to *btcutil.AddressWitnessPubKeyHash, @@ -325,9 +346,9 @@ func (signer *BTCSigner) TryProcessOutTx( gasprice.Add(gasprice, satPerByte) // compliance check - cancelTx := clientcommon.IsCctxRestricted(cctx) + cancelTx := compliance.IsCctxRestricted(cctx) if cancelTx { - clientcommon.PrintComplianceLog(logger, signer.loggerCompliance, + compliance.PrintComplianceLog(logger, signer.loggerCompliance, true, btcClient.chain.ChainId, cctx.Index, cctx.InboundTxParams.Sender, params.Receiver, "BTC") amount = 0.0 // zero out the amount to cancel the tx } diff --git a/zetaclient/bitcoin/utils.go b/zetaclient/bitcoin/utils.go index b11c1a7056..33d654fa6c 100644 --- a/zetaclient/bitcoin/utils.go +++ b/zetaclient/bitcoin/utils.go @@ -13,6 +13,7 @@ import ( "github.com/btcsuite/btcutil" "github.com/rs/zerolog" "github.com/zeta-chain/zetacore/common" + clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" @@ -188,7 +189,8 @@ func CalcDepositorFee(blockVb *btcjson.GetBlockVerboseTxResult, chainID int64, n feeRate = defaultDepositorFeeRate // use default fee rate if calculation fails, should not happen logger.Error().Err(err).Msgf("cannot calculate fee rate for block %d", blockVb.Height) } - feeRate = feeRate * common.DefaultGasPriceMultiplier + // #nosec G701 always in range + feeRate = int64(float64(feeRate) * clientcommon.BTCOuttxGasPriceMultiplier) return DepositorFee(feeRate) } diff --git a/zetaclient/common/constant.go b/zetaclient/common/constant.go new file mode 100644 index 0000000000..5d774f73c5 --- /dev/null +++ b/zetaclient/common/constant.go @@ -0,0 +1,9 @@ +package common + +const ( + // EVMOuttxGasPriceMultiplier is the default gas price multiplier for EVM-chain outbond txs + EVMOuttxGasPriceMultiplier = 1.2 + + // BTCOuttxGasPriceMultiplier is the default gas price multiplier for BTC outbond txs + BTCOuttxGasPriceMultiplier = 2.0 +) diff --git a/zetaclient/common/logger.go b/zetaclient/common/logger.go new file mode 100644 index 0000000000..251f4cf774 --- /dev/null +++ b/zetaclient/common/logger.go @@ -0,0 +1,18 @@ +package common + +import ( + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +type ClientLogger struct { + Std zerolog.Logger + Compliance zerolog.Logger +} + +func DefaultLoggers() ClientLogger { + return ClientLogger{ + Std: log.Logger, + Compliance: log.Logger, + } +} diff --git a/zetaclient/common/utils.go b/zetaclient/compliance/compliance.go similarity index 87% rename from zetaclient/common/utils.go rename to zetaclient/compliance/compliance.go index af5d2d0771..4bfd08fdf5 100644 --- a/zetaclient/common/utils.go +++ b/zetaclient/compliance/compliance.go @@ -1,24 +1,11 @@ -package common +package compliance import ( "github.com/rs/zerolog" - "github.com/rs/zerolog/log" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" "github.com/zeta-chain/zetacore/zetaclient/config" ) -type ClientLogger struct { - Std zerolog.Logger - Compliance zerolog.Logger -} - -func DefaultLoggers() ClientLogger { - return ClientLogger{ - Std: log.Logger, - Compliance: log.Logger, - } -} - // IsCctxRestricted returns true if the cctx involves restricted addresses func IsCctxRestricted(cctx *crosschaintypes.CrossChainTx) bool { sender := cctx.InboundTxParams.Sender diff --git a/zetaclient/common/utils_test.go b/zetaclient/compliance/compliance_test.go similarity index 99% rename from zetaclient/common/utils_test.go rename to zetaclient/compliance/compliance_test.go index b7fa29b9bf..638e6c8ba0 100644 --- a/zetaclient/common/utils_test.go +++ b/zetaclient/compliance/compliance_test.go @@ -1,4 +1,4 @@ -package common +package compliance import ( "path" diff --git a/zetaclient/config/types.go b/zetaclient/config/types.go index 200066f109..69e4e8da2c 100644 --- a/zetaclient/config/types.go +++ b/zetaclient/config/types.go @@ -2,13 +2,10 @@ package config import ( "encoding/json" - "fmt" "strings" "sync" - ethcommon "github.com/ethereum/go-ethereum/common" "github.com/zeta-chain/zetacore/common" - observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) // KeyringBackend is the type of keyring backend to use for the hotkey @@ -138,63 +135,3 @@ func (c *Config) GetKeyringBackend() KeyringBackend { defer c.cfgLock.RUnlock() return c.KeyringBackend } - -// TODO: remove this duplicated function https://github.com/zeta-chain/node/issues/1838 -// ValidateChainParams performs some basic checks on core params -func ValidateChainParams(chainParams *observertypes.ChainParams) error { - if chainParams == nil { - return fmt.Errorf("invalid chain params: nil") - } - chain := common.GetChainFromChainID(chainParams.ChainId) - if chain == nil { - return fmt.Errorf("invalid chain params: chain %d not supported", chainParams.ChainId) - } - if chainParams.ConfirmationCount < 1 { - return fmt.Errorf("invalid chain params: ConfirmationCount %d", chainParams.ConfirmationCount) - } - // zeta chain skips the rest of the checks for now - if chain.IsZetaChain() { - return nil - } - - // check tickers - if chainParams.GasPriceTicker < 1 { - return fmt.Errorf("invalid chain params: GasPriceTicker %d", chainParams.GasPriceTicker) - } - if chainParams.InTxTicker < 1 { - return fmt.Errorf("invalid chain params: InTxTicker %d", chainParams.InTxTicker) - } - if chainParams.OutTxTicker < 1 { - return fmt.Errorf("invalid chain params: OutTxTicker %d", chainParams.OutTxTicker) - } - if chainParams.OutboundTxScheduleInterval < 1 { - return fmt.Errorf("invalid chain params: OutboundTxScheduleInterval %d", chainParams.OutboundTxScheduleInterval) - } - if chainParams.OutboundTxScheduleLookahead < 1 { - return fmt.Errorf("invalid chain params: OutboundTxScheduleLookahead %d", chainParams.OutboundTxScheduleLookahead) - } - - // chain type specific checks - if common.IsBitcoinChain(chainParams.ChainId) && chainParams.WatchUtxoTicker < 1 { - return fmt.Errorf("invalid chain params: watchUtxo ticker %d", chainParams.WatchUtxoTicker) - } - if common.IsEVMChain(chainParams.ChainId) { - if !validCoreContractAddress(chainParams.ZetaTokenContractAddress) { - return fmt.Errorf("invalid chain params: zeta token contract address %s", chainParams.ZetaTokenContractAddress) - } - if !validCoreContractAddress(chainParams.ConnectorContractAddress) { - return fmt.Errorf("invalid chain params: connector contract address %s", chainParams.ConnectorContractAddress) - } - if !validCoreContractAddress(chainParams.Erc20CustodyContractAddress) { - return fmt.Errorf("invalid chain params: erc20 custody contract address %s", chainParams.Erc20CustodyContractAddress) - } - } - return nil -} - -func validCoreContractAddress(address string) bool { - if !strings.HasPrefix(address, "0x") { - return false - } - return ethcommon.IsHexAddress(address) -} diff --git a/zetaclient/config/types_test.go b/zetaclient/config/types_test.go new file mode 100644 index 0000000000..d7b82b3200 --- /dev/null +++ b/zetaclient/config/types_test.go @@ -0,0 +1 @@ +package config_test diff --git a/zetaclient/evm/evm_client.go b/zetaclient/evm/evm_client.go index 87103b8e37..3930daa076 100644 --- a/zetaclient/evm/evm_client.go +++ b/zetaclient/evm/evm_client.go @@ -40,6 +40,7 @@ import ( crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" + "github.com/zeta-chain/zetacore/zetaclient/compliance" "github.com/zeta-chain/zetacore/zetaclient/config" clienttypes "github.com/zeta-chain/zetacore/zetaclient/types" "gorm.io/driver/sqlite" @@ -64,6 +65,8 @@ type Log struct { Compliance zerolog.Logger // Compliance logger } +var _ interfaces.ChainClient = &ChainClient{} + // ChainClient represents the chain configuration for an EVM chain // Filled with above constants depending on chain type ChainClient struct { @@ -328,7 +331,7 @@ func (ob *ChainClient) IsSendOutTxProcessed(cctx *crosschaintypes.CrossChainTx, logger = logger.With().Str("sendID", sendID).Logger() // compliance check, special handling the cancelled cctx - if clientcommon.IsCctxRestricted(cctx) { + if compliance.IsCctxRestricted(cctx) { recvStatus := common.ReceiveStatus_Failed if receipt.Status == 1 { recvStatus = common.ReceiveStatus_Success diff --git a/zetaclient/evm/evm_signer.go b/zetaclient/evm/evm_signer.go index 6c7e182bf9..97a1a7bee5 100644 --- a/zetaclient/evm/evm_signer.go +++ b/zetaclient/evm/evm_signer.go @@ -25,6 +25,7 @@ import ( "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" + "github.com/zeta-chain/zetacore/zetaclient/compliance" "github.com/zeta-chain/zetacore/zetaclient/interfaces" "github.com/zeta-chain/zetacore/zetaclient/metrics" "github.com/zeta-chain/zetacore/zetaclient/outtxprocessor" @@ -32,21 +33,21 @@ import ( zbridge "github.com/zeta-chain/zetacore/zetaclient/zetabridge" ) +// Signer deals with the signing EVM transactions and implements the ChainSigner interface type Signer struct { - client interfaces.EVMRPCClient - chain *common.Chain - chainID *big.Int - tssSigner interfaces.TSSSigner - ethSigner ethtypes.Signer - abi abi.ABI - erc20CustodyABI abi.ABI - metaContractAddress ethcommon.Address - erc20CustodyContractAddress ethcommon.Address - logger clientcommon.ClientLogger - ts *metrics.TelemetryServer - - // for outTx tracker report only + client interfaces.EVMRPCClient + chain *common.Chain + tssSigner interfaces.TSSSigner + ethSigner ethtypes.Signer + logger clientcommon.ClientLogger + ts *metrics.TelemetryServer + + // mu protects below fields from concurrent access mu *sync.Mutex + zetaConnectorABI abi.ABI + erc20CustodyABI abi.ABI + zetaConnectorAddress ethcommon.Address + er20CustodyAddress ethcommon.Address outTxHashBeingReported map[string]bool } @@ -56,36 +57,35 @@ func NewEVMSigner( chain common.Chain, endpoint string, tssSigner interfaces.TSSSigner, - abiString string, - erc20CustodyABIString string, - metaContract ethcommon.Address, - erc20CustodyContract ethcommon.Address, + zetaConnectorABI string, + erc20CustodyABI string, + zetaConnectorAddress ethcommon.Address, + erc20CustodyAddress ethcommon.Address, loggers clientcommon.ClientLogger, ts *metrics.TelemetryServer, ) (*Signer, error) { - client, chainID, ethSigner, err := getEVMRPC(endpoint) + client, ethSigner, err := getEVMRPC(endpoint) if err != nil { return nil, err } - connectorABI, err := abi.JSON(strings.NewReader(abiString)) + connectorABI, err := abi.JSON(strings.NewReader(zetaConnectorABI)) if err != nil { return nil, err } - erc20CustodyABI, err := abi.JSON(strings.NewReader(erc20CustodyABIString)) + custodyABI, err := abi.JSON(strings.NewReader(erc20CustodyABI)) if err != nil { return nil, err } return &Signer{ - client: client, - chain: &chain, - tssSigner: tssSigner, - chainID: chainID, - ethSigner: ethSigner, - abi: connectorABI, - erc20CustodyABI: erc20CustodyABI, - metaContractAddress: metaContract, - erc20CustodyContractAddress: erc20CustodyContract, + client: client, + chain: &chain, + tssSigner: tssSigner, + ethSigner: ethSigner, + zetaConnectorABI: connectorABI, + erc20CustodyABI: custodyABI, + zetaConnectorAddress: zetaConnectorAddress, + er20CustodyAddress: erc20CustodyAddress, logger: clientcommon.ClientLogger{ Std: loggers.Std.With().Str("chain", chain.ChainName.String()).Str("module", "EVMSigner").Logger(), Compliance: loggers.Compliance, @@ -96,6 +96,34 @@ func NewEVMSigner( }, nil } +// SetZetaConnectorAddress sets the zeta connector address +func (signer *Signer) SetZetaConnectorAddress(addr ethcommon.Address) { + signer.mu.Lock() + defer signer.mu.Unlock() + signer.zetaConnectorAddress = addr +} + +// SetERC20CustodyAddress sets the erc20 custody address +func (signer *Signer) SetERC20CustodyAddress(addr ethcommon.Address) { + signer.mu.Lock() + defer signer.mu.Unlock() + signer.er20CustodyAddress = addr +} + +// GetZetaConnectorAddress returns the zeta connector address +func (signer *Signer) GetZetaConnectorAddress() ethcommon.Address { + signer.mu.Lock() + defer signer.mu.Unlock() + return signer.zetaConnectorAddress +} + +// GetERC20CustodyAddress returns the erc20 custody address +func (signer *Signer) GetERC20CustodyAddress() ethcommon.Address { + signer.mu.Lock() + defer signer.mu.Unlock() + return signer.er20CustodyAddress +} + // Sign given data, and metadata (gas, nonce, etc) // returns a signed transaction, sig bytes, hash bytes, and error func (signer *Signer) Sign( @@ -161,7 +189,7 @@ func (signer *Signer) SignOutboundTx(txData *OutBoundTransactionData) (*ethtypes var data []byte var err error - data, err = signer.abi.Pack("onReceive", + data, err = signer.zetaConnectorABI.Pack("onReceive", txData.sender.Bytes(), txData.srcChainID, txData.to, @@ -173,7 +201,7 @@ func (signer *Signer) SignOutboundTx(txData *OutBoundTransactionData) (*ethtypes } tx, _, _, err := signer.Sign(data, - signer.metaContractAddress, + signer.zetaConnectorAddress, txData.gasLimit, txData.gasPrice, txData.nonce, @@ -199,7 +227,7 @@ func (signer *Signer) SignRevertTx(txData *OutBoundTransactionData) (*ethtypes.T var data []byte var err error - data, err = signer.abi.Pack("onRevert", + data, err = signer.zetaConnectorABI.Pack("onRevert", txData.sender, txData.srcChainID, txData.to.Bytes(), @@ -212,7 +240,7 @@ func (signer *Signer) SignRevertTx(txData *OutBoundTransactionData) (*ethtypes.T } tx, _, _, err := signer.Sign(data, - signer.metaContractAddress, + signer.zetaConnectorAddress, txData.gasLimit, txData.gasPrice, txData.nonce, @@ -354,8 +382,8 @@ func (signer *Signer) TryProcessOutTx( var tx *ethtypes.Transaction // compliance check goes first - if clientcommon.IsCctxRestricted(cctx) { - clientcommon.PrintComplianceLog(logger, signer.logger.Compliance, + if compliance.IsCctxRestricted(cctx) { + compliance.PrintComplianceLog(logger, signer.logger.Compliance, true, evmClient.chain.ChainId, cctx.Index, cctx.InboundTxParams.Sender, txData.to.Hex(), cctx.GetCurrentOutTxParam().CoinType.String()) tx, err = signer.SignCancelTx(txData.nonce, txData.gasPrice, height) // cancel the tx if err != nil { @@ -589,7 +617,7 @@ func (signer *Signer) SignERC20WithdrawTx(txData *OutBoundTransactionData) (*eth return nil, fmt.Errorf("pack error: %w", err) } - tx, _, _, err := signer.Sign(data, signer.erc20CustodyContractAddress, txData.gasLimit, txData.gasPrice, txData.nonce, txData.height) + tx, _, _, err := signer.Sign(data, signer.er20CustodyAddress, txData.gasLimit, txData.gasPrice, txData.nonce, txData.height) if err != nil { return nil, fmt.Errorf("sign error: %w", err) } @@ -622,7 +650,7 @@ func (signer *Signer) SignWhitelistTx( return nil, fmt.Errorf("pack error: %w", err) } - tx, _, _, err := signer.Sign(data, signer.erc20CustodyContractAddress, gasLimit, gasPrice, nonce, height) + tx, _, _, err := signer.Sign(data, signer.er20CustodyAddress, gasLimit, gasPrice, nonce, height) if err != nil { return nil, fmt.Errorf("Sign error: %w", err) } @@ -645,25 +673,25 @@ func (signer *Signer) EvmSigner() ethtypes.Signer { // ________________________ // getEVMRPC is a helper function to set up the client and signer, also initializes a mock client for unit tests -func getEVMRPC(endpoint string) (interfaces.EVMRPCClient, *big.Int, ethtypes.Signer, error) { +func getEVMRPC(endpoint string) (interfaces.EVMRPCClient, ethtypes.Signer, error) { if endpoint == stub.EVMRPCEnabled { chainID := big.NewInt(common.BscMainnetChain().ChainId) ethSigner := ethtypes.NewEIP155Signer(chainID) client := &stub.MockEvmClient{} - return client, chainID, ethSigner, nil + return client, ethSigner, nil } client, err := ethclient.Dial(endpoint) if err != nil { - return nil, nil, nil, err + return nil, nil, err } chainID, err := client.ChainID(context.TODO()) if err != nil { - return nil, nil, nil, err + return nil, nil, err } ethSigner := ethtypes.LatestSignerForChainID(chainID) - return client, chainID, ethSigner, nil + return client, ethSigner, nil } func roundUpToNearestGwei(gasPrice *big.Int) *big.Int { diff --git a/zetaclient/evm/evm_signer_test.go b/zetaclient/evm/evm_signer_test.go index 3bbee44279..1685f359bb 100644 --- a/zetaclient/evm/evm_signer_test.go +++ b/zetaclient/evm/evm_signer_test.go @@ -76,6 +76,30 @@ func getInvalidCCTX() (*types.CrossChainTx, error) { return &cctx, err } +func TestSigner_SetGetConnectorAddress(t *testing.T) { + evmSigner, err := getNewEvmSigner() + require.NoError(t, err) + // Get and compare + require.Equal(t, ConnectorAddress, evmSigner.GetZetaConnectorAddress()) + + // Update and get again + newConnector := sample.EthAddress() + evmSigner.SetZetaConnectorAddress(newConnector) + require.Equal(t, newConnector, evmSigner.GetZetaConnectorAddress()) +} + +func TestSigner_SetGetERC20CustodyAddress(t *testing.T) { + evmSigner, err := getNewEvmSigner() + require.NoError(t, err) + // Get and compare + require.Equal(t, ERC20CustodyAddress, evmSigner.GetERC20CustodyAddress()) + + // Update and get again + newCustody := sample.EthAddress() + evmSigner.SetERC20CustodyAddress(newCustody) + require.Equal(t, newCustody, evmSigner.GetERC20CustodyAddress()) +} + func TestSigner_TryProcessOutTx(t *testing.T) { evmSigner, err := getNewEvmSigner() require.NoError(t, err) @@ -290,9 +314,8 @@ func TestSigner_BroadcastOutTx(t *testing.T) { func TestSigner_getEVMRPC(t *testing.T) { t.Run("getEVMRPC error dialing", func(t *testing.T) { - client, chainId, signer, err := getEVMRPC("invalidEndpoint") + client, signer, err := getEVMRPC("invalidEndpoint") require.Nil(t, client) - require.Nil(t, chainId) require.Nil(t, signer) require.Error(t, err) }) diff --git a/zetaclient/evm/inbounds.go b/zetaclient/evm/inbounds.go index 919ba6f9e2..8828d5b59f 100644 --- a/zetaclient/evm/inbounds.go +++ b/zetaclient/evm/inbounds.go @@ -13,7 +13,7 @@ import ( "github.com/pkg/errors" "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol" - clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" + "github.com/zeta-chain/zetacore/zetaclient/compliance" "github.com/zeta-chain/zetacore/zetaclient/config" clienttypes "github.com/zeta-chain/zetacore/zetaclient/types" @@ -228,7 +228,7 @@ func (ob *ChainClient) BuildInboundVoteMsgForDepositedEvent(event *erc20custody. maybeReceiver = parsedAddress.Hex() } if config.ContainRestrictedAddress(sender.Hex(), clienttypes.BytesToEthHex(event.Recipient), maybeReceiver) { - clientcommon.PrintComplianceLog(ob.logger.ExternalChainWatcher, ob.logger.Compliance, + compliance.PrintComplianceLog(ob.logger.ExternalChainWatcher, ob.logger.Compliance, false, ob.chain.ChainId, event.Raw.TxHash.Hex(), sender.Hex(), clienttypes.BytesToEthHex(event.Recipient), "ERC20") return nil } @@ -272,7 +272,7 @@ func (ob *ChainClient) BuildInboundVoteMsgForZetaSentEvent(event *zetaconnector. // compliance check sender := event.ZetaTxSenderAddress.Hex() if config.ContainRestrictedAddress(sender, destAddr, event.SourceTxOriginAddress.Hex()) { - clientcommon.PrintComplianceLog(ob.logger.ExternalChainWatcher, ob.logger.Compliance, + compliance.PrintComplianceLog(ob.logger.ExternalChainWatcher, ob.logger.Compliance, false, ob.chain.ChainId, event.Raw.TxHash.Hex(), sender, destAddr, "Zeta") return nil } @@ -322,7 +322,7 @@ func (ob *ChainClient) BuildInboundVoteMsgForTokenSentToTSS(tx *ethrpc.Transacti maybeReceiver = parsedAddress.Hex() } if config.ContainRestrictedAddress(sender.Hex(), maybeReceiver) { - clientcommon.PrintComplianceLog(ob.logger.ExternalChainWatcher, ob.logger.Compliance, + compliance.PrintComplianceLog(ob.logger.ExternalChainWatcher, ob.logger.Compliance, false, ob.chain.ChainId, tx.Hash, sender.Hex(), sender.Hex(), "Gas") return nil } diff --git a/zetaclient/evm/inbounds_test.go b/zetaclient/evm/inbounds_test.go index 0663d71eeb..e382340590 100644 --- a/zetaclient/evm/inbounds_test.go +++ b/zetaclient/evm/inbounds_test.go @@ -181,7 +181,7 @@ func TestEVM_CheckAndVoteInboundTokenGas(t *testing.T) { }) t.Run("should not act if receiver is not TSS", func(t *testing.T) { tx, receipt, _ := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_Gas) - tx.To = testutils.OtherAddress // use other address + tx.To = testutils.OtherAddress1 // use other address require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation @@ -342,9 +342,9 @@ func TestEVM_BuildInboundVoteMsgForTokenSentToTSS(t *testing.T) { t.Run("should return nil msg if receiver is restricted", func(t *testing.T) { txCopy := ðrpc.Transaction{} *txCopy = *tx - message := hex.EncodeToString(ethcommon.HexToAddress(testutils.OtherAddress).Bytes()) + message := hex.EncodeToString(ethcommon.HexToAddress(testutils.OtherAddress1).Bytes()) txCopy.Input = message // use other address as receiver - cfg.ComplianceConfig.RestrictedAddresses = []string{testutils.OtherAddress} + cfg.ComplianceConfig.RestrictedAddresses = []string{testutils.OtherAddress1} config.LoadComplianceConfig(cfg) msg := ob.BuildInboundVoteMsgForTokenSentToTSS(txCopy, ethcommon.HexToAddress(txCopy.From), receipt.BlockNumber.Uint64()) require.Nil(t, msg) diff --git a/zetaclient/interfaces/interfaces.go b/zetaclient/interfaces/interfaces.go index 5d9a40fcc9..5842c2b944 100644 --- a/zetaclient/interfaces/interfaces.go +++ b/zetaclient/interfaces/interfaces.go @@ -54,6 +54,10 @@ type ChainSigner interface { zetaBridge ZetaCoreBridger, height uint64, ) + SetZetaConnectorAddress(address ethcommon.Address) + SetERC20CustodyAddress(address ethcommon.Address) + GetZetaConnectorAddress() ethcommon.Address + GetERC20CustodyAddress() ethcommon.Address } // ZetaCoreBridger is the interface to interact with ZetaCore diff --git a/zetaclient/outtxprocessor/out_tx_processor_manager_test.go b/zetaclient/outtxprocessor/out_tx_processor_manager_test.go new file mode 100644 index 0000000000..cc7ff3888c --- /dev/null +++ b/zetaclient/outtxprocessor/out_tx_processor_manager_test.go @@ -0,0 +1 @@ +package outtxprocessor_test diff --git a/zetaclient/testutils/constant.go b/zetaclient/testutils/constant.go index f3b2cd0691..05cab82256 100644 --- a/zetaclient/testutils/constant.go +++ b/zetaclient/testutils/constant.go @@ -6,12 +6,16 @@ const ( // tss addresses TSSAddressEVMMainnet = "0x70e967acFcC17c3941E87562161406d41676FD83" TSSAddressBTCMainnet = "bc1qm24wp577nk8aacckv8np465z3dvmu7ry45el6y" + TssPubkeyEVMMainnet = "zetapub1addwnpepqtadxdyt037h86z60nl98t6zk56mw5zpnm79tsmvspln3hgt5phdc79kvfc" TSSAddressEVMAthens3 = "0x8531a5aB847ff5B22D855633C25ED1DA3255247e" TSSAddressBTCAthens3 = "tb1qy9pqmk2pd9sv63g27jt8r657wy0d9ueeh0nqur" + TssPubkeyEVMAthens3 = "zetapub1addwnpepq28c57cvcs0a2htsem5zxr6qnlvq9mzhmm76z3jncsnzz32rclangr2g35p" - // some other address - OtherAddress = "0x21248Decd0B7EcB0F30186297766b8AB6496265b" + // some other addresses + OtherAddress1 = "0x21248Decd0B7EcB0F30186297766b8AB6496265b" + OtherAddress2 = "0x33A351C90aF486AebC35042Bb0544123cAed26AB" + OtherAddress3 = "0x86B77E4fBd07CFdCc486cAe4F2787fB5C5a62cd3" ) // ConnectorAddresses contains constants ERC20 connector addresses for testing diff --git a/zetaclient/testutils/stub/chain_client.go b/zetaclient/testutils/stub/chain_client.go new file mode 100644 index 0000000000..642de62792 --- /dev/null +++ b/zetaclient/testutils/stub/chain_client.go @@ -0,0 +1,90 @@ +package stub + +import ( + "github.com/rs/zerolog" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" + "github.com/zeta-chain/zetacore/zetaclient/interfaces" +) + +// ---------------------------------------------------------------------------- +// EVMClient +// ---------------------------------------------------------------------------- +var _ interfaces.ChainClient = (*EVMClient)(nil) + +// EVMClient is a mock of evm chain client for testing +type EVMClient struct { + ChainParams observertypes.ChainParams +} + +func NewEVMClient(chainParams *observertypes.ChainParams) *EVMClient { + return &EVMClient{ + ChainParams: *chainParams, + } +} + +func (s *EVMClient) Start() { +} + +func (s *EVMClient) Stop() { +} + +func (s *EVMClient) IsSendOutTxProcessed(_ *crosschaintypes.CrossChainTx, _ zerolog.Logger) (bool, bool, error) { + return false, false, nil +} + +func (s *EVMClient) SetChainParams(chainParams observertypes.ChainParams) { + s.ChainParams = chainParams +} + +func (s *EVMClient) GetChainParams() observertypes.ChainParams { + return s.ChainParams +} + +func (s *EVMClient) GetTxID(_ uint64) string { + return "" +} + +func (s *EVMClient) ExternalChainWatcherForNewInboundTrackerSuggestions() { +} + +// ---------------------------------------------------------------------------- +// BTCClient +// ---------------------------------------------------------------------------- +var _ interfaces.ChainClient = (*BTCClient)(nil) + +// BTCClient is a mock of btc chain client for testing +type BTCClient struct { + ChainParams observertypes.ChainParams +} + +func NewBTCClient(chainParams *observertypes.ChainParams) *BTCClient { + return &BTCClient{ + ChainParams: *chainParams, + } +} + +func (s *BTCClient) Start() { +} + +func (s *BTCClient) Stop() { +} + +func (s *BTCClient) IsSendOutTxProcessed(_ *crosschaintypes.CrossChainTx, _ zerolog.Logger) (bool, bool, error) { + return false, false, nil +} + +func (s *BTCClient) SetChainParams(chainParams observertypes.ChainParams) { + s.ChainParams = chainParams +} + +func (s *BTCClient) GetChainParams() observertypes.ChainParams { + return s.ChainParams +} + +func (s *BTCClient) GetTxID(_ uint64) string { + return "" +} + +func (s *BTCClient) ExternalChainWatcherForNewInboundTrackerSuggestions() { +} diff --git a/zetaclient/testutils/stub/chain_signer.go b/zetaclient/testutils/stub/chain_signer.go new file mode 100644 index 0000000000..364cbc22bb --- /dev/null +++ b/zetaclient/testutils/stub/chain_signer.go @@ -0,0 +1,96 @@ +package stub + +import ( + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/zeta-chain/zetacore/common" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + "github.com/zeta-chain/zetacore/zetaclient/interfaces" + "github.com/zeta-chain/zetacore/zetaclient/outtxprocessor" +) + +// ---------------------------------------------------------------------------- +// EVMSigner +// ---------------------------------------------------------------------------- +var _ interfaces.ChainSigner = (*EVMSigner)(nil) + +// EVMSigner is a mock of evm chain signer for testing +type EVMSigner struct { + Chain common.Chain + ZetaConnectorAddress ethcommon.Address + ERC20CustodyAddress ethcommon.Address +} + +func NewEVMSigner( + chain common.Chain, + zetaConnectorAddress ethcommon.Address, + erc20CustodyAddress ethcommon.Address, +) *EVMSigner { + return &EVMSigner{ + Chain: chain, + ZetaConnectorAddress: zetaConnectorAddress, + ERC20CustodyAddress: erc20CustodyAddress, + } +} + +func (s *EVMSigner) TryProcessOutTx( + _ *crosschaintypes.CrossChainTx, + _ *outtxprocessor.Processor, + _ string, + _ interfaces.ChainClient, + _ interfaces.ZetaCoreBridger, + _ uint64, +) { +} + +func (s *EVMSigner) SetZetaConnectorAddress(address ethcommon.Address) { + s.ZetaConnectorAddress = address +} + +func (s *EVMSigner) SetERC20CustodyAddress(address ethcommon.Address) { + s.ERC20CustodyAddress = address +} + +func (s *EVMSigner) GetZetaConnectorAddress() ethcommon.Address { + return s.ZetaConnectorAddress +} + +func (s *EVMSigner) GetERC20CustodyAddress() ethcommon.Address { + return s.ERC20CustodyAddress +} + +// ---------------------------------------------------------------------------- +// BTCSigner +// ---------------------------------------------------------------------------- +var _ interfaces.ChainSigner = (*BTCSigner)(nil) + +// BTCSigner is a mock of bitcoin chain signer for testing +type BTCSigner struct { +} + +func NewBTCSigner() *BTCSigner { + return &BTCSigner{} +} + +func (s *BTCSigner) TryProcessOutTx( + _ *crosschaintypes.CrossChainTx, + _ *outtxprocessor.Processor, + _ string, + _ interfaces.ChainClient, + _ interfaces.ZetaCoreBridger, + _ uint64, +) { +} + +func (s *BTCSigner) SetZetaConnectorAddress(_ ethcommon.Address) { +} + +func (s *BTCSigner) SetERC20CustodyAddress(_ ethcommon.Address) { +} + +func (s *BTCSigner) GetZetaConnectorAddress() ethcommon.Address { + return ethcommon.Address{} +} + +func (s *BTCSigner) GetERC20CustodyAddress() ethcommon.Address { + return ethcommon.Address{} +} diff --git a/zetaclient/types/ethish_test.go b/zetaclient/types/ethish_test.go new file mode 100644 index 0000000000..d932b46501 --- /dev/null +++ b/zetaclient/types/ethish_test.go @@ -0,0 +1 @@ +package types_test diff --git a/zetaclient/zetabridge/constant.go b/zetaclient/zetabridge/constant.go new file mode 100644 index 0000000000..8a9f527ae5 --- /dev/null +++ b/zetaclient/zetabridge/constant.go @@ -0,0 +1,43 @@ +package zetabridge + +const ( + // DefaultGasLimit is the default gas limit used for broadcasting txs + DefaultGasLimit = 200_000 + + // PostGasPriceGasLimit is the gas limit for voting new gas price + PostGasPriceGasLimit = 1_500_000 + + // AddTxHashToOutTxTrackerGasLimit is the gas limit for adding tx hash to out tx tracker + AddTxHashToOutTxTrackerGasLimit = 200_000 + + // PostBlameDataGasLimit is the gas limit for voting on blames + PostBlameDataGasLimit = 200_000 + + // DefaultRetryCount is the number of retries for broadcasting a tx + DefaultRetryCount = 5 + + // ExtendedRetryCount is an extended number of retries for broadcasting a tx, used in keygen operations + ExtendedRetryCount = 15 + + // DefaultRetryInterval is the interval between retries in seconds + DefaultRetryInterval = 5 + + // MonitorVoteInboundTxResultInterval is the interval between retries for monitoring tx result in seconds + MonitorVoteInboundTxResultInterval = 5 + + // MonitorVoteInboundTxResultRetryCount is the number of retries to fetch monitoring tx result + MonitorVoteInboundTxResultRetryCount = 20 + + // PostVoteOutboundGasLimit is the gas limit for voting on observed outbound tx + PostVoteOutboundGasLimit = 400_000 + + // PostVoteOutboundRevertGasLimit is the gas limit for voting on observed outbound tx for revert (when outbound fails) + // The value needs to be higher because reverting implies interacting with the EVM to perform swaps for the gas token + PostVoteOutboundRevertGasLimit = 1_500_000 + + // MonitorVoteOutboundTxResultInterval is the interval between retries for monitoring tx result in seconds + MonitorVoteOutboundTxResultInterval = 5 + + // MonitorVoteOutboundTxResultRetryCount is the number of retries to fetch monitoring tx result + MonitorVoteOutboundTxResultRetryCount = 20 +) diff --git a/zetaclient/zetabridge/tx.go b/zetaclient/zetabridge/tx.go index a142ad44f0..f1b4d4807a 100644 --- a/zetaclient/zetabridge/tx.go +++ b/zetaclient/zetabridge/tx.go @@ -9,6 +9,7 @@ import ( "cosmossdk.io/math" appcontext "github.com/zeta-chain/zetacore/zetaclient/app_context" authz2 "github.com/zeta-chain/zetacore/zetaclient/authz" + clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/authz" @@ -19,47 +20,15 @@ import ( observerTypes "github.com/zeta-chain/zetacore/x/observer/types" ) -const ( - // DefaultGasLimit is the default gas limit used for broadcasting txs - DefaultGasLimit = 200_000 - - // PostGasPriceGasLimit is the gas limit for voting new gas price - PostGasPriceGasLimit = 1_500_000 - - // AddTxHashToOutTxTrackerGasLimit is the gas limit for adding tx hash to out tx tracker - AddTxHashToOutTxTrackerGasLimit = 200_000 - - // PostBlameDataGasLimit is the gas limit for voting on blames - PostBlameDataGasLimit = 200_000 - - // DefaultRetryCount is the number of retries for broadcasting a tx - DefaultRetryCount = 5 - - // ExtendedRetryCount is an extended number of retries for broadcasting a tx, used in keygen operations - ExtendedRetryCount = 15 - - // DefaultRetryInterval is the interval between retries in seconds - DefaultRetryInterval = 5 - - // MonitorVoteInboundTxResultInterval is the interval between retries for monitoring tx result in seconds - MonitorVoteInboundTxResultInterval = 5 - - // MonitorVoteInboundTxResultRetryCount is the number of retries to fetch monitoring tx result - MonitorVoteInboundTxResultRetryCount = 20 - - // PostVoteOutboundGasLimit is the gas limit for voting on observed outbound tx - PostVoteOutboundGasLimit = 400_000 - - // PostVoteOutboundRevertGasLimit is the gas limit for voting on observed outbound tx for revert (when outbound fails) - // The value needs to be higher because reverting implies interacting with the EVM to perform swaps for the gas token - PostVoteOutboundRevertGasLimit = 1_500_000 - - // MonitorVoteOutboundTxResultInterval is the interval between retries for monitoring tx result in seconds - MonitorVoteOutboundTxResultInterval = 5 - - // MonitorVoteOutboundTxResultRetryCount is the number of retries to fetch monitoring tx result - MonitorVoteOutboundTxResultRetryCount = 20 -) +// GasPriceMultiplier returns the gas price multiplier for the given chain +func GasPriceMultiplier(chainID int64) (float64, error) { + if common.IsEVMChain(chainID) { + return clientcommon.EVMOuttxGasPriceMultiplier, nil + } else if common.IsBitcoinChain(chainID) { + return clientcommon.BTCOuttxGasPriceMultiplier, nil + } + return 0, fmt.Errorf("cannot get gas price multiplier for unknown chain %d", chainID) +} func (b *ZetaCoreBridge) WrapMessageWithAuthz(msg sdk.Msg) (sdk.Msg, authz2.Signer, error) { msgURL := sdk.MsgTypeURL(msg) @@ -75,8 +44,13 @@ func (b *ZetaCoreBridge) WrapMessageWithAuthz(msg sdk.Msg) (sdk.Msg, authz2.Sign } func (b *ZetaCoreBridge) PostGasPrice(chain common.Chain, gasPrice uint64, supply string, blockNum uint64) (string, error) { - // double the gas price to avoid gas price spike - gasPrice = gasPrice * common.DefaultGasPriceMultiplier + // apply gas price multiplier for the chain + multiplier, err := GasPriceMultiplier(chain.ChainId) + if err != nil { + return "", err + } + // #nosec G701 always in range + gasPrice = uint64(float64(gasPrice) * multiplier) signerAddress := b.keys.GetOperatorAddress().String() msg := types.NewMsgGasPriceVoter(signerAddress, chain.ChainId, gasPrice, supply, blockNum) diff --git a/zetaclient/zetabridge/tx_test.go b/zetaclient/zetabridge/tx_test.go new file mode 100644 index 0000000000..50cd82a929 --- /dev/null +++ b/zetaclient/zetabridge/tx_test.go @@ -0,0 +1,82 @@ +package zetabridge + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_GasPriceMultiplier(t *testing.T) { + tt := []struct { + name string + chainID int64 + multiplier float64 + fail bool + }{ + { + name: "get Ethereum multiplier", + chainID: 1, + multiplier: 1.2, + fail: false, + }, + { + name: "get Goerli multiplier", + chainID: 5, + multiplier: 1.2, + fail: false, + }, + { + name: "get BSC multiplier", + chainID: 56, + multiplier: 1.2, + fail: false, + }, + { + name: "get BSC Testnet multiplier", + chainID: 97, + multiplier: 1.2, + fail: false, + }, + { + name: "get Polygon multiplier", + chainID: 137, + multiplier: 1.2, + fail: false, + }, + { + name: "get Mumbai Testnet multiplier", + chainID: 80001, + multiplier: 1.2, + fail: false, + }, + { + name: "get Bitcoin multiplier", + chainID: 8332, + multiplier: 2.0, + fail: false, + }, + { + name: "get Bitcoin Testnet multiplier", + chainID: 18332, + multiplier: 2.0, + fail: false, + }, + { + name: "get unknown chain gas price multiplier", + chainID: 1234, + multiplier: 1.0, + fail: true, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + multiplier, err := GasPriceMultiplier(tc.chainID) + if tc.fail { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tc.multiplier, multiplier) + }) + } +} diff --git a/zetaclient/zetabridge/zetacore_bridge.go b/zetaclient/zetabridge/zetacore_bridge.go index fbe5187088..babdc505bf 100644 --- a/zetaclient/zetabridge/zetacore_bridge.go +++ b/zetaclient/zetabridge/zetacore_bridge.go @@ -221,7 +221,7 @@ func (b *ZetaCoreBridge) UpdateZetaCoreContext(coreContext *corecontext.ZetaCore // check and update chain params for each chain for _, chainParam := range chainParams { - err := config.ValidateChainParams(chainParam) + err := observertypes.ValidateChainParams(chainParam) if err != nil { b.logger.Warn().Err(err).Msgf("Invalid chain params for chain %d", chainParam.ChainId) continue diff --git a/zetaclient/zetacore_observer.go b/zetaclient/zetacore_observer.go index 429c209287..1307a2bad0 100644 --- a/zetaclient/zetacore_observer.go +++ b/zetaclient/zetacore_observer.go @@ -5,8 +5,10 @@ import ( "math" "time" + ethcommon "github.com/ethereum/go-ethereum/common" appcontext "github.com/zeta-chain/zetacore/zetaclient/app_context" "github.com/zeta-chain/zetacore/zetaclient/bitcoin" + corecontext "github.com/zeta-chain/zetacore/zetaclient/core_context" "github.com/zeta-chain/zetacore/zetaclient/interfaces" "github.com/zeta-chain/zetacore/zetaclient/outtxprocessor" @@ -32,8 +34,8 @@ type ZetaCoreLog struct { // CoreObserver wraps the zetabridge bridge and adds the client and signer maps to it . This is the high level object used for CCTX interactions type CoreObserver struct { bridge interfaces.ZetaCoreBridger - signerMap map[common.Chain]interfaces.ChainSigner - clientMap map[common.Chain]interfaces.ChainClient + signerMap map[int64]interfaces.ChainSigner + clientMap map[int64]interfaces.ChainClient logger ZetaCoreLog ts *metrics.TelemetryServer stop chan struct{} @@ -43,8 +45,8 @@ type CoreObserver struct { // NewCoreObserver creates a new CoreObserver func NewCoreObserver( bridge interfaces.ZetaCoreBridger, - signerMap map[common.Chain]interfaces.ChainSigner, - clientMap map[common.Chain]interfaces.ChainClient, + signerMap map[int64]interfaces.ChainSigner, + clientMap map[int64]interfaces.ChainClient, logger zerolog.Logger, ts *metrics.TelemetryServer, ) *CoreObserver { @@ -135,23 +137,29 @@ func (co *CoreObserver) startCctxScheduler(appContext *appcontext.AppContext) { metrics.HotKeyBurnRate.Set(float64(co.ts.HotKeyBurnRate.GetBurnRate().Int64())) // schedule keysign for pending cctxs on each chain - supportedChains := appContext.ZetaCoreContext().GetEnabledChains() + coreContext := appContext.ZetaCoreContext() + supportedChains := coreContext.GetEnabledChains() for _, c := range supportedChains { if c.ChainId == co.bridge.ZetaChain().ChainId { continue } - signer := co.signerMap[c] - - cctxList, totalPending, err := co.bridge.ListPendingCctx(c.ChainId) + // update chain parameters for signer and chain client + signer, err := co.GetUpdatedSigner(coreContext, c.ChainId) if err != nil { - co.logger.ZetaChainWatcher.Error().Err(err).Msgf("startCctxScheduler: ListPendingCctx failed for chain %d", c.ChainId) + co.logger.ZetaChainWatcher.Error().Err(err).Msgf("startCctxScheduler: getUpdatedSigner failed for chain %d", c.ChainId) continue } - ob, err := co.getUpdatedChainOb(appContext, c.ChainId) + ob, err := co.GetUpdatedChainClient(coreContext, c.ChainId) if err != nil { co.logger.ZetaChainWatcher.Error().Err(err).Msgf("startCctxScheduler: getTargetChainOb failed for chain %d", c.ChainId) continue } + + cctxList, totalPending, err := co.bridge.ListPendingCctx(c.ChainId) + if err != nil { + co.logger.ZetaChainWatcher.Error().Err(err).Msgf("startCctxScheduler: ListPendingCctx failed for chain %d", c.ChainId) + continue + } // Set Pending transactions prometheus gauge metrics.PendingTxsPerChain.WithLabelValues(c.ChainName.String()).Set(float64(totalPending)) @@ -317,45 +325,57 @@ func (co *CoreObserver) scheduleCctxBTC( } } -func (co *CoreObserver) getUpdatedChainOb(appContext *appcontext.AppContext, chainID int64) (interfaces.ChainClient, error) { - chainOb, err := co.getTargetChainOb(chainID) - if err != nil { - return nil, err +// GetUpdatedSigner returns signer with updated chain parameters +func (co *CoreObserver) GetUpdatedSigner(coreContext *corecontext.ZetaCoreContext, chainID int64) (interfaces.ChainSigner, error) { + signer, found := co.signerMap[chainID] + if !found { + return nil, fmt.Errorf("signer not found for chainID %d", chainID) + } + // update EVM signer parameters only. BTC signer doesn't use chain parameters for now. + if common.IsEVMChain(chainID) { + evmParams, found := coreContext.GetEVMChainParams(chainID) + if found { + // update zeta connector and ERC20 custody addresses + zetaConnectorAddress := ethcommon.HexToAddress(evmParams.GetConnectorContractAddress()) + erc20CustodyAddress := ethcommon.HexToAddress(evmParams.GetErc20CustodyContractAddress()) + if zetaConnectorAddress != signer.GetZetaConnectorAddress() { + signer.SetZetaConnectorAddress(zetaConnectorAddress) + co.logger.ZetaChainWatcher.Info().Msgf( + "updated zeta connector address for chainID %d, new address: %s", chainID, zetaConnectorAddress) + } + if erc20CustodyAddress != signer.GetERC20CustodyAddress() { + signer.SetERC20CustodyAddress(erc20CustodyAddress) + co.logger.ZetaChainWatcher.Info().Msgf( + "updated ERC20 custody address for chainID %d, new address: %s", chainID, erc20CustodyAddress) + } + } + } + return signer, nil +} + +// GetUpdatedChainClient returns chain client object with updated chain parameters +func (co *CoreObserver) GetUpdatedChainClient(coreContext *corecontext.ZetaCoreContext, chainID int64) (interfaces.ChainClient, error) { + chainOb, found := co.clientMap[chainID] + if !found { + return nil, fmt.Errorf("chain client not found for chainID %d", chainID) } - // update chain client core parameters + // update chain client chain parameters curParams := chainOb.GetChainParams() if common.IsEVMChain(chainID) { - evmParams, found := appContext.ZetaCoreContext().GetEVMChainParams(chainID) + evmParams, found := coreContext.GetEVMChainParams(chainID) if found && !observertypes.ChainParamsEqual(curParams, *evmParams) { chainOb.SetChainParams(*evmParams) co.logger.ZetaChainWatcher.Info().Msgf( - "updated chain params for chainID %d, new params: %v", - chainID, - *evmParams, - ) + "updated chain params for chainID %d, new params: %v", chainID, *evmParams) } } else if common.IsBitcoinChain(chainID) { - _, btcParams, found := appContext.ZetaCoreContext().GetBTCChainParams() + _, btcParams, found := coreContext.GetBTCChainParams() if found && !observertypes.ChainParamsEqual(curParams, *btcParams) { chainOb.SetChainParams(*btcParams) co.logger.ZetaChainWatcher.Info().Msgf( - "updated chain params for Bitcoin, new params: %v", - *btcParams, - ) + "updated chain params for Bitcoin, new params: %v", *btcParams) } } return chainOb, nil } - -func (co *CoreObserver) getTargetChainOb(chainID int64) (interfaces.ChainClient, error) { - c := common.GetChainFromChainID(chainID) - if c == nil { - return nil, fmt.Errorf("chain not found for chainID %d", chainID) - } - chainOb, found := co.clientMap[*c] - if !found { - return nil, fmt.Errorf("chain client not found for chainID %d", chainID) - } - return chainOb, nil -} diff --git a/zetaclient/zetacore_observer_test.go b/zetaclient/zetacore_observer_test.go index a2340ea4a4..3dd088a4b2 100644 --- a/zetaclient/zetacore_observer_test.go +++ b/zetaclient/zetacore_observer_test.go @@ -1,147 +1,186 @@ -//go:build metacore_observer -// +build metacore_observer - package zetaclient import ( - "os" - "path/filepath" - "time" - - "github.com/zeta-chain/zetacore/zetaclient/interfaces" - "github.com/zeta-chain/zetacore/zetaclient/keys" - "github.com/zeta-chain/zetacore/zetaclient/zetabridge" - - "github.com/zeta-chain/zetacore/zetaclient/evm" + "testing" + sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/rs/zerolog/log" + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/common" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/config" - "github.com/zeta-chain/zetacore/zetaclient/types" - . "gopkg.in/check.v1" + corecontext "github.com/zeta-chain/zetacore/zetaclient/core_context" + "github.com/zeta-chain/zetacore/zetaclient/interfaces" + "github.com/zeta-chain/zetacore/zetaclient/testutils" + "github.com/zeta-chain/zetacore/zetaclient/testutils/stub" ) -type COSuite struct { - bridge1 *zetabridge.ZetaCoreBridge - bridge2 *zetabridge.ZetaCoreBridge - signer *evm.Signer - coreObserver *CoreObserver +func MockCoreObserver(t *testing.T, evmChain, btcChain common.Chain, evmChainParams, btcChainParams *observertypes.ChainParams) *CoreObserver { + // create mock signers and clients + evmSigner := stub.NewEVMSigner( + evmChain, + ethcommon.HexToAddress(evmChainParams.ConnectorContractAddress), + ethcommon.HexToAddress(evmChainParams.Erc20CustodyContractAddress), + ) + btcSigner := stub.NewBTCSigner() + evmClient := stub.NewEVMClient(evmChainParams) + btcClient := stub.NewBTCClient(btcChainParams) + + // create core observer + observer := &CoreObserver{ + signerMap: map[int64]interfaces.ChainSigner{ + evmChain.ChainId: evmSigner, + btcChain.ChainId: btcSigner, + }, + clientMap: map[int64]interfaces.ChainClient{ + evmChain.ChainId: evmClient, + btcChain.ChainId: btcClient, + }, + } + return observer } -var _ = Suite(&COSuite{}) - -const ( - TEST_SENDER = "0x566bF3b1993FFd4BA134c107A63bb2aebAcCdbA0" - TEST_RECEIVER = "0x566bF3b1993FFd4BA134c107A63bb2aebAcCdbA0" -) - -func (s *COSuite) SetUpTest(c *C) { - types.SetupConfigForTest() // setup meta-prefix - - // setup 2 metabridges - homeDir, err := os.UserHomeDir() - if err != nil { - c.Logf("UserHomeDir error") - c.Fail() +func CreateCoreContext(evmChain, btcChain common.Chain, evmChainParams, btcChainParams *observertypes.ChainParams) *corecontext.ZetaCoreContext { + // new config + cfg := config.NewConfig() + cfg.EVMChainConfigs[evmChain.ChainId] = config.EVMConfig{ + Chain: evmChain, } - c.Logf("user home dir: %s", homeDir) - chainHomeFoler := filepath.Join(homeDir, ".zetacored") - c.Logf("chain home dir: %s", chainHomeFoler) - - // first signer & zetaClient - // alice is the default user created by Starport chain serve - { - signerName := "alice" - signerPass := "password" - kb, _, err := keys.GetKeyringKeybase(chainHomeFoler, signerName, signerPass) - if err != nil { - log.Fatal().Err(err).Msg("fail to get keyring keybase") - } - - k := keys.NewKeysWithKeybase(kb, signerName, signerPass) - - chainIP := os.Getenv("CHAIN_IP") - if chainIP == "" { - chainIP = "127.0.0.1" - } - bridge, err := zetabridge.NewZetaCoreBridge(k, chainIP, "alice") - if err != nil { - c.Fail() - } - s.bridge1 = bridge + cfg.BitcoinConfig = config.BTCConfig{ + RPCHost: "localhost", } + // new core context + coreContext := corecontext.NewZetaCoreContext(cfg) + evmChainParamsMap := make(map[int64]*observertypes.ChainParams) + evmChainParamsMap[evmChain.ChainId] = evmChainParams + + // feed chain params + coreContext.Update( + &observertypes.Keygen{}, + []common.Chain{evmChain, btcChain}, + evmChainParamsMap, + btcChainParams, + "", + true, + zerolog.Logger{}, + ) + return coreContext +} - // second signer & zetaClient - // alice is the default user created by Starport chain serve - { - signerName := "bob" - signerPass := "password" - kb, _, err := keys.GetKeyringKeybase(chainHomeFoler, signerName, signerPass) - if err != nil { - log.Fatal().Err(err).Msg("fail to get keyring keybase") - } - - k := keys.NewKeysWithKeybase(kb, signerName, signerPass) - - chainIP := os.Getenv("CHAIN_IP") - if chainIP == "" { - chainIP = "127.0.0.1" - } - bridge, err := zetabridge.NewZetaCoreBridge(k, chainIP, "bob") - if err != nil { - c.Fail() - } - s.bridge2 = bridge +func Test_GetUpdatedSigner(t *testing.T) { + // initial parameters for core observer creation + evmChain := common.EthChain() + btcChain := common.BtcMainnetChain() + evmChainParams := &observertypes.ChainParams{ + ChainId: evmChain.ChainId, + ConnectorContractAddress: testutils.ConnectorAddresses[evmChain.ChainId].Hex(), + Erc20CustodyContractAddress: testutils.CustodyAddresses[evmChain.ChainId].Hex(), } + btcChainParams := &observertypes.ChainParams{} - // setup mock TSS signers: - // The following PrivKey has address 0xE80B6467863EbF8865092544f441da8fD3cF6074 - privateKey, err := crypto.HexToECDSA(config.TssTestPrivkey) - c.Assert(err, IsNil) - tss := interfaces.TestSigner{ - PrivKey: privateKey, + // new chain params in core context + evmChainParamsNew := &observertypes.ChainParams{ + ChainId: evmChain.ChainId, + ConnectorContractAddress: testutils.OtherAddress1, + Erc20CustodyContractAddress: testutils.OtherAddress2, } - metaContractAddress := ethcommon.HexToAddress(config.ETH_MPI_ADDRESS) - signer, err := evm.NewEVMSigner(common.Chain("ETH"), config.GOERLI_RPC_ENDPOINT, tss.EVMAddress(), tss, config.META_TEST_GOERLI_ABI, metaContractAddress) - c.Assert(err, IsNil) - c.Logf("TSS EVMAddress %s", tss.EVMAddress().Hex()) - c.Logf("ETH MPI EVMAddress: %s", config.ETH_MPI_ADDRESS) - - s.signer = signer - // setup zetabridge observer - co := &CoreObserver{ - bridge: s.bridge1, - signer: signer, - } - s.coreObserver = co - s.coreObserver.MonitorCore() + t.Run("signer should not be found", func(t *testing.T) { + observer := MockCoreObserver(t, evmChain, btcChain, evmChainParams, btcChainParams) + coreContext := CreateCoreContext(evmChain, btcChain, evmChainParamsNew, btcChainParams) + // BSC signer should not be found + _, err := observer.GetUpdatedSigner(coreContext, common.BscMainnetChain().ChainId) + require.ErrorContains(t, err, "signer not found") + }) + t.Run("should be able to update connector and erc20 custody address", func(t *testing.T) { + observer := MockCoreObserver(t, evmChain, btcChain, evmChainParams, btcChainParams) + coreContext := CreateCoreContext(evmChain, btcChain, evmChainParamsNew, btcChainParams) + // update signer with new connector and erc20 custody address + signer, err := observer.GetUpdatedSigner(coreContext, evmChain.ChainId) + require.NoError(t, err) + require.Equal(t, testutils.OtherAddress1, signer.GetZetaConnectorAddress().Hex()) + require.Equal(t, testutils.OtherAddress2, signer.GetERC20CustodyAddress().Hex()) + }) } -func (s *COSuite) TestSendFlow(c *C) { - b1 := s.bridge1 - b2 := s.bridge2 - metaHash, err := b1.PostVoteInbound(TEST_SENDER, "Ethereum", TEST_SENDER, TEST_RECEIVER, "BSC", "1337", "0", "treat or trick", - "0xtxhash", 123123, "0xtoken") - c.Assert(err, IsNil) - c.Logf("PostVoteInbound metaHash %s", metaHash) - - timer1 := time.NewTimer(2 * time.Second) - <-timer1.C - - metaHash, err = b2.PostVoteInbound(TEST_SENDER, "Ethereum", TEST_SENDER, TEST_RECEIVER, "BSC", "1337", "0", "treat or trick", - "0xtxhash", 123123, "0xtoken") - c.Assert(err, IsNil) - c.Logf("Second PostVoteInbound metaHash %s", metaHash) +func Test_GetUpdatedChainClient(t *testing.T) { + // initial parameters for core observer creation + evmChain := common.EthChain() + btcChain := common.BtcMainnetChain() + evmChainParams := &observertypes.ChainParams{ + ChainId: evmChain.ChainId, + ConnectorContractAddress: testutils.ConnectorAddresses[evmChain.ChainId].Hex(), + Erc20CustodyContractAddress: testutils.CustodyAddresses[evmChain.ChainId].Hex(), + } + btcChainParams := &observertypes.ChainParams{ + ChainId: btcChain.ChainId, + } - timer2 := time.NewTimer(2 * time.Second) - <-timer2.C + // new chain params in core context + evmChainParamsNew := &observertypes.ChainParams{ + ChainId: evmChain.ChainId, + ConfirmationCount: 10, + GasPriceTicker: 11, + InTxTicker: 12, + OutTxTicker: 13, + WatchUtxoTicker: 14, + ZetaTokenContractAddress: testutils.OtherAddress1, + ConnectorContractAddress: testutils.OtherAddress2, + Erc20CustodyContractAddress: testutils.OtherAddress3, + OutboundTxScheduleInterval: 15, + OutboundTxScheduleLookahead: 16, + BallotThreshold: sdk.OneDec(), + MinObserverDelegation: sdk.OneDec(), + IsSupported: true, + } + btcChainParamsNew := &observertypes.ChainParams{ + ChainId: btcChain.ChainId, + ConfirmationCount: 3, + GasPriceTicker: 300, + InTxTicker: 60, + OutTxTicker: 60, + WatchUtxoTicker: 30, + ZetaTokenContractAddress: testutils.OtherAddress1, + ConnectorContractAddress: testutils.OtherAddress2, + Erc20CustodyContractAddress: testutils.OtherAddress3, + OutboundTxScheduleInterval: 60, + OutboundTxScheduleLookahead: 200, + BallotThreshold: sdk.OneDec(), + MinObserverDelegation: sdk.OneDec(), + IsSupported: true, + } - time.Sleep(15 * time.Second) - //ch := make(chan os.Signal, 1) - //signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) - //<-ch - //c.Logf("stop signal received") + t.Run("evm chain client should not be found", func(t *testing.T) { + observer := MockCoreObserver(t, evmChain, btcChain, evmChainParams, btcChainParams) + coreContext := CreateCoreContext(evmChain, btcChain, evmChainParamsNew, btcChainParams) + // BSC chain client should not be found + _, err := observer.GetUpdatedChainClient(coreContext, common.BscMainnetChain().ChainId) + require.ErrorContains(t, err, "chain client not found") + }) + t.Run("chain params in evm chain client should be updated successfully", func(t *testing.T) { + observer := MockCoreObserver(t, evmChain, btcChain, evmChainParams, btcChainParams) + coreContext := CreateCoreContext(evmChain, btcChain, evmChainParamsNew, btcChainParams) + // update evm chain client with new chain params + chainOb, err := observer.GetUpdatedChainClient(coreContext, evmChain.ChainId) + require.NoError(t, err) + require.NotNil(t, chainOb) + require.True(t, observertypes.ChainParamsEqual(*evmChainParamsNew, chainOb.GetChainParams())) + }) + t.Run("btc chain client should not be found", func(t *testing.T) { + observer := MockCoreObserver(t, evmChain, btcChain, evmChainParams, btcChainParams) + coreContext := CreateCoreContext(btcChain, btcChain, evmChainParams, btcChainParamsNew) + // BTC testnet chain client should not be found + _, err := observer.GetUpdatedChainClient(coreContext, common.BtcTestNetChain().ChainId) + require.ErrorContains(t, err, "chain client not found") + }) + t.Run("chain params in btc chain client should be updated successfully", func(t *testing.T) { + observer := MockCoreObserver(t, evmChain, btcChain, evmChainParams, btcChainParams) + coreContext := CreateCoreContext(btcChain, btcChain, evmChainParams, btcChainParamsNew) + // update btc chain client with new chain params + chainOb, err := observer.GetUpdatedChainClient(coreContext, btcChain.ChainId) + require.NoError(t, err) + require.NotNil(t, chainOb) + require.True(t, observertypes.ChainParamsEqual(*btcChainParamsNew, chainOb.GetChainParams())) + }) }