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) +}