From 3b0431eba17657d3e13fae4078b00d6657539776 Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Mon, 5 Aug 2024 14:37:44 +0200 Subject: [PATCH 1/4] fix(`crosschain`): set sender for ERC20 whitelist admin CCTX inbound (#2631) * fix whitelist sender * add E2E test --- cmd/zetae2e/local/local.go | 1 + e2e/e2etests/e2etests.go | 7 ++ e2e/e2etests/test_whitelist_erc20.go | 107 ++++++++++++++++++ .../keeper/msg_server_whitelist_erc20.go | 2 +- 4 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 e2e/e2etests/test_whitelist_erc20.go diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 0eee41b744..8b93f2da9c 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -292,6 +292,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { if testAdmin { eg.Go(adminTestRoutine(conf, deployerRunner, verbose, + e2etests.TestWhitelistERC20Name, e2etests.TestRateLimiterName, e2etests.TestPauseZRC20Name, e2etests.TestUpdateBytecodeZRC20Name, diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index fc0f0988dc..402eed367e 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -100,6 +100,7 @@ const ( Admin tests Test admin functionalities */ + TestWhitelistERC20Name = "whitelist_erc20" TestDepositEtherLiquidityCapName = "deposit_eth_liquidity_cap" TestMigrateChainSupportName = "migrate_chain_support" TestPauseZRC20Name = "pause_zrc20" @@ -521,6 +522,12 @@ var AllE2ETests = []runner.E2ETest{ /* Admin tests */ + runner.NewE2ETest( + TestWhitelistERC20Name, + "whitelist a new ERC20 token", + []runner.ArgDefinition{}, + TestWhitelistERC20, + ), runner.NewE2ETest( TestDepositEtherLiquidityCapName, "deposit Ethers into ZEVM with a liquidity cap", diff --git a/e2e/e2etests/test_whitelist_erc20.go b/e2e/e2etests/test_whitelist_erc20.go new file mode 100644 index 0000000000..5a09decd71 --- /dev/null +++ b/e2e/e2etests/test_whitelist_erc20.go @@ -0,0 +1,107 @@ +package e2etests + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + ethcommon "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" + + "github.com/zeta-chain/zetacore/e2e/contracts/erc20" + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/txserver" + "github.com/zeta-chain/zetacore/e2e/utils" + "github.com/zeta-chain/zetacore/pkg/chains" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +// TestWhitelistERC20 tests the whitelist ERC20 functionality +func TestWhitelistERC20(r *runner.E2ERunner, _ []string) { + // Deploy a new ERC20 on the new EVM chain + r.Logger.Info("Deploying new ERC20 contract") + erc20Addr, txERC20, _, err := erc20.DeployERC20(r.EVMAuth, r.EVMClient, "NEWERC20", "NEWERC20", 6) + require.NoError(r, err) + + // wait for the ERC20 to be mined + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.EVMClient, txERC20, r.Logger, r.ReceiptTimeout) + require.Equal(r, ethtypes.ReceiptStatusSuccessful, receipt.Status) + + // ERC20 test + + // whitelist erc20 zrc20 + r.Logger.Info("whitelisting ERC20 on new network") + res, err := r.ZetaTxServer.BroadcastTx(utils.AdminPolicyName, crosschaintypes.NewMsgWhitelistERC20( + r.ZetaTxServer.MustGetAccountAddressFromName(utils.AdminPolicyName), + erc20Addr.Hex(), + chains.GoerliLocalnet.ChainId, + "NEWERC20", + "NEWERC20", + 6, + 100000, + )) + require.NoError(r, err) + + // retrieve zrc20 and cctx from event + whitelistCCTXIndex, err := txserver.FetchAttributeFromTxResponse(res, "whitelist_cctx_index") + require.NoError(r, err) + + erc20zrc20Addr, err := txserver.FetchAttributeFromTxResponse(res, "zrc20_address") + require.NoError(r, err) + + // ensure CCTX created + resCCTX, err := r.CctxClient.Cctx(r.Ctx, &crosschaintypes.QueryGetCctxRequest{Index: whitelistCCTXIndex}) + require.NoError(r, err) + + cctx := resCCTX.CrossChainTx + r.Logger.CCTX(*cctx, "whitelist_cctx") + + // wait for the whitelist cctx to be mined + r.WaitForMinedCCTXFromIndex(whitelistCCTXIndex) + + // save old ERC20 attribute to set it back after the test + oldERC20Addr := r.ERC20Addr + oldERC20 := r.ERC20 + oldERC20ZRC20Addr := r.ERC20ZRC20Addr + oldERC20ZRC20 := r.ERC20ZRC20 + defer func() { + r.ERC20Addr = oldERC20Addr + r.ERC20 = oldERC20 + r.ERC20ZRC20Addr = oldERC20ZRC20Addr + r.ERC20ZRC20 = oldERC20ZRC20 + }() + + // set erc20 and zrc20 in runner + require.True(r, ethcommon.IsHexAddress(erc20zrc20Addr), "invalid contract address: %s", erc20zrc20Addr) + erc20zrc20AddrHex := ethcommon.HexToAddress(erc20zrc20Addr) + erc20ZRC20, err := zrc20.NewZRC20(erc20zrc20AddrHex, r.ZEVMClient) + require.NoError(r, err) + r.ERC20ZRC20Addr = erc20zrc20AddrHex + r.ERC20ZRC20 = erc20ZRC20 + + erc20ERC20, err := erc20.NewERC20(erc20Addr, r.EVMClient) + require.NoError(r, err) + r.ERC20Addr = erc20Addr + r.ERC20 = erc20ERC20 + + // get balance + balance, err := r.ERC20.BalanceOf(&bind.CallOpts{}, r.Account.EVMAddress()) + require.NoError(r, err) + r.Logger.Info("ERC20 balance: %s", balance.String()) + + // run deposit and withdraw ERC20 test + txHash := r.DepositERC20WithAmountAndMessage(r.EVMAddress(), balance, []byte{}) + r.WaitForMinedCCTX(txHash) + + // approve 1 unit of the gas token to cover the gas fee + tx, err := r.ETHZRC20.Approve(r.ZEVMAuth, r.ERC20ZRC20Addr, big.NewInt(1e18)) + require.NoError(r, err) + + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + utils.RequireTxSuccessful(r, receipt) + r.Logger.Info("eth zrc20 approve receipt: status %d", receipt.Status) + + tx = r.WithdrawERC20(balance) + r.WaitForMinedCCTX(tx.Hash()) +} diff --git a/x/crosschain/keeper/msg_server_whitelist_erc20.go b/x/crosschain/keeper/msg_server_whitelist_erc20.go index 7c9fe2363d..8b6ad08b26 100644 --- a/x/crosschain/keeper/msg_server_whitelist_erc20.go +++ b/x/crosschain/keeper/msg_server_whitelist_erc20.go @@ -149,7 +149,7 @@ func (k msgServer) WhitelistERC20( LastUpdateTimestamp: 0, }, InboundParams: &types.InboundParams{ - Sender: "", + Sender: msg.Creator, SenderChainId: 0, TxOrigin: "", CoinType: coin.CoinType_Cmd, From 5dd6fd66354a6b43d036b8c3c07f5580b0c6a9ae Mon Sep 17 00:00:00 2001 From: Christopher Fuka <97121270+CryptoFewka@users.noreply.github.com> Date: Mon, 5 Aug 2024 19:27:58 -0500 Subject: [PATCH 2/4] fix(ci): Update golang cross compile to 1.22.4 (#2635) * Update golang cross compile to 1.22.4 * update deprecated --skip-validate --skip-release flags --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 5788975d79..0e86d8be76 100644 --- a/Makefile +++ b/Makefile @@ -322,7 +322,7 @@ start-upgrade-import-mainnet-test: zetanode-upgrade ############################################################################### PACKAGE_NAME := github.com/zeta-chain/node -GOLANG_CROSS_VERSION ?= v1.20.7 +GOLANG_CROSS_VERSION ?= v1.22.4 GOPATH ?= '$(HOME)/go' release-dry-run: docker run \ @@ -334,7 +334,7 @@ release-dry-run: -v ${GOPATH}/pkg:/go/pkg \ -w /go/src/$(PACKAGE_NAME) \ ghcr.io/goreleaser/goreleaser-cross:${GOLANG_CROSS_VERSION} \ - --clean --skip-validate --skip-publish --snapshot + --clean --skip=validate --skip=publish --snapshot release: @if [ ! -f ".release-env" ]; then \ @@ -350,7 +350,7 @@ release: -v `pwd`:/go/src/$(PACKAGE_NAME) \ -w /go/src/$(PACKAGE_NAME) \ ghcr.io/goreleaser/goreleaser-cross:${GOLANG_CROSS_VERSION} \ - release --clean --skip-validate + release --clean --skip=validate ############################################################################### ### Local Mainnet Development ### @@ -430,4 +430,4 @@ filter-missed-eth: install-zetatool zetatool filterdeposit eth \ --config ./tool/filter_missed_deposits/zetatool_config.json \ --evm-max-range 1000 \ - --evm-start-block 19464041 \ No newline at end of file + --evm-start-block 19464041 From eb61f2da703c21034d21b1bdbcc076cff67b74cf Mon Sep 17 00:00:00 2001 From: Charlie <31941002+CharlieMc0@users.noreply.github.com> Date: Tue, 6 Aug 2024 02:36:24 -0500 Subject: [PATCH 3/4] Added goreleaser check (#2636) --- .github/workflows/publish-release.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index ff454a4032..3e848ff66c 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -23,7 +23,7 @@ concurrency: cancel-in-progress: false jobs: - check_branch: + check-branch: if: ${{ (startsWith(github.ref, 'refs/heads/release/v') || startsWith(github.ref, 'refs/heads/hotfix/v')) }} runs-on: ubuntu-22.04 steps: @@ -31,9 +31,18 @@ jobs: run: | echo "${{ github.ref }}" + check-goreleaser: + needs: + - check-branch + runs-on: ubuntu-22.04 + steps: + - name: Branch + run: | + make release-dry-run + check-changelog: needs: - - check_branch + - check-branch runs-on: ubuntu-22.04 steps: @@ -75,7 +84,7 @@ jobs: check-upgrade-handler-updated: needs: - - check_branch + - check-branch runs-on: ubuntu-22.04 timeout-minutes: 10 steps: @@ -114,7 +123,7 @@ jobs: needs: - check-changelog - check-upgrade-handler-updated - - check_branch + - check-branch runs-on: ubuntu-22.04 timeout-minutes: 60 environment: release From c3a5428734fd3abca6e17567361df44884ad92c4 Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Tue, 6 Aug 2024 09:41:45 -0700 Subject: [PATCH 4/4] feat(zetaclient): add generic rpc metrics (#2597) * feat(zetaclient): add generic rpc metrics * feedback * changelog * fmt --- changelog.md | 1 + zetaclient/chains/evm/signer/signer.go | 8 +++- zetaclient/metrics/metrics.go | 57 ++++++++++++++++++++++++++ zetaclient/metrics/metrics_test.go | 49 +++++++++++++++++++--- zetaclient/orchestrator/bootstrap.go | 10 ++++- 5 files changed, 116 insertions(+), 9 deletions(-) diff --git a/changelog.md b/changelog.md index 822cefe360..cf8a796932 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ ### Features * [2578](https://github.com/zeta-chain/node/pull/2578) - Add Gateway address in protocol contract list +* [2597](https://github.com/zeta-chain/node/pull/2597) - Add generic rpc metrics to zetaclient ## v19.0.0 diff --git a/zetaclient/chains/evm/signer/signer.go b/zetaclient/chains/evm/signer/signer.go index 9c928face1..5ec7df55a2 100644 --- a/zetaclient/chains/evm/signer/signer.go +++ b/zetaclient/chains/evm/signer/signer.go @@ -17,6 +17,7 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" + ethrpc "github.com/ethereum/go-ethereum/rpc" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" @@ -866,11 +867,16 @@ func getEVMRPC(ctx context.Context, endpoint string) (interfaces.EVMRPCClient, e client := &mocks.MockEvmClient{} return client, ethSigner, nil } + httpClient, err := metrics.GetInstrumentedHTTPClient(endpoint) + if err != nil { + return nil, nil, errors.Wrap(err, "unable to get instrumented HTTP client") + } - client, err := ethclient.Dial(endpoint) + rpcClient, err := ethrpc.DialHTTPWithClient(endpoint, httpClient) if err != nil { return nil, nil, errors.Wrapf(err, "unable to dial EVM client (endpoint %q)", endpoint) } + client := ethclient.NewClient(rpcClient) chainID, err := client.ChainID(ctx) if err != nil { diff --git a/zetaclient/metrics/metrics.go b/zetaclient/metrics/metrics.go index 50d88b398b..df956daa90 100644 --- a/zetaclient/metrics/metrics.go +++ b/zetaclient/metrics/metrics.go @@ -4,6 +4,7 @@ package metrics import ( "context" "net/http" + "net/url" "time" "github.com/prometheus/client_golang/prometheus" @@ -112,6 +113,34 @@ var ( Help: "Histogram of the TSS keysign latency", Buckets: []float64{1, 7, 15, 30, 60, 120, 240}, }, []string{"result"}) + + // RPCInProgress is a gauge that contains the number of RPCs requests in progress + RPCInProgress = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: ZetaClientNamespace, + Name: "rpc_in_progress", + Help: "Number of RPC requests in progress", + }, []string{"host"}) + + // RPCCount is a counter that contains the number of total RPC requests + RPCCount = promauto.NewCounterVec( + prometheus.CounterOpts{ + Namespace: ZetaClientNamespace, + Name: "rpc_count", + Help: "A counter for number of total RPC requests", + }, + []string{"host", "code"}, + ) + + // RPCLatency is a histogram of the RPC latency + RPCLatency = promauto.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: ZetaClientNamespace, + Name: "rpc_duration_seconds", + Help: "A histogram of the RPC duration in seconds", + Buckets: prometheus.DefBuckets, + }, + []string{"host"}, + ) ) // NewMetrics creates a new Metrics instance @@ -151,3 +180,31 @@ func (m *Metrics) Stop() error { defer cancel() return m.s.Shutdown(ctx) } + +// GetInstrumentedHTTPClient sets up a http client that emits prometheus metrics +func GetInstrumentedHTTPClient(endpoint string) (*http.Client, error) { + host := endpoint + // try to parse as url (so that we do not expose auth uuid in metrics) + endpointURL, err := url.Parse(endpoint) + if err == nil { + host = endpointURL.Host + } + labels := prometheus.Labels{"host": host} + rpcCounterMetric, err := RPCCount.CurryWith(labels) + if err != nil { + return nil, err + } + rpcLatencyMetric, err := RPCLatency.CurryWith(labels) + if err != nil { + return nil, err + } + + transport := http.DefaultTransport + transport = promhttp.InstrumentRoundTripperDuration(rpcLatencyMetric, transport) + transport = promhttp.InstrumentRoundTripperCounter(rpcCounterMetric, transport) + transport = promhttp.InstrumentRoundTripperInFlight(RPCInProgress.With(labels), transport) + + return &http.Client{ + Transport: transport, + }, nil +} diff --git a/zetaclient/metrics/metrics_test.go b/zetaclient/metrics/metrics_test.go index b73bf00530..6be8bc30c0 100644 --- a/zetaclient/metrics/metrics_test.go +++ b/zetaclient/metrics/metrics_test.go @@ -1,10 +1,15 @@ package metrics import ( + "fmt" + "io" "net/http" + "strings" "testing" "time" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" . "gopkg.in/check.v1" ) @@ -23,20 +28,52 @@ func (ms *MetricsSuite) SetUpSuite(c *C) { ms.m = m } +// assert that the curried metric actually uses the same underlying storage +func (ms *MetricsSuite) TestCurryWith(c *C) { + rpcTotalsC := RPCCount.MustCurryWith(prometheus.Labels{"host": "test"}) + rpcTotalsC.With(prometheus.Labels{"code": "400"}).Add(1.0) + + rpcCtr := testutil.ToFloat64(RPCCount.With(prometheus.Labels{"host": "test", "code": "400"})) + c.Assert(rpcCtr, Equals, 1.0) + + RPCCount.Reset() +} + func (ms *MetricsSuite) TestMetrics(c *C) { GetFilterLogsPerChain.WithLabelValues("chain1").Inc() GetFilterLogsPerChain.WithLabelValues("chain2").Inc() GetFilterLogsPerChain.WithLabelValues("chain2").Inc() time.Sleep(1 * time.Second) - res, err := http.Get("http://127.0.0.1:8886/metrics") + + chain1Ctr := testutil.ToFloat64(GetFilterLogsPerChain.WithLabelValues("chain1")) + c.Assert(chain1Ctr, Equals, 1.0) + + httpClient, err := GetInstrumentedHTTPClient("http://127.0.0.1:8886/myauthuuid") c.Assert(err, IsNil) - c.Assert(res.StatusCode, Equals, http.StatusOK) - defer res.Body.Close() - //out, err := ioutil.ReadAll(res.Body) - //fmt.Println(string(out)) - res, err = http.Get("http://127.0.0.1:8886") + res, err := httpClient.Get("http://127.0.0.1:8886") c.Assert(err, IsNil) + defer res.Body.Close() c.Assert(res.StatusCode, Equals, http.StatusOK) + + res, err = httpClient.Get("http://127.0.0.1:8886/metrics") + c.Assert(err, IsNil) defer res.Body.Close() + c.Assert(res.StatusCode, Equals, http.StatusOK) + body, err := io.ReadAll(res.Body) + c.Assert(err, IsNil) + metricsBody := string(body) + c.Assert(strings.Contains(metricsBody, fmt.Sprintf("%s_%s", ZetaClientNamespace, "rpc_count")), Equals, true) + + // assert that rpc count is being incremented at all + rpcCount := testutil.ToFloat64(RPCCount) + c.Assert(rpcCount, Equals, 2.0) + + // assert that rpc count is being incremented correctly + rpcCount = testutil.ToFloat64(RPCCount.With(prometheus.Labels{"host": "127.0.0.1:8886", "code": "200"})) + c.Assert(rpcCount, Equals, 2.0) + + // assert that rpc count is not being incremented incorrectly + rpcCount = testutil.ToFloat64(RPCCount.With(prometheus.Labels{"host": "127.0.0.1:8886", "code": "502"})) + c.Assert(rpcCount, Equals, 0.0) } diff --git a/zetaclient/orchestrator/bootstrap.go b/zetaclient/orchestrator/bootstrap.go index cd4d2a223c..ef4920f8b5 100644 --- a/zetaclient/orchestrator/bootstrap.go +++ b/zetaclient/orchestrator/bootstrap.go @@ -5,6 +5,7 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" + ethrpc "github.com/ethereum/go-ethereum/rpc" solrpc "github.com/gagliardetto/solana-go/rpc" "github.com/pkg/errors" @@ -279,12 +280,17 @@ func syncObserverMap( continue } - // create EVM client - evmClient, err := ethclient.DialContext(ctx, cfg.Endpoint) + httpClient, err := metrics.GetInstrumentedHTTPClient(cfg.Endpoint) + if err != nil { + logger.Std.Error().Err(err).Str("rpc.endpoint", cfg.Endpoint).Msgf("Unable to create HTTP client") + continue + } + rpcClient, err := ethrpc.DialHTTPWithClient(cfg.Endpoint, httpClient) if err != nil { logger.Std.Error().Err(err).Str("rpc.endpoint", cfg.Endpoint).Msgf("Unable to dial EVM RPC") continue } + evmClient := ethclient.NewClient(rpcClient) database, err := db.NewFromSqlite(dbpath, chainName, true) if err != nil {