code.vegaprotocol.io/vega@v0.79.0/datanode/sqlstore/votes_test.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package sqlstore_test
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"math/rand"
    22  	"testing"
    23  	"time"
    24  
    25  	"code.vegaprotocol.io/vega/datanode/entities"
    26  	"code.vegaprotocol.io/vega/datanode/sqlstore"
    27  	"code.vegaprotocol.io/vega/protos/vega"
    28  
    29  	"github.com/google/go-cmp/cmp"
    30  	"github.com/google/go-cmp/cmp/cmpopts"
    31  	"github.com/shopspring/decimal"
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/stretchr/testify/require"
    34  )
    35  
    36  func addTestVote(t *testing.T,
    37  	ctx context.Context,
    38  	vs *sqlstore.Votes,
    39  	party entities.Party,
    40  	proposal entities.Proposal,
    41  	value entities.VoteValue,
    42  	block entities.Block,
    43  	txHash entities.TxHash,
    44  ) entities.Vote {
    45  	t.Helper()
    46  	r := entities.Vote{
    47  		PartyID:                     party.ID,
    48  		ProposalID:                  proposal.ID,
    49  		Value:                       value,
    50  		TotalGovernanceTokenBalance: decimal.NewFromInt(100),
    51  		TotalGovernanceTokenWeight:  decimal.NewFromFloat(0.1),
    52  		TotalEquityLikeShareWeight:  decimal.NewFromFloat(0.3),
    53  		VegaTime:                    block.VegaTime,
    54  		InitialTime:                 block.VegaTime,
    55  		TxHash:                      txHash,
    56  	}
    57  	err := vs.Add(ctx, r)
    58  	require.NoError(t, err)
    59  	return r
    60  }
    61  
    62  func voteLessThan(x, y entities.Vote) bool {
    63  	if x.PartyID.String() != y.PartyID.String() {
    64  		return x.PartyID.String() < y.PartyID.String()
    65  	}
    66  	return x.ProposalID.String() < y.ProposalID.String()
    67  }
    68  
    69  func assertVotesMatch(t *testing.T, expected, actual []entities.Vote) {
    70  	t.Helper()
    71  	assert.Empty(t, cmp.Diff(actual, expected, cmpopts.SortSlices(voteLessThan)))
    72  }
    73  
    74  func TestVotes(t *testing.T) {
    75  	ctx := tempTransaction(t)
    76  
    77  	partyStore := sqlstore.NewParties(connectionSource)
    78  	propStore := sqlstore.NewProposals(connectionSource)
    79  	voteStore := sqlstore.NewVotes(connectionSource)
    80  	blockStore := sqlstore.NewBlocks(connectionSource)
    81  	block1 := addTestBlock(t, ctx, blockStore)
    82  	block2 := addTestBlock(t, ctx, blockStore)
    83  
    84  	party1 := addTestParty(t, ctx, partyStore, block1)
    85  	party2 := addTestParty(t, ctx, partyStore, block1)
    86  	prop1 := addTestProposal(t, ctx, propStore, GenerateID(), party1, GenerateID(), block1, entities.ProposalStateEnacted, entities.ProposalRationale{ProposalRationale: &vega.ProposalRationale{Title: "myurl1.com", Description: "desc"}}, entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{Change: &vega.ProposalTerms_NewMarket{}}}, entities.ProposalErrorUnspecified, nil, entities.BatchProposalTerms{})
    87  	prop2 := addTestProposal(t, ctx, propStore, GenerateID(), party1, GenerateID(), block1, entities.ProposalStateEnacted, entities.ProposalRationale{ProposalRationale: &vega.ProposalRationale{Title: "myurl2.com", Description: "desc"}}, entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{Change: &vega.ProposalTerms_NewMarket{}}}, entities.ProposalErrorUnspecified, nil, entities.BatchProposalTerms{})
    88  
    89  	party1ID := party1.ID.String()
    90  	prop1ID := prop1.ID.String()
    91  
    92  	txHash := txHashFromString("tx_vote_1")
    93  	txHash2 := txHashFromString("tx_vote_2")
    94  
    95  	vote1 := addTestVote(t, ctx, voteStore, party1, prop1, entities.VoteValueYes, block1, txHash)
    96  	vote2 := addTestVote(t, ctx, voteStore, party1, prop2, entities.VoteValueYes, block1, txHash2)
    97  	// Change vote in same block
    98  	vote3 := addTestVote(t, ctx, voteStore, party2, prop1, entities.VoteValueYes, block1, txHashFromString("tx_vote_3"))
    99  	vote3b := addTestVote(t, ctx, voteStore, party2, prop1, entities.VoteValueNo, block1, txHashFromString("tx_vote_4"))
   100  	// Change vote in next block
   101  	vote4 := addTestVote(t, ctx, voteStore, party2, prop2, entities.VoteValueYes, block1, txHashFromString("tx_vote_5"))
   102  	vote4b := addTestVote(t, ctx, voteStore, party2, prop2, entities.VoteValueNo, block2, txHashFromString("tx_vote_6"))
   103  
   104  	_ = vote3
   105  	_ = vote4
   106  
   107  	t.Run("GetAll", func(t *testing.T) {
   108  		expected := []entities.Vote{vote1, vote2, vote3b, vote4b}
   109  		actual, err := voteStore.Get(ctx, nil, nil, nil)
   110  		require.NoError(t, err)
   111  		assertVotesMatch(t, expected, actual)
   112  	})
   113  
   114  	t.Run("GetByTxHash", func(t *testing.T) {
   115  		expected := []entities.Vote{vote1}
   116  		actual, err := voteStore.GetByTxHash(ctx, txHash)
   117  		require.NoError(t, err)
   118  		assertVotesMatch(t, expected, actual)
   119  
   120  		expected = []entities.Vote{vote2}
   121  		actual, err = voteStore.GetByTxHash(ctx, txHash2)
   122  		require.NoError(t, err)
   123  		assertVotesMatch(t, expected, actual)
   124  	})
   125  
   126  	t.Run("GetByProposal", func(t *testing.T) {
   127  		expected := []entities.Vote{vote1, vote3b}
   128  		actual, err := voteStore.Get(ctx, &prop1ID, nil, nil)
   129  		require.NoError(t, err)
   130  		assertVotesMatch(t, expected, actual)
   131  	})
   132  
   133  	t.Run("GetByParty", func(t *testing.T) {
   134  		expected := []entities.Vote{vote1, vote2}
   135  		actual, err := voteStore.Get(ctx, nil, &party1ID, nil)
   136  		require.NoError(t, err)
   137  		assertVotesMatch(t, expected, actual)
   138  	})
   139  
   140  	t.Run("GetByValue", func(t *testing.T) {
   141  		expected := []entities.Vote{vote3b, vote4b}
   142  		no := entities.VoteValueNo
   143  		actual, err := voteStore.Get(ctx, nil, nil, &no)
   144  		require.NoError(t, err)
   145  		assertVotesMatch(t, expected, actual)
   146  	})
   147  
   148  	t.Run("GetByEverything", func(t *testing.T) {
   149  		expected := []entities.Vote{vote1}
   150  		yes := entities.VoteValueYes
   151  		actual, err := voteStore.Get(ctx, &prop1ID, &party1ID, &yes)
   152  		require.NoError(t, err)
   153  		assertVotesMatch(t, expected, actual)
   154  	})
   155  
   156  	t.Run("GetConnectionByEverything", func(t *testing.T) {
   157  		expected := []entities.Vote{vote1}
   158  		actual, _, err := voteStore.GetConnection(ctx, &prop1ID, &party1ID, entities.DefaultCursorPagination(true))
   159  		require.NoError(t, err)
   160  		assertVotesMatch(t, expected, actual)
   161  	})
   162  }
   163  
   164  func setupPaginationTestVotes(t *testing.T, ctx context.Context) (*sqlstore.Votes, entities.Party, []entities.Vote) {
   165  	t.Helper()
   166  	votes := make([]entities.Vote, 0, 10)
   167  
   168  	partyStore := sqlstore.NewParties(connectionSource)
   169  	propStore := sqlstore.NewProposals(connectionSource)
   170  	voteStore := sqlstore.NewVotes(connectionSource)
   171  	blockStore := sqlstore.NewBlocks(connectionSource)
   172  
   173  	blockTime := time.Now()
   174  	block := addTestBlockForTime(t, ctx, blockStore, blockTime)
   175  	party := addTestParty(t, ctx, partyStore, block)
   176  
   177  	rand.Seed(time.Now().UnixNano())
   178  
   179  	for i := 0; i < 10; i++ {
   180  		blockTime = blockTime.Add(time.Second)
   181  		block = addTestBlockForTime(t, ctx, blockStore, blockTime)
   182  		prop := addTestProposal(t,
   183  			ctx,
   184  			propStore,
   185  			GenerateID(),
   186  			party,
   187  			GenerateID(),
   188  			block,
   189  			entities.ProposalStateEnacted,
   190  			entities.ProposalRationale{ProposalRationale: &vega.ProposalRationale{Title: fmt.Sprintf("myurl%02d.com", i+1), Description: "desc"}},
   191  			entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{Change: &vega.ProposalTerms_NewMarket{}}},
   192  			entities.ProposalErrorUnspecified,
   193  			nil, entities.BatchProposalTerms{},
   194  		)
   195  
   196  		voteValue := entities.VoteValueYes
   197  		if rand.Intn(100)%2 == 0 {
   198  			voteValue = entities.VoteValueNo
   199  		}
   200  
   201  		vote := addTestVote(t, ctx, voteStore, party, prop, voteValue, block, txHashFromString("tx_hash_1"))
   202  		votes = append(votes, vote)
   203  	}
   204  
   205  	return voteStore, party, votes
   206  }
   207  
   208  func TestVotesCursorPagination(t *testing.T) {
   209  	t.Run("Should return all votes if no pagination is provided", testVotesCursorPaginationNoPagination)
   210  	t.Run("Should return first page of votes if first is provided no after cursor", testVotesCursorPaginationFirstNoAfter)
   211  	t.Run("Should return requested page of votes if first is provided with after cursor", testVotesCursorPaginationFirstWithAfter)
   212  	t.Run("Should return last page of votes if last is provided no before cursor", testVotesCursorPaginationLastNoBefore)
   213  	t.Run("Should return requested page of votes if last is provided with before cursor", testVotesCursorPaginationLastWithBefore)
   214  
   215  	t.Run("Should return all votes if no pagination is provided - newest first", testVotesCursorPaginationNoPaginationNewestFirst)
   216  	t.Run("Should return first page of votes if first is provided no after cursor - newest first", testVotesCursorPaginationFirstNoAfterNewestFirst)
   217  	t.Run("Should return requested page of votes if first is provided with after cursor - newest first", testVotesCursorPaginationFirstWithAfterNewestFirst)
   218  	t.Run("Should return last page of votes if last is provided no before cursor - newest first", testVotesCursorPaginationLastNoBeforeNewestFirst)
   219  	t.Run("Should return requested page of votes if last is provided with before cursor - newest first", testVotesCursorPaginationLastWithBeforeNewestFirst)
   220  }
   221  
   222  func testVotesCursorPaginationNoPagination(t *testing.T) {
   223  	ctx := tempTransaction(t)
   224  
   225  	vs, party, votes := setupPaginationTestVotes(t, ctx)
   226  	pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, false)
   227  	require.NoError(t, err)
   228  	got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination)
   229  	require.NoError(t, err)
   230  	require.Equal(t, votes, got)
   231  	require.Equal(t, entities.PageInfo{
   232  		HasNextPage:     false,
   233  		HasPreviousPage: false,
   234  		StartCursor:     votes[0].Cursor().Encode(),
   235  		EndCursor:       votes[len(votes)-1].Cursor().Encode(),
   236  	}, pageInfo)
   237  }
   238  
   239  func testVotesCursorPaginationFirstNoAfter(t *testing.T) {
   240  	ctx := tempTransaction(t)
   241  
   242  	vs, party, votes := setupPaginationTestVotes(t, ctx)
   243  	first := int32(3)
   244  	pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, false)
   245  	require.NoError(t, err)
   246  	got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination)
   247  	require.NoError(t, err)
   248  	require.Equal(t, votes[:3], got)
   249  	require.Equal(t, entities.PageInfo{
   250  		HasNextPage:     true,
   251  		HasPreviousPage: false,
   252  		StartCursor:     votes[0].Cursor().Encode(),
   253  		EndCursor:       votes[2].Cursor().Encode(),
   254  	}, pageInfo)
   255  }
   256  
   257  func testVotesCursorPaginationFirstWithAfter(t *testing.T) {
   258  	ctx := tempTransaction(t)
   259  
   260  	vs, party, votes := setupPaginationTestVotes(t, ctx)
   261  	first := int32(3)
   262  	after := votes[2].Cursor().Encode()
   263  	pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, false)
   264  	require.NoError(t, err)
   265  	got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination)
   266  	require.NoError(t, err)
   267  	require.Equal(t, votes[3:6], got)
   268  	require.Equal(t, entities.PageInfo{
   269  		HasNextPage:     true,
   270  		HasPreviousPage: true,
   271  		StartCursor:     votes[3].Cursor().Encode(),
   272  		EndCursor:       votes[5].Cursor().Encode(),
   273  	}, pageInfo)
   274  }
   275  
   276  func testVotesCursorPaginationLastNoBefore(t *testing.T) {
   277  	ctx := tempTransaction(t)
   278  
   279  	vs, party, votes := setupPaginationTestVotes(t, ctx)
   280  	last := int32(3)
   281  	pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, false)
   282  	require.NoError(t, err)
   283  	got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination)
   284  	require.NoError(t, err)
   285  	require.Equal(t, votes[7:], got)
   286  	require.Equal(t, entities.PageInfo{
   287  		HasNextPage:     false,
   288  		HasPreviousPage: true,
   289  		StartCursor:     votes[7].Cursor().Encode(),
   290  		EndCursor:       votes[9].Cursor().Encode(),
   291  	}, pageInfo)
   292  }
   293  
   294  func testVotesCursorPaginationLastWithBefore(t *testing.T) {
   295  	ctx := tempTransaction(t)
   296  
   297  	vs, party, votes := setupPaginationTestVotes(t, ctx)
   298  	last := int32(3)
   299  	before := votes[7].Cursor().Encode()
   300  	pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, false)
   301  	require.NoError(t, err)
   302  	got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination)
   303  	require.NoError(t, err)
   304  	require.Equal(t, votes[4:7], got)
   305  	require.Equal(t, entities.PageInfo{
   306  		HasNextPage:     true,
   307  		HasPreviousPage: true,
   308  		StartCursor:     votes[4].Cursor().Encode(),
   309  		EndCursor:       votes[6].Cursor().Encode(),
   310  	}, pageInfo)
   311  }
   312  
   313  func testVotesCursorPaginationNoPaginationNewestFirst(t *testing.T) {
   314  	ctx := tempTransaction(t)
   315  
   316  	vs, party, votes := setupPaginationTestVotes(t, ctx)
   317  	votes = entities.ReverseSlice(votes)
   318  	pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true)
   319  	require.NoError(t, err)
   320  	got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination)
   321  	require.NoError(t, err)
   322  	require.Equal(t, votes, got)
   323  	require.Equal(t, entities.PageInfo{
   324  		HasNextPage:     false,
   325  		HasPreviousPage: false,
   326  		StartCursor:     votes[0].Cursor().Encode(),
   327  		EndCursor:       votes[len(votes)-1].Cursor().Encode(),
   328  	}, pageInfo)
   329  }
   330  
   331  func testVotesCursorPaginationFirstNoAfterNewestFirst(t *testing.T) {
   332  	ctx := tempTransaction(t)
   333  
   334  	vs, party, votes := setupPaginationTestVotes(t, ctx)
   335  	votes = entities.ReverseSlice(votes)
   336  	first := int32(3)
   337  	pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, true)
   338  	require.NoError(t, err)
   339  	got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination)
   340  	require.NoError(t, err)
   341  	require.Equal(t, votes[:3], got)
   342  	require.Equal(t, entities.PageInfo{
   343  		HasNextPage:     true,
   344  		HasPreviousPage: false,
   345  		StartCursor:     votes[0].Cursor().Encode(),
   346  		EndCursor:       votes[2].Cursor().Encode(),
   347  	}, pageInfo)
   348  }
   349  
   350  func testVotesCursorPaginationFirstWithAfterNewestFirst(t *testing.T) {
   351  	ctx := tempTransaction(t)
   352  
   353  	vs, party, votes := setupPaginationTestVotes(t, ctx)
   354  	votes = entities.ReverseSlice(votes)
   355  	first := int32(3)
   356  	after := votes[2].Cursor().Encode()
   357  	pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, true)
   358  	require.NoError(t, err)
   359  	got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination)
   360  	require.NoError(t, err)
   361  	require.Equal(t, votes[3:6], got)
   362  	require.Equal(t, entities.PageInfo{
   363  		HasNextPage:     true,
   364  		HasPreviousPage: true,
   365  		StartCursor:     votes[3].Cursor().Encode(),
   366  		EndCursor:       votes[5].Cursor().Encode(),
   367  	}, pageInfo)
   368  }
   369  
   370  func testVotesCursorPaginationLastNoBeforeNewestFirst(t *testing.T) {
   371  	ctx := tempTransaction(t)
   372  
   373  	vs, party, votes := setupPaginationTestVotes(t, ctx)
   374  	votes = entities.ReverseSlice(votes)
   375  	last := int32(3)
   376  	pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, true)
   377  	require.NoError(t, err)
   378  	got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination)
   379  	require.NoError(t, err)
   380  	require.Equal(t, votes[7:], got)
   381  	require.Equal(t, entities.PageInfo{
   382  		HasNextPage:     false,
   383  		HasPreviousPage: true,
   384  		StartCursor:     votes[7].Cursor().Encode(),
   385  		EndCursor:       votes[9].Cursor().Encode(),
   386  	}, pageInfo)
   387  }
   388  
   389  func testVotesCursorPaginationLastWithBeforeNewestFirst(t *testing.T) {
   390  	ctx := tempTransaction(t)
   391  
   392  	vs, party, votes := setupPaginationTestVotes(t, ctx)
   393  	votes = entities.ReverseSlice(votes)
   394  	last := int32(3)
   395  	before := votes[7].Cursor().Encode()
   396  	pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, true)
   397  	require.NoError(t, err)
   398  	got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination)
   399  	require.NoError(t, err)
   400  	require.Equal(t, votes[4:7], got)
   401  	require.Equal(t, entities.PageInfo{
   402  		HasNextPage:     true,
   403  		HasPreviousPage: true,
   404  		StartCursor:     votes[4].Cursor().Encode(),
   405  		EndCursor:       votes[6].Cursor().Encode(),
   406  	}, pageInfo)
   407  }
   408  
   409  func TestVoteValueEnum(t *testing.T) {
   410  	var voteValue vega.Vote_Value
   411  	values := getEnums(t, voteValue)
   412  	assert.Len(t, values, 3)
   413  	for v, value := range values {
   414  		t.Run(value, func(t *testing.T) {
   415  			ctx := tempTransaction(t)
   416  
   417  			partyStore := sqlstore.NewParties(connectionSource)
   418  			propStore := sqlstore.NewProposals(connectionSource)
   419  			voteStore := sqlstore.NewVotes(connectionSource)
   420  			blockStore := sqlstore.NewBlocks(connectionSource)
   421  			block := addTestBlock(t, ctx, blockStore)
   422  
   423  			party := addTestParty(t, ctx, partyStore, block)
   424  			proposal := addTestProposal(t, ctx, propStore, GenerateID(), party, GenerateID(), block, entities.ProposalStateEnacted, entities.ProposalRationale{ProposalRationale: &vega.ProposalRationale{Title: "myurl1.com", Description: "desc"}}, entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{Change: &vega.ProposalTerms_NewMarket{}}}, entities.ProposalErrorUnspecified, nil, entities.BatchProposalTerms{})
   425  
   426  			txHash := txHashFromString("tx_vote_1")
   427  			want := entities.Vote{
   428  				PartyID:                     party.ID,
   429  				ProposalID:                  proposal.ID,
   430  				Value:                       entities.VoteValue(v),
   431  				TotalGovernanceTokenBalance: decimal.NewFromInt(100),
   432  				TotalGovernanceTokenWeight:  decimal.NewFromFloat(0.1),
   433  				TotalEquityLikeShareWeight:  decimal.NewFromFloat(0.3),
   434  				VegaTime:                    block.VegaTime,
   435  				InitialTime:                 block.VegaTime,
   436  				TxHash:                      txHash,
   437  			}
   438  			require.NoError(t, voteStore.Add(ctx, want))
   439  			got, err := voteStore.GetByTxHash(ctx, txHash)
   440  			require.NoError(t, err)
   441  			assert.Len(t, got, 1)
   442  			assert.Equal(t, want.Value, got[0].Value)
   443  		})
   444  	}
   445  }