Skip to content

Commit 0ab6e19

Browse files
authored
fix: check if cctx has already been created before casting inbound vote (#3636)
1 parent d11fbfe commit 0ab6e19

File tree

9 files changed

+142
-6
lines changed

9 files changed

+142
-6
lines changed

changelog.md

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

3+
## Unreleased
4+
5+
* [3636](https://github.com/zeta-chain/node/pull/3636) - add a check to stop posting inbound votes if the cctx has already been created
6+
37
## v28.1.0
48

59
This is a zetaclient only release.

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"
@@ -406,6 +408,25 @@ func (ob *Observer) PostVoteInbound(
406408
logs.FieldCoinType: coinType.String(),
407409
}
408410

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

zetaclient/chains/base/observer_test.go

+22-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"strings"
88
"testing"
99

10+
"github.com/pkg/errors"
1011
"github.com/rs/zerolog"
1112
"github.com/rs/zerolog/log"
1213
"github.com/stretchr/testify/require"
@@ -21,6 +22,8 @@ import (
2122
zctx "github.com/zeta-chain/node/zetaclient/context"
2223
"github.com/zeta-chain/node/zetaclient/db"
2324
"github.com/zeta-chain/node/zetaclient/testutils/mocks"
25+
"google.golang.org/grpc/codes"
26+
"google.golang.org/grpc/status"
2427
)
2528

2629
const (
@@ -538,9 +541,10 @@ func TestPostVoteInbound(t *testing.T) {
538541
ob := newTestSuite(t, chains.Ethereum)
539542

540543
ob.zetacore.WithPostVoteInbound("", "sampleBallotIndex")
541-
542544
// post vote inbound
543545
msg := sample.InboundVote(coin.CoinType_Gas, chains.Ethereum.ChainId, chains.ZetaChainMainnet.ChainId)
546+
ob.zetacore.MockGetCctxByHash(msg.Digest(), errors.New("not found"))
547+
544548
ballot, err := ob.PostVoteInbound(context.TODO(), &msg, 100000)
545549
require.NoError(t, err)
546550
require.Equal(t, "sampleBallotIndex", ballot)
@@ -553,12 +557,29 @@ func TestPostVoteInbound(t *testing.T) {
553557
// create sample message with long Message
554558
msg := sample.InboundVote(coin.CoinType_Gas, chains.Ethereum.ChainId, chains.ZetaChainMainnet.ChainId)
555559
msg.Message = strings.Repeat("1", crosschaintypes.MaxMessageLength+1)
560+
ob.zetacore.MockGetCctxByHash(msg.Digest(), errors.New("not found"))
556561

557562
// post vote inbound
558563
ballot, err := ob.PostVoteInbound(context.TODO(), &msg, 100000)
559564
require.NoError(t, err)
560565
require.Empty(t, ballot)
561566
})
567+
568+
t.Run("should not post vote cctx already exists and ballot is not found", func(t *testing.T) {
569+
//Arrange
570+
// create observer
571+
ob := newTestSuite(t, chains.Ethereum)
572+
// create sample message with long Message
573+
msg := sample.InboundVote(coin.CoinType_Gas, chains.Ethereum.ChainId, chains.ZetaChainMainnet.ChainId)
574+
msg.Message = strings.Repeat("1", crosschaintypes.MaxMessageLength+1)
575+
ob.zetacore.MockGetCctxByHash(msg.Digest(), nil)
576+
ob.zetacore.MockGetBallotByID(msg.Digest(), status.Error(codes.NotFound, "not found ballot"))
577+
// Act
578+
ballot, err := ob.PostVoteInbound(context.TODO(), &msg, 100000)
579+
// Assert
580+
require.NoError(t, err)
581+
require.Equal(t, ballot, msg.Digest())
582+
})
562583
}
563584

564585
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
@@ -85,6 +85,7 @@ type ZetacoreClient interface {
8585
GetPendingNoncesByChain(ctx context.Context, chainID int64) (observertypes.PendingNonces, error)
8686

8787
GetCctxByNonce(ctx context.Context, chainID int64, nonce uint64) (*crosschaintypes.CrossChainTx, error)
88+
GetCctxByHash(ctx context.Context, sendHash string) (*crosschaintypes.CrossChainTx, error)
8889
GetOutboundTracker(ctx context.Context, chain chains.Chain, nonce uint64) (*crosschaintypes.OutboundTracker, error)
8990
GetAllOutboundTrackerByChain(
9091
ctx context.Context,
@@ -98,6 +99,7 @@ type ZetacoreClient interface {
9899
GetBTCTSSAddress(ctx context.Context, chainID int64) (string, error)
99100
GetZetaHotKeyBalance(ctx context.Context) (sdkmath.Int, error)
100101
GetInboundTrackersForChain(ctx context.Context, chainID int64) ([]crosschaintypes.InboundTracker, error)
102+
GetBallotByID(ctx context.Context, id string) (*observertypes.QueryBallotByIdentifierResponse, error)
101103

102104
GetUpgradePlan(ctx context.Context) (*upgradetypes.Plan, error)
103105

zetaclient/chains/ton/observer/inbound_test.go

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

144144
ts.MockGetBlockHeader(depositTX.BlockID)
145+
ts.MockGetCctxByHash()
145146

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

206207
ts.MockGetBlockHeader(depositAndCallTX.BlockID)
208+
ts.MockGetCctxByHash()
207209

208210
// ACT
209211
// Observe inbounds once
@@ -350,6 +352,7 @@ func TestInbound(t *testing.T) {
350352
for _, tx := range txs {
351353
ts.MockGetBlockHeader(tx.BlockID)
352354
}
355+
ts.MockGetCctxByHash()
353356

354357
// ACT
355358
// Observe inbounds once
@@ -417,6 +420,7 @@ func TestInboundTracker(t *testing.T) {
417420
})
418421
ts.MockGetTransaction(ts.gateway.AccountID(), txWithdrawal)
419422
ts.MockGetBlockHeader(txWithdrawal.BlockID)
423+
ts.MockGetCctxByHash()
420424

421425
// Given inbound trackers from zetacore
422426
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
@@ -38,7 +38,16 @@ func (_m *ZetacoreClient) WithPostVoteInbound(zetaTxHash string, ballotIndex str
3838
_m.On("PostVoteInbound", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
3939
Maybe().
4040
Return(zetaTxHash, ballotIndex, nil)
41+
return _m
42+
}
43+
44+
func (_m *ZetacoreClient) MockGetCctxByHash(cctxIndex string, err error) *ZetacoreClient {
45+
_m.On("GetCctxByHash", mock.Anything, cctxIndex).Return(nil, err)
46+
return _m
47+
}
4148

49+
func (_m *ZetacoreClient) MockGetBallotByID(ballotIndex string, err error) *ZetacoreClient {
50+
_m.On("GetBallotByID", mock.Anything, ballotIndex).Return(nil, err)
4251
return _m
4352
}
4453

0 commit comments

Comments
 (0)