code.vegaprotocol.io/vega@v0.79.0/core/collateral/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 collateral_test 17 18 import ( 19 "bytes" 20 "context" 21 "strings" 22 "testing" 23 "time" 24 25 "code.vegaprotocol.io/vega/core/collateral" 26 "code.vegaprotocol.io/vega/core/integration/stubs" 27 "code.vegaprotocol.io/vega/core/snapshot" 28 "code.vegaprotocol.io/vega/core/stats" 29 "code.vegaprotocol.io/vega/core/types" 30 "code.vegaprotocol.io/vega/libs/config/encoding" 31 "code.vegaprotocol.io/vega/libs/num" 32 "code.vegaprotocol.io/vega/libs/proto" 33 vgtest "code.vegaprotocol.io/vega/libs/test" 34 "code.vegaprotocol.io/vega/logging" 35 "code.vegaprotocol.io/vega/paths" 36 37 "github.com/golang/mock/gomock" 38 "github.com/stretchr/testify/assert" 39 "github.com/stretchr/testify/require" 40 ) 41 42 func TestCheckpoint(t *testing.T) { 43 eng := getTestEngine(t) 44 ctx := context.Background() 45 46 party := "foo" 47 bal := num.NewUint(500) 48 insBal := num.NewUint(42) 49 // create party 50 eng.broker.EXPECT().Send(gomock.Any()).AnyTimes() 51 acc, err := eng.Engine.CreatePartyGeneralAccount(ctx, party, testMarketAsset) 52 assert.NoError(t, err) 53 err = eng.Engine.UpdateBalance(ctx, acc, bal) 54 assert.Nil(t, err) 55 56 // create a market then top insurance pool, 57 // this should get restored in the global pool 58 mktInsAcc, err := eng.GetMarketInsurancePoolAccount(testMarketID, testMarketAsset) 59 assert.NoError(t, err) 60 err = eng.Engine.UpdateBalance(ctx, mktInsAcc.ID, insBal) 61 assert.Nil(t, err) 62 63 pendingTransfersAcc := eng.GetPendingTransfersAccount(testMarketAsset) 64 assert.NoError(t, eng.UpdateBalance(ctx, pendingTransfersAcc.ID, num.NewUint(1789))) 65 66 pendingTransfersAcc = eng.GetPendingTransfersAccount(testMarketAsset) 67 assert.NoError(t, eng.UpdateBalance(ctx, pendingTransfersAcc.ID, num.NewUint(1789))) 68 69 // topup the global reward account 70 rewardAccount, err := eng.GetGlobalRewardAccount("VOTE") 71 assert.Nil(t, err) 72 err = eng.Engine.UpdateBalance(ctx, rewardAccount.ID, num.NewUint(10000)) 73 assert.Nil(t, err) 74 75 // topup the infra fee account for the test asset 76 for _, feeAccount := range eng.GetInfraFeeAccountIDs() { 77 // restricting the topup and the check later to the test asset because that gets enabled back in the test 78 if strings.Contains(feeAccount, testMarketAsset) { 79 err = eng.Engine.UpdateBalance(ctx, feeAccount, num.NewUint(12345)) 80 require.NoError(t, err) 81 } 82 } 83 84 // topup some reward accounts for markets 85 makerReceivedFeeReward1, err := eng.Engine.GetOrCreateRewardAccount(ctx, "VOTE", "market1", types.AccountTypeMakerReceivedFeeReward) 86 require.NoError(t, err) 87 err = eng.Engine.UpdateBalance(ctx, makerReceivedFeeReward1.ID, num.NewUint(11111)) 88 require.NoError(t, err) 89 90 makerReceivedFeeReward2, err := eng.Engine.GetOrCreateRewardAccount(ctx, "VOTE", "market2", types.AccountTypeMakerReceivedFeeReward) 91 require.NoError(t, err) 92 err = eng.Engine.UpdateBalance(ctx, makerReceivedFeeReward2.ID, num.NewUint(22222)) 93 require.NoError(t, err) 94 95 makerPaidFeeReward1, err := eng.Engine.GetOrCreateRewardAccount(ctx, "VOTE", "market3", types.AccountTypeMakerPaidFeeReward) 96 require.NoError(t, err) 97 err = eng.Engine.UpdateBalance(ctx, makerPaidFeeReward1.ID, num.NewUint(33333)) 98 require.NoError(t, err) 99 100 makerPaidFeeReward2, err := eng.Engine.GetOrCreateRewardAccount(ctx, "VOTE", "market4", types.AccountTypeMakerPaidFeeReward) 101 require.NoError(t, err) 102 err = eng.Engine.UpdateBalance(ctx, makerPaidFeeReward2.ID, num.NewUint(44444)) 103 require.NoError(t, err) 104 105 lpFeeReward1, err := eng.Engine.GetOrCreateRewardAccount(ctx, "VOTE", "market5", types.AccountTypeLPFeeReward) 106 require.NoError(t, err) 107 err = eng.Engine.UpdateBalance(ctx, lpFeeReward1.ID, num.NewUint(55555)) 108 require.NoError(t, err) 109 110 lpFeeReward2, err := eng.Engine.GetOrCreateRewardAccount(ctx, "VOTE", "market6", types.AccountTypeLPFeeReward) 111 require.NoError(t, err) 112 err = eng.Engine.UpdateBalance(ctx, lpFeeReward2.ID, num.NewUint(66666)) 113 require.NoError(t, err) 114 115 marketBonusReward1, err := eng.Engine.GetOrCreateRewardAccount(ctx, "VOTE", "market7", types.AccountTypeMarketProposerReward) 116 require.NoError(t, err) 117 err = eng.Engine.UpdateBalance(ctx, marketBonusReward1.ID, num.NewUint(77777)) 118 require.NoError(t, err) 119 120 marketBonusReward2, err := eng.Engine.GetOrCreateRewardAccount(ctx, "VOTE", "market8", types.AccountTypeMarketProposerReward) 121 require.NoError(t, err) 122 err = eng.Engine.UpdateBalance(ctx, marketBonusReward2.ID, num.NewUint(88888)) 123 require.NoError(t, err) 124 125 treasury, err := eng.Engine.GetNetworkTreasuryAccount("VOTE") 126 require.NoError(t, err) 127 err = eng.Engine.UpdateBalance(ctx, treasury.ID, num.NewUint(99999)) 128 require.NoError(t, err) 129 130 ins, err := eng.Engine.GetGlobalInsuranceAccount("VOTE") 131 require.NoError(t, err) 132 err = eng.Engine.UpdateBalance(ctx, ins.ID, num.NewUint(100900)) 133 require.NoError(t, err) 134 135 rewardAccounts := []*types.Account{makerReceivedFeeReward1, makerReceivedFeeReward2, makerPaidFeeReward1, makerPaidFeeReward2, lpFeeReward1, lpFeeReward2, marketBonusReward1, marketBonusReward2} 136 137 checkpoint, err := eng.Checkpoint() 138 require.NoError(t, err) 139 require.NotEmpty(t, checkpoint) 140 141 conf := collateral.NewDefaultConfig() 142 conf.Level = encoding.LogLevel{Level: logging.DebugLevel} 143 // system accounts created 144 loadEng := collateral.New(logging.NewTestLogger(), conf, eng.timeSvc, eng.broker) 145 enableGovernanceAsset(t, loadEng) 146 147 asset := types.Asset{ 148 ID: testMarketAsset, 149 Details: &types.AssetDetails{ 150 Symbol: testMarketAsset, 151 }, 152 } 153 // we need to enable the assets before being able to load the balances 154 loadEng.EnableAsset(ctx, asset) 155 require.NoError(t, err) 156 157 err = loadEng.Load(ctx, checkpoint) 158 require.NoError(t, err) 159 loadedPartyAcc, err := loadEng.GetPartyGeneralAccount(party, testMarketAsset) 160 require.NoError(t, err) 161 require.Equal(t, bal, loadedPartyAcc.Balance) 162 163 loadedIns, err := loadEng.GetGlobalInsuranceAccount(testMarketAsset) 164 require.NoError(t, err) 165 require.Equal(t, insBal, loadedIns.Balance) 166 167 loadedTreasury, err := loadEng.GetNetworkTreasuryAccount("VOTE") 168 require.NoError(t, err) 169 require.Equal(t, num.NewUint(99999), loadedTreasury.Balance) 170 171 loadedReward, err := loadEng.GetGlobalRewardAccount("VOTE") 172 require.NoError(t, err) 173 require.Equal(t, num.NewUint(10000), loadedReward.Balance) 174 175 loadedPendingTransfers := loadEng.GetPendingTransfersAccount(testMarketAsset) 176 require.Equal(t, num.NewUint(1789), loadedPendingTransfers.Balance) 177 178 for _, feeAcc := range loadEng.GetInfraFeeAccountIDs() { 179 if strings.Contains(feeAcc, testMarketAsset) { 180 acc, err := loadEng.GetAccountByID(feeAcc) 181 require.NoError(t, err) 182 require.Equal(t, num.NewUint(12345), acc.Balance) 183 } 184 } 185 186 for i, a := range rewardAccounts { 187 acc, err := loadEng.GetAccountByID(a.ID) 188 require.NoError(t, err) 189 require.Equal(t, num.NewUint(uint64((i+1)*11111)), acc.Balance) 190 } 191 } 192 193 func TestSnapshots(t *testing.T) { 194 t.Run("Creating a snapshot produces the same hash every single time", testSnapshotConsistentHash) 195 t.Run("Loading a snapshot should produce the same state - check snapshot after restore", testSnapshotRestore) 196 } 197 198 func testSnapshotConsistentHash(t *testing.T) { 199 mkt := "market1" 200 ctx := context.Background() 201 asset := types.Asset{ 202 ID: "foo", 203 Details: &types.AssetDetails{ 204 Name: "foo", 205 Symbol: "FOO", 206 Decimals: 5, 207 Quantum: num.DecimalFromFloat(1), 208 Source: types.AssetDetailsBuiltinAsset{ 209 BuiltinAsset: &types.BuiltinAsset{ 210 MaxFaucetAmountMint: num.NewUint(100000000), 211 }, 212 }, 213 }, 214 } 215 eng := getTestEngine(t) 216 // create assets, accounts, and update balances 217 eng.broker.EXPECT().Send(gomock.Any()).AnyTimes() 218 require.NoError(t, eng.EnableAsset(ctx, asset)) 219 parties := []string{ 220 "party1", 221 "party2", 222 "party3", 223 } 224 balances := map[string]map[types.AccountType]*num.Uint{ 225 parties[0]: { 226 types.AccountTypeGeneral: num.NewUint(500), 227 types.AccountTypeMargin: num.NewUint(500), 228 }, 229 parties[1]: { 230 types.AccountTypeGeneral: num.NewUint(1000), 231 }, 232 parties[2]: { 233 types.AccountTypeGeneral: num.NewUint(100000), 234 types.AccountTypeBond: num.NewUint(100), 235 types.AccountTypeMargin: num.NewUint(500), 236 }, 237 } 238 inc := num.NewUint(50) 239 var last string 240 for _, p := range parties { 241 // always create general account first 242 if gb, ok := balances[p][types.AccountTypeGeneral]; ok { 243 id, err := eng.CreatePartyGeneralAccount(ctx, p, asset.ID) 244 require.NoError(t, err) 245 require.NoError(t, eng.IncrementBalance(ctx, id, gb)) 246 last = id 247 } 248 for tp, b := range balances[p] { 249 switch tp { 250 case types.AccountTypeGeneral: 251 continue 252 case types.AccountTypeMargin: 253 id, err := eng.CreatePartyMarginAccount(ctx, p, mkt, asset.ID) 254 require.NoError(t, err) 255 require.NoError(t, eng.IncrementBalance(ctx, id, b)) 256 last = id 257 case types.AccountTypeBond: 258 id, err := eng.CreatePartyBondAccount(ctx, p, mkt, asset.ID) 259 require.NoError(t, err) 260 require.NoError(t, eng.IncrementBalance(ctx, id, b)) 261 last = id 262 } 263 } 264 } 265 keys := eng.Keys() 266 data := make(map[string][]byte, len(keys)) 267 for _, k := range keys { 268 state, _, err := eng.GetState(k) 269 require.NoError(t, err) 270 data[k] = state 271 } 272 // now no changes, check hashes again: 273 for k, d := range data { 274 state, _, err := eng.GetState(k) 275 require.NoError(t, err) 276 require.EqualValues(t, d, state) 277 } 278 // now change one account: 279 require.NoError(t, eng.IncrementBalance(ctx, last, inc)) 280 changes := 0 281 for k, d := range data { 282 got, _, err := eng.GetState(k) 283 require.NoError(t, err) 284 if !bytes.Equal(d, got) { 285 changes++ 286 } 287 } 288 require.Equal(t, 1, changes) 289 } 290 291 func testSnapshotRestore(t *testing.T) { 292 mkt := "market1" 293 ctx := context.Background() 294 erc20 := types.AssetDetailsErc20{ 295 ERC20: &types.ERC20{ 296 ContractAddress: "0x6d53C489bbda35B8096C8b4Cb362e2889F82E19B", 297 ChainID: "1", 298 }, 299 } 300 asset := types.Asset{ 301 ID: "foo", 302 Details: &types.AssetDetails{ 303 Name: "foo", 304 Symbol: "FOO", 305 Decimals: 5, 306 Quantum: num.DecimalFromFloat(1), 307 Source: erc20, 308 }, 309 } 310 eng := getTestEngine(t) 311 // create assets, accounts, and update balances 312 eng.broker.EXPECT().Send(gomock.Any()).AnyTimes() 313 require.NoError(t, eng.EnableAsset(ctx, asset)) 314 parties := []string{ 315 "party1", 316 "party2", 317 "party3", 318 "*", 319 } 320 balances := map[string]map[types.AccountType]*num.Uint{ 321 parties[0]: { 322 types.AccountTypeGeneral: num.NewUint(500), 323 types.AccountTypeMargin: num.NewUint(500), 324 }, 325 parties[1]: { 326 types.AccountTypeGeneral: num.NewUint(1000), 327 }, 328 parties[2]: { 329 types.AccountTypeGeneral: num.NewUint(100000), 330 types.AccountTypeBond: num.NewUint(100), 331 types.AccountTypeMargin: num.NewUint(500), 332 }, 333 "*": { 334 types.AccountTypeBuyBackFees: num.NewUint(1000), 335 }, 336 } 337 inc := num.NewUint(50) 338 var last string 339 for _, p := range parties { 340 // always create general account first 341 if gb, ok := balances[p][types.AccountTypeGeneral]; ok && p != "!" { 342 id, err := eng.CreatePartyGeneralAccount(ctx, p, asset.ID) 343 require.NoError(t, err) 344 require.NoError(t, eng.IncrementBalance(ctx, id, gb)) 345 last = id 346 } 347 for tp, b := range balances[p] { 348 switch tp { 349 case types.AccountTypeGeneral: 350 continue 351 case types.AccountTypeMargin: 352 id, err := eng.CreatePartyMarginAccount(ctx, p, mkt, asset.ID) 353 require.NoError(t, err) 354 require.NoError(t, eng.IncrementBalance(ctx, id, b)) 355 last = id 356 case types.AccountTypeBond: 357 id, err := eng.CreatePartyBondAccount(ctx, p, mkt, asset.ID) 358 require.NoError(t, err) 359 require.NoError(t, eng.IncrementBalance(ctx, id, b)) 360 last = id 361 case types.AccountTypeBuyBackFees: 362 id := eng.GetOrCreateBuyBackFeesAccountID(ctx, asset.ID) 363 require.NoError(t, eng.IncrementBalance(ctx, id, b)) 364 last = id 365 } 366 } 367 } 368 // earmark 500 out of the 1000 in the buy back account 369 _, err := eng.EarmarkForAutomatedPurchase(asset.ID, types.AccountTypeBuyBackFees, num.UintZero(), num.NewUint(500)) 370 require.NoError(t, err) 371 372 keys := eng.Keys() 373 payloads := make(map[string]*types.Payload, len(keys)) 374 data := make(map[string][]byte, len(keys)) 375 for _, k := range keys { 376 payloads[k] = &types.Payload{} 377 s, _, err := eng.GetState(k) 378 require.NoError(t, err) 379 data[k] = s 380 } 381 newEng := getTestEngine(t) 382 // we expect 2 batches of events to be sent 383 384 newEng.broker.EXPECT().Send(gomock.Any()).AnyTimes() 385 newEng.broker.EXPECT().SendBatch(gomock.Any()).Times(2) 386 for k, pl := range payloads { 387 state := data[k] 388 ptype := pl.IntoProto() 389 require.NoError(t, proto.Unmarshal(state, ptype)) 390 payloads[k] = types.PayloadFromProto(ptype) 391 _, err := newEng.LoadState(ctx, payloads[k]) 392 require.NoError(t, err) 393 } 394 for k, d := range data { 395 got, _, err := newEng.GetState(k) 396 require.NoError(t, err) 397 require.EqualValues(t, d, got) 398 } 399 require.NoError(t, eng.IncrementBalance(ctx, last, inc)) 400 // unearmark 200 on eng 401 require.NoError(t, eng.UnearmarkForAutomatedPurchase(asset.ID, types.AccountTypeBuyBackFees, num.NewUint(200))) 402 // now we expect 1 different hash 403 diff := 0 404 for k, h := range data { 405 old, _, err := eng.GetState(k) 406 require.NoError(t, err) 407 reload, _, err := newEng.GetState(k) 408 require.NoError(t, err) 409 if !bytes.Equal(h, old) { 410 diff++ 411 require.NotEqualValues(t, reload, old) 412 } 413 } 414 require.Equal(t, 1, diff) 415 require.NoError(t, newEng.IncrementBalance(ctx, last, inc)) 416 require.NoError(t, newEng.UnearmarkForAutomatedPurchase(asset.ID, types.AccountTypeBuyBackFees, num.NewUint(200))) 417 // now the state should match up once again 418 for k := range data { 419 old, _, err := eng.GetState(k) 420 require.NoError(t, err) 421 restore, _, err := newEng.GetState(k) 422 require.NoError(t, err) 423 require.EqualValues(t, old, restore) 424 } 425 } 426 427 func TestSnapshotRoundTripViaEngine(t *testing.T) { 428 mkt := "market1" 429 ctx := vgtest.VegaContext("chainid", 100) 430 431 erc20 := types.AssetDetailsErc20{ 432 ERC20: &types.ERC20{ 433 ContractAddress: "0x6d53C489bbda35B8096C8b4Cb362e2889F82E19B", 434 ChainID: "1", 435 }, 436 } 437 asset := types.Asset{ 438 ID: "foo", 439 Details: &types.AssetDetails{ 440 Name: "foo", 441 Symbol: "FOO", 442 Decimals: 5, 443 Quantum: num.DecimalFromFloat(1), 444 Source: erc20, 445 }, 446 } 447 collateralEngine1 := getTestEngine(t) 448 // create assets, accounts, and update balances 449 collateralEngine1.broker.EXPECT().Send(gomock.Any()).AnyTimes() 450 require.NoError(t, collateralEngine1.EnableAsset(ctx, asset)) 451 parties := []string{ 452 "party1", 453 "party2", 454 "party3", 455 } 456 balances := map[string]map[types.AccountType]*num.Uint{ 457 parties[0]: { 458 types.AccountTypeGeneral: num.NewUint(500), 459 types.AccountTypeMargin: num.NewUint(500), 460 }, 461 parties[1]: { 462 types.AccountTypeGeneral: num.NewUint(1000), 463 }, 464 parties[2]: { 465 types.AccountTypeGeneral: num.NewUint(100000), 466 types.AccountTypeBond: num.NewUint(100), 467 types.AccountTypeMargin: num.NewUint(500), 468 }, 469 } 470 for _, p := range parties { 471 // always create general account first 472 if gb, ok := balances[p][types.AccountTypeGeneral]; ok { 473 id, err := collateralEngine1.CreatePartyGeneralAccount(ctx, p, asset.ID) 474 require.NoError(t, err) 475 require.NoError(t, collateralEngine1.IncrementBalance(ctx, id, gb)) 476 } 477 for tp, b := range balances[p] { 478 switch tp { 479 case types.AccountTypeGeneral: 480 continue 481 case types.AccountTypeMargin: 482 id, err := collateralEngine1.CreatePartyMarginAccount(ctx, p, mkt, asset.ID) 483 require.NoError(t, err) 484 require.NoError(t, collateralEngine1.IncrementBalance(ctx, id, b)) 485 case types.AccountTypeBond: 486 id, err := collateralEngine1.CreatePartyBondAccount(ctx, p, mkt, asset.ID) 487 require.NoError(t, err) 488 require.NoError(t, collateralEngine1.IncrementBalance(ctx, id, b)) 489 } 490 } 491 } 492 493 newAsset := types.Asset{ 494 ID: "foo2", 495 Details: &types.AssetDetails{ 496 Name: "foo2", 497 Symbol: "FOO2", 498 Decimals: 5, 499 Quantum: num.DecimalFromFloat(2), 500 Source: erc20, 501 }, 502 } 503 504 // setup snapshot engine 505 now := time.Now() 506 log := logging.NewTestLogger() 507 timeService := stubs.NewTimeStub() 508 vegaPath := paths.New(t.TempDir()) 509 timeService.SetTime(now) 510 statsData := stats.New(log, stats.NewDefaultConfig()) 511 config := snapshot.DefaultConfig() 512 513 snapshotEngine1, err := snapshot.NewEngine(vegaPath, config, log, timeService, statsData.Blockchain) 514 require.NoError(t, err) 515 snapshotEngine1.AddProviders(collateralEngine1.Engine) 516 snapshotEngine1CloseFn := vgtest.OnlyOnce(snapshotEngine1.Close) 517 defer snapshotEngine1CloseFn() 518 519 require.NoError(t, snapshotEngine1.Start(ctx)) 520 521 hash1, err := snapshotEngine1.SnapshotNow(ctx) 522 require.NoError(t, err) 523 524 require.NoError(t, collateralEngine1.EnableAsset(ctx, newAsset)) 525 526 id, err := collateralEngine1.CreatePartyGeneralAccount(ctx, "party4", newAsset.ID) 527 require.NoError(t, err) 528 require.NoError(t, collateralEngine1.IncrementBalance(ctx, id, num.NewUint(100))) 529 530 state1 := map[string][]byte{} 531 for _, key := range collateralEngine1.Keys() { 532 state, additionalProvider, err := collateralEngine1.GetState(key) 533 require.NoError(t, err) 534 assert.Empty(t, additionalProvider) 535 state1[key] = state 536 } 537 538 snapshotEngine1CloseFn() 539 540 collateralEngine2 := getTestEngine(t) 541 collateralEngine2.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() 542 collateralEngine2.broker.EXPECT().Send(gomock.Any()).AnyTimes() 543 544 snapshotEngine2, err := snapshot.NewEngine(vegaPath, config, log, timeService, statsData.Blockchain) 545 require.NoError(t, err) 546 defer snapshotEngine2.Close() 547 548 snapshotEngine2.AddProviders(collateralEngine2.Engine) 549 550 // This triggers the state restoration from the local snapshot. 551 require.NoError(t, snapshotEngine2.Start(ctx)) 552 553 // Comparing the hash after restoration, to ensure it produces the same result. 554 hash2, _, _ := snapshotEngine2.Info() 555 require.Equal(t, hash1, hash2) 556 557 require.NoError(t, collateralEngine2.EnableAsset(ctx, newAsset)) 558 559 id2, err := collateralEngine2.CreatePartyGeneralAccount(ctx, "party4", newAsset.ID) 560 require.NoError(t, err) 561 require.NoError(t, collateralEngine2.IncrementBalance(ctx, id2, num.NewUint(100))) 562 563 state2 := map[string][]byte{} 564 for _, key := range collateralEngine2.Keys() { 565 state, additionalProvider, err := collateralEngine2.GetState(key) 566 require.NoError(t, err) 567 assert.Empty(t, additionalProvider) 568 state2[key] = state 569 } 570 571 for key := range state1 { 572 assert.Equalf(t, state1[key], state2[key], "Key %q does not have the same data", key) 573 } 574 }