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 }