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

feat: zetatools cctx tracker #3455

Merged
merged 20 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -6,6 +6,7 @@

* [3461](https://github.com/zeta-chain/node/pull/3461) - add new `ConfirmationParams` field to chain params to enable multiple confirmation count values, deprecating `confirmation_count`
* [3489](https://github.com/zeta-chain/node/pull/3489) - add Sui chain info
* [3455](https://github.com/zeta-chain/node/pull/3455) - add `track-cctx` command to zetatools

### Refactor

Expand Down
59 changes: 59 additions & 0 deletions cmd/zetae2e/local/bitcoin.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,72 @@ import (

"github.com/fatih/color"
"github.com/stretchr/testify/require"
"golang.org/x/sync/errgroup"

"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/testutil"
)

// startBitcoinTests starts Bitcoin related tests
func startBitcoinTests(
eg *errgroup.Group,
conf config.Config,
deployerRunner *runner.E2ERunner,
verbose bool,
light, skipBitcoinSetup bool,
) {
// start the bitcoin tests
// btc withdraw tests are those that need a Bitcoin node wallet to send UTXOs
bitcoinDepositTests := []string{
e2etests.TestBitcoinDonationName,
e2etests.TestBitcoinDepositName,
e2etests.TestBitcoinDepositAndCallName,
e2etests.TestBitcoinDepositAndCallRevertName,
e2etests.TestBitcoinStdMemoDepositName,
e2etests.TestBitcoinStdMemoDepositAndCallName,
e2etests.TestBitcoinStdMemoDepositAndCallRevertName,
e2etests.TestBitcoinStdMemoInscribedDepositAndCallName,
e2etests.TestBitcoinDepositAndAbortWithLowDepositFeeName,
e2etests.TestCrosschainSwapName,
}
bitcoinDepositTestsAdvanced := []string{
e2etests.TestBitcoinDepositAndCallRevertWithDustName,
e2etests.TestBitcoinStdMemoDepositAndCallRevertOtherAddressName,
e2etests.TestBitcoinDepositAndWithdrawWithDustName,
}
bitcoinWithdrawTests := []string{
e2etests.TestBitcoinWithdrawSegWitName,
e2etests.TestBitcoinWithdrawInvalidAddressName,
e2etests.TestLegacyZetaWithdrawBTCRevertName,
}
bitcoinWithdrawTestsAdvanced := []string{
e2etests.TestBitcoinWithdrawTaprootName,
e2etests.TestBitcoinWithdrawLegacyName,
e2etests.TestBitcoinWithdrawP2SHName,
e2etests.TestBitcoinWithdrawP2WSHName,
e2etests.TestBitcoinWithdrawMultipleName,
e2etests.TestBitcoinWithdrawRestrictedName,
}

if !light {
// if light is enabled, only the most basic tests are run and advanced are skipped
bitcoinDepositTests = append(bitcoinDepositTests, bitcoinDepositTestsAdvanced...)
bitcoinWithdrawTests = append(bitcoinWithdrawTests, bitcoinWithdrawTestsAdvanced...)
}
bitcoinDepositTestRoutine, bitcoinWithdrawTestRoutine := bitcoinTestRoutines(
conf,
deployerRunner,
verbose,
!skipBitcoinSetup,
bitcoinDepositTests,
bitcoinWithdrawTests,
)
eg.Go(bitcoinDepositTestRoutine)
eg.Go(bitcoinWithdrawTestRoutine)
}

// bitcoinTestRoutines returns test routines for deposit and withdraw tests
func bitcoinTestRoutines(
conf config.Config,
Expand Down
50 changes: 1 addition & 49 deletions cmd/zetae2e/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,55 +291,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) {
if !skipRegular {
// start the EVM tests
startEVMTests(&eg, conf, deployerRunner, verbose)

// start the bitcoin tests
// btc withdraw tests are those that need a Bitcoin node wallet to send UTXOs
bitcoinDepositTests := []string{
e2etests.TestBitcoinDonationName,
e2etests.TestBitcoinDepositName,
e2etests.TestBitcoinDepositAndCallName,
e2etests.TestBitcoinDepositAndCallRevertName,
e2etests.TestBitcoinStdMemoDepositName,
e2etests.TestBitcoinStdMemoDepositAndCallName,
e2etests.TestBitcoinStdMemoDepositAndCallRevertName,
e2etests.TestBitcoinStdMemoInscribedDepositAndCallName,
e2etests.TestBitcoinDepositAndAbortWithLowDepositFeeName,
e2etests.TestCrosschainSwapName,
}
bitcoinDepositTestsAdvanced := []string{
e2etests.TestBitcoinDepositAndCallRevertWithDustName,
e2etests.TestBitcoinStdMemoDepositAndCallRevertOtherAddressName,
e2etests.TestBitcoinDepositAndWithdrawWithDustName,
}
bitcoinWithdrawTests := []string{
e2etests.TestBitcoinWithdrawSegWitName,
e2etests.TestBitcoinWithdrawInvalidAddressName,
e2etests.TestLegacyZetaWithdrawBTCRevertName,
}
bitcoinWithdrawTestsAdvanced := []string{
e2etests.TestBitcoinWithdrawTaprootName,
e2etests.TestBitcoinWithdrawLegacyName,
e2etests.TestBitcoinWithdrawP2SHName,
e2etests.TestBitcoinWithdrawP2WSHName,
e2etests.TestBitcoinWithdrawMultipleName,
e2etests.TestBitcoinWithdrawRestrictedName,
}

if !light {
// if light is enabled, only the most basic tests are run and advanced are skipped
bitcoinDepositTests = append(bitcoinDepositTests, bitcoinDepositTestsAdvanced...)
bitcoinWithdrawTests = append(bitcoinWithdrawTests, bitcoinWithdrawTestsAdvanced...)
}
bitcoinDepositTestRoutine, bitcoinWithdrawTestRoutine := bitcoinTestRoutines(
conf,
deployerRunner,
verbose,
!skipBitcoinSetup,
bitcoinDepositTests,
bitcoinWithdrawTests,
)
eg.Go(bitcoinDepositTestRoutine)
eg.Go(bitcoinWithdrawTestRoutine)
startBitcoinTests(&eg, conf, deployerRunner, verbose, light, skipBitcoinSetup)
}

if !skipPrecompiles {
Expand Down
178 changes: 178 additions & 0 deletions cmd/zetatool/cctx/cctx_details.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package cctx

import (
"fmt"

"github.com/zeta-chain/node/cmd/zetatool/context"
"github.com/zeta-chain/node/pkg/chains"
crosschaintypes "github.com/zeta-chain/node/x/crosschain/types"
)

// TrackingDetails tracks the status of a CCTX transaction
type TrackingDetails struct {
CCTXIdentifier string `json:"cctx_identifier"`
Status Status `json:"status"`
OutboundChain chains.Chain `json:"outbound_chain_id"`
OutboundTssNonce uint64 `json:"outbound_tss_nonce"`
OutboundTrackerHashList []string `json:"outbound_tracker_hash_list"`
Message string `json:"message"`
}

func NewTrackingDetails() *TrackingDetails {
return &TrackingDetails{
CCTXIdentifier: "",
Status: Unknown,
}
}

// UpdateStatusFromZetacoreCCTX updates the status of the TrackingDetails from the zetacore CCTX status
func (c *TrackingDetails) UpdateStatusFromZetacoreCCTX(status crosschaintypes.CctxStatus) {
switch status {
case crosschaintypes.CctxStatus_PendingOutbound:
c.Status = PendingOutbound
case crosschaintypes.CctxStatus_OutboundMined:
c.Status = OutboundMined
case crosschaintypes.CctxStatus_Reverted:
c.Status = Reverted
case crosschaintypes.CctxStatus_PendingRevert:
c.Status = PendingRevert
case crosschaintypes.CctxStatus_Aborted:
c.Status = Aborted
default:
c.Status = Unknown
}
}

func (c *TrackingDetails) Print() string {
return fmt.Sprintf("CCTX Identifier: %s Status: %s", c.CCTXIdentifier, c.Status.String())
}

func (c *TrackingDetails) DebugPrint() string {
return fmt.Sprintf("CCTX Identifier: %s Status: %s Message: %s", c.CCTXIdentifier, c.Status.String(), c.Message)
}

// UpdateCCTXStatus updates the TrackingDetails with status from zetacore
func (c *TrackingDetails) UpdateCCTXStatus(ctx *context.Context) {
var (
zetacoreClient = ctx.GetZetaCoreClient()
goCtx = ctx.GetContext()
)

CCTX, err := zetacoreClient.GetCctxByHash(goCtx, c.CCTXIdentifier)
if err != nil {
c.Message = fmt.Sprintf("failed to get cctx: %v", err)
return
}

c.UpdateStatusFromZetacoreCCTX(CCTX.CctxStatus.Status)

return
}

// UpdateCCTXOutboundDetails updates the TrackingDetails with the outbound chain and nonce
func (c *TrackingDetails) UpdateCCTXOutboundDetails(ctx *context.Context) {
var (
zetacoreClient = ctx.GetZetaCoreClient()
goCtx = ctx.GetContext()
)
CCTX, err := zetacoreClient.GetCctxByHash(goCtx, c.CCTXIdentifier)
if err != nil {
c.Message = fmt.Sprintf("failed to get cctx: %v", err)
}
outboundParams := CCTX.GetCurrentOutboundParam()
if outboundParams == nil {
c.Message = "outbound params not found"
return
}
chainID := CCTX.GetCurrentOutboundParam().ReceiverChainId

// This is almost impossible to happen as the cctx would not have been created if the chain was not supported
chain, found := chains.GetChainFromChainID(chainID, []chains.Chain{})
if !found {
c.Message = fmt.Sprintf("receiver chain not supported,chain id: %d", chainID)
}
c.OutboundChain = chain
c.OutboundTssNonce = CCTX.GetCurrentOutboundParam().TssNonce
return
}

// UpdateHashListAndPendingStatus updates the TrackingDetails with the hash list and updates pending status
// If the tracker is found, it means the outbound is broadcast, but we are waiting for the confirmations
// If the tracker is not found, it means the outbound is not broadcast yet; we are waiting for the tss to sign the outbound
func (c *TrackingDetails) UpdateHashListAndPendingStatus(ctx *context.Context) {
var (
zetacoreClient = ctx.GetZetaCoreClient()
goCtx = ctx.GetContext()
outboundChain = c.OutboundChain
outboundNonce = c.OutboundTssNonce
)

tracker, err := zetacoreClient.GetOutboundTracker(goCtx, outboundChain, outboundNonce)
// the tracker is found that means the outbound has been broadcast, but we are waiting for confirmations
if err == nil && tracker != nil {
c.updateOutboundConfirmation()
var hashList []string
for _, hash := range tracker.HashList {
hashList = append(hashList, hash.TxHash)
}
c.OutboundTrackerHashList = hashList
return
}
// the cctx is in pending state, but the outbound signing has not been done
c.updateOutboundSigning()
return
}

// IsInboundFinalized checks if the inbound voting has been finalized
func (c *TrackingDetails) IsInboundFinalized() bool {
return !(c.Status == PendingInboundConfirmation || c.Status == PendingInboundVoting)
}

// IsPendingOutbound checks if the cctx is pending processing the outbound transaction (outbound or revert)
func (c *TrackingDetails) IsPendingOutbound() bool {
return c.Status == PendingOutbound || c.Status == PendingRevert
}

// IsPendingConfirmation checks if the cctx is pending outbound confirmation (outbound or revert
func (c *TrackingDetails) IsPendingConfirmation() bool {
return c.Status == PendingOutboundConfirmation || c.Status == PendingRevertConfirmation
}

// State transitions for TrackingDetails
// 0 - Inbound Confirmation
func (c *TrackingDetails) updateInboundConfirmation(isConfirmed bool) {
c.Status = PendingInboundConfirmation
if isConfirmed {
c.Status = PendingInboundVoting
}
}

// 1 - Outbound Signing
func (c *TrackingDetails) updateOutboundSigning() {
switch {
case c.Status == PendingOutbound:
c.Status = PendingOutboundSigning
case c.Status == PendingRevert:
c.Status = PendingRevertSigning
}
}

// 2 - Outbound Confirmation
func (c *TrackingDetails) updateOutboundConfirmation() {
switch {
case c.Status == PendingOutbound:
c.Status = PendingOutboundConfirmation
case c.Status == PendingRevert:
c.Status = PendingRevertConfirmation
}
}

// 3 - Outbound Voting
func (c *TrackingDetails) updateOutboundVoting() {
switch {
case c.Status == PendingOutboundConfirmation:
c.Status = PendingOutboundVoting
case c.Status == PendingRevertConfirmation:
c.Status = PendingRevertVoting
}
}
67 changes: 67 additions & 0 deletions cmd/zetatool/cctx/cctx_status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package cctx

// Status represents the status of a CCTX transaction, it is more granular than the status present on zetacore
type Status int

const (
Unknown Status = iota
// Zetacore statuses
PendingOutbound Status = 1
OutboundMined Status = 2
PendingRevert Status = 3
Reverted Status = 4
Aborted Status = 5
// Zetatool only statuses
// PendingInboundConfirmation the inbound transaction is pending confirmation on the inbound chain
PendingInboundConfirmation Status = 6
// PendingInboundVoting the inbound transaction is confirmed on the inbound chain, and we are waiting for observers to vote
PendingInboundVoting Status = 7
// PendingOutboundSigning the outbound transaction is pending signing by the tss
PendingOutboundSigning Status = 8
// PendingRevertSigning the revert transaction is pending signing by the tss
PendingRevertSigning Status = 9
// PendingOutboundConfirmation the outbound transaction
// broadcast by the tss is pending confirmation on the outbound chain
PendingOutboundConfirmation Status = 10
// PendingRevertConfirmation the revert transaction broadcast by the tss is pending confirmation on the outbound chain
PendingRevertConfirmation Status = 11
// PendingOutboundVoting the outbound transaction is confirmed on the outbound chain,
//and we are waiting for observers to vote
PendingOutboundVoting Status = 12
// PendingRevertVoting the revert transaction is confirmed on the outbound chain,
//and we are waiting for observers to vote
PendingRevertVoting Status = 13
)

func (s Status) String() string {
switch s {
case PendingInboundConfirmation:
return "PendingInboundConfirmation"
case PendingInboundVoting:
return "PendingInboundVoting"
case PendingOutbound:
return "PendingOutbound"
case OutboundMined:
return "OutboundMined"
case PendingRevert:
return "PendingRevert"
case Reverted:
return "Reverted"
case PendingOutboundConfirmation:
return "PendingOutboundConfirmation"
case PendingRevertConfirmation:
return "PendingRevertConfirmation"
case PendingRevertVoting:
return "PendingRevertVoting"
case Aborted:
return "Aborted"
case PendingOutboundSigning:
return "PendingOutboundSigning"
case PendingRevertSigning:
return "PendingRevertSigning"
case PendingOutboundVoting:
return "PendingOutboundVoting"
default:
return "Unknown"
}
}
Loading