diff --git a/Makefile b/Makefile index 82e9c89a0a..9d399507e6 100644 --- a/Makefile +++ b/Makefile @@ -327,7 +327,7 @@ ifdef UPGRADE_TEST_FROM_SOURCE zetanode-upgrade: e2e-images @echo "Building zetanode-upgrade from source" $(DOCKER) build -t zetanode:old -f Dockerfile-localnet --target old-runtime-source \ - --build-arg OLD_VERSION='release/v22' \ + --build-arg OLD_VERSION='release/v23' \ --build-arg NODE_VERSION=$(NODE_VERSION) \ --build-arg NODE_COMMIT=$(NODE_COMMIT) . @@ -336,7 +336,7 @@ else zetanode-upgrade: e2e-images @echo "Building zetanode-upgrade from binaries" $(DOCKER) build -t zetanode:old -f Dockerfile-localnet --target old-runtime \ - --build-arg OLD_VERSION='https://github.com/zeta-chain/node/releases/download/v22.1.1' \ + --build-arg OLD_VERSION='https://github.com/zeta-chain/node/releases/download/v23.1.5' \ --build-arg NODE_VERSION=$(NODE_VERSION) \ --build-arg NODE_COMMIT=$(NODE_COMMIT) \ . @@ -409,7 +409,7 @@ test-sim-fullappsimulation: $(call run-sim-test,"TestFullAppSimulation",TestFullAppSimulation,100,200,30m) test-sim-import-export: - $(call run-sim-test,"test-import-export",TestAppImportExport,100,200,30m) + $(call run-sim-test,"test-import-export",TestAppImportExport,50,100,30m) test-sim-after-import: $(call run-sim-test,"test-sim-after-import",TestAppSimulationAfterImport,100,200,30m) diff --git a/app/app.go b/app/app.go index 90bbe8576b..c79552bc74 100644 --- a/app/app.go +++ b/app/app.go @@ -481,6 +481,8 @@ func New( app.SlashingKeeper, app.AuthorityKeeper, app.LightclientKeeper, + app.BankKeeper, + app.AccountKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) diff --git a/app/modules.go b/app/modules.go index 6f164a9409..0feeb7cb9a 100644 --- a/app/modules.go +++ b/app/modules.go @@ -178,5 +178,8 @@ func simulationModules( evm.NewAppModule(app.EvmKeeper, app.AccountKeeper, app.GetSubspace(evmtypes.ModuleName)), authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), groupmodule.NewAppModule(appCodec, app.GroupKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), + crosschainmodule.NewAppModule(appCodec, app.CrosschainKeeper), + observermodule.NewAppModule(appCodec, *app.ObserverKeeper), + fungiblemodule.NewAppModule(appCodec, app.FungibleKeeper), } } diff --git a/changelog.md b/changelog.md index d43dda7069..14baffeb92 100644 --- a/changelog.md +++ b/changelog.md @@ -10,6 +10,7 @@ * [3205](https://github.com/zeta-chain/node/issues/3205) - move Bitcoin revert address test to advanced group to avoid upgrade test failure * [3254](https://github.com/zeta-chain/node/pull/3254) - rename v2 E2E tests as evm tests and rename old evm tests as legacy +* [3095](https://github.com/zeta-chain/node/pull/3095) - initialize simulation tests for custom zetachain modules ## Refactor diff --git a/cmd/zetaclientd/inbound.go b/cmd/zetaclientd/inbound.go index 20a3422a37..ee602357fa 100644 --- a/cmd/zetaclientd/inbound.go +++ b/cmd/zetaclientd/inbound.go @@ -7,20 +7,19 @@ import ( "strings" "cosmossdk.io/errors" - "github.com/btcsuite/btcd/rpcclient" sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/onrik/ethrpc" "github.com/rs/zerolog" "github.com/spf13/cobra" "github.com/zeta-chain/node/pkg/coin" "github.com/zeta-chain/node/testutil/sample" + "github.com/zeta-chain/node/zetaclient/chains/base" btcobserver "github.com/zeta-chain/node/zetaclient/chains/bitcoin/observer" evmobserver "github.com/zeta-chain/node/zetaclient/chains/evm/observer" "github.com/zeta-chain/node/zetaclient/config" zctx "github.com/zeta-chain/node/zetaclient/context" + "github.com/zeta-chain/node/zetaclient/db" "github.com/zeta-chain/node/zetaclient/keys" "github.com/zeta-chain/node/zetaclient/orchestrator" "github.com/zeta-chain/node/zetaclient/zetacore" @@ -87,27 +86,26 @@ func InboundGetBallot(_ *cobra.Command, args []string) error { return err } - chainProto := chain.RawChain() + baseLogger := base.Logger{Std: zerolog.Nop(), Compliance: zerolog.Nop()} + + observers, err := orchestrator.CreateChainObserverMap(ctx, client, nil, db.SqliteInMemory, baseLogger, nil) + if err != nil { + return errors.Wrap(err, "failed to create chain observer map") + } // get ballot identifier according to the chain type if chain.IsEVM() { - evmObserver := evmobserver.Observer{} - evmObserver.WithZetacoreClient(client) - var ethRPC *ethrpc.EthRPC - var client *ethclient.Client - coinType := coin.CoinType_Cmd - for chainIDFromConfig, evmConfig := range cfg.GetAllEVMConfigs() { - if chainIDFromConfig == chainID { - ethRPC = ethrpc.NewEthRPC(evmConfig.Endpoint) - client, err = ethclient.Dial(evmConfig.Endpoint) - if err != nil { - return err - } - evmObserver.WithEvmClient(client) - evmObserver.WithEvmJSONRPC(ethRPC) - evmObserver.WithChain(*chainProto) - } + observer, ok := observers[chainID] + if !ok { + return fmt.Errorf("observer not found for evm chain %d", chain.ID()) + } + + evmObserver, ok := observer.(*evmobserver.Observer) + if !ok { + return fmt.Errorf("observer is not evm observer for chain %d", chain.ID()) } + + coinType := coin.CoinType_Cmd hash := ethcommon.HexToHash(inboundHash) tx, isPending, err := evmObserver.TransactionByHash(inboundHash) if err != nil { @@ -118,7 +116,7 @@ func InboundGetBallot(_ *cobra.Command, args []string) error { return fmt.Errorf("tx is still pending") } - receipt, err := client.TransactionReceipt(context.Background(), hash) + receipt, err := evmObserver.TransactionReceipt(ctx, hash) if err != nil { return fmt.Errorf("tx receipt not found on chain %s, %d", err.Error(), chain.ID()) } @@ -158,33 +156,23 @@ func InboundGetBallot(_ *cobra.Command, args []string) error { } fmt.Println("CoinType : ", coinType) } else if chain.IsBitcoin() { - btcObserver := btcobserver.Observer{} - btcObserver.WithZetacoreClient(client) - btcObserver.WithChain(*chainProto) - btcConfig, found := cfg.GetBTCConfig(chainID) - if !found { - return fmt.Errorf("unable to find config for BTC chain %d", chainID) - } - connCfg := &rpcclient.ConnConfig{ - Host: btcConfig.RPCHost, - User: btcConfig.RPCUsername, - Pass: btcConfig.RPCPassword, - HTTPPostMode: true, - DisableTLS: true, - Params: btcConfig.RPCParams, + observer, ok := observers[chainID] + if !ok { + return fmt.Errorf("observer not found for btc chain %d", chainID) } - btcClient, err := rpcclient.New(connCfg, nil) - if err != nil { - return err + btcObserver, ok := observer.(*btcobserver.Observer) + if !ok { + return fmt.Errorf("observer is not btc observer for chain %d", chainID) } - btcObserver.WithBtcClient(btcClient) + ballotIdentifier, err = btcObserver.CheckReceiptForBtcTxHash(ctx, inboundHash, false) if err != nil { return err } } - fmt.Println("BallotIdentifier : ", ballotIdentifier) + + fmt.Println("BallotIdentifier: ", ballotIdentifier) // query ballot ballot, err := client.GetBallot(ctx, ballotIdentifier) @@ -193,9 +181,10 @@ func InboundGetBallot(_ *cobra.Command, args []string) error { } for _, vote := range ballot.Voters { - fmt.Printf("%s : %s \n", vote.VoterAddress, vote.VoteType) + fmt.Printf("%s: %s\n", vote.VoterAddress, vote.VoteType) } - fmt.Println("BallotStatus : ", ballot.BallotStatus) + + fmt.Println("BallotStatus: ", ballot.BallotStatus) return nil } diff --git a/cmd/zetaclientd/start.go b/cmd/zetaclientd/start.go index 76a3c0808c..9906d735fc 100644 --- a/cmd/zetaclientd/start.go +++ b/cmd/zetaclientd/start.go @@ -197,7 +197,7 @@ func Start(_ *cobra.Command, _ []string) error { // CreateSignerMap: This creates a map of all signers for each chain. // Each signer is responsible for signing transactions for a particular chain - signerMap, err := orchestrator.CreateSignerMap(ctx, tss, logger, telemetryServer) + signerMap, err := orchestrator.CreateSignerMap(ctx, tss, logger) if err != nil { log.Error().Err(err).Msg("Unable to create signer map") return err diff --git a/codecov.yml b/codecov.yml index da90e44bd9..092dd4baa4 100644 --- a/codecov.yml +++ b/codecov.yml @@ -52,7 +52,7 @@ ignore: - "x/**/events.go" - "x/**/migrator.go" - "x/**/module_simulation.go" - - "x/**/simulation/**/*" + - "x/**/simulation/*.go" - "**/*.proto" - "**/*.md" - "**/*.yml" diff --git a/contrib/docker-scripts/start.sh b/contrib/docker-scripts/start.sh index 6d79effa57..bb151a49fe 100644 --- a/contrib/docker-scripts/start.sh +++ b/contrib/docker-scripts/start.sh @@ -332,4 +332,4 @@ else logt "Start Network" start_network -fi +fi \ No newline at end of file diff --git a/go.mod b/go.mod index 05a7166c93..e2eac8fdac 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.22.8 require ( cosmossdk.io/errors v1.0.1 - cosmossdk.io/math v1.3.0 + cosmossdk.io/math v1.4.0 cosmossdk.io/tools/rosetta v0.2.1 // indirect github.com/99designs/keyring v1.2.1 github.com/btcsuite/btcd v0.24.2 diff --git a/go.sum b/go.sum index 5862cca9f4..9cd653fcf0 100644 --- a/go.sum +++ b/go.sum @@ -195,8 +195,8 @@ cosmossdk.io/errors v1.0.1 h1:bzu+Kcr0kS/1DuPBtUFdWjzLqyUuCiyHjyJB6srBV/0= cosmossdk.io/errors v1.0.1/go.mod h1:MeelVSZThMi4bEakzhhhE/CKqVv3nOJDA25bIqRDu/U= cosmossdk.io/log v1.4.1 h1:wKdjfDRbDyZRuWa8M+9nuvpVYxrEOwbD/CA8hvhU8QM= cosmossdk.io/log v1.4.1/go.mod h1:k08v0Pyq+gCP6phvdI6RCGhLf/r425UT6Rk/m+o74rU= -cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE= -cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k= +cosmossdk.io/math v1.4.0 h1:XbgExXFnXmF/CccPPEto40gOO7FpWu9yWNAZPN3nkNQ= +cosmossdk.io/math v1.4.0/go.mod h1:O5PkD4apz2jZs4zqFdTr16e1dcaQCc5z6lkEnrrppuk= cosmossdk.io/tools/rosetta v0.2.1 h1:ddOMatOH+pbxWbrGJKRAawdBkPYLfKXutK9IETnjYxw= cosmossdk.io/tools/rosetta v0.2.1/go.mod h1:Pqdc1FdvkNV3LcNIkYWt2RQY6IP1ge6YWZk8MhhO9Hw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -597,8 +597,6 @@ github.com/gagliardetto/binary v0.8.0 h1:U9ahc45v9HW0d15LoN++vIXSJyqR/pWw8DDlhd7 github.com/gagliardetto/binary v0.8.0/go.mod h1:2tfj51g5o9dnvsc+fL3Jxr22MuWzYXwx9wEoN0XQ7/c= github.com/gagliardetto/gofuzz v1.2.2 h1:XL/8qDMzcgvR4+CyRQW9UGdwPRPMHVJfqQ/uMvSUuQw= github.com/gagliardetto/gofuzz v1.2.2/go.mod h1:bkH/3hYLZrMLbfYWA0pWzXmi5TTRZnu4pMGZBkqMKvY= -github.com/gagliardetto/solana-go v1.10.0 h1:lDuHGC+XLxw9j8fCHBZM9tv4trI0PVhev1m9NAMaIdM= -github.com/gagliardetto/solana-go v1.10.0/go.mod h1:afBEcIRrDLJst3lvAahTr63m6W2Ns6dajZxe2irF7Jg= github.com/gagliardetto/solana-go v1.12.0 h1:rzsbilDPj6p+/DOPXBMLhwMZeBgeRuXjm5zQFCoXgsg= github.com/gagliardetto/solana-go v1.12.0/go.mod h1:l/qqqIN6qJJPtxW/G1PF4JtcE3Zg2vD2EliZrr9Gn5k= github.com/gagliardetto/treeout v0.1.4 h1:ozeYerrLCmCubo1TcIjFiOWTTGteOOHND1twdFpgwaw= @@ -1462,7 +1460,6 @@ github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= @@ -1502,9 +1499,7 @@ github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= -github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= @@ -1547,8 +1542,6 @@ go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 h1:qxen9oVGzDdIRP6ejyAJc760RwW4SnVDiTYTzwnXuxo= go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5/go.mod h1:eW0HG9/oHQhvRCvb1/pIXW4cOvtDqeQK+XSi3TnwaXY= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.mongodb.org/mongo-driver v1.11.0 h1:FZKhBSTydeuffHj9CBjXlR8vQLee1cQyTWYPA6/tqiE= -go.mongodb.org/mongo-driver v1.11.0/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= go.mongodb.org/mongo-driver v1.12.2 h1:gbWY1bJkkmUB9jjZzcdhOL8O85N9H+Vvsf2yFN0RDws= go.mongodb.org/mongo-driver v1.12.2/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= go.nhat.io/aferomock v0.4.0 h1:gs3nJzIqAezglUuaPfautAmZwulwRWLcfSSzdK4YCC0= diff --git a/server/start.go b/server/start.go index 14c64482f8..9e2711d265 100644 --- a/server/start.go +++ b/server/start.go @@ -172,7 +172,7 @@ which accepts a path for the resulting pprof file. cmd.Flags(). String(server.FlagMinGasPrices, "", "Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 0.01photon;0.0001stake)") - //nolint:lll + //nolint:lll cmd.Flags(). IntSlice(server.FlagUnsafeSkipUpgrades, []int{}, "Skip a set of upgrade heights to continue the old binary") cmd.Flags(). @@ -189,7 +189,7 @@ which accepts a path for the resulting pprof file. cmd.Flags(). Uint64(server.FlagPruningInterval, 0, "Height interval at which pruned heights are removed from disk (ignored if pruning is not 'custom')") - //nolint:lll + //nolint:lll cmd.Flags().Uint(server.FlagInvCheckPeriod, 0, "Assert registered invariants every N blocks") cmd.Flags(). Uint64(server.FlagMinRetainBlocks, 0, "Minimum block height offset during ABCI commit to prune Tendermint blocks") @@ -217,11 +217,11 @@ which accepts a path for the resulting pprof file. cmd.Flags(). Uint64(srvflags.JSONRPCGasCap, config.DefaultGasCap, "Sets a cap on gas that can be used in eth_call/estimateGas unit is aphoton (0=infinite)") - //nolint:lll + //nolint:lll cmd.Flags(). Float64(srvflags.JSONRPCTxFeeCap, config.DefaultTxFeeCap, "Sets a cap on transaction fee that can be sent via the RPC APIs (1 = default 1 photon)") - //nolint:lll + //nolint:lll cmd.Flags(). Int32(srvflags.JSONRPCFilterCap, config.DefaultFilterCap, "Sets the global cap for total number of filters that can be created") cmd.Flags(). @@ -233,7 +233,7 @@ which accepts a path for the resulting pprof file. cmd.Flags(). Bool(srvflags.JSONRPCAllowUnprotectedTxs, config.DefaultAllowUnprotectedTxs, "Allow for unprotected (non EIP155 signed) transactions to be submitted via the node's RPC when the global parameter is disabled") - //nolint:lll + //nolint:lll cmd.Flags(). Int32(srvflags.JSONRPCLogsCap, config.DefaultLogsCap, "Sets the max number of results can be returned from single `eth_getLogs` query") cmd.Flags(). @@ -241,18 +241,18 @@ which accepts a path for the resulting pprof file. cmd.Flags(). Int(srvflags.JSONRPCMaxOpenConnections, config.DefaultMaxOpenConnections, "Sets the maximum number of simultaneous connections for the server listener") - //nolint:lll + //nolint:lll cmd.Flags().Bool(srvflags.JSONRPCEnableIndexer, false, "Enable the custom tx indexer for json-rpc") cmd.Flags().Bool(srvflags.JSONRPCEnableMetrics, false, "Define if EVM rpc metrics server should be enabled") cmd.Flags(). String(srvflags.EVMTracer, config.DefaultEVMTracer, "the EVM tracer type to collect execution traces from the EVM transaction execution (json|struct|access_list|markdown)") - //nolint:lll + //nolint:lll cmd.Flags(). Uint64(srvflags.EVMMaxTxGasWanted, config.DefaultMaxTxGasWanted, "the gas wanted for each eth tx returned in ante handler in check tx mode") - //nolint:lll + //nolint:lll cmd.Flags().String(srvflags.TLSCertPath, "", "the cert.pem file path for the server TLS configuration") cmd.Flags().String(srvflags.TLSKeyPath, "", "the key.pem file path for the server TLS configuration") diff --git a/simulation/simulation_test.go b/simulation/simulation_test.go index 3f39c77fa1..818250cbaf 100644 --- a/simulation/simulation_test.go +++ b/simulation/simulation_test.go @@ -10,29 +10,30 @@ import ( abci "github.com/cometbft/cometbft/abci/types" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/store" storetypes "github.com/cosmos/cosmos-sdk/store/types" + cosmossimutils "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + cosmossim "github.com/cosmos/cosmos-sdk/types/simulation" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/cosmos/cosmos-sdk/x/simulation" + cosmossimcli "github.com/cosmos/cosmos-sdk/x/simulation/client/cli" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" "github.com/zeta-chain/node/app" zetasimulation "github.com/zeta-chain/node/simulation" - - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/server" - cosmossimutils "github.com/cosmos/cosmos-sdk/testutil/sims" - cosmossim "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" - cosmossimcli "github.com/cosmos/cosmos-sdk/x/simulation/client/cli" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" + fungibletypes "github.com/zeta-chain/node/x/fungible/types" + observertypes "github.com/zeta-chain/node/x/observer/types" ) // AppChainID hardcoded chainID for simulation @@ -41,10 +42,12 @@ func init() { zetasimulation.GetSimulatorFlags() } +// StoreKeysPrefixes defines a struct used in comparing two keys for two different stores +// SkipPrefixes is used to skip certain prefixes when comparing the stores type StoreKeysPrefixes struct { - A storetypes.StoreKey - B storetypes.StoreKey - Prefixes [][]byte + A storetypes.StoreKey + B storetypes.StoreKey + SkipPrefixes [][]byte } const ( @@ -133,9 +136,11 @@ func TestAppStateDeterminism(t *testing.T) { os.Stdout, simApp.BaseApp, zetasimulation.AppStateFn( + t, simApp.AppCodec(), simApp.SimulationManager(), simApp.BasicManager().DefaultGenesis(simApp.AppCodec()), + nil, ), cosmossim.RandomAccounts, cosmossimutils.SimulationOperations(simApp, simApp.AppCodec(), config), @@ -222,9 +227,11 @@ func TestFullAppSimulation(t *testing.T) { os.Stdout, simApp.BaseApp, zetasimulation.AppStateFn( + t, simApp.AppCodec(), simApp.SimulationManager(), simApp.BasicManager().DefaultGenesis(simApp.AppCodec()), + nil, ), cosmossim.RandomAccounts, cosmossimutils.SimulationOperations(simApp, simApp.AppCodec(), config), @@ -292,9 +299,11 @@ func TestAppImportExport(t *testing.T) { os.Stdout, simApp.BaseApp, zetasimulation.AppStateFn( + t, simApp.AppCodec(), simApp.SimulationManager(), simApp.BasicManager().DefaultGenesis(simApp.AppCodec()), + nil, ), cosmossim.RandomAccounts, cosmossimutils.SimulationOperations(simApp, simApp.AppCodec(), config), @@ -314,7 +323,6 @@ func TestAppImportExport(t *testing.T) { exported, err := simApp.ExportAppStateAndValidators(false, []string{}, []string{}) require.NoError(t, err) - t.Log("importing genesis") newDB, newDir, _, _, err := cosmossimutils.SetupSimulation( config, SimDBBackend+"_new", @@ -360,20 +368,29 @@ func TestAppImportExport(t *testing.T) { ctxSimApp := simApp.NewContext(true, tmproto.Header{ Height: simApp.LastBlockHeight(), ChainID: SimAppChainID, - }).WithChainID(SimAppChainID) + }) + ctxNewSimApp := newSimApp.NewContext(true, tmproto.Header{ Height: simApp.LastBlockHeight(), ChainID: SimAppChainID, - }).WithChainID(SimAppChainID) + }) + t.Log("initializing genesis for the new app using exported genesis state") // Use genesis state from the first app to initialize the second app newSimApp.ModuleManager().InitGenesis(ctxNewSimApp, newSimApp.AppCodec(), genesisState) newSimApp.StoreConsensusParams(ctxNewSimApp, exported.ConsensusParams) t.Log("comparing stores") + // The ordering of the keys is not important, we compare the same prefix for both simulations storeKeysPrefixes := []StoreKeysPrefixes{ - {simApp.GetKey(authtypes.StoreKey), newSimApp.GetKey(authtypes.StoreKey), [][]byte{}}, + // Interaction with EVM module, + // such as deploying contracts or interacting with them such as setting gas price, + // causes the state for the auth module to change on export.The order of keys within the store is modified. + // We will need to explore this further to find a definitive answer + // TODO:https://github.com/zeta-chain/node/issues/3263 + + // {simApp.GetKey(authtypes.StoreKey), newSimApp.GetKey(authtypes.StoreKey), [][]byte{}}, { simApp.GetKey(stakingtypes.StoreKey), newSimApp.GetKey(stakingtypes.StoreKey), [][]byte{ @@ -388,13 +405,23 @@ func TestAppImportExport(t *testing.T) { {simApp.GetKey(govtypes.StoreKey), newSimApp.GetKey(govtypes.StoreKey), [][]byte{}}, {simApp.GetKey(evidencetypes.StoreKey), newSimApp.GetKey(evidencetypes.StoreKey), [][]byte{}}, {simApp.GetKey(evmtypes.StoreKey), newSimApp.GetKey(evmtypes.StoreKey), [][]byte{}}, + {simApp.GetKey(crosschaintypes.StoreKey), newSimApp.GetKey(crosschaintypes.StoreKey), [][]byte{ + // We update the timestamp for cctx when importing the genesis state which results in a different value + crosschaintypes.KeyPrefix(crosschaintypes.CCTXKey), + }}, + + {simApp.GetKey(observertypes.StoreKey), newSimApp.GetKey(observertypes.StoreKey), [][]byte{ + // The order of ballots when importing is not preserved which causes the value to be different. + observertypes.KeyPrefix(observertypes.BallotListKey), + }}, + {simApp.GetKey(fungibletypes.StoreKey), newSimApp.GetKey(fungibletypes.StoreKey), [][]byte{}}, } for _, skp := range storeKeysPrefixes { storeA := ctxSimApp.KVStore(skp.A) storeB := ctxNewSimApp.KVStore(skp.B) - failedKVAs, failedKVBs := sdk.DiffKVStores(storeA, storeB, skp.Prefixes) + failedKVAs, failedKVBs := sdk.DiffKVStores(storeA, storeB, skp.SkipPrefixes) require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare") t.Logf("compared %d different key/value pairs between %s and %s\n", len(failedKVAs), skp.A, skp.B) @@ -459,9 +486,11 @@ func TestAppSimulationAfterImport(t *testing.T) { os.Stdout, simApp.BaseApp, zetasimulation.AppStateFn( + t, simApp.AppCodec(), simApp.SimulationManager(), simApp.BasicManager().DefaultGenesis(simApp.AppCodec()), + nil, ), cosmossim.RandomAccounts, cosmossimutils.SimulationOperations(simApp, simApp.AppCodec(), config), @@ -487,8 +516,6 @@ func TestAppSimulationAfterImport(t *testing.T) { exported, err := simApp.ExportAppStateAndValidators(true, []string{}, []string{}) require.NoError(t, err) - t.Log("importing genesis") - newDB, newDir, _, _, err := cosmossimutils.SetupSimulation( config, SimDBBackend+"_new", @@ -517,6 +544,7 @@ func TestAppSimulationAfterImport(t *testing.T) { ) require.NoError(t, err) + t.Log("Importing genesis into the new app") newSimApp.InitChain(abci.RequestInitChain{ ChainId: SimAppChainID, AppStateBytes: exported.AppState, @@ -527,9 +555,11 @@ func TestAppSimulationAfterImport(t *testing.T) { os.Stdout, newSimApp.BaseApp, zetasimulation.AppStateFn( - simApp.AppCodec(), - simApp.SimulationManager(), - simApp.BasicManager().DefaultGenesis(simApp.AppCodec()), + t, + nil, + nil, + nil, + exported.AppState, ), cosmossim.RandomAccounts, cosmossimutils.SimulationOperations(newSimApp, newSimApp.AppCodec(), config), diff --git a/simulation/state.go b/simulation/state.go index 540cd0aca7..9bf2c24949 100644 --- a/simulation/state.go +++ b/simulation/state.go @@ -6,6 +6,7 @@ import ( "io" "math/rand" "os" + "testing" "time" "cosmossdk.io/math" @@ -19,24 +20,228 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/require" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" zetaapp "github.com/zeta-chain/node/app" + "github.com/zeta-chain/node/testutil/sample" + authoritytypes "github.com/zeta-chain/node/x/authority/types" + fungibletypes "github.com/zeta-chain/node/x/fungible/types" + observertypes "github.com/zeta-chain/node/x/observer/types" ) -// Simulation parameter constants +// simulation parameter constants const ( StakePerAccount = "stake_per_account" InitiallyBondedValidators = "initially_bonded_validators" ) +func updateBankState( + t *testing.T, + rawState map[string]json.RawMessage, + cdc codec.Codec, + notBondedCoins sdk.Coin, +) *banktypes.GenesisState { + bankStateBz, ok := rawState[banktypes.ModuleName] + require.True(t, ok, "bank genesis state is missing") + + bankState := new(banktypes.GenesisState) + err := cdc.UnmarshalJSON(bankStateBz, bankState) + require.NoError(t, err) + + stakingAddr := authtypes.NewModuleAddress(stakingtypes.NotBondedPoolName).String() + var found bool + for _, balance := range bankState.Balances { + if balance.Address == stakingAddr { + found = true + break + } + } + if !found { + bankState.Balances = append(bankState.Balances, banktypes.Balance{ + Address: stakingAddr, + Coins: sdk.NewCoins(notBondedCoins), + }) + } + + return bankState +} + +func updateEVMState( + t *testing.T, + rawState map[string]json.RawMessage, + cdc codec.Codec, + bondDenom string, +) *evmtypes.GenesisState { + evmStateBz, ok := rawState[evmtypes.ModuleName] + require.True(t, ok, "evm genesis state is missing") + + evmState := new(evmtypes.GenesisState) + cdc.MustUnmarshalJSON(evmStateBz, evmState) + + // replace the EvmDenom with BondDenom + evmState.Params.EvmDenom = bondDenom + + return evmState +} + +func updateStakingState( + t *testing.T, + rawState map[string]json.RawMessage, + cdc codec.Codec, +) (*stakingtypes.GenesisState, sdk.Coin) { + stakingStateBz, ok := rawState[stakingtypes.ModuleName] + require.True(t, ok, "staking genesis state is missing") + + stakingState := new(stakingtypes.GenesisState) + err := cdc.UnmarshalJSON(stakingStateBz, stakingState) + if err != nil { + panic(err) + } + + // compute not bonded balance + notBondedTokens := math.ZeroInt() + for _, val := range stakingState.Validators { + if val.Status != stakingtypes.Unbonded { + continue + } + notBondedTokens = notBondedTokens.Add(val.GetTokens()) + } + notBondedCoins := sdk.NewCoin(stakingState.Params.BondDenom, notBondedTokens) + + return stakingState, notBondedCoins +} + +func updateObserverState( + t *testing.T, + rawState map[string]json.RawMessage, + cdc codec.Codec, + r *rand.Rand, + validators stakingtypes.Validators, +) *observertypes.GenesisState { + observerStateBz, ok := rawState[observertypes.ModuleName] + require.True(t, ok, "observer genesis state is missing") + + observerState := new(observertypes.GenesisState) + cdc.MustUnmarshalJSON(observerStateBz, observerState) + + observers := make([]string, 0) + for _, validator := range validators { + accAddress, err := observertypes.GetAccAddressFromOperatorAddress(validator.OperatorAddress) + if err != nil { + continue + } + observers = append(observers, accAddress.String()) + } + + r.Shuffle(len(observers), func(i, j int) { + observers[i], observers[j] = observers[j], observers[i] + }) + + numObservers := r.Intn(11) + 5 + if numObservers > len(observers) { + numObservers = len(observers) + } + observers = observers[:numObservers] + + observerState.Observers.ObserverList = observers + observerState.CrosschainFlags.IsInboundEnabled = true + observerState.CrosschainFlags.IsOutboundEnabled = true + + tss := sample.TSSFromRand(t, r) + tss.OperatorAddressList = observers + observerState.Tss = &tss + + return observerState +} + +func updateAuthorityState( + t *testing.T, + rawState map[string]json.RawMessage, + cdc codec.Codec, + r *rand.Rand, + accs []simtypes.Account, +) *authoritytypes.GenesisState { + authorityStateBz, ok := rawState[authoritytypes.ModuleName] + require.True(t, ok, "authority genesis state is missing") + + authorityState := new(authoritytypes.GenesisState) + cdc.MustUnmarshalJSON(authorityStateBz, authorityState) + + randomAccount := accs[r.Intn(len(accs))] + policies := authoritytypes.Policies{ + Items: []*authoritytypes.Policy{ + { + Address: randomAccount.Address.String(), + PolicyType: authoritytypes.PolicyType_groupEmergency, + }, + { + Address: randomAccount.Address.String(), + PolicyType: authoritytypes.PolicyType_groupAdmin, + }, + { + Address: randomAccount.Address.String(), + PolicyType: authoritytypes.PolicyType_groupOperational, + }, + }, + } + authorityState.Policies = policies + + return authorityState +} + +func updateFungibleState( + t *testing.T, + rawState map[string]json.RawMessage, + cdc codec.Codec, + r *rand.Rand, +) *fungibletypes.GenesisState { + fungibleStateBz, ok := rawState[fungibletypes.ModuleName] + require.True(t, ok, "fungible genesis state is missing") + + fungibleState := new(fungibletypes.GenesisState) + cdc.MustUnmarshalJSON(fungibleStateBz, fungibleState) + fungibleState.SystemContract = &fungibletypes.SystemContract{ + SystemContract: sample.EthAddressFromRand(r).String(), + ConnectorZevm: sample.EthAddressFromRand(r).String(), + Gateway: sample.EthAddressFromRand(r).String(), + } + + return fungibleState +} + +func updateRawState( + t *testing.T, + rawState map[string]json.RawMessage, + cdc codec.Codec, + r *rand.Rand, + accs []simtypes.Account, +) { + stakingState, notBondedCoins := updateStakingState(t, rawState, cdc) + bankState := updateBankState(t, rawState, cdc, notBondedCoins) + evmState := updateEVMState(t, rawState, cdc, stakingState.Params.BondDenom) + observerState := updateObserverState(t, rawState, cdc, r, stakingState.Validators) + authorityState := updateAuthorityState(t, rawState, cdc, r, accs) + fungibleState := updateFungibleState(t, rawState, cdc, r) + + rawState[stakingtypes.ModuleName] = cdc.MustMarshalJSON(stakingState) + rawState[banktypes.ModuleName] = cdc.MustMarshalJSON(bankState) + rawState[evmtypes.ModuleName] = cdc.MustMarshalJSON(evmState) + rawState[observertypes.ModuleName] = cdc.MustMarshalJSON(observerState) + rawState[authoritytypes.ModuleName] = cdc.MustMarshalJSON(authorityState) + rawState[fungibletypes.ModuleName] = cdc.MustMarshalJSON(fungibleState) +} + // AppStateFn returns the initial application state using a genesis or the simulation parameters. // It panics if the user provides files for both of them. // If a file is not given for the genesis or the sim params, it creates a randomized one. +// All modifications to the genesis state should be done in this function. func AppStateFn( + t *testing.T, cdc codec.Codec, simManager *module.SimulationManager, genesisState map[string]json.RawMessage, + exportedState json.RawMessage, ) simtypes.AppStateFn { return func(r *rand.Rand, accs []simtypes.Account, config simtypes.Config, ) (appState json.RawMessage, simAccs []simtypes.Account, chainID string, genesisTimestamp time.Time) { @@ -47,135 +252,35 @@ func AppStateFn( } chainID = config.ChainID - switch { - case config.ParamsFile != "" && config.GenesisFile != "": - panic("cannot provide both a genesis file and a params file") - - case config.GenesisFile != "": - // override the default chain-id from simapp to set it later to the config - genesisDoc, accounts, err := AppStateFromGenesisFileFn(r, cdc, config.GenesisFile) - if err != nil { - panic(err) - } - - if FlagGenesisTimeValue == 0 { - // use genesis timestamp if no custom timestamp is provided (i.e no random timestamp) - genesisTimestamp = genesisDoc.GenesisTime - } - - appState = genesisDoc.AppState - chainID = genesisDoc.ChainID - simAccs = accounts - - case config.ParamsFile != "": - appParams := make(simtypes.AppParams) - bz, err := os.ReadFile(config.ParamsFile) - if err != nil { - panic(err) - } - - err = json.Unmarshal(bz, &appParams) - if err != nil { - panic(err) - } - appState, simAccs = AppStateRandomizedFn( - simManager, - r, - cdc, - accs, - genesisTimestamp, - appParams, - genesisState, - ) - - default: - appParams := make(simtypes.AppParams) - appState, simAccs = AppStateRandomizedFn( - simManager, - r, - cdc, - accs, - genesisTimestamp, - appParams, - genesisState, - ) - } - rawState := make(map[string]json.RawMessage) - err := json.Unmarshal(appState, &rawState) - if err != nil { - panic(err) + // if exported state is provided then use it + if exportedState != nil { + return exportedState, accs, chainID, genesisTimestamp } - stakingStateBz, ok := rawState[stakingtypes.ModuleName] - if !ok { - panic("staking genesis state is missing") - } + appParams := make(simtypes.AppParams) + appState, simAccs = AppStateRandomizedFn( + simManager, + r, + cdc, + accs, + genesisTimestamp, + appParams, + genesisState, + ) - stakingState := new(stakingtypes.GenesisState) - err = cdc.UnmarshalJSON(stakingStateBz, stakingState) - if err != nil { - panic(err) - } - - // compute not bonded balance - notBondedTokens := math.ZeroInt() - for _, val := range stakingState.Validators { - if val.Status != stakingtypes.Unbonded { - continue - } - notBondedTokens = notBondedTokens.Add(val.GetTokens()) - } - notBondedCoins := sdk.NewCoin(stakingState.Params.BondDenom, notBondedTokens) - - // edit bank state to make it have the not bonded pool tokens - bankStateBz, ok := rawState[banktypes.ModuleName] - if !ok { - panic("bank genesis state is missing") - } - bankState := new(banktypes.GenesisState) - err = cdc.UnmarshalJSON(bankStateBz, bankState) + rawState := make(map[string]json.RawMessage) + err := json.Unmarshal(appState, &rawState) if err != nil { panic(err) } - stakingAddr := authtypes.NewModuleAddress(stakingtypes.NotBondedPoolName).String() - var found bool - for _, balance := range bankState.Balances { - if balance.Address == stakingAddr { - found = true - break - } - } - if !found { - bankState.Balances = append(bankState.Balances, banktypes.Balance{ - Address: stakingAddr, - Coins: sdk.NewCoins(notBondedCoins), - }) - } - - // Set the bond denom in the EVM genesis state - evmStateBz, ok := rawState[evmtypes.ModuleName] - if !ok { - panic("evm genesis state is missing") - } - - evmState := new(evmtypes.GenesisState) - cdc.MustUnmarshalJSON(evmStateBz, evmState) - - // we should replace the EvmDenom with BondDenom - evmState.Params.EvmDenom = stakingState.Params.BondDenom - - // change appState back - rawState[evmtypes.ModuleName] = cdc.MustMarshalJSON(evmState) - rawState[stakingtypes.ModuleName] = cdc.MustMarshalJSON(stakingState) - rawState[banktypes.ModuleName] = cdc.MustMarshalJSON(bankState) + updateRawState(t, rawState, cdc, r, simAccs) // replace appstate appState, err = json.Marshal(rawState) - if err != nil { - panic(err) - } + require.NoError(t, err) + return appState, simAccs, chainID, genesisTimestamp } } @@ -208,7 +313,7 @@ func AppStateRandomizedFn( numInitiallyBonded = numAccs } - // Set the default power reduction to be one less than the initial stake so that all randomised validators are part of the validator set + // set the default power reduction to be one less than the initial stake so that all randomised validators are part of the validator set sdk.DefaultPowerReduction = initialStake.Sub(sdk.OneInt()) fmt.Printf( diff --git a/testutil/keeper/crosschain.go b/testutil/keeper/crosschain.go index 8418bbdaaf..d0f7d0220c 100644 --- a/testutil/keeper/crosschain.go +++ b/testutil/keeper/crosschain.go @@ -129,6 +129,8 @@ func CrosschainKeeperWithMocks( sdkKeepers.SlashingKeeper, authorityKeeperTmp, lightclientKeeperTmp, + sdkKeepers.BankKeeper, + sdkKeepers.AuthKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) diff --git a/testutil/keeper/emissions.go b/testutil/keeper/emissions.go index b2ce7a004c..bfbe2813d9 100644 --- a/testutil/keeper/emissions.go +++ b/testutil/keeper/emissions.go @@ -54,6 +54,8 @@ func EmissionKeeperWithMockOptions( sdkKeepers.StakingKeeper, sdkKeepers.SlashingKeeper, authorityKeeper, + sdkKeepers.BankKeeper, + sdkKeepers.AuthKeeper, initLightclientKeeper(cdc, stateStore, authorityKeeper), ) diff --git a/testutil/keeper/fungible.go b/testutil/keeper/fungible.go index aa0e975c97..5b51d08934 100644 --- a/testutil/keeper/fungible.go +++ b/testutil/keeper/fungible.go @@ -116,6 +116,8 @@ func FungibleKeeperWithMocks( sdkKeepers.SlashingKeeper, authorityKeeperTmp, lightclientKeeperTmp, + sdkKeepers.BankKeeper, + sdkKeepers.AuthKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) diff --git a/testutil/keeper/ibccrosschain.go b/testutil/keeper/ibccrosschain.go index 95cf7559e7..5bd222141d 100644 --- a/testutil/keeper/ibccrosschain.go +++ b/testutil/keeper/ibccrosschain.go @@ -77,6 +77,8 @@ func IBCCrosschainKeeperWithMocks( sdkKeepers.StakingKeeper, sdkKeepers.SlashingKeeper, authorityKeeper, + sdkKeepers.BankKeeper, + sdkKeepers.AuthKeeper, lightclientKeeper, ) fungibleKeeper := initFungibleKeeper( diff --git a/testutil/keeper/keeper.go b/testutil/keeper/keeper.go index 5819c6b74b..1342cdfac8 100644 --- a/testutil/keeper/keeper.go +++ b/testutil/keeper/keeper.go @@ -525,6 +525,8 @@ func NewSDKKeepersWithKeys( slashingKeeper, authorityKeeper, lightclientKeeper, + bankKeeper, + authKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) emissionsKeeper := emissionskeeper.NewKeeper( diff --git a/testutil/keeper/mocks/crosschain/account.go b/testutil/keeper/mocks/crosschain/account.go index fbd7c0377b..3ff30725e6 100644 --- a/testutil/keeper/mocks/crosschain/account.go +++ b/testutil/keeper/mocks/crosschain/account.go @@ -14,6 +14,26 @@ type CrosschainAccountKeeper struct { mock.Mock } +// GetAccount provides a mock function with given fields: ctx, addr +func (_m *CrosschainAccountKeeper) GetAccount(ctx types.Context, addr types.AccAddress) authtypes.AccountI { + ret := _m.Called(ctx, addr) + + if len(ret) == 0 { + panic("no return value specified for GetAccount") + } + + var r0 authtypes.AccountI + if rf, ok := ret.Get(0).(func(types.Context, types.AccAddress) authtypes.AccountI); ok { + r0 = rf(ctx, addr) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(authtypes.AccountI) + } + } + + return r0 +} + // GetModuleAccount provides a mock function with given fields: ctx, name func (_m *CrosschainAccountKeeper) GetModuleAccount(ctx types.Context, name string) authtypes.ModuleAccountI { ret := _m.Called(ctx, name) diff --git a/testutil/keeper/mocks/crosschain/bank.go b/testutil/keeper/mocks/crosschain/bank.go index 90f4e17e29..7e6a3ae058 100644 --- a/testutil/keeper/mocks/crosschain/bank.go +++ b/testutil/keeper/mocks/crosschain/bank.go @@ -49,6 +49,26 @@ func (_m *CrosschainBankKeeper) MintCoins(ctx types.Context, moduleName string, return r0 } +// SpendableCoins provides a mock function with given fields: ctx, addr +func (_m *CrosschainBankKeeper) SpendableCoins(ctx types.Context, addr types.AccAddress) types.Coins { + ret := _m.Called(ctx, addr) + + if len(ret) == 0 { + panic("no return value specified for SpendableCoins") + } + + var r0 types.Coins + if rf, ok := ret.Get(0).(func(types.Context, types.AccAddress) types.Coins); ok { + r0 = rf(ctx, addr) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.Coins) + } + } + + return r0 +} + // NewCrosschainBankKeeper creates a new instance of CrosschainBankKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewCrosschainBankKeeper(t interface { diff --git a/testutil/keeper/mocks/fungible/authority.go b/testutil/keeper/mocks/fungible/authority.go index 214834ac86..ccf891e5f7 100644 --- a/testutil/keeper/mocks/fungible/authority.go +++ b/testutil/keeper/mocks/fungible/authority.go @@ -3,8 +3,10 @@ package mocks import ( - mock "github.com/stretchr/testify/mock" chains "github.com/zeta-chain/node/pkg/chains" + authoritytypes "github.com/zeta-chain/node/x/authority/types" + + mock "github.com/stretchr/testify/mock" types "github.com/cosmos/cosmos-sdk/types" ) @@ -52,6 +54,34 @@ func (_m *FungibleAuthorityKeeper) GetAdditionalChainList(ctx types.Context) []c return r0 } +// GetPolicies provides a mock function with given fields: ctx +func (_m *FungibleAuthorityKeeper) GetPolicies(ctx types.Context) (authoritytypes.Policies, bool) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetPolicies") + } + + var r0 authoritytypes.Policies + var r1 bool + if rf, ok := ret.Get(0).(func(types.Context) (authoritytypes.Policies, bool)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(types.Context) authoritytypes.Policies); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(authoritytypes.Policies) + } + + if rf, ok := ret.Get(1).(func(types.Context) bool); ok { + r1 = rf(ctx) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + // NewFungibleAuthorityKeeper creates a new instance of FungibleAuthorityKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewFungibleAuthorityKeeper(t interface { diff --git a/testutil/keeper/mocks/fungible/bank.go b/testutil/keeper/mocks/fungible/bank.go index 1c46b35688..26c07bb9ad 100644 --- a/testutil/keeper/mocks/fungible/bank.go +++ b/testutil/keeper/mocks/fungible/bank.go @@ -67,6 +67,26 @@ func (_m *FungibleBankKeeper) SendCoinsFromModuleToAccount(ctx types.Context, se return r0 } +// SpendableCoins provides a mock function with given fields: ctx, addr +func (_m *FungibleBankKeeper) SpendableCoins(ctx types.Context, addr types.AccAddress) types.Coins { + ret := _m.Called(ctx, addr) + + if len(ret) == 0 { + panic("no return value specified for SpendableCoins") + } + + var r0 types.Coins + if rf, ok := ret.Get(0).(func(types.Context, types.AccAddress) types.Coins); ok { + r0 = rf(ctx, addr) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.Coins) + } + } + + return r0 +} + // NewFungibleBankKeeper creates a new instance of FungibleBankKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewFungibleBankKeeper(t interface { diff --git a/testutil/keeper/mocks/observer/authority.go b/testutil/keeper/mocks/observer/authority.go index 24a105fb4d..0bfc08f9fe 100644 --- a/testutil/keeper/mocks/observer/authority.go +++ b/testutil/keeper/mocks/observer/authority.go @@ -54,6 +54,34 @@ func (_m *ObserverAuthorityKeeper) GetAdditionalChainList(ctx types.Context) []c return r0 } +// GetPolicies provides a mock function with given fields: ctx +func (_m *ObserverAuthorityKeeper) GetPolicies(ctx types.Context) (authoritytypes.Policies, bool) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetPolicies") + } + + var r0 authoritytypes.Policies + var r1 bool + if rf, ok := ret.Get(0).(func(types.Context) (authoritytypes.Policies, bool)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(types.Context) authoritytypes.Policies); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(authoritytypes.Policies) + } + + if rf, ok := ret.Get(1).(func(types.Context) bool); ok { + r1 = rf(ctx) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + // SetPolicies provides a mock function with given fields: ctx, policies func (_m *ObserverAuthorityKeeper) SetPolicies(ctx types.Context, policies authoritytypes.Policies) { _m.Called(ctx, policies) diff --git a/testutil/keeper/observer.go b/testutil/keeper/observer.go index d84f722b0c..39b6796c7f 100644 --- a/testutil/keeper/observer.go +++ b/testutil/keeper/observer.go @@ -10,7 +10,9 @@ import ( "github.com/cosmos/cosmos-sdk/store/rootmulti" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" @@ -47,6 +49,8 @@ func initObserverKeeper( stakingKeeper stakingkeeper.Keeper, slashingKeeper slashingkeeper.Keeper, authorityKeeper types.AuthorityKeeper, + bankKeeper bankkeeper.Keeper, + authKeeper authkeeper.AccountKeeper, lightclientKeeper types.LightclientKeeper, ) *keeper.Keeper { storeKey := sdk.NewKVStoreKey(types.StoreKey) @@ -62,6 +66,8 @@ func initObserverKeeper( slashingKeeper, authorityKeeper, lightclientKeeper, + bankKeeper, + authKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) } @@ -103,6 +109,8 @@ func ObserverKeeperWithMocks( var slashingKeeper types.SlashingKeeper = sdkKeepers.SlashingKeeper var authorityKeeper types.AuthorityKeeper = authorityKeeperTmp var lightclientKeeper types.LightclientKeeper = lightclientKeeperTmp + var bankKeeper types.BankKeeper = sdkKeepers.BankKeeper + var authKeeper types.AccountKeeper = sdkKeepers.AuthKeeper if mockOptions.UseStakingMock { stakingKeeper = observermocks.NewObserverStakingKeeper(t) } @@ -124,6 +132,8 @@ func ObserverKeeperWithMocks( slashingKeeper, authorityKeeper, lightclientKeeper, + bankKeeper, + authKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) diff --git a/testutil/sample/crosschain.go b/testutil/sample/crosschain.go index e58c457073..82e683604a 100644 --- a/testutil/sample/crosschain.go +++ b/testutil/sample/crosschain.go @@ -11,6 +11,7 @@ import ( "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" + ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" @@ -290,6 +291,7 @@ func ZetaAccounting(t *testing.T, index string) types.ZetaAccounting { } } +// InboundVote creates a sample inbound vote message func InboundVote(coinType coin.CoinType, from, to int64) types.MsgVoteInbound { return types.MsgVoteInbound{ Creator: Bech32AccAddress().String(), @@ -311,6 +313,29 @@ func InboundVote(coinType coin.CoinType, from, to int64) types.MsgVoteInbound { } } +// InboundVoteFromRand creates a simulated inbound vote message. This function uses the provided source of randomness to generate the vote +func InboundVoteFromRand(coinType coin.CoinType, from, to int64, r *rand.Rand) types.MsgVoteInbound { + EthAddress() + return types.MsgVoteInbound{ + Creator: "", + Sender: EthAddressFromRand(r).String(), + SenderChainId: from, + Receiver: EthAddressFromRand(r).String(), + ReceiverChain: to, + Amount: math.NewUint(r.Uint64()), + Message: base64.StdEncoding.EncodeToString(RandomBytes(r)), + InboundBlockHeight: r.Uint64(), + CallOptions: &types.CallOptions{ + GasLimit: 1000000000, + }, + InboundHash: ethcommon.BytesToHash(RandomBytes(r)).String(), + CoinType: coinType, + TxOrigin: EthAddressFromRand(r).String(), + Asset: StringRandom(r, 32), + EventIndex: r.Uint64(), + } +} + func ZRC20Withdrawal(to []byte, value *big.Int) *zrc20.ZRC20Withdrawal { return &zrc20.ZRC20Withdrawal{ From: EthAddress(), diff --git a/testutil/sample/crypto.go b/testutil/sample/crypto.go index 7cc565936a..9e643fa123 100644 --- a/testutil/sample/crypto.go +++ b/testutil/sample/crypto.go @@ -60,6 +60,10 @@ func EthAddress() ethcommon.Address { return ethcommon.BytesToAddress(sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()).Bytes()) } +func EthAddressFromRand(r *rand.Rand) ethcommon.Address { + return ethcommon.BytesToAddress(sdk.AccAddress(PubKey(r).Address()).Bytes()) +} + // BtcAddressP2WPKH returns a sample btc P2WPKH address func BtcAddressP2WPKH(t *testing.T, net *chaincfg.Params) string { privateKey, err := btcec.NewPrivateKey() diff --git a/testutil/sample/observer.go b/testutil/sample/observer.go index 8d0877ce9b..e9e8eb7cd7 100644 --- a/testutil/sample/observer.go +++ b/testutil/sample/observer.go @@ -2,12 +2,14 @@ package sample import ( "fmt" + "math/rand" "testing" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/pkg/cosmos" @@ -119,6 +121,25 @@ func ChainParamsList() (cpl types.ChainParamsList) { return } +// TSSFromRand returns a random TSS,it uses the randomness provided as a parameter +func TSSFromRand(t *testing.T, r *rand.Rand) types.TSS { + pubKey := PubKey(r) + spk, err := cosmos.Bech32ifyPubKey(cosmos.Bech32PubKeyTypeAccPub, pubKey) + require.NoError(t, err) + pk, err := zetacrypto.NewPubKey(spk) + require.NoError(t, err) + pubkeyString := pk.String() + return types.TSS{ + TssPubkey: pubkeyString, + TssParticipantList: []string{}, + OperatorAddressList: []string{}, + FinalizedZetaHeight: r.Int63(), + KeyGenZetaHeight: r.Int63(), + } +} + +// TODO: rename to TSS +// https://github.com/zeta-chain/node/issues/3098 func Tss() types.TSS { _, pubKey, _ := testdata.KeyTestPubAddr() spk, err := cosmos.Bech32ifyPubKey(cosmos.Bech32PubKeyTypeAccPub, pubKey) diff --git a/testutil/sample/sample.go b/testutil/sample/sample.go index 365d1c7e85..3f0390ddad 100644 --- a/testutil/sample/sample.go +++ b/testutil/sample/sample.go @@ -61,6 +61,12 @@ func Bytes() []byte { return []byte("sample") } +func RandomBytes(r *rand.Rand) []byte { + b := make([]byte, 10) + _, _ = r.Read(b) + return b +} + // String returns a sample string func String() string { return "sample" diff --git a/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go index d4764d9637..3ae74fe45c 100644 --- a/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go +++ b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go @@ -20,7 +20,6 @@ func (k Keeper) ValidateInbound( if !tssFound { return nil, types.ErrCannotFindTSSKeys } - err := k.CheckIfTSSMigrationTransfer(ctx, msg) if err != nil { return nil, err diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx.go b/x/crosschain/keeper/msg_server_vote_inbound_tx.go index a34e75c026..263b7b23bc 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx.go @@ -93,6 +93,7 @@ func (k msgServer) VoteInbound( } } commit() + // If the ballot is not finalized return nil here to add vote to commit state if !finalized { return &types.MsgVoteInboundResponse{}, nil @@ -102,9 +103,9 @@ func (k msgServer) VoteInbound( if err != nil { return nil, sdkerrors.Wrap(err, voteInboundID) } - // Save the inbound CCTX to the store. This is called irrespective of the status of the CCTX or the outcome of the process function. k.SaveObservedInboundInformation(ctx, cctx, msg.EventIndex) + return &types.MsgVoteInboundResponse{}, nil } diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go b/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go index 27f5a8ad98..7fc5c9be99 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go @@ -51,19 +51,20 @@ func TestKeeper_VoteInbound(t *testing.T) { k, ctx, sdkk, zk := keepertest.CrosschainKeeper(t) msgServer := keeper.NewMsgServerImpl(*k) validatorList := setObservers(t, k, ctx, zk) + to, from := int64(1337), int64(101) supportedChains := zk.ObserverKeeper.GetSupportedChains(ctx) for _, chain := range supportedChains { - if chains.IsEVMChain(chain.ChainId, []chains.Chain{}) { + if chains.IsEthereumChain(chain.ChainId, []chains.Chain{}) { from = chain.ChainId } if chains.IsZetaChain(chain.ChainId, []chains.Chain{}) { to = chain.ChainId } } - zk.ObserverKeeper.SetTSS(ctx, sample.Tss()) msg := sample.InboundVote(0, from, to) + zk.ObserverKeeper.SetTSS(ctx, sample.Tss()) err := sdkk.EvmKeeper.SetAccount(ctx, ethcommon.HexToAddress(msg.Receiver), statedb.Account{ Nonce: 0, @@ -210,7 +211,7 @@ func TestKeeper_VoteInbound(t *testing.T) { to, from := int64(1337), int64(101) supportedChains := zk.ObserverKeeper.GetSupportedChains(ctx) for _, chain := range supportedChains { - if chains.IsEVMChain(chain.ChainId, []chains.Chain{}) { + if chains.IsEthereumChain(chain.ChainId, []chains.Chain{}) { from = chain.ChainId } if chains.IsZetaChain(chain.ChainId, []chains.Chain{}) { diff --git a/x/crosschain/module_simulation.go b/x/crosschain/module_simulation.go index 38ff067fbf..1399b27cdb 100644 --- a/x/crosschain/module_simulation.go +++ b/x/crosschain/module_simulation.go @@ -5,17 +5,14 @@ import ( "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/zeta-chain/node/x/crosschain/simulation" "github.com/zeta-chain/node/x/crosschain/types" ) -// GenerateGenesisState creates a randomized GenState of the module +// GenerateGenesisState creates a GenState of the module used to initialize the simulation runs func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - accs := make([]string, len(simState.Accounts)) - for i, acc := range simState.Accounts { - accs[i] = acc.Address.String() - } - crosschainGenesis := types.GenesisState{} - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&crosschainGenesis) + crosschainGenesis := types.DefaultGenesis() + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(crosschainGenesis) } // ProposalContents doesn't return any content functions for governance proposals @@ -28,11 +25,13 @@ func (AppModule) ProposalMsgs(_ module.SimulationState) []simtypes.WeightedPropo } // RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} +func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) +} // WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation { - operations := make([]simtypes.WeightedOperation, 0) - - return operations +func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { + return simulation.WeightedOperations( + simState.AppParams, simState.Cdc, am.keeper, + ) } diff --git a/x/crosschain/simulation/decoders.go b/x/crosschain/simulation/decoders.go new file mode 100644 index 0000000000..dd2c6c4e07 --- /dev/null +++ b/x/crosschain/simulation/decoders.go @@ -0,0 +1,62 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/zeta-chain/node/x/crosschain/types" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding crosschain types. +func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + switch { + case bytes.Equal(kvA.Key, types.KeyPrefix(types.CCTXKey)): + var cctxA, cctxB types.CrossChainTx + cdc.MustUnmarshal(kvA.Value, &cctxA) + cdc.MustUnmarshal(kvB.Value, &cctxB) + return fmt.Sprintf("%v\n%v", cctxA, cctxB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.LastBlockHeightKey)): + var lastBlockHeightA, lastBlockHeightB types.LastBlockHeight + cdc.MustUnmarshal(kvA.Value, &lastBlockHeightA) + cdc.MustUnmarshal(kvB.Value, &lastBlockHeightB) + return fmt.Sprintf("%v\n%v", lastBlockHeightA, lastBlockHeightB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.FinalizedInboundsKey)): + var finalizedInboundsA, finalizedInboundsB []byte + finalizedInboundsA = kvA.Value + finalizedInboundsB = kvB.Value + return fmt.Sprintf("%v\n%v", finalizedInboundsA, finalizedInboundsB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.GasPriceKey)): + var gasPriceA, gasPriceB types.GasPrice + cdc.MustUnmarshal(kvA.Value, &gasPriceA) + cdc.MustUnmarshal(kvB.Value, &gasPriceB) + return fmt.Sprintf("%v\n%v", gasPriceA, gasPriceB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.OutboundTrackerKeyPrefix)): + var outboundTrackerA, outboundTrackerB types.OutboundTracker + cdc.MustUnmarshal(kvA.Value, &outboundTrackerA) + cdc.MustUnmarshal(kvB.Value, &outboundTrackerB) + return fmt.Sprintf("%v\n%v", outboundTrackerA, outboundTrackerB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.InboundTrackerKeyPrefix)): + var inboundTrackerA, inboundTrackerB types.InboundTracker + cdc.MustUnmarshal(kvA.Value, &inboundTrackerA) + cdc.MustUnmarshal(kvB.Value, &inboundTrackerB) + return fmt.Sprintf("%v\n%v", inboundTrackerA, inboundTrackerB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.ZetaAccountingKey)): + var zetaAccountingA, zetaAccountingB types.ZetaAccounting + cdc.MustUnmarshal(kvA.Value, &zetaAccountingA) + cdc.MustUnmarshal(kvB.Value, &zetaAccountingB) + return fmt.Sprintf("%v\n%v", zetaAccountingA, zetaAccountingB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.RateLimiterFlagsKey)): + var rateLimiterFlagsA, rateLimiterFlagsB types.RateLimiterFlags + cdc.MustUnmarshal(kvA.Value, &rateLimiterFlagsA) + cdc.MustUnmarshal(kvB.Value, &rateLimiterFlagsB) + return fmt.Sprintf("%v\n%v", rateLimiterFlagsA, rateLimiterFlagsB) + default: + panic(fmt.Sprintf("invalid crosschain key prefix %X", kvA.Key[:1])) + } + } +} diff --git a/x/crosschain/simulation/decoders_test.go b/x/crosschain/simulation/decoders_test.go new file mode 100644 index 0000000000..9765aca43d --- /dev/null +++ b/x/crosschain/simulation/decoders_test.go @@ -0,0 +1,60 @@ +package simulation_test + +import ( + "fmt" + "testing" + + "github.com/cosmos/cosmos-sdk/types/kv" + "github.com/stretchr/testify/require" + keepertest "github.com/zeta-chain/node/testutil/keeper" + "github.com/zeta-chain/node/testutil/sample" + "github.com/zeta-chain/node/x/crosschain/simulation" + "github.com/zeta-chain/node/x/crosschain/types" +) + +func TestDecodeStore(t *testing.T) { + k, _, _, _ := keepertest.CrosschainKeeper(t) + cdc := k.GetCodec() + dec := simulation.NewDecodeStore(cdc) + cctx := sample.CrossChainTx(t, "sample") + lastBlockHeight := sample.LastBlockHeight(t, "sample") + gasPrice := sample.GasPrice(t, "sample") + outboundTracker := sample.OutboundTracker(t, "sample") + inboundTracker := sample.InboundTracker(t, "sample") + zetaAccounting := sample.ZetaAccounting(t, "sample") + rateLimiterFlags := sample.RateLimiterFlags() + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + {Key: types.KeyPrefix(types.CCTXKey), Value: cdc.MustMarshal(cctx)}, + {Key: types.KeyPrefix(types.LastBlockHeightKey), Value: cdc.MustMarshal(lastBlockHeight)}, + {Key: types.KeyPrefix(types.GasPriceKey), Value: cdc.MustMarshal(gasPrice)}, + {Key: types.KeyPrefix(types.OutboundTrackerKeyPrefix), Value: cdc.MustMarshal(&outboundTracker)}, + {Key: types.KeyPrefix(types.InboundTrackerKeyPrefix), Value: cdc.MustMarshal(&inboundTracker)}, + {Key: types.KeyPrefix(types.ZetaAccountingKey), Value: cdc.MustMarshal(&zetaAccounting)}, + {Key: types.KeyPrefix(types.RateLimiterFlagsKey), Value: cdc.MustMarshal(&rateLimiterFlags)}, + {Key: types.KeyPrefix(types.FinalizedInboundsKey), Value: []byte{1}}, + }, + } + + tests := []struct { + name string + expectedLog string + }{ + {"CrossChainTx", fmt.Sprintf("%v\n%v", *cctx, *cctx)}, + {"LastBlockHeight", fmt.Sprintf("%v\n%v", *lastBlockHeight, *lastBlockHeight)}, + {"GasPrice", fmt.Sprintf("%v\n%v", *gasPrice, *gasPrice)}, + {"OutboundTracker", fmt.Sprintf("%v\n%v", outboundTracker, outboundTracker)}, + {"InboundTracker", fmt.Sprintf("%v\n%v", inboundTracker, inboundTracker)}, + {"ZetaAccounting", fmt.Sprintf("%v\n%v", zetaAccounting, zetaAccounting)}, + {"RateLimiterFlags", fmt.Sprintf("%v\n%v", rateLimiterFlags, rateLimiterFlags)}, + {"FinalizedInbounds", fmt.Sprintf("%v\n%v", []byte{1}, []byte{1})}, + } + + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i])) + }) + } +} diff --git a/x/crosschain/simulation/operations.go b/x/crosschain/simulation/operations.go new file mode 100644 index 0000000000..96b04970b9 --- /dev/null +++ b/x/crosschain/simulation/operations.go @@ -0,0 +1,492 @@ +package simulation + +import ( + "fmt" + "math" + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/zeta-chain/node/pkg/authz" + "github.com/zeta-chain/node/pkg/chains" + "github.com/zeta-chain/node/testutil/sample" + "github.com/zeta-chain/node/x/crosschain/keeper" + "github.com/zeta-chain/node/x/crosschain/types" + observerTypes "github.com/zeta-chain/node/x/observer/types" +) + +// Simulation operation weights constants +// Operation weights are used by the `SimulateFromSeed` +// function to pick a random operation based on the weights.The functions with higher weights are more likely to be picked. + +// Therefore, this decides the percentage of a certain operation that is part of a block. + +// Based on the weights assigned in the cosmos sdk modules, +// 100 seems to the max weight used,and we should use relative weights +// to signify the number of each operation in a block. + +// TODO Add more details to comment based on what the number represents in terms of percentage of operations in a block +// https://github.com/zeta-chain/node/issues/3100 +const ( + DefaultWeightMsgAddOutboundTracker = 50 + DefaultWeightAddInboundTracker = 50 + DefaultWeightRemoveOutboundTracker = 5 + DefaultWeightVoteGasPrice = 100 + DefaultWeightVoteOutbound = 50 + DefaultWeightVoteInbound = 100 + DefaultWeightWhitelistERC20 = 1 + DefaultWeightMigrateTssFunds = 1 + DefaultWeightUpdateTssAddress = 1 + DefaultWeightAbortStuckCCTX = 10 + DefaultWeightUpdateRateLimiterFlags = 1 + + OpWeightMsgAddOutboundTracker = "op_weight_msg_add_outbound_tracker" // #nosec G101 not a hardcoded credential + OpWeightAddInboundTracker = "op_weight_msg_add_inbound_tracker" // #nosec G101 not a hardcoded credential + OpWeightRemoveOutboundTracker = "op_weight_msg_remove_outbound_tracker" // #nosec G101 not a hardcoded credential + OpWeightVoteGasPrice = "op_weight_msg_vote_gas_price" // #nosec G101 not a hardcoded credential + OpWeightVoteOutbound = "op_weight_msg_vote_outbound" // #nosec G101 not a hardcoded credential + OpWeightVoteInbound = "op_weight_msg_vote_inbound" // #nosec G101 not a hardcoded credential + OpWeightWhitelistERC20 = "op_weight_msg_whitelist_erc20" // #nosec G101 not a hardcoded credential + OpWeightMigrateTssFunds = "op_weight_msg_migrate_tss_funds" // #nosec G101 not a hardcoded credential + OpWeightUpdateTssAddress = "op_weight_msg_update_tss_address" // #nosec G101 not a hardcoded credential + OpWeightAbortStuckCCTX = "op_weight_msg_abort_stuck_cctx" // #nosec G101 not a hardcoded credential + OpWeightUpdateRateLimiterFlags = "op_weight_msg_update_rate_limiter_flags" // #nosec G101 not a hardcoded credential + +) + +func WeightedOperations( + appParams simtypes.AppParams, cdc codec.JSONCodec, k keeper.Keeper) simulation.WeightedOperations { + var ( + weightMsgAddOutboundTracker int + weightAddInboundTracker int + weightRemoveOutboundTracker int + weightVoteGasPrice int + weightVoteOutbound int + weightVoteInbound int + weightWhitelistERC20 int + weightMigrateTssFunds int + weightUpdateTssAddress int + weightAbortStuckCCTX int + weightUpdateRateLimiterFlags int + ) + + appParams.GetOrGenerate(cdc, OpWeightMsgAddOutboundTracker, &weightMsgAddOutboundTracker, nil, + func(_ *rand.Rand) { + weightMsgAddOutboundTracker = DefaultWeightMsgAddOutboundTracker + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightAddInboundTracker, &weightAddInboundTracker, nil, + func(_ *rand.Rand) { + weightAddInboundTracker = DefaultWeightAddInboundTracker + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightRemoveOutboundTracker, &weightRemoveOutboundTracker, nil, + func(_ *rand.Rand) { + weightRemoveOutboundTracker = DefaultWeightRemoveOutboundTracker + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightVoteGasPrice, &weightVoteGasPrice, nil, + func(_ *rand.Rand) { + weightVoteGasPrice = DefaultWeightVoteGasPrice + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightVoteOutbound, &weightVoteOutbound, nil, + func(_ *rand.Rand) { + weightVoteOutbound = DefaultWeightVoteOutbound + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightVoteInbound, &weightVoteInbound, nil, + func(_ *rand.Rand) { + weightVoteInbound = DefaultWeightVoteInbound + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightWhitelistERC20, &weightWhitelistERC20, nil, + func(_ *rand.Rand) { + weightWhitelistERC20 = DefaultWeightWhitelistERC20 + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightMigrateTssFunds, &weightMigrateTssFunds, nil, + func(_ *rand.Rand) { + weightMigrateTssFunds = DefaultWeightMigrateTssFunds + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightUpdateTssAddress, &weightUpdateTssAddress, nil, + func(_ *rand.Rand) { + weightUpdateTssAddress = DefaultWeightUpdateTssAddress + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightAbortStuckCCTX, &weightAbortStuckCCTX, nil, + func(_ *rand.Rand) { + weightAbortStuckCCTX = DefaultWeightAbortStuckCCTX + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightUpdateRateLimiterFlags, &weightUpdateRateLimiterFlags, nil, + func(_ *rand.Rand) { + weightUpdateRateLimiterFlags = DefaultWeightUpdateRateLimiterFlags + }, + ) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightVoteGasPrice, + SimulateMsgVoteGasPrice(k), + ), + simulation.NewWeightedOperation( + weightVoteInbound, + SimulateVoteInbound(k), + ), + } +} + +// operationSimulateVoteInbound generates a MsgVoteInbound with a random vote and delivers it. +func operationSimulateVoteInbound( + k keeper.Keeper, + msg types.MsgVoteInbound, + simAccount simtypes.Account, +) simtypes.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, _ []simtypes.Account, _ string, + ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { + // Fetch the account from the auth keeper which can then be used to fetch spendable coins + authAccount := k.GetAuthKeeper().GetAccount(ctx, simAccount.Address) + spendable := k.GetBankKeeper().SpendableCoins(ctx, authAccount.GetAddress()) + + // Generate a transaction with a random fee and deliver it + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: &msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: simAccount, + AccountKeeper: k.GetAuthKeeper(), + Bankkeeper: k.GetBankKeeper(), + ModuleName: types.ModuleName, + CoinsSpentInMsg: spendable, + } + + // Generate and deliver the transaction using the function defined by us instead of using the default function provided by the cosmos-sdk + // The main difference between the two functions is that the one defined by us does not error out if the vote fails. + // We need this behaviour as the votes are assigned to future operations, i.e., they are scheduled to be executed in a future block. We do not know at the time of scheduling if the vote will be successful or not. + // There might be multiple reasons for a vote to fail , like the observer not being present in the observer set, the observer not being an observer, etc. + return GenAndDeliverTxWithRandFees(txCtx) + } +} + +func SimulateVoteInbound(k keeper.Keeper) simtypes.Operation { + // The states are: + // column 1: All observers vote + // column 2: 90% vote + // column 3: 75% vote + // column 4: 40% vote + // column 5: 15% vote + // column 6: noone votes + // All columns sum to 100 for simplicity, but this is arbitrary and can be changed + numVotesTransitionMatrix, _ := simulation.CreateTransitionMatrix([][]int{ + {20, 10, 0, 0, 0, 0}, + {55, 50, 20, 10, 0, 0}, + {25, 25, 30, 25, 30, 15}, + {0, 15, 30, 25, 30, 30}, + {0, 0, 20, 30, 30, 30}, + {0, 0, 0, 10, 10, 25}, + }) + + statePercentageArray := []float64{1, .9, .75, .4, .15, 0} + curNumVotesState := 1 + + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accs []simtypes.Account, + chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + // TODO : randomize these values + // Right now we use a constant value for cctx creation , this is the same as the one used in unit tests for the successful condition. + // TestKeeper_VoteInbound/successfully vote on evm deposit + // But this can improved by adding more randomization + + to, from := int64(1337), int64(101) + supportedChains := k.GetObserverKeeper().GetSupportedChains(ctx) + for _, chain := range supportedChains { + if chains.IsEthereumChain(chain.ChainId, []chains.Chain{}) { + from = chain.ChainId + } + if chains.IsZetaChain(chain.ChainId, []chains.Chain{}) { + to = chain.ChainId + } + } + + msg := sample.InboundVoteFromRand(0, from, to, r) + + // Pick a random observer to create the ballot + // If this returns an error, it is likely that the entire observer set has been removed + simAccount, firstVoter, err := GetRandomAccountAndObserver(r, ctx, k, accs) + if err != nil { + return simtypes.OperationMsg{}, nil, nil + } + + txGen := moduletestutil.MakeTestEncodingConfig().TxConfig + account := k.GetAuthKeeper().GetAccount(ctx, simAccount.Address) + firstMsg := msg + firstMsg.Creator = firstVoter + + err = firstMsg.ValidateBasic() + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate first inbound vote"), nil, err + } + + tx, err := simtestutil.GenSignedMockTx( + r, + txGen, + []sdk.Msg{&firstMsg}, + sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)}, + simtestutil.DefaultGenTxGas, + chainID, + []uint64{account.GetAccountNumber()}, + []uint64{account.GetSequence()}, + simAccount.PrivKey, + ) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err + } + + // We can return error here as we can guarantee that the first vote will be successful. + // Since we query the observer set before adding votes + _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, err + } + + opMsg := simtypes.NewOperationMsg(&msg, true, "", nil) + + // Add subsequent votes + observerSet, found := k.GetObserverKeeper().GetObserverSet(ctx) + if !found { + return simtypes.NoOpMsg(types.ModuleName, authz.InboundVoter.String(), "observer set not found"), nil, nil + } + + // 1) Schedule operations for votes + // 1.1) first pick a number of people to vote. + curNumVotesState = numVotesTransitionMatrix.NextState(r, curNumVotesState) + numVotes := int(math.Ceil(float64(len(observerSet.ObserverList)) * statePercentageArray[curNumVotesState])) + + // 1.2) select who votes + whoVotes := r.Perm(len(observerSet.ObserverList)) + whoVotes = whoVotes[:numVotes] + + var fops []simtypes.FutureOperation + + for _, observerIdx := range whoVotes { + observerAddress := observerSet.ObserverList[observerIdx] + // firstVoter has already voted. + if observerAddress == firstVoter { + continue + } + observerAccount, err := GetObserverAccount(observerAddress, accs) + if err != nil { + continue + } + // 1.3) schedule the vote + votingMsg := msg + votingMsg.Creator = observerAddress + + e := votingMsg.ValidateBasic() + if e != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate voting msg"), nil, e + } + + fops = append(fops, simtypes.FutureOperation{ + // Submit all subsequent votes in the next block. + // We can consider adding a random block height between 1 and ballot maturity blocks in the future. + BlockHeight: int(ctx.BlockHeight() + 1), + Op: operationSimulateVoteInbound(k, votingMsg, observerAccount), + }) + } + return opMsg, fops, nil + } +} + +// SimulateMsgVoteGasPrice generates a MsgVoteGasPrice and delivers it +func SimulateMsgVoteGasPrice(k keeper.Keeper) simtypes.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []simtypes.Account, _ string, + ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { + // Get a random account and observer + // If this returns an error, it is likely that the entire observer set has been removed + simAccount, randomObserver, err := GetRandomAccountAndObserver(r, ctx, k, accounts) + if err != nil { + return simtypes.OperationMsg{}, nil, nil + } + authAccount := k.GetAuthKeeper().GetAccount(ctx, simAccount.Address) + spendable := k.GetBankKeeper().SpendableCoins(ctx, authAccount.GetAddress()) + + supportedChains := k.GetObserverKeeper().GetSupportedChains(ctx) + if len(supportedChains) == 0 { + return simtypes.NoOpMsg( + types.ModuleName, + authz.GasPriceVoter.String(), + "no supported chains found", + ), nil, nil + } + randomChainID := GetRandomChainID(r, supportedChains) + + // Vote for random gas price. Gas prices do not use a ballot system, so we can vote directly without having to schedule future operations. + // The random nature of the price might create weird gas prices for the chain, but it is fine for now. We can remove the randomness if needed + msg := types.MsgVoteGasPrice{ + Creator: randomObserver, + ChainId: randomChainID, + Price: r.Uint64(), + PriorityFee: r.Uint64(), + BlockNumber: r.Uint64(), + Supply: fmt.Sprintf("%d", r.Int63()), + } + + // System contracts are deployed on the first block, so we cannot vote on gas prices before that + if ctx.BlockHeight() <= 1 { + return simtypes.NewOperationMsg(&msg, true, "block height less than 1", nil), nil, nil + } + + err = msg.ValidateBasic() + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate vote gas price msg"), nil, err + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: &msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: simAccount, + AccountKeeper: k.GetAuthKeeper(), + Bankkeeper: k.GetBankKeeper(), + ModuleName: types.ModuleName, + CoinsSpentInMsg: spendable, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +func GetRandomObserver(r *rand.Rand, observerList []string) string { + idx := r.Intn(len(observerList)) + return observerList[idx] +} + +func GetRandomChainID(r *rand.Rand, chains []chains.Chain) int64 { + idx := r.Intn(len(chains)) + return chains[idx].ChainId +} + +// GetRandomAccountAndObserver returns a random account and the associated observer address +func GetRandomAccountAndObserver( + r *rand.Rand, + ctx sdk.Context, + k keeper.Keeper, + accounts []simtypes.Account, +) (simtypes.Account, string, error) { + observers, found := k.GetObserverKeeper().GetObserverSet(ctx) + if !found { + return simtypes.Account{}, "", fmt.Errorf("observer set not found") + } + + if len(observers.ObserverList) == 0 { + return simtypes.Account{}, "", fmt.Errorf("no observers present in observer set found") + } + + randomObserver := GetRandomObserver(r, observers.ObserverList) + simAccount, err := GetObserverAccount(randomObserver, accounts) + if err != nil { + return simtypes.Account{}, "", err + } + return simAccount, randomObserver, nil +} + +// GetObserverAccount returns the account associated with the observer address from the list of accounts provided +// GetObserverAccount can fail if all the observers are removed from the observer set ,this can happen +//if the other modules create transactions which affect the validator +//and triggers any of the staking hooks defined in the observer modules + +func GetObserverAccount(observerAddress string, accounts []simtypes.Account) (simtypes.Account, error) { + operatorAddress, err := observerTypes.GetOperatorAddressFromAccAddress(observerAddress) + if err != nil { + return simtypes.Account{}, fmt.Errorf("validator not found for observer ") + } + + simAccount, found := simtypes.FindAccount(accounts, operatorAddress) + if !found { + return simtypes.Account{}, fmt.Errorf("operator account not found") + } + return simAccount, nil +} + +// GenAndDeliverTxWithRandFees generates a transaction with a random fee and delivers it. +func GenAndDeliverTxWithRandFees( + txCtx simulation.OperationInput, +) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + account := txCtx.AccountKeeper.GetAccount(txCtx.Context, txCtx.SimAccount.Address) + spendable := txCtx.Bankkeeper.SpendableCoins(txCtx.Context, account.GetAddress()) + + var fees sdk.Coins + var err error + + coins, hasNeg := spendable.SafeSub(txCtx.CoinsSpentInMsg...) + if hasNeg { + return simtypes.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "message doesn't leave room for fees"), nil, err + } + + fees, err = simtypes.RandomFees(txCtx.R, txCtx.Context, coins) + if err != nil { + return simtypes.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to generate fees"), nil, err + } + return GenAndDeliverTx(txCtx, fees) +} + +// GenAndDeliverTx generates a transactions and delivers it with the provided fees. +// This function does not return an error if the transaction fails to deliver. +func GenAndDeliverTx( + txCtx simulation.OperationInput, + fees sdk.Coins, +) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + account := txCtx.AccountKeeper.GetAccount(txCtx.Context, txCtx.SimAccount.Address) + tx, err := simtestutil.GenSignedMockTx( + txCtx.R, + txCtx.TxGen, + []sdk.Msg{txCtx.Msg}, + fees, + simtestutil.DefaultGenTxGas, + txCtx.Context.ChainID(), + []uint64{account.GetAccountNumber()}, + []uint64{account.GetSequence()}, + txCtx.SimAccount.PrivKey, + ) + if err != nil { + return simtypes.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to generate mock tx"), nil, err + } + + _, _, err = txCtx.App.SimDeliver(txCtx.TxGen.TxEncoder(), tx) + if err != nil { + return simtypes.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to deliver tx"), nil, nil + } + + return simtypes.NewOperationMsg(txCtx.Msg, true, "", txCtx.Cdc), nil, nil +} diff --git a/x/crosschain/simulation/simap.go b/x/crosschain/simulation/simap.go deleted file mode 100644 index 92c437c0d1..0000000000 --- a/x/crosschain/simulation/simap.go +++ /dev/null @@ -1,15 +0,0 @@ -package simulation - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -// FindAccount find a specific address from an account list -func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { - creator, err := sdk.AccAddressFromBech32(address) - if err != nil { - panic(err) - } - return simtypes.FindAccount(accs, creator) -} diff --git a/x/crosschain/types/expected_keepers.go b/x/crosschain/types/expected_keepers.go index 0f496c13d3..2fee9cb31a 100644 --- a/x/crosschain/types/expected_keepers.go +++ b/x/crosschain/types/expected_keepers.go @@ -24,12 +24,14 @@ type StakingKeeper interface { // AccountKeeper defines the expected account keeper (noalias) type AccountKeeper interface { GetModuleAccount(ctx sdk.Context, name string) types.ModuleAccountI + GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI } // BankKeeper defines the expected interface needed to retrieve account balances. type BankKeeper interface { BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) error MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins } type ObserverKeeper interface { diff --git a/x/fungible/keeper/evm.go b/x/fungible/keeper/evm.go index 95b1b05dae..6abd4444d9 100644 --- a/x/fungible/keeper/evm.go +++ b/x/fungible/keeper/evm.go @@ -322,6 +322,7 @@ func (k Keeper) DepositZRC20AndCallContract(ctx sdk.Context, if !found { return nil, cosmoserrors.Wrapf(types.ErrContractNotFound, "GetSystemContract address not found") } + systemAddress := common.HexToAddress(system.SystemContract) sysConABI, err := systemcontract.SystemContractMetaData.GetAbi() diff --git a/x/fungible/keeper/keeper.go b/x/fungible/keeper/keeper.go index ce1e9d5fdc..6e4af7f194 100644 --- a/x/fungible/keeper/keeper.go +++ b/x/fungible/keeper/keeper.go @@ -13,7 +13,7 @@ import ( type ( Keeper struct { - cdc codec.BinaryCodec + cdc codec.Codec storeKey storetypes.StoreKey memKey storetypes.StoreKey authKeeper types.AccountKeeper @@ -25,7 +25,7 @@ type ( ) func NewKeeper( - cdc codec.BinaryCodec, + cdc codec.Codec, storeKey, memKey storetypes.StoreKey, authKeeper types.AccountKeeper, @@ -54,6 +54,10 @@ func (k Keeper) GetAuthKeeper() types.AccountKeeper { return k.authKeeper } +func (k Keeper) GetCodec() codec.Codec { + return k.cdc +} + func (k Keeper) GetEVMKeeper() types.EVMKeeper { return k.evmKeeper } diff --git a/x/fungible/keeper/msg_server_update_gateway_contract_test.go b/x/fungible/keeper/msg_server_update_gateway_contract_test.go index b1ab5a7fbf..ba838b3674 100644 --- a/x/fungible/keeper/msg_server_update_gateway_contract_test.go +++ b/x/fungible/keeper/msg_server_update_gateway_contract_test.go @@ -1,10 +1,11 @@ package keeper_test import ( + "testing" + "github.com/ethereum/go-ethereum/common" "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" - "testing" "github.com/stretchr/testify/require" keepertest "github.com/zeta-chain/node/testutil/keeper" diff --git a/x/fungible/module.go b/x/fungible/module.go index 149ef5cce4..13b7e59e40 100644 --- a/x/fungible/module.go +++ b/x/fungible/module.go @@ -31,10 +31,10 @@ var ( // AppModuleBasic implements the AppModuleBasic interface for the fungible module. type AppModuleBasic struct { - cdc codec.BinaryCodec + cdc codec.Codec } -func NewAppModuleBasic(cdc codec.BinaryCodec) AppModuleBasic { +func NewAppModuleBasic(cdc codec.Codec) AppModuleBasic { return AppModuleBasic{cdc: cdc} } diff --git a/x/fungible/module_simulation.go b/x/fungible/module_simulation.go index e502102502..120f9158f4 100644 --- a/x/fungible/module_simulation.go +++ b/x/fungible/module_simulation.go @@ -5,17 +5,14 @@ import ( "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/zeta-chain/node/x/fungible/simulation" "github.com/zeta-chain/node/x/fungible/types" ) -// GenerateGenesisState creates a randomized GenState of the module +// GenerateGenesisState creates a GenState of the module used to initialize the simulation runs func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - accs := make([]string, len(simState.Accounts)) - for i, acc := range simState.Accounts { - accs[i] = acc.Address.String() - } - fungibleGenesis := types.GenesisState{} - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&fungibleGenesis) + fungibleGenesis := types.DefaultGenesis() + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(fungibleGenesis) } // ProposalContents doesn't return any content functions for governance proposals @@ -28,11 +25,13 @@ func (AppModule) ProposalMsgs(_ module.SimulationState) []simtypes.WeightedPropo } // RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} +func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) +} // WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation { - operations := make([]simtypes.WeightedOperation, 0) - - return operations +func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { + return simulation.WeightedOperations( + simState.AppParams, simState.Cdc, am.keeper, + ) } diff --git a/x/fungible/simulation/decoders.go b/x/fungible/simulation/decoders.go new file mode 100644 index 0000000000..04be66f781 --- /dev/null +++ b/x/fungible/simulation/decoders.go @@ -0,0 +1,35 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/zeta-chain/node/x/fungible/types" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding fungible types. +func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + if !bytes.Equal(kvA.Key[:1], kvB.Key[:1]) { + return fmt.Sprintf("key prefixes do not match. A: %X, B: %X", kvA.Key[:1], kvB.Key[:1]) + } + switch { + case bytes.Equal(kvA.Key, types.KeyPrefix(types.SystemContractKey)): + var systemContractA, systemContractB types.SystemContract + cdc.MustUnmarshal(kvA.Value, &systemContractA) + cdc.MustUnmarshal(kvB.Value, &systemContractB) + return fmt.Sprintf("%v\n%v", systemContractA, systemContractB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.ForeignCoinsKeyPrefix)): + var foreignCoinsA, foreignCoinsB types.ForeignCoins + cdc.MustUnmarshal(kvA.Value, &foreignCoinsA) + cdc.MustUnmarshal(kvB.Value, &foreignCoinsB) + return fmt.Sprintf("%v\n%v", foreignCoinsA, foreignCoinsB) + default: + return fmt.Sprintf("invalid fungible key prefix %X", kvA.Key[:1]) + } + } +} diff --git a/x/fungible/simulation/decoders_test.go b/x/fungible/simulation/decoders_test.go new file mode 100644 index 0000000000..c2e4cdfd44 --- /dev/null +++ b/x/fungible/simulation/decoders_test.go @@ -0,0 +1,43 @@ +package simulation_test + +import ( + "fmt" + "testing" + + "github.com/cosmos/cosmos-sdk/types/kv" + "github.com/stretchr/testify/require" + keepertest "github.com/zeta-chain/node/testutil/keeper" + "github.com/zeta-chain/node/testutil/sample" + "github.com/zeta-chain/node/x/fungible/simulation" + "github.com/zeta-chain/node/x/fungible/types" +) + +func TestDecodeStore(t *testing.T) { + k, _, _, _ := keepertest.FungibleKeeper(t) + cdc := k.GetCodec() + dec := simulation.NewDecodeStore(cdc) + systemContract := sample.SystemContract() + foreignCoins := sample.ForeignCoins(t, sample.EthAddress().String()) + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + {Key: []byte(types.SystemContractKey), Value: cdc.MustMarshal(systemContract)}, + {Key: []byte(types.ForeignCoinsKeyPrefix), Value: cdc.MustMarshal(&foreignCoins)}, + }, + } + + tests := []struct { + name string + expectedLog string + }{ + {"SystemContract", fmt.Sprintf("%v\n%v", *systemContract, *systemContract)}, + {"ForeignCoins", fmt.Sprintf("%v\n%v", foreignCoins, foreignCoins)}, + } + + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i])) + }) + } +} diff --git a/x/fungible/simulation/operations.go b/x/fungible/simulation/operations.go new file mode 100644 index 0000000000..2a14713979 --- /dev/null +++ b/x/fungible/simulation/operations.go @@ -0,0 +1,116 @@ +package simulation + +import ( + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/zeta-chain/node/x/fungible/keeper" + "github.com/zeta-chain/node/x/fungible/types" + observerTypes "github.com/zeta-chain/node/x/observer/types" +) + +// Simulation operation weights constants +// Operation weights are used by the `SimulateFromSeed` +// function to pick a random operation based on the weights.The functions with higher weights are more likely to be picked. + +// Therefore, this decides the percentage of a certain operation that is part of a block. + +// Based on the weights assigned in the cosmos sdk modules, +// 100 seems to the max weight used,and we should use relative weights +// to signify the number of each operation in a block. + +// TODO Add more details to comment based on what the number represents in terms of percentage of operations in a block +// https://github.com/zeta-chain/node/issues/3100 +const ( + // #nosec G101 not a hardcoded credential + OpWeightMsgDeploySystemContracts = "op_weight_msg_deploy_system_contracts" + DefaultWeightMsgDeploySystemContracts = 5 +) + +// DeployedSystemContracts Use a flag to ensure that the system contracts are deployed only once +// https://github.com/zeta-chain/node/issues/3102 +func WeightedOperations( + appParams simtypes.AppParams, cdc codec.JSONCodec, k keeper.Keeper) simulation.WeightedOperations { + var weightMsgDeploySystemContracts int + + appParams.GetOrGenerate(cdc, OpWeightMsgDeploySystemContracts, &weightMsgDeploySystemContracts, nil, + func(_ *rand.Rand) { + weightMsgDeploySystemContracts = DefaultWeightMsgDeploySystemContracts + }) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgDeploySystemContracts, + SimulateMsgDeploySystemContracts(k), + ), + } +} + +// SimulateMsgDeploySystemContracts deploy system contracts.It is run only once in first block. +func SimulateMsgDeploySystemContracts(k keeper.Keeper) simtypes.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []simtypes.Account, _ string, + ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { + policies, found := k.GetAuthorityKeeper().GetPolicies(ctx) + if !found { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgDeploySystemContracts, + "policies object not found", + ), nil, nil + } + if len(policies.Items) == 0 { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgDeploySystemContracts, + "no policies found", + ), nil, nil + } + admin := policies.Items[0].Address + + address, err := observerTypes.GetOperatorAddressFromAccAddress(admin) + if err != nil { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgDeploySystemContracts, + "unable to get operator address", + ), nil, err + } + simAccount, found := simtypes.FindAccount(accounts, address) + if !found { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgDeploySystemContracts, + "sim account for admin address not found", + ), nil, nil + } + + msg := types.MsgDeploySystemContracts{Creator: admin} + + err = msg.ValidateBasic() + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "failed to validate basic msg"), nil, err + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: &msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: simAccount, + AccountKeeper: k.GetAuthKeeper(), + Bankkeeper: k.GetBankKeeper(), + ModuleName: types.ModuleName, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} diff --git a/x/fungible/simulation/simap.go b/x/fungible/simulation/simap.go deleted file mode 100644 index 92c437c0d1..0000000000 --- a/x/fungible/simulation/simap.go +++ /dev/null @@ -1,15 +0,0 @@ -package simulation - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -// FindAccount find a specific address from an account list -func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { - creator, err := sdk.AccAddressFromBech32(address) - if err != nil { - panic(err) - } - return simtypes.FindAccount(accs, creator) -} diff --git a/x/fungible/types/expected_keepers.go b/x/fungible/types/expected_keepers.go index 442f625c10..a0ee828878 100644 --- a/x/fungible/types/expected_keepers.go +++ b/x/fungible/types/expected_keepers.go @@ -13,6 +13,7 @@ import ( evmtypes "github.com/zeta-chain/ethermint/x/evm/types" "github.com/zeta-chain/node/pkg/chains" + authoritytypes "github.com/zeta-chain/node/x/authority/types" ) // AccountKeeper defines the expected account keeper used for simulations (noalias) @@ -33,6 +34,7 @@ type BankKeeper interface { amt sdk.Coins, ) error MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins GetSupply(ctx sdk.Context, denom string) sdk.Coin } @@ -62,4 +64,5 @@ type EVMKeeper interface { type AuthorityKeeper interface { CheckAuthorization(ctx sdk.Context, msg sdk.Msg) error GetAdditionalChainList(ctx sdk.Context) (list []chains.Chain) + GetPolicies(ctx sdk.Context) (val authoritytypes.Policies, found bool) } diff --git a/x/observer/keeper/keeper.go b/x/observer/keeper/keeper.go index ad7fa984c9..b9fdc0f97d 100644 --- a/x/observer/keeper/keeper.go +++ b/x/observer/keeper/keeper.go @@ -13,25 +13,29 @@ import ( type ( Keeper struct { - cdc codec.BinaryCodec + cdc codec.Codec storeKey storetypes.StoreKey memKey storetypes.StoreKey stakingKeeper types.StakingKeeper slashingKeeper types.SlashingKeeper authorityKeeper types.AuthorityKeeper lightclientKeeper types.LightclientKeeper + bankKeeper types.BankKeeper + authKeeper types.AccountKeeper authority string } ) func NewKeeper( - cdc codec.BinaryCodec, + cdc codec.Codec, storeKey, memKey storetypes.StoreKey, stakingKeeper types.StakingKeeper, slashinKeeper types.SlashingKeeper, authorityKeeper types.AuthorityKeeper, lightclientKeeper types.LightclientKeeper, + bankKeeper types.BankKeeper, + authKeeper types.AccountKeeper, authority string, ) *Keeper { if _, err := sdk.AccAddressFromBech32(authority); err != nil { @@ -46,6 +50,8 @@ func NewKeeper( slashingKeeper: slashinKeeper, authorityKeeper: authorityKeeper, lightclientKeeper: lightclientKeeper, + bankKeeper: bankKeeper, + authKeeper: authKeeper, authority: authority, } } @@ -62,6 +68,14 @@ func (k Keeper) GetAuthorityKeeper() types.AuthorityKeeper { return k.authorityKeeper } +func (k Keeper) GetBankKeeper() types.BankKeeper { + return k.bankKeeper +} + +func (k Keeper) GetAuthKeeper() types.AccountKeeper { + return k.authKeeper +} + func (k Keeper) GetLightclientKeeper() types.LightclientKeeper { return k.lightclientKeeper } @@ -74,7 +88,7 @@ func (k Keeper) StoreKey() storetypes.StoreKey { return k.storeKey } -func (k Keeper) Codec() codec.BinaryCodec { +func (k Keeper) Codec() codec.Codec { return k.cdc } diff --git a/x/observer/keeper/msg_server_disable_cctx_flags.go b/x/observer/keeper/msg_server_disable_cctx_flags.go index bd5a0a9f17..70a77849cb 100644 --- a/x/observer/keeper/msg_server_disable_cctx_flags.go +++ b/x/observer/keeper/msg_server_disable_cctx_flags.go @@ -37,7 +37,6 @@ func (k msgServer) DisableCCTX( if msg.DisableOutbound { flags.IsOutboundEnabled = false } - k.SetCrosschainFlags(ctx, flags) err = ctx.EventManager().EmitTypedEvents(&types.EventCCTXDisabled{ diff --git a/x/observer/module.go b/x/observer/module.go index 41724bc8b1..1136be6ecd 100644 --- a/x/observer/module.go +++ b/x/observer/module.go @@ -31,10 +31,10 @@ var ( // AppModuleBasic implements the AppModuleBasic interface for the observer module. type AppModuleBasic struct { - cdc codec.BinaryCodec + cdc codec.Codec } -func NewAppModuleBasic(cdc codec.BinaryCodec) AppModuleBasic { +func NewAppModuleBasic(cdc codec.Codec) AppModuleBasic { return AppModuleBasic{cdc: cdc} } diff --git a/x/observer/module_simulation.go b/x/observer/module_simulation.go index 555532c090..373682019c 100644 --- a/x/observer/module_simulation.go +++ b/x/observer/module_simulation.go @@ -1,29 +1,18 @@ package observer import ( - "math/rand" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/zeta-chain/node/x/observer/simulation" "github.com/zeta-chain/node/x/observer/types" ) -const ( - // #nosec G101 not a hardcoded credential - opWeightMsgUpdateClientParams = "op_weight_msg_update_client_params" - defaultWeightMsgUpdateClientParams int = 100 -) - -// GenerateGenesisState creates a randomized GenState of the module +// GenerateGenesisState creates a GenState of the module used to initialize the simulation runs func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - accs := make([]string, len(simState.Accounts)) - for i, acc := range simState.Accounts { - accs[i] = acc.Address.String() - } - observerGenesis := types.GenesisState{} - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&observerGenesis) + observerGenesis := types.DefaultGenesis() + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(observerGenesis) } // ProposalContents doesn't return any content functions for governance proposals @@ -36,18 +25,13 @@ func (AppModule) ProposalMsgs(_ module.SimulationState) []simtypes.WeightedPropo } // RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} +func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) +} // WeightedOperations returns the all the gov module operations with their respective weights. func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - operations := make([]simtypes.WeightedOperation, 0) - - var weightMsgUpdateClientParams int - simState.AppParams.GetOrGenerate(simState.Cdc, opWeightMsgUpdateClientParams, &weightMsgUpdateClientParams, nil, - func(_ *rand.Rand) { - weightMsgUpdateClientParams = defaultWeightMsgUpdateClientParams - }, + return simulation.WeightedOperations( + simState.AppParams, simState.Cdc, am.keeper, ) - - return operations } diff --git a/x/observer/simulation/decoders.go b/x/observer/simulation/decoders.go new file mode 100644 index 0000000000..fd8eae7535 --- /dev/null +++ b/x/observer/simulation/decoders.go @@ -0,0 +1,97 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/zeta-chain/node/x/observer/types" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding observer types. +func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + switch { + case bytes.Equal(kvA.Key, types.KeyPrefix(types.CrosschainFlagsKey)): + var crosschainFlagsA, crosschainFlagsB types.CrosschainFlags + cdc.MustUnmarshal(kvA.Value, &crosschainFlagsA) + cdc.MustUnmarshal(kvB.Value, &crosschainFlagsB) + return fmt.Sprintf("%v\n%v", crosschainFlagsA, crosschainFlagsB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.LastBlockObserverCountKey)): + var lastBlockObserverCountA, lastBlockObserverCountB types.LastObserverCount + cdc.MustUnmarshal(kvA.Value, &lastBlockObserverCountA) + cdc.MustUnmarshal(kvB.Value, &lastBlockObserverCountB) + return fmt.Sprintf("%v\n%v", lastBlockObserverCountA, lastBlockObserverCountB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.NodeAccountKey)): + var nodeAccountA, nodeAccountB types.NodeAccount + cdc.MustUnmarshal(kvA.Value, &nodeAccountA) + cdc.MustUnmarshal(kvB.Value, &nodeAccountB) + return fmt.Sprintf("%v\n%v", nodeAccountA, nodeAccountB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.KeygenKey)): + var keygenA, keygenB types.Keygen + cdc.MustUnmarshal(kvA.Value, &keygenA) + cdc.MustUnmarshal(kvB.Value, &keygenB) + return fmt.Sprintf("%v\n%v", keygenA, keygenB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.BallotListKey)): + var ballotListA, ballotListB types.BallotListForHeight + cdc.MustUnmarshal(kvA.Value, &ballotListA) + cdc.MustUnmarshal(kvB.Value, &ballotListB) + return fmt.Sprintf("%v\n%v", ballotListA, ballotListB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.VoterKey)): + var voterA, voterB types.Ballot + cdc.MustUnmarshal(kvA.Value, &voterA) + cdc.MustUnmarshal(kvB.Value, &voterB) + return fmt.Sprintf("%v\n%v", voterA, voterB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.TSSKey)): + var tssA, tssB types.TSS + cdc.MustUnmarshal(kvA.Value, &tssA) + cdc.MustUnmarshal(kvB.Value, &tssB) + return fmt.Sprintf("%v\n%v", tssA, tssB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.ObserverSetKey)): + var observerSetA, observerSetB types.ObserverSet + cdc.MustUnmarshal(kvA.Value, &observerSetA) + cdc.MustUnmarshal(kvB.Value, &observerSetB) + return fmt.Sprintf("%v\n%v", observerSetA, observerSetB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.AllChainParamsKey)): + var allChainParamsA, allChainParamsB types.ChainParamsList + cdc.MustUnmarshal(kvA.Value, &allChainParamsA) + cdc.MustUnmarshal(kvB.Value, &allChainParamsB) + return fmt.Sprintf("%v\n%v", allChainParamsA, allChainParamsB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.TSSHistoryKey)): + var tssHistoryA, tssHistoryB types.TSS + cdc.MustUnmarshal(kvA.Value, &tssHistoryA) + cdc.MustUnmarshal(kvB.Value, &tssHistoryB) + return fmt.Sprintf("%v\n%v", tssHistoryA, tssHistoryB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.TssFundMigratorKey)): + var tssFundMigratorA, tssFundMigratorB types.TssFundMigratorInfo + cdc.MustUnmarshal(kvA.Value, &tssFundMigratorA) + cdc.MustUnmarshal(kvB.Value, &tssFundMigratorB) + return fmt.Sprintf("%v\n%v", tssFundMigratorA, tssFundMigratorB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.PendingNoncesKeyPrefix)): + var pendingNoncesA, pendingNoncesB types.PendingNonces + cdc.MustUnmarshal(kvA.Value, &pendingNoncesA) + cdc.MustUnmarshal(kvB.Value, &pendingNoncesB) + return fmt.Sprintf("%v\n%v", pendingNoncesA, pendingNoncesB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.ChainNoncesKey)): + var chainNoncesA, chainNoncesB types.ChainNonces + cdc.MustUnmarshal(kvA.Value, &chainNoncesA) + cdc.MustUnmarshal(kvB.Value, &chainNoncesB) + return fmt.Sprintf("%v\n%v", chainNoncesA, chainNoncesB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.NonceToCctxKeyPrefix)): + var nonceToCctxA, nonceToCctxB types.NonceToCctx + cdc.MustUnmarshal(kvA.Value, &nonceToCctxA) + cdc.MustUnmarshal(kvB.Value, &nonceToCctxB) + return fmt.Sprintf("%v\n%v", nonceToCctxA, nonceToCctxB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.ParamsKey)): + var paramsA, paramsB types.Params + cdc.MustUnmarshal(kvA.Value, ¶msA) + cdc.MustUnmarshal(kvB.Value, ¶msB) + return fmt.Sprintf("%v\n%v", paramsA, paramsB) + default: + panic(fmt.Sprintf("invalid observer key prefix %X", kvA.Key)) + } + } +} diff --git a/x/observer/simulation/decoders_test.go b/x/observer/simulation/decoders_test.go new file mode 100644 index 0000000000..616994a664 --- /dev/null +++ b/x/observer/simulation/decoders_test.go @@ -0,0 +1,88 @@ +package simulation_test + +import ( + "fmt" + "testing" + + "github.com/cosmos/cosmos-sdk/types/kv" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/pkg/chains" + keepertest "github.com/zeta-chain/node/testutil/keeper" + "github.com/zeta-chain/node/testutil/sample" + "github.com/zeta-chain/node/x/observer/simulation" + "github.com/zeta-chain/node/x/observer/types" +) + +func TestNewDecodeStore(t *testing.T) { + k, _, _, _ := keepertest.ObserverKeeper(t) + cdc := k.Codec() + dec := simulation.NewDecodeStore(cdc) + crosschainFlags := sample.CrosschainFlags() + lastBlockObserverCount := sample.LastObserverCount(10) + nodeAccount := sample.NodeAccount() + keygen := sample.Keygen(t) + + ballotList := types.BallotListForHeight{ + Height: 10, + BallotsIndexList: []string{sample.ZetaIndex(t)}, + } + + ballot := sample.Ballot(t, "sample") + tss := sample.Tss() + observerSet := sample.ObserverSet(10) + chainParamsList := sample.ChainParamsList() + //tssHistory := sample.TssList(10) + tssFundMigrator := sample.TssFundsMigrator(chains.Ethereum.ChainId) + pendingNonce := sample.PendingNoncesList(t, "index", 10)[0] + chainNonces := sample.ChainNonces(chains.Ethereum.ChainId) + nonceToCctx := sample.NonceToCCTX(t, "index") + params := types.Params{BallotMaturityBlocks: 100} + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + {Key: types.KeyPrefix(types.CrosschainFlagsKey), Value: cdc.MustMarshal(crosschainFlags)}, + {Key: types.KeyPrefix(types.LastBlockObserverCountKey), Value: cdc.MustMarshal(lastBlockObserverCount)}, + {Key: types.KeyPrefix(types.NodeAccountKey), Value: cdc.MustMarshal(nodeAccount)}, + {Key: types.KeyPrefix(types.KeygenKey), Value: cdc.MustMarshal(keygen)}, + {Key: types.KeyPrefix(types.BallotListKey), Value: cdc.MustMarshal(&ballotList)}, + {Key: types.KeyPrefix(types.VoterKey), Value: cdc.MustMarshal(ballot)}, + {Key: types.KeyPrefix(types.TSSKey), Value: cdc.MustMarshal(&tss)}, + {Key: types.KeyPrefix(types.TSSHistoryKey), Value: cdc.MustMarshal(&tss)}, + {Key: types.KeyPrefix(types.ObserverSetKey), Value: cdc.MustMarshal(&observerSet)}, + {Key: types.KeyPrefix(types.AllChainParamsKey), Value: cdc.MustMarshal(&chainParamsList)}, + {Key: types.KeyPrefix(types.TssFundMigratorKey), Value: cdc.MustMarshal(&tssFundMigrator)}, + {Key: types.KeyPrefix(types.PendingNoncesKeyPrefix), Value: cdc.MustMarshal(&pendingNonce)}, + {Key: types.KeyPrefix(types.ChainNoncesKey), Value: cdc.MustMarshal(&chainNonces)}, + {Key: types.KeyPrefix(types.NonceToCctxKeyPrefix), Value: cdc.MustMarshal(&nonceToCctx)}, + {Key: types.KeyPrefix(types.ParamsKey), Value: cdc.MustMarshal(¶ms)}, + }, + } + + tests := []struct { + name string + expectedLog string + }{ + {"CrosschainFlags", fmt.Sprintf("%v\n%v", *crosschainFlags, *crosschainFlags)}, + {"LastBlockObserverCount", fmt.Sprintf("%v\n%v", *lastBlockObserverCount, *lastBlockObserverCount)}, + {"NodeAccount", fmt.Sprintf("%v\n%v", *nodeAccount, *nodeAccount)}, + {"Keygen", fmt.Sprintf("%v\n%v", *keygen, *keygen)}, + {"BallotList", fmt.Sprintf("%v\n%v", ballotList, ballotList)}, + {"Ballot", fmt.Sprintf("%v\n%v", *ballot, *ballot)}, + {"TSS", fmt.Sprintf("%v\n%v", tss, tss)}, + {"TSSHistory", fmt.Sprintf("%v\n%v", tss, tss)}, + {"ObserverSet", fmt.Sprintf("%v\n%v", observerSet, observerSet)}, + {"ChainParamsList", fmt.Sprintf("%v\n%v", chainParamsList, chainParamsList)}, + {"TssFundMigrator", fmt.Sprintf("%v\n%v", tssFundMigrator, tssFundMigrator)}, + {"PendingNonces", fmt.Sprintf("%v\n%v", pendingNonce, pendingNonce)}, + {"ChainNonces", fmt.Sprintf("%v\n%v", chainNonces, chainNonces)}, + {"NonceToCctx", fmt.Sprintf("%v\n%v", nonceToCctx, nonceToCctx)}, + {"Params", fmt.Sprintf("%v\n%v", params, params)}, + } + + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i])) + }) + } +} diff --git a/x/observer/simulation/operations.go b/x/observer/simulation/operations.go new file mode 100644 index 0000000000..7c12ced3c0 --- /dev/null +++ b/x/observer/simulation/operations.go @@ -0,0 +1,107 @@ +package simulation + +import ( + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/zeta-chain/node/x/observer/keeper" + "github.com/zeta-chain/node/x/observer/types" +) + +// Simulation operation weights constants +// Operation weights are used by the `SimulateFromSeed` +// function to pick a random operation based on the weights.The functions with higher weights are more likely to be picked. + +// Therefore, this decides the percentage of a certain operation that is part of a block. + +// Based on the weights assigned in the cosmos sdk modules, +// 100 seems to the max weight used,and we should use relative weights +// to signify the number of each operation in a block. + +// TODO Add more details to comment based on what the number represents in terms of percentage of operations in a block +// https://github.com/zeta-chain/node/issues/3100 +const ( + // #nosec G101 not a hardcoded credential + OpWeightMsgTypeMsgEnableCCTX = "op_weight_msg_enable_crosschain_flags" + // DefaultWeightMsgTypeMsgEnableCCTX We ues a high weight for this operation + // to ensure that it is present in the block more number of times than any operation that changes the validator set + + // Arrived at this number based on the weights used in the cosmos sdk staking module and through some trial and error + DefaultWeightMsgTypeMsgEnableCCTX = 3650 +) + +// WeightedOperations for observer module +func WeightedOperations( + appParams simtypes.AppParams, cdc codec.JSONCodec, k keeper.Keeper, +) simulation.WeightedOperations { + var weightMsgTypeMsgEnableCCTX int + + appParams.GetOrGenerate(cdc, OpWeightMsgTypeMsgEnableCCTX, &weightMsgTypeMsgEnableCCTX, nil, + func(_ *rand.Rand) { + weightMsgTypeMsgEnableCCTX = DefaultWeightMsgTypeMsgEnableCCTX + }) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgTypeMsgEnableCCTX, + SimulateMsgTypeMsgEnableCCTX(k), + ), + } +} + +// SimulateMsgTypeMsgEnableCCTX generates a MsgEnableCCTX and delivers it. +func SimulateMsgTypeMsgEnableCCTX(k keeper.Keeper) simtypes.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []simtypes.Account, _ string, + ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { + policies, found := k.GetAuthorityKeeper().GetPolicies(ctx) + if !found { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgEnableCCTX, "policies object not found"), nil, nil + } + if len(policies.Items) == 0 { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgEnableCCTX, "no policies found"), nil, nil + } + + admin := policies.Items[0].Address + address, err := types.GetOperatorAddressFromAccAddress(admin) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgEnableCCTX, err.Error()), nil, err + } + simAccount, found := simtypes.FindAccount(accounts, address) + if !found { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgEnableCCTX, "admin account not found"), nil, nil + } + + msg := types.MsgEnableCCTX{ + Creator: simAccount.Address.String(), + EnableInbound: true, + EnableOutbound: false, + } + + err = msg.ValidateBasic() + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), err.Error()), nil, err + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: &msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: simAccount, + AccountKeeper: k.GetAuthKeeper(), + Bankkeeper: k.GetBankKeeper(), + ModuleName: types.ModuleName, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} diff --git a/x/observer/simulation/simap.go b/x/observer/simulation/simap.go deleted file mode 100644 index 92c437c0d1..0000000000 --- a/x/observer/simulation/simap.go +++ /dev/null @@ -1,15 +0,0 @@ -package simulation - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -// FindAccount find a specific address from an account list -func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { - creator, err := sdk.AccAddressFromBech32(address) - if err != nil { - panic(err) - } - return simtypes.FindAccount(accs, creator) -} diff --git a/x/observer/types/expected_keepers.go b/x/observer/types/expected_keepers.go index 2cf2b9ac75..2788187c94 100644 --- a/x/observer/types/expected_keepers.go +++ b/x/observer/types/expected_keepers.go @@ -2,6 +2,7 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/types" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -38,6 +39,7 @@ type AuthorityKeeper interface { // SetPolicies is solely used for the migration of policies from observer to authority SetPolicies(ctx sdk.Context, policies authoritytypes.Policies) + GetPolicies(ctx sdk.Context) (val authoritytypes.Policies, found bool) } type LightclientKeeper interface { @@ -57,3 +59,12 @@ type LightclientKeeper interface { parentHash []byte, ) } + +type BankKeeper interface { + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins +} + +// AccountKeeper defines the expected account keeper used for simulations (noalias) +type AccountKeeper interface { + GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI +} diff --git a/x/observer/types/keys.go b/x/observer/types/keys.go index 539eab83c0..abdb2543d0 100644 --- a/x/observer/types/keys.go +++ b/x/observer/types/keys.go @@ -49,13 +49,15 @@ func ChainNoncesKeyPrefix(chainID int64) []byte { return []byte(strconv.FormatInt(chainID, 10)) } +// Address TODOS for name changes: https://github.com/zeta-chain/node/issues/3098 const ( BlameKey = "Blame-" - // TODO change identifier for VoterKey to something more descriptive + // TODO change identifier for VoterKey to BallotKey VoterKey = "Voter-value-" // AllChainParamsKey is the ke prefix for all chain params // NOTE: CoreParams is old name for AllChainParams we keep it as key value for backward compatibility + // TODO rename to ChainParamsListKey AllChainParamsKey = "CoreParams" ObserverSetKey = "ObserverSet-value-" @@ -64,20 +66,25 @@ const ( // NOTE: PermissionFlags is old name for CrosschainFlags we keep it as key value for backward compatibility CrosschainFlagsKey = "PermissionFlags-value-" + // TODO rename to LastObserverCountKey LastBlockObserverCountKey = "ObserverCount-value-" NodeAccountKey = "NodeAccount-value-" KeygenKey = "Keygen-value-" - BlockHeaderKey = "BlockHeader-value-" - BlockHeaderStateKey = "BlockHeaderState-value-" - BallotListKey = "BallotList-value-" - TSSKey = "TSS-value-" - TSSHistoryKey = "TSS-History-value-" + // TODO rename to BallotListForHeightKey + BallotListKey = "BallotList-value-" + TSSKey = "TSS-value-" + TSSHistoryKey = "TSS-History-value-" + + // TODO rename to TssFundMigratorInfoKey TssFundMigratorKey = "FundsMigrator-value-" + // TODO Rename to PendingNoncesListKey PendingNoncesKeyPrefix = "PendingNonces-value-" ChainNoncesKey = "ChainNonces-value-" - NonceToCctxKeyPrefix = "NonceToCctx-value-" + + // TODO rename to NonceToCctxListKey + NonceToCctxKeyPrefix = "NonceToCctx-value-" ParamsKey = "Params-value-" ) diff --git a/zetaclient/chains/base/observer.go b/zetaclient/chains/base/observer.go index af366faa5e..8cb3ab7cdb 100644 --- a/zetaclient/chains/base/observer.go +++ b/zetaclient/chains/base/observer.go @@ -31,10 +31,6 @@ const ( // DefaultBlockCacheSize is the default number of blocks that the observer will keep in cache for performance (without RPC calls) // Cached blocks can be used to get block information and verify transactions DefaultBlockCacheSize = 1000 - - // DefaultHeaderCacheSize is the default number of headers that the observer will keep in cache for performance (without RPC calls) - // Cached headers can be used to get header information - DefaultHeaderCacheSize = 1000 ) // Observer is the base structure for chain observers, grouping the common logic for each chain observer client. @@ -64,12 +60,8 @@ type Observer struct { // rpcAlertLatency is the threshold of RPC latency to trigger an alert rpcAlertLatency time.Duration - // blockCache is the cache for blocks blockCache *lru.Cache - // headerCache is the cache for headers - headerCache *lru.Cache - // db is the database to persist data db *db.DB @@ -95,13 +87,17 @@ func NewObserver( zetacoreClient interfaces.ZetacoreClient, tss interfaces.TSSSigner, blockCacheSize int, - headerCacheSize int, rpcAlertLatency int64, ts *metrics.TelemetryServer, database *db.DB, logger Logger, ) (*Observer, error) { - ob := Observer{ + blockCache, err := lru.New(blockCacheSize) + if err != nil { + return nil, errors.Wrap(err, "error creating block cache") + } + + return &Observer{ chain: chain, chainParams: chainParams, zetacoreClient: zetacoreClient, @@ -112,27 +108,11 @@ func NewObserver( rpcAlertLatency: time.Duration(rpcAlertLatency) * time.Second, ts: ts, db: database, + blockCache: blockCache, mu: &sync.Mutex{}, + logger: newObserverLogger(chain, logger), stop: make(chan struct{}), - } - - // setup loggers - ob.WithLogger(logger) - - // create block cache - var err error - ob.blockCache, err = lru.New(blockCacheSize) - if err != nil { - return nil, errors.Wrap(err, "error creating block cache") - } - - // create header cache - ob.headerCache, err = lru.New(headerCacheSize) - if err != nil { - return nil, errors.Wrap(err, "error creating header cache") - } - - return &ob, nil + }, nil } // Start starts the observer. Returns false if it's already started (noop). @@ -178,12 +158,6 @@ func (ob *Observer) Chain() chains.Chain { return ob.chain } -// WithChain attaches a new chain to the observer. -func (ob *Observer) WithChain(chain chains.Chain) *Observer { - ob.chain = chain - return ob -} - // ChainParams returns the chain params for the observer. func (ob *Observer) ChainParams() observertypes.ChainParams { ob.mu.Lock() @@ -205,23 +179,11 @@ func (ob *Observer) ZetacoreClient() interfaces.ZetacoreClient { return ob.zetacoreClient } -// WithZetacoreClient attaches a new zetacore client to the observer. -func (ob *Observer) WithZetacoreClient(client interfaces.ZetacoreClient) *Observer { - ob.zetacoreClient = client - return ob -} - // TSS returns the tss signer for the observer. func (ob *Observer) TSS() interfaces.TSSSigner { return ob.tss } -// WithTSS attaches a new tss signer to the observer. -func (ob *Observer) WithTSS(tss interfaces.TSSSigner) *Observer { - ob.tss = tss - return ob -} - // TSSAddressString returns the TSS address for the chain. // // Note: all chains uses TSS EVM address except Bitcoin chain. @@ -287,23 +249,6 @@ func (ob *Observer) BlockCache() *lru.Cache { return ob.blockCache } -// WithBlockCache attaches a new block cache to the observer. -func (ob *Observer) WithBlockCache(cache *lru.Cache) *Observer { - ob.blockCache = cache - return ob -} - -// HeaderCache returns the header cache for the observer. -func (ob *Observer) HeaderCache() *lru.Cache { - return ob.headerCache -} - -// WithHeaderCache attaches a new header cache to the observer. -func (ob *Observer) WithHeaderCache(cache *lru.Cache) *Observer { - ob.headerCache = cache - return ob -} - // OutboundID returns a unique identifier for the outbound transaction. // The identifier is now used as the key for maps that store outbound related data (e.g. transaction, receipt, etc). func (ob *Observer) OutboundID(nonce uint64) string { @@ -316,12 +261,6 @@ func (ob *Observer) DB() *db.DB { return ob.db } -// WithTelemetryServer attaches a new telemetry server to the observer. -func (ob *Observer) WithTelemetryServer(ts *metrics.TelemetryServer) *Observer { - ob.ts = ts - return ob -} - // TelemetryServer returns the telemetry server for the observer. func (ob *Observer) TelemetryServer() *metrics.TelemetryServer { return ob.ts @@ -332,26 +271,6 @@ func (ob *Observer) Logger() *ObserverLogger { return &ob.logger } -// WithLogger attaches a new logger to the observer. -func (ob *Observer) WithLogger(logger Logger) *Observer { - chainLogger := logger.Std. - With(). - Int64(logs.FieldChain, ob.chain.ChainId). - Str(logs.FieldChainNetwork, ob.chain.Network.String()). - Logger() - - ob.logger = ObserverLogger{ - Chain: chainLogger, - Inbound: chainLogger.With().Str(logs.FieldModule, logs.ModNameInbound).Logger(), - Outbound: chainLogger.With().Str(logs.FieldModule, logs.ModNameOutbound).Logger(), - GasPrice: chainLogger.With().Str(logs.FieldModule, logs.ModNameGasPrice).Logger(), - Headers: chainLogger.With().Str(logs.FieldModule, logs.ModNameHeaders).Logger(), - Compliance: logger.Compliance, - } - - return ob -} - // Mu returns the mutex for the observer. func (ob *Observer) Mu() *sync.Mutex { return ob.mu @@ -544,3 +463,24 @@ func EnvVarLatestBlockByChain(chain chains.Chain) string { func EnvVarLatestTxByChain(chain chains.Chain) string { return fmt.Sprintf("CHAIN_%d_SCAN_FROM_TX", chain.ChainId) } + +func newObserverLogger(chain chains.Chain, logger Logger) ObserverLogger { + withLogFields := func(l zerolog.Logger) zerolog.Logger { + return l.With(). + Int64(logs.FieldChain, chain.ChainId). + Str(logs.FieldChainNetwork, chain.Network.String()). + Logger() + } + + log := withLogFields(logger.Std) + complianceLog := withLogFields(logger.Compliance) + + return ObserverLogger{ + Chain: log, + Inbound: log.With().Str(logs.FieldModule, logs.ModNameInbound).Logger(), + Outbound: log.With().Str(logs.FieldModule, logs.ModNameOutbound).Logger(), + GasPrice: log.With().Str(logs.FieldModule, logs.ModNameGasPrice).Logger(), + Headers: log.With().Str(logs.FieldModule, logs.ModNameHeaders).Logger(), + Compliance: complianceLog, + } +} diff --git a/zetaclient/chains/base/observer_test.go b/zetaclient/chains/base/observer_test.go index 0ca1a6a147..5c08563b7b 100644 --- a/zetaclient/chains/base/observer_test.go +++ b/zetaclient/chains/base/observer_test.go @@ -8,7 +8,6 @@ import ( "testing" "time" - lru "github.com/hashicorp/golang-lru" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/stretchr/testify/require" @@ -22,7 +21,6 @@ import ( "github.com/zeta-chain/node/zetaclient/config" zctx "github.com/zeta-chain/node/zetaclient/context" "github.com/zeta-chain/node/zetaclient/db" - "github.com/zeta-chain/node/zetaclient/metrics" "github.com/zeta-chain/node/zetaclient/testutils/mocks" ) @@ -34,8 +32,15 @@ const ( defaultConfirmationCount = 2 ) -// createObserver creates a new observer for testing -func createObserver(t *testing.T, chain chains.Chain, alertLatency int64) *base.Observer { +type testSuite struct { + *base.Observer + db *db.DB + tss *mocks.TSS + zetacore *mocks.ZetacoreClient +} + +// newTestSuite creates a new observer for testing +func newTestSuite(t *testing.T, chain chains.Chain, alertLatency int64) *testSuite { // constructor parameters chainParams := *sample.ChainParams(chain.ChainId) chainParams.ConfirmationCount = defaultConfirmationCount @@ -52,7 +57,6 @@ func createObserver(t *testing.T, chain chains.Chain, alertLatency int64) *base. zetacoreClient, tss, base.DefaultBlockCacheSize, - base.DefaultHeaderCacheSize, alertLatency, nil, database, @@ -60,7 +64,12 @@ func createObserver(t *testing.T, chain chains.Chain, alertLatency int64) *base. ) require.NoError(t, err) - return ob + return &testSuite{ + Observer: ob, + db: database, + tss: tss, + zetacore: zetacoreClient, + } } func TestNewObserver(t *testing.T) { @@ -71,57 +80,41 @@ func TestNewObserver(t *testing.T) { zetacoreClient := mocks.NewZetacoreClient(t) tss := mocks.NewTSS(t) blockCacheSize := base.DefaultBlockCacheSize - headersCacheSize := base.DefaultHeaderCacheSize database := createDatabase(t) // test cases tests := []struct { - name string - chain chains.Chain - chainParams observertypes.ChainParams - appContext *zctx.AppContext - zetacoreClient interfaces.ZetacoreClient - tss interfaces.TSSSigner - blockCacheSize int - headerCacheSize int - fail bool - message string + name string + chain chains.Chain + chainParams observertypes.ChainParams + appContext *zctx.AppContext + zetacoreClient interfaces.ZetacoreClient + tss interfaces.TSSSigner + blockCacheSize int + fail bool + message string }{ { - name: "should be able to create new observer", - chain: chain, - chainParams: chainParams, - appContext: appContext, - zetacoreClient: zetacoreClient, - tss: tss, - blockCacheSize: blockCacheSize, - headerCacheSize: headersCacheSize, - fail: false, - }, - { - name: "should return error on invalid block cache size", - chain: chain, - chainParams: chainParams, - appContext: appContext, - zetacoreClient: zetacoreClient, - tss: tss, - blockCacheSize: 0, - headerCacheSize: headersCacheSize, - fail: true, - message: "error creating block cache", + name: "should be able to create new observer", + chain: chain, + chainParams: chainParams, + appContext: appContext, + zetacoreClient: zetacoreClient, + tss: tss, + blockCacheSize: blockCacheSize, + fail: false, }, { - name: "should return error on invalid header cache size", - chain: chain, - chainParams: chainParams, - appContext: appContext, - zetacoreClient: zetacoreClient, - tss: tss, - blockCacheSize: blockCacheSize, - headerCacheSize: 0, - fail: true, - message: "error creating header cache", + name: "should return error on invalid block cache size", + chain: chain, + chainParams: chainParams, + appContext: appContext, + zetacoreClient: zetacoreClient, + tss: tss, + blockCacheSize: 0, + fail: true, + message: "error creating block cache", }, } @@ -134,7 +127,6 @@ func TestNewObserver(t *testing.T) { tt.zetacoreClient, tt.tss, tt.blockCacheSize, - tt.headerCacheSize, 60, nil, database, @@ -155,7 +147,7 @@ func TestNewObserver(t *testing.T) { func TestStop(t *testing.T) { t.Run("should be able to stop observer", func(t *testing.T) { // create observer and initialize db - ob := createObserver(t, chains.Ethereum, defaultAlertLatency) + ob := newTestSuite(t, chains.Ethereum, defaultAlertLatency) // stop observer ob.Stop() @@ -165,93 +157,35 @@ func TestStop(t *testing.T) { func TestObserverGetterAndSetter(t *testing.T) { chain := chains.Ethereum - t.Run("should be able to update chain", func(t *testing.T) { - ob := createObserver(t, chain, defaultAlertLatency) - - // update chain - newChain := chains.BscMainnet - ob = ob.WithChain(chains.BscMainnet) - require.Equal(t, newChain, ob.Chain()) - }) - - t.Run("should be able to update zetacore client", func(t *testing.T) { - ob := createObserver(t, chain, defaultAlertLatency) - - // update zetacore client - newZetacoreClient := mocks.NewZetacoreClient(t) - ob = ob.WithZetacoreClient(newZetacoreClient) - require.Equal(t, newZetacoreClient, ob.ZetacoreClient()) - }) - - t.Run("should be able to update tss", func(t *testing.T) { - ob := createObserver(t, chain, defaultAlertLatency) - - // update tss - newTSS := mocks.NewTSS(t) - ob = ob.WithTSS(newTSS) - require.Equal(t, newTSS, ob.TSS()) - }) - t.Run("should be able to update last block", func(t *testing.T) { - ob := createObserver(t, chain, defaultAlertLatency) + ob := newTestSuite(t, chain, defaultAlertLatency) // update last block newLastBlock := uint64(100) - ob = ob.WithLastBlock(newLastBlock) + ob.Observer.WithLastBlock(newLastBlock) require.Equal(t, newLastBlock, ob.LastBlock()) }) t.Run("should be able to update last block scanned", func(t *testing.T) { - ob := createObserver(t, chain, defaultAlertLatency) + ob := newTestSuite(t, chain, defaultAlertLatency) // update last block scanned newLastBlockScanned := uint64(100) - ob = ob.WithLastBlockScanned(newLastBlockScanned) + ob.Observer.WithLastBlockScanned(newLastBlockScanned) require.Equal(t, newLastBlockScanned, ob.LastBlockScanned()) }) t.Run("should be able to update last tx scanned", func(t *testing.T) { - ob := createObserver(t, chain, defaultAlertLatency) + ob := newTestSuite(t, chain, defaultAlertLatency) // update last tx scanned newLastTxScanned := sample.EthAddress().String() - ob = ob.WithLastTxScanned(newLastTxScanned) + ob.Observer.WithLastTxScanned(newLastTxScanned) require.Equal(t, newLastTxScanned, ob.LastTxScanned()) }) - t.Run("should be able to replace block cache", func(t *testing.T) { - ob := createObserver(t, chain, defaultAlertLatency) - - // update block cache - newBlockCache, err := lru.New(200) - require.NoError(t, err) - - ob = ob.WithBlockCache(newBlockCache) - require.Equal(t, newBlockCache, ob.BlockCache()) - }) - - t.Run("should be able to replace header cache", func(t *testing.T) { - ob := createObserver(t, chain, defaultAlertLatency) - - // update headers cache - newHeadersCache, err := lru.New(200) - require.NoError(t, err) - - ob = ob.WithHeaderCache(newHeadersCache) - require.Equal(t, newHeadersCache, ob.HeaderCache()) - }) - - t.Run("should be able to update telemetry server", func(t *testing.T) { - ob := createObserver(t, chain, defaultAlertLatency) - - // update telemetry server - newServer := metrics.NewTelemetryServer() - ob = ob.WithTelemetryServer(newServer) - require.Equal(t, newServer, ob.TelemetryServer()) - }) - t.Run("should be able to get logger", func(t *testing.T) { - ob := createObserver(t, chain, defaultAlertLatency) + ob := newTestSuite(t, chain, defaultAlertLatency) logger := ob.Logger() // should be able to print log @@ -265,10 +199,12 @@ func TestObserverGetterAndSetter(t *testing.T) { } func TestTSSAddressString(t *testing.T) { + btcSomething := chains.BitcoinMainnet + btcSomething.ChainId = 123123123 + tests := []struct { name string chain chains.Chain - forceError bool addrExpected string }{ { @@ -288,8 +224,7 @@ func TestTSSAddressString(t *testing.T) { }, { name: "should return empty address for unknown BTC chain", - chain: chains.BitcoinMainnet, - forceError: true, + chain: btcSomething, addrExpected: "", }, } @@ -298,18 +233,7 @@ func TestTSSAddressString(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // create observer - ob := createObserver(t, tt.chain, defaultAlertLatency) - - // force error if needed - if tt.forceError { - // pause TSS to cause error - tss := mocks.NewTSS(t) - tss.Pause() - ob = ob.WithTSS(tss) - c := chains.BitcoinRegtest - c.ChainId = 123123123 - ob.WithChain(c) - } + ob := newTestSuite(t, tt.chain, defaultAlertLatency) // get TSS address addr := ob.TSSAddressString() @@ -362,8 +286,8 @@ func TestIsBlockConfirmed(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // create observer - ob := createObserver(t, tt.chain, defaultAlertLatency) - ob = ob.WithLastBlock(tt.lastBlock) + ob := newTestSuite(t, tt.chain, defaultAlertLatency) + ob.Observer.WithLastBlock(tt.lastBlock) // check if block is confirmed confirmed := ob.IsBlockConfirmed(tt.block) @@ -376,19 +300,16 @@ func TestOutboundID(t *testing.T) { tests := []struct { name string chain chains.Chain - tss interfaces.TSSSigner nonce uint64 }{ { name: "should get correct outbound id for Ethereum chain", chain: chains.Ethereum, - tss: mocks.NewTSS(t), nonce: 100, }, { name: "should get correct outbound id for Bitcoin chain", chain: chains.BitcoinMainnet, - tss: mocks.NewTSS(t), nonce: 200, }, } @@ -397,8 +318,7 @@ func TestOutboundID(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // create observer - ob := createObserver(t, tt.chain, defaultAlertLatency) - ob = ob.WithTSS(tt.tss) + ob := newTestSuite(t, tt.chain, defaultAlertLatency) // get outbound id outboundID := ob.OutboundID(tt.nonce) @@ -416,7 +336,7 @@ func TestLoadLastBlockScanned(t *testing.T) { t.Run("should be able to load last block scanned", func(t *testing.T) { // create observer and open db - ob := createObserver(t, chain, defaultAlertLatency) + ob := newTestSuite(t, chain, defaultAlertLatency) // create db and write 100 as last block scanned err := ob.WriteLastBlockScannedToDB(100) @@ -430,7 +350,7 @@ func TestLoadLastBlockScanned(t *testing.T) { t.Run("latest block scanned should be 0 if not found in db", func(t *testing.T) { // create observer and open db - ob := createObserver(t, chain, defaultAlertLatency) + ob := newTestSuite(t, chain, defaultAlertLatency) // read last block scanned err := ob.LoadLastBlockScanned(log.Logger) @@ -440,7 +360,7 @@ func TestLoadLastBlockScanned(t *testing.T) { t.Run("should overwrite last block scanned if env var is set", func(t *testing.T) { // create observer and open db - ob := createObserver(t, chain, defaultAlertLatency) + ob := newTestSuite(t, chain, defaultAlertLatency) // create db and write 100 as last block scanned ob.WriteLastBlockScannedToDB(100) @@ -456,7 +376,7 @@ func TestLoadLastBlockScanned(t *testing.T) { t.Run("last block scanned should remain 0 if env var is set to latest", func(t *testing.T) { // create observer and open db - ob := createObserver(t, chain, defaultAlertLatency) + ob := newTestSuite(t, chain, defaultAlertLatency) // create db and write 100 as last block scanned ob.WriteLastBlockScannedToDB(100) @@ -472,7 +392,7 @@ func TestLoadLastBlockScanned(t *testing.T) { t.Run("should return error on invalid env var", func(t *testing.T) { // create observer and open db - ob := createObserver(t, chain, defaultAlertLatency) + ob := newTestSuite(t, chain, defaultAlertLatency) // set invalid env var os.Setenv(envvar, "invalid") @@ -486,7 +406,7 @@ func TestLoadLastBlockScanned(t *testing.T) { func TestSaveLastBlockScanned(t *testing.T) { t.Run("should be able to save last block scanned", func(t *testing.T) { // create observer and open db - ob := createObserver(t, chains.Ethereum, defaultAlertLatency) + ob := newTestSuite(t, chains.Ethereum, defaultAlertLatency) // save 100 as last block scanned err := ob.SaveLastBlockScanned(100) @@ -506,7 +426,7 @@ func TestReadWriteDBLastBlockScanned(t *testing.T) { chain := chains.Ethereum t.Run("should be able to write and read last block scanned to db", func(t *testing.T) { // create observer and open db - ob := createObserver(t, chain, defaultAlertLatency) + ob := newTestSuite(t, chain, defaultAlertLatency) // write last block scanned err := ob.WriteLastBlockScannedToDB(100) @@ -519,7 +439,7 @@ func TestReadWriteDBLastBlockScanned(t *testing.T) { t.Run("should return error when last block scanned not found in db", func(t *testing.T) { // create empty db - ob := createObserver(t, chain, defaultAlertLatency) + ob := newTestSuite(t, chain, defaultAlertLatency) lastScannedBlock, err := ob.ReadLastBlockScannedFromDB() require.Error(t, err) @@ -533,7 +453,7 @@ func TestLoadLastTxScanned(t *testing.T) { t.Run("should be able to load last tx scanned", func(t *testing.T) { // create observer and open db - ob := createObserver(t, chain, defaultAlertLatency) + ob := newTestSuite(t, chain, defaultAlertLatency) // create db and write sample hash as last tx scanned ob.WriteLastTxScannedToDB(lastTx) @@ -545,7 +465,7 @@ func TestLoadLastTxScanned(t *testing.T) { t.Run("latest tx scanned should be empty if not found in db", func(t *testing.T) { // create observer and open db - ob := createObserver(t, chain, defaultAlertLatency) + ob := newTestSuite(t, chain, defaultAlertLatency) // read last tx scanned ob.LoadLastTxScanned() @@ -554,7 +474,7 @@ func TestLoadLastTxScanned(t *testing.T) { t.Run("should overwrite last tx scanned if env var is set", func(t *testing.T) { // create observer and open db - ob := createObserver(t, chain, defaultAlertLatency) + ob := newTestSuite(t, chain, defaultAlertLatency) // create db and write sample hash as last tx scanned ob.WriteLastTxScannedToDB(lastTx) @@ -573,7 +493,7 @@ func TestSaveLastTxScanned(t *testing.T) { chain := chains.SolanaDevnet t.Run("should be able to save last tx scanned", func(t *testing.T) { // create observer and open db - ob := createObserver(t, chain, defaultAlertLatency) + ob := newTestSuite(t, chain, defaultAlertLatency) // save random tx hash lastSlot := uint64(100) @@ -596,7 +516,7 @@ func TestReadWriteDBLastTxScanned(t *testing.T) { chain := chains.SolanaDevnet t.Run("should be able to write and read last tx scanned to db", func(t *testing.T) { // create observer and open db - ob := createObserver(t, chain, defaultAlertLatency) + ob := newTestSuite(t, chain, defaultAlertLatency) // write last tx scanned lastTx := "5LuQMorgd11p8GWEw6pmyHCDtA26NUyeNFhLWPNk2oBoM9pkag1LzhwGSRos3j4TJLhKjswFhZkGtvSGdLDkmqsk" @@ -610,7 +530,7 @@ func TestReadWriteDBLastTxScanned(t *testing.T) { t.Run("should return error when last tx scanned not found in db", func(t *testing.T) { // create empty db - ob := createObserver(t, chain, defaultAlertLatency) + ob := newTestSuite(t, chain, defaultAlertLatency) lastTxScanned, err := ob.ReadLastTxScannedFromDB() require.Error(t, err) @@ -621,12 +541,9 @@ func TestReadWriteDBLastTxScanned(t *testing.T) { func TestPostVoteInbound(t *testing.T) { t.Run("should be able to post vote inbound", func(t *testing.T) { // create observer - ob := createObserver(t, chains.Ethereum, defaultAlertLatency) + ob := newTestSuite(t, chains.Ethereum, defaultAlertLatency) - // create mock zetacore client - zetacoreClient := mocks.NewZetacoreClient(t) - zetacoreClient.WithPostVoteInbound("", "sampleBallotIndex") - ob = ob.WithZetacoreClient(zetacoreClient) + ob.zetacore.WithPostVoteInbound("", "sampleBallotIndex") // post vote inbound msg := sample.InboundVote(coin.CoinType_Gas, chains.Ethereum.ChainId, chains.ZetaChainMainnet.ChainId) @@ -637,11 +554,7 @@ func TestPostVoteInbound(t *testing.T) { t.Run("should not post vote if message basic validation fails", func(t *testing.T) { // create observer - ob := createObserver(t, chains.Ethereum, defaultAlertLatency) - - // create mock zetacore client - zetacoreClient := mocks.NewZetacoreClient(t) - ob = ob.WithZetacoreClient(zetacoreClient) + ob := newTestSuite(t, chains.Ethereum, defaultAlertLatency) // create sample message with long Message msg := sample.InboundVote(coin.CoinType_Gas, chains.Ethereum.ChainId, chains.ZetaChainMainnet.ChainId) @@ -693,7 +606,7 @@ func TestAlertOnRPCLatency(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // create observer - ob := createObserver(t, chains.Ethereum, tt.alertLatency) + ob := newTestSuite(t, chains.Ethereum, tt.alertLatency) alerted := ob.AlertOnRPCLatency(tt.blockTime, time.Duration(defaultAlertLatency)*time.Second) require.Equal(t, tt.alerted, alerted) diff --git a/zetaclient/chains/base/signer.go b/zetaclient/chains/base/signer.go index 71cebaf47a..e2e6ae45a4 100644 --- a/zetaclient/chains/base/signer.go +++ b/zetaclient/chains/base/signer.go @@ -3,10 +3,11 @@ package base import ( "sync" + "github.com/rs/zerolog" + "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/zetaclient/chains/interfaces" "github.com/zeta-chain/node/zetaclient/logs" - "github.com/zeta-chain/node/zetaclient/metrics" ) // Signer is the base structure for grouping the common logic between chain signers. @@ -18,9 +19,6 @@ type Signer struct { // tss is the TSS signer tss interfaces.TSSSigner - // ts is the telemetry server for metrics - ts *metrics.TelemetryServer - // logger contains the loggers used by signer logger Logger @@ -33,19 +31,22 @@ type Signer struct { } // NewSigner creates a new base signer. -func NewSigner(chain chains.Chain, tss interfaces.TSSSigner, ts *metrics.TelemetryServer, logger Logger) *Signer { +func NewSigner(chain chains.Chain, tss interfaces.TSSSigner, logger Logger) *Signer { + withLogFields := func(log zerolog.Logger) zerolog.Logger { + return log.With(). + Int64(logs.FieldChain, chain.ChainId). + Str(logs.FieldModule, "signer"). + Logger() + } + return &Signer{ - chain: chain, - tss: tss, - ts: ts, + chain: chain, + tss: tss, + outboundBeingReported: make(map[string]bool), logger: Logger{ - Std: logger.Std.With(). - Int64(logs.FieldChain, chain.ChainId). - Str(logs.FieldModule, "signer"). - Logger(), - Compliance: logger.Compliance, + Std: withLogFields(logger.Std), + Compliance: withLogFields(logger.Compliance), }, - outboundBeingReported: make(map[string]bool), } } @@ -54,34 +55,11 @@ func (s *Signer) Chain() chains.Chain { return s.chain } -// WithChain attaches a new chain to the signer. -func (s *Signer) WithChain(chain chains.Chain) *Signer { - s.chain = chain - return s -} - -// Tss returns the tss signer for the signer. +// TSS returns the tss signer for the signer. func (s *Signer) TSS() interfaces.TSSSigner { return s.tss } -// WithTSS attaches a new tss signer to the signer. -func (s *Signer) WithTSS(tss interfaces.TSSSigner) *Signer { - s.tss = tss - return s -} - -// TelemetryServer returns the telemetry server for the signer. -func (s *Signer) TelemetryServer() *metrics.TelemetryServer { - return s.ts -} - -// WithTelemetryServer attaches a new telemetry server to the signer. -func (s *Signer) WithTelemetryServer(ts *metrics.TelemetryServer) *Signer { - s.ts = ts - return s -} - // Logger returns the logger for the signer. func (s *Signer) Logger() *Logger { return &s.logger diff --git a/zetaclient/chains/base/signer_test.go b/zetaclient/chains/base/signer_test.go index 6a7489741c..54883236bb 100644 --- a/zetaclient/chains/base/signer_test.go +++ b/zetaclient/chains/base/signer_test.go @@ -7,7 +7,6 @@ import ( "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/zetaclient/chains/base" - "github.com/zeta-chain/node/zetaclient/metrics" "github.com/zeta-chain/node/zetaclient/testutils/mocks" ) @@ -19,7 +18,7 @@ func createSigner(t *testing.T) *base.Signer { logger := base.DefaultLogger() // create signer - return base.NewSigner(chain, tss, nil, logger) + return base.NewSigner(chain, tss, logger) } func TestNewSigner(t *testing.T) { @@ -27,41 +26,6 @@ func TestNewSigner(t *testing.T) { require.NotNil(t, signer) } -func TestSignerGetterAndSetter(t *testing.T) { - t.Run("should be able to update chain", func(t *testing.T) { - signer := createSigner(t) - - // update chain - newChain := chains.BscMainnet - signer = signer.WithChain(chains.BscMainnet) - require.Equal(t, newChain, signer.Chain()) - }) - t.Run("should be able to update tss", func(t *testing.T) { - signer := createSigner(t) - - // update tss - newTSS := mocks.NewTSS(t) - signer = signer.WithTSS(newTSS) - require.Equal(t, newTSS, signer.TSS()) - }) - t.Run("should be able to update telemetry server", func(t *testing.T) { - signer := createSigner(t) - - // update telemetry server - newTs := metrics.NewTelemetryServer() - signer = signer.WithTelemetryServer(newTs) - require.Equal(t, newTs, signer.TelemetryServer()) - }) - t.Run("should be able to get logger", func(t *testing.T) { - ob := createSigner(t) - logger := ob.Logger() - - // should be able to print log - logger.Std.Info().Msg("print standard log") - logger.Compliance.Info().Msg("print compliance log") - }) -} - func Test_BeingReportedFlag(t *testing.T) { signer := createSigner(t) diff --git a/zetaclient/chains/bitcoin/observer/event_test.go b/zetaclient/chains/bitcoin/observer/event_test.go index 9a73d28069..ab78269527 100644 --- a/zetaclient/chains/bitcoin/observer/event_test.go +++ b/zetaclient/chains/bitcoin/observer/event_test.go @@ -21,7 +21,6 @@ import ( "github.com/zeta-chain/node/zetaclient/config" "github.com/zeta-chain/node/zetaclient/keys" "github.com/zeta-chain/node/zetaclient/testutils" - "github.com/zeta-chain/node/zetaclient/testutils/mocks" clienttypes "github.com/zeta-chain/node/zetaclient/types" ) @@ -305,10 +304,9 @@ func Test_ValidateStandardMemo(t *testing.T) { func Test_IsEventProcessable(t *testing.T) { // can use any bitcoin chain for testing chain := chains.BitcoinMainnet - params := mocks.MockChainParams(chain.ChainId, 10) // create test observer - ob := MockBTCObserver(t, chain, params, nil) + ob := newTestSuite(t, chain) // setup compliance config cfg := config.Config{ @@ -354,12 +352,10 @@ func Test_IsEventProcessable(t *testing.T) { func Test_NewInboundVoteFromLegacyMemo(t *testing.T) { // can use any bitcoin chain for testing chain := chains.BitcoinMainnet - params := mocks.MockChainParams(chain.ChainId, 10) // create test observer - ob := MockBTCObserver(t, chain, params, nil) - zetacoreClient := mocks.NewZetacoreClient(t).WithKeys(&keys.Keys{}).WithZetaChain() - ob.WithZetacoreClient(zetacoreClient) + ob := newTestSuite(t, chain) + ob.zetacore.WithKeys(&keys.Keys{}).WithZetaChain() t.Run("should create new inbound vote msg V1", func(t *testing.T) { // create test event @@ -396,12 +392,10 @@ func Test_NewInboundVoteFromLegacyMemo(t *testing.T) { func Test_NewInboundVoteFromStdMemo(t *testing.T) { // can use any bitcoin chain for testing chain := chains.BitcoinMainnet - params := mocks.MockChainParams(chain.ChainId, 10) // create test observer - ob := MockBTCObserver(t, chain, params, nil) - zetacoreClient := mocks.NewZetacoreClient(t).WithKeys(&keys.Keys{}).WithZetaChain() - ob.WithZetacoreClient(zetacoreClient) + ob := newTestSuite(t, chain) + ob.zetacore.WithKeys(&keys.Keys{}).WithZetaChain() t.Run("should create new inbound vote msg with standard memo", func(t *testing.T) { // create revert options diff --git a/zetaclient/chains/bitcoin/observer/inbound_test.go b/zetaclient/chains/bitcoin/observer/inbound_test.go index 7ec938aab0..b1cfe8d369 100644 --- a/zetaclient/chains/bitcoin/observer/inbound_test.go +++ b/zetaclient/chains/bitcoin/observer/inbound_test.go @@ -153,12 +153,10 @@ func TestAvgFeeRateBlock828440Errors(t *testing.T) { func Test_GetInboundVoteFromBtcEvent(t *testing.T) { // can use any bitcoin chain for testing chain := chains.BitcoinMainnet - params := mocks.MockChainParams(chain.ChainId, 10) // create test observer - ob := MockBTCObserver(t, chain, params, nil) - zetacoreClient := mocks.NewZetacoreClient(t).WithKeys(&keys.Keys{}).WithZetaChain() - ob.WithZetacoreClient(zetacoreClient) + ob := newTestSuite(t, chain) + ob.zetacore.WithKeys(&keys.Keys{}).WithZetaChain() // test cases tests := []struct { diff --git a/zetaclient/chains/bitcoin/observer/observer.go b/zetaclient/chains/bitcoin/observer/observer.go index 8a3516f3d0..74bc263e3a 100644 --- a/zetaclient/chains/bitcoin/observer/observer.go +++ b/zetaclient/chains/bitcoin/observer/observer.go @@ -107,7 +107,6 @@ func NewObserver( zetacoreClient, tss, btcBlocksPerDay, - base.DefaultHeaderCacheSize, rpcAlertLatency, ts, database, @@ -157,11 +156,6 @@ func (ob *Observer) BtcClient() interfaces.BTCRPCClient { return ob.btcClient } -// WithBtcClient attaches a new btc client to the observer -func (ob *Observer) WithBtcClient(client interfaces.BTCRPCClient) { - ob.btcClient = client -} - // Start starts the Go routine processes to observe the Bitcoin chain func (ob *Observer) Start(ctx context.Context) { if ok := ob.Observer.Start(); !ok { diff --git a/zetaclient/chains/bitcoin/observer/observer_test.go b/zetaclient/chains/bitcoin/observer/observer_test.go index 7679b00bc9..18358703b5 100644 --- a/zetaclient/chains/bitcoin/observer/observer_test.go +++ b/zetaclient/chains/bitcoin/observer/observer_test.go @@ -8,8 +8,8 @@ import ( "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/wire" - lru "github.com/hashicorp/golang-lru" "github.com/pkg/errors" + "github.com/rs/zerolog" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/zeta-chain/node/zetaclient/db" @@ -63,40 +63,6 @@ func setupDBTxResults(t *testing.T) (*gorm.DB, map[string]btcjson.GetTransaction return database.Client(), submittedTx } -// MockBTCObserver creates a mock Bitcoin observer for testing -func MockBTCObserver( - t *testing.T, - chain chains.Chain, - params observertypes.ChainParams, - btcClient interfaces.BTCRPCClient, -) *observer.Observer { - // use default mock btc client if not provided - if btcClient == nil { - rpcClient := mocks.NewBTCRPCClient(t) - rpcClient.On("GetBlockCount").Return(int64(100), nil) - btcClient = rpcClient - } - - database, err := db.NewFromSqliteInMemory(true) - require.NoError(t, err) - - // create observer - ob, err := observer.NewObserver( - chain, - btcClient, - params, - nil, - nil, - 60, - database, - base.Logger{}, - nil, - ) - require.NoError(t, err) - - return ob -} - func Test_NewObserver(t *testing.T) { // use Bitcoin mainnet chain for testing chain := chains.BitcoinMainnet @@ -199,22 +165,15 @@ func Test_NewObserver(t *testing.T) { func Test_BlockCache(t *testing.T) { t.Run("should add and get block from cache", func(t *testing.T) { // create observer - ob := &observer.Observer{} - blockCache, err := lru.New(100) - require.NoError(t, err) - ob.WithBlockCache(blockCache) - - // create mock btc client - btcClient := mocks.NewBTCRPCClient(t) - ob.WithBtcClient(btcClient) + ob := newTestSuite(t, chains.BitcoinMainnet) // feed block hash, header and block to btc client hash := sample.BtcHash() header := &wire.BlockHeader{Version: 1} block := &btcjson.GetBlockVerboseTxResult{Version: 1} - btcClient.On("GetBlockHash", mock.Anything).Return(&hash, nil) - btcClient.On("GetBlockHeader", &hash).Return(header, nil) - btcClient.On("GetBlockVerboseTx", &hash).Return(block, nil) + ob.client.On("GetBlockHash", mock.Anything).Return(&hash, nil) + ob.client.On("GetBlockHeader", &hash).Return(header, nil) + ob.client.On("GetBlockVerboseTx", &hash).Return(block, nil) // get block and header from observer, fallback to btc client result, err := ob.GetBlockByNumberCached(100) @@ -230,14 +189,11 @@ func Test_BlockCache(t *testing.T) { }) t.Run("should fail if stored type is not BlockNHeader", func(t *testing.T) { // create observer - ob := &observer.Observer{} - blockCache, err := lru.New(100) - require.NoError(t, err) - ob.WithBlockCache(blockCache) + ob := newTestSuite(t, chains.BitcoinMainnet) // add a string to cache blockNumber := int64(100) - blockCache.Add(blockNumber, "a string value") + ob.BlockCache().Add(blockNumber, "a string value") // get result from cache result, err := ob.GetBlockByNumberCached(blockNumber) @@ -249,15 +205,10 @@ func Test_BlockCache(t *testing.T) { func Test_LoadLastBlockScanned(t *testing.T) { // use Bitcoin mainnet chain for testing chain := chains.BitcoinMainnet - params := mocks.MockChainParams(chain.ChainId, 10) - - // create mock btc client with block height 200 - btcClient := mocks.NewBTCRPCClient(t) - btcClient.On("GetBlockCount").Return(int64(200), nil) t.Run("should load last block scanned", func(t *testing.T) { // create observer and write 199 as last block scanned - ob := MockBTCObserver(t, chain, params, btcClient) + ob := newTestSuite(t, chain) ob.WriteLastBlockScannedToDB(199) // load last block scanned @@ -267,7 +218,7 @@ func Test_LoadLastBlockScanned(t *testing.T) { }) t.Run("should fail on invalid env var", func(t *testing.T) { // create observer - ob := MockBTCObserver(t, chain, params, btcClient) + ob := newTestSuite(t, chain) // set invalid environment variable envvar := base.EnvVarLatestBlockByChain(chain) @@ -280,15 +231,14 @@ 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 := MockBTCObserver(t, chain, params, btcClient) + obOther := newTestSuite(t, chain) // reset last block scanned to 0 so that it will be loaded from RPC obOther.WithLastBlockScanned(0) // attach a mock btc client that returns rpc error - errClient := mocks.NewBTCRPCClient(t) - errClient.On("GetBlockCount").Return(int64(0), errors.New("rpc error")) - obOther.WithBtcClient(errClient) + obOther.client.ExpectedCalls = nil + obOther.client.On("GetBlockCount").Return(int64(0), errors.New("rpc error")) // load last block scanned err := obOther.LoadLastBlockScanned() @@ -296,8 +246,7 @@ func Test_LoadLastBlockScanned(t *testing.T) { }) t.Run("should use hardcode block 100 for regtest", func(t *testing.T) { // use regtest chain - regtest := chains.BitcoinRegtest - obRegnet := MockBTCObserver(t, regtest, params, btcClient) + obRegnet := newTestSuite(t, chains.BitcoinRegtest) // load last block scanned err := obRegnet.LoadLastBlockScanned() @@ -308,8 +257,7 @@ func Test_LoadLastBlockScanned(t *testing.T) { func TestConfirmationThreshold(t *testing.T) { chain := chains.BitcoinMainnet - params := mocks.MockChainParams(chain.ChainId, 10) - ob := MockBTCObserver(t, chain, params, nil) + ob := newTestSuite(t, chain) t.Run("should return confirmations in chain param", func(t *testing.T) { ob.SetChainParams(observertypes.ChainParams{ConfirmationCount: 3}) @@ -348,3 +296,47 @@ func TestSubmittedTx(t *testing.T) { require.Equal(t, want, have) } } + +type testSuite struct { + *observer.Observer + + client *mocks.BTCRPCClient + zetacore *mocks.ZetacoreClient + db *db.DB +} + +func newTestSuite(t *testing.T, chain chains.Chain) *testSuite { + require.True(t, chain.IsBitcoinChain()) + + chainParams := mocks.MockChainParams(chain.ChainId, 10) + + client := mocks.NewBTCRPCClient(t) + client.On("GetBlockCount").Return(int64(100), nil).Maybe() + + zetacore := mocks.NewZetacoreClient(t) + + database, err := db.NewFromSqliteInMemory(true) + require.NoError(t, err) + + log := zerolog.New(zerolog.NewTestWriter(t)) + + ob, err := observer.NewObserver( + chain, + client, + chainParams, + zetacore, + nil, + 60, + database, + base.Logger{Std: log, Compliance: log}, + nil, + ) + require.NoError(t, err) + + return &testSuite{ + Observer: ob, + client: client, + zetacore: zetacore, + db: database, + } +} diff --git a/zetaclient/chains/bitcoin/observer/outbound_test.go b/zetaclient/chains/bitcoin/observer/outbound_test.go index fd477e64bd..98b34ea435 100644 --- a/zetaclient/chains/bitcoin/observer/outbound_test.go +++ b/zetaclient/chains/bitcoin/observer/outbound_test.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/rs/zerolog" "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/zetaclient/chains/interfaces" "github.com/zeta-chain/node/zetaclient/db" "github.com/zeta-chain/node/pkg/chains" @@ -22,11 +23,14 @@ import ( var TestDataDir = "../../../" // MockBTCObserverMainnet creates a mock Bitcoin mainnet observer for testing -func MockBTCObserverMainnet(t *testing.T) *Observer { +func MockBTCObserverMainnet(t *testing.T, tss interfaces.TSSSigner) *Observer { // setup mock arguments chain := chains.BitcoinMainnet params := mocks.MockChainParams(chain.ChainId, 10) - tss := mocks.NewTSS(t).FakePubKey(testutils.TSSPubKeyMainnet) + + if tss == nil { + tss = mocks.NewTSS(t).FakePubKey(testutils.TSSPubKeyMainnet) + } // create mock rpc client btcClient := mocks.NewBTCRPCClient(t) @@ -53,8 +57,7 @@ func createObserverWithPrivateKey(t *testing.T) *Observer { tss := mocks.NewTSSFromPrivateKey(t, privateKey) // create Bitcoin observer with mock tss - ob := MockBTCObserverMainnet(t) - ob.WithTSS(tss) + ob := MockBTCObserverMainnet(t, tss) return ob } @@ -107,7 +110,7 @@ func TestCheckTSSVout(t *testing.T) { nonce := uint64(148) // create mainnet mock client - ob := MockBTCObserverMainnet(t) + ob := MockBTCObserverMainnet(t, nil) t.Run("valid TSS vout should pass", func(t *testing.T) { rawResult, cctx := testutils.LoadBTCTxRawResultNCctx(t, TestDataDir, chainID, nonce) @@ -189,7 +192,7 @@ func TestCheckTSSVoutCancelled(t *testing.T) { nonce := uint64(148) // create mainnet mock client - ob := MockBTCObserverMainnet(t) + ob := MockBTCObserverMainnet(t, nil) t.Run("valid TSS vout should pass", func(t *testing.T) { // remove change vout to simulate cancelled tx diff --git a/zetaclient/chains/bitcoin/signer/signer.go b/zetaclient/chains/bitcoin/signer/signer.go index 1321b0c14f..c142756f2d 100644 --- a/zetaclient/chains/bitcoin/signer/signer.go +++ b/zetaclient/chains/bitcoin/signer/signer.go @@ -31,7 +31,6 @@ import ( "github.com/zeta-chain/node/zetaclient/compliance" "github.com/zeta-chain/node/zetaclient/config" "github.com/zeta-chain/node/zetaclient/logs" - "github.com/zeta-chain/node/zetaclient/metrics" "github.com/zeta-chain/node/zetaclient/outboundprocessor" ) @@ -63,12 +62,11 @@ type Signer struct { func NewSigner( chain chains.Chain, tss interfaces.TSSSigner, - ts *metrics.TelemetryServer, logger base.Logger, cfg config.BTCConfig, ) (*Signer, error) { // create base signer - baseSigner := base.NewSigner(chain, tss, ts, logger) + baseSigner := base.NewSigner(chain, tss, logger) // create the bitcoin rpc client using the provided config connCfg := &rpcclient.ConnConfig{ diff --git a/zetaclient/chains/bitcoin/signer/signer_test.go b/zetaclient/chains/bitcoin/signer/signer_test.go index 131fbe963f..f06ad4a9c2 100644 --- a/zetaclient/chains/bitcoin/signer/signer_test.go +++ b/zetaclient/chains/bitcoin/signer/signer_test.go @@ -50,7 +50,6 @@ func (s *BTCSignerSuite) SetUpTest(c *C) { s.btcSigner, err = NewSigner( chains.Chain{}, tss, - nil, base.DefaultLogger(), config.BTCConfig{}, ) @@ -231,7 +230,6 @@ func TestAddWithdrawTxOutputs(t *testing.T) { signer, err := NewSigner( chains.BitcoinMainnet, mocks.NewTSS(t).FakePubKey(testutils.TSSPubKeyMainnet), - nil, base.DefaultLogger(), config.BTCConfig{}, ) @@ -392,7 +390,6 @@ func TestNewBTCSigner(t *testing.T) { btcSigner, err := NewSigner( chains.Chain{}, tss, - nil, base.DefaultLogger(), config.BTCConfig{}) require.NoError(t, err) diff --git a/zetaclient/chains/evm/observer/inbound_test.go b/zetaclient/chains/evm/observer/inbound_test.go index 1542e5fea1..e3b0a6e82c 100644 --- a/zetaclient/chains/evm/observer/inbound_test.go +++ b/zetaclient/chains/evm/observer/inbound_test.go @@ -1,7 +1,6 @@ package observer_test import ( - "context" "encoding/hex" "errors" "testing" @@ -9,17 +8,12 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/onrik/ethrpc" - "github.com/rs/zerolog" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - zctx "github.com/zeta-chain/node/zetaclient/context" - "github.com/zeta-chain/node/zetaclient/keys" - "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/pkg/coin" "github.com/zeta-chain/node/pkg/constant" "github.com/zeta-chain/node/zetaclient/chains/evm" - "github.com/zeta-chain/node/zetaclient/chains/interfaces" "github.com/zeta-chain/node/zetaclient/config" "github.com/zeta-chain/node/zetaclient/testutils" "github.com/zeta-chain/node/zetaclient/testutils/mocks" @@ -30,14 +24,12 @@ func Test_CheckAndVoteInboundTokenZeta(t *testing.T) { // load archived ZetaSent inbound, receipt and cctx // https://etherscan.io/tx/0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76 chain := chains.Ethereum - confirmation := uint64(10) chainID := chain.ChainId - chainParam := mocks.MockChainParams(chain.ChainId, confirmation) inboundHash := "0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76" - ctx, _ := makeAppContext(t) - t.Run("should pass for archived inbound, receipt and cctx", func(t *testing.T) { + ob := newTestSuite(t) + tx, receipt, cctx := testutils.LoadEVMInboundNReceiptNCctx( t, TestDataDir, @@ -46,16 +38,16 @@ func Test_CheckAndVoteInboundTokenZeta(t *testing.T) { coin.CoinType_Zeta, ) require.NoError(t, evm.ValidateEvmTransaction(tx)) - lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob, appContext := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) - voteCtx := zctx.WithAppContext(context.Background(), appContext) + ob.WithLastBlock(receipt.BlockNumber.Uint64() + ob.chainParams.ConfirmationCount) - ballot, err := ob.CheckAndVoteInboundTokenZeta(voteCtx, tx, receipt, false) + ballot, err := ob.CheckAndVoteInboundTokenZeta(ob.ctx, tx, receipt, false) require.NoError(t, err) require.Equal(t, cctx.InboundParams.BallotIndex, ballot) }) t.Run("should fail on unconfirmed inbound", func(t *testing.T) { + ob := newTestSuite(t) + tx, receipt, _ := testutils.LoadEVMInboundNReceiptNCctx( t, TestDataDir, @@ -64,13 +56,15 @@ func Test_CheckAndVoteInboundTokenZeta(t *testing.T) { coin.CoinType_Zeta, ) require.NoError(t, evm.ValidateEvmTransaction(tx)) - lastBlock := receipt.BlockNumber.Uint64() + confirmation - 1 - ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) - _, err := ob.CheckAndVoteInboundTokenZeta(ctx, tx, receipt, false) + ob.WithLastBlock(receipt.BlockNumber.Uint64() + ob.chainParams.ConfirmationCount - 1) + + _, err := ob.CheckAndVoteInboundTokenZeta(ob.ctx, tx, receipt, false) require.ErrorContains(t, err, "not been confirmed") }) t.Run("should not act if no ZetaSent event", func(t *testing.T) { + ob := newTestSuite(t) + tx, receipt, _ := testutils.LoadEVMInboundNReceiptNCctx( t, TestDataDir, @@ -80,14 +74,18 @@ func Test_CheckAndVoteInboundTokenZeta(t *testing.T) { ) receipt.Logs = receipt.Logs[:2] // remove ZetaSent event 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, true) + ob.WithLastBlock(receipt.BlockNumber.Uint64() + ob.chainParams.ConfirmationCount) + + ballot, err := ob.CheckAndVoteInboundTokenZeta(ob.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) { + // Given observer with another chain to trigger logic for + // different evm address (based on mocked chain params) + ob := newTestSuite(t, func(cfg *testSuiteConfig) { cfg.chain = &chains.BscMainnet }) + // Given tx from ETH tx, receipt, _ := testutils.LoadEVMInboundNReceiptNCctx(t, TestDataDir, @@ -96,25 +94,11 @@ func Test_CheckAndVoteInboundTokenZeta(t *testing.T) { coin.CoinType_Zeta, ) require.NoError(t, evm.ValidateEvmTransaction(tx)) - lastBlock := receipt.BlockNumber.Uint64() + confirmation - // Given BSC observer - chain := chains.BscMainnet - params := mocks.MockChainParams(chain.ChainId, confirmation) - - ob, _ := MockEVMObserver( - t, - chain, - nil, - nil, - nil, - nil, - lastBlock, - params, - ) + ob.WithLastBlock(receipt.BlockNumber.Uint64() + ob.chainParams.ConfirmationCount) // ACT - _, err := ob.CheckAndVoteInboundTokenZeta(ctx, tx, receipt, true) + _, err := ob.CheckAndVoteInboundTokenZeta(ob.ctx, tx, receipt, true) // ASSERT require.ErrorContains(t, err, "emitter address mismatch") @@ -125,14 +109,12 @@ func Test_CheckAndVoteInboundTokenERC20(t *testing.T) { // load archived ERC20 inbound, receipt and cctx // https://etherscan.io/tx/0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da chain := chains.Ethereum - confirmation := uint64(10) chainID := chain.ChainId - chainParam := mocks.MockChainParams(chain.ChainId, confirmation) inboundHash := "0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da" - ctx := context.Background() - t.Run("should pass for archived inbound, receipt and cctx", func(t *testing.T) { + ob := newTestSuite(t) + tx, receipt, cctx := testutils.LoadEVMInboundNReceiptNCctx( t, TestDataDir, @@ -141,14 +123,16 @@ func Test_CheckAndVoteInboundTokenERC20(t *testing.T) { coin.CoinType_ERC20, ) require.NoError(t, evm.ValidateEvmTransaction(tx)) - lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) - ballot, err := ob.CheckAndVoteInboundTokenERC20(ctx, tx, receipt, false) + ob.WithLastBlock(receipt.BlockNumber.Uint64() + ob.chainParams.ConfirmationCount) + + ballot, err := ob.CheckAndVoteInboundTokenERC20(ob.ctx, tx, receipt, false) require.NoError(t, err) require.Equal(t, cctx.InboundParams.BallotIndex, ballot) }) t.Run("should fail on unconfirmed inbound", func(t *testing.T) { + ob := newTestSuite(t) + tx, receipt, _ := testutils.LoadEVMInboundNReceiptNCctx( t, TestDataDir, @@ -157,13 +141,15 @@ func Test_CheckAndVoteInboundTokenERC20(t *testing.T) { coin.CoinType_ERC20, ) require.NoError(t, evm.ValidateEvmTransaction(tx)) - lastBlock := receipt.BlockNumber.Uint64() + confirmation - 1 - ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) - _, err := ob.CheckAndVoteInboundTokenERC20(ctx, tx, receipt, false) + ob.WithLastBlock(receipt.BlockNumber.Uint64() + ob.chainParams.ConfirmationCount - 1) + + _, err := ob.CheckAndVoteInboundTokenERC20(ob.ctx, tx, receipt, false) require.ErrorContains(t, err, "not been confirmed") }) t.Run("should not act if no Deposit event", func(t *testing.T) { + ob := newTestSuite(t) + tx, receipt, _ := testutils.LoadEVMInboundNReceiptNCctx( t, TestDataDir, @@ -173,15 +159,18 @@ func Test_CheckAndVoteInboundTokenERC20(t *testing.T) { ) receipt.Logs = receipt.Logs[:1] // remove Deposit event require.NoError(t, evm.ValidateEvmTransaction(tx)) - lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) - ballot, err := ob.CheckAndVoteInboundTokenERC20(ctx, tx, receipt, true) + ob.WithLastBlock(receipt.BlockNumber.Uint64() + ob.chainParams.ConfirmationCount) + + ballot, err := ob.CheckAndVoteInboundTokenERC20(ob.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 observer with different chain (thus chain params) to have different evm addresses + ob := newTestSuite(t, func(cfg *testSuiteConfig) { cfg.chain = &chains.BscMainnet }) + // Given tx from ETH tx, receipt, _ := testutils.LoadEVMInboundNReceiptNCctx( t, @@ -191,25 +180,11 @@ func Test_CheckAndVoteInboundTokenERC20(t *testing.T) { coin.CoinType_ERC20, ) require.NoError(t, evm.ValidateEvmTransaction(tx)) - lastBlock := receipt.BlockNumber.Uint64() + confirmation - // Given BSC observer - chain := chains.BscMainnet - params := mocks.MockChainParams(chain.ChainId, confirmation) - - ob, _ := MockEVMObserver( - t, - chain, - nil, - nil, - nil, - nil, - lastBlock, - params, - ) + ob.WithLastBlock(receipt.BlockNumber.Uint64() + ob.chainParams.ConfirmationCount) // ACT - _, err := ob.CheckAndVoteInboundTokenERC20(ctx, tx, receipt, true) + _, err := ob.CheckAndVoteInboundTokenERC20(ob.ctx, tx, receipt, true) // ASSERT require.ErrorContains(t, err, "emitter address mismatch") @@ -222,11 +197,8 @@ func Test_CheckAndVoteInboundTokenGas(t *testing.T) { chain := chains.Ethereum confirmation := uint64(10) chainID := chain.ChainId - chainParam := mocks.MockChainParams(chain.ChainId, confirmation) inboundHash := "0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532" - ctx := context.Background() - t.Run("should pass for archived inbound, receipt and cctx", func(t *testing.T) { tx, receipt, cctx := testutils.LoadEVMInboundNReceiptNCctx( t, @@ -238,8 +210,10 @@ 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) - ballot, err := ob.CheckAndVoteInboundTokenGas(ctx, tx, receipt, false) + ob := newTestSuite(t) + ob.WithLastBlock(lastBlock) + + ballot, err := ob.CheckAndVoteInboundTokenGas(ob.ctx, tx, receipt, false) require.NoError(t, err) require.Equal(t, cctx.InboundParams.BallotIndex, ballot) }) @@ -248,8 +222,10 @@ 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) - _, err := ob.CheckAndVoteInboundTokenGas(ctx, tx, receipt, false) + ob := newTestSuite(t) + ob.WithLastBlock(lastBlock) + + _, err := ob.CheckAndVoteInboundTokenGas(ob.ctx, tx, receipt, false) require.ErrorContains(t, err, "not been confirmed") }) t.Run("should not act if receiver is not TSS", func(t *testing.T) { @@ -258,8 +234,10 @@ 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) - ballot, err := ob.CheckAndVoteInboundTokenGas(ctx, tx, receipt, false) + ob := newTestSuite(t) + ob.WithLastBlock(lastBlock) + + ballot, err := ob.CheckAndVoteInboundTokenGas(ob.ctx, tx, receipt, false) require.ErrorContains(t, err, "not TSS address") require.Equal(t, "", ballot) }) @@ -269,8 +247,10 @@ 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) - ballot, err := ob.CheckAndVoteInboundTokenGas(ctx, tx, receipt, false) + ob := newTestSuite(t) + ob.WithLastBlock(lastBlock) + + ballot, err := ob.CheckAndVoteInboundTokenGas(ob.ctx, tx, receipt, false) require.ErrorContains(t, err, "not a successful tx") require.Equal(t, "", ballot) }) @@ -280,8 +260,10 @@ 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) - ballot, err := ob.CheckAndVoteInboundTokenGas(ctx, tx, receipt, false) + ob := newTestSuite(t) + ob.WithLastBlock(lastBlock) + + ballot, err := ob.CheckAndVoteInboundTokenGas(ob.ctx, tx, receipt, false) require.NoError(t, err) require.Equal(t, "", ballot) }) @@ -291,13 +273,13 @@ func Test_BuildInboundVoteMsgForZetaSentEvent(t *testing.T) { // load archived ZetaSent receipt // https://etherscan.io/tx/0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76 chainID := int64(1) - chain := chains.Ethereum inboundHash := "0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76" receipt := testutils.LoadEVMInboundReceipt(t, TestDataDir, chainID, inboundHash, coin.CoinType_Zeta) cctx := testutils.LoadCctxByInbound(t, chainID, coin.CoinType_Zeta, inboundHash) // parse ZetaSent event - ob, app := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, mocks.MockChainParams(1, 1)) + ob := newTestSuite(t) + connector := mocks.MockConnectorNonEth(t, chainID) event := testutils.ParseReceiptZetaSent(receipt, connector) @@ -307,7 +289,7 @@ func Test_BuildInboundVoteMsgForZetaSentEvent(t *testing.T) { } t.Run("should return vote msg for archived ZetaSent event", func(t *testing.T) { - msg := ob.BuildInboundVoteMsgForZetaSentEvent(app, event) + msg := ob.BuildInboundVoteMsgForZetaSentEvent(ob.appContext, event) require.NotNil(t, msg) require.Equal(t, cctx.InboundParams.BallotIndex, msg.Digest()) }) @@ -315,21 +297,21 @@ func Test_BuildInboundVoteMsgForZetaSentEvent(t *testing.T) { sender := event.ZetaTxSenderAddress.Hex() cfg.ComplianceConfig.RestrictedAddresses = []string{sender} config.LoadComplianceConfig(cfg) - msg := ob.BuildInboundVoteMsgForZetaSentEvent(app, event) + msg := ob.BuildInboundVoteMsgForZetaSentEvent(ob.appContext, event) require.Nil(t, msg) }) t.Run("should return nil msg if receiver is restricted", func(t *testing.T) { receiver := clienttypes.BytesToEthHex(event.DestinationAddress) cfg.ComplianceConfig.RestrictedAddresses = []string{receiver} config.LoadComplianceConfig(cfg) - msg := ob.BuildInboundVoteMsgForZetaSentEvent(app, event) + msg := ob.BuildInboundVoteMsgForZetaSentEvent(ob.appContext, event) require.Nil(t, msg) }) t.Run("should return nil msg if txOrigin is restricted", func(t *testing.T) { txOrigin := event.SourceTxOriginAddress.Hex() cfg.ComplianceConfig.RestrictedAddresses = []string{txOrigin} config.LoadComplianceConfig(cfg) - msg := ob.BuildInboundVoteMsgForZetaSentEvent(app, event) + msg := ob.BuildInboundVoteMsgForZetaSentEvent(ob.appContext, event) require.Nil(t, msg) }) } @@ -344,7 +326,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 := newTestSuite(t) custody := mocks.MockERC20Custody(t, chainID) event := testutils.ParseReceiptERC20Deposited(receipt, custody) sender := ethcommon.HexToAddress(tx.From) @@ -402,7 +384,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 := newTestSuite(t) cfg := config.Config{ ComplianceConfig: config.ComplianceConfig{}, } @@ -451,8 +433,6 @@ func Test_ObserveTSSReceiveInBlock(t *testing.T) { // https://etherscan.io/tx/0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532 chain := chains.Ethereum chainID := chain.ChainId - confirmation := uint64(1) - chainParam := mocks.MockChainParams(chain.ChainId, confirmation) inboundHash := "0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532" // load archived tx and receipt @@ -464,77 +444,68 @@ func Test_ObserveTSSReceiveInBlock(t *testing.T) { blockNumber := receipt.BlockNumber.Uint64() block := testutils.LoadEVMBlock(t, TestDataDir, chainID, blockNumber, true) - // create mock zetacore client - tss := mocks.NewTSS(t).FakePubKey(testutils.TSSPubKeyMainnet) - lastBlock := receipt.BlockNumber.Uint64() + confirmation - zetacoreClient := mocks.NewZetacoreClient(t). - WithKeys(&keys.Keys{}). - WithZetaChain(). - WithPostVoteInbound("", ""). - WithPostVoteInbound("", "") - // test cases tests := []struct { - name string - evmClient interfaces.EVMRPCClient - jsonClient interfaces.EVMJSONRPCClient - errMsg string + name string + mockEVMClient func(m *mocks.EVMRPCClient) + mockJSONClient func(m *mocks.MockJSONRPCClient) + errMsg string }{ { name: "should observe TSS receive in block", - evmClient: func() interfaces.EVMRPCClient { + mockEVMClient: func(m *mocks.EVMRPCClient) { // feed block number and receipt to mock client - evmClient := mocks.NewEVMRPCClient(t) - evmClient.On("BlockNumber", mock.Anything).Return(uint64(1000), nil) - evmClient.On("TransactionReceipt", mock.Anything, mock.Anything).Return(receipt, nil) - return evmClient - }(), - jsonClient: mocks.NewMockJSONRPCClient().WithBlock(block), - errMsg: "", + m.On("BlockNumber", mock.Anything).Return(uint64(1000), nil) + m.On("TransactionReceipt", mock.Anything, mock.Anything).Return(receipt, nil) + }, + mockJSONClient: func(m *mocks.MockJSONRPCClient) { + m.WithBlock(block) + }, + errMsg: "", }, { name: "should not observe on error getting block", - evmClient: func() interfaces.EVMRPCClient { + mockEVMClient: func(m *mocks.EVMRPCClient) { // feed block number to allow construction of observer - evmClient := mocks.NewEVMRPCClient(t) - evmClient.On("BlockNumber", mock.Anything).Return(uint64(1000), nil) - return evmClient - }(), - jsonClient: mocks.NewMockJSONRPCClient(), // no block - errMsg: "error getting block", + m.On("BlockNumber", mock.Anything).Return(uint64(1000), nil) + }, + mockJSONClient: nil, // no block + errMsg: "error getting block", }, { name: "should not observe on error getting receipt", - evmClient: func() interfaces.EVMRPCClient { + mockEVMClient: func(m *mocks.EVMRPCClient) { // feed block number but RPC error on getting receipt - evmClient := mocks.NewEVMRPCClient(t) - evmClient.On("BlockNumber", mock.Anything).Return(uint64(1000), nil) - evmClient.On("TransactionReceipt", mock.Anything, mock.Anything).Return(nil, errors.New("RPC error")) - return evmClient - }(), - jsonClient: mocks.NewMockJSONRPCClient().WithBlock(block), - errMsg: "error getting receipt", + m.On("BlockNumber", mock.Anything).Return(uint64(1000), nil) + m.On("TransactionReceipt", mock.Anything, mock.Anything).Return(nil, errors.New("RPC error")) + }, + mockJSONClient: func(m *mocks.MockJSONRPCClient) { + m.WithBlock(block) + }, + errMsg: "error getting receipt", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ob, _ := MockEVMObserver(t, chain, tt.evmClient, tt.jsonClient, zetacoreClient, tss, lastBlock, chainParam) - err := ob.ObserveTSSReceiveInBlock(context.Background(), blockNumber) + ob := newTestSuite(t) + ob.WithLastBlock(receipt.BlockNumber.Uint64() + ob.chainParams.ConfirmationCount) + + if tt.mockEVMClient != nil { + tt.mockEVMClient(ob.evmClient) + } + + if tt.mockJSONClient != nil { + tt.mockJSONClient(ob.rpcClient) + } + + err := ob.ObserveTSSReceiveInBlock(ob.ctx, blockNumber) if tt.errMsg != "" { require.ErrorContains(t, err, tt.errMsg) - } else { - require.NoError(t, err) + return } + + require.NoError(t, err) }) } } - -func makeAppContext(t *testing.T) (context.Context, *zctx.AppContext) { - var ( - app = zctx.New(config.New(false), nil, zerolog.New(zerolog.NewTestWriter(t))) - ctx = context.Background() - ) - - return zctx.WithAppContext(ctx, app), app -} diff --git a/zetaclient/chains/evm/observer/observer.go b/zetaclient/chains/evm/observer/observer.go index 06f2df0506..79ffd40545 100644 --- a/zetaclient/chains/evm/observer/observer.go +++ b/zetaclient/chains/evm/observer/observer.go @@ -5,7 +5,6 @@ import ( "context" "fmt" "math" - "math/big" "strings" ethcommon "github.com/ethereum/go-ethereum/common" @@ -82,7 +81,6 @@ func NewObserver( zetacoreClient, tss, base.DefaultBlockCacheSize, - base.DefaultHeaderCacheSize, rpcAlertLatency, ts, database, @@ -110,16 +108,6 @@ func NewObserver( return ob, nil } -// WithEvmClient attaches a new evm client to the observer -func (ob *Observer) WithEvmClient(client interfaces.EVMRPCClient) { - ob.evmClient = client -} - -// WithEvmJSONRPC attaches a new evm json rpc client to the observer -func (ob *Observer) WithEvmJSONRPC(client interfaces.EVMJSONRPCClient) { - ob.evmJSONRPC = client -} - // GetConnectorContract returns the non-Eth connector address and binder func (ob *Observer) GetConnectorContract() (ethcommon.Address, *zetaconnector.ZetaConnectorNonEth, error) { addr := ethcommon.HexToAddress(ob.ChainParams().ConnectorContractAddress) @@ -255,20 +243,8 @@ func (ob *Observer) TransactionByHash(txHash string) (*ethrpc.Transaction, bool, return tx, tx.BlockNumber == nil, nil } -// GetBlockHeaderCached get block header by number from cache -func (ob *Observer) GetBlockHeaderCached(ctx context.Context, blockNumber uint64) (*ethtypes.Header, error) { - if result, ok := ob.HeaderCache().Get(blockNumber); ok { - if header, ok := result.(*ethtypes.Header); ok { - return header, nil - } - return nil, errors.New("cached value is not of type *ethtypes.Header") - } - header, err := ob.evmClient.HeaderByNumber(ctx, new(big.Int).SetUint64(blockNumber)) - if err != nil { - return nil, err - } - ob.HeaderCache().Add(blockNumber, header) - return header, nil +func (ob *Observer) TransactionReceipt(ctx context.Context, hash ethcommon.Hash) (*ethtypes.Receipt, error) { + return ob.evmClient.TransactionReceipt(ctx, hash) } // GetBlockByNumberCached get block by number from cache diff --git a/zetaclient/chains/evm/observer/observer_gas_test.go b/zetaclient/chains/evm/observer/observer_gas_test.go index da64d32972..d2a61f1e3e 100644 --- a/zetaclient/chains/evm/observer/observer_gas_test.go +++ b/zetaclient/chains/evm/observer/observer_gas_test.go @@ -8,41 +8,27 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "github.com/zeta-chain/node/pkg/chains" - "github.com/zeta-chain/node/zetaclient/testutils/mocks" ) func TestPostGasPrice(t *testing.T) { const ( - gwei = 10e9 - blockNumber = 1000 - anything = mock.Anything + gwei = 10e9 + anything = mock.Anything ) ctx := context.Background() t.Run("Pre EIP-1559 doesn't support priorityFee", func(t *testing.T) { // ARRANGE - // Given ETH rpc mock - ethRPC := mocks.NewEVMRPCClient(t) - ethRPC.On("BlockNumber", mock.Anything).Return(uint64(blockNumber), nil) - - // Given zetacore client mock - zetacoreClient := mocks.NewZetacoreClient(t).WithZetaChain() - // Given an observer - chain := chains.Ethereum - confirmation := uint64(10) - chainParam := mocks.MockChainParams(chain.ChainId, confirmation) - - observer, _ := MockEVMObserver(t, chain, ethRPC, nil, zetacoreClient, nil, blockNumber, chainParam) + observer := newTestSuite(t) // Given empty baseFee from RPC - ethRPC.On("HeaderByNumber", anything, anything).Return(ðtypes.Header{BaseFee: nil}, nil) + observer.evmClient.On("HeaderByNumber", anything, anything).Return(ðtypes.Header{BaseFee: nil}, nil) // Given gasPrice and priorityFee from RPC - ethRPC.On("SuggestGasPrice", anything).Return(big.NewInt(3*gwei), nil) - ethRPC.On("SuggestGasTipCap", anything).Return(big.NewInt(0), nil) + observer.evmClient.On("SuggestGasPrice", anything).Return(big.NewInt(3*gwei), nil) + observer.evmClient.On("SuggestGasTipCap", anything).Return(big.NewInt(0), nil) // Given mock collector for zetacore call // PostVoteGasPrice(ctx, chain, gasPrice, priorityFee, blockNum) @@ -52,7 +38,7 @@ func TestPostGasPrice(t *testing.T) { priorityFee = args.Get(3).(uint64) } - zetacoreClient. + observer.zetacore. On("PostVoteGasPrice", anything, anything, anything, anything, anything). Run(collector). Return("0xABC123...", nil) @@ -70,26 +56,16 @@ func TestPostGasPrice(t *testing.T) { t.Run("Post EIP-1559 supports priorityFee", func(t *testing.T) { // ARRANGE - // Given ETH rpc mock - ethRPC := mocks.NewEVMRPCClient(t) - ethRPC.On("BlockNumber", mock.Anything).Return(uint64(blockNumber), nil) - - // Given zetacore client mock - zetacoreClient := mocks.NewZetacoreClient(t).WithZetaChain() - // Given an observer - chain := chains.Ethereum - confirmation := uint64(10) - chainParam := mocks.MockChainParams(chain.ChainId, confirmation) - - observer, _ := MockEVMObserver(t, chain, ethRPC, nil, zetacoreClient, nil, blockNumber, chainParam) + observer := newTestSuite(t) // Given 1 gwei baseFee from RPC - ethRPC.On("HeaderByNumber", anything, anything).Return(ðtypes.Header{BaseFee: big.NewInt(gwei)}, nil) + observer.evmClient.On("HeaderByNumber", anything, anything). + Return(ðtypes.Header{BaseFee: big.NewInt(gwei)}, nil) // Given gasPrice and priorityFee from RPC - ethRPC.On("SuggestGasPrice", anything).Return(big.NewInt(3*gwei), nil) - ethRPC.On("SuggestGasTipCap", anything).Return(big.NewInt(2*gwei), nil) + observer.evmClient.On("SuggestGasPrice", anything).Return(big.NewInt(3*gwei), nil) + observer.evmClient.On("SuggestGasTipCap", anything).Return(big.NewInt(2*gwei), nil) // Given mock collector for zetacore call // PostVoteGasPrice(ctx, chain, gasPrice, priorityFee, blockNum) @@ -99,7 +75,7 @@ func TestPostGasPrice(t *testing.T) { priorityFee = args.Get(3).(uint64) } - zetacoreClient. + observer.zetacore. On("PostVoteGasPrice", anything, anything, anything, anything, anything). Run(collector). Return("0xABC123...", nil) diff --git a/zetaclient/chains/evm/observer/observer_test.go b/zetaclient/chains/evm/observer/observer_test.go index e8fc39b9fc..10fbaced9f 100644 --- a/zetaclient/chains/evm/observer/observer_test.go +++ b/zetaclient/chains/evm/observer/observer_test.go @@ -3,13 +3,10 @@ package observer_test import ( "context" "fmt" - "math/big" "os" "testing" "cosmossdk.io/math" - ethtypes "github.com/ethereum/go-ethereum/core/types" - lru "github.com/hashicorp/golang-lru" "github.com/onrik/ethrpc" "github.com/rs/zerolog" "github.com/stretchr/testify/mock" @@ -80,72 +77,6 @@ func getAppContext( return appContext, cfg.EVMChainConfigs[evmChain.ChainId] } -// MockEVMObserver creates a mock ChainObserver with custom chain, TSS, params etc -func MockEVMObserver( - t *testing.T, - chain chains.Chain, - evmClient interfaces.EVMRPCClient, - evmJSONRPC interfaces.EVMJSONRPCClient, - zetacoreClient interfaces.ZetacoreClient, - tss interfaces.TSSSigner, - lastBlock uint64, - params observertypes.ChainParams, -) (*observer.Observer, *zctx.AppContext) { - ctx := context.Background() - - // use default mock evm client if not provided - if evmClient == nil { - evmClientDefault := mocks.NewEVMRPCClient(t) - evmClientDefault.On("BlockNumber", mock.Anything).Return(uint64(1000), nil) - evmClient = evmClientDefault - } - - // use default mock evm client if not provided - if evmJSONRPC == nil { - evmJSONRPC = mocks.NewMockJSONRPCClient() - } - - // use default mock zetacore client if not provided - if zetacoreClient == nil { - zetacoreClient = mocks.NewZetacoreClient(t). - WithKeys(&keys.Keys{}). - WithZetaChain(). - WithPostVoteInbound("", ""). - WithPostVoteOutbound("", "") - } - // use default mock tss if not provided - if tss == nil { - tss = mocks.NewTSS(t).FakePubKey(testutils.TSSPubKeyMainnet) - } - // create AppContext - appContext, _ := 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, - chain, - evmClient, - evmJSONRPC, - params, - zetacoreClient, - tss, - 60, - database, - logger, - nil, - ) - require.NoError(t, err) - ob.WithLastBlock(lastBlock) - - return ob, appContext -} - func Test_NewObserver(t *testing.T) { ctx := context.Background() @@ -274,14 +205,9 @@ func Test_NewObserver(t *testing.T) { func Test_LoadLastBlockScanned(t *testing.T) { ctx := context.Background() - // use Ethereum chain for testing - chain := chains.Ethereum - params := mocks.MockChainParams(chain.ChainId, 10) - // create observer using mock evm client - evmClient := mocks.NewEVMRPCClient(t) - evmClient.On("BlockNumber", mock.Anything).Return(uint64(100), nil) - ob, _ := MockEVMObserver(t, chain, evmClient, nil, nil, nil, 1, params) + ob := newTestSuite(t) + ob.evmClient.On("BlockNumber", mock.Anything).Return(uint64(100), nil) t.Run("should load last block scanned", func(t *testing.T) { // create db and write 123 as last block scanned @@ -294,7 +220,7 @@ func Test_LoadLastBlockScanned(t *testing.T) { }) t.Run("should fail on invalid env var", func(t *testing.T) { // set invalid environment variable - envvar := base.EnvVarLatestBlockByChain(chain) + envvar := base.EnvVarLatestBlockByChain(ob.Chain()) os.Setenv(envvar, "invalid") defer os.Unsetenv(envvar) @@ -304,17 +230,14 @@ 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 := newTestSuite(t) // reset last block scanned to 0 so that it will be loaded from RPC obOther.WithLastBlockScanned(0) - // create mock evm client with RPC error - evmClient := mocks.NewEVMRPCClient(t) - evmClient.On("BlockNumber", mock.Anything).Return(uint64(0), fmt.Errorf("error RPC")) - // attach mock evm client to observer - obOther.WithEvmClient(evmClient) + obOther.evmClient.On("BlockNumber", mock.Anything).Unset() + obOther.evmClient.On("BlockNumber", mock.Anything).Return(uint64(0), fmt.Errorf("error RPC")) // load last block scanned err := obOther.LoadLastBlockScanned(ctx) @@ -325,109 +248,54 @@ func Test_LoadLastBlockScanned(t *testing.T) { func Test_BlockCache(t *testing.T) { t.Run("should get block from cache", func(t *testing.T) { // create observer - ob := &observer.Observer{} - blockCache, err := lru.New(100) - require.NoError(t, err) - ob.WithBlockCache(blockCache) - - // create mock evm client - JSONRPC := mocks.NewMockJSONRPCClient() - ob.WithEvmJSONRPC(JSONRPC) + ts := newTestSuite(t) // feed block to JSON rpc client block := ðrpc.Block{Number: 100} - JSONRPC.WithBlock(block) + ts.rpcClient.WithBlock(block) // get block header from observer, fallback to JSON RPC - result, err := ob.GetBlockByNumberCached(uint64(100)) + result, err := ts.Observer.GetBlockByNumberCached(uint64(100)) require.NoError(t, err) require.EqualValues(t, block, result) // get block header from cache - result, err = ob.GetBlockByNumberCached(uint64(100)) + result, err = ts.Observer.GetBlockByNumberCached(uint64(100)) require.NoError(t, err) require.EqualValues(t, block, result) }) t.Run("should fail if stored type is not block", func(t *testing.T) { // create observer - ob := &observer.Observer{} - blockCache, err := lru.New(100) - require.NoError(t, err) - ob.WithBlockCache(blockCache) + ts := newTestSuite(t) // add a string to cache blockNumber := uint64(100) - blockCache.Add(blockNumber, "a string value") + ts.BlockCache().Add(blockNumber, "a string value") // get result header from cache - result, err := ob.GetBlockByNumberCached(blockNumber) + result, err := ts.Observer.GetBlockByNumberCached(blockNumber) require.ErrorContains(t, err, "cached value is not of type *ethrpc.Block") require.Nil(t, result) }) t.Run("should be able to remove block from cache", func(t *testing.T) { // create observer - ob := &observer.Observer{} - blockCache, err := lru.New(100) - require.NoError(t, err) - ob.WithBlockCache(blockCache) + ts := newTestSuite(t) // delete non-existing block should not panic blockNumber := uint64(123) - ob.RemoveCachedBlock(blockNumber) + ts.RemoveCachedBlock(blockNumber) // add a block block := ðrpc.Block{Number: 123} - blockCache.Add(blockNumber, block) - ob.WithBlockCache(blockCache) + ts.BlockCache().Add(blockNumber, block) // block should be in cache - result, err := ob.GetBlockByNumberCached(blockNumber) + result, err := ts.GetBlockByNumberCached(blockNumber) require.NoError(t, err) require.EqualValues(t, block, result) // delete the block should not panic - ob.RemoveCachedBlock(blockNumber) - }) -} - -func Test_HeaderCache(t *testing.T) { - ctx := context.Background() - - t.Run("should get block header from cache", func(t *testing.T) { - // create observer - ob := &observer.Observer{} - headerCache, err := lru.New(100) - require.NoError(t, err) - ob.WithHeaderCache(headerCache) - - // create mock evm client - evmClient := mocks.NewEVMRPCClient(t) - ob.WithEvmClient(evmClient) - - // feed block header to evm client - header := ðtypes.Header{Number: big.NewInt(100)} - evmClient.On("HeaderByNumber", mock.Anything, mock.Anything).Return(header, nil) - - // get block header from observer - resHeader, err := ob.GetBlockHeaderCached(ctx, uint64(100)) - require.NoError(t, err) - require.EqualValues(t, header, resHeader) - }) - t.Run("should fail if stored type is not block header", func(t *testing.T) { - // create observer - ob := &observer.Observer{} - headerCache, err := lru.New(100) - require.NoError(t, err) - ob.WithHeaderCache(headerCache) - - // add a string to cache - blockNumber := uint64(100) - headerCache.Add(blockNumber, "a string value") - - // get block header from cache - header, err := ob.GetBlockHeaderCached(ctx, blockNumber) - require.ErrorContains(t, err, "cached value is not of type *ethtypes.Header") - require.Nil(t, header) + ts.RemoveCachedBlock(blockNumber) }) } @@ -444,17 +312,14 @@ func Test_CheckTxInclusion(t *testing.T) { blockNumber := receipt.BlockNumber.Uint64() block := testutils.LoadEVMBlock(t, TestDataDir, chainID, blockNumber, true) - // create client - blockCache, err := lru.New(1000) - require.NoError(t, err) - ob := &observer.Observer{} + // create observer + ts := newTestSuite(t) // save block to cache - blockCache.Add(blockNumber, block) - ob.WithBlockCache(blockCache) + ts.BlockCache().Add(blockNumber, block) t.Run("should pass for archived outbound", func(t *testing.T) { - err := ob.CheckTxInclusion(tx, receipt) + err := ts.CheckTxInclusion(tx, receipt) require.NoError(t, err) }) t.Run("should fail on tx index out of range", func(t *testing.T) { @@ -462,22 +327,21 @@ func Test_CheckTxInclusion(t *testing.T) { copyReceipt := *receipt // #nosec G115 non negative value copyReceipt.TransactionIndex = uint(len(block.Transactions)) - err := ob.CheckTxInclusion(tx, ©Receipt) + err := ts.CheckTxInclusion(tx, ©Receipt) require.ErrorContains(t, err, "out of range") }) t.Run("should fail on tx hash mismatch", func(t *testing.T) { // change the tx at position 'receipt.TransactionIndex' to a different tx priorTx := block.Transactions[receipt.TransactionIndex-1] block.Transactions[receipt.TransactionIndex] = priorTx - blockCache.Add(blockNumber, block) - ob.WithBlockCache(blockCache) + ts.BlockCache().Add(blockNumber, block) // check inclusion should fail - err := ob.CheckTxInclusion(tx, receipt) + err := ts.CheckTxInclusion(tx, receipt) require.ErrorContains(t, err, "has different hash") // wrong block should be removed from cache - _, ok := blockCache.Get(blockNumber) + _, ok := ts.BlockCache().Get(blockNumber) require.False(t, ok) }) } @@ -511,3 +375,81 @@ func Test_VoteOutboundBallot(t *testing.T) { require.Equal(t, ballotExpected, msg.Digest()) }) } + +type testSuite struct { + *observer.Observer + ctx context.Context + appContext *zctx.AppContext + chainParams *observertypes.ChainParams + tss *mocks.TSS + zetacore *mocks.ZetacoreClient + rpcClient *mocks.MockJSONRPCClient + evmClient *mocks.EVMRPCClient +} + +type testSuiteConfig struct { + chain *chains.Chain +} + +func newTestSuite(t *testing.T, opts ...func(*testSuiteConfig)) *testSuite { + var cfg testSuiteConfig + for _, opt := range opts { + opt(&cfg) + } + + chain := chains.Ethereum + if cfg.chain != nil { + chain = *cfg.chain + } + + chainParams := mocks.MockChainParams(chain.ChainId, 10) + + appContext, _ := getAppContext(t, chain, "", &chainParams) + ctx := zctx.WithAppContext(context.Background(), appContext) + + evmClient := mocks.NewEVMRPCClient(t) + evmClient.On("BlockNumber", mock.Anything).Return(uint64(1000), nil) + + rpcClient := mocks.NewMockJSONRPCClient() + + zetacore := mocks.NewZetacoreClient(t). + WithKeys(&keys.Keys{}). + WithZetaChain(). + WithPostVoteInbound("", ""). + WithPostVoteOutbound("", "") + + tss := mocks.NewTSS(t).FakePubKey(testutils.TSSPubKeyMainnet) + + database, err := db.NewFromSqliteInMemory(true) + require.NoError(t, err) + + log := zerolog.New(zerolog.NewTestWriter(t)).With().Caller().Logger() + logger := base.Logger{Std: log, Compliance: log} + + ob, err := observer.NewObserver( + ctx, + chain, + evmClient, + rpcClient, + chainParams, + zetacore, + tss, + 60, + database, + logger, + nil, + ) + require.NoError(t, err) + ob.WithLastBlock(1) + + return &testSuite{ + Observer: ob, + ctx: ctx, + appContext: appContext, + chainParams: &chainParams, + tss: tss, + zetacore: zetacore, + rpcClient: rpcClient, + evmClient: evmClient, + } +} diff --git a/zetaclient/chains/evm/observer/outbound_test.go b/zetaclient/chains/evm/observer/outbound_test.go index 90a20c4cb7..1e22abf153 100644 --- a/zetaclient/chains/evm/observer/outbound_test.go +++ b/zetaclient/chains/evm/observer/outbound_test.go @@ -5,7 +5,6 @@ import ( "testing" ethcommon "github.com/ethereum/go-ethereum/common" - lru "github.com/hashicorp/golang-lru" "github.com/rs/zerolog" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -20,25 +19,11 @@ import ( "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.non-eth.sol" ) -// getContractsByChainID is a helper func to get contracts and addresses by chainID -func getContractsByChainID( - t *testing.T, - chainID int64, -) (*zetaconnector.ZetaConnectorNonEth, ethcommon.Address, *erc20custody.ERC20Custody, ethcommon.Address) { - connector := mocks.MockConnectorNonEth(t, chainID) - connectorAddress := testutils.ConnectorAddresses[chainID] - custody := mocks.MockERC20Custody(t, chainID) - custodyAddress := testutils.CustodyAddresses[chainID] - return connector, connectorAddress, custody, custodyAddress -} - func Test_IsOutboundProcessed(t *testing.T) { // load archived outbound receipt that contains ZetaReceived event // https://etherscan.io/tx/0x81342051b8a85072d3e3771c1a57c7bdb5318e8caf37f5a687b7a91e50a7257f - chain := chains.Ethereum chainID := chains.Ethereum.ChainId nonce := uint64(9718) - chainParam := mocks.MockChainParams(chain.ChainId, 1) outboundHash := "0x81342051b8a85072d3e3771c1a57c7bdb5318e8caf37f5a687b7a91e50a7257f" cctx := testutils.LoadCctxByNonce(t, chainID, nonce) receipt := testutils.LoadEVMOutboundReceipt( @@ -61,7 +46,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 := newTestSuite(t) ob.SetTxNReceipt(nonce, receipt, outbound) // post outbound vote @@ -77,7 +62,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 := newTestSuite(t) ob.SetTxNReceipt(nonce, receipt, outbound) // modify compliance config to restrict sender address @@ -94,14 +79,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 := newTestSuite(t) 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 := newTestSuite(t) ob.SetTxNReceipt(nonce, receipt, outbound) // set connector contract address to an arbitrary address to make event parsing fail @@ -124,10 +109,8 @@ func Test_IsOutboundProcessed_ContractError(t *testing.T) { // load archived outbound receipt that contains ZetaReceived event // https://etherscan.io/tx/0x81342051b8a85072d3e3771c1a57c7bdb5318e8caf37f5a687b7a91e50a7257f - chain := chains.Ethereum chainID := chains.Ethereum.ChainId nonce := uint64(9718) - chainParam := mocks.MockChainParams(chain.ChainId, 1) outboundHash := "0x81342051b8a85072d3e3771c1a57c7bdb5318e8caf37f5a687b7a91e50a7257f" cctx := testutils.LoadCctxByNonce(t, chainID, nonce) receipt := testutils.LoadEVMOutboundReceipt( @@ -150,7 +133,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 := newTestSuite(t) ob.SetTxNReceipt(nonce, receipt, outbound) abiConnector := zetaconnector.ZetaConnectorNonEthMetaData.ABI abiCustody := erc20custody.ERC20CustodyMetaData.ABI @@ -194,7 +177,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, mocks.MockChainParams(chain.ChainId, 100)) + ob := newTestSuite(t) ob.PostVoteOutbound( ctx, cctx.Index, @@ -420,7 +403,6 @@ func Test_FilterTSSOutbound(t *testing.T) { // https://etherscan.io/block/19363323 chain := chains.Ethereum chainID := chain.ChainId - chainParam := mocks.MockChainParams(chainID, 1) // load archived evm block // https://etherscan.io/block/19363323 @@ -435,22 +417,18 @@ func Test_FilterTSSOutbound(t *testing.T) { ctx := context.Background() t.Run("should filter TSS outbound", func(t *testing.T) { - // create mock evm client with preloaded block, tx and receipt - evmClient := mocks.NewEVMRPCClient(t) - evmClient.On("BlockNumber", mock.Anything).Return(blockNumber+1, nil) // +1 confirmations - evmClient.On("TransactionByHash", mock.Anything, outboundHash).Return(tx, false, nil) - evmClient.On("TransactionReceipt", mock.Anything, outboundHash).Return(receipt, nil) - // create evm observer for testing - tss := mocks.NewTSS(t).FakePubKey(testutils.TSSPubKeyMainnet) + ob := newTestSuite(t) - ob, _ := MockEVMObserver(t, chain, evmClient, nil, nil, tss, 1, chainParam) + confirmations := ob.chainParams.ConfirmationCount - // feed archived block to observer cache - blockCache, err := lru.New(1000) - require.NoError(t, err) - blockCache.Add(blockNumber, block) - ob.WithBlockCache(blockCache) + // create mock evm client with preloaded block, tx and receipt + ob.evmClient.On("BlockNumber", mock.Anything).Unset() + ob.evmClient.On("BlockNumber", mock.Anything).Return(blockNumber+confirmations, nil) + ob.evmClient.On("TransactionByHash", mock.Anything, outboundHash).Return(tx, false, nil) + ob.evmClient.On("TransactionReceipt", mock.Anything, outboundHash).Return(receipt, nil) + + ob.BlockCache().Add(blockNumber, block) // filter TSS outbound ob.FilterTSSOutbound(ctx, blockNumber, blockNumber) @@ -460,7 +438,7 @@ func Test_FilterTSSOutbound(t *testing.T) { require.True(t, found) // retrieve tx and receipt - receipt, tx := ob.GetTxNReceipt(outboundNonce) + receipt, tx = ob.GetTxNReceipt(outboundNonce) require.NotNil(t, tx) require.NotNil(t, receipt) require.Equal(t, outboundHash, tx.Hash()) @@ -468,16 +446,7 @@ func Test_FilterTSSOutbound(t *testing.T) { }) t.Run("should filter nothing on RPC error", func(t *testing.T) { - // create mock evm client block number - evmClient := mocks.NewEVMRPCClient(t) - evmClient.On("BlockNumber", mock.Anything).Return(blockNumber+1, nil) - - // create evm JSON-RPC client without block to simulate RPC error - evmJSONRPC := mocks.NewMockJSONRPCClient() - - // create evm observer for testing - tss := mocks.NewTSS(t) - ob, _ := MockEVMObserver(t, chain, evmClient, evmJSONRPC, nil, tss, 1, chainParam) + ob := newTestSuite(t) // filter TSS outbound ob.FilterTSSOutbound(ctx, blockNumber, blockNumber) diff --git a/zetaclient/chains/evm/signer/sign_test.go b/zetaclient/chains/evm/signer/sign_test.go index d87badfe1d..0e408b3a57 100644 --- a/zetaclient/chains/evm/signer/sign_test.go +++ b/zetaclient/chains/evm/signer/sign_test.go @@ -8,21 +8,17 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/zeta-chain/node/zetaclient/testutils/mocks" ) func TestSigner_SignConnectorOnReceive(t *testing.T) { ctx := makeCtx(t) // Setup evm signer - tss := mocks.NewTSS(t) - evmSigner, err := getNewEvmSigner(t, tss) - require.NoError(t, err) + evmSigner := newTestSuite(t) // Setup txData struct cctx := getCCTX(t) - require.NoError(t, err) txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) require.False(t, skip) require.NoError(t, err) @@ -33,17 +29,17 @@ func TestSigner_SignConnectorOnReceive(t *testing.T) { require.NoError(t, err) // Verify Signature - verifyTxSender(t, tx, tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) + verifyTxSender(t, tx, evmSigner.tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) }) t.Run("SignConnectorOnReceive - should fail if keysign fails", func(t *testing.T) { // Pause tss to make keysign fail - tss.Pause() + evmSigner.tss.Pause() // Call SignConnectorOnReceive tx, err := evmSigner.SignConnectorOnReceive(ctx, txData) require.ErrorContains(t, err, "sign onReceive error") require.Nil(t, tx) - tss.Unpause() + evmSigner.tss.Unpause() }) t.Run("SignOutbound - should successfully sign LegacyTx", func(t *testing.T) { @@ -52,7 +48,7 @@ func TestSigner_SignConnectorOnReceive(t *testing.T) { require.NoError(t, err) // Verify Signature - verifyTxSender(t, tx, tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) + verifyTxSender(t, tx, evmSigner.tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) // check that by default tx type is legacy tx assert.Equal(t, ethtypes.LegacyTxType, int(tx.Type())) @@ -78,14 +74,14 @@ func TestSigner_SignConnectorOnReceive(t *testing.T) { require.NoError(t, err) // Given a working TSS - tss.Unpause() + evmSigner.tss.Unpause() // ACT tx, err := evmSigner.SignConnectorOnReceive(ctx, txData) require.NoError(t, err) // ASSERT - verifyTxSender(t, tx, tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) + verifyTxSender(t, tx, evmSigner.tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) // check that by default tx type is a dynamic fee tx assert.Equal(t, ethtypes.DynamicFeeTxType, int(tx.Type())) @@ -100,13 +96,10 @@ func TestSigner_SignConnectorOnRevert(t *testing.T) { ctx := makeCtx(t) // Setup evm signer - tss := mocks.NewTSS(t) - evmSigner, err := getNewEvmSigner(t, tss) - require.NoError(t, err) + evmSigner := newTestSuite(t) // Setup txData struct cctx := getCCTX(t) - require.NoError(t, err) txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) require.False(t, skip) require.NoError(t, err) @@ -117,7 +110,7 @@ func TestSigner_SignConnectorOnRevert(t *testing.T) { require.NoError(t, err) // Verify tx signature - verifyTxSender(t, tx, tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) + verifyTxSender(t, tx, evmSigner.tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) // Verify tx body basics // Note: Revert tx calls connector contract with 0 gas token @@ -125,7 +118,7 @@ func TestSigner_SignConnectorOnRevert(t *testing.T) { }) t.Run("SignConnectorOnRevert - should fail if keysign fails", func(t *testing.T) { // Pause tss to make keysign fail - tss.Pause() + evmSigner.tss.Pause() // Call SignConnectorOnRevert tx, err := evmSigner.SignConnectorOnRevert(ctx, txData) @@ -138,13 +131,10 @@ func TestSigner_SignCancel(t *testing.T) { ctx := makeCtx(t) // Setup evm signer - tss := mocks.NewTSS(t) - evmSigner, err := getNewEvmSigner(t, tss) - require.NoError(t, err) + evmSigner := newTestSuite(t) // Setup txData struct cctx := getCCTX(t) - require.NoError(t, err) txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) require.False(t, skip) require.NoError(t, err) @@ -155,15 +145,15 @@ func TestSigner_SignCancel(t *testing.T) { require.NoError(t, err) // Verify tx signature - verifyTxSender(t, tx, tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) + verifyTxSender(t, tx, evmSigner.tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) // Verify tx body basics // Note: Cancel tx sends 0 gas token to TSS self address - verifyTxBodyBasics(t, tx, tss.PubKey().AddressEVM(), txData.nonce, big.NewInt(0)) + verifyTxBodyBasics(t, tx, evmSigner.tss.PubKey().AddressEVM(), txData.nonce, big.NewInt(0)) }) t.Run("SignCancel - should fail if keysign fails", func(t *testing.T) { // Pause tss to make keysign fail - tss.Pause() + evmSigner.tss.Pause() // Call SignCancel tx, err := evmSigner.SignCancel(ctx, txData) @@ -176,13 +166,10 @@ func TestSigner_SignGasWithdraw(t *testing.T) { ctx := makeCtx(t) // Setup evm signer - tss := mocks.NewTSS(t) - evmSigner, err := getNewEvmSigner(t, tss) - require.NoError(t, err) + evmSigner := newTestSuite(t) // Setup txData struct cctx := getCCTX(t) - require.NoError(t, err) txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) require.False(t, skip) require.NoError(t, err) @@ -193,14 +180,14 @@ func TestSigner_SignGasWithdraw(t *testing.T) { require.NoError(t, err) // Verify tx signature - verifyTxSender(t, tx, tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) + verifyTxSender(t, tx, evmSigner.tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) // Verify tx body basics verifyTxBodyBasics(t, tx, txData.to, txData.nonce, txData.amount) }) t.Run("SignGasWithdraw - should fail if keysign fails", func(t *testing.T) { // Pause tss to make keysign fail - tss.Pause() + evmSigner.tss.Pause() // Call SignGasWithdraw tx, err := evmSigner.SignGasWithdraw(ctx, txData) @@ -213,9 +200,7 @@ func TestSigner_SignERC20Withdraw(t *testing.T) { ctx := makeCtx(t) // Setup evm signer - tss := mocks.NewTSS(t) - evmSigner, err := getNewEvmSigner(t, tss) - require.NoError(t, err) + evmSigner := newTestSuite(t) // Setup txData struct cctx := getCCTX(t) @@ -229,7 +214,7 @@ func TestSigner_SignERC20Withdraw(t *testing.T) { require.NoError(t, err) // Verify tx signature - verifyTxSender(t, tx, tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) + verifyTxSender(t, tx, evmSigner.tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) // Verify tx body basics // Note: Withdraw tx calls erc20 custody contract with 0 gas token @@ -238,7 +223,7 @@ func TestSigner_SignERC20Withdraw(t *testing.T) { t.Run("SignERC20WithdrawTx - should fail if keysign fails", func(t *testing.T) { // pause tss to make keysign fail - tss.Pause() + evmSigner.tss.Pause() // Call SignERC20WithdrawTx tx, err := evmSigner.SignERC20Withdraw(ctx, txData) diff --git a/zetaclient/chains/evm/signer/signer.go b/zetaclient/chains/evm/signer/signer.go index e23203627a..4a6c194b21 100644 --- a/zetaclient/chains/evm/signer/signer.go +++ b/zetaclient/chains/evm/signer/signer.go @@ -75,7 +75,6 @@ func NewSigner( ctx context.Context, chain chains.Chain, tss interfaces.TSSSigner, - ts *metrics.TelemetryServer, logger base.Logger, endpoint string, zetaConnectorAddress ethcommon.Address, @@ -83,7 +82,7 @@ func NewSigner( gatewayAddress ethcommon.Address, ) (*Signer, error) { // create base signer - baseSigner := base.NewSigner(chain, tss, ts, logger) + baseSigner := base.NewSigner(chain, tss, logger) // create EVM client client, ethSigner, err := getEVMRPC(ctx, endpoint) @@ -101,11 +100,6 @@ func NewSigner( }, nil } -// WithEvmClient attaches a new client to the signer -func (signer *Signer) WithEvmClient(client interfaces.EVMRPCClient) { - signer.client = client -} - // SetZetaConnectorAddress sets the zeta connector address func (signer *Signer) SetZetaConnectorAddress(addr ethcommon.Address) { // noop @@ -546,11 +540,6 @@ func (signer *Signer) BroadcastOutbound( } } -// EvmClient returns the EVM RPC client -func (signer *Signer) EvmClient() interfaces.EVMRPCClient { - return signer.client -} - // EvmSigner returns the EVM signer object for the signer func (signer *Signer) EvmSigner() ethtypes.Signer { // TODO(revamp): rename field into evmSigner diff --git a/zetaclient/chains/evm/signer/signer_admin_test.go b/zetaclient/chains/evm/signer/signer_admin_test.go index c466b69240..0e3575cf17 100644 --- a/zetaclient/chains/evm/signer/signer_admin_test.go +++ b/zetaclient/chains/evm/signer/signer_admin_test.go @@ -9,21 +9,17 @@ import ( "github.com/stretchr/testify/require" "github.com/zeta-chain/node/pkg/constant" "github.com/zeta-chain/node/testutil/sample" - "github.com/zeta-chain/node/zetaclient/testutils/mocks" ) func TestSigner_SignAdminTx(t *testing.T) { ctx := makeCtx(t) // Setup evm signer - tss := mocks.NewTSS(t) - evmSigner, err := getNewEvmSigner(t, tss) - require.NoError(t, err) + evmSigner := newTestSuite(t) // Setup txData struct cctx := getCCTX(t) - require.NoError(t, err) txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) require.False(t, skip) @@ -37,7 +33,7 @@ func TestSigner_SignAdminTx(t *testing.T) { require.NoError(t, err) // Verify tx signature - verifyTxSender(t, tx, tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) + verifyTxSender(t, tx, evmSigner.tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) // Verify tx body basics // Note: Revert tx calls erc20 custody contract with 0 gas token @@ -57,7 +53,7 @@ func TestSigner_SignAdminTx(t *testing.T) { require.NoError(t, err) // Verify tx signature - verifyTxSender(t, tx, tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) + verifyTxSender(t, tx, evmSigner.tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) // Verify tx body basics // Note: Revert tx calls erc20 custody contract with 0 gas token @@ -72,7 +68,7 @@ func TestSigner_SignAdminTx(t *testing.T) { require.NoError(t, err) // Verify tx signature - verifyTxSender(t, tx, tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) + verifyTxSender(t, tx, evmSigner.tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) // Verify tx body basics // Note: Revert tx calls erc20 custody contract with 0 gas token @@ -86,7 +82,7 @@ func TestSigner_SignAdminTx(t *testing.T) { require.NoError(t, err) // Verify tx signature - verifyTxSender(t, tx, tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) + verifyTxSender(t, tx, evmSigner.tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) // Verify tx body basics verifyTxBodyBasics(t, tx, txData.to, txData.nonce, txData.amount) @@ -97,15 +93,11 @@ func TestSigner_SignWhitelistERC20Cmd(t *testing.T) { ctx := makeCtx(t) // Setup evm signer - tss := mocks.NewTSS(t) - evmSigner, err := getNewEvmSigner(t, tss) - require.NoError(t, err) + evmSigner := newTestSuite(t) // Setup txData struct cctx := getCCTX(t) - require.NoError(t, err) - txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) require.NoError(t, err) @@ -118,7 +110,7 @@ func TestSigner_SignWhitelistERC20Cmd(t *testing.T) { require.NotNil(t, tx) // Verify tx signature - verifyTxSender(t, tx, tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) + verifyTxSender(t, tx, evmSigner.tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) // Verify tx body basics verifyTxBodyBasics(t, tx, txData.to, txData.nonce, zeroValue) @@ -132,7 +124,7 @@ func TestSigner_SignWhitelistERC20Cmd(t *testing.T) { t.Run("signWhitelistERC20Cmd - should fail if keysign fails", func(t *testing.T) { // Pause tss to make keysign fail - tss.Pause() + evmSigner.tss.Pause() // Call signWhitelistERC20Cmd tx, err := evmSigner.signWhitelistERC20Cmd(ctx, txData, sample.EthAddress().Hex()) @@ -145,15 +137,11 @@ func TestSigner_SignMigrateERC20CustodyFundsCmd(t *testing.T) { ctx := makeCtx(t) // Setup evm signer - tss := mocks.NewTSS(t) - evmSigner, err := getNewEvmSigner(t, tss) - require.NoError(t, err) + evmSigner := newTestSuite(t) // Setup txData struct cctx := getCCTX(t) - require.NoError(t, err) - txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) require.NoError(t, err) @@ -174,7 +162,7 @@ func TestSigner_SignMigrateERC20CustodyFundsCmd(t *testing.T) { require.NotNil(t, tx) // Verify tx signature - verifyTxSender(t, tx, tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) + verifyTxSender(t, tx, evmSigner.tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) // Verify tx body basics verifyTxBodyBasics(t, tx, txData.to, txData.nonce, zeroValue) @@ -190,7 +178,7 @@ func TestSigner_SignMigrateERC20CustodyFundsCmd(t *testing.T) { t.Run("signMigrateERC20CustodyFundsCmd - should fail if keysign fails", func(t *testing.T) { // Pause tss to make keysign fail - tss.Pause() + evmSigner.tss.Pause() params := fmt.Sprintf( "%s,%s,%s", @@ -210,14 +198,11 @@ func TestSigner_SignUpdateERC20CustodyPauseStatusCmd(t *testing.T) { ctx := makeCtx(t) // Setup evm signer - tss := mocks.NewTSS(t) - evmSigner, err := getNewEvmSigner(t, tss) - require.NoError(t, err) + evmSigner := newTestSuite(t) // Setup txData struct cctx := getCCTX(t) - require.NoError(t, err) txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) require.False(t, skip) @@ -233,7 +218,7 @@ func TestSigner_SignUpdateERC20CustodyPauseStatusCmd(t *testing.T) { require.NotNil(t, tx) // Verify tx signature - verifyTxSender(t, tx, tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) + verifyTxSender(t, tx, evmSigner.tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) // Verify tx body basics verifyTxBodyBasics(t, tx, txData.to, txData.nonce, zeroValue) @@ -249,7 +234,7 @@ func TestSigner_SignUpdateERC20CustodyPauseStatusCmd(t *testing.T) { require.NotNil(t, tx) // Verify tx signature - verifyTxSender(t, tx, tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) + verifyTxSender(t, tx, evmSigner.tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) // Verify tx body basics verifyTxBodyBasics(t, tx, txData.to, txData.nonce, zeroValue) @@ -271,7 +256,7 @@ func TestSigner_SignUpdateERC20CustodyPauseStatusCmd(t *testing.T) { t.Run("signUpdateERC20CustodyPauseStatusCmd - should fail if keysign fails", func(t *testing.T) { // Pause tss to make keysign fail - tss.Pause() + evmSigner.tss.Pause() params := constant.OptionPause @@ -286,14 +271,11 @@ func TestSigner_SignMigrateTssFundsCmd(t *testing.T) { ctx := makeCtx(t) // Setup evm signer - tss := mocks.NewTSS(t) - evmSigner, err := getNewEvmSigner(t, tss) - require.NoError(t, err) + evmSigner := newTestSuite(t) // Setup txData struct cctx := getCCTX(t) - require.NoError(t, err) txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) require.False(t, skip) @@ -306,7 +288,7 @@ func TestSigner_SignMigrateTssFundsCmd(t *testing.T) { require.NotNil(t, tx) // Verify tx signature - verifyTxSender(t, tx, tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) + verifyTxSender(t, tx, evmSigner.tss.PubKey().AddressEVM(), evmSigner.EvmSigner()) // Verify tx body basics verifyTxBodyBasics(t, tx, txData.to, txData.nonce, txData.amount) @@ -314,7 +296,7 @@ func TestSigner_SignMigrateTssFundsCmd(t *testing.T) { t.Run("signMigrateTssFundsCmd - should fail if keysign fails", func(t *testing.T) { // Pause tss to make keysign fail - tss.Pause() + evmSigner.tss.Pause() // Call signMigrateTssFundsCmd tx, err := evmSigner.signMigrateTssFundsCmd(ctx, txData) diff --git a/zetaclient/chains/evm/signer/signer_test.go b/zetaclient/chains/evm/signer/signer_test.go index 4165021f0e..bcd1114b80 100644 --- a/zetaclient/chains/evm/signer/signer_test.go +++ b/zetaclient/chains/evm/signer/signer_test.go @@ -35,30 +35,38 @@ var ( ERC20CustodyAddress = sample.EthAddress() ) -// getNewEvmSigner creates a new EVM chain signer for testing -func getNewEvmSigner(t *testing.T, tss interfaces.TSSSigner) (*Signer, error) { - ctx := context.Background() - - // use default mock TSS if not provided - if tss == nil { - tss = mocks.NewTSS(t) - } +type testSuite struct { + *Signer + tss *mocks.TSS + client *mocks.EVMRPCClient +} - connectorAddress := ConnectorAddress - erc20CustodyAddress := ERC20CustodyAddress - logger := base.Logger{} +func newTestSuite(t *testing.T) *testSuite { + ctx := context.Background() + chain := chains.BscMainnet + tss := mocks.NewTSS(t) + logger := zerolog.New(zerolog.NewTestWriter(t)) - return NewSigner( + s, err := NewSigner( ctx, - chains.BscMainnet, + chain, tss, - nil, - logger, + base.Logger{Std: logger, Compliance: logger}, testutils.MockEVMRPCEndpoint, - connectorAddress, - erc20CustodyAddress, + ConnectorAddress, + ERC20CustodyAddress, sample.EthAddress(), ) + require.NoError(t, err) + + client, ok := s.client.(*mocks.EVMRPCClient) + require.True(t, ok) + + return &testSuite{ + Signer: s, + tss: tss, + client: client, + } } // getNewEvmChainObserver creates a new EVM chain observer for testing @@ -136,8 +144,8 @@ func verifyTxBodyBasics( } func TestSigner_SetGetConnectorAddress(t *testing.T) { - evmSigner, err := getNewEvmSigner(t, nil) - require.NoError(t, err) + evmSigner := newTestSuite(t) + // Get and compare require.Equal(t, ConnectorAddress, evmSigner.GetZetaConnectorAddress()) @@ -148,8 +156,7 @@ func TestSigner_SetGetConnectorAddress(t *testing.T) { } func TestSigner_SetGetERC20CustodyAddress(t *testing.T) { - evmSigner, err := getNewEvmSigner(t, nil) - require.NoError(t, err) + evmSigner := newTestSuite(t) // Get and compare require.Equal(t, ERC20CustodyAddress, evmSigner.GetERC20CustodyAddress()) @@ -163,17 +170,14 @@ func TestSigner_TryProcessOutbound(t *testing.T) { ctx := makeCtx(t) // Setup evm signer - evmSigner, err := getNewEvmSigner(t, nil) - require.NoError(t, err) + evmSigner := newTestSuite(t) cctx := getCCTX(t) processor := getNewOutboundProcessor() mockObserver, err := getNewEvmChainObserver(t, nil) require.NoError(t, err) // Attach mock EVM client to the signer - evmClient := mocks.NewEVMRPCClient(t) - evmClient.On("SendTransaction", mock.Anything, mock.Anything).Return(nil) - evmSigner.WithEvmClient(evmClient) + evmSigner.client.On("SendTransaction", mock.Anything, mock.Anything).Return(nil) // Test with mock client that has keys client := mocks.NewZetacoreClient(t). @@ -192,8 +196,7 @@ func TestSigner_BroadcastOutbound(t *testing.T) { ctx := makeCtx(t) // Setup evm signer - evmSigner, err := getNewEvmSigner(t, nil) - require.NoError(t, err) + evmSigner := newTestSuite(t) // Setup txData struct cctx := getCCTX(t) @@ -202,9 +205,7 @@ func TestSigner_BroadcastOutbound(t *testing.T) { require.False(t, skip) // Attach mock EVM evmClient to the signer - evmClient := mocks.NewEVMRPCClient(t) - evmClient.On("SendTransaction", mock.Anything, mock.Anything).Return(nil) - evmSigner.WithEvmClient(evmClient) + evmSigner.client.On("SendTransaction", mock.Anything, mock.Anything).Return(nil) t.Run("BroadcastOutbound - should successfully broadcast", func(t *testing.T) { // Call SignERC20Withdraw diff --git a/zetaclient/chains/solana/observer/observer.go b/zetaclient/chains/solana/observer/observer.go index 6187ca2c39..0a9b2dcc3b 100644 --- a/zetaclient/chains/solana/observer/observer.go +++ b/zetaclient/chains/solana/observer/observer.go @@ -56,7 +56,6 @@ func NewObserver( zetacoreClient, tss, base.DefaultBlockCacheSize, - base.DefaultHeaderCacheSize, rpcAlertLatency, ts, db, @@ -86,16 +85,6 @@ func NewObserver( return ob, nil } -// SolClient returns the solana rpc client -func (ob *Observer) SolClient() interfaces.SolanaRPCClient { - return ob.solClient -} - -// WithSolClient attaches a new solana rpc client to the observer -func (ob *Observer) WithSolClient(client interfaces.SolanaRPCClient) { - ob.solClient = client -} - // Start starts the Go routine processes to observe the Solana chain func (ob *Observer) Start(ctx context.Context) { if ok := ob.Observer.Start(); !ok { diff --git a/zetaclient/chains/solana/signer/signer.go b/zetaclient/chains/solana/signer/signer.go index 1e3d6914f3..72174ab5e9 100644 --- a/zetaclient/chains/solana/signer/signer.go +++ b/zetaclient/chains/solana/signer/signer.go @@ -53,11 +53,10 @@ func NewSigner( solClient interfaces.SolanaRPCClient, tss interfaces.TSSSigner, relayerKey *keys.RelayerKey, - ts *metrics.TelemetryServer, logger base.Logger, ) (*Signer, error) { // create base signer - baseSigner := base.NewSigner(chain, tss, ts, logger) + baseSigner := base.NewSigner(chain, tss, logger) // parse gateway ID and PDA gatewayID, pda, err := contracts.ParseGatewayWithPDA(chainParams.GatewayAddress) diff --git a/zetaclient/chains/solana/signer/signer_test.go b/zetaclient/chains/solana/signer/signer_test.go index b05fc75638..dfb6d678a0 100644 --- a/zetaclient/chains/solana/signer/signer_test.go +++ b/zetaclient/chains/solana/signer/signer_test.go @@ -89,7 +89,7 @@ func Test_NewSigner(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s, err := signer.NewSigner(tt.chain, tt.chainParams, tt.solClient, tt.tss, tt.relayerKey, tt.ts, tt.logger) + s, err := signer.NewSigner(tt.chain, tt.chainParams, tt.solClient, tt.tss, tt.relayerKey, tt.logger) if tt.errMessage != "" { require.ErrorContains(t, err, tt.errMessage) require.Nil(t, s) @@ -110,7 +110,7 @@ func Test_SetGatewayAddress(t *testing.T) { // helper functor to create signer signerCreator := func() *signer.Signer { - s, err := signer.NewSigner(chain, *chainParams, nil, nil, nil, nil, base.DefaultLogger()) + s, err := signer.NewSigner(chain, *chainParams, nil, nil, nil, base.DefaultLogger()) require.NoError(t, err) return s } @@ -159,7 +159,7 @@ func Test_SetRelayerBalanceMetrics(t *testing.T) { mckClient.On("GetBalance", mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("rpc error")) // create signer and set relayer balance metrics - s, err := signer.NewSigner(chain, *chainParams, mckClient, nil, relayerKey, nil, base.DefaultLogger()) + s, err := signer.NewSigner(chain, *chainParams, mckClient, nil, relayerKey, base.DefaultLogger()) require.NoError(t, err) s.SetRelayerBalanceMetrics(ctx) @@ -174,7 +174,7 @@ func Test_SetRelayerBalanceMetrics(t *testing.T) { }, nil) // create signer and set relayer balance metrics again - s, err = signer.NewSigner(chain, *chainParams, mckClient, nil, relayerKey, nil, base.DefaultLogger()) + s, err = signer.NewSigner(chain, *chainParams, mckClient, nil, relayerKey, base.DefaultLogger()) require.NoError(t, err) s.SetRelayerBalanceMetrics(ctx) diff --git a/zetaclient/chains/ton/observer/observer_test.go b/zetaclient/chains/ton/observer/observer_test.go index d08103ea63..808277c226 100644 --- a/zetaclient/chains/ton/observer/observer_test.go +++ b/zetaclient/chains/ton/observer/observer_test.go @@ -81,7 +81,6 @@ func newTestSuite(t *testing.T) *testSuite { zetacore, tss, 1, - 1, 60, nil, database, diff --git a/zetaclient/chains/ton/signer/signer_test.go b/zetaclient/chains/ton/signer/signer_test.go index ce0d4fef16..1ac464ca48 100644 --- a/zetaclient/chains/ton/signer/signer_test.go +++ b/zetaclient/chains/ton/signer/signer_test.go @@ -166,7 +166,7 @@ func newTestSuite(t *testing.T) *testSuite { proc: outboundprocessor.NewProcessor(logger.Std), gw: toncontracts.NewGateway(gwAccountID), - baseSigner: base.NewSigner(chain, tss, nil, logger), + baseSigner: base.NewSigner(chain, tss, logger), } // Setup mocks diff --git a/zetaclient/orchestrator/bootstap_test.go b/zetaclient/orchestrator/bootstap_test.go index 99f58b9e5b..0e36d4721d 100644 --- a/zetaclient/orchestrator/bootstap_test.go +++ b/zetaclient/orchestrator/bootstap_test.go @@ -29,7 +29,6 @@ const ( func TestCreateSignerMap(t *testing.T) { var ( - ts = metrics.NewTelemetryServer() tss = mocks.NewTSS(t) log = zerolog.New(zerolog.NewTestWriter(t)) baseLogger = base.Logger{Std: log, Compliance: log} @@ -64,7 +63,7 @@ func TestCreateSignerMap(t *testing.T) { }) // ACT - signers, err := CreateSignerMap(ctx, tss, baseLogger, ts) + signers, err := CreateSignerMap(ctx, tss, baseLogger) // ASSERT assert.NoError(t, err) @@ -82,7 +81,7 @@ func TestCreateSignerMap(t *testing.T) { }) // ACT - added, removed, err := syncSignerMap(ctx, tss, baseLogger, ts, &signers) + added, removed, err := syncSignerMap(ctx, tss, baseLogger, &signers) // ASSERT assert.NoError(t, err) @@ -101,7 +100,7 @@ func TestCreateSignerMap(t *testing.T) { }) // ACT - added, removed, err := syncSignerMap(ctx, tss, baseLogger, ts, &signers) + added, removed, err := syncSignerMap(ctx, tss, baseLogger, &signers) // ASSERT assert.NoError(t, err) @@ -122,7 +121,7 @@ func TestCreateSignerMap(t *testing.T) { }) // ACT - added, removed, err := syncSignerMap(ctx, tss, baseLogger, ts, &signers) + added, removed, err := syncSignerMap(ctx, tss, baseLogger, &signers) // ASSERT assert.NoError(t, err) @@ -142,7 +141,7 @@ func TestCreateSignerMap(t *testing.T) { }) // ACT - added, removed, err := syncSignerMap(ctx, tss, baseLogger, ts, &signers) + added, removed, err := syncSignerMap(ctx, tss, baseLogger, &signers) // ASSERT assert.NoError(t, err) @@ -164,7 +163,7 @@ func TestCreateSignerMap(t *testing.T) { }) // ACT - added, removed, err := syncSignerMap(ctx, tss, baseLogger, ts, &signers) + added, removed, err := syncSignerMap(ctx, tss, baseLogger, &signers) // ASSERT assert.NoError(t, err) @@ -181,7 +180,7 @@ func TestCreateSignerMap(t *testing.T) { before := len(signers) // ACT - added, removed, err := syncSignerMap(ctx, tss, baseLogger, ts, &signers) + added, removed, err := syncSignerMap(ctx, tss, baseLogger, &signers) // ASSERT assert.NoError(t, err) diff --git a/zetaclient/orchestrator/bootstrap.go b/zetaclient/orchestrator/bootstrap.go index 77793d341b..bfff2c3f65 100644 --- a/zetaclient/orchestrator/bootstrap.go +++ b/zetaclient/orchestrator/bootstrap.go @@ -40,10 +40,9 @@ func CreateSignerMap( ctx context.Context, tss interfaces.TSSSigner, logger base.Logger, - ts *metrics.TelemetryServer, ) (map[int64]interfaces.ChainSigner, error) { signers := make(map[int64]interfaces.ChainSigner) - _, _, err := syncSignerMap(ctx, tss, logger, ts, &signers) + _, _, err := syncSignerMap(ctx, tss, logger, &signers) if err != nil { return nil, err } @@ -58,7 +57,6 @@ func syncSignerMap( ctx context.Context, tss interfaces.TSSSigner, logger base.Logger, - ts *metrics.TelemetryServer, signers *map[int64]interfaces.ChainSigner, ) (int, int, error) { if signers == nil { @@ -128,7 +126,6 @@ func syncSignerMap( ctx, *rawChain, tss, - ts, logger, cfg.Endpoint, zetaConnectorAddress, @@ -148,7 +145,7 @@ func syncSignerMap( continue } - signer, err := btcsigner.NewSigner(*rawChain, tss, ts, logger, cfg) + signer, err := btcsigner.NewSigner(*rawChain, tss, logger, cfg) if err != nil { logger.Std.Error().Err(err).Msgf("Unable to construct signer for BTC chain %d", chainID) continue @@ -179,7 +176,7 @@ func syncSignerMap( } // create Solana signer - signer, err := solanasigner.NewSigner(*rawChain, *params, rpcClient, tss, relayerKey, ts, logger) + signer, err := solanasigner.NewSigner(*rawChain, *params, rpcClient, tss, relayerKey, logger) if err != nil { logger.Std.Error().Err(err).Msgf("Unable to construct signer for SOL chain %d", chainID) continue @@ -200,7 +197,7 @@ func syncSignerMap( } tonSigner := tonsigner.New( - base.NewSigner(*rawChain, tss, ts, logger), + base.NewSigner(*rawChain, tss, logger), tonClient, gateway, ) @@ -440,7 +437,6 @@ func syncObserverMap( client, tss, base.DefaultBlockCacheSize, - base.DefaultHeaderCacheSize, cfg.RPCAlertLatency, ts, database, diff --git a/zetaclient/orchestrator/orchestrator.go b/zetaclient/orchestrator/orchestrator.go index 2e73131357..0e50777f10 100644 --- a/zetaclient/orchestrator/orchestrator.go +++ b/zetaclient/orchestrator/orchestrator.go @@ -772,7 +772,7 @@ func (oc *Orchestrator) syncObserverSigner(ctx context.Context) error { Msg("synced observers") } - added, removed, err = syncSignerMap(ctx, oc.tss, oc.baseLogger, oc.ts, &oc.signerMap) + added, removed, err = syncSignerMap(ctx, oc.tss, oc.baseLogger, &oc.signerMap) if err != nil { return errors.Wrap(err, "syncSignerMap failed") } diff --git a/zetaclient/testutils/mocks/zetacore_client.go b/zetaclient/testutils/mocks/zetacore_client.go index fa5b34486b..fefd653874 100644 --- a/zetaclient/testutils/mocks/zetacore_client.go +++ b/zetaclient/testutils/mocks/zetacore_client.go @@ -22,8 +22,8 @@ import ( upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" - zerolog "github.com/rs/zerolog" cometbfttypes "github.com/cometbft/cometbft/types" + zerolog "github.com/rs/zerolog" ) // ZetacoreClient is an autogenerated mock type for the ZetacoreClient type