Skip to content

Commit ea0afab

Browse files
authored
fix: backport(v29) check if cctx has already been created before casting inbound vote (#3664)
* check if cctx exists before casting inbound vote * update comments * return cctxindex instead of empty string * added a status check * status check for ballot check * fix unit tests * add changelog * update changelog
1 parent 8e1d348 commit ea0afab

File tree

10 files changed

+183
-6
lines changed

10 files changed

+183
-6
lines changed

changelog.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# CHANGELOG
22

3+
## v29.0.1
4+
5+
### Fixes
6+
7+
* [3664](https://github.com/zeta-chain/node/pull/3664/) - add a check to stop posting inbound votes if the cctx has already been created
8+
39
## v29.0.0
410

511
### Breaking Changes

zetaclient/chains/base/observer.go

+21
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
lru "github.com/hashicorp/golang-lru"
1313
"github.com/pkg/errors"
1414
"github.com/rs/zerolog"
15+
"google.golang.org/grpc/codes"
16+
"google.golang.org/grpc/status"
1517

1618
"github.com/zeta-chain/node/pkg/chains"
1719
crosschaintypes "github.com/zeta-chain/node/x/crosschain/types"
@@ -401,6 +403,25 @@ func (ob *Observer) PostVoteInbound(
401403
logs.FieldConfirmationMode: msg.ConfirmationMode.String(),
402404
}
403405

406+
cctxIndex := msg.Digest()
407+
408+
// The cctx is created after the inbound ballot is finalized
409+
// 1. if the cctx already exists, we could try voting if the ballot is present
410+
// 2. if the cctx exists but the ballot does not exist, we do not need to vote
411+
_, err := ob.ZetacoreClient().GetCctxByHash(ctx, cctxIndex)
412+
if err == nil {
413+
// The cctx exists we should still vote if the ballot is present
414+
_, ballotErr := ob.ZetacoreClient().GetBallotByID(ctx, cctxIndex)
415+
if ballotErr != nil {
416+
// Verify ballot is not found
417+
if st, ok := status.FromError(ballotErr); ok && st.Code() == codes.NotFound {
418+
// Query for ballot failed, the ballot does not exist we can return
419+
ob.logger.Inbound.Info().Fields(lf).Msg("inbound detected: cctx exists but the ballot does not")
420+
return cctxIndex, nil
421+
}
422+
}
423+
}
424+
404425
// make sure the message is valid to avoid unnecessary retries
405426
if err := msg.ValidateBasic(); err != nil {
406427
ob.logger.Inbound.Warn().Err(err).Fields(lf).Msg("invalid inbound vote message")

zetaclient/chains/base/observer_test.go

+58-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package base_test
22

33
import (
4+
"bytes"
45
"context"
56
"fmt"
67
"os"
78
"strings"
89
"testing"
910

11+
"github.com/pkg/errors"
1012
"github.com/rs/zerolog"
1113
"github.com/rs/zerolog/log"
1214
"github.com/stretchr/testify/require"
@@ -21,6 +23,8 @@ import (
2123
zctx "github.com/zeta-chain/node/zetaclient/context"
2224
"github.com/zeta-chain/node/zetaclient/db"
2325
"github.com/zeta-chain/node/zetaclient/testutils/mocks"
26+
"google.golang.org/grpc/codes"
27+
"google.golang.org/grpc/status"
2428
)
2529

2630
const (
@@ -518,9 +522,10 @@ func TestPostVoteInbound(t *testing.T) {
518522
ob := newTestSuite(t, chains.Ethereum)
519523

520524
ob.zetacore.WithPostVoteInbound("", "sampleBallotIndex")
521-
522525
// post vote inbound
523526
msg := sample.InboundVote(coin.CoinType_Gas, chains.Ethereum.ChainId, chains.ZetaChainMainnet.ChainId)
527+
ob.zetacore.MockGetCctxByHash(errors.New("not found"))
528+
524529
ballot, err := ob.PostVoteInbound(context.TODO(), &msg, 100000)
525530
require.NoError(t, err)
526531
require.Equal(t, "sampleBallotIndex", ballot)
@@ -533,12 +538,64 @@ func TestPostVoteInbound(t *testing.T) {
533538
// create sample message with long Message
534539
msg := sample.InboundVote(coin.CoinType_Gas, chains.Ethereum.ChainId, chains.ZetaChainMainnet.ChainId)
535540
msg.Message = strings.Repeat("1", crosschaintypes.MaxMessageLength+1)
541+
ob.zetacore.MockGetCctxByHash(errors.New("not found"))
536542

537543
// post vote inbound
538544
ballot, err := ob.PostVoteInbound(context.TODO(), &msg, 100000)
539545
require.NoError(t, err)
540546
require.Empty(t, ballot)
541547
})
548+
549+
t.Run("should not post vote cctx already exists and ballot is not found", func(t *testing.T) {
550+
//Arrange
551+
// create observer
552+
ob := newTestSuite(t, chains.Ethereum)
553+
554+
ob.zetacore.WithPostVoteInbound("", "sampleBallotIndex")
555+
msg := sample.InboundVote(coin.CoinType_Gas, chains.Ethereum.ChainId, chains.ZetaChainMainnet.ChainId)
556+
557+
ob.zetacore.MockGetCctxByHash(nil)
558+
ob.zetacore.MockGetBallotByID(msg.Digest(), status.Error(codes.NotFound, "not found ballot"))
559+
560+
var logBuffer bytes.Buffer
561+
consoleWriter := zerolog.ConsoleWriter{Out: &logBuffer}
562+
logger := zerolog.New(consoleWriter)
563+
ob.Observer.Logger().Inbound = logger
564+
565+
// Act
566+
ballot, err := ob.PostVoteInbound(context.TODO(), &msg, 100000)
567+
// Assert
568+
require.NoError(t, err)
569+
require.Equal(t, ballot, msg.Digest())
570+
571+
logOutput := logBuffer.String()
572+
require.Contains(t, logOutput, "inbound detected: cctx exists but the ballot does not")
573+
})
574+
575+
t.Run("should post vote cctx already exists but ballot is found", func(t *testing.T) {
576+
//Arrange
577+
// create observer
578+
ob := newTestSuite(t, chains.Ethereum)
579+
580+
msg := sample.InboundVote(coin.CoinType_Gas, chains.Ethereum.ChainId, chains.ZetaChainMainnet.ChainId)
581+
ob.zetacore.WithPostVoteInbound(sample.ZetaIndex(t), msg.Digest())
582+
ob.zetacore.MockGetCctxByHash(nil)
583+
ob.zetacore.MockGetBallotByID(msg.Digest(), nil)
584+
585+
var logBuffer bytes.Buffer
586+
consoleWriter := zerolog.ConsoleWriter{Out: &logBuffer}
587+
logger := zerolog.New(consoleWriter)
588+
ob.Observer.Logger().Inbound = logger
589+
590+
// Act
591+
ballot, err := ob.PostVoteInbound(context.TODO(), &msg, 100000)
592+
// Assert
593+
require.NoError(t, err)
594+
require.Equal(t, ballot, msg.Digest())
595+
596+
logOutput := logBuffer.String()
597+
require.Contains(t, logOutput, "inbound detected: vote posted")
598+
})
542599
}
543600

544601
func createDatabase(t *testing.T) *db.DB {

zetaclient/chains/evm/observer/inbound_test.go

+14-5
Original file line numberDiff line numberDiff line change
@@ -446,9 +446,10 @@ func Test_ObserveTSSReceiveInBlock(t *testing.T) {
446446

447447
// test cases
448448
tests := []struct {
449-
name string
450-
mockEVMClient func(m *mocks.EVMRPCClient)
451-
errMsg string
449+
name string
450+
mockEVMClient func(m *mocks.EVMRPCClient)
451+
mockZetacoreClient func(m *mocks.ZetacoreClient)
452+
errMsg string
452453
}{
453454
{
454455
name: "should observe TSS receive in block",
@@ -458,6 +459,9 @@ func Test_ObserveTSSReceiveInBlock(t *testing.T) {
458459
m.On("TransactionReceipt", mock.Anything, mock.Anything).Return(receipt, nil)
459460
m.On("BlockByNumberCustom", mock.Anything, mock.Anything).Return(block, nil)
460461
},
462+
mockZetacoreClient: func(m *mocks.ZetacoreClient) {
463+
m.On("GetCctxByHash", mock.Anything, mock.Anything).Return(nil, errors.New("not found"))
464+
},
461465
errMsg: "",
462466
},
463467
{
@@ -469,7 +473,8 @@ func Test_ObserveTSSReceiveInBlock(t *testing.T) {
469473
m.On("BlockNumber", mock.Anything).Return(uint64(0), errors.New("RPC error"))
470474
m.On("BlockByNumberCustom", mock.Anything, mock.Anything).Return(nil, errors.New("RPC error"))
471475
},
472-
errMsg: "error getting block",
476+
mockZetacoreClient: nil,
477+
errMsg: "error getting block",
473478
},
474479
{
475480
name: "should not observe on error getting receipt",
@@ -479,7 +484,8 @@ func Test_ObserveTSSReceiveInBlock(t *testing.T) {
479484
m.On("TransactionReceipt", mock.Anything, mock.Anything).Return(nil, errors.New("RPC error"))
480485
m.On("BlockByNumberCustom", mock.Anything, mock.Anything).Return(block, nil)
481486
},
482-
errMsg: "error getting receipt",
487+
mockZetacoreClient: nil,
488+
errMsg: "error getting receipt",
483489
},
484490
}
485491

@@ -491,6 +497,9 @@ func Test_ObserveTSSReceiveInBlock(t *testing.T) {
491497
if tt.mockEVMClient != nil {
492498
tt.mockEVMClient(ob.evmMock)
493499
}
500+
if tt.mockZetacoreClient != nil {
501+
tt.mockZetacoreClient(ob.zetacore)
502+
}
494503

495504
err := ob.observeTSSReceiveInBlock(ob.ctx, blockNumber)
496505
if tt.errMsg != "" {

zetaclient/chains/interfaces/interfaces.go

+2
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ type ZetacoreClient interface {
9191
GetPendingNoncesByChain(ctx context.Context, chainID int64) (observertypes.PendingNonces, error)
9292

9393
GetCctxByNonce(ctx context.Context, chainID int64, nonce uint64) (*crosschaintypes.CrossChainTx, error)
94+
GetCctxByHash(ctx context.Context, sendHash string) (*crosschaintypes.CrossChainTx, error)
9495
GetOutboundTracker(ctx context.Context, chain chains.Chain, nonce uint64) (*crosschaintypes.OutboundTracker, error)
9596
GetAllOutboundTrackerByChain(
9697
ctx context.Context,
@@ -104,6 +105,7 @@ type ZetacoreClient interface {
104105
GetBTCTSSAddress(ctx context.Context, chainID int64) (string, error)
105106
GetZetaHotKeyBalance(ctx context.Context) (sdkmath.Int, error)
106107
GetInboundTrackersForChain(ctx context.Context, chainID int64) ([]crosschaintypes.InboundTracker, error)
108+
GetBallotByID(ctx context.Context, id string) (*observertypes.QueryBallotByIdentifierResponse, error)
107109

108110
GetUpgradePlan(ctx context.Context) (*upgradetypes.Plan, error)
109111

zetaclient/chains/sui/observer/observer_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"cosmossdk.io/math"
1010
"github.com/block-vision/sui-go-sdk/models"
11+
"github.com/pkg/errors"
1112
"github.com/stretchr/testify/assert"
1213
"github.com/stretchr/testify/mock"
1314
"github.com/stretchr/testify/require"
@@ -112,6 +113,7 @@ func TestObserver(t *testing.T) {
112113

113114
// Given inbound votes catches so we can assert them later
114115
ts.CatchInboundVotes()
116+
ts.zetaMock.MockGetCctxByHash(errors.New("not found"))
115117

116118
// ACT
117119
err := ts.ObserveInbound(ts.ctx)
@@ -187,6 +189,7 @@ func TestObserver(t *testing.T) {
187189

188190
// Given votes catcher
189191
ts.CatchInboundVotes()
192+
ts.zetaMock.MockGetCctxByHash(errors.New("not found"))
190193

191194
// ACT
192195
err := ts.ProcessInboundTrackers(ts.ctx)

zetaclient/chains/ton/observer/inbound_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ func TestInbound(t *testing.T) {
141141
Once()
142142

143143
ts.MockGetBlockHeader(depositTX.BlockID)
144+
ts.MockGetCctxByHash()
144145

145146
// ACT
146147
// Observe inbounds once
@@ -205,6 +206,7 @@ func TestInbound(t *testing.T) {
205206
Once()
206207

207208
ts.MockGetBlockHeader(depositAndCallTX.BlockID)
209+
ts.MockGetCctxByHash()
208210

209211
// ACT
210212
// Observe inbounds once
@@ -347,6 +349,7 @@ func TestInbound(t *testing.T) {
347349
for _, tx := range txs {
348350
ts.MockGetBlockHeader(tx.BlockID)
349351
}
352+
ts.MockGetCctxByHash()
350353

351354
// ACT
352355
// Observe inbounds once
@@ -414,6 +417,7 @@ func TestInboundTracker(t *testing.T) {
414417
})
415418
ts.MockGetTransaction(ts.gateway.AccountID(), txWithdrawal)
416419
ts.MockGetBlockHeader(txWithdrawal.BlockID)
420+
ts.MockGetCctxByHash()
417421

418422
// Given inbound trackers from zetacore
419423
trackers := []cc.InboundTracker{

zetaclient/chains/ton/observer/observer_test.go

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"cosmossdk.io/math"
99
eth "github.com/ethereum/go-ethereum/common"
10+
"github.com/pkg/errors"
1011
"github.com/rs/zerolog"
1112
"github.com/stretchr/testify/mock"
1213
"github.com/stretchr/testify/require"
@@ -175,6 +176,11 @@ func (ts *testSuite) MockGetBlockHeader(id ton.BlockIDExt) *mock.Call {
175176
Return(blockInfo, nil)
176177
}
177178

179+
func (ts *testSuite) MockGetCctxByHash() *mock.Call {
180+
return ts.zetacore.
181+
On("GetCctxByHash", mock.Anything, mock.Anything).Return(nil, errors.New("not found"))
182+
}
183+
178184
func (ts *testSuite) OnGetInboundTrackersForChain(trackers []cc.InboundTracker) *mock.Call {
179185
return ts.zetacore.
180186
On("GetInboundTrackersForChain", mock.Anything, ts.chain.ChainId).

zetaclient/testutils/mocks/zetacore_client.go

+60
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

zetaclient/testutils/mocks/zetacore_client_opts.go

+9
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,16 @@ func (_m *ZetacoreClient) WithPostVoteInbound(zetaTxHash string, ballotIndex str
4949
_m.On("PostVoteInbound", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
5050
Maybe().
5151
Return(zetaTxHash, ballotIndex, nil)
52+
return _m
53+
}
54+
55+
func (_m *ZetacoreClient) MockGetCctxByHash(err error) *ZetacoreClient {
56+
_m.On("GetCctxByHash", mock.Anything, mock.Anything).Return(nil, err)
57+
return _m
58+
}
5259

60+
func (_m *ZetacoreClient) MockGetBallotByID(ballotIndex string, err error) *ZetacoreClient {
61+
_m.On("GetBallotByID", mock.Anything, ballotIndex).Return(nil, err)
5362
return _m
5463
}
5564

0 commit comments

Comments
 (0)