diff --git a/changelog.md b/changelog.md index 51fa015214..562422fcbc 100644 --- a/changelog.md +++ b/changelog.md @@ -28,6 +28,7 @@ ### Fixes * [2654](https://github.com/zeta-chain/node/pull/2654) - add validation for authorization list in when validating genesis state for authorization module +* [2674](https://github.com/zeta-chain/node/pull/2674) - allow operators to vote on ballots assosiated with discarded keygen without affecting the status of the current keygen. * [2672](https://github.com/zeta-chain/node/pull/2672) - check observer set for duplicates when adding a new observer or updating an existing one ## v19.0.0 diff --git a/x/observer/keeper/msg_server_vote_tss.go b/x/observer/keeper/msg_server_vote_tss.go index 617411d575..9455dfa25b 100644 --- a/x/observer/keeper/msg_server_vote_tss.go +++ b/x/observer/keeper/msg_server_vote_tss.go @@ -42,11 +42,6 @@ func (k msgServer) VoteTSS(goCtx context.Context, msg *types.MsgVoteTSS) (*types return &types.MsgVoteTSSResponse{}, errorsmod.Wrap(types.ErrKeygenNotFound, voteTSSid) } - // Use a separate transaction to update keygen status to pending when trying to change the TSS address. - if keygen.Status == types.KeygenStatus_KeyGenSuccess { - return &types.MsgVoteTSSResponse{}, errorsmod.Wrap(types.ErrKeygenCompleted, voteTSSid) - } - // GetBallot checks against the supported chains list before querying for Ballot. ballotCreated := false index := msg.Digest() @@ -94,6 +89,30 @@ func (k msgServer) VoteTSS(goCtx context.Context, msg *types.MsgVoteTSS) (*types }, nil } + // The ballot is finalized, we check if this is the correct ballot for updating the TSS + // The requirements are + // 1. The keygen is still pending + // 2. The keygen block number matches the ballot block number ,which makes sure this the correct ballot for the current keygen + + // Return without an error so the vote is added to the ballot + if keygen.Status != types.KeygenStatus_PendingKeygen { + // The response is used for testing only.Setting false for keygen success as the keygen has already been finalized and it doesnt matter what the final status is.We are just asserting that the keygen was previously finalized and is not in pending status. + return &types.MsgVoteTSSResponse{ + VoteFinalized: isFinalized, + BallotCreated: ballotCreated, + KeygenSuccess: false, + }, nil + } + + // For cases when an observer tries to vote for an older pending ballot, associated with a keygen that was discarded , we would return at this check while still adding the vote to the ballot + if msg.KeygenZetaHeight != keygen.BlockNumber { + return &types.MsgVoteTSSResponse{ + VoteFinalized: isFinalized, + BallotCreated: ballotCreated, + KeygenSuccess: false, + }, nil + } + // Set TSS only on success, set keygen either way. // Keygen block can be updated using a policy transaction if keygen fails. keygenSuccess := false diff --git a/x/observer/keeper/msg_server_vote_tss_test.go b/x/observer/keeper/msg_server_vote_tss_test.go index f44ceaa07d..2d9466e89c 100644 --- a/x/observer/keeper/msg_server_vote_tss_test.go +++ b/x/observer/keeper/msg_server_vote_tss_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "fmt" "math" "testing" @@ -16,19 +17,24 @@ import ( func TestMsgServer_VoteTSS(t *testing.T) { t.Run("fail if node account not found", func(t *testing.T) { + // ARRANGE k, ctx, _, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) + // ACT _, err := srv.VoteTSS(ctx, &types.MsgVoteTSS{ Creator: sample.AccAddress(), TssPubkey: sample.Tss().TssPubkey, KeygenZetaHeight: 42, Status: chains.ReceiveStatus_success, }) + + // ASSERT require.ErrorIs(t, err, sdkerrors.ErrorInvalidSigner) }) t.Run("fail if keygen is not found", func(t *testing.T) { + // ARRANGE k, ctx, _, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) @@ -36,16 +42,20 @@ func TestMsgServer_VoteTSS(t *testing.T) { nodeAcc := sample.NodeAccount() k.SetNodeAccount(ctx, *nodeAcc) + // ACT _, err := srv.VoteTSS(ctx, &types.MsgVoteTSS{ Creator: nodeAcc.Operator, TssPubkey: sample.Tss().TssPubkey, KeygenZetaHeight: 42, Status: chains.ReceiveStatus_success, }) + + // ASSERT require.ErrorIs(t, err, types.ErrKeygenNotFound) }) t.Run("fail if keygen already completed ", func(t *testing.T) { + // ARRANGE k, ctx, _, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) @@ -53,30 +63,43 @@ func TestMsgServer_VoteTSS(t *testing.T) { nodeAcc := sample.NodeAccount() keygen := sample.Keygen(t) keygen.Status = types.KeygenStatus_KeyGenSuccess + keygen.BlockNumber = 42 k.SetNodeAccount(ctx, *nodeAcc) k.SetKeygen(ctx, *keygen) + // ACT _, err := srv.VoteTSS(ctx, &types.MsgVoteTSS{ Creator: nodeAcc.Operator, TssPubkey: sample.Tss().TssPubkey, KeygenZetaHeight: 42, Status: chains.ReceiveStatus_success, }) - require.ErrorIs(t, err, types.ErrKeygenCompleted) + + // ASSERT + // keygen is already completed, but the vote can still be added if the operator has not voted yet + require.NoError(t, err) + ballot, found := k.GetBallot(ctx, fmt.Sprintf("%d-%s", 42, "tss-keygen")) + require.True(t, found) + require.EqualValues(t, types.BallotStatus_BallotFinalized_SuccessObservation, ballot.BallotStatus) + require.True(t, ballot.HasVoted(nodeAcc.Operator)) }) t.Run("can create a new ballot, vote success and finalize", func(t *testing.T) { + // ARRANGE k, ctx, _, _ := keepertest.ObserverKeeper(t) - ctx = ctx.WithBlockHeight(42) + finalizingHeight := int64(55) + ctx = ctx.WithBlockHeight(finalizingHeight) srv := keeper.NewMsgServerImpl(*k) // setup state nodeAcc := sample.NodeAccount() keygen := sample.Keygen(t) keygen.Status = types.KeygenStatus_PendingKeygen + keygen.BlockNumber = 42 k.SetNodeAccount(ctx, *nodeAcc) k.SetKeygen(ctx, *keygen) + // ACT // there is a single node account, so the ballot will be created and finalized in a single vote res, err := srv.VoteTSS(ctx, &types.MsgVoteTSS{ Creator: nodeAcc.Operator, @@ -84,8 +107,9 @@ func TestMsgServer_VoteTSS(t *testing.T) { KeygenZetaHeight: 42, Status: chains.ReceiveStatus_success, }) - require.NoError(t, err) + // ASSERT + require.NoError(t, err) // check response require.True(t, res.BallotCreated) require.True(t, res.VoteFinalized) @@ -95,10 +119,17 @@ func TestMsgServer_VoteTSS(t *testing.T) { newKeygen, found := k.GetKeygen(ctx) require.True(t, found) require.EqualValues(t, types.KeygenStatus_KeyGenSuccess, newKeygen.Status) - require.EqualValues(t, ctx.BlockHeight(), newKeygen.BlockNumber) + require.EqualValues(t, finalizingHeight, newKeygen.BlockNumber) + + // check tss updated + tss, found := k.GetTSS(ctx) + require.True(t, found) + require.Equal(t, tss.KeyGenZetaHeight, int64(42)) + require.Equal(t, tss.FinalizedZetaHeight, finalizingHeight) }) t.Run("can create a new ballot, vote failure and finalize", func(t *testing.T) { + // ARRANGE k, ctx, _, _ := keepertest.ObserverKeeper(t) ctx = ctx.WithBlockHeight(42) srv := keeper.NewMsgServerImpl(*k) @@ -106,10 +137,12 @@ func TestMsgServer_VoteTSS(t *testing.T) { // setup state nodeAcc := sample.NodeAccount() keygen := sample.Keygen(t) + keygen.BlockNumber = 42 keygen.Status = types.KeygenStatus_PendingKeygen k.SetNodeAccount(ctx, *nodeAcc) k.SetKeygen(ctx, *keygen) + // ACT // there is a single node account, so the ballot will be created and finalized in a single vote res, err := srv.VoteTSS(ctx, &types.MsgVoteTSS{ Creator: nodeAcc.Operator, @@ -117,8 +150,9 @@ func TestMsgServer_VoteTSS(t *testing.T) { KeygenZetaHeight: 42, Status: chains.ReceiveStatus_failed, }) - require.NoError(t, err) + // ASSERT + require.NoError(t, err) // check response require.True(t, res.BallotCreated) require.True(t, res.VoteFinalized) @@ -131,7 +165,135 @@ func TestMsgServer_VoteTSS(t *testing.T) { require.EqualValues(t, math.MaxInt64, newKeygen.BlockNumber) }) - t.Run("can create a new ballot, vote without finalizing, then add vote and finalizing", func(t *testing.T) { + t.Run( + "can create a new ballot, vote without finalizing, then add final vote to update keygen and set tss", + func(t *testing.T) { + // ARRANGE + k, ctx, _, _ := keepertest.ObserverKeeper(t) + finalizingHeight := int64(55) + ctx = ctx.WithBlockHeight(finalizingHeight) + srv := keeper.NewMsgServerImpl(*k) + + // setup state with 3 node accounts + nodeAcc1 := sample.NodeAccount() + nodeAcc2 := sample.NodeAccount() + nodeAcc3 := sample.NodeAccount() + keygen := sample.Keygen(t) + keygen.BlockNumber = 42 + keygen.Status = types.KeygenStatus_PendingKeygen + tss := sample.Tss() + k.SetNodeAccount(ctx, *nodeAcc1) + k.SetNodeAccount(ctx, *nodeAcc2) + k.SetNodeAccount(ctx, *nodeAcc3) + k.SetKeygen(ctx, *keygen) + + // ACT + // 1st vote: created ballot, but not finalized + res, err := srv.VoteTSS(ctx, &types.MsgVoteTSS{ + Creator: nodeAcc1.Operator, + TssPubkey: tss.TssPubkey, + KeygenZetaHeight: 42, + Status: chains.ReceiveStatus_success, + }) + require.NoError(t, err) + + // check response + require.True(t, res.BallotCreated) + require.False(t, res.VoteFinalized) + require.False(t, res.KeygenSuccess) + + // check keygen not updated + newKeygen, found := k.GetKeygen(ctx) + require.True(t, found) + require.EqualValues(t, types.KeygenStatus_PendingKeygen, newKeygen.Status) + + // 2nd vote: already created ballot, and not finalized + res, err = srv.VoteTSS(ctx, &types.MsgVoteTSS{ + Creator: nodeAcc2.Operator, + TssPubkey: tss.TssPubkey, + KeygenZetaHeight: 42, + Status: chains.ReceiveStatus_success, + }) + require.NoError(t, err) + + // check response + require.False(t, res.BallotCreated) + require.False(t, res.VoteFinalized) + require.False(t, res.KeygenSuccess) + + // check keygen not updated + newKeygen, found = k.GetKeygen(ctx) + require.True(t, found) + require.EqualValues(t, types.KeygenStatus_PendingKeygen, newKeygen.Status) + + // 3rd vote: finalize the ballot + res, err = srv.VoteTSS(ctx, &types.MsgVoteTSS{ + Creator: nodeAcc3.Operator, + TssPubkey: tss.TssPubkey, + KeygenZetaHeight: 42, + Status: chains.ReceiveStatus_success, + }) + require.NoError(t, err) + + // ASSERT + // check response + require.False(t, res.BallotCreated) + require.True(t, res.VoteFinalized) + require.True(t, res.KeygenSuccess) + + // check keygen updated + newKeygen, found = k.GetKeygen(ctx) + require.True(t, found) + require.EqualValues(t, types.KeygenStatus_KeyGenSuccess, newKeygen.Status) + require.EqualValues(t, ctx.BlockHeight(), newKeygen.BlockNumber) + + // check tss updated + tss, found = k.GetTSS(ctx) + require.True(t, found) + require.Equal(t, tss.KeyGenZetaHeight, int64(42)) + require.Equal(t, tss.FinalizedZetaHeight, finalizingHeight) + }, + ) + + t.Run("fail if voting fails", func(t *testing.T) { + // ARRANGE + k, ctx, _, _ := keepertest.ObserverKeeper(t) + ctx = ctx.WithBlockHeight(42) + srv := keeper.NewMsgServerImpl(*k) + + // setup state with two node accounts to not finalize the ballot + nodeAcc := sample.NodeAccount() + keygen := sample.Keygen(t) + keygen.Status = types.KeygenStatus_PendingKeygen + k.SetNodeAccount(ctx, *nodeAcc) + k.SetNodeAccount(ctx, *sample.NodeAccount()) + k.SetKeygen(ctx, *keygen) + + // add a first vote + res, err := srv.VoteTSS(ctx, &types.MsgVoteTSS{ + Creator: nodeAcc.Operator, + TssPubkey: sample.Tss().TssPubkey, + KeygenZetaHeight: 42, + Status: chains.ReceiveStatus_success, + }) + require.NoError(t, err) + require.False(t, res.VoteFinalized) + + // ACT + // vote again: voting should fail + _, err = srv.VoteTSS(ctx, &types.MsgVoteTSS{ + Creator: nodeAcc.Operator, + TssPubkey: sample.Tss().TssPubkey, + KeygenZetaHeight: 42, + Status: chains.ReceiveStatus_success, + }) + + // ASSERT + require.ErrorIs(t, err, types.ErrUnableToAddVote) + }) + + t.Run("can create a new ballot, without finalizing the older and then finalize older ballot", func(t *testing.T) { + // ARRANGE k, ctx, _, _ := keepertest.ObserverKeeper(t) ctx = ctx.WithBlockHeight(42) srv := keeper.NewMsgServerImpl(*k) @@ -148,6 +310,7 @@ func TestMsgServer_VoteTSS(t *testing.T) { k.SetNodeAccount(ctx, *nodeAcc3) k.SetKeygen(ctx, *keygen) + // ACT // 1st vote: created ballot, but not finalized res, err := srv.VoteTSS(ctx, &types.MsgVoteTSS{ Creator: nodeAcc1.Operator, @@ -186,7 +349,51 @@ func TestMsgServer_VoteTSS(t *testing.T) { require.True(t, found) require.EqualValues(t, types.KeygenStatus_PendingKeygen, newKeygen.Status) - // 3rd vote: finalize the ballot + keygen.Status = types.KeygenStatus_PendingKeygen + keygen.BlockNumber = 52 + k.SetKeygen(ctx, *keygen) + + // Start voting on a new ballot + tss2 := sample.Tss() + // 1st Vote on new ballot (acc1) + res, err = srv.VoteTSS(ctx, &types.MsgVoteTSS{ + Creator: nodeAcc1.Operator, + TssPubkey: tss2.TssPubkey, + KeygenZetaHeight: 52, + Status: chains.ReceiveStatus_success, + }) + require.NoError(t, err) + + // check response + require.True(t, res.BallotCreated) + require.False(t, res.VoteFinalized) + require.False(t, res.KeygenSuccess) + + // 2nd vote on new ballot: already created ballot, and not finalized (acc3) + res, err = srv.VoteTSS(ctx, &types.MsgVoteTSS{ + Creator: nodeAcc3.Operator, + TssPubkey: tss2.TssPubkey, + KeygenZetaHeight: 52, + Status: chains.ReceiveStatus_success, + }) + require.NoError(t, err) + + // check response + require.False(t, res.BallotCreated) + require.False(t, res.VoteFinalized) + require.False(t, res.KeygenSuccess) + + // check keygen status + newKeygen, found = k.GetKeygen(ctx) + require.True(t, found) + require.EqualValues(t, types.KeygenStatus_PendingKeygen, newKeygen.Status) + + // At this point, we have two ballots + // 1. Ballot for keygen 42 Voted : (acc1, acc2) + // 2. Ballot for keygen 52 Voted : (acc1, acc3) + + // 3rd vote on ballot 1: finalize the older ballot + res, err = srv.VoteTSS(ctx, &types.MsgVoteTSS{ Creator: nodeAcc3.Operator, TssPubkey: tss.TssPubkey, @@ -195,48 +402,317 @@ func TestMsgServer_VoteTSS(t *testing.T) { }) require.NoError(t, err) - // check response + // ASSERT + // Check response require.False(t, res.BallotCreated) require.True(t, res.VoteFinalized) - require.True(t, res.KeygenSuccess) + require.False(t, res.KeygenSuccess) + // Older ballot should be finalized which still keep keygen in pending state. + newKeygen, found = k.GetKeygen(ctx) + require.True(t, found) + require.EqualValues(t, types.KeygenStatus_PendingKeygen, newKeygen.Status) + + _, found = k.GetTSS(ctx) + require.False(t, found) + + oldBallot, found := k.GetBallot(ctx, fmt.Sprintf("%d-%s", 42, "tss-keygen")) + require.True(t, found) + require.EqualValues(t, types.BallotStatus_BallotFinalized_SuccessObservation, oldBallot.BallotStatus) + + newBallot, found := k.GetBallot(ctx, fmt.Sprintf("%d-%s", 52, "tss-keygen")) + require.True(t, found) + require.EqualValues(t, types.BallotStatus_BallotInProgress, newBallot.BallotStatus) + }) + + t.Run("can create a new ballot, vote without finalizing,then finalize newer ballot", func(t *testing.T) { + // ARRANGE + k, ctx, _, _ := keepertest.ObserverKeeper(t) + ctx = ctx.WithBlockHeight(42) + srv := keeper.NewMsgServerImpl(*k) + + // setup state with 3 node accounts + nodeAcc1 := sample.NodeAccount() + nodeAcc2 := sample.NodeAccount() + nodeAcc3 := sample.NodeAccount() + keygen := sample.Keygen(t) + keygen.Status = types.KeygenStatus_PendingKeygen + keygen.BlockNumber = 42 + tss := sample.Tss() + k.SetNodeAccount(ctx, *nodeAcc1) + k.SetNodeAccount(ctx, *nodeAcc2) + k.SetNodeAccount(ctx, *nodeAcc3) + k.SetKeygen(ctx, *keygen) + + // ACT + // 1st vote: created ballot, but not finalized + res, err := srv.VoteTSS(ctx, &types.MsgVoteTSS{ + Creator: nodeAcc1.Operator, + TssPubkey: tss.TssPubkey, + KeygenZetaHeight: 42, + Status: chains.ReceiveStatus_success, + }) + require.NoError(t, err) + + // check response + require.True(t, res.BallotCreated) + require.False(t, res.VoteFinalized) + require.False(t, res.KeygenSuccess) + + // check keygen not updated + newKeygen, found := k.GetKeygen(ctx) + require.True(t, found) + require.EqualValues(t, types.KeygenStatus_PendingKeygen, newKeygen.Status) + + // 2nd vote: already created ballot, and not finalized + res, err = srv.VoteTSS(ctx, &types.MsgVoteTSS{ + Creator: nodeAcc2.Operator, + TssPubkey: tss.TssPubkey, + KeygenZetaHeight: 42, + Status: chains.ReceiveStatus_success, + }) + require.NoError(t, err) + + // check response + require.False(t, res.BallotCreated) + require.False(t, res.VoteFinalized) + require.False(t, res.KeygenSuccess) // check keygen not updated newKeygen, found = k.GetKeygen(ctx) require.True(t, found) + require.EqualValues(t, types.KeygenStatus_PendingKeygen, newKeygen.Status) + + // Update keygen to 52 and start voting on new ballot + keygen.Status = types.KeygenStatus_PendingKeygen + keygen.BlockNumber = 52 + k.SetKeygen(ctx, *keygen) + + // Start voting on a new ballot + tss2 := sample.Tss() + // 1st Vote on new ballot (acc1) + res, err = srv.VoteTSS(ctx, &types.MsgVoteTSS{ + Creator: nodeAcc1.Operator, + TssPubkey: tss2.TssPubkey, + KeygenZetaHeight: 52, + Status: chains.ReceiveStatus_success, + }) + require.NoError(t, err) + + // check response + require.True(t, res.BallotCreated) + require.False(t, res.VoteFinalized) + require.False(t, res.KeygenSuccess) + + // 2nd vote on new ballot: already created ballot, and not finalized (acc3) + res, err = srv.VoteTSS(ctx, &types.MsgVoteTSS{ + Creator: nodeAcc3.Operator, + TssPubkey: tss2.TssPubkey, + KeygenZetaHeight: 52, + Status: chains.ReceiveStatus_success, + }) + require.NoError(t, err) + + // check response + require.False(t, res.BallotCreated) + require.False(t, res.VoteFinalized) + require.False(t, res.KeygenSuccess) + + // check keygen status + newKeygen, found = k.GetKeygen(ctx) + require.True(t, found) + require.EqualValues(t, types.KeygenStatus_PendingKeygen, newKeygen.Status) + + // At this point, we have two ballots + // 1. Ballot for keygen 42 Voted : (acc1, acc2) + // 2. Ballot for keygen 52 Voted : (acc1, acc3) + + // 3rd vote on ballot 2: finalize the newer ballot + + finalizingHeight := int64(55) + ctx = ctx.WithBlockHeight(finalizingHeight) + res, err = srv.VoteTSS(ctx, &types.MsgVoteTSS{ + Creator: nodeAcc2.Operator, + TssPubkey: tss2.TssPubkey, + KeygenZetaHeight: 52, + Status: chains.ReceiveStatus_success, + }) + require.NoError(t, err) + + // ASSERT + require.False(t, res.BallotCreated) + require.True(t, res.VoteFinalized) + require.True(t, res.KeygenSuccess) + // Newer ballot should be finalized which make keygen success + newKeygen, found = k.GetKeygen(ctx) + require.True(t, found) + require.EqualValues(t, finalizingHeight, newKeygen.BlockNumber) require.EqualValues(t, types.KeygenStatus_KeyGenSuccess, newKeygen.Status) - require.EqualValues(t, ctx.BlockHeight(), newKeygen.BlockNumber) + + tss, found = k.GetTSS(ctx) + require.True(t, found) + require.Equal(t, tss.KeyGenZetaHeight, int64(52)) + require.Equal(t, tss.FinalizedZetaHeight, finalizingHeight) + + oldBallot, found := k.GetBallot(ctx, fmt.Sprintf("%d-%s", 42, "tss-keygen")) + require.True(t, found) + require.EqualValues(t, types.BallotStatus_BallotInProgress, oldBallot.BallotStatus) + + newBallot, found := k.GetBallot(ctx, fmt.Sprintf("%d-%s", 52, "tss-keygen")) + require.True(t, found) + require.EqualValues(t, types.BallotStatus_BallotFinalized_SuccessObservation, newBallot.BallotStatus) }) - t.Run("fail if voting fails", func(t *testing.T) { + t.Run("add vote to a successful keygen", func(t *testing.T) { + // ARRANGE k, ctx, _, _ := keepertest.ObserverKeeper(t) ctx = ctx.WithBlockHeight(42) srv := keeper.NewMsgServerImpl(*k) - // setup state with two node accounts to not finalize the ballot - nodeAcc := sample.NodeAccount() + // setup state with 3 node accounts + nodeAcc1 := sample.NodeAccount() + nodeAcc2 := sample.NodeAccount() + nodeAcc3 := sample.NodeAccount() keygen := sample.Keygen(t) - keygen.Status = types.KeygenStatus_PendingKeygen - k.SetNodeAccount(ctx, *nodeAcc) - k.SetNodeAccount(ctx, *sample.NodeAccount()) + keygen.Status = types.KeygenStatus_KeyGenSuccess + tss := sample.Tss() + k.SetNodeAccount(ctx, *nodeAcc1) + k.SetNodeAccount(ctx, *nodeAcc2) + k.SetNodeAccount(ctx, *nodeAcc3) k.SetKeygen(ctx, *keygen) - // add a first vote + // ACT + // 1st vote: created ballot, but not finalized res, err := srv.VoteTSS(ctx, &types.MsgVoteTSS{ - Creator: nodeAcc.Operator, - TssPubkey: sample.Tss().TssPubkey, + Creator: nodeAcc1.Operator, + TssPubkey: tss.TssPubkey, KeygenZetaHeight: 42, Status: chains.ReceiveStatus_success, }) require.NoError(t, err) + + // check response + require.True(t, res.BallotCreated) require.False(t, res.VoteFinalized) + require.False(t, res.KeygenSuccess) - // vote again: voting should fail - _, err = srv.VoteTSS(ctx, &types.MsgVoteTSS{ - Creator: nodeAcc.Operator, - TssPubkey: sample.Tss().TssPubkey, + // check keygen not updated + newKeygen, found := k.GetKeygen(ctx) + require.True(t, found) + require.EqualValues(t, types.KeygenStatus_KeyGenSuccess, newKeygen.Status) + + // 2nd vote: already created ballot, and not finalized + res, err = srv.VoteTSS(ctx, &types.MsgVoteTSS{ + Creator: nodeAcc2.Operator, + TssPubkey: tss.TssPubkey, KeygenZetaHeight: 42, Status: chains.ReceiveStatus_success, }) - require.ErrorIs(t, err, types.ErrUnableToAddVote) + require.NoError(t, err) + + // check response + require.False(t, res.BallotCreated) + require.False(t, res.VoteFinalized) + require.False(t, res.KeygenSuccess) + + // check keygen not updated + newKeygen, found = k.GetKeygen(ctx) + require.True(t, found) + require.EqualValues(t, types.KeygenStatus_KeyGenSuccess, newKeygen.Status) + + // 3nd vote: already created ballot, and not finalized (acc3) + res, err = srv.VoteTSS(ctx, &types.MsgVoteTSS{ + Creator: nodeAcc3.Operator, + TssPubkey: tss.TssPubkey, + KeygenZetaHeight: 42, + Status: chains.ReceiveStatus_success, + }) + require.NoError(t, err) + + // check response + require.False(t, res.BallotCreated) + require.True(t, res.VoteFinalized) + require.False(t, res.KeygenSuccess) + + // check keygen not updated + newKeygen, found = k.GetKeygen(ctx) + require.True(t, found) + require.EqualValues(t, types.KeygenStatus_KeyGenSuccess, newKeygen.Status) + }) + + t.Run("add vote to a failed keygen ", func(t *testing.T) { + // ARRANGE + k, ctx, _, _ := keepertest.ObserverKeeper(t) + ctx = ctx.WithBlockHeight(42) + srv := keeper.NewMsgServerImpl(*k) + + // setup state with 3 node accounts + nodeAcc1 := sample.NodeAccount() + nodeAcc2 := sample.NodeAccount() + nodeAcc3 := sample.NodeAccount() + keygen := sample.Keygen(t) + keygen.Status = types.KeygenStatus_KeyGenFailed + tss := sample.Tss() + k.SetNodeAccount(ctx, *nodeAcc1) + k.SetNodeAccount(ctx, *nodeAcc2) + k.SetNodeAccount(ctx, *nodeAcc3) + k.SetKeygen(ctx, *keygen) + + // ACT + // 1st vote: created ballot, but not finalized + res, err := srv.VoteTSS(ctx, &types.MsgVoteTSS{ + Creator: nodeAcc1.Operator, + TssPubkey: tss.TssPubkey, + KeygenZetaHeight: 42, + Status: chains.ReceiveStatus_failed, + }) + require.NoError(t, err) + + // check response + require.True(t, res.BallotCreated) + require.False(t, res.VoteFinalized) + require.False(t, res.KeygenSuccess) + + // check keygen not updated + newKeygen, found := k.GetKeygen(ctx) + require.True(t, found) + require.EqualValues(t, types.KeygenStatus_KeyGenFailed, newKeygen.Status) + + // 2nd vote: already created ballot, and not finalized + res, err = srv.VoteTSS(ctx, &types.MsgVoteTSS{ + Creator: nodeAcc2.Operator, + TssPubkey: tss.TssPubkey, + KeygenZetaHeight: 42, + Status: chains.ReceiveStatus_failed, + }) + require.NoError(t, err) + + // check response + require.False(t, res.BallotCreated) + require.False(t, res.VoteFinalized) + require.False(t, res.KeygenSuccess) + + // check keygen not updated + newKeygen, found = k.GetKeygen(ctx) + require.True(t, found) + require.EqualValues(t, types.KeygenStatus_KeyGenFailed, newKeygen.Status) + + // 3nd vote: already created ballot, and not finalized (acc3) + res, err = srv.VoteTSS(ctx, &types.MsgVoteTSS{ + Creator: nodeAcc3.Operator, + TssPubkey: tss.TssPubkey, + KeygenZetaHeight: 42, + Status: chains.ReceiveStatus_failed, + }) + require.NoError(t, err) + + // check response + require.False(t, res.BallotCreated) + require.True(t, res.VoteFinalized) + require.False(t, res.KeygenSuccess) + + // check keygen not updated + newKeygen, found = k.GetKeygen(ctx) + require.True(t, found) + require.EqualValues(t, types.KeygenStatus_KeyGenFailed, newKeygen.Status) }) }