From b4251e176c326bdaeba9212d082a9bc5e445869b Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Fri, 2 Aug 2024 15:41:03 +0200 Subject: [PATCH] refactor(zetaclient)!: improve AppContext (#2568) * Implement chain registry * Rewrite test-cases for AppContext * Drop `supplychecker` * Refactor app ctx Update worker * Refactor orchestrator * Refactor observer&signer; DROP postBlockHeaders * Fix test cases [1] * Update changelog * Allow Zeta Chain in appContext; address PR comments [1] * Fix app context update * Check for `chain.IsZeta()` * Add AppContext.FilterChains * Fix test cases [2] * Fix test cases [3] * Address PR comments [1] * Address PR comments [2] * Add tests for `slices` * Fix e2e tests [1] * Fix e2e tests [2] * Resolve conflicts, converge codebase between PRs * Add lodash; remove slices pkg * Address PR comments * Minor logging fix * Address PR comments --- changelog.md | 1 + cmd/zetaclientd/debug.go | 93 ++- cmd/zetaclientd/start.go | 33 +- cmd/zetae2e/local/admin.go | 2 +- cmd/zetae2e/local/bitcoin.go | 5 +- cmd/zetae2e/local/erc20.go | 2 +- cmd/zetae2e/local/ethereum.go | 3 +- cmd/zetae2e/local/local.go | 7 +- cmd/zetae2e/local/performance.go | 2 +- cmd/zetae2e/local/zeta.go | 2 +- cmd/zetae2e/local/zevm_mp.go | 2 +- cmd/zetae2e/stress.go | 2 +- e2e/e2etests/test_eth_deposit.go | 2 +- e2e/e2etests/test_migrate_chain_support.go | 2 +- e2e/e2etests/test_stress_eth_deposit.go | 2 +- e2e/runner/bitcoin.go | 97 +-- e2e/runner/evm.go | 76 +- go.mod | 17 +- go.sum | 34 +- x/observer/types/chain_params.go | 17 +- zetaclient/chains/bitcoin/observer/inbound.go | 21 +- .../chains/bitcoin/observer/observer.go | 40 - .../chains/bitcoin/observer/outbound.go | 2 +- zetaclient/chains/evm/observer/inbound.go | 79 +- .../chains/evm/observer/inbound_test.go | 80 +- zetaclient/chains/evm/observer/observer.go | 42 - .../chains/evm/observer/observer_gas_test.go | 4 +- .../chains/evm/observer/observer_test.go | 47 +- zetaclient/chains/evm/observer/outbound.go | 2 +- .../chains/evm/observer/outbound_test.go | 13 +- zetaclient/chains/evm/signer/outbound_data.go | 27 +- .../chains/evm/signer/outbound_data_test.go | 38 +- zetaclient/chains/evm/signer/signer.go | 61 +- zetaclient/chains/evm/signer/signer_test.go | 42 +- zetaclient/chains/interfaces/interfaces.go | 2 +- zetaclient/chains/solana/observer/inbound.go | 2 +- .../chains/solana/observer/inbound_tracker.go | 2 +- zetaclient/chains/solana/observer/outbound.go | 2 +- zetaclient/config/types.go | 4 + zetaclient/context/app.go | 385 ++++------ zetaclient/context/app_test.go | 726 +++++------------- zetaclient/context/chain.go | 177 +++++ zetaclient/context/chain_test.go | 87 +++ zetaclient/orchestrator/bootstap_test.go | 130 +--- zetaclient/orchestrator/bootstrap.go | 498 +++++------- zetaclient/orchestrator/orchestrator.go | 176 +++-- zetaclient/orchestrator/orchestrator_test.go | 436 ++++++----- zetaclient/supplychecker/logger.go | 31 - zetaclient/supplychecker/validate.go | 34 - .../supplychecker/zeta_supply_checker.go | 280 ------- .../supplychecker/zeta_supply_checker_test.go | 61 -- zetaclient/testutils/mocks/chain_params.go | 25 +- zetaclient/testutils/mocks/zetacore_client.go | 16 +- zetaclient/zetacore/client.go | 106 ++- zetaclient/zetacore/client_query_observer.go | 12 +- zetaclient/zetacore/client_query_test.go | 2 +- zetaclient/zetacore/client_worker.go | 2 +- zetaclient/zetacore/tx_test.go | 24 +- 58 files changed, 1634 insertions(+), 2485 deletions(-) create mode 100644 zetaclient/context/chain.go create mode 100644 zetaclient/context/chain_test.go delete mode 100644 zetaclient/supplychecker/logger.go delete mode 100644 zetaclient/supplychecker/validate.go delete mode 100644 zetaclient/supplychecker/zeta_supply_checker.go delete mode 100644 zetaclient/supplychecker/zeta_supply_checker_test.go diff --git a/changelog.md b/changelog.md index c31c3fe3f4..822cefe360 100644 --- a/changelog.md +++ b/changelog.md @@ -80,6 +80,7 @@ * [2542](https://github.com/zeta-chain/node/pull/2542) - adjust permissions to be more restrictive * [2572](https://github.com/zeta-chain/node/pull/2572) - turn off IBC modules * [2556](https://github.com/zeta-chain/node/pull/2556) - refactor migrator length check to use consensus type +* [2568](https://github.com/zeta-chain/node/pull/2568) - improve AppContext by converging chains, chainParams, enabledChains, and additionalChains into a single zctx.Chain ### Tests diff --git a/cmd/zetaclientd/debug.go b/cmd/zetaclientd/debug.go index d28f5cb898..6fc46f71f9 100644 --- a/cmd/zetaclientd/debug.go +++ b/cmd/zetaclientd/debug.go @@ -3,9 +3,11 @@ package main import ( "context" "fmt" + "os" "strconv" "strings" + "cosmossdk.io/errors" "github.com/btcsuite/btcd/rpcclient" sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" @@ -14,10 +16,8 @@ import ( "github.com/rs/zerolog" "github.com/spf13/cobra" - "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/testutil/sample" - observertypes "github.com/zeta-chain/zetacore/x/observer/types" btcobserver "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/observer" evmobserver "github.com/zeta-chain/zetacore/zetaclient/chains/evm/observer" "github.com/zeta-chain/zetacore/zetaclient/config" @@ -35,11 +35,14 @@ type debugArguments struct { } func init() { - RootCmd.AddCommand(DebugCmd()) - DebugCmd().Flags(). - StringVar(&debugArgs.zetaCoreHome, "core-home", "/Users/tanmay/.zetacored", "peer address, e.g. /dns/tss1/tcp/6668/ipfs/16Uiu2HAmACG5DtqmQsHtXg4G2sLS65ttv84e7MrL4kapkjfmhxAp") - DebugCmd().Flags().StringVar(&debugArgs.zetaNode, "node", "46.4.15.110", "public ip address") - DebugCmd().Flags().StringVar(&debugArgs.zetaChainID, "chain-id", "athens_7001-1", "pre-params file path") + defaultHomeDir := os.ExpandEnv("$HOME/.zetacored") + + cmd := DebugCmd() + cmd.Flags().StringVar(&debugArgs.zetaCoreHome, "core-home", defaultHomeDir, "zetacore home directory") + cmd.Flags().StringVar(&debugArgs.zetaNode, "node", "46.4.15.110", "public ip address") + cmd.Flags().StringVar(&debugArgs.zetaChainID, "chain-id", "athens_7001-1", "pre-params file path") + + RootCmd.AddCommand(cmd) } func DebugCmd() *cobra.Command { @@ -54,20 +57,16 @@ func debugCmd(_ *cobra.Command, args []string) error { cobra.ExactArgs(2) cfg, err := config.Load(debugArgs.zetaCoreHome) if err != nil { - return err + return errors.Wrap(err, "failed to load config") } - appContext := zctx.New(cfg, zerolog.Nop()) - ctx := zctx.WithAppContext(context.Background(), appContext) + inboundHash := args[0] chainID, err := strconv.ParseInt(args[1], 10, 64) if err != nil { - return err + return errors.Wrap(err, "failed to parse chain id") } - inboundHash := args[0] - var ballotIdentifier string - // create a new zetacore client client, err := zetacore.NewClient( &keys.Keys{OperatorAddress: sdk.MustAccAddressFromBech32(sample.AccAddress())}, @@ -80,21 +79,30 @@ func debugCmd(_ *cobra.Command, args []string) error { if err != nil { return err } - chainParams, err := client.GetChainParams(ctx) - if err != nil { - return err + + appContext := zctx.New(cfg, zerolog.Nop()) + ctx := zctx.WithAppContext(context.Background(), appContext) + + if err := client.UpdateAppContext(ctx, appContext, zerolog.Nop()); err != nil { + return errors.Wrap(err, "failed to update app context") } + + var ballotIdentifier string + tssEthAddress, err := client.GetEVMTSSAddress(ctx) if err != nil { return err } - chain, found := chains.GetChainFromChainID(chainID, appContext.GetAdditionalChains()) - if !found { - return fmt.Errorf("invalid chain id") + + chain, err := appContext.GetChain(chainID) + if err != nil { + return err } + chainProto := chain.RawChain() + // get ballot identifier according to the chain type - if chains.IsEVMChain(chain.ChainId, appContext.GetAdditionalChains()) { + if chain.IsEVM() { evmObserver := evmobserver.Observer{} evmObserver.WithZetacoreClient(client) var ethRPC *ethrpc.EthRPC @@ -109,43 +117,34 @@ func debugCmd(_ *cobra.Command, args []string) error { } evmObserver.WithEvmClient(client) evmObserver.WithEvmJSONRPC(ethRPC) - evmObserver.WithChain(chain) + evmObserver.WithChain(*chainProto) } } hash := ethcommon.HexToHash(inboundHash) tx, isPending, err := evmObserver.TransactionByHash(inboundHash) if err != nil { - return fmt.Errorf("tx not found on chain %s , %d", err.Error(), chain.ChainId) + return fmt.Errorf("tx not found on chain %s, %d", err.Error(), chain.ID()) } + if isPending { return fmt.Errorf("tx is still pending") } + receipt, err := client.TransactionReceipt(context.Background(), hash) if err != nil { - return fmt.Errorf("tx receipt not found on chain %s, %d", err.Error(), chain.ChainId) + return fmt.Errorf("tx receipt not found on chain %s, %d", err.Error(), chain.ID()) } - for _, chainParams := range chainParams { - if chainParams.ChainId == chainID { - evmObserver.SetChainParams(observertypes.ChainParams{ - ChainId: chainID, - ConnectorContractAddress: chainParams.ConnectorContractAddress, - ZetaTokenContractAddress: chainParams.ZetaTokenContractAddress, - Erc20CustodyContractAddress: chainParams.Erc20CustodyContractAddress, - }) - evmChainParams, found := appContext.GetEVMChainParams(chainID) - if !found { - return fmt.Errorf("missing chain params for chain %d", chainID) - } - evmChainParams.ZetaTokenContractAddress = chainParams.ZetaTokenContractAddress - if strings.EqualFold(tx.To, chainParams.ConnectorContractAddress) { - coinType = coin.CoinType_Zeta - } else if strings.EqualFold(tx.To, chainParams.Erc20CustodyContractAddress) { - coinType = coin.CoinType_ERC20 - } else if strings.EqualFold(tx.To, tssEthAddress) { - coinType = coin.CoinType_Gas - } - } + params := chain.Params() + + evmObserver.SetChainParams(*params) + + if strings.EqualFold(tx.To, params.ConnectorContractAddress) { + coinType = coin.CoinType_Zeta + } else if strings.EqualFold(tx.To, params.Erc20CustodyContractAddress) { + coinType = coin.CoinType_ERC20 + } else if strings.EqualFold(tx.To, tssEthAddress) { + coinType = coin.CoinType_Gas } switch coinType { @@ -170,10 +169,10 @@ func debugCmd(_ *cobra.Command, args []string) error { fmt.Println("CoinType not detected") } fmt.Println("CoinType : ", coinType) - } else if chains.IsBitcoinChain(chain.ChainId, appContext.GetAdditionalChains()) { + } else if chain.IsUTXO() { btcObserver := btcobserver.Observer{} btcObserver.WithZetacoreClient(client) - btcObserver.WithChain(chain) + btcObserver.WithChain(*chainProto) connCfg := &rpcclient.ConnConfig{ Host: cfg.BitcoinConfig.RPCHost, User: cfg.BitcoinConfig.RPCUsername, diff --git a/cmd/zetaclientd/start.go b/cmd/zetaclientd/start.go index bbec0723f4..281043cb27 100644 --- a/cmd/zetaclientd/start.go +++ b/cmd/zetaclientd/start.go @@ -21,7 +21,6 @@ import ( "github.com/spf13/cobra" "github.com/zeta-chain/zetacore/pkg/authz" - "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/constant" observerTypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/chains/base" @@ -143,11 +142,11 @@ func start(_ *cobra.Command, _ []string) error { startLogger.Debug().Msgf("CreateAuthzSigner is ready") // Initialize core parameters from zetacore - err = zetacoreClient.UpdateAppContext(ctx, appContext, true, startLogger) - if err != nil { + if err = zetacoreClient.UpdateAppContext(ctx, appContext, startLogger); err != nil { startLogger.Error().Err(err).Msg("Error getting core parameters") return err } + startLogger.Info().Msgf("Config is updated from zetacore %s", maskCfg(cfg)) go zetacoreClient.UpdateAppContextWorker(ctx, appContext) @@ -214,16 +213,21 @@ func start(_ *cobra.Command, _ []string) error { return err } - bitcoinChainID := chains.BitcoinRegtest.ChainId - btcChain, _, btcEnabled := appContext.GetBTCChainAndConfig() - if btcEnabled { - bitcoinChainID = btcChain.ChainId + btcChains := appContext.FilterChains(zctx.Chain.IsUTXO) + switch { + case len(btcChains) == 0: + return errors.New("no BTC chains found") + case len(btcChains) > 1: + // In the future we might support multiple UTXO chains; + // right now we only support BTC. Let's make sure there are no surprises. + return errors.New("more than one BTC chain found") } + tss, err := mc.NewTSS( ctx, zetacoreClient, tssHistoricalList, - bitcoinChainID, + btcChains[0].ID(), hotkeyPass, server, ) @@ -263,11 +267,16 @@ func start(_ *cobra.Command, _ []string) error { tss.CurrentPubkey = currentTss.TssPubkey if tss.EVMAddress() == (ethcommon.Address{}) || tss.BTCAddress() == "" { startLogger.Error().Msg("TSS address is not set in zetacore") + } else { + startLogger.Info(). + Str("tss.eth", tss.EVMAddress().String()). + Str("tss.btc", tss.BTCAddress()). + Str("tss.pub_key", tss.CurrentPubkey). + Msg("Current TSS") } - startLogger.Info(). - Msgf("Current TSS address \n ETH : %s \n BTC : %s \n PubKey : %s ", tss.EVMAddress(), tss.BTCAddress(), tss.CurrentPubkey) - if len(appContext.GetEnabledChains()) == 0 { - startLogger.Error().Msgf("No chains enabled in updated config %s ", cfg.String()) + + if len(appContext.ListChainIDs()) == 0 { + startLogger.Error().Interface("config", cfg).Msgf("No chains in updated config") } isObserver, err := isObserverNode(ctx, zetacoreClient) diff --git a/cmd/zetae2e/local/admin.go b/cmd/zetae2e/local/admin.go index bc76aeeedc..6aaf386496 100644 --- a/cmd/zetae2e/local/admin.go +++ b/cmd/zetae2e/local/admin.go @@ -45,7 +45,7 @@ func adminTestRoutine( // depositing the necessary tokens on ZetaChain txZetaDeposit := adminRunner.DepositZeta() - txEtherDeposit := adminRunner.DepositEther(false) + txEtherDeposit := adminRunner.DepositEther() txERC20Deposit := adminRunner.DepositERC20() adminRunner.WaitForMinedCCTX(txZetaDeposit) adminRunner.WaitForMinedCCTX(txEtherDeposit) diff --git a/cmd/zetae2e/local/bitcoin.go b/cmd/zetae2e/local/bitcoin.go index 05098fd5a9..184277d0cc 100644 --- a/cmd/zetae2e/local/bitcoin.go +++ b/cmd/zetae2e/local/bitcoin.go @@ -17,7 +17,6 @@ func bitcoinTestRoutine( deployerRunner *runner.E2ERunner, verbose bool, initBitcoinNetwork bool, - testHeader bool, testNames ...string, ) func() error { return func() (err error) { @@ -42,14 +41,14 @@ func bitcoinTestRoutine( bitcoinRunner.WaitForTxReceiptOnEvm(txERC20Send) // depositing the necessary tokens on ZetaChain - txEtherDeposit := bitcoinRunner.DepositEther(false) + txEtherDeposit := bitcoinRunner.DepositEther() txERC20Deposit := bitcoinRunner.DepositERC20() bitcoinRunner.WaitForMinedCCTX(txEtherDeposit) bitcoinRunner.WaitForMinedCCTX(txERC20Deposit) bitcoinRunner.SetupBitcoinAccount(initBitcoinNetwork) - bitcoinRunner.DepositBTC(testHeader) + bitcoinRunner.DepositBTC() // run bitcoin test // Note: due to the extensive block generation in Bitcoin localnet, block header test is run first diff --git a/cmd/zetae2e/local/erc20.go b/cmd/zetae2e/local/erc20.go index 8b0d21e564..94c3cbfc29 100644 --- a/cmd/zetae2e/local/erc20.go +++ b/cmd/zetae2e/local/erc20.go @@ -41,7 +41,7 @@ func erc20TestRoutine( erc20Runner.WaitForTxReceiptOnEvm(txERC20Send) // depositing the necessary tokens on ZetaChain - txEtherDeposit := erc20Runner.DepositEther(false) + txEtherDeposit := erc20Runner.DepositEther() txERC20Deposit := erc20Runner.DepositERC20() erc20Runner.WaitForMinedCCTX(txEtherDeposit) erc20Runner.WaitForMinedCCTX(txERC20Deposit) diff --git a/cmd/zetae2e/local/ethereum.go b/cmd/zetae2e/local/ethereum.go index ae2eebc268..84b68608c8 100644 --- a/cmd/zetae2e/local/ethereum.go +++ b/cmd/zetae2e/local/ethereum.go @@ -16,7 +16,6 @@ func ethereumTestRoutine( conf config.Config, deployerRunner *runner.E2ERunner, verbose bool, - testHeader bool, testNames ...string, ) func() error { return func() (err error) { @@ -36,7 +35,7 @@ func ethereumTestRoutine( startTime := time.Now() // depositing the necessary tokens on ZetaChain - txEtherDeposit := ethereumRunner.DepositEther(testHeader) + txEtherDeposit := ethereumRunner.DepositEther() ethereumRunner.WaitForMinedCCTX(txEtherDeposit) // run ethereum test diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 77041b00f4..0eee41b744 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -283,14 +283,11 @@ func localE2ETest(cmd *cobra.Command, _ []string) { ethereumTests = append(ethereumTests, ethereumAdvancedTests...) } - // skip the header proof test if we run light test or skipHeaderProof is enabled - testHeader := !light && !skipHeaderProof - eg.Go(erc20TestRoutine(conf, deployerRunner, verbose, erc20Tests...)) eg.Go(zetaTestRoutine(conf, deployerRunner, verbose, zetaTests...)) eg.Go(zevmMPTestRoutine(conf, deployerRunner, verbose, zevmMPTests...)) - eg.Go(bitcoinTestRoutine(conf, deployerRunner, verbose, !skipBitcoinSetup, testHeader, bitcoinTests...)) - eg.Go(ethereumTestRoutine(conf, deployerRunner, verbose, testHeader, ethereumTests...)) + eg.Go(bitcoinTestRoutine(conf, deployerRunner, verbose, !skipBitcoinSetup, bitcoinTests...)) + eg.Go(ethereumTestRoutine(conf, deployerRunner, verbose, ethereumTests...)) } if testAdmin { diff --git a/cmd/zetae2e/local/performance.go b/cmd/zetae2e/local/performance.go index d6ad06b29e..3bc8ed8377 100644 --- a/cmd/zetae2e/local/performance.go +++ b/cmd/zetae2e/local/performance.go @@ -86,7 +86,7 @@ func ethereumWithdrawPerformanceRoutine( startTime := time.Now() // depositing the necessary tokens on ZetaChain - txEtherDeposit := r.DepositEther(false) + txEtherDeposit := r.DepositEther() r.WaitForMinedCCTX(txEtherDeposit) tests, err := r.GetE2ETestsToRunByName( diff --git a/cmd/zetae2e/local/zeta.go b/cmd/zetae2e/local/zeta.go index 3fdb4f48cc..a0f6d49a09 100644 --- a/cmd/zetae2e/local/zeta.go +++ b/cmd/zetae2e/local/zeta.go @@ -41,7 +41,7 @@ func zetaTestRoutine( // depositing the necessary tokens on ZetaChain txZetaDeposit := zetaRunner.DepositZeta() - txEtherDeposit := zetaRunner.DepositEther(false) + txEtherDeposit := zetaRunner.DepositEther() zetaRunner.WaitForMinedCCTX(txZetaDeposit) zetaRunner.WaitForMinedCCTX(txEtherDeposit) diff --git a/cmd/zetae2e/local/zevm_mp.go b/cmd/zetae2e/local/zevm_mp.go index bc97c45e29..b8d6126ae0 100644 --- a/cmd/zetae2e/local/zevm_mp.go +++ b/cmd/zetae2e/local/zevm_mp.go @@ -41,7 +41,7 @@ func zevmMPTestRoutine( // depositing the necessary tokens on ZetaChain txZetaDeposit := zevmMPRunner.DepositZeta() - txEtherDeposit := zevmMPRunner.DepositEther(false) + txEtherDeposit := zevmMPRunner.DepositEther() zevmMPRunner.WaitForMinedCCTX(txZetaDeposit) zevmMPRunner.WaitForMinedCCTX(txEtherDeposit) diff --git a/cmd/zetae2e/stress.go b/cmd/zetae2e/stress.go index 51e4762635..b1d3a41bfc 100644 --- a/cmd/zetae2e/stress.go +++ b/cmd/zetae2e/stress.go @@ -144,7 +144,7 @@ func StressTest(cmd *cobra.Command, _ []string) { e2eTest.SetZEVMContracts() // deposit on ZetaChain - e2eTest.DepositEther(false) + e2eTest.DepositEther() e2eTest.DepositZeta() case "TESTNET": ethZRC20Addr := must(e2eTest.SystemContract.GasCoinZRC20ByChainId(&bind.CallOpts{}, big.NewInt(5))) diff --git a/e2e/e2etests/test_eth_deposit.go b/e2e/e2etests/test_eth_deposit.go index 03da8f6da4..c5f0701516 100644 --- a/e2e/e2etests/test_eth_deposit.go +++ b/e2e/e2etests/test_eth_deposit.go @@ -16,7 +16,7 @@ func TestEtherDeposit(r *runner.E2ERunner, args []string) { amount, ok := big.NewInt(0).SetString(args[0], 10) require.True(r, ok, "Invalid amount specified for TestEtherDeposit.") - hash := r.DepositEtherWithAmount(false, amount) // in wei + hash := r.DepositEtherWithAmount(amount) // in wei // wait for the cctx to be mined cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, hash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) r.Logger.CCTX(*cctx, "deposit") diff --git a/e2e/e2etests/test_migrate_chain_support.go b/e2e/e2etests/test_migrate_chain_support.go index 0fd2574c85..b8a92cd472 100644 --- a/e2e/e2etests/test_migrate_chain_support.go +++ b/e2e/e2etests/test_migrate_chain_support.go @@ -136,7 +136,7 @@ func TestMigrateChainSupport(r *runner.E2ERunner, _ []string) { // deposit Ethers and ERC20 on ZetaChain etherAmount := big.NewInt(1e18) etherAmount = etherAmount.Mul(etherAmount, big.NewInt(10)) - txEtherDeposit := newRunner.DepositEtherWithAmount(false, etherAmount) + txEtherDeposit := newRunner.DepositEtherWithAmount(etherAmount) newRunner.WaitForMinedCCTX(txEtherDeposit) // perform withdrawals on the new chain diff --git a/e2e/e2etests/test_stress_eth_deposit.go b/e2e/e2etests/test_stress_eth_deposit.go index 9e0208f7e3..04ef846889 100644 --- a/e2e/e2etests/test_stress_eth_deposit.go +++ b/e2e/e2etests/test_stress_eth_deposit.go @@ -31,7 +31,7 @@ func TestStressEtherDeposit(r *runner.E2ERunner, args []string) { // send the deposits for i := 0; i < numDeposits; i++ { i := i - hash := r.DepositEtherWithAmount(false, depositAmount) + hash := r.DepositEtherWithAmount(depositAmount) r.Logger.Print("index %d: starting deposit, tx hash: %s", i, hash.Hex()) eg.Go(func() error { return monitorEtherDeposit(r, hash, i, time.Now()) }) diff --git a/e2e/runner/bitcoin.go b/e2e/runner/bitcoin.go index 868c344766..3a4dad583e 100644 --- a/e2e/runner/bitcoin.go +++ b/e2e/runner/bitcoin.go @@ -2,7 +2,6 @@ package runner import ( "bytes" - "encoding/hex" "fmt" "sort" "time" @@ -19,17 +18,12 @@ import ( "github.com/zeta-chain/zetacore/e2e/utils" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/constant" - "github.com/zeta-chain/zetacore/pkg/proofs" - "github.com/zeta-chain/zetacore/pkg/proofs/bitcoin" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" - lightclienttypes "github.com/zeta-chain/zetacore/x/lightclient/types" zetabitcoin "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin" btcobserver "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/observer" "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/signer" ) -var blockHeaderBTCTimeout = 5 * time.Minute - // ListDeployerUTXOs list the deployer's UTXOs func (r *E2ERunner) ListDeployerUTXOs() ([]btcjson.ListUnspentResult, error) { // query UTXOs from node @@ -113,7 +107,7 @@ func (r *E2ERunner) DepositBTCWithAmount(amount float64) *chainhash.Hash { } // DepositBTC deposits BTC on ZetaChain -func (r *E2ERunner) DepositBTC(testHeader bool) { +func (r *E2ERunner) DepositBTC() { r.Logger.Print("⏳ depositing BTC into ZEVM") startTime := time.Now() defer func() { @@ -143,7 +137,7 @@ func (r *E2ERunner) DepositBTC(testHeader bool) { // send two transactions to the TSS address amount1 := 1.1 + zetabitcoin.DefaultDepositorFee - txHash1, err := r.SendToTSSFromDeployerToDeposit(amount1, utxos[:2]) + _, err = r.SendToTSSFromDeployerToDeposit(amount1, utxos[:2]) require.NoError(r, err) amount2 := 0.05 + zetabitcoin.DefaultDepositorFee @@ -169,12 +163,6 @@ func (r *E2ERunner) DepositBTC(testHeader bool) { balance, err := r.BTCZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress()) require.NoError(r, err) require.Equal(r, 1, balance.Sign(), "balance should be positive") - - // due to the high block throughput in localnet, ZetaClient might catch up slowly with the blocks - // to optimize block header proof test, this test is directly executed here on the first deposit instead of having a separate test - if testHeader { - r.ProveBTCTransaction(txHash1) - } } func (r *E2ERunner) SendToTSSFromDeployerToDeposit(amount float64, inputUTXOs []btcjson.ListUnspentResult) ( @@ -346,84 +334,3 @@ func (r *E2ERunner) MineBlocksIfLocalBitcoin() func() { close(stopChan) } } - -// ProveBTCTransaction proves that a BTC transaction is in a block header and that the block header is in ZetaChain -func (r *E2ERunner) ProveBTCTransaction(txHash *chainhash.Hash) { - // get tx result - btc := r.BtcRPCClient - txResult, err := btc.GetTransaction(txHash) - require.NoError(r, err, "should get tx result") - require.True(r, txResult.Confirmations > 0, "tx should have already confirmed") - - txBytes, err := hex.DecodeString(txResult.Hex) - require.NoError(r, err) - - // get the block with verbose transactions - blockHash, err := chainhash.NewHashFromStr(txResult.BlockHash) - require.NoError(r, err) - - blockVerbose, err := btc.GetBlockVerboseTx(blockHash) - require.NoError(r, err, "should get block verbose tx") - - // get the block header - header, err := btc.GetBlockHeader(blockHash) - require.NoError(r, err, "should get block header") - - // collect all the txs in the block - txns := []*btcutil.Tx{} - for _, res := range blockVerbose.Tx { - txBytes, err := hex.DecodeString(res.Hex) - require.NoError(r, err) - - tx, err := btcutil.NewTxFromBytes(txBytes) - require.NoError(r, err) - - txns = append(txns, tx) - } - - // build merkle proof - mk := bitcoin.NewMerkle(txns) - path, index, err := mk.BuildMerkleProof(int(txResult.BlockIndex)) - require.NoError(r, err, "should build merkle proof") - - // verify merkle proof statically - pass := bitcoin.Prove(*txHash, header.MerkleRoot, path, index) - require.True(r, pass, "should verify merkle proof") - - // wait for block header to show up in ZetaChain - startTime := time.Now() - hash := header.BlockHash() - for { - // timeout - reachedTimeout := time.Since(startTime) > blockHeaderBTCTimeout - require.False(r, reachedTimeout, "timed out waiting for block header to show up in observer") - - _, err := r.LightclientClient.BlockHeader(r.Ctx, &lightclienttypes.QueryGetBlockHeaderRequest{ - BlockHash: hash.CloneBytes(), - }) - if err != nil { - r.Logger.Info( - "waiting for block header to show up in observer... current hash %s; err %s", - hash.String(), - err.Error(), - ) - } - if err == nil { - break - } - time.Sleep(2 * time.Second) - } - - // verify merkle proof through RPC - res, err := r.LightclientClient.Prove(r.Ctx, &lightclienttypes.QueryProveRequest{ - ChainId: chains.BitcoinRegtest.ChainId, - TxHash: txHash.String(), - BlockHash: blockHash.String(), - Proof: proofs.NewBitcoinProof(txBytes, path, index), - TxIndex: 0, // bitcoin doesn't use txIndex - }) - require.NoError(r, err) - require.True(r, res.Valid, "txProof should be valid") - - r.Logger.Info("OK: txProof verified for inTx: %s", txHash.String()) -} diff --git a/e2e/runner/evm.go b/e2e/runner/evm.go index 5fd4bdd565..10fd599d63 100644 --- a/e2e/runner/evm.go +++ b/e2e/runner/evm.go @@ -11,14 +11,8 @@ import ( "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/e2e/utils" - "github.com/zeta-chain/zetacore/pkg/chains" - "github.com/zeta-chain/zetacore/pkg/proofs" - "github.com/zeta-chain/zetacore/pkg/proofs/ethereum" - lightclienttypes "github.com/zeta-chain/zetacore/x/lightclient/types" ) -var blockHeaderETHTimeout = 5 * time.Minute - // WaitForTxReceiptOnEvm waits for a tx receipt on EVM func (r *E2ERunner) WaitForTxReceiptOnEvm(tx *ethtypes.Transaction) { r.Lock() @@ -110,13 +104,13 @@ func (r *E2ERunner) DepositERC20WithAmountAndMessage(to ethcommon.Address, amoun } // DepositEther sends Ethers into ZEVM -func (r *E2ERunner) DepositEther(testHeader bool) ethcommon.Hash { +func (r *E2ERunner) DepositEther() ethcommon.Hash { amount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(100)) // 100 eth - return r.DepositEtherWithAmount(testHeader, amount) + return r.DepositEtherWithAmount(amount) } // DepositEtherWithAmount sends Ethers into ZEVM -func (r *E2ERunner) DepositEtherWithAmount(testHeader bool, amount *big.Int) ethcommon.Hash { +func (r *E2ERunner) DepositEtherWithAmount(amount *big.Int) ethcommon.Hash { r.Logger.Print("⏳ depositing Ethers into ZEVM") signedTx, err := r.SendEther(r.TSSAddress, amount, nil) @@ -129,12 +123,6 @@ func (r *E2ERunner) DepositEtherWithAmount(testHeader bool, amount *big.Int) eth r.Logger.EVMReceipt(*receipt, "send to TSS") - // due to the high block throughput in localnet, ZetaClient might catch up slowly with the blocks - // to optimize block header proof test, this test is directly executed here on the first deposit instead of having a separate test - if testHeader { - r.ProveEthTransaction(receipt) - } - return signedTx.Hash() } @@ -176,64 +164,6 @@ func (r *E2ERunner) SendEther(_ ethcommon.Address, value *big.Int, data []byte) return signedTx, nil } -// ProveEthTransaction proves an ETH transaction on ZetaChain -func (r *E2ERunner) ProveEthTransaction(receipt *ethtypes.Receipt) { - startTime := time.Now() - - txHash := receipt.TxHash - blockHash := receipt.BlockHash - - // #nosec G115 test - always in range - txIndex := int(receipt.TransactionIndex) - - block, err := r.EVMClient.BlockByHash(r.Ctx, blockHash) - require.NoError(r, err) - - for { - // check timeout - reachedTimeout := time.Since(startTime) > blockHeaderETHTimeout - require.False(r, reachedTimeout, "timeout waiting for block header") - - _, err := r.LightclientClient.BlockHeader(r.Ctx, &lightclienttypes.QueryGetBlockHeaderRequest{ - BlockHash: blockHash.Bytes(), - }) - if err != nil { - r.Logger.Info("WARN: block header not found; retrying... error: %s", err.Error()) - } else { - r.Logger.Info("OK: block header found") - break - } - - time.Sleep(2 * time.Second) - } - - trie := ethereum.NewTrie(block.Transactions()) - require.Equal(r, trie.Hash(), block.Header().TxHash, "tx root hash & block tx root mismatch") - - txProof, err := trie.GenerateProof(txIndex) - require.NoError(r, err, "error generating txProof") - - val, err := txProof.Verify(block.TxHash(), txIndex) - require.NoError(r, err, "error verifying txProof") - - var txx ethtypes.Transaction - require.NoError(r, txx.UnmarshalBinary(val)) - - res, err := r.LightclientClient.Prove(r.Ctx, &lightclienttypes.QueryProveRequest{ - BlockHash: blockHash.Hex(), - TxIndex: int64(txIndex), - TxHash: txHash.Hex(), - Proof: proofs.NewEthereumProof(txProof), - ChainId: chains.GoerliLocalnet.ChainId, - }) - - // FIXME: @lumtis: don't do this in production - require.NoError(r, err) - require.True(r, res.Valid, "txProof invalid") - - r.Logger.Info("OK: txProof verified") -} - // AnvilMineBlocks mines blocks on Anvil localnet // the block time is provided in seconds // the method returns a function to stop the mining diff --git a/go.mod b/go.mod index bd46b60e9b..7f952f66a6 100644 --- a/go.mod +++ b/go.mod @@ -68,6 +68,7 @@ require ( github.com/nanmu42/etherscan-api v1.10.0 github.com/near/borsh-go v0.3.1 github.com/onrik/ethrpc v1.2.0 + github.com/samber/lo v1.46.0 gitlab.com/thorchain/tss/tss-lib v0.2.0 go.nhat.io/grpcmock v0.25.0 ) @@ -326,16 +327,16 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.17.0 + golang.org/x/crypto v0.23.0 golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb - golang.org/x/mod v0.11.0 // indirect - golang.org/x/net v0.19.0 + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.25.0 golang.org/x/oauth2 v0.15.0 // indirect - golang.org/x/sync v0.6.0 - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.15.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.9.1 // indirect + golang.org/x/sync v0.7.0 + golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/api v0.152.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.32.0 diff --git a/go.sum b/go.sum index 9579b0702f..36e3a288c5 100644 --- a/go.sum +++ b/go.sum @@ -1456,6 +1456,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ= +github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10= github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= @@ -1782,8 +1784,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1830,8 +1832,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1902,8 +1904,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1946,8 +1948,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2070,14 +2072,14 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2089,8 +2091,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2166,8 +2168,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/x/observer/types/chain_params.go b/x/observer/types/chain_params.go index b35d7da930..5fb41aab2e 100644 --- a/x/observer/types/chain_params.go +++ b/x/observer/types/chain_params.go @@ -8,7 +8,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ethchains "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" "github.com/zeta-chain/zetacore/pkg/chains" solanacontracts "github.com/zeta-chain/zetacore/pkg/contracts/solana" @@ -57,19 +56,8 @@ func ValidateChainParams(params *ChainParams) error { return fmt.Errorf("chain params cannot be nil") } - // TODO: ZetaChain chain params should be completely removed - // Once removed, this check is no longer necessary as all chasin params would need the same checks - // https://github.com/zeta-chain/node/issues/2419 - _, err := chains.ZetaChainFromChainID(params.ChainId) - if err == nil { - // zeta chain skips the rest of the checks for now - return nil - } - - // ignore error from ZetaChainFromChainID if reason is chain is not zeta chain - // return error otherwise - if !errors.Is(err, chains.ErrNotZetaChain) { - return err + if chains.IsZetaChain(params.ChainId, nil) { + return errorsmod.Wrap(sdkerrors.ErrInvalidChainID, "zeta chain cannot have observer chain parameters") } if params.ConfirmationCount == 0 { @@ -165,7 +153,6 @@ func GetDefaultChainParams() ChainParamsList { GetDefaultBtcRegtestChainParams(), GetDefaultSolanaLocalnetChainParams(), GetDefaultGoerliLocalnetChainParams(), - GetDefaultZetaPrivnetChainParams(), }, } } diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index 15a3bfdc99..a7dc5afe3d 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -49,7 +49,7 @@ func (ob *Observer) WatchInbound(ctx context.Context) error { for { select { case <-ticker.C(): - if !app.IsInboundObservationEnabled(ob.GetChainParams()) { + if !app.IsInboundObservationEnabled() { sampledLogger.Info(). Msgf("WatchInbound: inbound observation is disabled for chain %d", ob.Chain().ChainId) continue @@ -69,11 +69,6 @@ func (ob *Observer) WatchInbound(ctx context.Context) error { // ObserveInbound observes the Bitcoin chain for inbounds and post votes to zetacore // TODO(revamp): simplify this function into smaller functions func (ob *Observer) ObserveInbound(ctx context.Context) error { - app, err := zctx.FromContext(ctx) - if err != nil { - return err - } - zetaCoreClient := ob.ZetacoreClient() // get and update latest block height @@ -119,18 +114,6 @@ func (ob *Observer) ObserveInbound(ctx context.Context) error { blockNumber, len(res.Block.Tx), cnt, lastScanned) // add block header to zetacore - // TODO: consider having a separate ticker(from TSS scaning) for posting block headers - // https://github.com/zeta-chain/node/issues/1847 - // TODO: move this logic in its own routine - // https://github.com/zeta-chain/node/issues/2204 - blockHeaderVerification, found := app.GetBlockHeaderEnabledChains(ob.Chain().ChainId) - if found && blockHeaderVerification.Enabled { - // #nosec G115 always in range - err = ob.postBlockHeader(ctx, int64(blockNumber)) - if err != nil { - ob.logger.Inbound.Warn().Err(err).Msgf("observeInboundBTC: error posting block header %d", blockNumber) - } - } if len(res.Block.Tx) > 1 { // get depositor fee depositorFee := bitcoin.CalcDepositorFee(res.Block, ob.Chain().ChainId, ob.netParams, ob.logger.Inbound) @@ -206,7 +189,7 @@ func (ob *Observer) WatchInboundTracker(ctx context.Context) error { for { select { case <-ticker.C(): - if !app.IsInboundObservationEnabled(ob.GetChainParams()) { + if !app.IsInboundObservationEnabled() { continue } err := ob.ProcessInboundTrackers(ctx) diff --git a/zetaclient/chains/bitcoin/observer/observer.go b/zetaclient/chains/bitcoin/observer/observer.go index 8b4c79ba39..6a15173c33 100644 --- a/zetaclient/chains/bitcoin/observer/observer.go +++ b/zetaclient/chains/bitcoin/observer/observer.go @@ -2,7 +2,6 @@ package observer import ( - "bytes" "context" "encoding/hex" "fmt" @@ -21,7 +20,6 @@ import ( "github.com/zeta-chain/zetacore/pkg/bg" "github.com/zeta-chain/zetacore/pkg/chains" - "github.com/zeta-chain/zetacore/pkg/proofs" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/chains/base" "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin" @@ -646,41 +644,3 @@ func (ob *Observer) isTssTransaction(txid string) bool { _, found := ob.includedTxHashes[txid] return found } - -// postBlockHeader posts block header to zetacore -// TODO(revamp): move to block header file -func (ob *Observer) postBlockHeader(ctx context.Context, tip int64) error { - ob.logger.Inbound.Info().Msgf("postBlockHeader: tip %d", tip) - bn := tip - chainState, err := ob.ZetacoreClient().GetBlockHeaderChainState(ctx, ob.Chain().ChainId) - if err == nil && chainState != nil && chainState.EarliestHeight > 0 { - bn = chainState.LatestHeight + 1 - } - if bn > tip { - return fmt.Errorf("postBlockHeader: must post block confirmed block header: %d > %d", bn, tip) - } - res2, err := ob.GetBlockByNumberCached(bn) - if err != nil { - return fmt.Errorf("error getting bitcoin block %d: %s", bn, err) - } - - var headerBuf bytes.Buffer - err = res2.Header.Serialize(&headerBuf) - if err != nil { // should never happen - ob.logger.Inbound.Error().Err(err).Msgf("error serializing bitcoin block header: %d", bn) - return err - } - blockHash := res2.Header.BlockHash() - _, err = ob.ZetacoreClient().PostVoteBlockHeader( - ctx, - ob.Chain().ChainId, - blockHash[:], - res2.Block.Height, - proofs.NewBitcoinHeader(headerBuf.Bytes()), - ) - ob.logger.Inbound.Info().Msgf("posted block header %d: %s", bn, blockHash) - if err != nil { // error shouldn't block the process - ob.logger.Inbound.Error().Err(err).Msgf("error posting bitcoin block header: %d", bn) - } - return err -} diff --git a/zetaclient/chains/bitcoin/observer/outbound.go b/zetaclient/chains/bitcoin/observer/outbound.go index d6dd003caa..009a49759e 100644 --- a/zetaclient/chains/bitcoin/observer/outbound.go +++ b/zetaclient/chains/bitcoin/observer/outbound.go @@ -46,7 +46,7 @@ func (ob *Observer) WatchOutbound(ctx context.Context) error { for { select { case <-ticker.C(): - if !app.IsOutboundObservationEnabled(ob.GetChainParams()) { + if !app.IsOutboundObservationEnabled() { sampledLogger.Info(). Msgf("WatchOutbound: outbound observation is disabled for chain %d", chainID) continue diff --git a/zetaclient/chains/evm/observer/inbound.go b/zetaclient/chains/evm/observer/inbound.go index b8ee361f37..19ad1f14d5 100644 --- a/zetaclient/chains/evm/observer/inbound.go +++ b/zetaclient/chains/evm/observer/inbound.go @@ -57,7 +57,7 @@ func (ob *Observer) WatchInbound(ctx context.Context) error { for { select { case <-ticker.C(): - if !app.IsInboundObservationEnabled(ob.GetChainParams()) { + if !app.IsInboundObservationEnabled() { sampledLogger.Info(). Msgf("WatchInbound: inbound observation is disabled for chain %d", ob.Chain().ChainId) continue @@ -97,7 +97,7 @@ func (ob *Observer) WatchInboundTracker(ctx context.Context) error { for { select { case <-ticker.C(): - if !app.IsInboundObservationEnabled(ob.GetChainParams()) { + if !app.IsInboundObservationEnabled() { continue } err := ob.ProcessInboundTrackers(ctx) @@ -311,18 +311,17 @@ func (ob *Observer) ObserveZetaSent(ctx context.Context, startBlock, toBlock uin guard[event.Raw.TxHash.Hex()] = true msg := ob.BuildInboundVoteMsgForZetaSentEvent(app, event) - if msg != nil { - _, err = ob.PostVoteInbound( - ctx, - msg, - zetacore.PostVoteInboundMessagePassingExecutionGasLimit, - ) - if err != nil { - // we have to re-scan from this block next time - return beingScanned - 1, err - } + if msg == nil { + continue + } + + const gasLimit = zetacore.PostVoteInboundMessagePassingExecutionGasLimit + if _, err = ob.PostVoteInbound(ctx, msg, gasLimit); err != nil { + // we have to re-scan from this block next time + return beingScanned - 1, err } } + // successful processed all events in [startBlock, toBlock] return toBlock, nil } @@ -414,34 +413,10 @@ func (ob *Observer) ObserveERC20Deposited(ctx context.Context, startBlock, toBlo // ObserverTSSReceive queries the incoming gas asset to TSS address and posts to zetacore // returns the last block successfully scanned func (ob *Observer) ObserverTSSReceive(ctx context.Context, startBlock, toBlock uint64) (uint64, error) { - app, err := zctx.FromContext(ctx) - if err != nil { - return 0, err - } - - var ( - // post new block header (if any) to zetacore and ignore error - // TODO: consider having a independent ticker(from TSS scaning) for posting block headers - // https://github.com/zeta-chain/node/issues/1847 - chainID = ob.Chain().ChainId - blockHeaderVerification, found = app.GetBlockHeaderEnabledChains(chainID) - shouldPostBlockHeader = found && blockHeaderVerification.Enabled - ) + chainID := ob.Chain().ChainId // query incoming gas asset for bn := startBlock; bn <= toBlock; bn++ { - if shouldPostBlockHeader { - // post block header for supported chains - // TODO: move this logic in its own routine - // https://github.com/zeta-chain/node/issues/2204 - if err := ob.postBlockHeader(ctx, toBlock); err != nil { - ob.Logger().Inbound. - Error().Err(err). - Uint64("tss.to_block", toBlock). - Msg("error posting block header") - } - } - // observe TSS received gas token in block 'bn' err := ob.ObserveTSSReceiveInBlock(ctx, bn) if err != nil { @@ -532,7 +507,7 @@ func (ob *Observer) CheckAndVoteInboundTokenERC20( } // get erc20 custody contract - addrCustory, custody, err := ob.GetERC20CustodyContract() + addrCustody, custody, err := ob.GetERC20CustodyContract() if err != nil { return "", err } @@ -544,7 +519,7 @@ func (ob *Observer) CheckAndVoteInboundTokenERC20( zetaDeposited, err := custody.ParseDeposited(*log) if err == nil && zetaDeposited != nil { // sanity check tx event - err = evm.ValidateEvmTxLog(&zetaDeposited.Raw, addrCustory, tx.Hash, evm.TopicsDeposited) + err = evm.ValidateEvmTxLog(&zetaDeposited.Raw, addrCustody, tx.Hash, evm.TopicsDeposited) if err == nil { msg = ob.BuildInboundVoteMsgForDepositedEvent(zetaDeposited, sender) } else { @@ -671,14 +646,13 @@ func (ob *Observer) BuildInboundVoteMsgForZetaSentEvent( appContext *zctx.AppContext, event *zetaconnector.ZetaConnectorNonEthZetaSent, ) *types.MsgVoteInbound { - destChain, found := chains.GetChainFromChainID( - event.DestinationChainId.Int64(), - appContext.GetAdditionalChains(), - ) - if !found { - ob.Logger().Inbound.Warn().Msgf("chain id not supported %d", event.DestinationChainId.Int64()) + // note that this is most likely zeta chain + destChain, err := appContext.GetChain(event.DestinationChainId.Int64()) + if err != nil { + ob.Logger().Inbound.Warn().Err(err).Msgf("chain id %d not supported", event.DestinationChainId.Int64()) return nil } + destAddr := clienttypes.BytesToEthHex(event.DestinationAddress) // compliance check @@ -689,17 +663,10 @@ func (ob *Observer) BuildInboundVoteMsgForZetaSentEvent( return nil } - if !destChain.IsZetaChain() { - paramsDest, found := appContext.GetEVMChainParams(destChain.ChainId) - if !found { - ob.Logger().Inbound.Warn(). - Msgf("chain id not present in EVMChainParams %d", event.DestinationChainId.Int64()) - return nil - } - - if strings.EqualFold(destAddr, paramsDest.ZetaTokenContractAddress) { + if !destChain.IsZeta() { + if strings.EqualFold(destAddr, destChain.Params().ZetaTokenContractAddress) { ob.Logger().Inbound.Warn(). - Msgf("potential attack attempt: %s destination address is ZETA token contract address %s", destChain.String(), destAddr) + Msgf("potential attack attempt: %s destination address is ZETA token contract address", destAddr) return nil } } @@ -713,7 +680,7 @@ func (ob *Observer) BuildInboundVoteMsgForZetaSentEvent( ob.Chain().ChainId, event.SourceTxOriginAddress.Hex(), destAddr, - destChain.ChainId, + destChain.ID(), sdkmath.NewUintFromBigInt(event.ZetaValueAndGas), message, event.Raw.TxHash.Hex(), diff --git a/zetaclient/chains/evm/observer/inbound_test.go b/zetaclient/chains/evm/observer/inbound_test.go index 9e01c214b3..231a3ae6c7 100644 --- a/zetaclient/chains/evm/observer/inbound_test.go +++ b/zetaclient/chains/evm/observer/inbound_test.go @@ -45,8 +45,10 @@ func Test_CheckAndVoteInboundTokenZeta(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) - ballot, err := ob.CheckAndVoteInboundTokenZeta(ctx, tx, receipt, false) + ob, appContext := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + voteCtx := zctx.WithAppContext(context.Background(), appContext) + + ballot, err := ob.CheckAndVoteInboundTokenZeta(voteCtx, tx, receipt, false) require.NoError(t, err) require.Equal(t, cctx.InboundParams.BallotIndex, ballot) }) @@ -61,7 +63,7 @@ func Test_CheckAndVoteInboundTokenZeta(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - 1 - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) _, err := ob.CheckAndVoteInboundTokenZeta(ctx, tx, receipt, false) require.ErrorContains(t, err, "not been confirmed") }) @@ -77,24 +79,27 @@ func Test_CheckAndVoteInboundTokenZeta(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenZeta(ctx, tx, receipt, true) require.NoError(t, err) require.Equal(t, "", ballot) }) t.Run("should not act if emitter is not ZetaConnector", func(t *testing.T) { - tx, receipt, _ := testutils.LoadEVMInboundNReceiptNCctx( - t, + // Given tx from ETH + tx, receipt, _ := testutils.LoadEVMInboundNReceiptNCctx(t, TestDataDir, - chainID, + chains.Ethereum.ChainId, inboundHash, coin.CoinType_Zeta, ) require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - chainID = 56 // use BSC chain connector - ob := MockEVMObserver( + // Given BSC observer + chain := chains.BscMainnet + params := mocks.MockChainParams(chain.ChainId, confirmation) + + ob, _ := MockEVMObserver( t, chain, nil, @@ -102,9 +107,13 @@ func Test_CheckAndVoteInboundTokenZeta(t *testing.T) { nil, nil, lastBlock, - mocks.MockChainParams(chainID, confirmation), + params, ) + + // ACT _, err := ob.CheckAndVoteInboundTokenZeta(ctx, tx, receipt, true) + + // ASSERT require.ErrorContains(t, err, "emitter address mismatch") }) } @@ -131,7 +140,7 @@ func Test_CheckAndVoteInboundTokenERC20(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenERC20(ctx, tx, receipt, false) require.NoError(t, err) require.Equal(t, cctx.InboundParams.BallotIndex, ballot) @@ -147,7 +156,7 @@ func Test_CheckAndVoteInboundTokenERC20(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - 1 - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) _, err := ob.CheckAndVoteInboundTokenERC20(ctx, tx, receipt, false) require.ErrorContains(t, err, "not been confirmed") }) @@ -163,24 +172,29 @@ func Test_CheckAndVoteInboundTokenERC20(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenERC20(ctx, tx, receipt, true) require.NoError(t, err) require.Equal(t, "", ballot) }) t.Run("should not act if emitter is not ERC20 Custody", func(t *testing.T) { + // ARRANGE + // Given tx from ETH tx, receipt, _ := testutils.LoadEVMInboundNReceiptNCctx( t, TestDataDir, - chainID, + chains.Ethereum.ChainId, inboundHash, coin.CoinType_ERC20, ) require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - chainID = 56 // use BSC chain ERC20 custody - ob := MockEVMObserver( + // Given BSC observer + chain := chains.BscMainnet + params := mocks.MockChainParams(chain.ChainId, confirmation) + + ob, _ := MockEVMObserver( t, chain, nil, @@ -188,9 +202,13 @@ func Test_CheckAndVoteInboundTokenERC20(t *testing.T) { nil, nil, lastBlock, - mocks.MockChainParams(chainID, confirmation), + params, ) + + // ACT _, err := ob.CheckAndVoteInboundTokenERC20(ctx, tx, receipt, true) + + // ASSERT require.ErrorContains(t, err, "emitter address mismatch") }) } @@ -217,7 +235,7 @@ func Test_CheckAndVoteInboundTokenGas(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenGas(ctx, tx, receipt, false) require.NoError(t, err) require.Equal(t, cctx.InboundParams.BallotIndex, ballot) @@ -227,7 +245,7 @@ func Test_CheckAndVoteInboundTokenGas(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - 1 - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) _, err := ob.CheckAndVoteInboundTokenGas(ctx, tx, receipt, false) require.ErrorContains(t, err, "not been confirmed") }) @@ -237,7 +255,7 @@ func Test_CheckAndVoteInboundTokenGas(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenGas(ctx, tx, receipt, false) require.ErrorContains(t, err, "not TSS address") require.Equal(t, "", ballot) @@ -248,7 +266,7 @@ func Test_CheckAndVoteInboundTokenGas(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenGas(ctx, tx, receipt, false) require.ErrorContains(t, err, "not a successful tx") require.Equal(t, "", ballot) @@ -259,7 +277,7 @@ func Test_CheckAndVoteInboundTokenGas(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenGas(ctx, tx, receipt, false) require.NoError(t, err) require.Equal(t, "", ballot) @@ -276,7 +294,7 @@ func Test_BuildInboundVoteMsgForZetaSentEvent(t *testing.T) { cctx := testutils.LoadCctxByInbound(t, chainID, coin.CoinType_Zeta, inboundHash) // parse ZetaSent event - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, mocks.MockChainParams(1, 1)) + ob, app := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, mocks.MockChainParams(1, 1)) connector := mocks.MockConnectorNonEth(t, chainID) event := testutils.ParseReceiptZetaSent(receipt, connector) @@ -285,8 +303,6 @@ func Test_BuildInboundVoteMsgForZetaSentEvent(t *testing.T) { ComplianceConfig: config.ComplianceConfig{}, } - _, app := makeAppContext(t) - t.Run("should return vote msg for archived ZetaSent event", func(t *testing.T) { msg := ob.BuildInboundVoteMsgForZetaSentEvent(app, event) require.NotNil(t, msg) @@ -325,7 +341,7 @@ func Test_BuildInboundVoteMsgForDepositedEvent(t *testing.T) { cctx := testutils.LoadCctxByInbound(t, chainID, coin.CoinType_ERC20, inboundHash) // parse Deposited event - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, mocks.MockChainParams(1, 1)) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, mocks.MockChainParams(1, 1)) custody := mocks.MockERC20Custody(t, chainID) event := testutils.ParseReceiptERC20Deposited(receipt, custody) sender := ethcommon.HexToAddress(tx.From) @@ -383,7 +399,7 @@ func Test_BuildInboundVoteMsgForTokenSentToTSS(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(txDonation)) // create test compliance config - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, mocks.MockChainParams(1, 1)) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, mocks.MockChainParams(1, 1)) cfg := config.Config{ ComplianceConfig: config.ComplianceConfig{}, } @@ -460,7 +476,7 @@ func Test_ObserveTSSReceiveInBlock(t *testing.T) { ctx := context.Background() t.Run("should observe TSS receive in block", func(t *testing.T) { - ob := MockEVMObserver(t, chain, evmClient, evmJSONRPC, zetacoreClient, tss, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, evmClient, evmJSONRPC, zetacoreClient, tss, lastBlock, chainParam) // feed archived block and receipt evmJSONRPC.WithBlock(block) @@ -469,13 +485,13 @@ func Test_ObserveTSSReceiveInBlock(t *testing.T) { require.NoError(t, err) }) t.Run("should not observe on error getting block", func(t *testing.T) { - ob := MockEVMObserver(t, chain, evmClient, evmJSONRPC, zetacoreClient, tss, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, evmClient, evmJSONRPC, zetacoreClient, tss, lastBlock, chainParam) err := ob.ObserveTSSReceiveInBlock(ctx, blockNumber) // error getting block is expected because the mock JSONRPC contains no block require.ErrorContains(t, err, "error getting block") }) t.Run("should not observe on error getting receipt", func(t *testing.T) { - ob := MockEVMObserver(t, chain, evmClient, evmJSONRPC, zetacoreClient, tss, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, evmClient, evmJSONRPC, zetacoreClient, tss, lastBlock, chainParam) evmJSONRPC.WithBlock(block) err := ob.ObserveTSSReceiveInBlock(ctx, blockNumber) // error getting block is expected because the mock evmClient contains no receipt @@ -483,9 +499,9 @@ func Test_ObserveTSSReceiveInBlock(t *testing.T) { }) } -func makeAppContext(_ *testing.T) (context.Context, *zctx.AppContext) { +func makeAppContext(t *testing.T) (context.Context, *zctx.AppContext) { var ( - app = zctx.New(config.New(false), zerolog.Nop()) + app = zctx.New(config.New(false), zerolog.New(zerolog.NewTestWriter(t))) ctx = context.Background() ) diff --git a/zetaclient/chains/evm/observer/observer.go b/zetaclient/chains/evm/observer/observer.go index b6ff80c769..4959b13b9a 100644 --- a/zetaclient/chains/evm/observer/observer.go +++ b/zetaclient/chains/evm/observer/observer.go @@ -11,7 +11,6 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/rlp" "github.com/onrik/ethrpc" "github.com/pkg/errors" "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" @@ -20,7 +19,6 @@ import ( "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol" "github.com/zeta-chain/zetacore/pkg/bg" - "github.com/zeta-chain/zetacore/pkg/proofs" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/chains/base" "github.com/zeta-chain/zetacore/zetaclient/chains/evm" @@ -389,43 +387,3 @@ func (ob *Observer) LoadLastBlockScanned(ctx context.Context) error { return nil } - -// postBlockHeader posts the block header to zetacore -// TODO(revamp): move to a block header file -func (ob *Observer) postBlockHeader(ctx context.Context, tip uint64) error { - bn := tip - - chainState, err := ob.ZetacoreClient().GetBlockHeaderChainState(ctx, ob.Chain().ChainId) - if err == nil && chainState != nil && chainState.EarliestHeight > 0 { - // #nosec G115 always positive - bn = uint64(chainState.LatestHeight) + 1 // the next header to post - } - - if bn > tip { - return fmt.Errorf("postBlockHeader: must post block confirmed block header: %d > %d", bn, tip) - } - - header, err := ob.GetBlockHeaderCached(ctx, bn) - if err != nil { - ob.Logger().Inbound.Error().Err(err).Msgf("postBlockHeader: error getting block: %d", bn) - return err - } - headerRLP, err := rlp.EncodeToBytes(header) - if err != nil { - ob.Logger().Inbound.Error().Err(err).Msgf("postBlockHeader: error encoding block header: %d", bn) - return err - } - - _, err = ob.ZetacoreClient().PostVoteBlockHeader( - ctx, - ob.Chain().ChainId, - header.Hash().Bytes(), - header.Number.Int64(), - proofs.NewEthereumHeader(headerRLP), - ) - if err != nil { - ob.Logger().Inbound.Error().Err(err).Msgf("postBlockHeader: error posting block header: %d", bn) - return err - } - return nil -} diff --git a/zetaclient/chains/evm/observer/observer_gas_test.go b/zetaclient/chains/evm/observer/observer_gas_test.go index ce0b681d43..3a416f1733 100644 --- a/zetaclient/chains/evm/observer/observer_gas_test.go +++ b/zetaclient/chains/evm/observer/observer_gas_test.go @@ -34,7 +34,7 @@ func TestPostGasPrice(t *testing.T) { confirmation := uint64(10) chainParam := mocks.MockChainParams(chain.ChainId, confirmation) - observer := MockEVMObserver(t, chain, ethRPC, nil, zetacoreClient, nil, blockNumber, chainParam) + observer, _ := MockEVMObserver(t, chain, ethRPC, nil, zetacoreClient, nil, blockNumber, chainParam) // Given empty baseFee from RPC ethRPC.WithHeader(ðtypes.Header{BaseFee: nil}) @@ -79,7 +79,7 @@ func TestPostGasPrice(t *testing.T) { confirmation := uint64(10) chainParam := mocks.MockChainParams(chain.ChainId, confirmation) - observer := MockEVMObserver(t, chain, ethRPC, nil, zetacoreClient, nil, blockNumber, chainParam) + observer, _ := MockEVMObserver(t, chain, ethRPC, nil, zetacoreClient, nil, blockNumber, chainParam) // Given 1 gwei baseFee from RPC ethRPC.WithHeader(ðtypes.Header{BaseFee: big.NewInt(gwei)}) diff --git a/zetaclient/chains/evm/observer/observer_test.go b/zetaclient/chains/evm/observer/observer_test.go index f0b47044d5..95d2ed2140 100644 --- a/zetaclient/chains/evm/observer/observer_test.go +++ b/zetaclient/chains/evm/observer/observer_test.go @@ -13,6 +13,7 @@ import ( "github.com/onrik/ethrpc" "github.com/rs/zerolog" "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/ptr" zctx "github.com/zeta-chain/zetacore/zetaclient/context" "github.com/zeta-chain/zetacore/zetaclient/db" "github.com/zeta-chain/zetacore/zetaclient/keys" @@ -36,6 +37,7 @@ var TestDataDir = "../../../" // getAppContext creates an AppContext for unit tests func getAppContext( + t *testing.T, evmChain chains.Chain, endpoint string, evmChainParams *observertypes.ChainParams, @@ -45,6 +47,8 @@ func getAppContext( endpoint = "http://localhost:8545" } + require.Equal(t, evmChain.ChainId, evmChainParams.ChainId, "chain id mismatch between chain and params") + // create config cfg := config.New(false) cfg.EVMChainConfigs[evmChain.ChainId] = config.EVMConfig{ @@ -52,24 +56,28 @@ func getAppContext( Endpoint: endpoint, } + logger := zerolog.New(zerolog.NewTestWriter(t)) + // create AppContext - appContext := zctx.New(cfg, zerolog.Nop()) - evmChainParamsMap := make(map[int64]*observertypes.ChainParams) - evmChainParamsMap[evmChain.ChainId] = evmChainParams + appContext := zctx.New(cfg, logger) + chainParams := map[int64]*observertypes.ChainParams{ + evmChain.ChainId: evmChainParams, + chains.ZetaChainMainnet.ChainId: ptr.Ptr( + mocks.MockChainParams(chains.ZetaChainMainnet.ChainId, 10), + ), + } // feed chain params - appContext.Update( - &observertypes.Keygen{}, - []chains.Chain{evmChain}, - evmChainParamsMap, + err := appContext.Update( + observertypes.Keygen{}, + []chains.Chain{evmChain, chains.ZetaChainMainnet}, nil, - nil, - "", + chainParams, + "tssPubKey", *sample.CrosschainFlags(), - []chains.Chain{}, - sample.HeaderSupportedChains(), - true, ) + require.NoError(t, err) + // create AppContext return appContext, cfg.EVMChainConfigs[evmChain.ChainId] } @@ -84,7 +92,7 @@ func MockEVMObserver( tss interfaces.TSSSigner, lastBlock uint64, params observertypes.ChainParams, -) *observer.Observer { +) (*observer.Observer, *zctx.AppContext) { ctx := context.Background() // use default mock evm client if not provided @@ -105,18 +113,21 @@ func MockEVMObserver( tss = mocks.NewTSSMainnet() } // create AppContext - _, evmCfg := getAppContext(chain, "", ¶ms) + appContext, evmCfg := getAppContext(t, chain, "", ¶ms) database, err := db.NewFromSqliteInMemory(true) require.NoError(t, err) + testLogger := zerolog.New(zerolog.NewTestWriter(t)) + logger := base.Logger{Std: testLogger, Compliance: testLogger} + // create observer - ob, err := observer.NewObserver(ctx, evmCfg, evmClient, params, zetacoreClient, tss, database, base.Logger{}, nil) + ob, err := observer.NewObserver(ctx, evmCfg, evmClient, params, zetacoreClient, tss, database, logger, nil) require.NoError(t, err) ob.WithEvmJSONRPC(evmJSONRPC) ob.WithLastBlock(lastBlock) - return ob + return ob, appContext } func Test_NewObserver(t *testing.T) { @@ -242,7 +253,7 @@ func Test_LoadLastBlockScanned(t *testing.T) { // create observer using mock evm client evmClient := mocks.NewMockEvmClient().WithBlockNumber(100) - ob := MockEVMObserver(t, chain, evmClient, nil, nil, nil, 1, params) + ob, _ := MockEVMObserver(t, chain, evmClient, nil, nil, nil, 1, params) t.Run("should load last block scanned", func(t *testing.T) { // create db and write 123 as last block scanned @@ -265,7 +276,7 @@ func Test_LoadLastBlockScanned(t *testing.T) { }) t.Run("should fail on RPC error", func(t *testing.T) { // create observer on separate path, as we need to reset last block scanned - obOther := MockEVMObserver(t, chain, evmClient, nil, nil, nil, 1, params) + obOther, _ := MockEVMObserver(t, chain, evmClient, nil, nil, nil, 1, params) // reset last block scanned to 0 so that it will be loaded from RPC obOther.WithLastBlockScanned(0) diff --git a/zetaclient/chains/evm/observer/outbound.go b/zetaclient/chains/evm/observer/outbound.go index ff256613a9..680fb4e3af 100644 --- a/zetaclient/chains/evm/observer/outbound.go +++ b/zetaclient/chains/evm/observer/outbound.go @@ -52,7 +52,7 @@ func (ob *Observer) WatchOutbound(ctx context.Context) error { for { select { case <-ticker.C(): - if !app.IsOutboundObservationEnabled(ob.GetChainParams()) { + if !app.IsOutboundObservationEnabled() { sampledLogger.Info(). Msgf("WatchOutbound: outbound observation is disabled for chain %d", ob.Chain().ChainId) continue diff --git a/zetaclient/chains/evm/observer/outbound_test.go b/zetaclient/chains/evm/observer/outbound_test.go index a2aae00433..4081f9a76c 100644 --- a/zetaclient/chains/evm/observer/outbound_test.go +++ b/zetaclient/chains/evm/observer/outbound_test.go @@ -12,7 +12,6 @@ import ( "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/testutil/sample" - observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/chains/evm/observer" "github.com/zeta-chain/zetacore/zetaclient/config" "github.com/zeta-chain/zetacore/zetaclient/testutils" @@ -60,7 +59,7 @@ func Test_IsOutboundProcessed(t *testing.T) { t.Run("should post vote and return true if outbound is processed", func(t *testing.T) { // create evm observer and set outbound and receipt - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) ob.SetTxNReceipt(nonce, receipt, outbound) // post outbound vote @@ -76,7 +75,7 @@ func Test_IsOutboundProcessed(t *testing.T) { cctx.InboundParams.Sender = sample.EthAddress().Hex() // create evm observer and set outbound and receipt - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) ob.SetTxNReceipt(nonce, receipt, outbound) // modify compliance config to restrict sender address @@ -93,14 +92,14 @@ func Test_IsOutboundProcessed(t *testing.T) { }) t.Run("should return false if outbound is not confirmed", func(t *testing.T) { // create evm observer and DO NOT set outbound as confirmed - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) continueKeysign, err := ob.VoteOutboundIfConfirmed(ctx, cctx) require.NoError(t, err) require.True(t, continueKeysign) }) t.Run("should fail if unable to parse ZetaReceived event", func(t *testing.T) { // create evm observer and set outbound and receipt - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) ob.SetTxNReceipt(nonce, receipt, outbound) // set connector contract address to an arbitrary address to make event parsing fail @@ -149,7 +148,7 @@ func Test_IsOutboundProcessed_ContractError(t *testing.T) { t.Run("should fail if unable to get connector/custody contract", func(t *testing.T) { // create evm observer and set outbound and receipt - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) ob.SetTxNReceipt(nonce, receipt, outbound) abiConnector := zetaconnector.ZetaConnectorNonEthMetaData.ABI abiCustody := erc20custody.ERC20CustodyMetaData.ABI @@ -193,7 +192,7 @@ func Test_PostVoteOutbound(t *testing.T) { receiveStatus := chains.ReceiveStatus_success // create evm client using mock zetacore client and post outbound vote - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, observertypes.ChainParams{}) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, mocks.MockChainParams(chain.ChainId, 100)) ob.PostVoteOutbound( ctx, cctx.Index, diff --git a/zetaclient/chains/evm/signer/outbound_data.go b/zetaclient/chains/evm/signer/outbound_data.go index 58ade4faf6..ebeef1c5b1 100644 --- a/zetaclient/chains/evm/signer/outbound_data.go +++ b/zetaclient/chains/evm/signer/outbound_data.go @@ -4,11 +4,11 @@ import ( "context" "encoding/base64" "encoding/hex" - "errors" "fmt" "math/big" ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/zeta-chain/zetacore/pkg/chains" @@ -93,7 +93,7 @@ func (txData *OutboundData) SetupGas( if chain.Network == chains.Network_eth { suggested, err := client.SuggestGasPrice(context.Background()) if err != nil { - return errors.Join(err, fmt.Errorf("cannot get gas price from chain %s ", chain.String())) + return errors.Wrapf(err, "cannot get gas price from chain %s ", chain.String()) } txData.gasPrice = roundUpToNearestGwei(suggested) } else { @@ -138,21 +138,30 @@ func NewOutboundData( return nil, false, err } - nonce := cctx.GetCurrentOutboundParam().TssNonce - toChain, found := chains.GetChainFromChainID(txData.toChainID.Int64(), app.GetAdditionalChains()) - if !found { - return nil, true, fmt.Errorf("unknown chain: %d", txData.toChainID.Int64()) + chainID := txData.toChainID.Int64() + + toChain, err := app.GetChain(chainID) + switch { + case err != nil: + return nil, true, errors.Wrapf(err, "unable to get chain %d from app context", chainID) + case toChain.IsZeta(): + // should not happen + return nil, true, errors.New("destination chain is Zeta") } + rawChain := toChain.RawChain() + // Set up gas limit and gas price - err = txData.SetupGas(cctx, logger, evmRPC, toChain) + err = txData.SetupGas(cctx, logger, evmRPC, *rawChain) if err != nil { - return nil, true, err + return nil, true, errors.Wrap(err, "unable to setup gas") } + nonce := cctx.GetCurrentOutboundParam().TssNonce + // Get sendHash logger.Info(). - Msgf("chain %s minting %d to %s, nonce %d, finalized zeta bn %d", toChain.String(), cctx.InboundParams.Amount, txData.to.Hex(), nonce, cctx.InboundParams.FinalizedZetaHeight) + Msgf("chain %d minting %d to %s, nonce %d, finalized zeta bn %d", toChain.ID(), cctx.InboundParams.Amount, txData.to.Hex(), nonce, cctx.InboundParams.FinalizedZetaHeight) cctxIndex, err := hex.DecodeString(cctx.Index[2:]) // remove the leading 0x if err != nil || len(cctxIndex) != 32 { return nil, true, fmt.Errorf("decode CCTX %s error", cctx.Index) diff --git a/zetaclient/chains/evm/signer/outbound_data_test.go b/zetaclient/chains/evm/signer/outbound_data_test.go index ac2b7061b5..0d44fd3dbb 100644 --- a/zetaclient/chains/evm/signer/outbound_data_test.go +++ b/zetaclient/chains/evm/signer/outbound_data_test.go @@ -7,9 +7,12 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/config" zctx "github.com/zeta-chain/zetacore/zetaclient/context" + "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/x/crosschain/types" @@ -72,6 +75,19 @@ func TestSigner_NewOutboundData(t *testing.T) { app := zctx.New(config.New(false), zerolog.Nop()) ctx := zctx.WithAppContext(context.Background(), app) + bscParams := mocks.MockChainParams(chains.BscMainnet.ChainId, 10) + + // Given app context + err := app.Update( + observertypes.Keygen{}, + []chains.Chain{chains.BscMainnet}, + nil, + map[int64]*observertypes.ChainParams{chains.BscMainnet.ChainId: &bscParams}, + "tssPubKey", + observertypes.CrosschainFlags{}, + ) + require.NoError(t, err) + // Setup evm signer evmSigner, err := getNewEvmSigner(nil) require.NoError(t, err) @@ -81,33 +97,35 @@ func TestSigner_NewOutboundData(t *testing.T) { t.Run("NewOutboundData success", func(t *testing.T) { cctx := getCCTX(t) + _, skip, err := NewOutboundData(ctx, cctx, mockObserver, evmSigner.EvmClient(), zerolog.Logger{}, 123) - require.False(t, skip) - require.NoError(t, err) + assert.NoError(t, err) + assert.False(t, skip) }) t.Run("NewOutboundData skip", func(t *testing.T) { cctx := getCCTX(t) cctx.CctxStatus.Status = types.CctxStatus_Aborted + _, skip, err := NewOutboundData(ctx, cctx, mockObserver, evmSigner.EvmClient(), zerolog.Logger{}, 123) - require.NoError(t, err) - require.True(t, skip) + assert.NoError(t, err) + assert.True(t, skip) }) t.Run("NewOutboundData unknown chain", func(t *testing.T) { cctx := getInvalidCCTX(t) - require.NoError(t, err) + _, skip, err := NewOutboundData(ctx, cctx, mockObserver, evmSigner.EvmClient(), zerolog.Logger{}, 123) - require.ErrorContains(t, err, "unknown chain") - require.True(t, skip) + assert.ErrorContains(t, err, "unable to get chain 13378337 from app context: id=13378337: chain not found") + assert.True(t, skip) }) t.Run("NewOutboundData setup gas error", func(t *testing.T) { cctx := getCCTX(t) - require.NoError(t, err) cctx.GetCurrentOutboundParam().GasPrice = "invalidGasPrice" + _, skip, err := NewOutboundData(ctx, cctx, mockObserver, evmSigner.EvmClient(), zerolog.Logger{}, 123) - require.True(t, skip) - require.ErrorContains(t, err, "cannot convert gas price") + assert.True(t, skip) + assert.ErrorContains(t, err, "cannot convert gas price") }) } diff --git a/zetaclient/chains/evm/signer/signer.go b/zetaclient/chains/evm/signer/signer.go index f0f00aa237..9c928face1 100644 --- a/zetaclient/chains/evm/signer/signer.go +++ b/zetaclient/chains/evm/signer/signer.go @@ -362,6 +362,12 @@ func (signer *Signer) TryProcessOutbound( zetacoreClient interfaces.ZetacoreClient, height uint64, ) { + app, err := zctx.FromContext(ctx) + if err != nil { + signer.Logger().Std.Error().Err(err).Msg("error getting app context") + return + } + // end outbound process on panic defer func() { outboundProc.EndTryProcess(outboundID) @@ -399,6 +405,17 @@ func (signer *Signer) TryProcessOutbound( return } + toChain, err := app.GetChain(txData.toChainID.Int64()) + switch { + case err != nil: + logger.Error().Err(err).Msgf("error getting toChain %d", txData.toChainID.Int64()) + return + case toChain.IsZeta(): + // should not happen + logger.Error().Msgf("unable to TryProcessOutbound when toChain is zetaChain (%d)", toChain.ID()) + return + } + // https://github.com/zeta-chain/node/issues/2050 var tx *ethtypes.Transaction // compliance check goes first @@ -447,7 +464,7 @@ func (signer *Signer) TryProcessOutbound( logger.Info().Msgf( "SignWithdrawTx: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, - txData.toChainID.Int64(), + toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, txData.gasPrice, ) @@ -456,7 +473,7 @@ func (signer *Signer) TryProcessOutbound( logger.Info().Msgf( "SignERC20WithdrawTx: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, - txData.toChainID.Int64(), + toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, txData.gasPrice, ) @@ -465,7 +482,7 @@ func (signer *Signer) TryProcessOutbound( logger.Info().Msgf( "SignOutbound: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, - txData.toChainID.Int64(), + toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, txData.gasPrice, ) @@ -481,7 +498,7 @@ func (signer *Signer) TryProcessOutbound( logger.Info().Msgf( "SignRevertTx: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, - txData.toChainID.Int64(), cctx.GetCurrentOutboundParam().TssNonce, + toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, txData.gasPrice, ) txData.srcChainID = big.NewInt(cctx.OutboundParams[0].ReceiverChainId) @@ -491,7 +508,7 @@ func (signer *Signer) TryProcessOutbound( logger.Info().Msgf( "SignWithdrawTx: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, - txData.toChainID.Int64(), + toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, txData.gasPrice, ) @@ -499,7 +516,7 @@ func (signer *Signer) TryProcessOutbound( case coin.CoinType_ERC20: logger.Info().Msgf("SignERC20WithdrawTx: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, - txData.toChainID.Int64(), + toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, txData.gasPrice, ) @@ -513,7 +530,7 @@ func (signer *Signer) TryProcessOutbound( logger.Info().Msgf( "SignRevertTx: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, - txData.toChainID.Int64(), + toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, txData.gasPrice, ) @@ -529,7 +546,7 @@ func (signer *Signer) TryProcessOutbound( logger.Info().Msgf( "SignOutbound: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, - txData.toChainID.Int64(), + toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, txData.gasPrice, ) @@ -543,7 +560,7 @@ func (signer *Signer) TryProcessOutbound( logger.Info().Msgf( "Key-sign success: %d => %d, nonce %d", cctx.InboundParams.SenderChainId, - txData.toChainID.Int64(), + toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, ) @@ -567,14 +584,16 @@ func (signer *Signer) BroadcastOutbound( return } - // Get destination chain for logging - toChain, found := chains.GetChainFromChainID(txData.toChainID.Int64(), app.GetAdditionalChains()) - if !found { - logger.Warn().Msgf("BroadcastOutbound: unknown chain %d", txData.toChainID.Int64()) + toChain, err := app.GetChain(txData.toChainID.Int64()) + switch { + case err != nil: + logger.Error().Err(err).Msgf("error getting toChain %d", txData.toChainID.Int64()) return - } - - if tx == nil { + case toChain.IsZeta(): + // should not happen + logger.Error().Msgf("unable to broadcast when toChain is zetaChain (%d)", toChain.ID()) + return + case tx == nil: logger.Warn().Msgf("BroadcastOutbound: no tx to broadcast %s", cctx.Index) return } @@ -591,15 +610,15 @@ func (signer *Signer) BroadcastOutbound( log.Warn(). Err(err). Msgf("BroadcastOutbound: error broadcasting tx %s on chain %d nonce %d retry %d signer %s", - outboundHash, toChain.ChainId, cctx.GetCurrentOutboundParam().TssNonce, i, myID) + outboundHash, toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, i, myID) retry, report := zetacore.HandleBroadcastError( err, strconv.FormatUint(cctx.GetCurrentOutboundParam().TssNonce, 10), - toChain.String(), + fmt.Sprintf("%d", toChain.ID()), outboundHash, ) if report { - signer.reportToOutboundTracker(ctx, zetacoreClient, toChain.ChainId, tx.Nonce(), outboundHash, logger) + signer.reportToOutboundTracker(ctx, zetacoreClient, toChain.ID(), tx.Nonce(), outboundHash, logger) } if !retry { break @@ -608,8 +627,8 @@ func (signer *Signer) BroadcastOutbound( continue } logger.Info().Msgf("BroadcastOutbound: broadcasted tx %s on chain %d nonce %d signer %s", - outboundHash, toChain.ChainId, cctx.GetCurrentOutboundParam().TssNonce, myID) - signer.reportToOutboundTracker(ctx, zetacoreClient, toChain.ChainId, tx.Nonce(), outboundHash, logger) + outboundHash, toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, myID) + signer.reportToOutboundTracker(ctx, zetacoreClient, toChain.ID(), tx.Nonce(), outboundHash, logger) break // successful broadcast; no need to retry } } diff --git a/zetaclient/chains/evm/signer/signer_test.go b/zetaclient/chains/evm/signer/signer_test.go index b0cf3e5504..95da02e648 100644 --- a/zetaclient/chains/evm/signer/signer_test.go +++ b/zetaclient/chains/evm/signer/signer_test.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/rs/zerolog" "github.com/stretchr/testify/require" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" zctx "github.com/zeta-chain/zetacore/zetaclient/context" "github.com/zeta-chain/zetacore/zetaclient/db" "github.com/zeta-chain/zetacore/zetaclient/keys" @@ -161,7 +162,7 @@ func TestSigner_SetGetERC20CustodyAddress(t *testing.T) { } func TestSigner_TryProcessOutbound(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) evmSigner, err := getNewEvmSigner(nil) require.NoError(t, err) @@ -184,7 +185,7 @@ func TestSigner_TryProcessOutbound(t *testing.T) { } func TestSigner_SignOutbound(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) // Setup evm signer tss := mocks.NewTSSMainnet() @@ -221,7 +222,7 @@ func TestSigner_SignOutbound(t *testing.T) { } func TestSigner_SignRevertTx(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) // Setup evm signer tss := mocks.NewTSSMainnet() @@ -261,7 +262,7 @@ func TestSigner_SignRevertTx(t *testing.T) { } func TestSigner_SignCancelTx(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) // Setup evm signer tss := mocks.NewTSSMainnet() @@ -301,7 +302,7 @@ func TestSigner_SignCancelTx(t *testing.T) { } func TestSigner_SignWithdrawTx(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) // Setup evm signer tss := mocks.NewTSSMainnet() @@ -340,7 +341,7 @@ func TestSigner_SignWithdrawTx(t *testing.T) { } func TestSigner_SignCommandTx(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) // Setup evm signer evmSigner, err := getNewEvmSigner(nil) @@ -386,7 +387,7 @@ func TestSigner_SignCommandTx(t *testing.T) { } func TestSigner_SignERC20WithdrawTx(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) // Setup evm signer tss := mocks.NewTSSMainnet() @@ -427,7 +428,7 @@ func TestSigner_SignERC20WithdrawTx(t *testing.T) { } func TestSigner_BroadcastOutbound(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) // Setup evm signer evmSigner, err := getNewEvmSigner(nil) @@ -437,9 +438,10 @@ func TestSigner_BroadcastOutbound(t *testing.T) { cctx := getCCTX(t) mockObserver, err := getNewEvmChainObserver(t, nil) require.NoError(t, err) + txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, evmSigner.EvmClient(), zerolog.Logger{}, 123) - require.False(t, skip) require.NoError(t, err) + require.False(t, skip) t.Run("BroadcastOutbound - should successfully broadcast", func(t *testing.T) { // Call SignERC20WithdrawTx @@ -481,7 +483,7 @@ func TestSigner_SignerErrorMsg(t *testing.T) { } func TestSigner_SignWhitelistERC20Cmd(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) // Setup evm signer tss := mocks.NewTSSMainnet() @@ -490,11 +492,13 @@ func TestSigner_SignWhitelistERC20Cmd(t *testing.T) { // Setup txData struct cctx := getCCTX(t) + mockObserver, err := getNewEvmChainObserver(t, tss) require.NoError(t, err) + txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, evmSigner.EvmClient(), zerolog.Logger{}, 123) - require.False(t, skip) require.NoError(t, err) + require.False(t, skip) t.Run("SignWhitelistERC20Cmd - should successfully sign", func(t *testing.T) { // Call SignWhitelistERC20Cmd @@ -526,7 +530,7 @@ func TestSigner_SignWhitelistERC20Cmd(t *testing.T) { } func TestSigner_SignMigrateTssFundsCmd(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) // Setup evm signer tss := mocks.NewTSSMainnet() @@ -565,8 +569,20 @@ func TestSigner_SignMigrateTssFundsCmd(t *testing.T) { require.Nil(t, tx) }) } -func makeCtx() context.Context { +func makeCtx(t *testing.T) context.Context { app := zctx.New(config.New(false), zerolog.Nop()) + bscParams := mocks.MockChainParams(chains.BscMainnet.ChainId, 10) + + err := app.Update( + observertypes.Keygen{}, + []chains.Chain{chains.BscMainnet}, + nil, + map[int64]*observertypes.ChainParams{chains.BscMainnet.ChainId: &bscParams}, + "tssPubKey", + observertypes.CrosschainFlags{}, + ) + require.NoError(t, err, "unable to update app context") + return zctx.WithAppContext(context.Background(), app) } diff --git a/zetaclient/chains/interfaces/interfaces.go b/zetaclient/chains/interfaces/interfaces.go index 4ca93b7259..8377d2ba33 100644 --- a/zetaclient/chains/interfaces/interfaces.go +++ b/zetaclient/chains/interfaces/interfaces.go @@ -106,7 +106,7 @@ type ZetacoreClient interface { GetLogger() *zerolog.Logger GetKeys() keyinterfaces.ObserverKeys - GetKeyGen(ctx context.Context) (*observertypes.Keygen, error) + GetKeyGen(ctx context.Context) (observertypes.Keygen, error) GetBlockHeight(ctx context.Context) (int64, error) GetBlockHeaderChainState(ctx context.Context, chainID int64) (*lightclienttypes.ChainState, error) diff --git a/zetaclient/chains/solana/observer/inbound.go b/zetaclient/chains/solana/observer/inbound.go index 7c14ec34c6..cf05f94cf9 100644 --- a/zetaclient/chains/solana/observer/inbound.go +++ b/zetaclient/chains/solana/observer/inbound.go @@ -52,7 +52,7 @@ func (ob *Observer) WatchInbound(ctx context.Context) error { for { select { case <-ticker.C(): - if !app.IsInboundObservationEnabled(ob.GetChainParams()) { + if !app.IsInboundObservationEnabled() { sampledLogger.Info(). Msgf("WatchInbound: inbound observation is disabled for chain %d", ob.Chain().ChainId) continue diff --git a/zetaclient/chains/solana/observer/inbound_tracker.go b/zetaclient/chains/solana/observer/inbound_tracker.go index 7665359949..be5b6bf38d 100644 --- a/zetaclient/chains/solana/observer/inbound_tracker.go +++ b/zetaclient/chains/solana/observer/inbound_tracker.go @@ -33,7 +33,7 @@ func (ob *Observer) WatchInboundTracker(ctx context.Context) error { for { select { case <-ticker.C(): - if !app.IsInboundObservationEnabled(ob.GetChainParams()) { + if !app.IsInboundObservationEnabled() { continue } err := ob.ProcessInboundTrackers(ctx) diff --git a/zetaclient/chains/solana/observer/outbound.go b/zetaclient/chains/solana/observer/outbound.go index 59d93d2efb..6eca0b437e 100644 --- a/zetaclient/chains/solana/observer/outbound.go +++ b/zetaclient/chains/solana/observer/outbound.go @@ -47,7 +47,7 @@ func (ob *Observer) WatchOutbound(ctx context.Context) error { for { select { case <-ticker.C(): - if !app.IsOutboundObservationEnabled(ob.GetChainParams()) { + if !app.IsOutboundObservationEnabled() { sampledLogger.Info().Msgf("WatchOutbound: outbound observation is disabled for chain %d", chainID) continue } diff --git a/zetaclient/config/types.go b/zetaclient/config/types.go index 1ec58e12aa..31298b00a3 100644 --- a/zetaclient/config/types.go +++ b/zetaclient/config/types.go @@ -194,3 +194,7 @@ func (c Config) LoadSolanaPrivateKey() (solana.PrivateKey, error) { return privKey, nil } + +func (c EVMConfig) Empty() bool { + return c.Endpoint == "" && c.Chain.IsEmpty() +} diff --git a/zetaclient/context/app.go b/zetaclient/context/app.go index 187500e781..032d0b759c 100644 --- a/zetaclient/context/app.go +++ b/zetaclient/context/app.go @@ -3,37 +3,30 @@ package context import ( "fmt" - "sort" "sync" + "github.com/pkg/errors" "github.com/rs/zerolog" + "github.com/samber/lo" + "golang.org/x/exp/constraints" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" "github.com/zeta-chain/zetacore/pkg/chains" - lightclienttypes "github.com/zeta-chain/zetacore/x/lightclient/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/config" ) -// AppContext represents application context. +// AppContext represents application (zetaclient) context. type AppContext struct { config config.Config logger zerolog.Logger - keygen observertypes.Keygen - chainsEnabled []chains.Chain - evmChainParams map[int64]*observertypes.ChainParams - bitcoinChainParams *observertypes.ChainParams - solanaChainParams *observertypes.ChainParams - currentTssPubkey string - crosschainFlags observertypes.CrosschainFlags + chainRegistry *ChainRegistry - // additionalChains is a list of additional static chain information to use when searching from chain IDs - // it is stored in the protocol to dynamically support new chains without doing an upgrade - additionalChain []chains.Chain - - // blockHeaderEnabledChains is used to store the list of chains that have block header verification enabled - // All chains in this list will have Enabled flag set to true - blockHeaderEnabledChains []lightclienttypes.HeaderSupportedChain + currentTssPubKey string + crosschainFlags observertypes.CrosschainFlags + keygen observertypes.Keygen mu sync.RWMutex } @@ -44,16 +37,13 @@ func New(cfg config.Config, logger zerolog.Logger) *AppContext { config: cfg, logger: logger.With().Str("module", "appcontext").Logger(), - chainsEnabled: []chains.Chain{}, - evmChainParams: map[int64]*observertypes.ChainParams{}, - bitcoinChainParams: nil, - solanaChainParams: nil, - crosschainFlags: observertypes.CrosschainFlags{}, - blockHeaderEnabledChains: []lightclienttypes.HeaderSupportedChain{}, + chainRegistry: NewChainRegistry(), - currentTssPubkey: "", + crosschainFlags: observertypes.CrosschainFlags{}, + currentTssPubKey: "", keygen: observertypes.Keygen{}, - mu: sync.RWMutex{}, + + mu: sync.RWMutex{}, } } @@ -62,43 +52,45 @@ func (a *AppContext) Config() config.Config { return a.config } -// GetBTCChainAndConfig returns btc chain and config if enabled -func (a *AppContext) GetBTCChainAndConfig() (chains.Chain, config.BTCConfig, bool) { - cfg, configEnabled := a.Config().GetBTCConfig() - if !configEnabled { - return chains.Chain{}, config.BTCConfig{}, false - } +// GetChain returns the chain by ID. +func (a *AppContext) GetChain(chainID int64) (Chain, error) { + return a.chainRegistry.Get(chainID) +} - chain, _, paramsEnabled := a.GetBTCChainParams() - if !paramsEnabled { - return chains.Chain{}, config.BTCConfig{}, false - } +// ListChainIDs returns the list of existing chain ids in the registry. +func (a *AppContext) ListChainIDs() []int64 { + return a.chainRegistry.ChainIDs() +} - return chain, cfg, true +// ListChains returns the list of existing chains in the registry. +func (a *AppContext) ListChains() []Chain { + return a.chainRegistry.All() } -// GetSolanaChainAndConfig returns solana chain and config if enabled -func (a *AppContext) GetSolanaChainAndConfig() (chains.Chain, config.SolanaConfig, bool) { - solConfig, configEnabled := a.Config().GetSolanaConfig() - solChain, _, paramsEnabled := a.GetSolanaChainParams() +// FilterChains returns the list of chains that satisfy the filter +func (a *AppContext) FilterChains(filter func(Chain) bool) []Chain { + var ( + all = a.ListChains() + out = make([]Chain, 0, len(all)) + ) - if !configEnabled || !paramsEnabled { - return chains.Chain{}, config.SolanaConfig{}, false + for _, chain := range all { + if filter(chain) { + out = append(out, chain) + } } - return solChain, solConfig, true + return out } -// IsOutboundObservationEnabled returns true if the chain is supported and outbound flag is enabled -func (a *AppContext) IsOutboundObservationEnabled(chainParams observertypes.ChainParams) bool { - flags := a.GetCrossChainFlags() - return chainParams.IsSupported && flags.IsOutboundEnabled +// IsOutboundObservationEnabled returns true if outbound flag is enabled +func (a *AppContext) IsOutboundObservationEnabled() bool { + return a.GetCrossChainFlags().IsOutboundEnabled } -// IsInboundObservationEnabled returns true if the chain is supported and inbound flag is enabled -func (a *AppContext) IsInboundObservationEnabled(chainParams observertypes.ChainParams) bool { - flags := a.GetCrossChainFlags() - return chainParams.IsSupported && flags.IsInboundEnabled +// IsInboundObservationEnabled returns true if inbound flag is enabled +func (a *AppContext) IsInboundObservationEnabled() bool { + return a.GetCrossChainFlags().IsInboundEnabled } // GetKeygen returns the current keygen @@ -106,237 +98,188 @@ func (a *AppContext) GetKeygen() observertypes.Keygen { a.mu.RLock() defer a.mu.RUnlock() - var copiedPubkeys []string + var copiedPubKeys []string if a.keygen.GranteePubkeys != nil { - copiedPubkeys = make([]string, len(a.keygen.GranteePubkeys)) - copy(copiedPubkeys, a.keygen.GranteePubkeys) + copiedPubKeys = make([]string, len(a.keygen.GranteePubkeys)) + copy(copiedPubKeys, a.keygen.GranteePubkeys) } return observertypes.Keygen{ Status: a.keygen.Status, - GranteePubkeys: copiedPubkeys, + GranteePubkeys: copiedPubKeys, BlockNumber: a.keygen.BlockNumber, } } -// GetCurrentTssPubKey returns the current tss pubkey +// GetCurrentTssPubKey returns the current tss pubKey. func (a *AppContext) GetCurrentTssPubKey() string { a.mu.RLock() defer a.mu.RUnlock() - return a.currentTssPubkey + return a.currentTssPubKey } -// GetEnabledChains returns all enabled chains including zetachain -func (a *AppContext) GetEnabledChains() []chains.Chain { +// GetCrossChainFlags returns crosschain flags +func (a *AppContext) GetCrossChainFlags() observertypes.CrosschainFlags { a.mu.RLock() defer a.mu.RUnlock() - copiedChains := make([]chains.Chain, len(a.chainsEnabled)) - copy(copiedChains, a.chainsEnabled) - - return copiedChains + return a.crosschainFlags } -// GetEnabledExternalChains returns all enabled external chains -func (a *AppContext) GetEnabledExternalChains() []chains.Chain { - a.mu.RLock() - defer a.mu.RUnlock() - - externalChains := make([]chains.Chain, 0) - for _, chain := range a.chainsEnabled { - if chain.IsExternal { - externalChains = append(externalChains, chain) +// Update updates AppContext and params for all chains +// this must be the ONLY function that writes to AppContext +func (a *AppContext) Update( + keygen observertypes.Keygen, + freshChains, additionalChains []chains.Chain, + freshChainParams map[int64]*observertypes.ChainParams, + tssPubKey string, + crosschainFlags observertypes.CrosschainFlags, +) error { + // some sanity checks + switch { + case len(freshChains) == 0: + return fmt.Errorf("no chains present") + case len(freshChainParams) == 0: + return fmt.Errorf("no chain params present") + case tssPubKey == "" && a.currentTssPubKey != "": + // note that if we're doing a fresh start, we ALLOW an empty tssPubKey + return fmt.Errorf("tss pubkey is empty") + case len(additionalChains) > 0: + for _, c := range additionalChains { + if !c.IsExternal { + return fmt.Errorf("additional chain %d is not external", c.ChainId) + } } } - return externalChains -} - -// GetEVMChainParams returns chain params for a specific EVM chain -func (a *AppContext) GetEVMChainParams(chainID int64) (*observertypes.ChainParams, bool) { - a.mu.RLock() - defer a.mu.RUnlock() - - evmChainParams, found := a.evmChainParams[chainID] - return evmChainParams, found -} - -// GetAllEVMChainParams returns all chain params for EVM chains -func (a *AppContext) GetAllEVMChainParams() map[int64]*observertypes.ChainParams { - a.mu.RLock() - defer a.mu.RUnlock() - - // deep copy evm chain params - copied := make(map[int64]*observertypes.ChainParams, len(a.evmChainParams)) - for chainID, evmConfig := range a.evmChainParams { - copied[chainID] = &observertypes.ChainParams{} - *copied[chainID] = *evmConfig - } - return copied -} - -// GetBTCChainParams returns (chain, chain params, found) for bitcoin chain -func (a *AppContext) GetBTCChainParams() (chains.Chain, *observertypes.ChainParams, bool) { - a.mu.RLock() - defer a.mu.RUnlock() - // bitcoin is not enabled - if a.bitcoinChainParams == nil { - return chains.Chain{}, nil, false + err := a.updateChainRegistry(freshChains, additionalChains, freshChainParams) + if err != nil { + return errors.Wrap(err, "unable to update chain registry") } - chain, found := chains.GetChainFromChainID(a.bitcoinChainParams.ChainId, a.additionalChain) - if !found { - return chains.Chain{}, nil, false - } - - return chain, a.bitcoinChainParams, true -} - -// GetSolanaChainParams returns (chain, chain params, found) for solana chain -func (a *AppContext) GetSolanaChainParams() (chains.Chain, *observertypes.ChainParams, bool) { - a.mu.RLock() - defer a.mu.RUnlock() - - // solana is not enabled - if a.solanaChainParams == nil { - return chains.Chain{}, nil, false - } - - chain, found := chains.GetChainFromChainID(a.solanaChainParams.ChainId, a.additionalChain) - if !found { - fmt.Printf("solana Chain %d not found", a.solanaChainParams.ChainId) - return chains.Chain{}, nil, false - } - - return chain, a.solanaChainParams, true -} - -// GetCrossChainFlags returns crosschain flags -func (a *AppContext) GetCrossChainFlags() observertypes.CrosschainFlags { - a.mu.RLock() - defer a.mu.RUnlock() + a.mu.Lock() + defer a.mu.Unlock() - return a.crosschainFlags -} + a.crosschainFlags = crosschainFlags + a.keygen = keygen + a.currentTssPubKey = tssPubKey -// GetAdditionalChains returns additional chains -func (a *AppContext) GetAdditionalChains() []chains.Chain { - a.mu.RLock() - defer a.mu.RUnlock() - return a.additionalChain + return nil } -// GetAllHeaderEnabledChains returns all verification flags -func (a *AppContext) GetAllHeaderEnabledChains() []lightclienttypes.HeaderSupportedChain { - a.mu.RLock() - defer a.mu.RUnlock() +// updateChainRegistry updates the chain registry with fresh chains and chain params. +// Note that there's an edge-case for ZetaChain itself because we WANT to have it in chains list, +// but it doesn't have chain params. +func (a *AppContext) updateChainRegistry( + freshChains []chains.Chain, + additionalChains []chains.Chain, + freshChainParams map[int64]*observertypes.ChainParams, +) error { + var zetaChainID int64 - return a.blockHeaderEnabledChains -} + // 1. build map[chainId]Chain + freshChainsByID := make(map[int64]chains.Chain, len(freshChains)+len(additionalChains)) + for _, c := range freshChains { + freshChainsByID[c.ChainId] = c -// GetBlockHeaderEnabledChains checks if block header verification is enabled for a specific chain -func (a *AppContext) GetBlockHeaderEnabledChains(chainID int64) (lightclienttypes.HeaderSupportedChain, bool) { - a.mu.RLock() - defer a.mu.RUnlock() - - for _, flags := range a.blockHeaderEnabledChains { - if flags.ChainId == chainID { - return flags, true + if isZeta(c.ChainId) && zetaChainID == 0 { + zetaChainID = c.ChainId } } - return lightclienttypes.HeaderSupportedChain{}, false -} + for _, c := range additionalChains { + // shouldn't happen, but just in case + if _, found := freshChainsByID[c.ChainId]; found { + continue + } -// Update updates AppContext and params for all chains -// this must be the ONLY function that writes to AppContext -func (a *AppContext) Update( - keygen *observertypes.Keygen, - newChains []chains.Chain, - evmChainParams map[int64]*observertypes.ChainParams, - btcChainParams *observertypes.ChainParams, - solChainParams *observertypes.ChainParams, - tssPubKey string, - crosschainFlags observertypes.CrosschainFlags, - additionalChains []chains.Chain, - blockHeaderEnabledChains []lightclienttypes.HeaderSupportedChain, - init bool, -) { - if len(newChains) == 0 { - a.logger.Warn().Msg("UpdateChainParams: No chains enabled in ZeroCore") + freshChainsByID[c.ChainId] = c } - // Ignore whatever order zetacore organizes chain list in state - sort.SliceStable(newChains, func(i, j int) bool { - return newChains[i].ChainId < newChains[j].ChainId - }) - - a.mu.Lock() - defer a.mu.Unlock() + var ( + freshChainIDs = maps.Keys(freshChainsByID) + existingChainIDs = a.chainRegistry.ChainIDs() + ) - // Add some warnings if chain list changes at runtime - if !init && !chainsEqual(a.chainsEnabled, newChains) { + // 2. Compare existing chains with fresh ones + if len(existingChainIDs) > 0 && !elementsMatch(existingChainIDs, freshChainIDs) { a.logger.Warn(). - Interface("chains.current", a.chainsEnabled). - Interface("chains.new", newChains). - Msg("ChainsEnabled changed at runtime!") + Ints64("chains.current", existingChainIDs). + Ints64("chains.new", freshChainIDs). + Msg("Chain list changed at the runtime!") } - if keygen != nil { - a.keygen = *keygen + // Log warn if somehow chain doesn't chainParam + for _, chainID := range freshChainIDs { + if _, ok := freshChainParams[chainID]; !ok && !isZeta(chainID) { + a.logger.Warn(). + Int64("chain.id", chainID). + Msg("Chain doesn't have according ChainParams present. Skipping.") + } } - a.chainsEnabled = newChains - a.crosschainFlags = crosschainFlags - a.additionalChain = additionalChains - a.blockHeaderEnabledChains = blockHeaderEnabledChains - - // update core params for evm chains we have configs in file - freshEvmChainParams := make(map[int64]*observertypes.ChainParams) - for _, cp := range evmChainParams { - _, found := a.config.EVMChainConfigs[cp.ChainId] - if !found { - a.logger.Warn(). - Int64("chain.id", cp.ChainId). - Msg("Encountered EVM ChainParams that are not present in the config file") + // 3. If we have zeta chain, we want to force "fake" chainParams for it + if zetaChainID != 0 { + freshChainParams[zetaChainID] = zetaObserverChainParams(zetaChainID) + } - continue + // 3. Update chain registry + // okay, let's update the chains. + // Set() ensures that chain, chainID, and params are consistent and chain is not zeta + chain is supported + for chainID, params := range freshChainParams { + chain, ok := freshChainsByID[chainID] + if !ok { + return fmt.Errorf("unable to locate fresh chain %d based on chain params", chainID) } - if chains.IsZetaChain(cp.ChainId, nil) { - continue + if !isZeta(chainID) { + if err := observertypes.ValidateChainParams(params); err != nil { + return errors.Wrapf(err, "invalid chain params for chain %d", chainID) + } } - freshEvmChainParams[cp.ChainId] = cp + if err := a.chainRegistry.Set(chainID, &chain, params); err != nil { + return errors.Wrap(err, "unable to set chain in the registry") + } } - a.evmChainParams = freshEvmChainParams + a.chainRegistry.SetAdditionalChains(additionalChains) - // update chain params for bitcoin if it has config in file - if btcChainParams != nil { - a.bitcoinChainParams = btcChainParams - } + toBeDeleted, _ := lo.Difference(existingChainIDs, freshChainIDs) + if len(toBeDeleted) > 0 { + a.logger.Warn(). + Ints64("chains.deleted", toBeDeleted). + Msg("Deleting chains that are no longer relevant") - // update chain params for solana if it has config in file - if solChainParams != nil { - a.solanaChainParams = solChainParams + a.chainRegistry.Delete(toBeDeleted...) } - if tssPubKey != "" { - a.currentTssPubkey = tssPubKey - } + return nil +} + +func isZeta(chainID int64) bool { + return chains.IsZetaChain(chainID, nil) +} + +// zetaObserverChainParams returns "fake" chain params because +// actually chainParams is a concept of observer +func zetaObserverChainParams(chainID int64) *observertypes.ChainParams { + return &observertypes.ChainParams{ChainId: chainID, IsSupported: true} } -func chainsEqual(a []chains.Chain, b []chains.Chain) bool { +// elementsMatch returns true if two slices are equal. +// SORTS the slices before comparison. +func elementsMatch[T constraints.Ordered](a, b []T) bool { if len(a) != len(b) { return false } - for i, left := range a { - right := b[i] + slices.Sort(a) + slices.Sort(b) - if left.ChainId != right.ChainId { + for i := range a { + if a[i] != b[i] { return false } } diff --git a/zetaclient/context/app_test.go b/zetaclient/context/app_test.go index e591fac1d8..0dd8e2daed 100644 --- a/zetaclient/context/app_test.go +++ b/zetaclient/context/app_test.go @@ -1,4 +1,4 @@ -package context_test +package context import ( "testing" @@ -6,572 +6,240 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" - "github.com/zeta-chain/zetacore/pkg/chains" - "github.com/zeta-chain/zetacore/testutil/sample" - lightclienttypes "github.com/zeta-chain/zetacore/x/lightclient/types" - observertypes "github.com/zeta-chain/zetacore/x/observer/types" + "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/config" - "github.com/zeta-chain/zetacore/zetaclient/context" + "golang.org/x/exp/maps" ) -func TestNew(t *testing.T) { +func TestAppContext(t *testing.T) { var ( testCfg = config.New(false) - logger = zerolog.Nop() - ) - - t.Run("should create new AppContext with empty config", func(t *testing.T) { - appContext := context.New(testCfg, logger) - require.NotNil(t, appContext) - - // assert keygen - keyGen := appContext.GetKeygen() - require.Equal(t, observertypes.Keygen{}, keyGen) - - // assert enabled chains - require.Empty(t, len(appContext.GetEnabledChains())) - - // assert external chains - require.Empty(t, len(appContext.GetEnabledExternalChains())) - - // assert current tss pubkey - require.Equal(t, "", appContext.GetCurrentTssPubKey()) + logger = zerolog.New(zerolog.NewTestWriter(t)) - // assert btc chain params - chain, btcChainParams, btcChainParamsFound := appContext.GetBTCChainParams() - require.Equal(t, chains.Chain{}, chain) - require.False(t, btcChainParamsFound) - require.Nil(t, btcChainParams) - - // assert evm chain params - allEVMChainParams := appContext.GetAllEVMChainParams() - require.Empty(t, allEVMChainParams) - }) - - t.Run("should return nil chain params if chain id is not found", func(t *testing.T) { - // create config with btc config - testCfg := config.New(false) - testCfg.BitcoinConfig = config.BTCConfig{ - RPCUsername: "test_user", - RPCPassword: "test_password", + keyGen = types.Keygen{ + Status: types.KeygenStatus_KeyGenSuccess, + GranteePubkeys: []string{"testPubKey1"}, + BlockNumber: 123, } + ccFlags = types.CrosschainFlags{ + IsInboundEnabled: true, + IsOutboundEnabled: true, + GasPriceIncreaseFlags: nil, + } + ttsPubKey = "tssPubKeyTest" + ) - // create AppContext with 0 chain id - appContext := context.New(testCfg, logger) - require.NotNil(t, appContext) + testCfg.BitcoinConfig.RPCUsername = "abc" - // assert btc chain params - chain, btcChainParams, btcChainParamsFound := appContext.GetBTCChainParams() - require.Equal(t, chains.Chain{}, chain) - require.False(t, btcChainParamsFound) - require.Nil(t, btcChainParams) - }) + ethParams := types.GetDefaultEthMainnetChainParams() + ethParams.IsSupported = true - t.Run("should create new AppContext with config containing evm chain params", func(t *testing.T) { - // ARRANGE - var ( - eth = chains.Ethereum.ChainId - matic = chains.Polygon.ChainId + btcParams := types.GetDefaultBtcMainnetChainParams() + btcParams.IsSupported = true - testCfg = config.New(false) + solParams := types.GetDefaultSolanaLocalnetChainParams() + solParams.IsSupported = true - ethChainParams = mocks.MockChainParams(eth, 200) - maticChainParams = mocks.MockChainParams(matic, 333) - ) + fancyL2 := chains.Chain{ + ChainId: 123, + Network: 0, + NetworkType: chains.NetworkType_mainnet, + Vm: chains.Vm_evm, + Consensus: chains.Consensus_ethereum, + IsExternal: true, + CctxGateway: 1, + } - // Given config with evm chain params (e.g. from a file) - testCfg.EVMChainConfigs = map[int64]config.EVMConfig{ - eth: {Chain: chains.Ethereum}, - matic: {Chain: chains.Polygon}, + fancyL2Params := types.GetDefaultEthMainnetChainParams() + fancyL2Params.ChainId = fancyL2.ChainId + fancyL2Params.IsSupported = true + + t.Run("Update", func(t *testing.T) { + // Given AppContext + appContext := New(testCfg, logger) + + // With expected default behavior + _, err := appContext.GetChain(123) + require.ErrorIs(t, err, ErrChainNotFound) + + require.Equal(t, testCfg, appContext.Config()) + require.Empty(t, appContext.GetKeygen()) + require.Empty(t, appContext.GetCurrentTssPubKey()) + require.Empty(t, appContext.GetCrossChainFlags()) + require.False(t, appContext.IsInboundObservationEnabled()) + require.False(t, appContext.IsOutboundObservationEnabled()) + + // Given some data that is supposed to come from ZetaCore RPC + newChains := []chains.Chain{ + chains.Ethereum, + chains.BitcoinMainnet, + chains.SolanaLocalnet, } - // And chain params from zetacore - chainParams := map[int64]*observertypes.ChainParams{ - eth: ðChainParams, - matic: &maticChainParams, + chainParams := map[int64]*types.ChainParams{ + chains.Ethereum.ChainId: ethParams, + chains.BitcoinMainnet.ChainId: btcParams, + chains.SolanaLocalnet.ChainId: solParams, + fancyL2.ChainId: fancyL2Params, } - // Given app context - appContext := context.New(testCfg, logger) - - // That was updated with chain params - appContext.Update(nil, nil, chainParams, nil, nil, "", observertypes.CrosschainFlags{}, nil, nil, false) - - // assert evm chain params - allEVMChainParams := appContext.GetAllEVMChainParams() - require.Equal(t, 2, len(allEVMChainParams)) - require.Equal(t, ðChainParams, allEVMChainParams[eth]) - require.Equal(t, &maticChainParams, allEVMChainParams[matic]) - - evmChainParams1, found := appContext.GetEVMChainParams(eth) - require.True(t, found) - require.Equal(t, ðChainParams, evmChainParams1) - - evmChainParams2, found := appContext.GetEVMChainParams(matic) - require.True(t, found) - require.Equal(t, &maticChainParams, evmChainParams2) - }) - - t.Run("should create new AppContext with config containing btc config", func(t *testing.T) { - testCfg := config.New(false) - testCfg.BitcoinConfig = config.BTCConfig{ - RPCUsername: "test username", - RPCPassword: "test password", - RPCHost: "test host", - RPCParams: "test params", + additionalChains := []chains.Chain{ + fancyL2, } - appContext := context.New(testCfg, logger) - require.NotNil(t, appContext) - }) -} -func TestAppContextUpdate(t *testing.T) { - var ( - testCfg = config.New(false) - logger = zerolog.Nop() - ) + // ACT + err = appContext.Update(keyGen, newChains, additionalChains, chainParams, ttsPubKey, ccFlags) + + // ASSERT + require.NoError(t, err) + + // Check getters + assert.Equal(t, testCfg, appContext.Config()) + assert.Equal(t, keyGen, appContext.GetKeygen()) + assert.Equal(t, ttsPubKey, appContext.GetCurrentTssPubKey()) + assert.Equal(t, ccFlags, appContext.GetCrossChainFlags()) + assert.True(t, appContext.IsInboundObservationEnabled()) + assert.True(t, appContext.IsOutboundObservationEnabled()) + + // Check ETH Chain + ethChain, err := appContext.GetChain(1) + assert.NoError(t, err) + assert.True(t, ethChain.IsEVM()) + assert.False(t, ethChain.IsUTXO()) + assert.False(t, ethChain.IsSolana()) + assert.Equal(t, ethParams, ethChain.Params()) + + // Check that fancyL2 chain is added as well + fancyL2Chain, err := appContext.GetChain(fancyL2.ChainId) + assert.NoError(t, err) + assert.True(t, fancyL2Chain.IsEVM()) + assert.Equal(t, fancyL2Params, fancyL2Chain.Params()) + + // Check chain IDs + expectedIDs := []int64{ethParams.ChainId, btcParams.ChainId, solParams.ChainId, fancyL2.ChainId} + assert.ElementsMatch(t, expectedIDs, appContext.ListChainIDs()) + + // Check config + assert.Equal(t, "abc", appContext.Config().BitcoinConfig.RPCUsername) + + t.Run("edge-cases", func(t *testing.T) { + for _, tt := range []struct { + name string + act func(*AppContext) error + assert func(*testing.T, *AppContext, error) + }{ + { + name: "update with empty chains results in an error", + act: func(a *AppContext) error { + return appContext.Update(keyGen, newChains, nil, nil, ttsPubKey, ccFlags) + }, + assert: func(t *testing.T, a *AppContext, err error) { + assert.ErrorContains(t, err, "no chain params present") + }, + }, + { + name: "trying to add non-supported chain results in an error", + act: func(a *AppContext) error { + // ASSERT + // GIven Optimism chain params from ZetaCore, but it's not supported YET + op := chains.OptimismMainnet + opParams := types.GetDefaultEthMainnetChainParams() + opParams.ChainId = op.ChainId + opParams.IsSupported = false - t.Run("should update AppContext after being created from empty config", func(t *testing.T) { - appContext := context.New(testCfg, logger) - require.NotNil(t, appContext) + chainsWithOpt := append(newChains, op) - keyGenToUpdate := observertypes.Keygen{ - Status: observertypes.KeygenStatus_KeyGenSuccess, - GranteePubkeys: []string{"testpubkey1"}, - } - enabledChainsToUpdate := []chains.Chain{ - { - ChainId: 1, - IsExternal: true, - }, - { - ChainId: 2, - IsExternal: true, - }, - chains.ZetaChainTestnet, - } - evmChainParamsToUpdate := map[int64]*observertypes.ChainParams{ - 1: { - ChainId: 1, - }, - 2: { - ChainId: 2, - }, - } - btcChainParamsToUpdate := &observertypes.ChainParams{ - ChainId: 3, - } - tssPubKeyToUpdate := "tsspubkeytest" - crosschainFlags := sample.CrosschainFlags() - verificationFlags := sample.HeaderSupportedChains() - - require.NotNil(t, crosschainFlags) - appContext.Update( - &keyGenToUpdate, - enabledChainsToUpdate, - evmChainParamsToUpdate, - btcChainParamsToUpdate, - nil, - tssPubKeyToUpdate, - *crosschainFlags, - []chains.Chain{}, - verificationFlags, - false, - ) - - // assert keygen updated - keyGen := appContext.GetKeygen() - require.Equal(t, keyGenToUpdate, keyGen) - - // assert enabled chains updated - require.Equal(t, enabledChainsToUpdate, appContext.GetEnabledChains()) - - // assert enabled external chains - require.Equal(t, enabledChainsToUpdate[0:2], appContext.GetEnabledExternalChains()) - - // assert current tss pubkey updated - require.Equal(t, tssPubKeyToUpdate, appContext.GetCurrentTssPubKey()) - - // assert btc chain params still empty because they were not specified in config - chain, btcChainParams, btcChainParamsFound := appContext.GetBTCChainParams() - require.Equal(t, chains.Chain{}, chain) - require.False(t, btcChainParamsFound) - require.Nil(t, btcChainParams) - - // assert evm chain params still empty because they were not specified in config - allEVMChainParams := appContext.GetAllEVMChainParams() - require.Empty(t, allEVMChainParams) - - ccFlags := appContext.GetCrossChainFlags() - require.Equal(t, *crosschainFlags, ccFlags) - - verFlags := appContext.GetAllHeaderEnabledChains() - require.Equal(t, verificationFlags, verFlags) - }) + chainParamsWithOpt := maps.Clone(chainParams) + chainParamsWithOpt[opParams.ChainId] = opParams - t.Run( - "should update AppContext after being created from config with evm and btc chain params", - func(t *testing.T) { - testCfg := config.New(false) - testCfg.EVMChainConfigs = map[int64]config.EVMConfig{ - 1: { - Chain: chains.Chain{ - ChainId: 1, + return a.Update(keyGen, chainsWithOpt, additionalChains, chainParamsWithOpt, ttsPubKey, ccFlags) }, - }, - 2: { - Chain: chains.Chain{ - ChainId: 2, + assert: func(t *testing.T, a *AppContext, err error) { + assert.ErrorIs(t, err, ErrChainNotSupported) + mustBeNotFound(t, a, chains.OptimismMainnet.ChainId) }, }, - } - testCfg.BitcoinConfig = config.BTCConfig{ - RPCUsername: "test username", - RPCPassword: "test password", - RPCHost: "test host", - RPCParams: "test params", - } - - appContext := context.New(testCfg, logger) - require.NotNil(t, appContext) - - keyGenToUpdate := observertypes.Keygen{ - Status: observertypes.KeygenStatus_KeyGenSuccess, - GranteePubkeys: []string{"testpubkey1"}, - } - enabledChainsToUpdate := []chains.Chain{ { - ChainId: 1, + name: "trying to add zeta chain without chain params is allowed", + act: func(a *AppContext) error { + chainsWithZeta := append(newChains, chains.ZetaChainMainnet) + return a.Update(keyGen, chainsWithZeta, additionalChains, chainParams, ttsPubKey, ccFlags) + }, + assert: func(t *testing.T, a *AppContext, err error) { + assert.NoError(t, err) + + zc := mustBePresent(t, a, chains.ZetaChainMainnet.ChainId) + assert.True(t, zc.IsZeta()) + }, }, { - ChainId: 2, - }, - } - evmChainParamsToUpdate := map[int64]*observertypes.ChainParams{ - 1: { - ChainId: 1, - }, - 2: { - ChainId: 2, - }, - } + name: "trying to add zetachain with chain params is allowed but forces fake params", + act: func(a *AppContext) error { + zetaParams := types.GetDefaultZetaPrivnetChainParams() + zetaParams.ChainId = chains.ZetaChainMainnet.ChainId + zetaParams.IsSupported = true + zetaParams.GatewayAddress = "ABC123" - testBtcChain := chains.BitcoinTestnet - btcChainParamsToUpdate := &observertypes.ChainParams{ - ChainId: testBtcChain.ChainId, - } - tssPubKeyToUpdate := "tsspubkeytest" - crosschainFlags := sample.CrosschainFlags() - verificationFlags := sample.HeaderSupportedChains() - require.NotNil(t, crosschainFlags) - appContext.Update( - &keyGenToUpdate, - enabledChainsToUpdate, - evmChainParamsToUpdate, - btcChainParamsToUpdate, - nil, - tssPubKeyToUpdate, - *crosschainFlags, - []chains.Chain{}, - verificationFlags, - false, - ) - - // assert keygen updated - keyGen := appContext.GetKeygen() - require.Equal(t, keyGenToUpdate, keyGen) - - // assert enabled chains updated - require.Equal(t, enabledChainsToUpdate, appContext.GetEnabledChains()) - - // assert current tss pubkey updated - require.Equal(t, tssPubKeyToUpdate, appContext.GetCurrentTssPubKey()) - - // assert btc chain params - chain, btcChainParams, btcChainParamsFound := appContext.GetBTCChainParams() - require.Equal(t, testBtcChain, chain) - require.True(t, btcChainParamsFound) - require.Equal(t, btcChainParamsToUpdate, btcChainParams) - - // assert evm chain params - allEVMChainParams := appContext.GetAllEVMChainParams() - require.Equal(t, evmChainParamsToUpdate, allEVMChainParams) - - evmChainParams1, found := appContext.GetEVMChainParams(1) - require.True(t, found) - require.Equal(t, evmChainParamsToUpdate[1], evmChainParams1) - - evmChainParams2, found := appContext.GetEVMChainParams(2) - require.True(t, found) - require.Equal(t, evmChainParamsToUpdate[2], evmChainParams2) - - ccFlags := appContext.GetCrossChainFlags() - require.Equal(t, ccFlags, *crosschainFlags) - - verFlags := appContext.GetAllHeaderEnabledChains() - require.Equal(t, verFlags, verificationFlags) - }, - ) -} + chainParamsWithZeta := maps.Clone(chainParams) + chainParamsWithZeta[zetaParams.ChainId] = zetaParams -func TestIsOutboundObservationEnabled(t *testing.T) { - // create test chain params and flags - evmChain := chains.Ethereum - ccFlags := *sample.CrosschainFlags() - verificationFlags := sample.HeaderSupportedChains() - chainParams := &observertypes.ChainParams{ - ChainId: evmChain.ChainId, - IsSupported: true, - } - - t.Run("should return true if chain is supported and outbound flag is enabled", func(t *testing.T) { - appContext := makeAppContext(evmChain, chainParams, ccFlags, verificationFlags) - - require.True(t, appContext.IsOutboundObservationEnabled(*chainParams)) - }) - t.Run("should return false if chain is not supported yet", func(t *testing.T) { - paramsUnsupported := &observertypes.ChainParams{ChainId: evmChain.ChainId, IsSupported: false} - appContextUnsupported := makeAppContext(evmChain, paramsUnsupported, ccFlags, verificationFlags) - - require.False(t, appContextUnsupported.IsOutboundObservationEnabled(*paramsUnsupported)) - }) - t.Run("should return false if outbound flag is disabled", func(t *testing.T) { - flagsDisabled := ccFlags - flagsDisabled.IsOutboundEnabled = false - appContextDisabled := makeAppContext(evmChain, chainParams, flagsDisabled, verificationFlags) - - require.False(t, appContextDisabled.IsOutboundObservationEnabled(*chainParams)) - }) -} - -func TestIsInboundObservationEnabled(t *testing.T) { - // create test chain params and flags - evmChain := chains.Ethereum - ccFlags := *sample.CrosschainFlags() - verificationFlags := sample.HeaderSupportedChains() - chainParams := &observertypes.ChainParams{ - ChainId: evmChain.ChainId, - IsSupported: true, - } - - t.Run("should return true if chain is supported and inbound flag is enabled", func(t *testing.T) { - appContext := makeAppContext(evmChain, chainParams, ccFlags, verificationFlags) - - require.True(t, appContext.IsInboundObservationEnabled(*chainParams)) - }) - - t.Run("should return false if chain is not supported yet", func(t *testing.T) { - paramsUnsupported := &observertypes.ChainParams{ChainId: evmChain.ChainId, IsSupported: false} - appContextUnsupported := makeAppContext(evmChain, paramsUnsupported, ccFlags, verificationFlags) - - require.False(t, appContextUnsupported.IsInboundObservationEnabled(*paramsUnsupported)) - }) + chainsWithZeta := append(newChains, chains.ZetaChainMainnet) - t.Run("should return false if inbound flag is disabled", func(t *testing.T) { - flagsDisabled := ccFlags - flagsDisabled.IsInboundEnabled = false - appContextDisabled := makeAppContext(evmChain, chainParams, flagsDisabled, verificationFlags) - - require.False(t, appContextDisabled.IsInboundObservationEnabled(*chainParams)) - }) -} - -func TestGetBTCChainAndConfig(t *testing.T) { - logger := zerolog.Nop() - - emptyConfig := config.New(false) - nonEmptyConfig := config.New(true) - - assertEmpty := func(t *testing.T, chain chains.Chain, btcConfig config.BTCConfig, enabled bool) { - assert.Empty(t, chain) - assert.Empty(t, btcConfig) - assert.False(t, enabled) - } + return a.Update(keyGen, chainsWithZeta, additionalChains, chainParamsWithZeta, ttsPubKey, ccFlags) + }, + assert: func(t *testing.T, a *AppContext, err error) { + assert.NoError(t, err) - for _, tt := range []struct { - name string - cfg config.Config - setup func(app *context.AppContext) - assert func(t *testing.T, chain chains.Chain, btcConfig config.BTCConfig, enabled bool) - }{ - { - name: "no btc config", - cfg: emptyConfig, - setup: nil, - assert: assertEmpty, - }, - { - name: "btc config exists, but not chain params are set", - cfg: nonEmptyConfig, - setup: nil, - assert: assertEmpty, - }, - { - name: "btc config exists but chain is invalid", - cfg: nonEmptyConfig, - setup: func(app *context.AppContext) { - app.Update( - &observertypes.Keygen{}, - []chains.Chain{}, - nil, - &observertypes.ChainParams{ChainId: 123}, - nil, - "", - observertypes.CrosschainFlags{}, - []chains.Chain{}, - nil, - true, - ) - }, - assert: assertEmpty, - }, - { - name: "btc config exists and chain params are set", - cfg: nonEmptyConfig, - setup: func(app *context.AppContext) { - app.Update( - &observertypes.Keygen{}, - []chains.Chain{}, - nil, - &observertypes.ChainParams{ChainId: chains.BitcoinMainnet.ChainId}, - nil, - "", - observertypes.CrosschainFlags{}, - []chains.Chain{}, - nil, - true, - ) - }, - assert: func(t *testing.T, chain chains.Chain, btcConfig config.BTCConfig, enabled bool) { - assert.Equal(t, chains.BitcoinMainnet.ChainId, chain.ChainId) - assert.Equal(t, "smoketest", btcConfig.RPCUsername) - assert.True(t, enabled) - }, - }, - } { - t.Run(tt.name, func(t *testing.T) { - // ARRANGE - // Given app context - appContext := context.New(tt.cfg, logger) - - // And optional setup - if tt.setup != nil { - tt.setup(appContext) + zc := mustBePresent(t, a, chains.ZetaChainMainnet.ChainId) + assert.True(t, zc.IsZeta()) + assert.Equal(t, "", zc.Params().GatewayAddress) + }, + }, + { + name: "trying to add new chainParams without chain results in an error", + act: func(a *AppContext) error { + // ASSERT + // Given polygon chain params WITHOUT the chain itself + maticParams := types.GetDefaultMumbaiTestnetChainParams() + maticParams.ChainId = chains.Polygon.ChainId + maticParams.IsSupported = true + + updatedChainParams := maps.Clone(chainParams) + updatedChainParams[maticParams.ChainId] = maticParams + delete(updatedChainParams, chains.ZetaChainMainnet.ChainId) + + return a.Update(keyGen, newChains, additionalChains, updatedChainParams, ttsPubKey, ccFlags) + }, + assert: func(t *testing.T, a *AppContext, err error) { + assert.ErrorContains(t, err, "unable to locate fresh chain 137 based on chain params") + mustBeNotFound(t, a, chains.Polygon.ChainId) + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + // ACT + errAct := tt.act(appContext) + + // ASSERT + require.NotNil(t, tt.assert) + tt.assert(t, appContext, errAct) + }) } - - // ACT - chain, btcConfig, enabled := appContext.GetBTCChainAndConfig() - - // ASSERT - tt.assert(t, chain, btcConfig, enabled) }) - } -} - -func TestGetBlockHeaderEnabledChains(t *testing.T) { - // ARRANGE - // Given app config - appContext := context.New(config.New(false), zerolog.Nop()) - - // That was eventually updated - appContext.Update( - &observertypes.Keygen{}, - []chains.Chain{}, - nil, - &observertypes.ChainParams{ChainId: chains.BitcoinMainnet.ChainId}, - nil, - "", - observertypes.CrosschainFlags{}, - []chains.Chain{}, - []lightclienttypes.HeaderSupportedChain{ - {ChainId: 1, Enabled: true}, - }, - true, - ) - - // ACT #1 (found) - chain, found := appContext.GetBlockHeaderEnabledChains(1) - - // ASSERT #1 - assert.True(t, found) - assert.Equal(t, int64(1), chain.ChainId) - assert.True(t, chain.Enabled) - - // ACT #2 (not found) - chain, found = appContext.GetBlockHeaderEnabledChains(2) - - // ASSERT #2 - assert.False(t, found) - assert.Empty(t, chain) + }) } -func TestGetAdditionalChains(t *testing.T) { - // ARRANGE - // Given app config - appContext := context.New(config.New(false), zerolog.Nop()) - - additionalChains := []chains.Chain{ - sample.Chain(1), - sample.Chain(2), - sample.Chain(3), - } - - // That was eventually updated - appContext.Update( - &observertypes.Keygen{}, - []chains.Chain{}, - nil, - &observertypes.ChainParams{}, - nil, - "", - observertypes.CrosschainFlags{}, - additionalChains, - []lightclienttypes.HeaderSupportedChain{ - {ChainId: 1, Enabled: true}, - }, - true, - ) - - // ACT - found := appContext.GetAdditionalChains() - - // ASSERT - assert.EqualValues(t, additionalChains, found) +func mustBeNotFound(t *testing.T, a *AppContext, chainID int64) { + t.Helper() + _, err := a.GetChain(chainID) + require.ErrorIs(t, err, ErrChainNotFound) } -func makeAppContext( - evmChain chains.Chain, - evmChainParams *observertypes.ChainParams, - ccFlags observertypes.CrosschainFlags, - headerSupportedChains []lightclienttypes.HeaderSupportedChain, -) *context.AppContext { - // create config - cfg := config.New(false) - logger := zerolog.Nop() - cfg.EVMChainConfigs[evmChain.ChainId] = config.EVMConfig{ - Chain: evmChain, - } - - // create AppContext - appContext := context.New(cfg, logger) - evmChainParamsMap := make(map[int64]*observertypes.ChainParams) - evmChainParamsMap[evmChain.ChainId] = evmChainParams - - // feed chain params - appContext.Update( - &observertypes.Keygen{}, - []chains.Chain{evmChain}, - evmChainParamsMap, - nil, - nil, - "", - ccFlags, - []chains.Chain{}, - headerSupportedChains, - true, - ) +func mustBePresent(t *testing.T, a *AppContext, chainID int64) Chain { + t.Helper() + c, err := a.GetChain(chainID) + require.NoError(t, err) - return appContext + return c } diff --git a/zetaclient/context/chain.go b/zetaclient/context/chain.go new file mode 100644 index 0000000000..b3ec993766 --- /dev/null +++ b/zetaclient/context/chain.go @@ -0,0 +1,177 @@ +package context + +import ( + "fmt" + "sync" + + "github.com/pkg/errors" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" + + "github.com/zeta-chain/zetacore/pkg/chains" + observer "github.com/zeta-chain/zetacore/x/observer/types" +) + +// ChainRegistry is a registry of supported chains +type ChainRegistry struct { + chains map[int64]Chain + + // additionalChains is a list of additional static chain information to use when searching from + // chain IDs. It's stored in the protocol to dynamically support new chains without doing an upgrade + additionalChains []chains.Chain + + mu sync.Mutex +} + +// Chain represents chain with its parameters +type Chain struct { + chainInfo *chains.Chain + observerParams *observer.ChainParams + + // reference to the registry it necessary for some operations + // like checking if the chain is EVM or not because it uses some "global" context state + registry *ChainRegistry +} + +var ( + ErrChainNotFound = errors.New("chain not found") + ErrChainNotSupported = errors.New("chain not supported") +) + +// NewChainRegistry constructs a new ChainRegistry +func NewChainRegistry() *ChainRegistry { + return &ChainRegistry{ + chains: make(map[int64]Chain), + additionalChains: []chains.Chain{}, + mu: sync.Mutex{}, + } +} + +// Get returns a chain by ID. +func (cr *ChainRegistry) Get(chainID int64) (Chain, error) { + chain, ok := cr.chains[chainID] + if !ok { + return Chain{}, errors.Wrapf(ErrChainNotFound, "id=%d", chainID) + } + + return chain, nil +} + +// All returns all chains in the registry sorted by chain ID. +func (cr *ChainRegistry) All() []Chain { + items := maps.Values(cr.chains) + + slices.SortFunc(items, func(a, b Chain) bool { return a.ID() < b.ID() }) + + return items +} + +// Set sets a chain in the registry. +// A chain must be SUPPORTED; otherwise returns ErrChainNotSupported +func (cr *ChainRegistry) Set(chainID int64, chain *chains.Chain, params *observer.ChainParams) error { + item, err := newChain(cr, chainID, chain, params) + if err != nil { + return err + } + + item.registry = cr + + cr.mu.Lock() + defer cr.mu.Unlock() + + cr.chains[chainID] = item + + return nil +} + +// SetAdditionalChains sets additional chains to the registry +func (cr *ChainRegistry) SetAdditionalChains(chains []chains.Chain) { + cr.mu.Lock() + defer cr.mu.Unlock() + + cr.additionalChains = chains +} + +// Delete deletes one or more chains from the registry +func (cr *ChainRegistry) Delete(chainIDs ...int64) { + cr.mu.Lock() + defer cr.mu.Unlock() + + for _, id := range chainIDs { + delete(cr.chains, id) + } +} + +// Has checks if the chain is in the registry +func (cr *ChainRegistry) Has(chainID int64) bool { + _, ok := cr.chains[chainID] + return ok +} + +// ChainIDs returns a list of chain IDs in the registry +func (cr *ChainRegistry) ChainIDs() []int64 { + cr.mu.Lock() + defer cr.mu.Unlock() + + return maps.Keys(cr.chains) +} + +func newChain(cr *ChainRegistry, chainID int64, chain *chains.Chain, params *observer.ChainParams) (Chain, error) { + if err := validateNewChain(chainID, chain, params); err != nil { + return Chain{}, errors.Wrap(err, "invalid input") + } + + return Chain{ + chainInfo: chain, + observerParams: params, + registry: cr, + }, nil +} + +func (c Chain) ID() int64 { + return c.chainInfo.ChainId +} + +func (c Chain) Params() *observer.ChainParams { + return c.observerParams +} + +// RawChain returns the underlying Chain object. Better not to use this method +func (c Chain) RawChain() *chains.Chain { + return c.chainInfo +} + +func (c Chain) IsEVM() bool { + return chains.IsEVMChain(c.ID(), c.registry.additionalChains) +} + +func (c Chain) IsZeta() bool { + return chains.IsZetaChain(c.ID(), c.registry.additionalChains) +} + +func (c Chain) IsUTXO() bool { + return chains.IsBitcoinChain(c.ID(), c.registry.additionalChains) +} + +func (c Chain) IsSolana() bool { + return chains.IsSolanaChain(c.ID(), c.registry.additionalChains) +} + +func validateNewChain(chainID int64, chain *chains.Chain, params *observer.ChainParams) error { + switch { + case chainID < 1: + return fmt.Errorf("invalid chain id %d", chainID) + case chain == nil: + return fmt.Errorf("chain is nil") + case params == nil: + return fmt.Errorf("chain params is nil") + case chain.ChainId != chainID: + return fmt.Errorf("chain id %d does not match chain.ChainId %d", chainID, chain.ChainId) + case params.ChainId != chainID: + return fmt.Errorf("chain id %d does not match params.ChainId %d", chainID, params.ChainId) + case !params.IsSupported: + return ErrChainNotSupported + } + + return nil +} diff --git a/zetaclient/context/chain_test.go b/zetaclient/context/chain_test.go new file mode 100644 index 0000000000..a679ed020a --- /dev/null +++ b/zetaclient/context/chain_test.go @@ -0,0 +1,87 @@ +package context + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/chains" + observer "github.com/zeta-chain/zetacore/x/observer/types" + "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" +) + +func TestChainRegistry(t *testing.T) { + // Given chains & chainParams + var ( + btc = &chains.BitcoinMainnet + btcParams = makeParams(btc.ChainId, true) + + eth = &chains.Ethereum + ethParams = makeParams(eth.ChainId, true) + + matic = &chains.Polygon + maticParams = makeParams(matic.ChainId, true) + + // NOT supported! + opt = &chains.OptimismSepolia + optParams = makeParams(opt.ChainId, false) + + sol = &chains.SolanaMainnet + solParams = makeParams(sol.ChainId, true) + + // Zetachain itself + zeta = &chains.ZetaChainMainnet + zetaParams = makeParams(zeta.ChainId, true) + ) + + t.Run("Sample Flow", func(t *testing.T) { + // Given registry + r := NewChainRegistry() + + // With some chains added + require.NoError(t, r.Set(btc.ChainId, btc, btcParams)) + require.NoError(t, r.Set(eth.ChainId, eth, ethParams)) + require.NoError(t, r.Set(matic.ChainId, matic, maticParams)) + require.NoError(t, r.Set(sol.ChainId, sol, solParams)) + require.NoError(t, r.Set(zeta.ChainId, zeta, zetaParams)) + + // With failures on invalid data + require.Error(t, r.Set(0, btc, btcParams)) + require.Error(t, r.Set(btc.ChainId, btc, nil)) + require.Error(t, r.Set(btc.ChainId, nil, btcParams)) + require.Error(t, r.Set(123, btc, btcParams)) + require.Error(t, r.Set(btc.ChainId, btc, ethParams)) + + // With failure on adding unsupported chains + require.ErrorIs(t, r.Set(opt.ChainId, opt, optParams), ErrChainNotSupported) + + // Should return a proper chain list + expectedChains := []int64{ + btc.ChainId, + eth.ChainId, + matic.ChainId, + sol.ChainId, + zeta.ChainId, + } + + require.ElementsMatch(t, expectedChains, r.ChainIDs()) + + // Should return not found error + _, err := r.Get(123) + require.ErrorIs(t, err, ErrChainNotFound) + + // Let's check ETH + ethChain, err := r.Get(eth.ChainId) + require.NoError(t, err) + require.True(t, ethChain.IsEVM()) + require.False(t, ethChain.IsUTXO()) + require.False(t, ethChain.IsSolana()) + require.Equal(t, ethParams, ethChain.Params()) + }) +} + +func makeParams(id int64, supported bool) *observer.ChainParams { + cp := mocks.MockChainParams(id, 123) + cp.IsSupported = supported + + return &cp +} diff --git a/zetaclient/orchestrator/bootstap_test.go b/zetaclient/orchestrator/bootstap_test.go index 555c830df5..55b6f47614 100644 --- a/zetaclient/orchestrator/bootstap_test.go +++ b/zetaclient/orchestrator/bootstap_test.go @@ -6,6 +6,7 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/ptr" observertypes "github.com/zeta-chain/zetacore/x/observer/types" @@ -172,36 +173,6 @@ func TestCreateSignerMap(t *testing.T) { hasSigner(t, signers, chains.BitcoinMainnet.ChainId) }) - t.Run("Polygon is there but not supported, should be disabled", func(t *testing.T) { - // ARRANGE - // Given updated data from zetacore containing polygon chain - supportedChain, evmParams, btcParams, solParams := chainParams([]chains.Chain{ - chains.Ethereum, - chains.Polygon, - chains.BitcoinMainnet, - }) - - // BUT (!) it's disabled via zetacore - evmParams[chains.Polygon.ChainId].IsSupported = false - - mustUpdateAppContext(t, app, supportedChain, evmParams, btcParams, solParams) - - // Should have signer BEFORE disabling - hasSigner(t, signers, chains.Polygon.ChainId) - - // ACT - added, removed, err := syncSignerMap(ctx, tss, baseLogger, ts, &signers) - - // ASSERT - assert.NoError(t, err) - assert.Equal(t, 0, added) - assert.Equal(t, 1, removed) - - hasSigner(t, signers, chains.Ethereum.ChainId) - missesSigner(t, signers, chains.Polygon.ChainId) - hasSigner(t, signers, chains.BitcoinMainnet.ChainId) - }) - t.Run("No changes", func(t *testing.T) { // ARRANGE before := len(signers) @@ -401,36 +372,6 @@ func TestCreateChainObserverMap(t *testing.T) { hasObserver(t, observers, chains.BitcoinMainnet.ChainId) }) - t.Run("Polygon is there but not supported, should be disabled", func(t *testing.T) { - // ARRANGE - // Given updated data from zetacore containing polygon chain - supportedChain, evmParams, btcParams, solParams := chainParams([]chains.Chain{ - chains.Ethereum, - chains.Polygon, - chains.BitcoinMainnet, - }) - - // BUT (!) it's disabled via zetacore - evmParams[chains.Polygon.ChainId].IsSupported = false - - mustUpdateAppContext(t, app, supportedChain, evmParams, btcParams, solParams) - - // Should have signer BEFORE disabling - hasObserver(t, observers, chains.Polygon.ChainId) - - // ACT - added, removed, err := syncObserverMap(ctx, client, tss, dbPath, baseLogger, ts, &observers) - - // ASSERT - assert.NoError(t, err) - assert.Equal(t, 0, added) - assert.Equal(t, 1, removed) - - hasObserver(t, observers, chains.Ethereum.ChainId) - missesObserver(t, observers, chains.Polygon.ChainId) - hasObserver(t, observers, chains.BitcoinMainnet.ChainId) - }) - t.Run("No changes", func(t *testing.T) { // ARRANGE before := len(observers) @@ -447,69 +388,54 @@ func TestCreateChainObserverMap(t *testing.T) { }) } -func chainParams(supportedChains []chains.Chain) ( - []chains.Chain, - map[int64]*observertypes.ChainParams, - *observertypes.ChainParams, - *observertypes.ChainParams, -) { - var ( - evmParams = make(map[int64]*observertypes.ChainParams) - btcParams = &observertypes.ChainParams{} - solParams = &observertypes.ChainParams{} - ) +func chainParams(supportedChains []chains.Chain) ([]chains.Chain, map[int64]*observertypes.ChainParams) { + params := make(map[int64]*observertypes.ChainParams) for _, chain := range supportedChains { - if chains.IsBitcoinChain(chain.ChainId, nil) { - btcParams = &observertypes.ChainParams{ - ChainId: chain.ChainId, - IsSupported: true, - } - + chainID := chain.ChainId + if chains.IsBitcoinChain(chainID, nil) { + p := mocks.MockChainParams(chainID, 100) + params[chainID] = &p continue } - if chains.IsSolanaChain(chain.ChainId, nil) { - solParams = &observertypes.ChainParams{ - ChainId: chain.ChainId, - IsSupported: true, - GatewayAddress: solanaGatewayAddress, - } + if chains.IsSolanaChain(chainID, nil) { + p := mocks.MockChainParams(chainID, 100) + p.GatewayAddress = solanaGatewayAddress + params[chainID] = &p + continue } - if chains.IsEVMChain(chain.ChainId, nil) { - evmParams[chain.ChainId] = ptr.Ptr(mocks.MockChainParams(chain.ChainId, 100)) + if chains.IsEVMChain(chainID, nil) { + params[chainID] = ptr.Ptr(mocks.MockChainParams(chainID, 100)) + continue } } - return supportedChains, evmParams, btcParams, solParams + return supportedChains, params } func mustUpdateAppContextChainParams(t *testing.T, app *zctx.AppContext, chains []chains.Chain) { - supportedChain, evmParams, btcParams, solParams := chainParams(chains) - mustUpdateAppContext(t, app, supportedChain, evmParams, btcParams, solParams) + supportedChain, params := chainParams(chains) + mustUpdateAppContext(t, app, supportedChain, nil, params) } func mustUpdateAppContext( - _ *testing.T, + t *testing.T, app *zctx.AppContext, - chains []chains.Chain, - evmParams map[int64]*observertypes.ChainParams, - utxoParams *observertypes.ChainParams, - solParams *observertypes.ChainParams, + chains, additionalChains []chains.Chain, + chainParams map[int64]*observertypes.ChainParams, ) { - app.Update( - ptr.Ptr(app.GetKeygen()), + err := app.Update( + app.GetKeygen(), chains, - evmParams, - utxoParams, - solParams, - app.GetCurrentTssPubKey(), + additionalChains, + chainParams, + "tssPubKey", app.GetCrossChainFlags(), - app.GetAdditionalChains(), - nil, - false, ) + + require.NoError(t, err) } func hasSigner(t *testing.T, signers map[int64]interfaces.ChainSigner, chainId int64) { diff --git a/zetaclient/orchestrator/bootstrap.go b/zetaclient/orchestrator/bootstrap.go index cffb9085c7..cd4d2a223c 100644 --- a/zetaclient/orchestrator/bootstrap.go +++ b/zetaclient/orchestrator/bootstrap.go @@ -8,7 +8,6 @@ import ( solrpc "github.com/gagliardetto/solana-go/rpc" "github.com/pkg/errors" - "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/zetaclient/chains/base" btcobserver "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/observer" "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/rpc" @@ -84,20 +83,14 @@ func syncSignerMap( } ) - // EVM signers - for _, evmConfig := range app.Config().GetAllEVMConfigs() { - chainID := evmConfig.Chain.ChainId - - evmChainParams, found := app.GetEVMChainParams(chainID) - switch { - case !found: - logger.Std.Warn().Msgf("Unable to find chain params for EVM chain %d", chainID) - continue - case !evmChainParams.IsSupported: - logger.Std.Warn().Msgf("EVM chain %d is not supported", chainID) + for _, chain := range app.ListChains() { + // skip ZetaChain + if chain.IsZeta() { continue } + chainID := chain.ID() + presentChainIDs = append(presentChainIDs, chainID) // noop for existing signers @@ -105,119 +98,93 @@ func syncSignerMap( continue } - var ( - mpiAddress = ethcommon.HexToAddress(evmChainParams.ConnectorContractAddress) - erc20CustodyAddress = ethcommon.HexToAddress(evmChainParams.Erc20CustodyContractAddress) - ) - - signer, err := evmsigner.NewSigner( - ctx, - evmConfig.Chain, - tss, - ts, - logger, - evmConfig.Endpoint, - config.GetConnectorABI(), - config.GetERC20CustodyABI(), - mpiAddress, - erc20CustodyAddress, - ) - if err != nil { - logger.Std.Error().Err(err).Msgf("Unable to construct signer for EVM chain %d", chainID) - continue - } - - addSigner(chainID, signer) - } + rawChain := chain.RawChain() - // BTC signer - // Emulate same loop semantics as for EVM chains - for i := 0; i < 1; i++ { - btcChain, btcChainParams, btcChainParamsFound := app.GetBTCChainParams() switch { - case !btcChainParamsFound: - logger.Std.Warn().Msgf("Unable to find chain params for BTC chain") - continue - case !btcChainParams.IsSupported: - logger.Std.Warn().Msgf("BTC chain is not supported") - continue - } - - chainID := btcChainParams.ChainId - - presentChainIDs = append(presentChainIDs, chainID) - - // noop - if mapHas(signers, chainID) { - continue - } - - // get BTC config - cfg, found := app.Config().GetBTCConfig() - if !found { - logger.Std.Error().Msgf("Unable to find BTC config for chain %d", chainID) - continue - } - - signer, err := btcsigner.NewSigner(btcChain, tss, ts, logger, cfg) - if err != nil { - logger.Std.Error().Err(err).Msgf("Unable to construct signer for BTC chain %d", chainID) - continue + case chain.IsEVM(): + var ( + mpiAddress = ethcommon.HexToAddress(chain.Params().ConnectorContractAddress) + erc20CustodyAddress = ethcommon.HexToAddress(chain.Params().Erc20CustodyContractAddress) + ) + + cfg, found := app.Config().GetEVMConfig(chainID) + if !found || cfg.Empty() { + logger.Std.Warn().Msgf("Unable to find EVM config for chain %d", chainID) + continue + } + + signer, err := evmsigner.NewSigner( + ctx, + *rawChain, + tss, + ts, + logger, + cfg.Endpoint, + config.GetConnectorABI(), + config.GetERC20CustodyABI(), + mpiAddress, + erc20CustodyAddress, + ) + if err != nil { + logger.Std.Error().Err(err).Msgf("Unable to construct signer for EVM chain %d", chainID) + continue + } + + addSigner(chainID, signer) + case chain.IsUTXO(): + cfg, found := app.Config().GetBTCConfig() + if !found { + logger.Std.Warn().Msgf("Unable to find UTXO config for chain %d", chainID) + continue + } + + signer, err := btcsigner.NewSigner(*rawChain, tss, ts, logger, cfg) + if err != nil { + logger.Std.Error().Err(err).Msgf("Unable to construct signer for UTXO chain %d", chainID) + continue + } + + addSigner(chainID, signer) + case chain.IsSolana(): + cfg, found := app.Config().GetSolanaConfig() + if !found { + logger.Std.Warn().Msgf("Unable to find SOL config for chain %d", chainID) + continue + } + + // create Solana client + rpcClient := solrpc.New(cfg.Endpoint) + if rpcClient == nil { + // should never happen + logger.Std.Error().Msgf("Unable to create SOL client from endpoint %s", cfg.Endpoint) + continue + } + + // load the Solana private key + solanaKey, err := app.Config().LoadSolanaPrivateKey() + if err != nil { + logger.Std.Error().Err(err).Msg("Unable to get Solana private key") + } + + var ( + chainRaw = chain.RawChain() + paramsRaw = chain.Params() + ) + + // create Solana signer + signer, err := solanasigner.NewSigner(*chainRaw, *paramsRaw, rpcClient, tss, solanaKey, ts, logger) + if err != nil { + logger.Std.Error().Err(err).Msgf("Unable to construct signer for SOL chain %d", chainID) + continue + } + + addSigner(chainID, signer) + default: + logger.Std.Warn(). + Int64("signer.chain_id", chain.ID()). + Str("signer.chain_name", chain.RawChain().Name). + Msgf("Unable to create a signer") } - - addSigner(chainID, signer) - } - - // Solana signer - // Emulate same loop semantics as for EVM chains - for i := 0; i < 1; i++ { - solChain, solChainParams, solChainParamsFound := app.GetSolanaChainParams() - switch { - case !solChainParamsFound: - logger.Std.Warn().Msgf("Unable to find chain params for Solana chain") - continue - case !solChainParams.IsSupported: - logger.Std.Warn().Msgf("Solana chain is not supported") - continue - } - - chainID := solChainParams.ChainId - presentChainIDs = append(presentChainIDs, chainID) - - // noop - if mapHas(signers, chainID) { - continue - } - - // get Solana config - cfg, found := app.Config().GetSolanaConfig() - if !found { - logger.Std.Error().Msgf("Unable to find Solana config for chain %d", chainID) - continue - } - - // create Solana client - rpcClient := solrpc.New(cfg.Endpoint) - if rpcClient == nil { - // should never happen - logger.Std.Error().Msgf("Unable to create Solana client from endpoint %s", cfg.Endpoint) - continue - } - - // load the Solana private key - solanaKey, err := app.Config().LoadSolanaPrivateKey() - if err != nil { - logger.Std.Error().Err(err).Msg("Unable to get Solana private key") - } - - // create Solana signer - signer, err := solanasigner.NewSigner(solChain, *solChainParams, rpcClient, tss, solanaKey, ts, logger) - if err != nil { - logger.Std.Error().Err(err).Msgf("Unable to construct signer for Solana chain %d", chainID) - continue - } - - addSigner(chainID, signer) } // Remove all disabled signers @@ -284,86 +251,13 @@ func syncObserverMap( } ) - // EVM observers - for _, evmConfig := range app.Config().GetAllEVMConfigs() { - var chainID = evmConfig.Chain.ChainId - - chain, found := chains.GetChainFromChainID(chainID, app.GetAdditionalChains()) - if !found { - logger.Std.Error().Msgf("Unable to find chain %d", chainID) - continue - } - - chainParams, found := app.GetEVMChainParams(chainID) - switch { - case !found: - logger.Std.Error().Msgf("Unable to find chain params for EVM chain %d", chainID) - continue - case !chainParams.IsSupported: - logger.Std.Error().Msgf("EVM chain %d is not supported", chainID) - continue - } - - presentChainIDs = append(presentChainIDs, chainID) - - // noop - if mapHas(observerMap, chainID) { - continue - } - - // create EVM client - evmClient, err := ethclient.DialContext(ctx, evmConfig.Endpoint) - if err != nil { - logger.Std.Error().Err(err).Str("rpc.endpoint", evmConfig.Endpoint).Msgf("Unable to dial EVM RPC") - continue - } - - database, err := db.NewFromSqlite(dbpath, chain.Name, true) - if err != nil { - logger.Std.Error().Err(err).Msgf("Unable to open a database for EVM chain %q", chain.Name) - continue - } - - // create EVM chain observer - observer, err := evmobserver.NewObserver( - ctx, - evmConfig, - evmClient, - *chainParams, - client, - tss, - database, - logger, - ts, - ) - if err != nil { - logger.Std.Error().Err(err).Msgf("NewObserver error for EVM chain %s", evmConfig.Chain.String()) - continue - } - - addObserver(chainID, observer) - } - - // Emulate same loop semantics as for EVM chains - // create BTC chain observer - for i := 0; i < 1; i++ { - btcChain, btcConfig, btcEnabled := app.GetBTCChainAndConfig() - if !btcEnabled { - continue - } - - chainID := btcChain.ChainId - - _, btcChainParams, found := app.GetBTCChainParams() - switch { - case !found: - logger.Std.Warn().Msgf("Unable to find chain params for BTC chain %d", chainID) - continue - case !btcChainParams.IsSupported: - logger.Std.Warn().Msgf("BTC chain %d is not supported", chainID) + for _, chain := range app.ListChains() { + // skip ZetaChain + if chain.IsZeta() { continue } + chainID := chain.ID() presentChainIDs = append(presentChainIDs, chainID) // noop @@ -371,100 +265,128 @@ func syncObserverMap( continue } - btcRPC, err := rpc.NewRPCClient(btcConfig) - if err != nil { - logger.Std.Error().Err(err).Msgf("unable to create rpc client for BTC chain %d", chainID) - continue - } - - database, err := db.NewFromSqlite(dbpath, btcDatabaseFilename, true) - if err != nil { - logger.Std.Error().Err(err).Msgf("unable to open database for BTC chain %d", chainID) - continue - } - - btcObserver, err := btcobserver.NewObserver( - btcChain, - btcRPC, - *btcChainParams, - client, - tss, - database, - logger, - ts, - ) - if err != nil { - logger.Std.Error().Err(err).Msgf("NewObserver error for BTC chain %d", chainID) - continue - } - - addObserver(chainID, btcObserver) - } - - // Emulate same loop semantics as for EVM chains - // create SOL chain observer - for i := 0; i < 1; i++ { - solChain, solConfig, solEnabled := app.GetSolanaChainAndConfig() - if !solEnabled { - continue - } - var ( - chainID = solChain.ChainId + params = chain.Params() + rawChain = chain.RawChain() + chainName = rawChain.Name ) - chain, found := chains.GetChainFromChainID(chainID, app.GetAdditionalChains()) - if !found { - logger.Std.Error().Msgf("Unable to find chain %d", chainID) - continue - } - - _, solanaChainParams, found := app.GetSolanaChainParams() switch { - case !found: - logger.Std.Warn().Msgf("Unable to find chain params for SOL chain %d", chainID) - continue - case !solanaChainParams.IsSupported: - logger.Std.Warn().Msgf("SOL chain %d is not supported", chainID) - continue + case chain.IsEVM(): + cfg, found := app.Config().GetEVMConfig(chainID) + if !found || cfg.Empty() { + logger.Std.Warn().Msgf("Unable to find EVM config for chain %d", chainID) + continue + } + + // create EVM client + evmClient, err := ethclient.DialContext(ctx, cfg.Endpoint) + if err != nil { + logger.Std.Error().Err(err).Str("rpc.endpoint", cfg.Endpoint).Msgf("Unable to dial EVM RPC") + continue + } + + database, err := db.NewFromSqlite(dbpath, chainName, true) + if err != nil { + logger.Std.Error().Err(err).Msgf("Unable to open a database for EVM chain %q", chainName) + continue + } + + // create EVM chain observer + observer, err := evmobserver.NewObserver( + ctx, + cfg, + evmClient, + *params, + client, + tss, + database, + logger, + ts, + ) + if err != nil { + logger.Std.Error().Err(err).Msgf("NewObserver error for EVM chain %d", chainID) + continue + } + + addObserver(chainID, observer) + case chain.IsUTXO(): + cfg, found := app.Config().GetBTCConfig() + if !found { + logger.Std.Warn().Msgf("Unable to find chain params for BTC chain %d", chainID) + continue + } + + btcRPC, err := rpc.NewRPCClient(cfg) + if err != nil { + logger.Std.Error().Err(err).Msgf("unable to create rpc client for BTC chain %d", chainID) + continue + } + + database, err := db.NewFromSqlite(dbpath, btcDatabaseFilename, true) + if err != nil { + logger.Std.Error().Err(err).Msgf("unable to open database for BTC chain %d", chainID) + continue + } + + btcObserver, err := btcobserver.NewObserver( + *rawChain, + btcRPC, + *params, + client, + tss, + database, + logger, + ts, + ) + if err != nil { + logger.Std.Error().Err(err).Msgf("NewObserver error for BTC chain %d", chainID) + continue + } + + addObserver(chainID, btcObserver) + case chain.IsSolana(): + cfg, found := app.Config().GetSolanaConfig() + if !found { + logger.Std.Warn().Msgf("Unable to find chain params for SOL chain %d", chainID) + continue + } + + rpcClient := solrpc.New(cfg.Endpoint) + if rpcClient == nil { + // should never happen + logger.Std.Error().Msg("solana create Solana client error") + continue + } + + database, err := db.NewFromSqlite(dbpath, chainName, true) + if err != nil { + logger.Std.Error().Err(err).Msgf("unable to open database for SOL chain %d", chainID) + continue + } + + solObserver, err := solbserver.NewObserver( + *rawChain, + rpcClient, + *params, + client, + tss, + database, + logger, + ts, + ) + if err != nil { + logger.Std.Error().Err(err).Msgf("NewObserver error for SOL chain %d", chainID) + continue + } + + addObserver(chainID, solObserver) + default: + logger.Std.Warn(). + Int64("observer.chain_id", chain.ID()). + Str("observer.chain_name", chain.RawChain().Name). + Msgf("Unable to create an observer") } - - presentChainIDs = append(presentChainIDs, chainID) - - // noop - if mapHas(observerMap, chainID) { - continue - } - - rpcClient := solrpc.New(solConfig.Endpoint) - if rpcClient == nil { - // should never happen - logger.Std.Error().Msgf("Unable to create Solana client from endpoint %s", solConfig.Endpoint) - continue - } - - database, err := db.NewFromSqlite(dbpath, chain.Name, true) - if err != nil { - logger.Std.Error().Err(err).Msgf("unable to open database for SOL chain %s", chain.Name) - continue - } - - solObserver, err := solbserver.NewObserver( - solChain, - rpcClient, - *solanaChainParams, - client, - tss, - database, - logger, - ts, - ) - if err != nil { - logger.Std.Error().Err(err).Msgf("NewObserver error for SOL chain %d", chainID) - continue - } - - addObserver(chainID, solObserver) } // Remove all disabled observers diff --git a/zetaclient/orchestrator/orchestrator.go b/zetaclient/orchestrator/orchestrator.go index edcaa3d9d5..8bff065099 100644 --- a/zetaclient/orchestrator/orchestrator.go +++ b/zetaclient/orchestrator/orchestrator.go @@ -12,9 +12,9 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" "github.com/rs/zerolog" + "github.com/samber/lo" "github.com/zeta-chain/zetacore/pkg/bg" - "github.com/zeta-chain/zetacore/pkg/chains" zetamath "github.com/zeta-chain/zetacore/pkg/math" "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" @@ -154,37 +154,41 @@ func (oc *Orchestrator) resolveSigner(app *zctx.AppContext, chainID int64) (inte return nil, err } - // update signer chain parameters - if chains.IsEVMChain(chainID, app.GetAdditionalChains()) { - evmParams, found := app.GetEVMChainParams(chainID) - if found { - // update zeta connector and ERC20 custody addresses - zetaConnectorAddress := ethcommon.HexToAddress(evmParams.GetConnectorContractAddress()) - if zetaConnectorAddress != signer.GetZetaConnectorAddress() { - signer.SetZetaConnectorAddress(zetaConnectorAddress) - oc.logger.Info(). - Str("signer.connector_address", zetaConnectorAddress.String()). - Msgf("updated zeta connector address for chain %d", chainID) - } + chain, err := app.GetChain(chainID) + switch { + case err != nil: + return nil, err + case chain.IsZeta(): + // should not happen + return nil, fmt.Errorf("unable to resolve signer for zeta chain %d", chainID) + case chain.IsEVM(): + params := chain.Params() + + // update zeta connector and ERC20 custody addresses + zetaConnectorAddress := ethcommon.HexToAddress(params.GetConnectorContractAddress()) + if zetaConnectorAddress != signer.GetZetaConnectorAddress() { + signer.SetZetaConnectorAddress(zetaConnectorAddress) + oc.logger.Info(). + Str("signer.connector_address", zetaConnectorAddress.String()). + Msgf("updated zeta connector address for chain %d", chainID) + } - erc20CustodyAddress := ethcommon.HexToAddress(evmParams.GetErc20CustodyContractAddress()) - if erc20CustodyAddress != signer.GetERC20CustodyAddress() { - signer.SetERC20CustodyAddress(erc20CustodyAddress) - oc.logger.Info(). - Str("signer.erc20_custody", erc20CustodyAddress.String()). - Msgf("updated zeta connector address for chain %d", chainID) - } + erc20CustodyAddress := ethcommon.HexToAddress(params.GetErc20CustodyContractAddress()) + if erc20CustodyAddress != signer.GetERC20CustodyAddress() { + signer.SetERC20CustodyAddress(erc20CustodyAddress) + oc.logger.Info(). + Str("signer.erc20_custody", erc20CustodyAddress.String()). + Msgf("updated zeta connector address for chain %d", chainID) } - } else if chains.IsSolanaChain(chainID, app.GetAdditionalChains()) { - _, solParams, found := app.GetSolanaChainParams() - if found { - // update solana gateway address - if solParams.GatewayAddress != signer.GetGatewayAddress() { - signer.SetGatewayAddress(solParams.GatewayAddress) - oc.logger.Info(). - Str("signer.gateway_address", solParams.GatewayAddress). - Msgf("updated gateway address for chain %d", chainID) - } + case chain.IsSolana(): + params := chain.Params() + + // update solana gateway address + if params.GatewayAddress != signer.GetGatewayAddress() { + signer.SetGatewayAddress(params.GatewayAddress) + oc.logger.Info(). + Str("signer.gateway_address", params.GatewayAddress). + Msgf("updated gateway address for chain %d", chainID) } } @@ -210,31 +214,26 @@ func (oc *Orchestrator) resolveObserver(app *zctx.AppContext, chainID int64) (in return nil, err } + chain, err := app.GetChain(chainID) + switch { + case err != nil: + return nil, errors.Wrapf(err, "unable to get chain %d", chainID) + case chain.IsZeta(): + // should not happen + return nil, fmt.Errorf("unable to resolve observer for zeta chain %d", chainID) + } + // update chain observer chain parameters - curParams := observer.GetChainParams() - if chains.IsEVMChain(chainID, app.GetAdditionalChains()) { - evmParams, found := app.GetEVMChainParams(chainID) - if found && !observertypes.ChainParamsEqual(curParams, *evmParams) { - observer.SetChainParams(*evmParams) - oc.logger.Info(). - Interface("observer.chain_params", *evmParams). - Msgf("updated chain params for EVM chainID %d", chainID) - } - } else if chains.IsBitcoinChain(chainID, app.GetAdditionalChains()) { - _, btcParams, found := app.GetBTCChainParams() - if found && !observertypes.ChainParamsEqual(curParams, *btcParams) { - observer.SetChainParams(*btcParams) - oc.logger.Info(). - Interface("observer.chain_params", *btcParams). - Msgf("updated chain params for UTXO chainID %d", btcParams.ChainId) - } - } else if chains.IsSolanaChain(chainID, app.GetAdditionalChains()) { - _, solParams, found := app.GetSolanaChainParams() - if found && !observertypes.ChainParamsEqual(curParams, *solParams) { - observer.SetChainParams(*solParams) - oc.logger.Info().Msgf( - "updated chain params for Solana, new params: %v", *solParams) - } + var ( + curParams = observer.GetChainParams() + freshParams = chain.Params() + ) + + if !observertypes.ChainParamsEqual(curParams, *freshParams) { + observer.SetChainParams(*freshParams) + oc.logger.Info(). + Interface("observer.chain_params", *freshParams). + Msgf("updated chain params for chainID %d", chainID) } return observer, nil @@ -253,10 +252,10 @@ func (oc *Orchestrator) getObserver(chainID int64) (interfaces.ChainObserver, er } // GetPendingCctxsWithinRateLimit get pending cctxs across foreign chains within rate limit -func (oc *Orchestrator) GetPendingCctxsWithinRateLimit( - ctx context.Context, - foreignChains []chains.Chain, -) (map[int64][]*types.CrossChainTx, error) { +func (oc *Orchestrator) GetPendingCctxsWithinRateLimit(ctx context.Context, chainIDs []int64) ( + map[int64][]*types.CrossChainTx, + error, +) { // get rate limiter flags rateLimitFlags, err := oc.zetacoreClient.GetRateLimiterFlags(ctx) if err != nil { @@ -269,10 +268,10 @@ func (oc *Orchestrator) GetPendingCctxsWithinRateLimit( // fallback to non-rate-limited query if rate limiter is not usable cctxsMap := make(map[int64][]*types.CrossChainTx) if !rateLimiterUsable { - for _, chain := range foreignChains { - resp, _, err := oc.zetacoreClient.ListPendingCCTX(ctx, chain.ChainId) + for _, chainID := range chainIDs { + resp, _, err := oc.zetacoreClient.ListPendingCCTX(ctx, chainID) if err == nil && resp != nil { - cctxsMap[chain.ChainId] = resp + cctxsMap[chainID] = resp } } return cctxsMap, nil @@ -352,51 +351,68 @@ func (oc *Orchestrator) runScheduler(ctx context.Context) error { // set current hot key burn rate metrics.HotKeyBurnRate.Set(float64(oc.ts.HotKeyBurnRate.GetBurnRate().Int64())) - // get supported external chains - externalChains := app.GetEnabledExternalChains() + // get chain ids without zeta chain + chainIDs := lo.FilterMap(app.ListChains(), func(c zctx.Chain, _ int) (int64, bool) { + return c.ID(), !c.IsZeta() + }) // query pending cctxs across all external chains within rate limit - cctxMap, err := oc.GetPendingCctxsWithinRateLimit(ctx, externalChains) + cctxMap, err := oc.GetPendingCctxsWithinRateLimit(ctx, chainIDs) if err != nil { oc.logger.Error().Err(err).Msgf("runScheduler: GetPendingCctxsWithinRatelimit failed") } // schedule keysign for pending cctxs on each chain - for _, c := range externalChains { + for _, chain := range app.ListChains() { + // skip zeta chain + if chain.IsZeta() { + continue + } + + chainID := chain.ID() + // get cctxs from map and set pending transactions prometheus gauge - cctxList := cctxMap[c.ChainId] - metrics.PendingTxsPerChain.WithLabelValues(c.Name).Set(float64(len(cctxList))) + cctxList := cctxMap[chainID] + + metrics.PendingTxsPerChain. + WithLabelValues(fmt.Sprintf("chain_%d", chainID)). + Set(float64(len(cctxList))) + if len(cctxList) == 0 { continue } // update chain parameters for signer and chain observer - signer, err := oc.resolveSigner(app, c.ChainId) + signer, err := oc.resolveSigner(app, chainID) if err != nil { oc.logger.Error().Err(err). - Msgf("runScheduler: unable to resolve signer for chain %d", c.ChainId) + Msgf("runScheduler: unable to resolve signer for chain %d", chainID) continue } - ob, err := oc.resolveObserver(app, c.ChainId) + + ob, err := oc.resolveObserver(app, chainID) if err != nil { oc.logger.Error().Err(err). - Msgf("runScheduler: resolveObserver failed for chain %d", c.ChainId) + Msgf("runScheduler: resolveObserver failed for chain %d", chainID) continue } - if !app.IsOutboundObservationEnabled(ob.GetChainParams()) { + + if !app.IsOutboundObservationEnabled() { continue } // #nosec G115 range is verified zetaHeight := uint64(bn) - if chains.IsEVMChain(c.ChainId, app.GetAdditionalChains()) { - oc.ScheduleCctxEVM(ctx, zetaHeight, c.ChainId, cctxList, ob, signer) - } else if chains.IsBitcoinChain(c.ChainId, app.GetAdditionalChains()) { - oc.ScheduleCctxBTC(ctx, zetaHeight, c.ChainId, cctxList, ob, signer) - } else if chains.IsSolanaChain(c.ChainId, app.GetAdditionalChains()) { - oc.ScheduleCctxSolana(ctx, zetaHeight, c.ChainId, cctxList, ob, signer) - } else { - oc.logger.Error().Msgf("runScheduler: unsupported chain %d", c.ChainId) + + switch { + case chain.IsEVM(): + oc.ScheduleCctxEVM(ctx, zetaHeight, chainID, cctxList, ob, signer) + case chain.IsUTXO(): + oc.ScheduleCctxBTC(ctx, zetaHeight, chainID, cctxList, ob, signer) + case chain.IsSolana(): + oc.ScheduleCctxSolana(ctx, zetaHeight, chainID, cctxList, ob, signer) + default: + oc.logger.Error().Msgf("runScheduler: no scheduler found chain %d", chainID) continue } } diff --git a/zetaclient/orchestrator/orchestrator_test.go b/zetaclient/orchestrator/orchestrator_test.go index af4ca5c346..3594accc2a 100644 --- a/zetaclient/orchestrator/orchestrator_test.go +++ b/zetaclient/orchestrator/orchestrator_test.go @@ -7,6 +7,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/rs/zerolog" + "github.com/samber/lo" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" zctx "github.com/zeta-chain/zetacore/zetaclient/context" @@ -24,172 +25,64 @@ import ( "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" ) -// MockOrchestrator creates a mock orchestrator for testing -func MockOrchestrator( - t *testing.T, - zetacoreClient interfaces.ZetacoreClient, - evmChain, btcChain, solChain *chains.Chain, - evmChainParams, btcChainParams, solChainParams *observertypes.ChainParams, -) *Orchestrator { - // create maps to store signers and observers - signerMap := make(map[int64]interfaces.ChainSigner) - observerMap := make(map[int64]interfaces.ChainObserver) - - // a functor to add a signer and observer to the maps - addSignerObserver := func(chain *chains.Chain, signer interfaces.ChainSigner, observer interfaces.ChainObserver) { - signerMap[chain.ChainId] = signer - observerMap[chain.ChainId] = observer - } - - // create evm mock signer/observer - if evmChain != nil { - evmSigner := mocks.NewEVMSigner( - *evmChain, - ethcommon.HexToAddress(evmChainParams.ConnectorContractAddress), - ethcommon.HexToAddress(evmChainParams.Erc20CustodyContractAddress), - ) - evmObserver := mocks.NewEVMObserver(evmChainParams) - addSignerObserver(evmChain, evmSigner, evmObserver) - } - - // create btc mock signer/observer - if btcChain != nil { - btcSigner := mocks.NewBTCSigner() - btcObserver := mocks.NewBTCObserver(btcChainParams) - addSignerObserver(btcChain, btcSigner, btcObserver) - } - - // create solana mock signer/observer - if solChain != nil { - solSigner := mocks.NewSolanaSigner() - solObserver := mocks.NewSolanaObserver(solChainParams) - addSignerObserver(solChain, solSigner, solObserver) - } - - // create orchestrator - orchestrator := &Orchestrator{ - zetacoreClient: zetacoreClient, - signerMap: signerMap, - observerMap: observerMap, - } - return orchestrator -} - -func CreateAppContext( - evmChain, btcChain, solChain chains.Chain, - evmChainParams, btcChainParams, solChainParams *observertypes.ChainParams, -) *zctx.AppContext { - // new config - cfg := config.New(false) - cfg.EVMChainConfigs[evmChain.ChainId] = config.EVMConfig{ - Chain: evmChain, - } - cfg.BitcoinConfig = config.BTCConfig{ - RPCHost: "localhost", - } - // new AppContext - appContext := zctx.New(cfg, zerolog.Nop()) - evmChainParamsMap := make(map[int64]*observertypes.ChainParams) - evmChainParamsMap[evmChain.ChainId] = evmChainParams - ccFlags := sample.CrosschainFlags() - verificationFlags := sample.HeaderSupportedChains() +func Test_GetUpdatedSigner(t *testing.T) { + // initial parameters for orchestrator creation + var ( + evmChain = chains.Ethereum + btcChain = chains.BitcoinMainnet + solChain = chains.SolanaMainnet + ) - // feed chain params - appContext.Update( - &observertypes.Keygen{}, - []chains.Chain{evmChain, btcChain, solChain}, - evmChainParamsMap, - btcChainParams, - solChainParams, - "", - *ccFlags, - []chains.Chain{}, - verificationFlags, - true, + var ( + evmChainParams = mocks.MockChainParams(evmChain.ChainId, 100) + btcChainParams = mocks.MockChainParams(btcChain.ChainId, 100) + solChainParams = mocks.MockChainParams(solChain.ChainId, 100) ) - return appContext -} -func Test_GetUpdatedSigner(t *testing.T) { - // initial parameters for orchestrator creation - evmChain := chains.Ethereum - btcChain := chains.BitcoinMainnet - solChain := chains.SolanaMainnet - evmChainParams := &observertypes.ChainParams{ - ChainId: evmChain.ChainId, - ConnectorContractAddress: testutils.ConnectorAddresses[evmChain.ChainId].Hex(), - Erc20CustodyContractAddress: testutils.CustodyAddresses[evmChain.ChainId].Hex(), - } - btcChainParams := &observertypes.ChainParams{} - solChainParams := &observertypes.ChainParams{ - ChainId: solChain.ChainId, - GatewayAddress: solanacontracts.SolanaGatewayProgramID, - } + solChainParams.GatewayAddress = solanacontracts.SolanaGatewayProgramID - // new evm chain params in AppContext - evmChainParamsNew := &observertypes.ChainParams{ - ChainId: evmChain.ChainId, - ConnectorContractAddress: testutils.OtherAddress1, - Erc20CustodyContractAddress: testutils.OtherAddress2, - } + // new chain params in AppContext + evmChainParamsNew := mocks.MockChainParams(evmChainParams.ChainId, 100) + evmChainParamsNew.ConnectorContractAddress = testutils.OtherAddress1 + evmChainParamsNew.Erc20CustodyContractAddress = testutils.OtherAddress2 // new solana chain params in AppContext - solChainParamsNew := &observertypes.ChainParams{ - ChainId: solChain.ChainId, - GatewayAddress: sample.SolanaAddress(t), - } - - t.Run("evm signer should not be found", func(t *testing.T) { - orchestrator := MockOrchestrator( - t, - nil, - &evmChain, - &btcChain, - &solChain, - evmChainParams, - btcChainParams, - solChainParams, - ) - context := CreateAppContext(evmChain, btcChain, solChain, evmChainParamsNew, btcChainParams, solChainParams) + solChainParamsNew := mocks.MockChainParams(solChain.ChainId, 100) + solChainParamsNew.GatewayAddress = sample.SolanaAddress(t) + t.Run("signer should not be found", func(t *testing.T) { + orchestrator := mockOrchestrator(t, nil, evmChain, btcChain, evmChainParams, btcChainParams) + appContext := createAppContext(t, evmChain, btcChain, evmChainParamsNew, btcChainParams) // BSC signer should not be found - _, err := orchestrator.resolveSigner(context, chains.BscMainnet.ChainId) + _, err := orchestrator.resolveSigner(appContext, chains.BscMainnet.ChainId) require.ErrorContains(t, err, "signer not found") }) - t.Run("should be able to update evm connector and erc20 custody address", func(t *testing.T) { - orchestrator := MockOrchestrator( - t, - nil, - &evmChain, - &btcChain, - &solChain, - evmChainParams, - btcChainParams, - solChainParams, - ) - context := CreateAppContext(evmChain, btcChain, solChain, evmChainParamsNew, btcChainParams, solChainParams) + + t.Run("should be able to update connector and erc20 custody address", func(t *testing.T) { + orchestrator := mockOrchestrator(t, nil, evmChain, btcChain, evmChainParams, btcChainParams) + appContext := createAppContext(t, evmChain, btcChain, evmChainParamsNew, btcChainParams) // update signer with new connector and erc20 custody address - signer, err := orchestrator.resolveSigner(context, evmChain.ChainId) + signer, err := orchestrator.resolveSigner(appContext, evmChain.ChainId) require.NoError(t, err) + require.Equal(t, testutils.OtherAddress1, signer.GetZetaConnectorAddress().Hex()) require.Equal(t, testutils.OtherAddress2, signer.GetERC20CustodyAddress().Hex()) }) + t.Run("should be able to update solana gateway address", func(t *testing.T) { - orchestrator := MockOrchestrator( - t, - nil, - &evmChain, - &btcChain, - &solChain, - evmChainParams, - btcChainParams, - solChainParams, + orchestrator := mockOrchestrator(t, nil, + evmChain, btcChain, solChain, + evmChainParams, btcChainParams, solChainParams, + ) + + appContext := createAppContext(t, + evmChain, btcChain, solChain, + evmChainParams, btcChainParams, solChainParamsNew, ) - context := CreateAppContext(evmChain, btcChain, solChain, evmChainParams, btcChainParams, solChainParamsNew) // update signer with new gateway address - signer, err := orchestrator.resolveSigner(context, solChain.ChainId) + signer, err := orchestrator.resolveSigner(appContext, solChain.ChainId) require.NoError(t, err) require.Equal(t, solChainParamsNew.GatewayAddress, signer.GetGatewayAddress()) }) @@ -197,21 +90,19 @@ func Test_GetUpdatedSigner(t *testing.T) { func Test_GetUpdatedChainObserver(t *testing.T) { // initial parameters for orchestrator creation - evmChain := chains.Ethereum - btcChain := chains.BitcoinMainnet - solChain := chains.SolanaMainnet - evmChainParams := &observertypes.ChainParams{ - ChainId: evmChain.ChainId, - ConnectorContractAddress: testutils.ConnectorAddresses[evmChain.ChainId].Hex(), - Erc20CustodyContractAddress: testutils.CustodyAddresses[evmChain.ChainId].Hex(), - } - btcChainParams := &observertypes.ChainParams{ - ChainId: btcChain.ChainId, - } - solChainParams := &observertypes.ChainParams{ - ChainId: solChain.ChainId, - GatewayAddress: solanacontracts.SolanaGatewayProgramID, - } + var ( + evmChain = chains.Ethereum + btcChain = chains.BitcoinMainnet + solChain = chains.SolanaMainnet + ) + + var ( + evmChainParams = mocks.MockChainParams(evmChain.ChainId, 100) + btcChainParams = mocks.MockChainParams(btcChain.ChainId, 100) + solChainParams = mocks.MockChainParams(solChain.ChainId, 100) + ) + + solChainParams.GatewayAddress = solanacontracts.SolanaGatewayProgramID // new chain params in AppContext evmChainParamsNew := &observertypes.ChainParams{ @@ -264,67 +155,91 @@ func Test_GetUpdatedChainObserver(t *testing.T) { } t.Run("evm chain observer should not be found", func(t *testing.T) { - orchestrator := MockOrchestrator( + orchestrator := mockOrchestrator( t, nil, - &evmChain, - &btcChain, - &solChain, + evmChain, + btcChain, + solChain, evmChainParams, btcChainParams, solChainParams, ) - appContext := CreateAppContext(evmChain, btcChain, solChain, evmChainParamsNew, btcChainParams, solChainParams) + appContext := createAppContext(t, evmChain, btcChain, evmChainParamsNew, btcChainParams) + // BSC chain observer should not be found _, err := orchestrator.resolveObserver(appContext, chains.BscMainnet.ChainId) require.ErrorContains(t, err, "observer not found") }) t.Run("chain params in evm chain observer should be updated successfully", func(t *testing.T) { - orchestrator := MockOrchestrator( + orchestrator := mockOrchestrator( t, nil, - &evmChain, - &btcChain, - &solChain, + evmChain, + btcChain, + solChain, evmChainParams, btcChainParams, solChainParams, ) - appContext := CreateAppContext(evmChain, btcChain, solChain, evmChainParamsNew, btcChainParams, solChainParams) + appContext := createAppContext( + t, + evmChain, + btcChain, + solChain, + evmChainParamsNew, + btcChainParams, + solChainParams, + ) + // update evm chain observer with new chain params chainOb, err := orchestrator.resolveObserver(appContext, evmChain.ChainId) require.NoError(t, err) require.NotNil(t, chainOb) require.True(t, observertypes.ChainParamsEqual(*evmChainParamsNew, chainOb.GetChainParams())) }) + t.Run("btc chain observer should not be found", func(t *testing.T) { - orchestrator := MockOrchestrator( + orchestrator := mockOrchestrator( t, nil, - &evmChain, - &btcChain, - &solChain, + evmChain, + btcChain, + solChain, evmChainParams, btcChainParams, solChainParams, ) - appContext := CreateAppContext(btcChain, btcChain, solChain, evmChainParams, btcChainParamsNew, solChainParams) + appContext := createAppContext( + t, + evmChain, + btcChain, + solChain, + evmChainParams, + btcChainParamsNew, + solChainParams, + ) + // BTC testnet chain observer should not be found _, err := orchestrator.resolveObserver(appContext, chains.BitcoinTestnet.ChainId) require.ErrorContains(t, err, "observer not found") }) t.Run("chain params in btc chain observer should be updated successfully", func(t *testing.T) { - orchestrator := MockOrchestrator( + orchestrator := mockOrchestrator( t, nil, - &evmChain, - &btcChain, - &solChain, + evmChain, btcChain, solChain, + evmChainParams, btcChainParams, solChainParams, + ) + appContext := createAppContext( + t, + evmChain, + btcChain, + solChain, evmChainParams, - btcChainParams, + btcChainParamsNew, solChainParams, ) - appContext := CreateAppContext(btcChain, btcChain, solChain, evmChainParams, btcChainParamsNew, solChainParams) // update btc chain observer with new chain params chainOb, err := orchestrator.resolveObserver(appContext, btcChain.ChainId) require.NoError(t, err) @@ -332,33 +247,37 @@ func Test_GetUpdatedChainObserver(t *testing.T) { require.True(t, observertypes.ChainParamsEqual(*btcChainParamsNew, chainOb.GetChainParams())) }) t.Run("solana chain observer should not be found", func(t *testing.T) { - orchestrator := MockOrchestrator( + orchestrator := mockOrchestrator( t, nil, - &evmChain, - &btcChain, - &solChain, + evmChain, btcChain, solChain, + evmChainParams, btcChainParams, solChainParams, + ) + + appContext := createAppContext( + t, + evmChain, + btcChain, + solChain, evmChainParams, btcChainParams, - solChainParams, + solChainParamsNew, ) - appContext := CreateAppContext(solChain, btcChain, solChain, evmChainParams, btcChainParams, solChainParamsNew) + // Solana Devnet chain observer should not be found _, err := orchestrator.resolveObserver(appContext, chains.SolanaDevnet.ChainId) require.ErrorContains(t, err, "observer not found") }) t.Run("chain params in solana chain observer should be updated successfully", func(t *testing.T) { - orchestrator := MockOrchestrator( - t, - nil, - &evmChain, - &btcChain, - &solChain, - evmChainParams, - btcChainParams, - solChainParams, + orchestrator := mockOrchestrator(t, nil, + evmChain, btcChain, solChain, + evmChainParams, btcChainParams, solChainParams, + ) + appContext := createAppContext(t, + evmChain, btcChain, solChain, + evmChainParams, btcChainParams, solChainParamsNew, ) - appContext := CreateAppContext(solChain, btcChain, solChain, evmChainParams, btcChainParams, solChainParamsNew) + // update solana chain observer with new chain params chainOb, err := orchestrator.resolveObserver(appContext, solChain.ChainId) require.NoError(t, err) @@ -527,10 +446,12 @@ func Test_GetPendingCctxsWithinRateLimit(t *testing.T) { client.WithPendingCctx(btcChain.ChainId, tt.btcCctxsFallback) // create orchestrator - orchestrator := MockOrchestrator(t, client, ðChain, &btcChain, nil, ethChainParams, btcChainParams, nil) + orchestrator := mockOrchestrator(t, client, ethChain, btcChain, ethChainParams, btcChainParams) + + chainIDs := lo.Map(foreignChains, func(c chains.Chain, _ int) int64 { return c.ChainId }) // run the test - cctxsMap, err := orchestrator.GetPendingCctxsWithinRateLimit(ctx, foreignChains) + cctxsMap, err := orchestrator.GetPendingCctxsWithinRateLimit(ctx, chainIDs) if tt.fail { assert.Error(t, err) assert.Empty(t, cctxsMap) @@ -541,3 +462,118 @@ func Test_GetPendingCctxsWithinRateLimit(t *testing.T) { }) } } + +func mockOrchestrator(t *testing.T, zetaClient interfaces.ZetacoreClient, chainsOrParams ...any) *Orchestrator { + supportedChains, obsParams := parseChainsWithParams(t, chainsOrParams...) + + var ( + signers = make(map[int64]interfaces.ChainSigner) + observers = make(map[int64]interfaces.ChainObserver) + ) + + mustFindChain := func(chainID int64) chains.Chain { + for _, c := range supportedChains { + if c.ChainId == chainID { + return c + } + } + + t.Fatalf("mock orchestrator: must find chain: chain %d not found", chainID) + + return chains.Chain{} + } + + for i := range obsParams { + cp := obsParams[i] + + switch { + case chains.IsEVMChain(cp.ChainId, nil): + observers[cp.ChainId] = mocks.NewEVMObserver(cp) + signers[cp.ChainId] = mocks.NewEVMSigner( + mustFindChain(cp.ChainId), + ethcommon.HexToAddress(cp.ConnectorContractAddress), + ethcommon.HexToAddress(cp.Erc20CustodyContractAddress), + ) + case chains.IsBitcoinChain(cp.ChainId, nil): + observers[cp.ChainId] = mocks.NewBTCObserver(cp) + signers[cp.ChainId] = mocks.NewBTCSigner() + case chains.IsSolanaChain(cp.ChainId, nil): + observers[cp.ChainId] = mocks.NewSolanaObserver(cp) + signers[cp.ChainId] = mocks.NewSolanaSigner() + default: + t.Fatalf("mock orcestrator: unsupported chain %d", cp.ChainId) + } + } + + return &Orchestrator{ + zetacoreClient: zetaClient, + signerMap: signers, + observerMap: observers, + } +} + +func createAppContext(t *testing.T, chainsOrParams ...any) *zctx.AppContext { + supportedChains, obsParams := parseChainsWithParams(t, chainsOrParams...) + + cfg := config.New(false) + + // Mock config + cfg.BitcoinConfig = config.BTCConfig{ + RPCHost: "localhost", + } + + for _, c := range supportedChains { + if chains.IsEVMChain(c.ChainId, nil) { + cfg.EVMChainConfigs[c.ChainId] = config.EVMConfig{Chain: c} + } + } + + params := map[int64]*observertypes.ChainParams{} + for i := range obsParams { + cp := obsParams[i] + params[cp.ChainId] = cp + } + + // new AppContext + appContext := zctx.New(cfg, zerolog.New(zerolog.NewTestWriter(t))) + + ccFlags := sample.CrosschainFlags() + + // feed chain params + err := appContext.Update( + observertypes.Keygen{}, + supportedChains, + nil, + params, + "tssPubKey", + *ccFlags, + ) + require.NoError(t, err, "failed to update app context") + + return appContext +} + +// handy helper for testing +func parseChainsWithParams(t *testing.T, chainsOrParams ...any) ([]chains.Chain, []*observertypes.ChainParams) { + var ( + supportedChains = make([]chains.Chain, 0, len(chainsOrParams)) + obsParams = make([]*observertypes.ChainParams, 0, len(chainsOrParams)) + ) + + for _, something := range chainsOrParams { + switch tt := something.(type) { + case *chains.Chain: + supportedChains = append(supportedChains, *tt) + case chains.Chain: + supportedChains = append(supportedChains, tt) + case *observertypes.ChainParams: + obsParams = append(obsParams, tt) + case observertypes.ChainParams: + obsParams = append(obsParams, &tt) + default: + t.Fatalf("parse chains and params: unsupported type %T (%+v)", tt, tt) + } + } + + return supportedChains, obsParams +} diff --git a/zetaclient/supplychecker/logger.go b/zetaclient/supplychecker/logger.go deleted file mode 100644 index 89da0300d6..0000000000 --- a/zetaclient/supplychecker/logger.go +++ /dev/null @@ -1,31 +0,0 @@ -package supplychecker - -import ( - sdkmath "cosmossdk.io/math" - "github.com/rs/zerolog" - - "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin" -) - -// ZetaSupplyCheckLogs is a struct to log the output of the ZetaSupplyChecker -type ZetaSupplyCheckLogs struct { - Logger zerolog.Logger - AbortedTxAmounts sdkmath.Int `json:"aborted_tx_amounts"` - ZetaInTransit sdkmath.Int `json:"zeta_in_transit"` - ExternalChainTotalSupply sdkmath.Int `json:"external_chain_total_supply"` - ZetaTokenSupplyOnNode sdkmath.Int `json:"zeta_token_supply_on_node"` - EthLockedAmount sdkmath.Int `json:"eth_locked_amount"` - NodeAmounts sdkmath.Int `json:"node_amounts"` - LHS sdkmath.Int `json:"LHS"` - RHS sdkmath.Int `json:"RHS"` - SupplyCheckSuccess bool `json:"supply_check_success"` -} - -// LogOutput logs the output of the ZetaSupplyChecker -func (z ZetaSupplyCheckLogs) LogOutput() { - output, err := bitcoin.PrettyPrintStruct(z) - if err != nil { - z.Logger.Error().Err(err).Msgf("error pretty printing struct") - } - z.Logger.Info().Msgf(output) -} diff --git a/zetaclient/supplychecker/validate.go b/zetaclient/supplychecker/validate.go deleted file mode 100644 index f9e4dbaf79..0000000000 --- a/zetaclient/supplychecker/validate.go +++ /dev/null @@ -1,34 +0,0 @@ -package supplychecker - -import ( - sdkmath "cosmossdk.io/math" - "github.com/rs/zerolog" -) - -// ValidateZetaSupply validates the zeta supply from the checked values -func ValidateZetaSupply( - logger zerolog.Logger, - abortedTxAmounts, zetaInTransit, genesisAmounts, externalChainTotalSupply, zetaTokenSupplyOnNode, ethLockedAmount sdkmath.Int, -) bool { - lhs := ethLockedAmount.Sub(abortedTxAmounts) - rhs := zetaTokenSupplyOnNode.Add(zetaInTransit).Add(externalChainTotalSupply).Sub(genesisAmounts) - - copyZetaTokenSupplyOnNode := zetaTokenSupplyOnNode - copyGenesisAmounts := genesisAmounts - nodeAmounts := copyZetaTokenSupplyOnNode.Sub(copyGenesisAmounts) - logs := ZetaSupplyCheckLogs{ - Logger: logger, - AbortedTxAmounts: abortedTxAmounts, - ZetaInTransit: zetaInTransit, - ExternalChainTotalSupply: externalChainTotalSupply, - NodeAmounts: nodeAmounts, - ZetaTokenSupplyOnNode: zetaTokenSupplyOnNode, - EthLockedAmount: ethLockedAmount, - LHS: lhs, - RHS: rhs, - } - defer logs.LogOutput() - - logs.SupplyCheckSuccess = lhs.Equal(rhs) - return logs.SupplyCheckSuccess -} diff --git a/zetaclient/supplychecker/zeta_supply_checker.go b/zetaclient/supplychecker/zeta_supply_checker.go deleted file mode 100644 index 53a61c707b..0000000000 --- a/zetaclient/supplychecker/zeta_supply_checker.go +++ /dev/null @@ -1,280 +0,0 @@ -// Package supplychecker provides functionalities to check the total supply of Zeta tokens -// Currently not used in the codebase -package supplychecker - -import ( - "context" - "fmt" - - sdkmath "cosmossdk.io/math" - ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/pkg/errors" - "github.com/rs/zerolog" - - "github.com/zeta-chain/zetacore/pkg/chains" - "github.com/zeta-chain/zetacore/pkg/coin" - "github.com/zeta-chain/zetacore/x/crosschain/types" - "github.com/zeta-chain/zetacore/zetaclient/chains/evm/observer" - "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" - zctx "github.com/zeta-chain/zetacore/zetaclient/context" - clienttypes "github.com/zeta-chain/zetacore/zetaclient/types" - "github.com/zeta-chain/zetacore/zetaclient/zetacore" -) - -// ZetaSupplyChecker is a utility to check the total supply of Zeta tokens -type ZetaSupplyChecker struct { - evmClient map[int64]*ethclient.Client - zetaClient *zetacore.Client - ticker *clienttypes.DynamicTicker - stop chan struct{} - logger zerolog.Logger - externalEvmChain []chains.Chain - ethereumChain chains.Chain - genesisSupply sdkmath.Int -} - -// NewZetaSupplyChecker creates a new ZetaSupplyChecker -func NewZetaSupplyChecker( - ctx context.Context, - zetaClient *zetacore.Client, - logger zerolog.Logger, -) (*ZetaSupplyChecker, error) { - dynamicTicker, err := clienttypes.NewDynamicTicker("ZETASupplyTicker", 15) - if err != nil { - return nil, err - } - - app, err := zctx.FromContext(ctx) - if err != nil { - return nil, err - } - - zetaSupplyChecker := &ZetaSupplyChecker{ - stop: make(chan struct{}), - ticker: dynamicTicker, - evmClient: make(map[int64]*ethclient.Client), - logger: logger.With(). - Str("module", "ZetaSupplyChecker"). - Logger(), - zetaClient: zetaClient, - } - - for _, evmConfig := range app.Config().GetAllEVMConfigs() { - if evmConfig.Chain.IsZetaChain() { - continue - } - client, err := ethclient.Dial(evmConfig.Endpoint) - if err != nil { - return nil, err - } - - zetaSupplyChecker.evmClient[evmConfig.Chain.ChainId] = client - } - - for chainID := range zetaSupplyChecker.evmClient { - chain, found := chains.GetChainFromChainID(chainID, app.GetAdditionalChains()) - if !found { - return zetaSupplyChecker, fmt.Errorf("chain not found for chain id %d", chainID) - } - if chain.IsExternalChain() && chain.IsEVMChain() && - chain.Network != chains.Network_eth { - zetaSupplyChecker.externalEvmChain = append(zetaSupplyChecker.externalEvmChain, chain) - } else { - zetaSupplyChecker.ethereumChain = chain - } - } - - balances, err := zetaSupplyChecker.zetaClient.GetGenesisSupply(ctx) - if err != nil { - return nil, err - } - - tokensMintedAtBeginBlock, ok := sdkmath.NewIntFromString("200000000000000000") - if !ok { - return nil, fmt.Errorf("error parsing tokens minted at begin block") - } - - zetaSupplyChecker.genesisSupply = balances.Add(tokensMintedAtBeginBlock) - - logger.Info(). - Msgf("zeta supply checker initialized , external chains : %v ,ethereum chain :%v", zetaSupplyChecker.externalEvmChain, zetaSupplyChecker.ethereumChain) - - return zetaSupplyChecker, nil -} - -// Start starts the ZetaSupplyChecker -func (zs *ZetaSupplyChecker) Start(ctx context.Context) { - defer zs.ticker.Stop() - for { - select { - case <-zs.ticker.C(): - err := zs.CheckZetaTokenSupply(ctx) - if err != nil { - zs.logger.Error().Err(err).Msgf("ZetaSupplyChecker error") - } - case <-zs.stop: - return - } - } -} - -// Stop stops the ZetaSupplyChecker -func (zs *ZetaSupplyChecker) Stop() { - zs.logger.Info().Msgf("ZetaSupplyChecker is stopping") - close(zs.stop) -} - -// CheckZetaTokenSupply checks the total supply of Zeta tokens -func (zs *ZetaSupplyChecker) CheckZetaTokenSupply(ctx context.Context) error { - app, err := zctx.FromContext(ctx) - if err != nil { - return err - } - - externalChainTotalSupply := sdkmath.ZeroInt() - for _, chain := range zs.externalEvmChain { - externalEvmChainParams, ok := app.GetEVMChainParams(chain.ChainId) - if !ok { - return fmt.Errorf("externalEvmChainParams not found for chain id %d", chain.ChainId) - } - - zetaTokenAddressString := externalEvmChainParams.ZetaTokenContractAddress - zetaTokenAddress := ethcommon.HexToAddress(zetaTokenAddressString) - zetatokenNonEth, err := observer.FetchZetaTokenContract(zetaTokenAddress, zs.evmClient[chain.ChainId]) - if err != nil { - return err - } - - totalSupply, err := zetatokenNonEth.TotalSupply(nil) - if err != nil { - return err - } - - totalSupplyInt, ok := sdkmath.NewIntFromString(totalSupply.String()) - if !ok { - zs.logger.Error().Msgf("error parsing total supply for chain %d", chain.ChainId) - continue - } - - externalChainTotalSupply = externalChainTotalSupply.Add(totalSupplyInt) - } - - evmChainParams, ok := app.GetEVMChainParams(zs.ethereumChain.ChainId) - if !ok { - return fmt.Errorf("eth config not found for chain id %d", zs.ethereumChain.ChainId) - } - - ethConnectorAddressString := evmChainParams.ConnectorContractAddress - ethConnectorAddress := ethcommon.HexToAddress(ethConnectorAddressString) - ethConnectorContract, err := observer.FetchConnectorContractEth( - ethConnectorAddress, - zs.evmClient[zs.ethereumChain.ChainId], - ) - if err != nil { - return err - } - - ethLockedAmount, err := ethConnectorContract.GetLockedAmount(nil) - if err != nil { - return err - } - - ethLockedAmountInt, ok := sdkmath.NewIntFromString(ethLockedAmount.String()) - if !ok { - return fmt.Errorf("error parsing eth locked amount") - } - - zetaInTransit, err := zs.GetAmountOfZetaInTransit(ctx) - if err != nil { - return err - } - zetaTokenSupplyOnNode, err := zs.zetaClient.GetZetaTokenSupplyOnNode(ctx) - if err != nil { - return err - } - - abortedAmount, err := zs.AbortedTxAmount(ctx) - if err != nil { - return err - } - - ValidateZetaSupply( - zs.logger, - abortedAmount, - zetaInTransit, - zs.genesisSupply, - externalChainTotalSupply, - zetaTokenSupplyOnNode, - ethLockedAmountInt, - ) - - return nil -} - -// AbortedTxAmount returns the amount of Zeta tokens in aborted transactions -func (zs *ZetaSupplyChecker) AbortedTxAmount(ctx context.Context) (sdkmath.Int, error) { - amount, err := zs.zetaClient.GetAbortedZetaAmount(ctx) - if err != nil { - return sdkmath.ZeroInt(), errors.Wrap(err, "error getting aborted zeta amount") - } - amountInt, ok := sdkmath.NewIntFromString(amount) - if !ok { - return sdkmath.ZeroInt(), errors.New("error parsing aborted zeta amount") - } - return amountInt, nil -} - -// GetAmountOfZetaInTransit returns the amount of Zeta tokens in transit -func (zs *ZetaSupplyChecker) GetAmountOfZetaInTransit(ctx context.Context) (sdkmath.Int, error) { - chainsToCheck := make([]chains.Chain, len(zs.externalEvmChain)+1) - chainsToCheck = append(append(chainsToCheck, zs.externalEvmChain...), zs.ethereumChain) - cctxs := zs.GetPendingCCTXInTransit(ctx, chainsToCheck) - amount := sdkmath.ZeroUint() - - for _, cctx := range cctxs { - amount = amount.Add(cctx.GetCurrentOutboundParam().Amount) - } - amountInt, ok := sdkmath.NewIntFromString(amount.String()) - if !ok { - return sdkmath.ZeroInt(), fmt.Errorf("error parsing amount %s", amount.String()) - } - - return amountInt, nil -} - -// GetPendingCCTXInTransit returns the pending CCTX in transit -func (zs *ZetaSupplyChecker) GetPendingCCTXInTransit( - ctx context.Context, - receivingChains []chains.Chain, -) []*types.CrossChainTx { - cctxInTransit := make([]*types.CrossChainTx, 0) - for _, chain := range receivingChains { - cctx, _, err := zs.zetaClient.ListPendingCCTX(ctx, chain.ChainId) - if err != nil { - continue - } - nonceToCctxMap := make(map[uint64]*types.CrossChainTx) - for _, c := range cctx { - if c.InboundParams.CoinType == coin.CoinType_Zeta { - nonceToCctxMap[c.GetCurrentOutboundParam().TssNonce] = c - } - } - - trackers, err := zs.zetaClient.GetAllOutboundTrackerByChain(ctx, chain.ChainId, interfaces.Ascending) - if err != nil { - continue - } - for _, tracker := range trackers { - zs.logger.Info().Msgf("tracker exists for nonce: %d , removing from supply checks", tracker.Nonce) - delete(nonceToCctxMap, tracker.Nonce) - } - for _, c := range nonceToCctxMap { - if c != nil { - cctxInTransit = append(cctxInTransit, c) - } - } - } - - return cctxInTransit -} diff --git a/zetaclient/supplychecker/zeta_supply_checker_test.go b/zetaclient/supplychecker/zeta_supply_checker_test.go deleted file mode 100644 index ed984de2d8..0000000000 --- a/zetaclient/supplychecker/zeta_supply_checker_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package supplychecker - -import ( - "os" - "testing" - - sdkmath "cosmossdk.io/math" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" -) - -func MustNewIntFromString(t *testing.T, val string) sdkmath.Int { - v, ok := sdkmath.NewIntFromString(val) - require.True(t, ok) - return v -} -func TestZetaSupplyChecker_ValidateZetaSupply(t *testing.T) { - tt := []struct { - name string - abortedTxAmount sdkmath.Int - zetaInTransit sdkmath.Int - genesisAmounts sdkmath.Int - externalChainTotalSupply sdkmath.Int - zetaTokenSupplyOnNode sdkmath.Int - ethLockedAmount sdkmath.Int - validate require.BoolAssertionFunc - }{ - { - name: "1 zeta cctx in progress", - abortedTxAmount: MustNewIntFromString(t, "0"), - zetaInTransit: MustNewIntFromString(t, "1000000000000000000"), - externalChainTotalSupply: MustNewIntFromString(t, "9000000000000000000"), - genesisAmounts: MustNewIntFromString(t, "1000000000000000000"), - zetaTokenSupplyOnNode: MustNewIntFromString(t, "1000000000000000000"), - ethLockedAmount: MustNewIntFromString(t, "10000000000000000000"), - validate: func(t require.TestingT, b bool, i ...interface{}) { - require.True(t, b, i...) - }, - }, - // Todo add more scenarios - //https://github.com/zeta-chain/node/issues/1375 - } - - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - logger := zerolog.New(os.Stdout).With().Timestamp().Logger() - tc.validate( - t, - ValidateZetaSupply( - logger, - tc.abortedTxAmount, - tc.zetaInTransit, - tc.genesisAmounts, - tc.externalChainTotalSupply, - tc.zetaTokenSupplyOnNode, - tc.ethLockedAmount, - ), - ) - }) - } -} diff --git a/zetaclient/testutils/mocks/chain_params.go b/zetaclient/testutils/mocks/chain_params.go index 45c5df2497..19603eda34 100644 --- a/zetaclient/testutils/mocks/chain_params.go +++ b/zetaclient/testutils/mocks/chain_params.go @@ -13,11 +13,32 @@ import ( ) func MockChainParams(chainID int64, confirmation uint64) observertypes.ChainParams { + const zeroAddress = "0x0000000000000000000000000000000000000000" + + connectorAddr := zeroAddress + if a, ok := testutils.ConnectorAddresses[chainID]; ok { + connectorAddr = a.Hex() + } + + erc20CustodyAddr := zeroAddress + if a, ok := testutils.CustodyAddresses[chainID]; ok { + erc20CustodyAddr = a.Hex() + } + return observertypes.ChainParams{ ChainId: chainID, ConfirmationCount: confirmation, - ConnectorContractAddress: testutils.ConnectorAddresses[chainID].Hex(), - Erc20CustodyContractAddress: testutils.CustodyAddresses[chainID].Hex(), + ZetaTokenContractAddress: zeroAddress, + ConnectorContractAddress: connectorAddr, + Erc20CustodyContractAddress: erc20CustodyAddr, + InboundTicker: 12, + OutboundTicker: 15, + WatchUtxoTicker: 0, + GasPriceTicker: 30, + OutboundScheduleInterval: 30, + OutboundScheduleLookahead: 60, + BallotThreshold: observertypes.DefaultBallotThreshold, + MinObserverDelegation: observertypes.DefaultMinObserverDelegation, IsSupported: true, } } diff --git a/zetaclient/testutils/mocks/zetacore_client.go b/zetaclient/testutils/mocks/zetacore_client.go index b1dbd3f741..168b580ada 100644 --- a/zetaclient/testutils/mocks/zetacore_client.go +++ b/zetaclient/testutils/mocks/zetacore_client.go @@ -1,10 +1,10 @@ -// Code generated by mockery v2.38.0. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( - blame "gitlab.com/thorchain/tss/go-tss/blame" chains "github.com/zeta-chain/zetacore/pkg/chains" + blame "gitlab.com/thorchain/tss/go-tss/blame" context "context" @@ -283,24 +283,22 @@ func (_m *ZetacoreClient) GetInboundTrackersForChain(ctx context.Context, chainI } // GetKeyGen provides a mock function with given fields: ctx -func (_m *ZetacoreClient) GetKeyGen(ctx context.Context) (*observertypes.Keygen, error) { +func (_m *ZetacoreClient) GetKeyGen(ctx context.Context) (observertypes.Keygen, error) { ret := _m.Called(ctx) if len(ret) == 0 { panic("no return value specified for GetKeyGen") } - var r0 *observertypes.Keygen + var r0 observertypes.Keygen var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (*observertypes.Keygen, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context) (observertypes.Keygen, error)); ok { return rf(ctx) } - if rf, ok := ret.Get(0).(func(context.Context) *observertypes.Keygen); ok { + if rf, ok := ret.Get(0).(func(context.Context) observertypes.Keygen); ok { r0 = rf(ctx) } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*observertypes.Keygen) - } + r0 = ret.Get(0).(observertypes.Keygen) } if rf, ok := ret.Get(1).(func(context.Context) error); ok { diff --git a/zetaclient/zetacore/client.go b/zetaclient/zetacore/client.go index 8789dce26b..0806c709fe 100644 --- a/zetaclient/zetacore/client.go +++ b/zetaclient/zetacore/client.go @@ -341,20 +341,15 @@ func (c *Client) WaitForZetacoreToCreateBlocks(ctx context.Context) error { // UpdateAppContext updates zctx.AppContext // zetacore stores AppContext for all clients -func (c *Client) UpdateAppContext( - ctx context.Context, - appContext *zctx.AppContext, - init bool, - sampledLogger zerolog.Logger, -) error { +func (c *Client) UpdateAppContext(ctx context.Context, appContext *zctx.AppContext, logger zerolog.Logger) error { bn, err := c.GetBlockHeight(ctx) if err != nil { - return fmt.Errorf("failed to get zetablock height: %w", err) + return errors.Wrap(err, "unable to get zetablock height") } plan, err := c.GetUpgradePlan(ctx) if err != nil { - return fmt.Errorf("failed to get upgrade plan: %w", err) + return errors.Wrap(err, "unable to get upgrade plan") } // Stop client and notify dependant services to stop (Orchestrator, Observers, and Signers) @@ -367,87 +362,72 @@ func (c *Client) UpdateAppContext( ) c.Stop() - } - additionalChains, err := c.GetAdditionalChains(ctx) - if err != nil { - return fmt.Errorf("failed to additional chains: %w", err) + return nil } - chainParams, err := c.GetChainParams(ctx) + supportedChains, err := c.GetSupportedChains(ctx) if err != nil { - return fmt.Errorf("failed to get chain params: %w", err) + return errors.Wrap(err, "unable to fetch supported chains") } - newEVMParams := make(map[int64]*observertypes.ChainParams) - var newBTCParams *observertypes.ChainParams - var newSolanaParams *observertypes.ChainParams - - // check and update chain params for each chain - for _, chainParam := range chainParams { - err := observertypes.ValidateChainParams(chainParam) - if err != nil { - sampledLogger.Warn().Err(err).Msgf("Invalid chain params for chain %d", chainParam.ChainId) - continue - } - if chains.IsBitcoinChain(chainParam.ChainId, additionalChains) { - newBTCParams = chainParam - } else if chains.IsSolanaChain(chainParam.ChainId, additionalChains) { - newSolanaParams = chainParam - } else if chains.IsEVMChain(chainParam.ChainId, additionalChains) { - newEVMParams[chainParam.ChainId] = chainParam - } - } - - supportedChains, err := c.GetSupportedChains(ctx) + additionalChains, err := c.GetAdditionalChains(ctx) if err != nil { - return fmt.Errorf("failed to get supported chains: %w", err) + return errors.Wrap(err, "unable to fetch additional chains") } - newChains := make([]chains.Chain, len(supportedChains)) - for i, chain := range supportedChains { - newChains[i] = chain + chainParams, err := c.GetChainParams(ctx) + if err != nil { + return errors.Wrap(err, "unable to fetch chain params") } keyGen, err := c.GetKeyGen(ctx) if err != nil { - c.logger.Info().Msg("Unable to fetch keygen from zetacore") - return fmt.Errorf("failed to get keygen: %w", err) + return errors.Wrap(err, "unable to fetch keygen from zetacore") } - tss, err := c.GetCurrentTSS(ctx) + crosschainFlags, err := c.GetCrosschainFlags(ctx) if err != nil { - c.logger.Info().Err(err).Msg("Unable to fetch TSS from zetacore") - return fmt.Errorf("failed to get current tss: %w", err) + return errors.Wrap(err, "unable to fetch crosschain flags from zetacore") } - tssPubKey := tss.GetTssPubkey() - crosschainFlags, err := c.GetCrosschainFlags(ctx) + tss, err := c.GetCurrentTSS(ctx) if err != nil { - c.logger.Info().Msg("Unable to fetch cross-chain flags from zetacore") - return fmt.Errorf("failed to get crosschain flags: %w", err) + return errors.Wrap(err, "unable to fetch current TSS") } - blockHeaderEnabledChains, err := c.GetBlockHeaderEnabledChains(ctx) - if err != nil { - c.logger.Info().Msg("Unable to fetch block header enabled chains from zetacore") - return err + freshParams := make(map[int64]*observertypes.ChainParams, len(chainParams)) + + // check and update chain params for each chain + // Note that we are EXCLUDING ZetaChain from the chainParams if it's present + for i := range chainParams { + cp := chainParams[i] + + if !cp.IsSupported { + logger.Warn().Int64("chain.id", cp.ChainId).Msg("Skipping unsupported chain") + continue + } + + if chains.IsZetaChain(cp.ChainId, nil) { + continue + } + + if err := observertypes.ValidateChainParams(cp); err != nil { + logger.Warn().Err(err).Int64("chain.id", cp.ChainId).Msg("Skipping invalid chain params") + continue + } + + freshParams[cp.ChainId] = cp } - appContext.Update( + return appContext.Update( keyGen, - newChains, - newEVMParams, - newBTCParams, - newSolanaParams, - tssPubKey, - crosschainFlags, + supportedChains, additionalChains, - blockHeaderEnabledChains, - init, + freshParams, + tss.GetTssPubkey(), + crosschainFlags, ) - - return nil } func cosmosREST(host string) string { diff --git a/zetaclient/zetacore/client_query_observer.go b/zetaclient/zetacore/client_query_observer.go index 45082aae16..4d98ca848e 100644 --- a/zetaclient/zetacore/client_query_observer.go +++ b/zetaclient/zetacore/client_query_observer.go @@ -2,6 +2,7 @@ package zetacore import ( "context" + "fmt" "cosmossdk.io/errors" @@ -95,18 +96,21 @@ func (c *Client) GetNonceByChain(ctx context.Context, chain chains.Chain) (types } // GetKeyGen returns the keygen -func (c *Client) GetKeyGen(ctx context.Context) (*types.Keygen, error) { +func (c *Client) GetKeyGen(ctx context.Context) (types.Keygen, error) { in := &types.QueryGetKeygenRequest{} resp, err := retry.DoTypedWithRetry(func() (*types.QueryGetKeygenResponse, error) { return c.client.observer.Keygen(ctx, in) }) - if err != nil { - return nil, errors.Wrap(err, "failed to get keygen") + switch { + case err != nil: + return types.Keygen{}, errors.Wrap(err, "failed to get keygen") + case resp.Keygen == nil: + return types.Keygen{}, fmt.Errorf("keygen is nil") } - return resp.GetKeygen(), nil + return *resp.Keygen, nil } // GetAllNodeAccounts returns all node accounts diff --git a/zetaclient/zetacore/client_query_test.go b/zetaclient/zetacore/client_query_test.go index 2b6a2a1c9c..ae995b069d 100644 --- a/zetaclient/zetacore/client_query_test.go +++ b/zetaclient/zetacore/client_query_test.go @@ -626,7 +626,7 @@ func TestZetacore_GetKeyGen(t *testing.T) { resp, err := client.GetKeyGen(ctx) require.NoError(t, err) - require.Equal(t, expectedOutput.Keygen, resp) + require.Equal(t, *expectedOutput.Keygen, resp) } func TestZetacore_GetBallotByID(t *testing.T) { diff --git a/zetaclient/zetacore/client_worker.go b/zetaclient/zetacore/client_worker.go index 05029a9a22..fcf02766a0 100644 --- a/zetaclient/zetacore/client_worker.go +++ b/zetaclient/zetacore/client_worker.go @@ -32,7 +32,7 @@ func (c *Client) UpdateAppContextWorker(ctx context.Context, app *appcontext.App select { case <-ticker.C: c.logger.Debug().Msg("UpdateAppContextWorker invocation") - if err := c.UpdateAppContext(ctx, app, false, logger); err != nil { + if err := c.UpdateAppContext(ctx, app, logger); err != nil { c.logger.Err(err).Msg("UpdateAppContextWorker failed to update config") } case <-c.stop: diff --git a/zetaclient/zetacore/tx_test.go b/zetaclient/zetacore/tx_test.go index e5100ef3c7..a01c11be2b 100644 --- a/zetaclient/zetacore/tx_test.go +++ b/zetaclient/zetacore/tx_test.go @@ -227,6 +227,8 @@ func TestZetacore_UpdateAppContext(t *testing.T) { listener, err := net.Listen("tcp", "127.0.0.1:9090") require.NoError(t, err) + ethChainParams := mocks.MockChainParams(chains.Ethereum.ChainId, 100) + server := grpcmock.MockUnstartedServer( grpcmock.RegisterService(crosschaintypes.RegisterQueryServer), grpcmock.RegisterService(upgradetypes.RegisterQueryServer), @@ -259,9 +261,8 @@ func TestZetacore_UpdateAppContext(t *testing.T) { WithPayload(observertypes.QueryGetChainParamsRequest{}). Return(observertypes.QueryGetChainParamsResponse{ChainParams: &observertypes.ChainParamsList{ ChainParams: []*observertypes.ChainParams{ - { - ChainId: 7000, - }, + {ChainId: 7000}, // ZetaChain + ðChainParams, }, }}) @@ -329,21 +330,6 @@ func TestZetacore_UpdateAppContext(t *testing.T) { GasPriceIncreaseFlags: nil, }}) - method = "/zetachain.zetacore.lightclient.Query/HeaderEnabledChains" - s.ExpectUnary(method). - UnlimitedTimes(). - WithPayload(lightclienttypes.QueryHeaderEnabledChainsRequest{}). - Return(lightclienttypes.QueryHeaderEnabledChainsResponse{HeaderEnabledChains: []lightclienttypes.HeaderSupportedChain{ - { - ChainId: chains.Ethereum.ChainId, - Enabled: true, - }, - { - ChainId: chains.BitcoinMainnet.ChainId, - Enabled: false, - }, - }}) - method = "/zetachain.zetacore.authority.Query/ChainInfo" s.ExpectUnary(method). UnlimitedTimes(). @@ -372,7 +358,7 @@ func TestZetacore_UpdateAppContext(t *testing.T) { t.Run("zetacore update success", func(t *testing.T) { cfg := config.New(false) appContext := zctx.New(cfg, zerolog.Nop()) - err := client.UpdateAppContext(ctx, appContext, false, zerolog.Logger{}) + err := client.UpdateAppContext(ctx, appContext, zerolog.New(zerolog.NewTestWriter(t))) require.NoError(t, err) }) }