code.vegaprotocol.io/vega@v0.79.0/core/governance/snapshot_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 governance_test
    17  
    18  import (
    19  	"bytes"
    20  	"context"
    21  	"testing"
    22  	"time"
    23  
    24  	"code.vegaprotocol.io/vega/core/integration/stubs"
    25  	"code.vegaprotocol.io/vega/core/netparams"
    26  	"code.vegaprotocol.io/vega/core/snapshot"
    27  	"code.vegaprotocol.io/vega/core/stats"
    28  	"code.vegaprotocol.io/vega/core/types"
    29  	"code.vegaprotocol.io/vega/libs/proto"
    30  	vgrand "code.vegaprotocol.io/vega/libs/rand"
    31  	vgtest "code.vegaprotocol.io/vega/libs/test"
    32  	"code.vegaprotocol.io/vega/logging"
    33  	"code.vegaprotocol.io/vega/paths"
    34  	snapshotpb "code.vegaprotocol.io/vega/protos/vega/snapshot/v1"
    35  
    36  	"github.com/golang/mock/gomock"
    37  	"github.com/stretchr/testify/assert"
    38  	"github.com/stretchr/testify/require"
    39  )
    40  
    41  var (
    42  	activeKey         = (&types.PayloadGovernanceActive{}).Key()
    43  	enactedKey        = (&types.PayloadGovernanceEnacted{}).Key()
    44  	nodeValidationKey = (&types.PayloadGovernanceNode{}).Key()
    45  )
    46  
    47  func TestGovernanceSnapshotProposalReject(t *testing.T) {
    48  	eng := getTestEngine(t, time.Now())
    49  
    50  	// get snapshot hash for active proposals
    51  	emptyState, _, err := eng.GetState(activeKey)
    52  	require.Nil(t, err)
    53  
    54  	// Submit a proposal
    55  	party := eng.newValidParty("a-valid-party", 123456789)
    56  	proposal := eng.newProposalForNewMarket(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true)
    57  	eng.ensureAllAssetEnabled(t)
    58  	eng.expectOpenProposalEvent(t, party.Id, proposal.ID)
    59  
    60  	toSubmit, err := eng.SubmitProposal(context.Background(), *types.ProposalSubmissionFromProposal(&proposal), proposal.ID, party.Id)
    61  	require.NoError(t, err)
    62  
    63  	// get snapshot hash for active proposals
    64  	s1, _, err := eng.GetState(activeKey)
    65  	require.Nil(t, err)
    66  
    67  	// Reject proposal
    68  	eng.expectRejectedProposalEvent(t, party.Id, proposal.ID, types.ProposalErrorCouldNotInstantiateMarket)
    69  	err = eng.RejectProposal(context.Background(), toSubmit.Proposal(), types.ProposalErrorCouldNotInstantiateMarket, assert.AnError)
    70  	require.NoError(t, err)
    71  
    72  	// Check its changed now proposal has been rejected
    73  	s2, _, err := eng.GetState(activeKey)
    74  	require.Nil(t, err)
    75  	require.False(t, bytes.Equal(s1, s2))
    76  
    77  	// Check the hash is the same before we submitted the proposal
    78  	require.True(t, bytes.Equal(emptyState, s2))
    79  }
    80  
    81  func TestGovernanceSnapshotProposalEnacted(t *testing.T) {
    82  	eng := getTestEngine(t, time.Now())
    83  
    84  	// get snapshot hashes
    85  	emptyActive, _, err := eng.GetState(activeKey)
    86  	require.Nil(t, err)
    87  
    88  	emptyEnacted, _, err := eng.GetState(enactedKey)
    89  	require.Nil(t, err)
    90  
    91  	proposer := eng.newValidParty("proposer", 1)
    92  	voter1 := eng.newValidPartyTimes("voter-1", 7, 2)
    93  	proposal := eng.newProposalForNewMarket(proposer.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true)
    94  
    95  	eng.ensureStakingAssetTotalSupply(t, 9)
    96  	eng.ensureAllAssetEnabled(t)
    97  	eng.expectOpenProposalEvent(t, proposer.Id, proposal.ID)
    98  
    99  	// make proposal
   100  	_, err = eng.submitProposal(t, proposal)
   101  
   102  	require.NoError(t, err)
   103  
   104  	eng.GetState(activeKey) // we call get state to get change back to false
   105  
   106  	// vote for it
   107  	eng.expectVoteEvent(t, voter1.Id, proposal.ID)
   108  	err = eng.addYesVote(t, voter1.Id, proposal.ID)
   109  	require.NoError(t, err)
   110  
   111  	eng.GetState(activeKey) // we call get state to get change back to false
   112  
   113  	// chain update
   114  	eng.expectPassedProposalEvent(t, proposal.ID)
   115  	eng.expectTotalGovernanceTokenFromVoteEvents(t, "1", "7")
   116  
   117  	afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second)
   118  	eng.expectGetMarketState(t, proposal.ID)
   119  	eng.OnTick(context.Background(), afterClosing)
   120  
   121  	eng.GetState(activeKey) // we call get state to get change back to false
   122  
   123  	afterEnactment := time.Unix(proposal.Terms.EnactmentTimestamp, 0).Add(time.Second)
   124  	eng.OnTick(context.Background(), afterEnactment)
   125  
   126  	eng.GetState(activeKey) // we call get state to get change back to false
   127  
   128  	// check snapshot hashes (should have no active proposals and one enacted proposal)
   129  	activeHash, _, err := eng.GetState(activeKey)
   130  	require.Nil(t, err)
   131  	require.True(t, bytes.Equal(emptyActive, activeHash)) // active proposal should be gone now its enacted
   132  
   133  	enactedHash, _, err := eng.GetState(enactedKey)
   134  	require.Nil(t, err)
   135  	require.False(t, bytes.Equal(emptyEnacted, enactedHash))
   136  }
   137  
   138  func TestGovernanceSnapshotWithInternalTimeTerminationProposalEnacted(t *testing.T) {
   139  	eng := getTestEngine(t, time.Now())
   140  
   141  	// get snapshot hashes
   142  	emptyActive, _, err := eng.GetState(activeKey)
   143  	require.Nil(t, err)
   144  
   145  	emptyEnacted, _, err := eng.GetState(enactedKey)
   146  	require.Nil(t, err)
   147  
   148  	proposer := eng.newValidParty("proposer", 1)
   149  	voter1 := eng.newValidPartyTimes("voter-1", 7, 2)
   150  	proposal := eng.newProposalForNewMarket(proposer.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, false)
   151  
   152  	eng.ensureStakingAssetTotalSupply(t, 9)
   153  	eng.ensureAllAssetEnabled(t)
   154  	eng.expectOpenProposalEvent(t, proposer.Id, proposal.ID)
   155  
   156  	// make proposal
   157  	_, err = eng.submitProposal(t, proposal)
   158  
   159  	require.NoError(t, err)
   160  
   161  	eng.GetState(activeKey) // we call get state to get change back to false
   162  
   163  	// vote for it
   164  	eng.expectVoteEvent(t, voter1.Id, proposal.ID)
   165  	err = eng.addYesVote(t, voter1.Id, proposal.ID)
   166  	require.NoError(t, err)
   167  
   168  	eng.GetState(activeKey) // we call get state to get change back to false
   169  
   170  	// chain update
   171  	eng.expectPassedProposalEvent(t, proposal.ID)
   172  	eng.expectTotalGovernanceTokenFromVoteEvents(t, "1", "7")
   173  
   174  	afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second)
   175  	eng.expectGetMarketState(t, proposal.ID)
   176  	eng.OnTick(context.Background(), afterClosing)
   177  
   178  	eng.GetState(activeKey) // we call get state to get change back to false
   179  
   180  	afterEnactment := time.Unix(proposal.Terms.EnactmentTimestamp, 0).Add(time.Second)
   181  	eng.OnTick(context.Background(), afterEnactment)
   182  
   183  	eng.GetState(activeKey) // we call get state to get change back to false
   184  
   185  	// check snapshot hashes (should have no active proposals and one enacted proposal)
   186  	activeHash, _, err := eng.GetState(activeKey)
   187  	require.Nil(t, err)
   188  	require.True(t, bytes.Equal(emptyActive, activeHash)) // active proposal should be gone now its enacted
   189  
   190  	enactedHash, _, err := eng.GetState(enactedKey)
   191  	require.Nil(t, err)
   192  	require.False(t, bytes.Equal(emptyEnacted, enactedHash))
   193  }
   194  
   195  func TestGovernanceSnapshotNodeProposal(t *testing.T) {
   196  	eng := getTestEngine(t, time.Now())
   197  	defer eng.ctrl.Finish()
   198  
   199  	// get snapshot state for active proposals
   200  	emptyState, _, err := eng.GetState(nodeValidationKey)
   201  	require.Nil(t, err)
   202  
   203  	// Submit a proposal
   204  	party := eng.newValidParty("a-valid-party", 123456789)
   205  	proposal := eng.newProposalForNewAsset(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour))
   206  
   207  	eng.expectProposalWaitingForNodeVoteEvent(t, party.Id, proposal.ID)
   208  	eng.assets.EXPECT().NewAsset(gomock.Any(), gomock.Any(), gomock.Any()).Times(1)
   209  	eng.assets.EXPECT().Get(gomock.Any()).AnyTimes()
   210  	eng.witness.EXPECT().StartCheck(gomock.Any(), gomock.Any(), gomock.Any()).Times(1)
   211  
   212  	// submit new asset proposal
   213  	_, err = eng.SubmitProposal(context.Background(), *types.ProposalSubmissionFromProposal(&proposal), proposal.ID, party.Id)
   214  	require.Nil(t, err)
   215  
   216  	// vote on it even though its in waiting-for-node-vote-state
   217  	voter1 := vgrand.RandomStr(5)
   218  	eng.ensureTokenBalanceForParty(t, voter1, 1)
   219  	eng.expectVoteEvent(t, voter1, proposal.ID)
   220  	err = eng.addYesVote(t, voter1, proposal.ID)
   221  	require.NoError(t, err)
   222  
   223  	// get snapshot state for node proposals and hope its changed
   224  	s1, _, err := eng.GetState(nodeValidationKey)
   225  	require.Nil(t, err)
   226  	require.False(t, bytes.Equal(emptyState, s1))
   227  
   228  	// Get snapshot payload
   229  	state, _, err := eng.GetState(nodeValidationKey)
   230  	require.Nil(t, err)
   231  
   232  	snap := &snapshotpb.Payload{}
   233  	err = proto.Unmarshal(state, snap)
   234  	require.Nil(t, err)
   235  
   236  	snapEng := getTestEngine(t, time.Now())
   237  	defer snapEng.ctrl.Finish()
   238  
   239  	snapEng.assets.EXPECT().NewAsset(gomock.Any(), gomock.Any(), gomock.Any()).Times(1)
   240  	snapEng.witness.EXPECT().RestoreResource(gomock.Any(), gomock.Any()).Times(1)
   241  
   242  	// Load snapshot into a new engine
   243  	snapEng.broker.EXPECT().Send(gomock.Any()).Times(1)
   244  	_, err = snapEng.LoadState(
   245  		context.Background(),
   246  		types.PayloadFromProto(snap),
   247  	)
   248  	require.Nil(t, err)
   249  
   250  	s2, _, err := snapEng.GetState(nodeValidationKey)
   251  	require.Nil(t, err)
   252  	require.True(t, bytes.Equal(s1, s2))
   253  
   254  	// check the vote still exists
   255  	err = proto.Unmarshal(s2, snap)
   256  	require.Nil(t, err)
   257  	pp := types.PayloadFromProto(snap)
   258  	dd := pp.Data.(*types.PayloadGovernanceNode)
   259  	assert.Equal(t, 1, len(dd.GovernanceNode.ProposalData[0].Yes))
   260  }
   261  
   262  func TestGovernanceSnapshotRoundTrip(t *testing.T) {
   263  	activeKey := (&types.PayloadGovernanceActive{}).Key()
   264  	eng := getTestEngine(t, time.Now())
   265  	defer eng.ctrl.Finish()
   266  
   267  	// initial state
   268  	emptyState, _, err := eng.GetState(activeKey)
   269  	require.Nil(t, err)
   270  
   271  	proposer := eng.newValidParty("proposer", 1)
   272  	proposal := eng.newProposalForNewMarket(proposer.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true)
   273  	ctx := context.Background()
   274  
   275  	eng.ensureAllAssetEnabled(t)
   276  	eng.expectOpenProposalEvent(t, proposer.Id, proposal.ID)
   277  
   278  	_, err = eng.SubmitProposal(ctx, *types.ProposalSubmissionFromProposal(&proposal), proposal.ID, proposer.Id)
   279  	assert.Nil(t, err)
   280  
   281  	s1, _, err := eng.GetState(activeKey)
   282  	require.Nil(t, err)
   283  	assert.False(t, bytes.Equal(emptyState, s1))
   284  
   285  	// given
   286  	voter1 := vgrand.RandomStr(5)
   287  	eng.ensureTokenBalanceForParty(t, voter1, 1)
   288  	eng.expectVoteEvent(t, voter1, proposal.ID)
   289  	err = eng.addYesVote(t, voter1, proposal.ID)
   290  	require.NoError(t, err)
   291  	s2, _, err := eng.GetState(activeKey)
   292  	require.Nil(t, err)
   293  	assert.False(t, bytes.Equal(s1, s2))
   294  
   295  	snapEng := getTestEngine(t, time.Now())
   296  	defer snapEng.ctrl.Finish()
   297  
   298  	state, _, err := eng.GetState(activeKey)
   299  	require.Nil(t, err)
   300  
   301  	snap := &snapshotpb.Payload{}
   302  	err = proto.Unmarshal(state, snap)
   303  	require.Nil(t, err)
   304  
   305  	snapEng.broker.EXPECT().SendBatch(gomock.Any()).Times(2)
   306  	_, err = snapEng.LoadState(ctx, types.PayloadFromProto(snap))
   307  	require.Nil(t, err)
   308  
   309  	s3, _, err := snapEng.GetState(activeKey)
   310  	require.Nil(t, err)
   311  	require.True(t, bytes.Equal(s2, s3))
   312  }
   313  
   314  func TestGovernanceWithInternalTimeTerminationSnapshotRoundTrip(t *testing.T) {
   315  	activeKey := (&types.PayloadGovernanceActive{}).Key()
   316  	eng := getTestEngine(t, time.Now())
   317  	defer eng.ctrl.Finish()
   318  
   319  	// initial state
   320  	emptyState, _, err := eng.GetState(activeKey)
   321  	require.Nil(t, err)
   322  
   323  	proposer := eng.newValidParty("proposer", 1)
   324  	proposal := eng.newProposalForNewMarket(proposer.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, false)
   325  	ctx := context.Background()
   326  
   327  	eng.ensureAllAssetEnabled(t)
   328  	eng.expectOpenProposalEvent(t, proposer.Id, proposal.ID)
   329  
   330  	_, err = eng.SubmitProposal(ctx, *types.ProposalSubmissionFromProposal(&proposal), proposal.ID, proposer.Id)
   331  	assert.Nil(t, err)
   332  
   333  	s1, _, err := eng.GetState(activeKey)
   334  	require.Nil(t, err)
   335  	assert.False(t, bytes.Equal(emptyState, s1))
   336  
   337  	// given
   338  	voter1 := vgrand.RandomStr(5)
   339  	eng.ensureTokenBalanceForParty(t, voter1, 1)
   340  	eng.expectVoteEvent(t, voter1, proposal.ID)
   341  	err = eng.addYesVote(t, voter1, proposal.ID)
   342  	require.NoError(t, err)
   343  	s2, _, err := eng.GetState(activeKey)
   344  	require.Nil(t, err)
   345  	assert.False(t, bytes.Equal(s1, s2))
   346  
   347  	snapEng := getTestEngine(t, time.Now())
   348  	defer snapEng.ctrl.Finish()
   349  
   350  	state, _, err := eng.GetState(activeKey)
   351  	require.Nil(t, err)
   352  
   353  	snap := &snapshotpb.Payload{}
   354  	err = proto.Unmarshal(state, snap)
   355  	require.Nil(t, err)
   356  
   357  	snapEng.broker.EXPECT().SendBatch(gomock.Any()).Times(2)
   358  	_, err = snapEng.LoadState(ctx, types.PayloadFromProto(snap))
   359  	require.Nil(t, err)
   360  
   361  	s3, _, err := snapEng.GetState(activeKey)
   362  	require.Nil(t, err)
   363  	require.True(t, bytes.Equal(s2, s3))
   364  }
   365  
   366  func TestGovernanceSnapshotEmpty(t *testing.T) {
   367  	activeKey := (&types.PayloadGovernanceActive{}).Key()
   368  	eng := getTestEngine(t, time.Now())
   369  	defer eng.ctrl.Finish()
   370  
   371  	s, _, err := eng.GetState(activeKey)
   372  	require.Nil(t, err)
   373  	require.NotNil(t, s)
   374  
   375  	s, _, err = eng.GetState(enactedKey)
   376  	require.Nil(t, err)
   377  	require.NotNil(t, s)
   378  
   379  	s, _, err = eng.GetState(nodeValidationKey)
   380  	require.Nil(t, err)
   381  	require.NotNil(t, s)
   382  }
   383  
   384  func TestGovernanceSnapshotBatchProposals(t *testing.T) {
   385  	ctx := vgtest.VegaContext("chainid", 100)
   386  
   387  	log := logging.NewTestLogger()
   388  
   389  	vegaPath := paths.New(t.TempDir())
   390  
   391  	now := time.Now()
   392  	timeService := stubs.NewTimeStub()
   393  	timeService.SetTime(now)
   394  
   395  	statsData := stats.New(log, stats.NewDefaultConfig())
   396  
   397  	// Create the engines
   398  	govEngine1 := getTestEngine(t, now)
   399  
   400  	snapshotEngine1, err := snapshot.NewEngine(vegaPath, snapshot.DefaultConfig(), log, timeService, statsData.Blockchain)
   401  	require.NoError(t, err)
   402  
   403  	closeSnapshotEngine1 := vgtest.OnlyOnce(snapshotEngine1.Close)
   404  	defer closeSnapshotEngine1()
   405  
   406  	snapshotEngine1.AddProviders(govEngine1)
   407  
   408  	require.NoError(t, snapshotEngine1.Start(ctx))
   409  
   410  	batchProposalID1 := "batch-1"
   411  	party := govEngine1.newValidParty("a-valid-party", 123456789)
   412  	govEngine1.ensureAllAssetEnabled(t)
   413  
   414  	govEngine1.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   415  	govEngine1.expectOpenProposalEvent(t, party.Id, batchProposalID1)
   416  
   417  	proposalNow := govEngine1.tsvc.GetTimeNow().Add(2 * time.Hour)
   418  
   419  	sub1 := govEngine1.newBatchSubmission(
   420  		proposalNow.Add(48*time.Hour).Unix(),
   421  		govEngine1.newProposalForNewMarket(party.Id, proposalNow, nil, nil, true),
   422  		govEngine1.newProposalForNetParam(party.Id, netparams.MarketAuctionMaximumDuration, "10h", now),
   423  	)
   424  	_, err = govEngine1.SubmitBatchProposal(ctx, sub1, batchProposalID1, party.Id)
   425  	require.NoError(t, err)
   426  
   427  	batchProposalID2 := "batch-2"
   428  	govEngine1.expectOpenProposalEvent(t, party.Id, batchProposalID2)
   429  
   430  	sub2 := govEngine1.newBatchSubmission(
   431  		proposalNow.Add(49*time.Hour).Unix(),
   432  		govEngine1.newProposalForNewMarket(party.Id, now, nil, nil, true),
   433  		govEngine1.newProposalForNetParam(party.Id, netparams.MarketAuctionMaximumDuration, "10h", now),
   434  	)
   435  	_, err = govEngine1.SubmitBatchProposal(ctx, sub2, batchProposalID2, party.Id)
   436  	require.NoError(t, err)
   437  
   438  	hash1, err := snapshotEngine1.SnapshotNow(ctx)
   439  	require.NoError(t, err)
   440  
   441  	batchProposalID3 := "batch-3"
   442  	govEngine1.expectOpenProposalEvent(t, party.Id, batchProposalID3)
   443  
   444  	sub3 := govEngine1.newBatchSubmission(
   445  		proposalNow.Add(50*time.Hour).Unix(),
   446  		govEngine1.newProposalForNewMarket(party.Id, now, nil, nil, true),
   447  		govEngine1.newProposalForNetParam(party.Id, netparams.MarketAuctionMaximumDuration, "10h", now),
   448  	)
   449  	_, err = govEngine1.SubmitBatchProposal(ctx, sub3, batchProposalID3, party.Id)
   450  	require.NoError(t, err)
   451  
   452  	state1 := map[string][]byte{}
   453  	for _, key := range govEngine1.Keys() {
   454  		state, additionalProvider, err := govEngine1.GetState(key)
   455  		require.NoError(t, err)
   456  		assert.Empty(t, additionalProvider)
   457  		state1[key] = state
   458  	}
   459  
   460  	closeSnapshotEngine1()
   461  
   462  	// Create the engines
   463  	govEngine2 := getTestEngine(t, now)
   464  	snapshotEngine2, err := snapshot.NewEngine(vegaPath, snapshot.DefaultConfig(), log, timeService, statsData.Blockchain)
   465  	require.NoError(t, err)
   466  	defer snapshotEngine2.Close()
   467  
   468  	snapshotEngine2.AddProviders(govEngine2.Engine)
   469  
   470  	govEngine2.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   471  
   472  	require.NoError(t, snapshotEngine2.Start(ctx))
   473  
   474  	hash2, _, _ := snapshotEngine2.Info()
   475  	require.Equal(t, hash1, hash2)
   476  
   477  	party2 := govEngine2.newValidParty("a-valid-party", 123456789)
   478  	govEngine2.ensureAllAssetEnabled(t)
   479  
   480  	govEngine2.expectOpenProposalEvent(t, party2.Id, batchProposalID3)
   481  	_, err = govEngine2.SubmitBatchProposal(ctx, sub3, batchProposalID3, party2.Id)
   482  	require.NoError(t, err)
   483  
   484  	state2 := map[string][]byte{}
   485  	for _, key := range govEngine2.Keys() {
   486  		state, additionalProvider, err := govEngine2.GetState(key)
   487  		require.NoError(t, err)
   488  		assert.Empty(t, additionalProvider)
   489  		state2[key] = state
   490  	}
   491  
   492  	for key := range state1 {
   493  		assert.Equalf(t, state1[key], state2[key], "Key %q does not have the same data", key)
   494  	}
   495  }