code.vegaprotocol.io/vega@v0.79.0/core/protocolupgrade/engine_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 protocolupgrade_test
    17  
    18  import (
    19  	"context"
    20  	"testing"
    21  	"time"
    22  
    23  	bmocks "code.vegaprotocol.io/vega/core/broker/mocks"
    24  	"code.vegaprotocol.io/vega/core/events"
    25  	"code.vegaprotocol.io/vega/core/integration/stubs"
    26  	"code.vegaprotocol.io/vega/core/protocolupgrade"
    27  	snp "code.vegaprotocol.io/vega/core/snapshot"
    28  	"code.vegaprotocol.io/vega/core/stats"
    29  	"code.vegaprotocol.io/vega/libs/num"
    30  	vgtest "code.vegaprotocol.io/vega/libs/test"
    31  	"code.vegaprotocol.io/vega/logging"
    32  	"code.vegaprotocol.io/vega/paths"
    33  	eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1"
    34  
    35  	"github.com/golang/mock/gomock"
    36  	"github.com/stretchr/testify/assert"
    37  	"github.com/stretchr/testify/require"
    38  )
    39  
    40  type TestValidatorToplogy struct {
    41  	totalVotingPower int64
    42  }
    43  
    44  func (vt *TestValidatorToplogy) IsSelfTendermintValidator() bool          { return true }
    45  func (vt *TestValidatorToplogy) IsTendermintValidator(pubkey string) bool { return true }
    46  func (vt *TestValidatorToplogy) GetVotingPower(pubkey string) int64       { return 10 }
    47  func (vt *TestValidatorToplogy) GetTotalVotingPower() int64               { return vt.totalVotingPower }
    48  
    49  func testEngine(t *testing.T, vegaPath paths.Paths) (*protocolupgrade.Engine, *snp.Engine, *bmocks.MockBroker, *TestValidatorToplogy) {
    50  	t.Helper()
    51  	ctrl := gomock.NewController(t)
    52  	broker := bmocks.NewMockBroker(ctrl)
    53  	now := time.Now()
    54  	log := logging.NewTestLogger()
    55  	testTopology := &TestValidatorToplogy{}
    56  	engine := protocolupgrade.New(log, protocolupgrade.NewDefaultConfig(), broker, testTopology, "0.54.0")
    57  	engine.OnRequiredMajorityChanged(context.Background(), num.DecimalFromFloat(0.66))
    58  	timeService := stubs.NewTimeStub()
    59  	timeService.SetTime(now)
    60  	statsData := stats.New(log, stats.NewDefaultConfig())
    61  	config := snp.DefaultConfig()
    62  	snapshotEngine, err := snp.NewEngine(vegaPath, config, log, timeService, statsData.Blockchain)
    63  	require.NoError(t, err)
    64  	snapshotEngine.AddProviders(engine)
    65  	return engine, snapshotEngine, broker, testTopology
    66  }
    67  
    68  func Test(t *testing.T) {
    69  	t.Run("Upgrade proposal gets rejected", testUpgradeProposalRejected)
    70  	t.Run("Upgrade proposal gets accepted", testProposalApproved)
    71  	t.Run("Multiple upgrade proposal get accepted, earliest is chosen", testMultiProposalApproved)
    72  	t.Run("Snapshot roundtrip test", testSnapshotRoundTrip)
    73  	t.Run("Revert a proposal", testRevertProposal)
    74  	t.Run("Downgrade is not allowed", testDowngradeVersionNotAllowed)
    75  }
    76  
    77  func testDowngradeVersionNotAllowed(t *testing.T) {
    78  	e, _, broker, _ := testEngine(t, paths.New(t.TempDir()))
    79  	var evts []events.Event
    80  	broker.EXPECT().Send(gomock.Any()).DoAndReturn(func(event events.Event) {
    81  		evts = append(evts, event)
    82  	}).AnyTimes()
    83  	// validator1 proposed an upgrade to v1 at block height 100
    84  	require.EqualError(t, e.UpgradeProposal(context.Background(), "pk1", 100, "0.53.0"), "upgrade version is too old (0.54.0 > 0.53.0)")
    85  }
    86  
    87  func testRevertProposal(t *testing.T) {
    88  	e, _, broker, _ := testEngine(t, paths.New(t.TempDir()))
    89  	var evts []events.Event
    90  	broker.EXPECT().Send(gomock.Any()).DoAndReturn(func(event events.Event) {
    91  		evts = append(evts, event)
    92  	}).AnyTimes()
    93  	// validator1 proposed an upgrade to v1 at block height 100
    94  	require.NoError(t, e.UpgradeProposal(context.Background(), "pk1", 100, "1.0.0"))
    95  	require.Equal(t, eventspb.ProtocolUpgradeProposalStatus_PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING, evts[0].StreamMessage().GetProtocolUpgradeEvent().Status)
    96  	require.Equal(t, 1, len(evts[0].StreamMessage().GetProtocolUpgradeEvent().Approvers))
    97  
    98  	// validator1 proposed an upgrade to v1 at block height 100
    99  	require.NoError(t, e.UpgradeProposal(context.Background(), "pk1", 100, "0.54.0"))
   100  
   101  	require.Equal(t, eventspb.ProtocolUpgradeProposalStatus_PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED, evts[1].StreamMessage().GetProtocolUpgradeEvent().Status)
   102  
   103  	require.Equal(t, 0, len(evts[1].StreamMessage().GetProtocolUpgradeEvent().Approvers))
   104  }
   105  
   106  func testUpgradeProposalRejected(t *testing.T) {
   107  	e, _, broker, testTopology := testEngine(t, paths.New(t.TempDir()))
   108  	var evts []events.Event
   109  	broker.EXPECT().Send(gomock.Any()).DoAndReturn(func(event events.Event) {
   110  		evts = append(evts, event)
   111  	}).AnyTimes()
   112  
   113  	// validator1 proposed an upgrade to v1 at block height 100
   114  	require.NoError(t, e.UpgradeProposal(context.Background(), "pk1", 100, "1.0.0"))
   115  	// validator2 agrees
   116  	require.NoError(t, e.UpgradeProposal(context.Background(), "pk2", 100, "1.0.0"))
   117  	// validator3 proposed an upgrade to v2 at block height 100
   118  	require.NoError(t, e.UpgradeProposal(context.Background(), "pk3", 100, "1.0.2"))
   119  
   120  	// we reached block 100 and only 50% (<66%) of the voting power agreed so the proposal is rejected
   121  	testTopology.totalVotingPower = 40
   122  	e.BeginBlock(context.Background(), 100)
   123  	require.Equal(t, 5, len(evts))
   124  
   125  	require.Equal(t, eventspb.ProtocolUpgradeProposalStatus_PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING, evts[0].StreamMessage().GetProtocolUpgradeEvent().Status)
   126  	require.Equal(t, 1, len(evts[0].StreamMessage().GetProtocolUpgradeEvent().Approvers))
   127  	require.Equal(t, "1.0.0", evts[0].StreamMessage().GetProtocolUpgradeEvent().VegaReleaseTag)
   128  
   129  	require.Equal(t, eventspb.ProtocolUpgradeProposalStatus_PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING, evts[1].StreamMessage().GetProtocolUpgradeEvent().Status)
   130  	require.Equal(t, 2, len(evts[1].StreamMessage().GetProtocolUpgradeEvent().Approvers))
   131  	require.Equal(t, "1.0.0", evts[1].StreamMessage().GetProtocolUpgradeEvent().VegaReleaseTag)
   132  
   133  	require.Equal(t, eventspb.ProtocolUpgradeProposalStatus_PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING, evts[2].StreamMessage().GetProtocolUpgradeEvent().Status)
   134  	require.Equal(t, 1, len(evts[2].StreamMessage().GetProtocolUpgradeEvent().Approvers))
   135  	require.Equal(t, "1.0.2", evts[2].StreamMessage().GetProtocolUpgradeEvent().VegaReleaseTag)
   136  
   137  	require.Equal(t, eventspb.ProtocolUpgradeProposalStatus_PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED, evts[3].StreamMessage().GetProtocolUpgradeEvent().Status)
   138  	require.Equal(t, "1.0.0", evts[3].StreamMessage().GetProtocolUpgradeEvent().VegaReleaseTag)
   139  
   140  	require.Equal(t, eventspb.ProtocolUpgradeProposalStatus_PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED, evts[4].StreamMessage().GetProtocolUpgradeEvent().Status)
   141  	require.Equal(t, "1.0.2", evts[4].StreamMessage().GetProtocolUpgradeEvent().VegaReleaseTag)
   142  
   143  	require.False(t, e.TimeForUpgrade())
   144  }
   145  
   146  func testProposalApproved(t *testing.T) {
   147  	e, _, broker, testTopology := testEngine(t, paths.New(t.TempDir()))
   148  	var evts []events.Event
   149  	broker.EXPECT().Send(gomock.Any()).DoAndReturn(func(event events.Event) {
   150  		evts = append(evts, event)
   151  	}).AnyTimes()
   152  
   153  	// validator1 proposed an upgrade to v1 at block height 100
   154  	require.NoError(t, e.UpgradeProposal(context.Background(), "pk1", 100, "1.0.0"))
   155  	// validator2 agrees
   156  	require.NoError(t, e.UpgradeProposal(context.Background(), "pk2", 100, "1.0.0"))
   157  	// validator3 agrees
   158  	require.NoError(t, e.UpgradeProposal(context.Background(), "pk3", 100, "1.0.0"))
   159  
   160  	// full house
   161  	testTopology.totalVotingPower = 30
   162  
   163  	e.BeginBlock(context.Background(), 50)
   164  	require.Equal(t, 3, len(evts))
   165  
   166  	require.Equal(t, eventspb.ProtocolUpgradeProposalStatus_PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING, evts[0].StreamMessage().GetProtocolUpgradeEvent().Status)
   167  	require.Equal(t, 1, len(evts[0].StreamMessage().GetProtocolUpgradeEvent().Approvers))
   168  	require.Equal(t, "1.0.0", evts[0].StreamMessage().GetProtocolUpgradeEvent().VegaReleaseTag)
   169  
   170  	require.Equal(t, eventspb.ProtocolUpgradeProposalStatus_PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING, evts[1].StreamMessage().GetProtocolUpgradeEvent().Status)
   171  	require.Equal(t, 2, len(evts[1].StreamMessage().GetProtocolUpgradeEvent().Approvers))
   172  	require.Equal(t, "1.0.0", evts[1].StreamMessage().GetProtocolUpgradeEvent().VegaReleaseTag)
   173  
   174  	require.Equal(t, eventspb.ProtocolUpgradeProposalStatus_PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING, evts[2].StreamMessage().GetProtocolUpgradeEvent().Status)
   175  	require.Equal(t, 3, len(evts[2].StreamMessage().GetProtocolUpgradeEvent().Approvers))
   176  	require.Equal(t, "1.0.0", evts[2].StreamMessage().GetProtocolUpgradeEvent().VegaReleaseTag)
   177  
   178  	e.BeginBlock(context.Background(), 100)
   179  	require.True(t, e.TimeForUpgrade())
   180  	e.Cleanup(context.Background())
   181  	require.Equal(t, 4, len(evts))
   182  	require.Equal(t, eventspb.ProtocolUpgradeProposalStatus_PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED, evts[3].StreamMessage().GetProtocolUpgradeEvent().Status)
   183  	require.Equal(t, "1.0.0", evts[3].StreamMessage().GetProtocolUpgradeEvent().VegaReleaseTag)
   184  
   185  	e.SetCoreReadyForUpgrade()
   186  	e.SetReadyForUpgrade()
   187  }
   188  
   189  func testMultiProposalApproved(t *testing.T) {
   190  	e, _, broker, testTopology := testEngine(t, paths.New(t.TempDir()))
   191  	var evts []events.Event
   192  	broker.EXPECT().Send(gomock.Any()).DoAndReturn(func(event events.Event) {
   193  		evts = append(evts, event)
   194  	}).AnyTimes()
   195  
   196  	testTopology.totalVotingPower = 20
   197  
   198  	// validator1 proposed an upgrade to v1 at block height 100
   199  	require.NoError(t, e.UpgradeProposal(context.Background(), "pk1", 100, "1.0.0"))
   200  	// validator2 agrees
   201  	require.NoError(t, e.UpgradeProposal(context.Background(), "pk2", 100, "1.0.0"))
   202  	// validator3 agrees
   203  	require.NoError(t, e.UpgradeProposal(context.Background(), "pk3", 100, "1.0.0"))
   204  
   205  	require.Equal(t, 3, len(evts[2].StreamMessage().GetProtocolUpgradeEvent().Approvers))
   206  
   207  	// validator1 also proposed an upgrade to v1 at block height 90
   208  	require.NoError(t, e.UpgradeProposal(context.Background(), "pk1", 90, "1.0.1"))
   209  
   210  	// the new proposal from pk1 voids their approval of the former proposal
   211  	require.Equal(t, 2, len(evts[4].StreamMessage().GetProtocolUpgradeEvent().Approvers))
   212  
   213  	// validator2 agrees
   214  	require.NoError(t, e.UpgradeProposal(context.Background(), "pk2", 90, "1.0.1"))
   215  
   216  	// the new proposal from pk1 voids their approval of the former proposal
   217  	require.Equal(t, 1, len(evts[6].StreamMessage().GetProtocolUpgradeEvent().Approvers))
   218  
   219  	// validator3 agrees
   220  	require.NoError(t, e.UpgradeProposal(context.Background(), "pk3", 90, "1.0.1"))
   221  
   222  	// at this point there are no votes for the proposal for 1.0.0 so it gets removed
   223  
   224  	// the new proposal from pk1 voids their approval of the former proposal
   225  	require.Equal(t, 0, len(evts[8].StreamMessage().GetProtocolUpgradeEvent().Approvers))
   226  
   227  	e.BeginBlock(context.Background(), 55)
   228  	require.Equal(t, 9, len(evts))
   229  
   230  	e.BeginBlock(context.Background(), 90)
   231  	e.Cleanup(context.Background())
   232  	require.Equal(t, 10, len(evts))
   233  	require.True(t, e.TimeForUpgrade())
   234  
   235  	require.Equal(t, eventspb.ProtocolUpgradeProposalStatus_PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED, evts[9].StreamMessage().GetProtocolUpgradeEvent().Status)
   236  	require.Equal(t, "1.0.1", evts[9].StreamMessage().GetProtocolUpgradeEvent().VegaReleaseTag)
   237  	require.Equal(t, uint64(90), evts[9].StreamMessage().GetProtocolUpgradeEvent().UpgradeBlockHeight)
   238  }
   239  
   240  func testSnapshotRoundTrip(t *testing.T) {
   241  	ctx := vgtest.VegaContext("chainid", 100)
   242  
   243  	vegaPath := paths.New(t.TempDir())
   244  	puEngine1, snapshotEngine1, broker, _ := testEngine(t, vegaPath)
   245  	snapshotEngine1CloseFn := vgtest.OnlyOnce(snapshotEngine1.Close)
   246  	defer snapshotEngine1CloseFn()
   247  	require.NoError(t, snapshotEngine1.Start(ctx))
   248  
   249  	var evts []events.Event
   250  	broker.EXPECT().Send(gomock.Any()).DoAndReturn(func(event events.Event) {
   251  		evts = append(evts, event)
   252  	}).AnyTimes()
   253  
   254  	puEngine1.BeginBlock(ctx, 50)
   255  
   256  	// validator1 proposed an upgrade to v1 at block height 100
   257  	require.NoError(t, puEngine1.UpgradeProposal(context.Background(), "pk1", 100, "1.0.0"))
   258  	// validator2 agrees
   259  	require.NoError(t, puEngine1.UpgradeProposal(context.Background(), "pk2", 100, "1.0.0"))
   260  	// validator3 agrees
   261  	require.NoError(t, puEngine1.UpgradeProposal(context.Background(), "pk3", 100, "1.0.0"))
   262  
   263  	// validator1 also proposed an upgrade to v1 at block height 90
   264  	require.NoError(t, puEngine1.UpgradeProposal(context.Background(), "pk1", 90, "1.0.1"))
   265  	// validator2 agrees
   266  	require.NoError(t, puEngine1.UpgradeProposal(context.Background(), "pk2", 90, "1.0.1"))
   267  	// validator3 agrees
   268  	require.NoError(t, puEngine1.UpgradeProposal(context.Background(), "pk3", 90, "1.0.1"))
   269  
   270  	// take a snapshot
   271  	hash1, err := snapshotEngine1.SnapshotNow(ctx)
   272  	require.NoError(t, err)
   273  
   274  	puEngine1.BeginBlock(context.Background(), 91)
   275  
   276  	state1 := map[string][]byte{}
   277  	for _, key := range puEngine1.Keys() {
   278  		state, additionalProvider, err := puEngine1.GetState(key)
   279  		require.NoError(t, err)
   280  		assert.Empty(t, additionalProvider)
   281  		state1[key] = state
   282  	}
   283  
   284  	snapshotEngine1CloseFn()
   285  
   286  	puEngine2, snapshotEngine2, brokerLoad, _ := testEngine(t, vegaPath)
   287  	brokerLoad.EXPECT().Send(gomock.Any()).AnyTimes()
   288  
   289  	// This triggers the state restoration from the local snapshot.
   290  	require.NoError(t, snapshotEngine2.Start(ctx))
   291  
   292  	// Comparing the hash after restoration, to ensure it produces the same result.
   293  	hash2, _, _ := snapshotEngine2.Info()
   294  	require.Equal(t, hash1, hash2)
   295  
   296  	puEngine2.BeginBlock(context.Background(), 91)
   297  
   298  	state2 := map[string][]byte{}
   299  	for _, key := range puEngine2.Keys() {
   300  		state, additionalProvider, err := puEngine2.GetState(key)
   301  		require.NoError(t, err)
   302  		assert.Empty(t, additionalProvider)
   303  		state2[key] = state
   304  	}
   305  
   306  	for key := range state1 {
   307  		assert.Equalf(t, state1[key], state2[key], "Key %q does not have the same data", key)
   308  	}
   309  }