Skip to content

Commit

Permalink
feat: initialize cctx gateway interface (#2291)
Browse files Browse the repository at this point in the history
  • Loading branch information
skosito authored Jun 3, 2024
1 parent 8be6ce1 commit 833a253
Show file tree
Hide file tree
Showing 25 changed files with 388 additions and 219 deletions.
3 changes: 1 addition & 2 deletions app/ante/vesting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,7 @@ func TestVesting_AnteHandle(t *testing.T) {
_, err = decorator.AnteHandle(ctx, tx, false, mmd.AnteHandle)

if tt.wantHasErr {
require.Error(t, err)
require.Contains(t, err.Error(), tt.wantErr)
require.ErrorContains(t, err, tt.wantErr)
} else {
require.NoError(t, err)
}
Expand Down
9 changes: 9 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ import (

"github.com/zeta-chain/zetacore/app/ante"
"github.com/zeta-chain/zetacore/docs/openapi"
"github.com/zeta-chain/zetacore/pkg/chains"
zetamempool "github.com/zeta-chain/zetacore/pkg/mempool"
srvflags "github.com/zeta-chain/zetacore/server/flags"
authoritymodule "github.com/zeta-chain/zetacore/x/authority"
Expand Down Expand Up @@ -597,6 +598,14 @@ func New(
app.LightclientKeeper,
)

// initializing map of cctx gateways so crosschain module can decide which one to use
// based on chain info of destination chain
cctxGateways := map[chains.CCTXGateway]crosschainkeeper.CCTXGateway{
chains.CCTXGateway_observers: crosschainkeeper.NewCCTXGatewayObservers(app.CrosschainKeeper),
chains.CCTXGateway_zevm: crosschainkeeper.NewCCTXGatewayZEVM(app.CrosschainKeeper),
}
app.CrosschainKeeper.SetCCTXGateways(cctxGateways)

// initialize ibccrosschain keeper and set it to the crosschain keeper
// there is a circular dependency between the two keepers, crosschain keeper must be initialized first

Expand Down
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* [2287](https://github.com/zeta-chain/node/pull/2287) - implement `MsgUpdateChainInfo` message
* [2279](https://github.com/zeta-chain/node/pull/2279) - add a CCTXGateway field to chain static data
* [2275](https://github.com/zeta-chain/node/pull/2275) - add ChainInfo singleton state variable in authority
* [2291](https://github.com/zeta-chain/node/pull/2291) - initialize cctx gateway interface
* [2289](https://github.com/zeta-chain/node/pull/2289) - add an authorization list to keep track of all authorizations on the chain

### Refactor
Expand Down
14 changes: 7 additions & 7 deletions pkg/chains/address_taproot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func TestAddressTaproot(t *testing.T) {
// should parse mainnet taproot address
addrStr := "bc1p4ur084x8y63mj5hj7eydscuc4awals7ly749x8vhyquc0twcmvhquspa5c"
addr, err := DecodeTaprootAddress(addrStr)
require.Nil(t, err)
require.NoError(t, err)
require.Equal(t, addrStr, addr.String())
require.Equal(t, addrStr, addr.EncodeAddress())
require.True(t, addr.IsForNet(&chaincfg.MainNetParams))
Expand All @@ -22,7 +22,7 @@ func TestAddressTaproot(t *testing.T) {
// should parse testnet taproot address
addrStr := "tb1pzeclkt6upu8xwuksjcz36y4q56dd6jw5r543eu8j8238yaxpvcvq7t8f33"
addr, err := DecodeTaprootAddress(addrStr)
require.Nil(t, err)
require.NoError(t, err)
require.Equal(t, addrStr, addr.String())
require.Equal(t, addrStr, addr.EncodeAddress())
require.True(t, addr.IsForNet(&chaincfg.TestNet3Params))
Expand All @@ -31,7 +31,7 @@ func TestAddressTaproot(t *testing.T) {
// should parse regtest taproot address
addrStr := "bcrt1pqqqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0sj9hjuh"
addr, err := DecodeTaprootAddress(addrStr)
require.Nil(t, err)
require.NoError(t, err)
require.Equal(t, addrStr, addr.String())
require.Equal(t, addrStr, addr.EncodeAddress())
require.True(t, addr.IsForNet(&chaincfg.RegressionNetParams))
Expand All @@ -50,17 +50,17 @@ func TestAddressTaproot(t *testing.T) {
witnessProg[i] = byte(i)
}
_, err := newAddressTaproot("bcrt", witnessProg[:])
require.Nil(t, err)
require.NoError(t, err)
//t.Logf("addr: %v", addr)
}
{
// should create correct taproot address from given witness program
// these hex string comes from link
// https://mempool.space/tx/41f7cbaaf9a8d378d09ee86de32eebef455225520cb71015cc9a7318fb42e326
witnessProg, err := hex.DecodeString("af06f3d4c726a3b952f2f648d86398af5ddfc3df27aa531d97203987add8db2e")
require.Nil(t, err)
require.NoError(t, err)
addr, err := NewAddressTaproot(witnessProg[:], &chaincfg.MainNetParams)
require.Nil(t, err)
require.NoError(t, err)
require.Equal(t, addr.EncodeAddress(), "bc1p4ur084x8y63mj5hj7eydscuc4awals7ly749x8vhyquc0twcmvhquspa5c")
}
{
Expand All @@ -69,7 +69,7 @@ func TestAddressTaproot(t *testing.T) {
// https://blockstream.info/tx/09298a2f32f5267f419aeaf8a58c4807dcf6cac3edb59815a3b129cd8f1219b0?expand
addrStr := "bc1p6pls9gpm24g8ntl37pajpjtuhd3y08hs5rnf9a4n0wq595hwdh9suw7m2h"
addr, err := DecodeTaprootAddress(addrStr)
require.Nil(t, err)
require.NoError(t, err)
require.Equal(
t,
"d07f02a03b555079aff1f07b20c97cbb62479ef0a0e692f6b37b8142d2ee6dcb",
Expand Down
16 changes: 8 additions & 8 deletions pkg/crypto/pubkey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,15 +207,15 @@ func TestNewPubKey(t *testing.T) {
t.Run("should create new pub key from string", func(t *testing.T) {
_, pubKey, _ := testdata.KeyTestPubAddr()
spk, err := cosmos.Bech32ifyPubKey(cosmos.Bech32PubKeyTypeAccPub, pubKey)
require.Nil(t, err)
require.NoError(t, err)
pk, err := NewPubKey(spk)
require.Nil(t, err)
require.NoError(t, err)
require.Equal(t, PubKey(spk), pk)
})

t.Run("should return empty pub key from empty string", func(t *testing.T) {
pk, err := NewPubKey("")
require.Nil(t, err)
require.NoError(t, err)
require.Equal(t, EmptyPubKey, pk)
})

Expand All @@ -230,9 +230,9 @@ func TestGetAddressFromPubkeyString(t *testing.T) {
t.Run("should get address from pubkey string", func(t *testing.T) {
_, pubKey, _ := testdata.KeyTestPubAddr()
spk, err := cosmos.Bech32ifyPubKey(cosmos.Bech32PubKeyTypeAccPub, pubKey)
require.Nil(t, err)
require.NoError(t, err)
_, err = GetAddressFromPubkeyString(spk)
require.Nil(t, err)
require.NoError(t, err)
})

t.Run("should get address from nonbech32 string", func(t *testing.T) {
Expand Down Expand Up @@ -349,7 +349,7 @@ func TestGetEVMAddress(t *testing.T) {
t.Run("should return empty if pubkey is empty", func(t *testing.T) {
pubKey := PubKey("")
e, err := pubKey.GetEVMAddress()
require.Nil(t, err)
require.NoError(t, err)
require.Equal(t, chains.NoAddress, e)
})

Expand All @@ -359,13 +359,13 @@ func TestGetEVMAddress(t *testing.T) {
pk, _ := NewPubKey(spk)

_, err := pk.GetEVMAddress()
require.Nil(t, err)
require.NoError(t, err)
})

t.Run("should error if non bech32", func(t *testing.T) {
pk := PubKey("invalid")
e, err := pk.GetEVMAddress()
require.NotNil(t, err)
require.ErrorContains(t, err, "decoding bech32 failed")
require.Equal(t, chains.NoAddress, e)
})
}
7 changes: 7 additions & 0 deletions testutil/keeper/crosschain.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,13 @@ func CrosschainKeeperWithMocks(
lightclientKeeper,
)

cctxGateways := map[chains.CCTXGateway]keeper.CCTXGateway{
chains.CCTXGateway_observers: keeper.NewCCTXGatewayObservers(*k),
chains.CCTXGateway_zevm: keeper.NewCCTXGatewayZEVM(*k),
}

k.SetCCTXGateways(cctxGateways)

// initialize ibccrosschain keeper and set it to the crosschain keeper
// there is a circular dependency between the two keepers, crosschain keeper must be initialized first

Expand Down
2 changes: 1 addition & 1 deletion x/authority/types/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func TestGenesisState_Validate(t *testing.T) {
err := tt.gs.Validate()
if tt.errContains != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.errContains)
require.ErrorContains(t, err, tt.errContains)
} else {
require.NoError(t, err)
}
Expand Down
60 changes: 60 additions & 0 deletions x/crosschain/keeper/cctx_gateway_observers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package keeper

import (
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/zeta-chain/zetacore/x/crosschain/types"
)

// CCTXGatewayObservers is implementation of CCTXGateway interface for observers
type CCTXGatewayObservers struct {
crosschainKeeper Keeper
}

// NewCCTXGatewayObservers returns new instance of CCTXGatewayObservers
func NewCCTXGatewayObservers(crosschainKeeper Keeper) CCTXGatewayObservers {
return CCTXGatewayObservers{
crosschainKeeper: crosschainKeeper,
}
}

/*
InitiateOutbound updates the store so observers can use the PendingCCTX query:
- If preprocessing of outbound is successful, the CCTX status is changed to PendingOutbound.
- if preprocessing of outbound, such as paying the gas fee for the destination fails, the state is reverted to aborted
We do not return an error from this function, as all changes need to be persisted to the state.
Instead, we use a temporary context to make changes and then commit the context on for the happy path, i.e cctx is set to PendingOutbound.
New CCTX status after preprocessing is returned.
*/
func (c CCTXGatewayObservers) InitiateOutbound(
ctx sdk.Context,
cctx *types.CrossChainTx,
) (newCCTXStatus types.CctxStatus) {
tmpCtx, commit := ctx.CacheContext()
outboundReceiverChainID := cctx.GetCurrentOutboundParam().ReceiverChainId
err := func() error {
err := c.crosschainKeeper.PayGasAndUpdateCctx(
tmpCtx,
outboundReceiverChainID,
cctx,
cctx.InboundParams.Amount,
false,
)
if err != nil {
return err
}
return c.crosschainKeeper.UpdateNonce(tmpCtx, outboundReceiverChainID, cctx)
}()
if err != nil {
// do not commit anything here as the CCTX should be aborted
cctx.SetAbort(err.Error())
return types.CctxStatus_Aborted
}
commit()
cctx.SetPendingOutbound("")
return types.CctxStatus_PendingOutbound
}
100 changes: 100 additions & 0 deletions x/crosschain/keeper/cctx_gateway_zevm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package keeper

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/zeta-chain/zetacore/x/crosschain/types"
)

// CCTXGatewayZEVM is implementation of CCTXGateway interface for ZEVM
type CCTXGatewayZEVM struct {
crosschainKeeper Keeper
}

// NewCCTXGatewayZEVM returns new instance of CCTXGatewayZEVM
func NewCCTXGatewayZEVM(crosschainKeeper Keeper) CCTXGatewayZEVM {
return CCTXGatewayZEVM{
crosschainKeeper: crosschainKeeper,
}
}

/*
InitiateOutbound handles evm deposit and call ValidateOutbound.
TODO (https://github.com/zeta-chain/node/issues/2278): move remaining of this comment to ValidateOutbound once it's added.
- If the deposit is successful, the CCTX status is changed to OutboundMined.
- If the deposit returns an internal error i.e if HandleEVMDeposit() returns an error, but isContractReverted is false, the CCTX status is changed to Aborted.
- If the deposit is reverted, the function tries to create a revert cctx with status PendingRevert.
- If the creation of revert tx also fails it changes the status to Aborted.
Note : Aborted CCTXs are not refunded in this function. The refund is done using a separate refunding mechanism.
We do not return an error from this function , as all changes need to be persisted to the state.
Instead we use a temporary context to make changes and then commit the context on for the happy path ,i.e cctx is set to OutboundMined.
New CCTX status after preprocessing is returned.
*/
func (c CCTXGatewayZEVM) InitiateOutbound(ctx sdk.Context, cctx *types.CrossChainTx) (newCCTXStatus types.CctxStatus) {
tmpCtx, commit := ctx.CacheContext()
isContractReverted, err := c.crosschainKeeper.HandleEVMDeposit(tmpCtx, cctx)

// TODO (https://github.com/zeta-chain/node/issues/2278): further processing will be in validateOutbound(...), for now keeping it here
if err != nil && !isContractReverted {
// exceptional case; internal error; should abort CCTX
cctx.SetAbort(err.Error())
return types.CctxStatus_Aborted
} else if err != nil && isContractReverted {
// contract call reverted; should refund via a revert tx
revertMessage := err.Error()
senderChain := c.crosschainKeeper.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, cctx.InboundParams.SenderChainId)
if senderChain == nil {
cctx.SetAbort(fmt.Sprintf("invalid sender chain id %d", cctx.InboundParams.SenderChainId))
return types.CctxStatus_Aborted
}
gasLimit, err := c.crosschainKeeper.GetRevertGasLimit(ctx, *cctx)
if err != nil {
cctx.SetAbort(fmt.Sprintf("revert gas limit error: %s", err.Error()))
return types.CctxStatus_Aborted
}
if gasLimit == 0 {
// use same gas limit of outbound as a fallback -- should not be required
gasLimit = cctx.GetCurrentOutboundParam().GasLimit
}

err = cctx.AddRevertOutbound(gasLimit)
if err != nil {
cctx.SetAbort(fmt.Sprintf("revert outbound error: %s", err.Error()))
return types.CctxStatus_Aborted
}
// we create a new cached context, and we don't commit the previous one with EVM deposit
tmpCtxRevert, commitRevert := ctx.CacheContext()
err = func() error {
err := c.crosschainKeeper.PayGasAndUpdateCctx(
tmpCtxRevert,
senderChain.ChainId,
cctx,
cctx.InboundParams.Amount,
false,
)
if err != nil {
return err
}
// Update nonce using senderchain id as this is a revert tx and would go back to the original sender
return c.crosschainKeeper.UpdateNonce(tmpCtxRevert, senderChain.ChainId, cctx)
}()
if err != nil {
cctx.SetAbort(fmt.Sprintf("deposit revert message: %s err : %s", revertMessage, err.Error()))
return types.CctxStatus_Aborted
}
commitRevert()
cctx.SetPendingRevert(revertMessage)
return types.CctxStatus_PendingRevert
}
// successful HandleEVMDeposit;
commit()
cctx.SetOutBoundMined("Remote omnichain contract call completed")
return types.CctxStatus_OutboundMined
}
39 changes: 39 additions & 0 deletions x/crosschain/keeper/initiate_outbound.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package keeper

import (
"fmt"

cosmoserrors "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/zeta-chain/zetacore/pkg/chains"
"github.com/zeta-chain/zetacore/x/crosschain/types"
)

// InitiateOutbound initiates the outbound for the CCTX depending on the CCTX gateway.
// It does a conditional dispatch to correct CCTX gateway based on the receiver chain
// which handles the state changes and error handling.
func (k Keeper) InitiateOutbound(ctx sdk.Context, cctx *types.CrossChainTx) (types.CctxStatus, error) {
receiverChainID := cctx.GetCurrentOutboundParam().ReceiverChainId
chainInfo := chains.GetChainFromChainID(receiverChainID)
if chainInfo == nil {
return cctx.CctxStatus.Status, cosmoserrors.Wrap(
types.ErrInitiatitingOutbound,
fmt.Sprintf(
"chain info not found for %d", receiverChainID,
),
)
}

cctxGateway, ok := k.cctxGateways[chainInfo.CctxGateway]
if !ok {
return cctx.CctxStatus.Status, cosmoserrors.Wrap(
types.ErrInitiatitingOutbound,
fmt.Sprintf(
"CCTXGateway not defined for receiver chain %d", receiverChainID,
),
)
}

return cctxGateway.InitiateOutbound(ctx, cctx), nil
}
Loading

0 comments on commit 833a253

Please sign in to comment.