From 85d508a89a5bdd8a28f3561f246cf2c552b6e7da Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Wed, 20 Nov 2024 13:09:33 +0100 Subject: [PATCH 1/2] fix: support tracker processing for v2 inbounds (#3179) * add tracker processing for v2 inbound * fmt * add inbound tracker e2e test * successful test * changelgo * add comment to PR * fix tests * add back ton sidecar config * comments --- changelog.md | 4 + cmd/zetae2e/config/local.yml | 4 +- cmd/zetae2e/run.go | 26 ++++++ e2e/e2etests/e2etests.go | 13 ++- e2e/e2etests/test_inbound_trackers.go | 87 ++++++++++++++++++ e2e/runner/zeta.go | 18 ++++ zetaclient/chains/base/observer.go | 2 +- zetaclient/chains/evm/observer/inbound.go | 52 +++++++---- .../chains/evm/observer/v2_inbound_tracker.go | 88 +++++++++++++++++++ 9 files changed, 272 insertions(+), 22 deletions(-) create mode 100644 e2e/e2etests/test_inbound_trackers.go create mode 100644 zetaclient/chains/evm/observer/v2_inbound_tracker.go diff --git a/changelog.md b/changelog.md index 184790d68f..2d1b39ead5 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,7 @@ ## Unreleased ### Features + * [2984](https://github.com/zeta-chain/node/pull/2984) - add Whitelist message ability to whitelist SPL tokens on Solana * [3091](https://github.com/zeta-chain/node/pull/3091) - improve build reproducability. `make release{,-build-only}` checksums should now be stable. * [3124](https://github.com/zeta-chain/node/pull/3124) - integrate SPL deposits @@ -15,6 +16,7 @@ * [3154](https://github.com/zeta-chain/node/pull/3154) - configure Solana gateway program id for E2E tests ### Refactor + * [3118](https://github.com/zeta-chain/node/pull/3118) - zetaclient: remove hsm signer * [3122](https://github.com/zeta-chain/node/pull/3122) - improve & refactor zetaclientd cli * [3125](https://github.com/zeta-chain/node/pull/3125) - drop support for header proofs @@ -22,6 +24,7 @@ * [3137](https://github.com/zeta-chain/node/pull/3137) - remove chain.Chain from zetaclientd config ### Fixes + * [3117](https://github.com/zeta-chain/node/pull/3117) - register messages for emissions module to legacy amino codec. * [3041](https://github.com/zeta-chain/node/pull/3041) - replace libp2p public DHT with private gossip peer discovery and connection gater for inbound connections * [3106](https://github.com/zeta-chain/node/pull/3106) - prevent blocked CCTX on out of gas during omnichain calls @@ -29,6 +32,7 @@ * [3149](https://github.com/zeta-chain/node/pull/3149) - abort the cctx if dust amount is detected in the revert outbound * [3155](https://github.com/zeta-chain/node/pull/3155) - fix potential panic in the Bitcoin inscription parsing * [3162](https://github.com/zeta-chain/node/pull/3162) - skip depositor fee calculation if transaction does not involve TSS address +* [3179](https://github.com/zeta-chain/node/pull/3179) - support inbound trackers for v2 cctx ## v21.0.0 diff --git a/cmd/zetae2e/config/local.yml b/cmd/zetae2e/config/local.yml index e4ec147e49..aed44fc155 100644 --- a/cmd/zetae2e/config/local.yml +++ b/cmd/zetae2e/config/local.yml @@ -110,13 +110,15 @@ contracts: wzeta: "0x5F0b1a82749cb4E2278EC87F8BF6B618dC71a8bf" test_dapp: "0xA8D5060feb6B456e886F023709A2795373691E63" gateway: "0xa825eAa55b497AF892faca73a3797046C10B7c23" + test_dapp_v2: "0xBFF76e77D56B3C1202107f059425D56f0AEF87Ed" evm: zeta_eth: "0x733aB8b06DDDEf27Eaa72294B0d7c9cEF7f12db9" connector_eth: "0xD28D6A0b8189305551a0A8bd247a6ECa9CE781Ca" - custody: "0xff3135df4F2775f4091b81f4c7B6359CfA07862a" + custody: "0x784aE8B474aebabB74526701146a708734f6931c" erc20: "0xbD1e64A22B9F92D9Ce81aA9B4b0fFacd80215564" test_dapp: "0xBFF76e77D56B3C1202107f059425D56f0AEF87Ed" gateway: "0xF0deebCB0E9C829519C4baa794c5445171973826" + test_dapp_v2: "0xa825eAa55b497AF892faca73a3797046C10B7c23" solana: gateway_program_id: "94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d" spl: "" \ No newline at end of file diff --git a/cmd/zetae2e/run.go b/cmd/zetae2e/run.go index fe52f056d5..91116f234c 100644 --- a/cmd/zetae2e/run.go +++ b/cmd/zetae2e/run.go @@ -16,6 +16,8 @@ import ( "github.com/zeta-chain/node/e2e/config" "github.com/zeta-chain/node/e2e/e2etests" "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/txserver" + "github.com/zeta-chain/node/e2e/utils" fungibletypes "github.com/zeta-chain/node/x/fungible/types" observertypes "github.com/zeta-chain/node/x/observer/types" ) @@ -104,6 +106,29 @@ func runE2ETest(cmd *cobra.Command, args []string) error { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + var runnerOpts []runner.E2ERunnerOption + + // if keys are defined for all policy accounts, we initialize a ZETA tx server allowing to send admin actions + emergencyKey := conf.PolicyAccounts.EmergencyPolicyAccount.RawPrivateKey.String() + operationalKey := conf.PolicyAccounts.OperationalPolicyAccount.RawPrivateKey.String() + adminKey := conf.PolicyAccounts.AdminPolicyAccount.RawPrivateKey.String() + if emergencyKey != "" && operationalKey != "" && adminKey != "" { + zetaTxServer, err := txserver.NewZetaTxServer( + conf.RPCs.ZetaCoreRPC, + []string{utils.EmergencyPolicyName, utils.OperationalPolicyName, utils.AdminPolicyName}, + []string{ + emergencyKey, + operationalKey, + adminKey, + }, + conf.ZetaChainID, + ) + if err != nil { + return err + } + runnerOpts = append(runnerOpts, runner.WithZetaTxServer(zetaTxServer)) + } + // initialize deployer runner with config testRunner, err := zetae2econfig.RunnerFromConfig( ctx, @@ -112,6 +137,7 @@ func runE2ETest(cmd *cobra.Command, args []string) error { conf, conf.DefaultAccount, logger, + runnerOpts..., ) if err != nil { return err diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 52c7022be5..53c145372d 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -110,9 +110,10 @@ const ( Miscellaneous tests Test various functionalities not related to assets */ - TestContextUpgradeName = "context_upgrade" - TestMyTestName = "my_test" - TestDonationEtherName = "donation_ether" + TestContextUpgradeName = "context_upgrade" + TestMyTestName = "my_test" + TestDonationEtherName = "donation_ether" + TestInboundTrackersName = "inbound_trackers" /* Stress tests @@ -737,6 +738,12 @@ var AllE2ETests = []runner.E2ETest{ }, TestDonationEther, ), + runner.NewE2ETest( + TestInboundTrackersName, + "test processing inbound trackers for observation", + []runner.ArgDefinition{}, + TestInboundTrackers, + ), /* Stress tests */ diff --git a/e2e/e2etests/test_inbound_trackers.go b/e2e/e2etests/test_inbound_trackers.go new file mode 100644 index 0000000000..b26b505cf1 --- /dev/null +++ b/e2e/e2etests/test_inbound_trackers.go @@ -0,0 +1,87 @@ +package e2etests + +import ( + "math/big" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + + "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/utils" + "github.com/zeta-chain/node/pkg/coin" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" +) + +// TestInboundTrackers tests inbound trackers processing in ZetaClient +// It run deposits, send inbound trackers and check cctxs are mined +// IMPORTANT: the test requires inbound observation to be disabled, the following line should be uncommented: +// https://github.com/zeta-chain/node/blob/9dcb42729653e033f5ba60a77dc37e5e19b092ad/zetaclient/chains/evm/observer/inbound.go#L210 +func TestInboundTrackers(r *runner.E2ERunner, args []string) { + require.Len(r, args, 0) + + amount := big.NewInt(1e17) + + addTrackerAndWaitForCCTX := func(coinType coin.CoinType, txHash string) { + r.AddInboundTracker(coinType, txHash) + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, txHash, r.CctxClient, r.Logger, r.CctxTimeout) + require.EqualValues(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) + r.Logger.CCTX(*cctx, "cctx") + } + + // send v1 eth deposit + r.Logger.Print("🏃test v1 eth deposit") + txHash := r.DepositEtherWithAmount(amount) + addTrackerAndWaitForCCTX(coin.CoinType_Gas, txHash.Hex()) + r.Logger.Print("🍾v1 eth deposit observed") + + // send v1 erc20 deposit + r.Logger.Print("🏃test v1 erc20 deposit") + txHash = r.DepositERC20WithAmountAndMessage(r.EVMAddress(), amount, []byte{}) + addTrackerAndWaitForCCTX(coin.CoinType_ERC20, txHash.Hex()) + r.Logger.Print("🍾v1 erc20 deposit observed") + + // send v2 eth deposit + r.Logger.Print("🏃test v2 eth deposit") + tx := r.V2ETHDeposit(r.EVMAddress(), amount, gatewayevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}) + addTrackerAndWaitForCCTX(coin.CoinType_Gas, tx.Hash().Hex()) + r.Logger.Print("🍾v2 eth deposit observed") + + // send v2 eth deposit and call + r.Logger.Print("🏃test v2 eth eposit and call") + tx = r.V2ETHDepositAndCall( + r.TestDAppV2ZEVMAddr, + amount, + []byte(randomPayload(r)), + gatewayevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}, + ) + addTrackerAndWaitForCCTX(coin.CoinType_Gas, tx.Hash().Hex()) + r.Logger.Print("🍾v2 eth deposit and call observed") + + // send v2 erc20 deposit + r.Logger.Print("🏃test v2 erc20 deposit") + r.ApproveERC20OnEVM(r.GatewayEVMAddr) + tx = r.V2ERC20Deposit(r.EVMAddress(), amount, gatewayevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}) + addTrackerAndWaitForCCTX(coin.CoinType_Gas, tx.Hash().Hex()) + r.Logger.Print("🍾v2 erc20 deposit observed") + + // send v2 erc20 deposit and call + r.Logger.Print("🏃test v2 erc20 deposit and call") + tx = r.V2ERC20DepositAndCall( + r.TestDAppV2ZEVMAddr, + amount, + []byte(randomPayload(r)), + gatewayevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}, + ) + addTrackerAndWaitForCCTX(coin.CoinType_Gas, tx.Hash().Hex()) + r.Logger.Print("🍾v2 erc20 deposit and call observed") + + // send v2 call + r.Logger.Print("🏃test v2 call") + tx = r.V2EVMToZEMVCall( + r.TestDAppV2ZEVMAddr, + []byte(randomPayload(r)), + gatewayevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}, + ) + addTrackerAndWaitForCCTX(coin.CoinType_NoAssetCall, tx.Hash().Hex()) + r.Logger.Print("🍾v2 call observed") +} diff --git a/e2e/runner/zeta.go b/e2e/runner/zeta.go index d59ef85645..fe55c3946c 100644 --- a/e2e/runner/zeta.go +++ b/e2e/runner/zeta.go @@ -15,6 +15,7 @@ import ( "github.com/zeta-chain/node/e2e/utils" "github.com/zeta-chain/node/pkg/chains" + "github.com/zeta-chain/node/pkg/coin" "github.com/zeta-chain/node/pkg/retry" "github.com/zeta-chain/node/x/crosschain/types" observertypes "github.com/zeta-chain/node/x/observer/types" @@ -332,3 +333,20 @@ func (r *E2ERunner) skipChainOperations(chainID int64) bool { return skip } + +// AddInboundTracker adds an inbound tracker from the tx hash +func (r *E2ERunner) AddInboundTracker(coinType coin.CoinType, txHash string) { + require.NotNil(r, r.ZetaTxServer) + + chainID, err := r.EVMClient.ChainID(r.Ctx) + require.NoError(r, err) + + msg := types.NewMsgAddInboundTracker( + r.ZetaTxServer.MustGetAccountAddressFromName(utils.EmergencyPolicyName), + chainID.Int64(), + coinType, + txHash, + ) + _, err = r.ZetaTxServer.BroadcastTx(utils.EmergencyPolicyName, msg) + require.NoError(r, err) +} diff --git a/zetaclient/chains/base/observer.go b/zetaclient/chains/base/observer.go index 67f38e627a..9056e3aa1d 100644 --- a/zetaclient/chains/base/observer.go +++ b/zetaclient/chains/base/observer.go @@ -211,7 +211,7 @@ func (ob *Observer) WithZetacoreClient(client interfaces.ZetacoreClient) *Observ return ob } -// Tss returns the tss signer for the observer. +// TSS returns the tss signer for the observer. func (ob *Observer) TSS() interfaces.TSSSigner { return ob.tss } diff --git a/zetaclient/chains/evm/observer/inbound.go b/zetaclient/chains/evm/observer/inbound.go index cb409f408e..d7632ac192 100644 --- a/zetaclient/chains/evm/observer/inbound.go +++ b/zetaclient/chains/evm/observer/inbound.go @@ -121,6 +121,7 @@ func (ob *Observer) ProcessInboundTrackers(ctx context.Context) error { if err != nil { return err } + for _, tracker := range trackers { // query tx and receipt tx, _, err := ob.TransactionByHash(tracker.TxHash) @@ -144,24 +145,35 @@ func (ob *Observer) ProcessInboundTrackers(ctx context.Context) error { } ob.Logger().Inbound.Info().Msgf("checking tracker for inbound %s chain %d", tracker.TxHash, ob.Chain().ChainId) - // check and vote on inbound tx - switch tracker.CoinType { - case coin.CoinType_Zeta: - _, err = ob.CheckAndVoteInboundTokenZeta(ctx, tx, receipt, true) - case coin.CoinType_ERC20: - _, err = ob.CheckAndVoteInboundTokenERC20(ctx, tx, receipt, true) - case coin.CoinType_Gas: - _, err = ob.CheckAndVoteInboundTokenGas(ctx, tx, receipt, true) - default: - return fmt.Errorf( - "unknown coin type %s for inbound %s chain %d", - tracker.CoinType, - tx.Hash, - ob.Chain().ChainId, - ) - } + // if the transaction is sent to the gateway, this is a v2 inbound + gatewayAddr, gateway, err := ob.GetGatewayContract() if err != nil { - return errors.Wrapf(err, "error checking and voting for inbound %s chain %d", tx.Hash, ob.Chain().ChainId) + ob.Logger().Inbound.Debug().Err(err).Msg("error getting gateway contract for processing inbound tracker") + } + if err == nil && tx != nil && ethcommon.HexToAddress(tx.To) == gatewayAddr { + if err := ob.ProcessInboundTrackerV2(ctx, gateway, tx, receipt); err != nil { + return err + } + } else { + // check and vote on inbound tx + switch tracker.CoinType { + case coin.CoinType_Zeta: + _, err = ob.CheckAndVoteInboundTokenZeta(ctx, tx, receipt, true) + case coin.CoinType_ERC20: + _, err = ob.CheckAndVoteInboundTokenERC20(ctx, tx, receipt, true) + case coin.CoinType_Gas: + _, err = ob.CheckAndVoteInboundTokenGas(ctx, tx, receipt, true) + default: + return fmt.Errorf( + "unknown coin type %s for inbound %s chain %d", + tracker.CoinType, + tx.Hash, + ob.Chain().ChainId, + ) + } + if err != nil { + return errors.Wrapf(err, "error checking and voting for inbound %s chain %d", tx.Hash, ob.Chain().ChainId) + } } } return nil @@ -186,6 +198,12 @@ func (ob *Observer) ObserveInbound(ctx context.Context, sampledLogger zerolog.Lo // increment prom counter metrics.GetBlockByNumberPerChain.WithLabelValues(ob.Chain().Name).Inc() + // uncomment this line to stop observing inbound and test observation with inbound trackers + // https://github.com/zeta-chain/node/blob/3879b5ef8b418542c82a4383263604222f0605c6/e2e/e2etests/test_inbound_trackers.go#L19 + // TODO: implement a better way to disable inbound observation + // https://github.com/zeta-chain/node/issues/3186 + //return nil + // skip if current height is too low if blockNumber < ob.ChainParams().ConfirmationCount { return fmt.Errorf("observeInbound: skipping observer, current block number %d is too low", blockNumber) diff --git a/zetaclient/chains/evm/observer/v2_inbound_tracker.go b/zetaclient/chains/evm/observer/v2_inbound_tracker.go new file mode 100644 index 0000000000..11f39fe2a5 --- /dev/null +++ b/zetaclient/chains/evm/observer/v2_inbound_tracker.go @@ -0,0 +1,88 @@ +package observer + +import ( + "context" + "fmt" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/onrik/ethrpc" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + + "github.com/zeta-chain/node/zetaclient/zetacore" +) + +// ProcessInboundTrackerV2 processes inbound tracker events from the gateway +func (ob *Observer) ProcessInboundTrackerV2( + ctx context.Context, + gateway *gatewayevm.GatewayEVM, + tx *ethrpc.Transaction, + receipt *ethtypes.Receipt, +) error { + // check confirmations + if confirmed := ob.HasEnoughConfirmations(receipt, ob.LastBlock()); !confirmed { + return fmt.Errorf( + "inbound %s has not been confirmed yet: receipt block %d", + tx.Hash, + receipt.BlockNumber.Uint64(), + ) + } + + for _, log := range receipt.Logs { + if log == nil { + continue + } + + // try parsing deposit + eventDeposit, err := gateway.ParseDeposited(*log) + if err == nil { + // check if the event is processable + if !ob.checkEventProcessability( + eventDeposit.Sender, + eventDeposit.Receiver, + eventDeposit.Raw.TxHash, + eventDeposit.Payload, + ) { + return fmt.Errorf("event from inbound tracker %s is not processable", tx.Hash) + } + msg := ob.newDepositInboundVote(eventDeposit) + _, err = ob.PostVoteInbound(ctx, &msg, zetacore.PostVoteInboundExecutionGasLimit) + return err + } + + // try parsing deposit and call + eventDepositAndCall, err := gateway.ParseDepositedAndCalled(*log) + if err == nil { + // check if the event is processable + if !ob.checkEventProcessability( + eventDepositAndCall.Sender, + eventDepositAndCall.Receiver, + eventDepositAndCall.Raw.TxHash, + eventDepositAndCall.Payload, + ) { + return fmt.Errorf("event from inbound tracker %s is not processable", tx.Hash) + } + msg := ob.newDepositAndCallInboundVote(eventDepositAndCall) + _, err = ob.PostVoteInbound(ctx, &msg, zetacore.PostVoteInboundExecutionGasLimit) + return err + } + + // try parsing call + eventCall, err := gateway.ParseCalled(*log) + if err == nil { + // check if the event is processable + if !ob.checkEventProcessability( + eventCall.Sender, + eventCall.Receiver, + eventCall.Raw.TxHash, + eventCall.Payload, + ) { + return fmt.Errorf("event from inbound tracker %s is not processable", tx.Hash) + } + msg := ob.newCallInboundVote(eventCall) + _, err = ob.PostVoteInbound(ctx, &msg, zetacore.PostVoteInboundExecutionGasLimit) + return err + } + } + + return fmt.Errorf("no gateway event found in inbound tracker %s", tx.Hash) +} From f5e5adb7b72a8b38b18b07f0c642c16f21fbfa57 Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Wed, 20 Nov 2024 09:43:24 -0800 Subject: [PATCH 2/2] feat(zetaclient): enable pprof http server (#3182) * feat(zetaclient): enable pprof http server * changelog * fmt --- changelog.md | 1 + cmd/zetaclientd/start.go | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/changelog.md b/changelog.md index 2d1b39ead5..378991ce1a 100644 --- a/changelog.md +++ b/changelog.md @@ -8,6 +8,7 @@ * [3091](https://github.com/zeta-chain/node/pull/3091) - improve build reproducability. `make release{,-build-only}` checksums should now be stable. * [3124](https://github.com/zeta-chain/node/pull/3124) - integrate SPL deposits * [3134](https://github.com/zeta-chain/node/pull/3134) - integrate SPL tokens withdraw to Solana +* [3182](https://github.com/zeta-chain/node/pull/3182) - enable zetaclient pprof server on port 6061 ### Tests diff --git a/cmd/zetaclientd/start.go b/cmd/zetaclientd/start.go index b9722c9213..0ac52f0690 100644 --- a/cmd/zetaclientd/start.go +++ b/cmd/zetaclientd/start.go @@ -5,6 +5,8 @@ import ( "encoding/json" "fmt" "io" + "net/http" + _ "net/http/pprof" // #nosec G108 -- pprof enablement is intentional "os" "os/signal" "path/filepath" @@ -256,6 +258,15 @@ func Start(_ *cobra.Command, _ []string) error { time.Sleep(30 * time.Second) } }() + // pprof http server + // zetacored/cometbft is already listening for pprof on 6060 (by default) + go func() { + // #nosec G114 -- timeouts uneeded + err := http.ListenAndServe("localhost:6061", nil) + if err != nil { + log.Error().Err(err).Msg("pprof http server error") + } + }() // Generate a new TSS if keygen is set and add it into the tss server // If TSS has already been generated, and keygen was successful ; we use the existing TSS