Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: cctx validate outbound #2317

Merged
merged 34 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4abefdb
move code to validate outbound methods
skosito Jun 4, 2024
28aa31e
refactor
skosito Jun 4, 2024
e6609e5
renaming and moving code around
skosito Jun 5, 2024
2e826a7
fixes
skosito Jun 5, 2024
61a3594
Merge branch 'develop' into cctx-validate-outbound
skosito Jun 5, 2024
6cc517e
refactoring
skosito Jun 5, 2024
e2d3c8b
changelog
skosito Jun 5, 2024
5f91691
make generate
skosito Jun 5, 2024
6cb69cd
fix initiate outbound tests
skosito Jun 5, 2024
47b7b88
fix validate outbound tests
skosito Jun 5, 2024
08dfea1
fix tests
skosito Jun 5, 2024
0bba576
fix e2e test
skosito Jun 5, 2024
20c775e
fix unit tests
skosito Jun 5, 2024
1f153e8
make generate
skosito Jun 5, 2024
455bce5
Merge branch 'develop' into cctx-validate-outbound
skosito Jun 6, 2024
0b5f4be
cleanup
skosito Jun 6, 2024
9394425
try to split initiate and validate outbound for zevm
skosito Jun 6, 2024
4288868
Merge branch 'develop' into cctx-validate-outbound
skosito Jun 6, 2024
992c55b
make generate
skosito Jun 6, 2024
bd125a8
cleanup
skosito Jun 6, 2024
8333985
cleanup
skosito Jun 6, 2024
b497cbc
e2e test fix
skosito Jun 6, 2024
d437a39
PR comment
skosito Jun 7, 2024
f856cd4
Merge branch 'develop' into cctx-validate-outbound
skosito Jun 7, 2024
c683384
fix typo after merge
skosito Jun 7, 2024
a11d1b2
Merge branch 'develop' into cctx-validate-outbound
skosito Jun 10, 2024
16f858f
renaming
skosito Jun 10, 2024
e7cedab
Merge branch 'develop' into cctx-validate-outbound
skosito Jun 12, 2024
dd56896
PR comments
skosito Jun 13, 2024
b823a45
fix changelog
skosito Jun 17, 2024
4ebe5e8
Merge branch 'develop' into cctx-validate-outbound
skosito Jun 19, 2024
268987e
PR comment
skosito Jun 19, 2024
a923566
Merge branch 'develop' into cctx-validate-outbound
skosito Jun 19, 2024
3a7cf12
Merge branch 'develop' into cctx-validate-outbound
skosito Jun 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* [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
* [2317](https://github.com/zeta-chain/node/pull/2317) - add ValidateOutbound method for cctx orchestrator
* [2289](https://github.com/zeta-chain/node/pull/2289) - add an authorization list to keep track of all authorizations on the chain
* [2305](https://github.com/zeta-chain/node/pull/2305) - add new messages `MsgAddAuthorization` and `MsgRemoveAuthorization` that can be used to update the authorization list
* [2313](https://github.com/zeta-chain/node/pull/2313) - add `CheckAuthorization` function to replace the `IsAuthorized` function. The new function uses the authorization list to verify the signer's authorization.
Expand Down
77 changes: 8 additions & 69 deletions x/crosschain/keeper/cctx_gateway_zevm.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package keeper

import (
"fmt"

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

"github.com/zeta-chain/zetacore/x/crosschain/types"
Expand All @@ -20,81 +18,22 @@ func NewCCTXGatewayZEVM(crosschainKeeper Keeper) CCTXGatewayZEVM {
}
}

/*
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.
*/
// InitiateOutbound handles evm deposit and immediately validates pending outbound
func (c CCTXGatewayZEVM) InitiateOutbound(ctx sdk.Context, cctx *types.CrossChainTx) (newCCTXStatus types.CctxStatus) {
tmpCtx, commit := ctx.CacheContext()
lumtis marked this conversation as resolved.
Show resolved Hide resolved
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
cctx.SetPendingOutbound("")
skosito marked this conversation as resolved.
Show resolved Hide resolved
newCCTXStatus = c.crosschainKeeper.ValidateOutboundZEVM(ctx, cctx, err, isContractReverted)
if newCCTXStatus == types.CctxStatus_OutboundMined {
commit()
}
// successful HandleEVMDeposit;
commit()
cctx.SetOutBoundMined("Remote omnichain contract call completed")
return types.CctxStatus_OutboundMined

return newCCTXStatus
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"

cosmoserrors "cosmossdk.io/errors"
"cosmossdk.io/math"
tmbytes "github.com/cometbft/cometbft/libs/bytes"
tmtypes "github.com/cometbft/cometbft/types"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -17,9 +18,54 @@ import (
observertypes "github.com/zeta-chain/zetacore/x/observer/types"
)

// ProcessOutbound processes the finalization of an outbound transaction based on the ballot status
// The state is committed only if the individual steps are successful
func (k Keeper) ProcessOutbound(
/*
ValidateOutboundZEVM processes the finalization of an outbound transaction if receiver is ZEVM.
It takes deposit error and information if contract revert happened during deposit, to make a decision:

- If the deposit was successful, the CCTX status is changed to OutboundMined.

- If the deposit returned an internal 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 (k Keeper) ValidateOutboundZEVM(
ctx sdk.Context,
cctx *types.CrossChainTx,
depositErr error,
isContractReverted bool,
) (newCCTXStatus types.CctxStatus) {
if depositErr != nil && isContractReverted {
tmpCtxRevert, commitRevert := ctx.CacheContext()
// contract call reverted; should refund via a revert tx
err := k.validateFailedOutbound(
tmpCtxRevert,
cctx,
types.CctxStatus_PendingOutbound,
depositErr.Error(),
cctx.InboundParams.Amount,
)
if err != nil {
cctx.SetAbort(err.Error())
return types.CctxStatus_Aborted
}

commitRevert()
return types.CctxStatus_PendingRevert
}
k.validateSuccessfulOutbound(ctx, cctx, "", false)
return types.CctxStatus_OutboundMined
}

// ValidateOutboundObservers processes the finalization of an outbound transaction based on the ballot status.
// The state is committed only if the individual steps are successful.
func (k Keeper) ValidateOutboundObservers(
ctx sdk.Context,
cctx *types.CrossChainTx,
ballotStatus observertypes.BallotStatus,
Expand All @@ -29,9 +75,9 @@ func (k Keeper) ProcessOutbound(
err := func() error {
switch ballotStatus {
case observertypes.BallotStatus_BallotFinalized_SuccessObservation:
k.ProcessSuccessfulOutbound(tmpCtx, cctx, valueReceived)
k.validateSuccessfulOutbound(tmpCtx, cctx, valueReceived, true)
case observertypes.BallotStatus_BallotFinalized_FailureObservation:
err := k.ProcessFailedOutbound(tmpCtx, cctx, valueReceived)
err := k.validateFailedOutboundObservers(tmpCtx, cctx, valueReceived)
if err != nil {
return err
}
Expand All @@ -49,35 +95,7 @@ func (k Keeper) ProcessOutbound(
return nil
}

// ProcessSuccessfulOutbound processes a successful outbound transaction. It does the following things in one function:
//
// 1. Change the status of the CCTX from
// - PendingRevert to Reverted
// - PendingOutbound to OutboundMined
//
// 2. Set the finalization status of the current outbound tx to executed
//
// 3. Emit an event for the successful outbound transaction
//
// This function sets CCTX status , in cases where the outbound tx is successful, but tx itself fails
// This is done because SaveSuccessfulOutbound does not set the cctx status
// For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the ProcessFailedOutbound function, so we can just return and error to trigger that
func (k Keeper) ProcessSuccessfulOutbound(ctx sdk.Context, cctx *types.CrossChainTx, valueReceived string) {
oldStatus := cctx.CctxStatus.Status
switch oldStatus {
case types.CctxStatus_PendingRevert:
cctx.SetReverted("Outbound succeeded, revert executed")
case types.CctxStatus_PendingOutbound:
cctx.SetOutBoundMined("Outbound succeeded, mined")
default:
return
}
cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed
newStatus := cctx.CctxStatus.Status.String()
EmitOutboundSuccess(ctx, valueReceived, oldStatus.String(), newStatus, cctx.Index)
}

// ProcessFailedOutbound processes a failed outbound transaction. It does the following things in one function:
// validateFailedOutboundObservers processes a failed outbound transaction for observers. It does the following things in one function:
//
// 1. For Admin Tx or a withdrawal from Zeta chain, it aborts the CCTX
//
Expand All @@ -91,12 +109,12 @@ func (k Keeper) ProcessSuccessfulOutbound(ctx sdk.Context, cctx *types.CrossChai
//
// This function sets CCTX status , in cases where the outbound tx is successful, but tx itself fails
// This is done because SaveSuccessfulOutbound does not set the cctx status
// For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the ProcessFailedOutbound function, so we can just return and error to trigger that
func (k Keeper) ProcessFailedOutbound(ctx sdk.Context, cctx *types.CrossChainTx, valueReceived string) error {
// For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the validateFailedOutboundObservers function, so we can just return and error to trigger that
func (k Keeper) validateFailedOutboundObservers(ctx sdk.Context, cctx *types.CrossChainTx, valueReceived string) error {
oldStatus := cctx.CctxStatus.Status
// The following logic is used to handler the mentioned conditions separately. The reason being
// All admin tx is created using a policy message , there is no associated inbound tx , therefore we do not need any revert logic
// For transactions which originated from ZEVM , we can process the outbound in the same block as there is no TSS signing required for the revert
// All admin tx is created using a policy message, there is no associated inbound tx, therefore we do not need any revert logic
// For transactions which originated from ZEVM, we can process the outbound in the same block as there is no TSS signing required for the revert
// For all other transactions we need to create a revert tx and set the status to pending revert

if cctx.InboundParams.CoinType == coin.CoinType_Cmd {
Expand All @@ -108,9 +126,9 @@ func (k Keeper) ProcessFailedOutbound(ctx sdk.Context, cctx *types.CrossChainTx,
// Try revert if the coin-type is ZETA
case coin.CoinType_Zeta:
{
err := k.processFailedOutboundForZEVM(ctx, cctx)
err := k.validateFailedOutboundObserversForZEVM(ctx, cctx)
if err != nil {
return cosmoserrors.Wrap(err, "ProcessFailedOutboundForZEVMTx")
return cosmoserrors.Wrap(err, "validateFailedOutboundObserversForZEVMTx")
}
}
// For all other coin-types, we do not revert, the cctx is aborted
Expand All @@ -121,24 +139,30 @@ func (k Keeper) ProcessFailedOutbound(ctx sdk.Context, cctx *types.CrossChainTx,
}
}
} else {
err := k.processFailedOutboundForExternalChainTx(ctx, cctx, oldStatus)
err := k.validateFailedOutbound(ctx, cctx, oldStatus, "", cctx.GetCurrentOutboundParam().Amount)
if err != nil {
return cosmoserrors.Wrap(err, "ProcessFailedOutBoundForExternalChainTx")
return cosmoserrors.Wrap(err, "validateFailedOutbound")
}
}
newStatus := cctx.CctxStatus.Status.String()
EmitOutboundFailure(ctx, valueReceived, oldStatus.String(), newStatus, cctx.Index)
return nil
}

// processFailedOutboundForExternalChainTx processes the failed outbound transaction for external chain tx
func (k Keeper) processFailedOutboundForExternalChainTx(
// validateFailedOutbound processes the failed outbound transaction
func (k Keeper) validateFailedOutbound(
skosito marked this conversation as resolved.
Show resolved Hide resolved
ctx sdk.Context,
cctx *types.CrossChainTx,
oldStatus types.CctxStatus,
revertMsg string,
inputAmount math.Uint, // TODO: find different way for this
kingpinXD marked this conversation as resolved.
Show resolved Hide resolved
) error {
switch oldStatus {
case types.CctxStatus_PendingOutbound:
senderChain := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, cctx.InboundParams.SenderChainId)
if senderChain == nil {
return observertypes.ErrSupportedChains
}

gasLimit, err := k.GetRevertGasLimit(ctx, *cctx)
if err != nil {
Expand All @@ -159,7 +183,7 @@ func (k Keeper) processFailedOutboundForExternalChainTx(
ctx,
cctx.InboundParams.SenderChainId,
cctx,
cctx.OutboundParams[0].Amount,
inputAmount,
false,
)
if err != nil {
Expand All @@ -169,20 +193,58 @@ func (k Keeper) processFailedOutboundForExternalChainTx(
if err != nil {
return err
}
if revertMsg == "" {
skosito marked this conversation as resolved.
Show resolved Hide resolved
revertMsg = "Outbound failed, start revert"
}
// Not setting the finalization status here, the required changes have been made while creating the revert tx
cctx.SetPendingRevert("Outbound failed, start revert")
cctx.SetPendingRevert(revertMsg)
case types.CctxStatus_PendingRevert:
cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed
cctx.SetAbort("Outbound failed: revert failed; abort TX")
}
return nil
}

// processFailedOutboundForZEVM processes the failed outbound transaction for ZEVM
func (k Keeper) processFailedOutboundForZEVM(ctx sdk.Context, cctx *types.CrossChainTx) error {
// validateSuccessfulOutbound processes a successful outbound transaction. It does the following things in one function:
//
// 1. Change the status of the CCTX from
// - PendingRevert to Reverted
// - PendingOutbound to OutboundMined
//
// 2. Set the finalization status of the current outbound tx to executed
//
// 3. Emit an event for the successful outbound transaction if flag is provided
//
// This function sets CCTX status, in cases where the outbound tx is successful, but tx itself fails
// This is done because SaveSuccessfulOutbound does not set the cctx status
// For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the validateFailedOutboundObservers function, so we can just return and error to trigger that
func (k Keeper) validateSuccessfulOutbound(
skosito marked this conversation as resolved.
Show resolved Hide resolved
ctx sdk.Context,
cctx *types.CrossChainTx,
valueReceived string,
emitEvent bool,
) {
oldStatus := cctx.CctxStatus.Status
switch oldStatus {
case types.CctxStatus_PendingRevert:
cctx.SetReverted("Outbound succeeded, revert executed")
case types.CctxStatus_PendingOutbound:
cctx.SetOutBoundMined("Outbound succeeded, mined")
default:
return
}
cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed
skosito marked this conversation as resolved.
Show resolved Hide resolved
newStatus := cctx.CctxStatus.Status.String()
if emitEvent {
EmitOutboundSuccess(ctx, valueReceived, oldStatus.String(), newStatus, cctx.Index)
}
}

// validateFailedOutboundObserversForZEVM processes the failed outbound transaction for ZEVM
func (k Keeper) validateFailedOutboundObserversForZEVM(ctx sdk.Context, cctx *types.CrossChainTx) error {
indexBytes, err := cctx.GetCCTXIndexBytes()
if err != nil {
// Return err to save the failed outbound ad set to aborted
// Return err to save the failed outbound and set to aborted
return fmt.Errorf("failed reverting GetCCTXIndexBytes: %s", err.Error())
}
// Finalize the older outbound tx
Expand Down
Loading
Loading