diff --git a/changelog.md b/changelog.md index 93b7cd0b68..76f482c087 100644 --- a/changelog.md +++ b/changelog.md @@ -45,6 +45,7 @@ ### Fixes * [1861](https://github.com/zeta-chain/node/pull/1861) - fix `ObserverSlashAmount` invalid read +* [1633](https://github.com/zeta-chain/node/issues/1633) - zetaclient should be able to pick up new connector and erc20Custody addresses ### Chores diff --git a/cmd/zetaclientd/utils.go b/cmd/zetaclientd/utils.go index f926f8118e..ca9a259594 100644 --- a/cmd/zetaclientd/utils.go +++ b/cmd/zetaclientd/utils.go @@ -3,7 +3,6 @@ package main import ( sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/common/cosmos" appcontext "github.com/zeta-chain/zetacore/zetaclient/app_context" "github.com/zeta-chain/zetacore/zetaclient/authz" @@ -55,8 +54,8 @@ func CreateSignerMap( tss interfaces.TSSSigner, loggers clientcommon.ClientLogger, ts *metrics.TelemetryServer, -) (map[common.Chain]interfaces.ChainSigner, error) { - signerMap := make(map[common.Chain]interfaces.ChainSigner) +) (map[int64]interfaces.ChainSigner, error) { + signerMap := make(map[int64]interfaces.ChainSigner) // EVM signers for _, evmConfig := range appContext.Config().GetAllEVMConfigs() { if evmConfig.Chain.IsZetaChain() { @@ -77,7 +76,7 @@ func CreateSignerMap( loggers.Std.Error().Err(err).Msgf("NewEVMSigner error for chain %s", evmConfig.Chain.String()) continue } - signerMap[evmConfig.Chain] = signer + signerMap[evmConfig.Chain.ChainId] = signer } // BTC signer btcChain, btcConfig, enabled := appContext.GetBTCChainAndConfig() @@ -86,7 +85,7 @@ func CreateSignerMap( if err != nil { loggers.Std.Error().Err(err).Msgf("NewBTCSigner error for chain %s", btcChain.String()) } else { - signerMap[btcChain] = signer + signerMap[btcChain.ChainId] = signer } } @@ -100,8 +99,8 @@ func CreateChainClientMap( dbpath string, loggers clientcommon.ClientLogger, ts *metrics.TelemetryServer, -) (map[common.Chain]interfaces.ChainClient, error) { - clientMap := make(map[common.Chain]interfaces.ChainClient) +) (map[int64]interfaces.ChainClient, error) { + clientMap := make(map[int64]interfaces.ChainClient) // EVM clients for _, evmConfig := range appContext.Config().GetAllEVMConfigs() { if evmConfig.Chain.IsZetaChain() { @@ -120,17 +119,17 @@ func CreateChainClientMap( loggers.Std.Error().Err(err).Msgf("NewEVMChainClient error for chain %s", evmConfig.Chain.String()) continue } - clientMap[evmConfig.Chain] = co + clientMap[evmConfig.Chain.ChainId] = co } // BTC client - btcChain, _, enabled := appContext.GetBTCChainAndConfig() + btcChain, btcConfig, enabled := appContext.GetBTCChainAndConfig() if enabled { - co, err := bitcoin.NewBitcoinClient(appContext, btcChain, bridge, tss, dbpath, loggers, ts) + co, err := bitcoin.NewBitcoinClient(appContext, btcChain, bridge, tss, dbpath, loggers, btcConfig, ts) if err != nil { loggers.Std.Error().Err(err).Msgf("NewBitcoinClient error for chain %s", btcChain.String()) } else { - clientMap[btcChain] = co + clientMap[btcChain.ChainId] = co } } diff --git a/zetaclient/bitcoin/bitcoin_client.go b/zetaclient/bitcoin/bitcoin_client.go index 34ddc580ab..b270974c37 100644 --- a/zetaclient/bitcoin/bitcoin_client.go +++ b/zetaclient/bitcoin/bitcoin_client.go @@ -140,6 +140,7 @@ func NewBitcoinClient( tss interfaces.TSSSigner, dbpath string, loggers clientcommon.ClientLogger, + btcCfg config.BTCConfig, ts *metrics.TelemetryServer, ) (*BTCChainClient, error) { ob := BTCChainClient{ @@ -174,7 +175,6 @@ func NewBitcoinClient( } ob.params = *chainParams // initialize the Client - btcCfg := appcontext.Config().BitcoinConfig ob.logger.ChainLogger.Info().Msgf("Chain %s endpoint %s", ob.chain.String(), btcCfg.RPCHost) connCfg := &rpcclient.ConnConfig{ Host: btcCfg.RPCHost, diff --git a/zetaclient/bitcoin/bitcoin_client_rpc_test.go b/zetaclient/bitcoin/bitcoin_client_rpc_test.go index 7f8c060dce..0f9acc6a3d 100644 --- a/zetaclient/bitcoin/bitcoin_client_rpc_test.go +++ b/zetaclient/bitcoin/bitcoin_client_rpc_test.go @@ -47,7 +47,8 @@ func (suite *BitcoinClientTestSuite) SetupTest() { PrivKey: privateKey, } appContext := appcontext.NewAppContext(&corecontext.ZetaCoreContext{}, config.Config{}) - client, err := NewBitcoinClient(appContext, common.BtcRegtestChain(), nil, tss, "/tmp", clientcommon.DefaultLoggers(), nil) + client, err := NewBitcoinClient(appContext, common.BtcRegtestChain(), nil, tss, "/tmp", + clientcommon.DefaultLoggers(), config.BTCConfig{}, nil) suite.Require().NoError(err) suite.BitcoinChainClient = client skBytes, err := hex.DecodeString(skHex) diff --git a/zetaclient/bitcoin/bitcoin_signer.go b/zetaclient/bitcoin/bitcoin_signer.go index 05106e9a48..b2cd62db70 100644 --- a/zetaclient/bitcoin/bitcoin_signer.go +++ b/zetaclient/bitcoin/bitcoin_signer.go @@ -8,6 +8,7 @@ import ( "math/rand" "time" + ethcommon "github.com/ethereum/go-ethereum/common" clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" "github.com/zeta-chain/zetacore/zetaclient/interfaces" "github.com/zeta-chain/zetacore/zetaclient/metrics" @@ -34,6 +35,7 @@ const ( outTxBytesMax = uint64(1531) // 1531v == EstimateSegWitTxSize(21, 3) ) +// BTCSigner deals with signing BTC transactions and implements the ChainSigner interface type BTCSigner struct { tssSigner interfaces.TSSSigner rpcClient interfaces.BTCRPCClient @@ -71,6 +73,24 @@ func NewBTCSigner( }, nil } +// SetZetaConnectorAddress does nothing for BTC +func (signer *BTCSigner) SetZetaConnectorAddress(_ ethcommon.Address) { +} + +// SetERC20CustodyAddress does nothing for BTC +func (signer *BTCSigner) SetERC20CustodyAddress(_ ethcommon.Address) { +} + +// GetZetaConnectorAddress returns dummy address +func (signer *BTCSigner) GetZetaConnectorAddress() ethcommon.Address { + return ethcommon.Address{} +} + +// GetERC20CustodyAddress returns dummy address +func (signer *BTCSigner) GetERC20CustodyAddress() ethcommon.Address { + return ethcommon.Address{} +} + // SignWithdrawTx receives utxos sorted by value, amount in BTC, feeRate in BTC per Kb func (signer *BTCSigner) SignWithdrawTx( to *btcutil.AddressWitnessPubKeyHash, diff --git a/zetaclient/evm/evm_client.go b/zetaclient/evm/evm_client.go index 87103b8e37..ed00b2379d 100644 --- a/zetaclient/evm/evm_client.go +++ b/zetaclient/evm/evm_client.go @@ -64,6 +64,8 @@ type Log struct { Compliance zerolog.Logger // Compliance logger } +var _ interfaces.ChainClient = &ChainClient{} + // ChainClient represents the chain configuration for an EVM chain // Filled with above constants depending on chain type ChainClient struct { diff --git a/zetaclient/evm/evm_signer.go b/zetaclient/evm/evm_signer.go index 3692dd666e..4578a8cba6 100644 --- a/zetaclient/evm/evm_signer.go +++ b/zetaclient/evm/evm_signer.go @@ -32,21 +32,21 @@ import ( zbridge "github.com/zeta-chain/zetacore/zetaclient/zetabridge" ) +// Signer deals with the signing EVM transactions and implements the ChainSigner interface type Signer struct { - client interfaces.EVMRPCClient - chain *common.Chain - chainID *big.Int - tssSigner interfaces.TSSSigner - ethSigner ethtypes.Signer - abi abi.ABI - erc20CustodyABI abi.ABI - metaContractAddress ethcommon.Address - erc20CustodyContractAddress ethcommon.Address - logger clientcommon.ClientLogger - ts *metrics.TelemetryServer - - // for outTx tracker report only + client interfaces.EVMRPCClient + chain *common.Chain + tssSigner interfaces.TSSSigner + ethSigner ethtypes.Signer + logger clientcommon.ClientLogger + ts *metrics.TelemetryServer + + // mu protects below fields from concurrent access mu *sync.Mutex + zetaConnectorABI abi.ABI + erc20CustodyABI abi.ABI + zetaConnectorAddress ethcommon.Address + er20CustodyAddress ethcommon.Address outTxHashBeingReported map[string]bool } @@ -56,36 +56,35 @@ func NewEVMSigner( chain common.Chain, endpoint string, tssSigner interfaces.TSSSigner, - abiString string, - erc20CustodyABIString string, - metaContract ethcommon.Address, - erc20CustodyContract ethcommon.Address, + zetaConnectorABI string, + erc20CustodyABI string, + zetaConnectorAddress ethcommon.Address, + erc20CustodyAddress ethcommon.Address, loggers clientcommon.ClientLogger, ts *metrics.TelemetryServer, ) (*Signer, error) { - client, chainID, ethSigner, err := getEVMRPC(endpoint) + client, ethSigner, err := getEVMRPC(endpoint) if err != nil { return nil, err } - connectorABI, err := abi.JSON(strings.NewReader(abiString)) + connectorABI, err := abi.JSON(strings.NewReader(zetaConnectorABI)) if err != nil { return nil, err } - erc20CustodyABI, err := abi.JSON(strings.NewReader(erc20CustodyABIString)) + custodyABI, err := abi.JSON(strings.NewReader(erc20CustodyABI)) if err != nil { return nil, err } return &Signer{ - client: client, - chain: &chain, - tssSigner: tssSigner, - chainID: chainID, - ethSigner: ethSigner, - abi: connectorABI, - erc20CustodyABI: erc20CustodyABI, - metaContractAddress: metaContract, - erc20CustodyContractAddress: erc20CustodyContract, + client: client, + chain: &chain, + tssSigner: tssSigner, + ethSigner: ethSigner, + zetaConnectorABI: connectorABI, + erc20CustodyABI: custodyABI, + zetaConnectorAddress: zetaConnectorAddress, + er20CustodyAddress: erc20CustodyAddress, logger: clientcommon.ClientLogger{ Std: loggers.Std.With().Str("chain", chain.ChainName.String()).Str("module", "EVMSigner").Logger(), Compliance: loggers.Compliance, @@ -96,6 +95,34 @@ func NewEVMSigner( }, nil } +// SetZetaConnectorAddress sets the zeta connector address +func (signer *Signer) SetZetaConnectorAddress(addr ethcommon.Address) { + signer.mu.Lock() + defer signer.mu.Unlock() + signer.zetaConnectorAddress = addr +} + +// SetERC20CustodyAddress sets the erc20 custody address +func (signer *Signer) SetERC20CustodyAddress(addr ethcommon.Address) { + signer.mu.Lock() + defer signer.mu.Unlock() + signer.er20CustodyAddress = addr +} + +// GetZetaConnectorAddress returns the zeta connector address +func (signer *Signer) GetZetaConnectorAddress() ethcommon.Address { + signer.mu.Lock() + defer signer.mu.Unlock() + return signer.zetaConnectorAddress +} + +// GetERC20CustodyAddress returns the erc20 custody address +func (signer *Signer) GetERC20CustodyAddress() ethcommon.Address { + signer.mu.Lock() + defer signer.mu.Unlock() + return signer.er20CustodyAddress +} + // Sign given data, and metadata (gas, nonce, etc) // returns a signed transaction, sig bytes, hash bytes, and error func (signer *Signer) Sign( @@ -150,7 +177,7 @@ func (signer *Signer) SignOutboundTx(txData *OutBoundTransactionData) (*ethtypes var data []byte var err error - data, err = signer.abi.Pack("onReceive", + data, err = signer.zetaConnectorABI.Pack("onReceive", txData.sender.Bytes(), txData.srcChainID, txData.to, @@ -162,7 +189,7 @@ func (signer *Signer) SignOutboundTx(txData *OutBoundTransactionData) (*ethtypes } tx, _, _, err := signer.Sign(data, - signer.metaContractAddress, + signer.zetaConnectorAddress, txData.gasLimit, txData.gasPrice, txData.nonce, @@ -188,7 +215,7 @@ func (signer *Signer) SignRevertTx(txData *OutBoundTransactionData) (*ethtypes.T var data []byte var err error - data, err = signer.abi.Pack("onRevert", + data, err = signer.zetaConnectorABI.Pack("onRevert", txData.sender, txData.srcChainID, txData.to.Bytes(), @@ -201,7 +228,7 @@ func (signer *Signer) SignRevertTx(txData *OutBoundTransactionData) (*ethtypes.T } tx, _, _, err := signer.Sign(data, - signer.metaContractAddress, + signer.zetaConnectorAddress, txData.gasLimit, txData.gasPrice, txData.nonce, @@ -556,7 +583,7 @@ func (signer *Signer) SignERC20WithdrawTx(txData *OutBoundTransactionData) (*eth return nil, fmt.Errorf("pack error: %w", err) } - tx, _, _, err := signer.Sign(data, signer.erc20CustodyContractAddress, txData.gasLimit, txData.gasPrice, txData.nonce, txData.height) + tx, _, _, err := signer.Sign(data, signer.er20CustodyAddress, txData.gasLimit, txData.gasPrice, txData.nonce, txData.height) if err != nil { return nil, fmt.Errorf("sign error: %w", err) } @@ -589,7 +616,7 @@ func (signer *Signer) SignWhitelistTx( return nil, fmt.Errorf("pack error: %w", err) } - tx, _, _, err := signer.Sign(data, signer.erc20CustodyContractAddress, gasLimit, gasPrice, nonce, height) + tx, _, _, err := signer.Sign(data, signer.er20CustodyAddress, gasLimit, gasPrice, nonce, height) if err != nil { return nil, fmt.Errorf("Sign error: %w", err) } @@ -612,25 +639,25 @@ func (signer *Signer) EvmSigner() ethtypes.Signer { // ________________________ // getEVMRPC is a helper function to set up the client and signer, also initializes a mock client for unit tests -func getEVMRPC(endpoint string) (interfaces.EVMRPCClient, *big.Int, ethtypes.Signer, error) { +func getEVMRPC(endpoint string) (interfaces.EVMRPCClient, ethtypes.Signer, error) { if endpoint == stub.EVMRPCEnabled { chainID := big.NewInt(common.BscMainnetChain().ChainId) ethSigner := ethtypes.NewEIP155Signer(chainID) client := &stub.MockEvmClient{} - return client, chainID, ethSigner, nil + return client, ethSigner, nil } client, err := ethclient.Dial(endpoint) if err != nil { - return nil, nil, nil, err + return nil, nil, err } chainID, err := client.ChainID(context.TODO()) if err != nil { - return nil, nil, nil, err + return nil, nil, err } ethSigner := ethtypes.LatestSignerForChainID(chainID) - return client, chainID, ethSigner, nil + return client, ethSigner, nil } func roundUpToNearestGwei(gasPrice *big.Int) *big.Int { diff --git a/zetaclient/evm/evm_signer_test.go b/zetaclient/evm/evm_signer_test.go index 3bbee44279..1685f359bb 100644 --- a/zetaclient/evm/evm_signer_test.go +++ b/zetaclient/evm/evm_signer_test.go @@ -76,6 +76,30 @@ func getInvalidCCTX() (*types.CrossChainTx, error) { return &cctx, err } +func TestSigner_SetGetConnectorAddress(t *testing.T) { + evmSigner, err := getNewEvmSigner() + require.NoError(t, err) + // Get and compare + require.Equal(t, ConnectorAddress, evmSigner.GetZetaConnectorAddress()) + + // Update and get again + newConnector := sample.EthAddress() + evmSigner.SetZetaConnectorAddress(newConnector) + require.Equal(t, newConnector, evmSigner.GetZetaConnectorAddress()) +} + +func TestSigner_SetGetERC20CustodyAddress(t *testing.T) { + evmSigner, err := getNewEvmSigner() + require.NoError(t, err) + // Get and compare + require.Equal(t, ERC20CustodyAddress, evmSigner.GetERC20CustodyAddress()) + + // Update and get again + newCustody := sample.EthAddress() + evmSigner.SetERC20CustodyAddress(newCustody) + require.Equal(t, newCustody, evmSigner.GetERC20CustodyAddress()) +} + func TestSigner_TryProcessOutTx(t *testing.T) { evmSigner, err := getNewEvmSigner() require.NoError(t, err) @@ -290,9 +314,8 @@ func TestSigner_BroadcastOutTx(t *testing.T) { func TestSigner_getEVMRPC(t *testing.T) { t.Run("getEVMRPC error dialing", func(t *testing.T) { - client, chainId, signer, err := getEVMRPC("invalidEndpoint") + client, signer, err := getEVMRPC("invalidEndpoint") require.Nil(t, client) - require.Nil(t, chainId) require.Nil(t, signer) require.Error(t, err) }) diff --git a/zetaclient/evm/inbounds_test.go b/zetaclient/evm/inbounds_test.go index 0663d71eeb..e382340590 100644 --- a/zetaclient/evm/inbounds_test.go +++ b/zetaclient/evm/inbounds_test.go @@ -181,7 +181,7 @@ func TestEVM_CheckAndVoteInboundTokenGas(t *testing.T) { }) t.Run("should not act if receiver is not TSS", func(t *testing.T) { tx, receipt, _ := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_Gas) - tx.To = testutils.OtherAddress // use other address + tx.To = testutils.OtherAddress1 // use other address require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation @@ -342,9 +342,9 @@ func TestEVM_BuildInboundVoteMsgForTokenSentToTSS(t *testing.T) { t.Run("should return nil msg if receiver is restricted", func(t *testing.T) { txCopy := ðrpc.Transaction{} *txCopy = *tx - message := hex.EncodeToString(ethcommon.HexToAddress(testutils.OtherAddress).Bytes()) + message := hex.EncodeToString(ethcommon.HexToAddress(testutils.OtherAddress1).Bytes()) txCopy.Input = message // use other address as receiver - cfg.ComplianceConfig.RestrictedAddresses = []string{testutils.OtherAddress} + cfg.ComplianceConfig.RestrictedAddresses = []string{testutils.OtherAddress1} config.LoadComplianceConfig(cfg) msg := ob.BuildInboundVoteMsgForTokenSentToTSS(txCopy, ethcommon.HexToAddress(txCopy.From), receipt.BlockNumber.Uint64()) require.Nil(t, msg) diff --git a/zetaclient/interfaces/interfaces.go b/zetaclient/interfaces/interfaces.go index 5d9a40fcc9..5842c2b944 100644 --- a/zetaclient/interfaces/interfaces.go +++ b/zetaclient/interfaces/interfaces.go @@ -54,6 +54,10 @@ type ChainSigner interface { zetaBridge ZetaCoreBridger, height uint64, ) + SetZetaConnectorAddress(address ethcommon.Address) + SetERC20CustodyAddress(address ethcommon.Address) + GetZetaConnectorAddress() ethcommon.Address + GetERC20CustodyAddress() ethcommon.Address } // ZetaCoreBridger is the interface to interact with ZetaCore diff --git a/zetaclient/testutils/constant.go b/zetaclient/testutils/constant.go index f3b2cd0691..05cab82256 100644 --- a/zetaclient/testutils/constant.go +++ b/zetaclient/testutils/constant.go @@ -6,12 +6,16 @@ const ( // tss addresses TSSAddressEVMMainnet = "0x70e967acFcC17c3941E87562161406d41676FD83" TSSAddressBTCMainnet = "bc1qm24wp577nk8aacckv8np465z3dvmu7ry45el6y" + TssPubkeyEVMMainnet = "zetapub1addwnpepqtadxdyt037h86z60nl98t6zk56mw5zpnm79tsmvspln3hgt5phdc79kvfc" TSSAddressEVMAthens3 = "0x8531a5aB847ff5B22D855633C25ED1DA3255247e" TSSAddressBTCAthens3 = "tb1qy9pqmk2pd9sv63g27jt8r657wy0d9ueeh0nqur" + TssPubkeyEVMAthens3 = "zetapub1addwnpepq28c57cvcs0a2htsem5zxr6qnlvq9mzhmm76z3jncsnzz32rclangr2g35p" - // some other address - OtherAddress = "0x21248Decd0B7EcB0F30186297766b8AB6496265b" + // some other addresses + OtherAddress1 = "0x21248Decd0B7EcB0F30186297766b8AB6496265b" + OtherAddress2 = "0x33A351C90aF486AebC35042Bb0544123cAed26AB" + OtherAddress3 = "0x86B77E4fBd07CFdCc486cAe4F2787fB5C5a62cd3" ) // ConnectorAddresses contains constants ERC20 connector addresses for testing diff --git a/zetaclient/testutils/stub/chain_client.go b/zetaclient/testutils/stub/chain_client.go new file mode 100644 index 0000000000..642de62792 --- /dev/null +++ b/zetaclient/testutils/stub/chain_client.go @@ -0,0 +1,90 @@ +package stub + +import ( + "github.com/rs/zerolog" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" + "github.com/zeta-chain/zetacore/zetaclient/interfaces" +) + +// ---------------------------------------------------------------------------- +// EVMClient +// ---------------------------------------------------------------------------- +var _ interfaces.ChainClient = (*EVMClient)(nil) + +// EVMClient is a mock of evm chain client for testing +type EVMClient struct { + ChainParams observertypes.ChainParams +} + +func NewEVMClient(chainParams *observertypes.ChainParams) *EVMClient { + return &EVMClient{ + ChainParams: *chainParams, + } +} + +func (s *EVMClient) Start() { +} + +func (s *EVMClient) Stop() { +} + +func (s *EVMClient) IsSendOutTxProcessed(_ *crosschaintypes.CrossChainTx, _ zerolog.Logger) (bool, bool, error) { + return false, false, nil +} + +func (s *EVMClient) SetChainParams(chainParams observertypes.ChainParams) { + s.ChainParams = chainParams +} + +func (s *EVMClient) GetChainParams() observertypes.ChainParams { + return s.ChainParams +} + +func (s *EVMClient) GetTxID(_ uint64) string { + return "" +} + +func (s *EVMClient) ExternalChainWatcherForNewInboundTrackerSuggestions() { +} + +// ---------------------------------------------------------------------------- +// BTCClient +// ---------------------------------------------------------------------------- +var _ interfaces.ChainClient = (*BTCClient)(nil) + +// BTCClient is a mock of btc chain client for testing +type BTCClient struct { + ChainParams observertypes.ChainParams +} + +func NewBTCClient(chainParams *observertypes.ChainParams) *BTCClient { + return &BTCClient{ + ChainParams: *chainParams, + } +} + +func (s *BTCClient) Start() { +} + +func (s *BTCClient) Stop() { +} + +func (s *BTCClient) IsSendOutTxProcessed(_ *crosschaintypes.CrossChainTx, _ zerolog.Logger) (bool, bool, error) { + return false, false, nil +} + +func (s *BTCClient) SetChainParams(chainParams observertypes.ChainParams) { + s.ChainParams = chainParams +} + +func (s *BTCClient) GetChainParams() observertypes.ChainParams { + return s.ChainParams +} + +func (s *BTCClient) GetTxID(_ uint64) string { + return "" +} + +func (s *BTCClient) ExternalChainWatcherForNewInboundTrackerSuggestions() { +} diff --git a/zetaclient/testutils/stub/chain_signer.go b/zetaclient/testutils/stub/chain_signer.go new file mode 100644 index 0000000000..364cbc22bb --- /dev/null +++ b/zetaclient/testutils/stub/chain_signer.go @@ -0,0 +1,96 @@ +package stub + +import ( + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/zeta-chain/zetacore/common" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + "github.com/zeta-chain/zetacore/zetaclient/interfaces" + "github.com/zeta-chain/zetacore/zetaclient/outtxprocessor" +) + +// ---------------------------------------------------------------------------- +// EVMSigner +// ---------------------------------------------------------------------------- +var _ interfaces.ChainSigner = (*EVMSigner)(nil) + +// EVMSigner is a mock of evm chain signer for testing +type EVMSigner struct { + Chain common.Chain + ZetaConnectorAddress ethcommon.Address + ERC20CustodyAddress ethcommon.Address +} + +func NewEVMSigner( + chain common.Chain, + zetaConnectorAddress ethcommon.Address, + erc20CustodyAddress ethcommon.Address, +) *EVMSigner { + return &EVMSigner{ + Chain: chain, + ZetaConnectorAddress: zetaConnectorAddress, + ERC20CustodyAddress: erc20CustodyAddress, + } +} + +func (s *EVMSigner) TryProcessOutTx( + _ *crosschaintypes.CrossChainTx, + _ *outtxprocessor.Processor, + _ string, + _ interfaces.ChainClient, + _ interfaces.ZetaCoreBridger, + _ uint64, +) { +} + +func (s *EVMSigner) SetZetaConnectorAddress(address ethcommon.Address) { + s.ZetaConnectorAddress = address +} + +func (s *EVMSigner) SetERC20CustodyAddress(address ethcommon.Address) { + s.ERC20CustodyAddress = address +} + +func (s *EVMSigner) GetZetaConnectorAddress() ethcommon.Address { + return s.ZetaConnectorAddress +} + +func (s *EVMSigner) GetERC20CustodyAddress() ethcommon.Address { + return s.ERC20CustodyAddress +} + +// ---------------------------------------------------------------------------- +// BTCSigner +// ---------------------------------------------------------------------------- +var _ interfaces.ChainSigner = (*BTCSigner)(nil) + +// BTCSigner is a mock of bitcoin chain signer for testing +type BTCSigner struct { +} + +func NewBTCSigner() *BTCSigner { + return &BTCSigner{} +} + +func (s *BTCSigner) TryProcessOutTx( + _ *crosschaintypes.CrossChainTx, + _ *outtxprocessor.Processor, + _ string, + _ interfaces.ChainClient, + _ interfaces.ZetaCoreBridger, + _ uint64, +) { +} + +func (s *BTCSigner) SetZetaConnectorAddress(_ ethcommon.Address) { +} + +func (s *BTCSigner) SetERC20CustodyAddress(_ ethcommon.Address) { +} + +func (s *BTCSigner) GetZetaConnectorAddress() ethcommon.Address { + return ethcommon.Address{} +} + +func (s *BTCSigner) GetERC20CustodyAddress() ethcommon.Address { + return ethcommon.Address{} +} diff --git a/zetaclient/zetacore_observer.go b/zetaclient/zetacore_observer.go index 429c209287..1307a2bad0 100644 --- a/zetaclient/zetacore_observer.go +++ b/zetaclient/zetacore_observer.go @@ -5,8 +5,10 @@ import ( "math" "time" + ethcommon "github.com/ethereum/go-ethereum/common" appcontext "github.com/zeta-chain/zetacore/zetaclient/app_context" "github.com/zeta-chain/zetacore/zetaclient/bitcoin" + corecontext "github.com/zeta-chain/zetacore/zetaclient/core_context" "github.com/zeta-chain/zetacore/zetaclient/interfaces" "github.com/zeta-chain/zetacore/zetaclient/outtxprocessor" @@ -32,8 +34,8 @@ type ZetaCoreLog struct { // CoreObserver wraps the zetabridge bridge and adds the client and signer maps to it . This is the high level object used for CCTX interactions type CoreObserver struct { bridge interfaces.ZetaCoreBridger - signerMap map[common.Chain]interfaces.ChainSigner - clientMap map[common.Chain]interfaces.ChainClient + signerMap map[int64]interfaces.ChainSigner + clientMap map[int64]interfaces.ChainClient logger ZetaCoreLog ts *metrics.TelemetryServer stop chan struct{} @@ -43,8 +45,8 @@ type CoreObserver struct { // NewCoreObserver creates a new CoreObserver func NewCoreObserver( bridge interfaces.ZetaCoreBridger, - signerMap map[common.Chain]interfaces.ChainSigner, - clientMap map[common.Chain]interfaces.ChainClient, + signerMap map[int64]interfaces.ChainSigner, + clientMap map[int64]interfaces.ChainClient, logger zerolog.Logger, ts *metrics.TelemetryServer, ) *CoreObserver { @@ -135,23 +137,29 @@ func (co *CoreObserver) startCctxScheduler(appContext *appcontext.AppContext) { metrics.HotKeyBurnRate.Set(float64(co.ts.HotKeyBurnRate.GetBurnRate().Int64())) // schedule keysign for pending cctxs on each chain - supportedChains := appContext.ZetaCoreContext().GetEnabledChains() + coreContext := appContext.ZetaCoreContext() + supportedChains := coreContext.GetEnabledChains() for _, c := range supportedChains { if c.ChainId == co.bridge.ZetaChain().ChainId { continue } - signer := co.signerMap[c] - - cctxList, totalPending, err := co.bridge.ListPendingCctx(c.ChainId) + // update chain parameters for signer and chain client + signer, err := co.GetUpdatedSigner(coreContext, c.ChainId) if err != nil { - co.logger.ZetaChainWatcher.Error().Err(err).Msgf("startCctxScheduler: ListPendingCctx failed for chain %d", c.ChainId) + co.logger.ZetaChainWatcher.Error().Err(err).Msgf("startCctxScheduler: getUpdatedSigner failed for chain %d", c.ChainId) continue } - ob, err := co.getUpdatedChainOb(appContext, c.ChainId) + ob, err := co.GetUpdatedChainClient(coreContext, c.ChainId) if err != nil { co.logger.ZetaChainWatcher.Error().Err(err).Msgf("startCctxScheduler: getTargetChainOb failed for chain %d", c.ChainId) continue } + + cctxList, totalPending, err := co.bridge.ListPendingCctx(c.ChainId) + if err != nil { + co.logger.ZetaChainWatcher.Error().Err(err).Msgf("startCctxScheduler: ListPendingCctx failed for chain %d", c.ChainId) + continue + } // Set Pending transactions prometheus gauge metrics.PendingTxsPerChain.WithLabelValues(c.ChainName.String()).Set(float64(totalPending)) @@ -317,45 +325,57 @@ func (co *CoreObserver) scheduleCctxBTC( } } -func (co *CoreObserver) getUpdatedChainOb(appContext *appcontext.AppContext, chainID int64) (interfaces.ChainClient, error) { - chainOb, err := co.getTargetChainOb(chainID) - if err != nil { - return nil, err +// GetUpdatedSigner returns signer with updated chain parameters +func (co *CoreObserver) GetUpdatedSigner(coreContext *corecontext.ZetaCoreContext, chainID int64) (interfaces.ChainSigner, error) { + signer, found := co.signerMap[chainID] + if !found { + return nil, fmt.Errorf("signer not found for chainID %d", chainID) + } + // update EVM signer parameters only. BTC signer doesn't use chain parameters for now. + if common.IsEVMChain(chainID) { + evmParams, found := coreContext.GetEVMChainParams(chainID) + if found { + // update zeta connector and ERC20 custody addresses + zetaConnectorAddress := ethcommon.HexToAddress(evmParams.GetConnectorContractAddress()) + erc20CustodyAddress := ethcommon.HexToAddress(evmParams.GetErc20CustodyContractAddress()) + if zetaConnectorAddress != signer.GetZetaConnectorAddress() { + signer.SetZetaConnectorAddress(zetaConnectorAddress) + co.logger.ZetaChainWatcher.Info().Msgf( + "updated zeta connector address for chainID %d, new address: %s", chainID, zetaConnectorAddress) + } + if erc20CustodyAddress != signer.GetERC20CustodyAddress() { + signer.SetERC20CustodyAddress(erc20CustodyAddress) + co.logger.ZetaChainWatcher.Info().Msgf( + "updated ERC20 custody address for chainID %d, new address: %s", chainID, erc20CustodyAddress) + } + } + } + return signer, nil +} + +// GetUpdatedChainClient returns chain client object with updated chain parameters +func (co *CoreObserver) GetUpdatedChainClient(coreContext *corecontext.ZetaCoreContext, chainID int64) (interfaces.ChainClient, error) { + chainOb, found := co.clientMap[chainID] + if !found { + return nil, fmt.Errorf("chain client not found for chainID %d", chainID) } - // update chain client core parameters + // update chain client chain parameters curParams := chainOb.GetChainParams() if common.IsEVMChain(chainID) { - evmParams, found := appContext.ZetaCoreContext().GetEVMChainParams(chainID) + evmParams, found := coreContext.GetEVMChainParams(chainID) if found && !observertypes.ChainParamsEqual(curParams, *evmParams) { chainOb.SetChainParams(*evmParams) co.logger.ZetaChainWatcher.Info().Msgf( - "updated chain params for chainID %d, new params: %v", - chainID, - *evmParams, - ) + "updated chain params for chainID %d, new params: %v", chainID, *evmParams) } } else if common.IsBitcoinChain(chainID) { - _, btcParams, found := appContext.ZetaCoreContext().GetBTCChainParams() + _, btcParams, found := coreContext.GetBTCChainParams() if found && !observertypes.ChainParamsEqual(curParams, *btcParams) { chainOb.SetChainParams(*btcParams) co.logger.ZetaChainWatcher.Info().Msgf( - "updated chain params for Bitcoin, new params: %v", - *btcParams, - ) + "updated chain params for Bitcoin, new params: %v", *btcParams) } } return chainOb, nil } - -func (co *CoreObserver) getTargetChainOb(chainID int64) (interfaces.ChainClient, error) { - c := common.GetChainFromChainID(chainID) - if c == nil { - return nil, fmt.Errorf("chain not found for chainID %d", chainID) - } - chainOb, found := co.clientMap[*c] - if !found { - return nil, fmt.Errorf("chain client not found for chainID %d", chainID) - } - return chainOb, nil -} diff --git a/zetaclient/zetacore_observer_test.go b/zetaclient/zetacore_observer_test.go index a2340ea4a4..3dd088a4b2 100644 --- a/zetaclient/zetacore_observer_test.go +++ b/zetaclient/zetacore_observer_test.go @@ -1,147 +1,186 @@ -//go:build metacore_observer -// +build metacore_observer - package zetaclient import ( - "os" - "path/filepath" - "time" - - "github.com/zeta-chain/zetacore/zetaclient/interfaces" - "github.com/zeta-chain/zetacore/zetaclient/keys" - "github.com/zeta-chain/zetacore/zetaclient/zetabridge" - - "github.com/zeta-chain/zetacore/zetaclient/evm" + "testing" + sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/rs/zerolog/log" + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/common" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/config" - "github.com/zeta-chain/zetacore/zetaclient/types" - . "gopkg.in/check.v1" + corecontext "github.com/zeta-chain/zetacore/zetaclient/core_context" + "github.com/zeta-chain/zetacore/zetaclient/interfaces" + "github.com/zeta-chain/zetacore/zetaclient/testutils" + "github.com/zeta-chain/zetacore/zetaclient/testutils/stub" ) -type COSuite struct { - bridge1 *zetabridge.ZetaCoreBridge - bridge2 *zetabridge.ZetaCoreBridge - signer *evm.Signer - coreObserver *CoreObserver +func MockCoreObserver(t *testing.T, evmChain, btcChain common.Chain, evmChainParams, btcChainParams *observertypes.ChainParams) *CoreObserver { + // create mock signers and clients + evmSigner := stub.NewEVMSigner( + evmChain, + ethcommon.HexToAddress(evmChainParams.ConnectorContractAddress), + ethcommon.HexToAddress(evmChainParams.Erc20CustodyContractAddress), + ) + btcSigner := stub.NewBTCSigner() + evmClient := stub.NewEVMClient(evmChainParams) + btcClient := stub.NewBTCClient(btcChainParams) + + // create core observer + observer := &CoreObserver{ + signerMap: map[int64]interfaces.ChainSigner{ + evmChain.ChainId: evmSigner, + btcChain.ChainId: btcSigner, + }, + clientMap: map[int64]interfaces.ChainClient{ + evmChain.ChainId: evmClient, + btcChain.ChainId: btcClient, + }, + } + return observer } -var _ = Suite(&COSuite{}) - -const ( - TEST_SENDER = "0x566bF3b1993FFd4BA134c107A63bb2aebAcCdbA0" - TEST_RECEIVER = "0x566bF3b1993FFd4BA134c107A63bb2aebAcCdbA0" -) - -func (s *COSuite) SetUpTest(c *C) { - types.SetupConfigForTest() // setup meta-prefix - - // setup 2 metabridges - homeDir, err := os.UserHomeDir() - if err != nil { - c.Logf("UserHomeDir error") - c.Fail() +func CreateCoreContext(evmChain, btcChain common.Chain, evmChainParams, btcChainParams *observertypes.ChainParams) *corecontext.ZetaCoreContext { + // new config + cfg := config.NewConfig() + cfg.EVMChainConfigs[evmChain.ChainId] = config.EVMConfig{ + Chain: evmChain, } - c.Logf("user home dir: %s", homeDir) - chainHomeFoler := filepath.Join(homeDir, ".zetacored") - c.Logf("chain home dir: %s", chainHomeFoler) - - // first signer & zetaClient - // alice is the default user created by Starport chain serve - { - signerName := "alice" - signerPass := "password" - kb, _, err := keys.GetKeyringKeybase(chainHomeFoler, signerName, signerPass) - if err != nil { - log.Fatal().Err(err).Msg("fail to get keyring keybase") - } - - k := keys.NewKeysWithKeybase(kb, signerName, signerPass) - - chainIP := os.Getenv("CHAIN_IP") - if chainIP == "" { - chainIP = "127.0.0.1" - } - bridge, err := zetabridge.NewZetaCoreBridge(k, chainIP, "alice") - if err != nil { - c.Fail() - } - s.bridge1 = bridge + cfg.BitcoinConfig = config.BTCConfig{ + RPCHost: "localhost", } + // new core context + coreContext := corecontext.NewZetaCoreContext(cfg) + evmChainParamsMap := make(map[int64]*observertypes.ChainParams) + evmChainParamsMap[evmChain.ChainId] = evmChainParams + + // feed chain params + coreContext.Update( + &observertypes.Keygen{}, + []common.Chain{evmChain, btcChain}, + evmChainParamsMap, + btcChainParams, + "", + true, + zerolog.Logger{}, + ) + return coreContext +} - // second signer & zetaClient - // alice is the default user created by Starport chain serve - { - signerName := "bob" - signerPass := "password" - kb, _, err := keys.GetKeyringKeybase(chainHomeFoler, signerName, signerPass) - if err != nil { - log.Fatal().Err(err).Msg("fail to get keyring keybase") - } - - k := keys.NewKeysWithKeybase(kb, signerName, signerPass) - - chainIP := os.Getenv("CHAIN_IP") - if chainIP == "" { - chainIP = "127.0.0.1" - } - bridge, err := zetabridge.NewZetaCoreBridge(k, chainIP, "bob") - if err != nil { - c.Fail() - } - s.bridge2 = bridge +func Test_GetUpdatedSigner(t *testing.T) { + // initial parameters for core observer creation + evmChain := common.EthChain() + btcChain := common.BtcMainnetChain() + evmChainParams := &observertypes.ChainParams{ + ChainId: evmChain.ChainId, + ConnectorContractAddress: testutils.ConnectorAddresses[evmChain.ChainId].Hex(), + Erc20CustodyContractAddress: testutils.CustodyAddresses[evmChain.ChainId].Hex(), } + btcChainParams := &observertypes.ChainParams{} - // setup mock TSS signers: - // The following PrivKey has address 0xE80B6467863EbF8865092544f441da8fD3cF6074 - privateKey, err := crypto.HexToECDSA(config.TssTestPrivkey) - c.Assert(err, IsNil) - tss := interfaces.TestSigner{ - PrivKey: privateKey, + // new chain params in core context + evmChainParamsNew := &observertypes.ChainParams{ + ChainId: evmChain.ChainId, + ConnectorContractAddress: testutils.OtherAddress1, + Erc20CustodyContractAddress: testutils.OtherAddress2, } - metaContractAddress := ethcommon.HexToAddress(config.ETH_MPI_ADDRESS) - signer, err := evm.NewEVMSigner(common.Chain("ETH"), config.GOERLI_RPC_ENDPOINT, tss.EVMAddress(), tss, config.META_TEST_GOERLI_ABI, metaContractAddress) - c.Assert(err, IsNil) - c.Logf("TSS EVMAddress %s", tss.EVMAddress().Hex()) - c.Logf("ETH MPI EVMAddress: %s", config.ETH_MPI_ADDRESS) - - s.signer = signer - // setup zetabridge observer - co := &CoreObserver{ - bridge: s.bridge1, - signer: signer, - } - s.coreObserver = co - s.coreObserver.MonitorCore() + t.Run("signer should not be found", func(t *testing.T) { + observer := MockCoreObserver(t, evmChain, btcChain, evmChainParams, btcChainParams) + coreContext := CreateCoreContext(evmChain, btcChain, evmChainParamsNew, btcChainParams) + // BSC signer should not be found + _, err := observer.GetUpdatedSigner(coreContext, common.BscMainnetChain().ChainId) + require.ErrorContains(t, err, "signer not found") + }) + t.Run("should be able to update connector and erc20 custody address", func(t *testing.T) { + observer := MockCoreObserver(t, evmChain, btcChain, evmChainParams, btcChainParams) + coreContext := CreateCoreContext(evmChain, btcChain, evmChainParamsNew, btcChainParams) + // update signer with new connector and erc20 custody address + signer, err := observer.GetUpdatedSigner(coreContext, evmChain.ChainId) + require.NoError(t, err) + require.Equal(t, testutils.OtherAddress1, signer.GetZetaConnectorAddress().Hex()) + require.Equal(t, testutils.OtherAddress2, signer.GetERC20CustodyAddress().Hex()) + }) } -func (s *COSuite) TestSendFlow(c *C) { - b1 := s.bridge1 - b2 := s.bridge2 - metaHash, err := b1.PostVoteInbound(TEST_SENDER, "Ethereum", TEST_SENDER, TEST_RECEIVER, "BSC", "1337", "0", "treat or trick", - "0xtxhash", 123123, "0xtoken") - c.Assert(err, IsNil) - c.Logf("PostVoteInbound metaHash %s", metaHash) - - timer1 := time.NewTimer(2 * time.Second) - <-timer1.C - - metaHash, err = b2.PostVoteInbound(TEST_SENDER, "Ethereum", TEST_SENDER, TEST_RECEIVER, "BSC", "1337", "0", "treat or trick", - "0xtxhash", 123123, "0xtoken") - c.Assert(err, IsNil) - c.Logf("Second PostVoteInbound metaHash %s", metaHash) +func Test_GetUpdatedChainClient(t *testing.T) { + // initial parameters for core observer creation + evmChain := common.EthChain() + btcChain := common.BtcMainnetChain() + evmChainParams := &observertypes.ChainParams{ + ChainId: evmChain.ChainId, + ConnectorContractAddress: testutils.ConnectorAddresses[evmChain.ChainId].Hex(), + Erc20CustodyContractAddress: testutils.CustodyAddresses[evmChain.ChainId].Hex(), + } + btcChainParams := &observertypes.ChainParams{ + ChainId: btcChain.ChainId, + } - timer2 := time.NewTimer(2 * time.Second) - <-timer2.C + // new chain params in core context + evmChainParamsNew := &observertypes.ChainParams{ + ChainId: evmChain.ChainId, + ConfirmationCount: 10, + GasPriceTicker: 11, + InTxTicker: 12, + OutTxTicker: 13, + WatchUtxoTicker: 14, + ZetaTokenContractAddress: testutils.OtherAddress1, + ConnectorContractAddress: testutils.OtherAddress2, + Erc20CustodyContractAddress: testutils.OtherAddress3, + OutboundTxScheduleInterval: 15, + OutboundTxScheduleLookahead: 16, + BallotThreshold: sdk.OneDec(), + MinObserverDelegation: sdk.OneDec(), + IsSupported: true, + } + btcChainParamsNew := &observertypes.ChainParams{ + ChainId: btcChain.ChainId, + ConfirmationCount: 3, + GasPriceTicker: 300, + InTxTicker: 60, + OutTxTicker: 60, + WatchUtxoTicker: 30, + ZetaTokenContractAddress: testutils.OtherAddress1, + ConnectorContractAddress: testutils.OtherAddress2, + Erc20CustodyContractAddress: testutils.OtherAddress3, + OutboundTxScheduleInterval: 60, + OutboundTxScheduleLookahead: 200, + BallotThreshold: sdk.OneDec(), + MinObserverDelegation: sdk.OneDec(), + IsSupported: true, + } - time.Sleep(15 * time.Second) - //ch := make(chan os.Signal, 1) - //signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) - //<-ch - //c.Logf("stop signal received") + t.Run("evm chain client should not be found", func(t *testing.T) { + observer := MockCoreObserver(t, evmChain, btcChain, evmChainParams, btcChainParams) + coreContext := CreateCoreContext(evmChain, btcChain, evmChainParamsNew, btcChainParams) + // BSC chain client should not be found + _, err := observer.GetUpdatedChainClient(coreContext, common.BscMainnetChain().ChainId) + require.ErrorContains(t, err, "chain client not found") + }) + t.Run("chain params in evm chain client should be updated successfully", func(t *testing.T) { + observer := MockCoreObserver(t, evmChain, btcChain, evmChainParams, btcChainParams) + coreContext := CreateCoreContext(evmChain, btcChain, evmChainParamsNew, btcChainParams) + // update evm chain client with new chain params + chainOb, err := observer.GetUpdatedChainClient(coreContext, evmChain.ChainId) + require.NoError(t, err) + require.NotNil(t, chainOb) + require.True(t, observertypes.ChainParamsEqual(*evmChainParamsNew, chainOb.GetChainParams())) + }) + t.Run("btc chain client should not be found", func(t *testing.T) { + observer := MockCoreObserver(t, evmChain, btcChain, evmChainParams, btcChainParams) + coreContext := CreateCoreContext(btcChain, btcChain, evmChainParams, btcChainParamsNew) + // BTC testnet chain client should not be found + _, err := observer.GetUpdatedChainClient(coreContext, common.BtcTestNetChain().ChainId) + require.ErrorContains(t, err, "chain client not found") + }) + t.Run("chain params in btc chain client should be updated successfully", func(t *testing.T) { + observer := MockCoreObserver(t, evmChain, btcChain, evmChainParams, btcChainParams) + coreContext := CreateCoreContext(btcChain, btcChain, evmChainParams, btcChainParamsNew) + // update btc chain client with new chain params + chainOb, err := observer.GetUpdatedChainClient(coreContext, btcChain.ChainId) + require.NoError(t, err) + require.NotNil(t, chainOb) + require.True(t, observertypes.ChainParamsEqual(*btcChainParamsNew, chainOb.GetChainParams())) + }) }