Skip to content

Commit

Permalink
refactor(crosschain): set outbound hash in cctx when adding outbound …
Browse files Browse the repository at this point in the history
…tracker (#3594)

* set outbound hash

* add tests

* changelog
  • Loading branch information
lumtis authored Feb 28, 2025
1 parent 06182c9 commit ed8e2dc
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 5 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

* [3381](https://github.com/zeta-chain/node/pull/3381) - split Bitcoin observer and signer into small files and organize outbound logic into reusable/testable functions; renaming, type unification, etc.
* [3496](https://github.com/zeta-chain/node/pull/3496) - zetaclient uses `ConfirmationParams` instead of old `ConfirmationCount`; use block ranged based observation for btc and evm chain.
* [3594](https://github.com/zeta-chain/node/pull/3594) - set outbound hash in cctx when adding outbound tracker

### Fixes

Expand Down
6 changes: 4 additions & 2 deletions testutil/keeper/crosschain.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,11 +486,11 @@ func MockCctxByNonce(
observerKeeper *crosschainmocks.CrosschainObserverKeeper,
cctxStatus types.CctxStatus,
isErr bool,
) {
) string {
if isErr {
// return error on GetTSS to make CctxByNonce return error
observerKeeper.On("GetTSS", mock.Anything).Return(observertypes.TSS{}, false).Once()
return
return ""
}

cctx := sample.CrossChainTx(t, sample.StringRandom(sample.Rand(), 10))
Expand All @@ -505,4 +505,6 @@ func MockCctxByNonce(
CctxIndex: cctx.Index,
}, true).
Once()

return cctx.Index
}
6 changes: 6 additions & 0 deletions x/crosschain/keeper/msg_server_add_outbound_tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ func (k msgServer) AddOutboundTracker(
return nil, cosmoserrors.Wrapf(authoritytypes.ErrUnauthorized, "Creator %s", msg.Creator)
}

// set the outbound hash from the last tracker and save it in the store
// this value is helpful for explorer or front-end application to find the outbound hash, it has no on-chain utility
// the hash will be replaced with the actual hash, if different, when the outbound transaction is observed and voted
cctx.CrossChainTx.GetCurrentOutboundParam().Hash = msg.TxHash
k.SetCrossChainTx(ctx, *cctx.CrossChainTx)

// fetch the tracker
// if the tracker does not exist, initialize a new one
tracker, found := k.GetOutboundTracker(ctx, msg.ChainId, msg.Nonce)
Expand Down
58 changes: 55 additions & 3 deletions x/crosschain/keeper/msg_server_add_outbound_tracker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func getEthereumChainID() int64 {

func TestMsgServer_AddToOutboundTracker(t *testing.T) {
t.Run("admin can add tracker", func(t *testing.T) {
// arrange
k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{
UseAuthorityMock: true,
UseObserverMock: true,
Expand All @@ -36,7 +37,7 @@ func TestMsgServer_AddToOutboundTracker(t *testing.T) {

observerMock.On("GetSupportedChainFromChainID", mock.Anything, mock.Anything).Return(chains.Chain{}, true)
observerMock.On("IsNonTombstonedObserver", mock.Anything, mock.Anything).Return(false)
keepertest.MockCctxByNonce(t, ctx, *k, observerMock, types.CctxStatus_PendingOutbound, false)
cctxIndex := keepertest.MockCctxByNonce(t, ctx, *k, observerMock, types.CctxStatus_PendingOutbound, false)

msg := types.MsgAddOutboundTracker{
Creator: admin,
Expand All @@ -45,14 +46,24 @@ func TestMsgServer_AddToOutboundTracker(t *testing.T) {
Nonce: 0,
}
keepertest.MockCheckAuthorization(&authorityMock.Mock, &msg, nil)

// act
_, err := msgServer.AddOutboundTracker(ctx, &msg)
require.NoError(t, err)

// assert
tracker, found := k.GetOutboundTracker(ctx, chainID, 0)
require.True(t, found)
require.Equal(t, hash, tracker.HashList[0].TxHash)

// related cctx outbound hash is set
cctx, found := k.GetCrossChainTx(ctx, cctxIndex)
require.True(t, found)
require.Equal(t, hash, cctx.GetCurrentOutboundParam().Hash)
})

t.Run("observer can add tracker", func(t *testing.T) {
// arrange
k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{
UseAuthorityMock: true,
UseObserverMock: true,
Expand All @@ -68,7 +79,7 @@ func TestMsgServer_AddToOutboundTracker(t *testing.T) {

observerMock.On("GetSupportedChainFromChainID", mock.Anything, mock.Anything).Return(chains.Chain{}, true)
observerMock.On("IsNonTombstonedObserver", mock.Anything, mock.Anything).Return(true)
keepertest.MockCctxByNonce(t, ctx, *k, observerMock, types.CctxStatus_PendingOutbound, false)
cctxIndex := keepertest.MockCctxByNonce(t, ctx, *k, observerMock, types.CctxStatus_PendingOutbound, false)

msg := types.MsgAddOutboundTracker{
Creator: admin,
Expand All @@ -77,14 +88,24 @@ func TestMsgServer_AddToOutboundTracker(t *testing.T) {
Nonce: 0,
}
keepertest.MockCheckAuthorization(&authorityMock.Mock, &msg, authoritytypes.ErrUnauthorized)

// act
_, err := msgServer.AddOutboundTracker(ctx, &msg)
require.NoError(t, err)

// assert
tracker, found := k.GetOutboundTracker(ctx, chainID, 0)
require.True(t, found)
require.Equal(t, hash, tracker.HashList[0].TxHash)

// related cctx outbound hash is set
cctx, found := k.GetCrossChainTx(ctx, cctxIndex)
require.True(t, found)
require.Equal(t, hash, cctx.GetCurrentOutboundParam().Hash)
})

t.Run("can add hash to existing tracker", func(t *testing.T) {
// arrange
k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{
UseAuthorityMock: true,
UseObserverMock: true,
Expand All @@ -101,7 +122,7 @@ func TestMsgServer_AddToOutboundTracker(t *testing.T) {

observerMock.On("GetSupportedChainFromChainID", mock.Anything, mock.Anything).Return(chains.Chain{}, true)
observerMock.On("IsNonTombstonedObserver", mock.Anything, mock.Anything).Return(false)
keepertest.MockCctxByNonce(t, ctx, *k, observerMock, types.CctxStatus_PendingOutbound, false)
cctxIndex := keepertest.MockCctxByNonce(t, ctx, *k, observerMock, types.CctxStatus_PendingOutbound, false)

k.SetOutboundTracker(ctx, types.OutboundTracker{
ChainId: chainID,
Expand All @@ -120,16 +141,26 @@ func TestMsgServer_AddToOutboundTracker(t *testing.T) {
Nonce: 42,
}
keepertest.MockCheckAuthorization(&authorityMock.Mock, &msg, nil)

// act
_, err := msgServer.AddOutboundTracker(ctx, &msg)
require.NoError(t, err)

// assert
tracker, found := k.GetOutboundTracker(ctx, chainID, 42)
require.True(t, found)
require.Len(t, tracker.HashList, 2)
require.EqualValues(t, existinghHash, tracker.HashList[0].TxHash)
require.EqualValues(t, newHash, tracker.HashList[1].TxHash)

// related cctx outbound hash is set
cctx, found := k.GetCrossChainTx(ctx, cctxIndex)
require.True(t, found)
require.Equal(t, newHash, cctx.GetCurrentOutboundParam().Hash)
})

t.Run("should return early if cctx not pending", func(t *testing.T) {
// arrange
k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{
UseAuthorityMock: true,
UseObserverMock: true,
Expand All @@ -151,8 +182,11 @@ func TestMsgServer_AddToOutboundTracker(t *testing.T) {
// set cctx status to outbound mined
keepertest.MockCctxByNonce(t, ctx, *k, observerMock, types.CctxStatus_OutboundMined, false)

// act
res, err := msgServer.AddOutboundTracker(ctx, &msg)
require.NoError(t, err)

// assert
require.Equal(t, &types.MsgAddOutboundTrackerResponse{IsRemoved: true}, res)

// check if tracker is removed
Expand All @@ -161,6 +195,7 @@ func TestMsgServer_AddToOutboundTracker(t *testing.T) {
})

t.Run("should error for unsupported chain", func(t *testing.T) {
// arrange
k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{
UseAuthorityMock: true,
UseObserverMock: true,
Expand All @@ -174,16 +209,20 @@ func TestMsgServer_AddToOutboundTracker(t *testing.T) {

chainID := getEthereumChainID()

// act
_, err := msgServer.AddOutboundTracker(ctx, &types.MsgAddOutboundTracker{
Creator: admin,
ChainId: chainID,
TxHash: sample.Hash().Hex(),
Nonce: 0,
})

// assert
require.ErrorIs(t, err, observertypes.ErrSupportedChains)
})

t.Run("should error if no CctxByNonce", func(t *testing.T) {
// arrange
k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{
UseAuthorityMock: true,
UseObserverMock: true,
Expand All @@ -199,16 +238,20 @@ func TestMsgServer_AddToOutboundTracker(t *testing.T) {

chainID := getEthereumChainID()

// act
_, err := msgServer.AddOutboundTracker(ctx, &types.MsgAddOutboundTracker{
Creator: admin,
ChainId: chainID,
TxHash: sample.Hash().Hex(),
Nonce: 0,
})

// assert
require.ErrorIs(t, err, types.ErrCannotFindCctx)
})

t.Run("should fail if max tracker hashes reached", func(t *testing.T) {
// arrange
k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{
UseAuthorityMock: true,
UseObserverMock: true,
Expand Down Expand Up @@ -247,11 +290,16 @@ func TestMsgServer_AddToOutboundTracker(t *testing.T) {
Nonce: 42,
}
keepertest.MockCheckAuthorization(&authorityMock.Mock, &msg, nil)

// act
_, err := msgServer.AddOutboundTracker(ctx, &msg)

// assert
require.ErrorIs(t, err, types.ErrMaxTxOutTrackerHashesReached)
})

t.Run("no hash added if already exist", func(t *testing.T) {
// arrange
k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{
UseAuthorityMock: true,
UseObserverMock: true,
Expand Down Expand Up @@ -287,8 +335,12 @@ func TestMsgServer_AddToOutboundTracker(t *testing.T) {
Nonce: 42,
}
keepertest.MockCheckAuthorization(&authorityMock.Mock, &msg, nil)

// act
_, err := msgServer.AddOutboundTracker(ctx, &msg)
require.NoError(t, err)

// assert
tracker, found := k.GetOutboundTracker(ctx, chainID, 42)
require.True(t, found)
require.Len(t, tracker.HashList, 1)
Expand Down

0 comments on commit ed8e2dc

Please sign in to comment.