github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/staking/candidate_center_test.go (about) 1 package staking 2 3 import ( 4 "math/big" 5 "testing" 6 "time" 7 8 "github.com/stretchr/testify/require" 9 10 "github.com/iotexproject/iotex-core/action" 11 "github.com/iotexproject/iotex-core/action/protocol" 12 "github.com/iotexproject/iotex-core/pkg/unit" 13 "github.com/iotexproject/iotex-core/test/identityset" 14 ) 15 16 // testEqual verifies m contains exactly the list 17 func testEqual(m *CandidateCenter, l CandidateList) bool { 18 if m.All().Len() != len(l) { 19 return false 20 } 21 for _, v := range l { 22 d := m.GetByOwner(v.Owner) 23 if d == nil { 24 return false 25 } 26 if !v.Equal(d) { 27 return false 28 } 29 30 d = m.GetByName(v.Name) 31 if d == nil { 32 return false 33 } 34 if !v.Equal(d) { 35 return false 36 } 37 38 d = m.GetBySelfStakingIndex(v.SelfStakeBucketIdx) 39 if d == nil && v.isSelfStakeBucketSettled() { 40 return false 41 } 42 if d != nil && !v.Equal(d) { 43 return false 44 } 45 } 46 return true 47 } 48 49 // testEqualAllCommit validates candidate center 50 // with original candidates = old, number of changed cand = change, and number of new cand = increase 51 func testEqualAllCommit(r *require.Assertions, m *CandidateCenter, old CandidateList, change, increase int, 52 ) (CandidateList, error) { 53 // capture all candidates 54 size := len(old) 55 list := m.All() 56 r.Equal(size+increase, len(list)) 57 r.Equal(size+increase, m.Size()) 58 all, err := list.toStateCandidateList() 59 r.NoError(err) 60 61 // number of changed cand = change 62 r.Equal(change, len(m.change.view())) 63 delta := m.Delta() 64 ser, err := delta.Serialize() 65 r.NoError(err) 66 67 // test abort the changes 68 r.NoError(m.SetDelta(nil)) 69 r.Equal(size, m.Size()) 70 // m equal to old list, not equal to current 71 r.True(testEqual(m, old)) 72 r.False(testEqual(m, list)) 73 r.Nil(m.Delta()) 74 75 // test commit 76 r.NoError(delta.Deserialize(ser)) 77 r.NoError(m.SetDelta(delta)) 78 r.NoError(m.LegacyCommit()) 79 r.NoError(m.LegacyCommit()) // commit is idempotent 80 r.Equal(size+increase, m.Size()) 81 // m equal to current list, not equal to old 82 r.True(testEqual(m, list)) 83 r.False(testEqual(m, old)) 84 85 // after commit All() is the same 86 list = m.All() 87 r.Equal(size+increase, len(list)) 88 r.Equal(size+increase, m.Size()) 89 all1, err := list.toStateCandidateList() 90 r.NoError(err) 91 r.Equal(all, all1) 92 return list, nil 93 } 94 95 func TestCandCenter(t *testing.T) { 96 r := require.New(t) 97 98 m, err := NewCandidateCenter(nil) 99 r.NoError(err) 100 for i, v := range testCandidates { 101 r.NoError(m.Upsert(testCandidates[i].d)) 102 r.True(m.ContainsName(v.d.Name)) 103 r.Equal(v.d, m.GetByName(v.d.Name)) 104 } 105 r.Equal(len(testCandidates), m.Size()) 106 107 // test export changes and commit 108 list := m.Delta() 109 r.NotNil(list) 110 r.Equal(len(list), m.Size()) 111 r.True(testEqual(m, list)) 112 r.NoError(m.SetDelta(list)) 113 r.NoError(m.LegacyCommit()) 114 r.Equal(len(testCandidates), m.Size()) 115 r.True(testEqual(m, list)) 116 old := m.All() 117 r.True(testEqual(m, old)) 118 119 // test existence 120 for _, v := range testCandidates { 121 r.True(m.ContainsName(v.d.Name)) 122 r.True(m.ContainsOwner(v.d.Owner)) 123 r.True(m.ContainsOperator(v.d.Operator)) 124 r.True(m.ContainsSelfStakingBucket(v.d.SelfStakeBucketIdx)) 125 r.Equal(v.d, m.GetByName(v.d.Name)) 126 r.Equal(v.d, m.GetByOwner(v.d.Owner)) 127 r.Equal(v.d, m.GetBySelfStakingIndex(v.d.SelfStakeBucketIdx)) 128 } 129 130 testDeltas := []*Candidate{ 131 &Candidate{ 132 Owner: identityset.Address(1), 133 Operator: identityset.Address(7), 134 Reward: identityset.Address(1), 135 Name: "update1", 136 Votes: big.NewInt(2), 137 SelfStakeBucketIdx: 1, 138 SelfStake: unit.ConvertIotxToRau(1200000), 139 }, 140 &Candidate{ 141 Owner: identityset.Address(2), 142 Operator: identityset.Address(8), 143 Reward: identityset.Address(1), 144 Name: "test2", 145 Votes: big.NewInt(3), 146 SelfStakeBucketIdx: 200, 147 SelfStake: unit.ConvertIotxToRau(1200000), 148 }, 149 &Candidate{ 150 Owner: identityset.Address(3), 151 Operator: identityset.Address(6), 152 Reward: identityset.Address(1), 153 Name: "test3", 154 Votes: big.NewInt(3), 155 SelfStakeBucketIdx: 3, 156 SelfStake: unit.ConvertIotxToRau(1200000), 157 }, 158 &Candidate{ 159 Owner: identityset.Address(4), 160 Operator: identityset.Address(10), 161 Reward: identityset.Address(1), 162 Name: "test4", 163 Votes: big.NewInt(1), 164 SelfStakeBucketIdx: 4, 165 SelfStake: unit.ConvertIotxToRau(1100000), 166 }, 167 168 &Candidate{ 169 Owner: identityset.Address(7), 170 Operator: identityset.Address(1), 171 Reward: identityset.Address(1), 172 Name: "new1", 173 Votes: big.NewInt(2), 174 SelfStakeBucketIdx: 6, 175 SelfStake: unit.ConvertIotxToRau(1200000), 176 }, 177 &Candidate{ 178 Owner: identityset.Address(8), 179 Operator: identityset.Address(2), 180 Reward: identityset.Address(1), 181 Name: "new2", 182 Votes: big.NewInt(3), 183 SelfStakeBucketIdx: 7, 184 SelfStake: unit.ConvertIotxToRau(1200000), 185 }, 186 &Candidate{ 187 Owner: identityset.Address(9), 188 Operator: identityset.Address(3), 189 Reward: identityset.Address(1), 190 Name: "new3", 191 Votes: big.NewInt(3), 192 SelfStakeBucketIdx: 8, 193 SelfStake: unit.ConvertIotxToRau(1200000), 194 }, 195 &Candidate{ 196 Owner: identityset.Address(10), 197 Operator: identityset.Address(4), 198 Reward: identityset.Address(1), 199 Name: "new4", 200 Votes: big.NewInt(1), 201 SelfStakeBucketIdx: 9, 202 SelfStake: unit.ConvertIotxToRau(1200000), 203 }, 204 } 205 206 for i := range testDeltas { 207 r.NoError(m.Upsert(testDeltas[i])) 208 } 209 list = m.All() 210 r.Equal(len(list), m.Size()) 211 r.True(testEqual(m, list)) 212 delta := m.Delta() 213 // 4 updates + 4 new 214 r.Equal(8, len(delta)) 215 r.Equal(len(testCandidates)+4, m.Size()) 216 217 // SetDelta using one's own Delta() does not change a thing 218 r.NoError(m.SetDelta(delta)) 219 r.Equal(len(list), m.Size()) 220 r.True(testEqual(m, list)) 221 r.NoError(m.LegacyCommit()) 222 r.Equal(len(list), m.Size()) 223 r.True(testEqual(m, list)) 224 225 // testDeltas[3] only updated self-stake 226 td3 := m.GetByName(testDeltas[3].Name) 227 r.Equal(td3, testDeltas[3]) 228 229 // test update existing and add new 230 // each time pick 2 random candidates to update, and add 2 new cand 231 for i := 0; i < 10; i++ { 232 size := len(list) 233 u1 := time.Now().Nanosecond() / 1000 % size 234 u2 := (u1 + 1) % size 235 d1 := list[u1].Clone() 236 d2 := list[u2].Clone() 237 // save the name, operator and self-stake 238 name1 := d1.Name 239 name2 := d2.Name 240 op1 := d1.Operator 241 op2 := d2.Operator 242 self1 := d1.SelfStakeBucketIdx 243 self2 := d2.SelfStakeBucketIdx 244 245 // d1 conflict with existing 246 conflict := list[(u1+3)%size] 247 d1.Name = conflict.Name 248 r.Equal(action.ErrInvalidCanName, m.Upsert(d1)) 249 d1.Name = name1 250 d1.Operator = conflict.Operator 251 r.Equal(ErrInvalidOperator, m.Upsert(d1)) 252 d1.Operator = op1 253 d1.SelfStakeBucketIdx = conflict.SelfStakeBucketIdx 254 r.Equal(ErrInvalidSelfStkIndex, m.Upsert(d1)) 255 d1.SelfStakeBucketIdx = self1 256 257 // no change yet, delta must be empty 258 r.Nil(m.Delta()) 259 260 // upsert(d1) 261 d1.Operator = identityset.Address(13 + i*2) // identityset[13]+ don't conflict with existing 262 r.NoError(m.Upsert(d1)) 263 d1.Name = d1.Operator.String()[:12] 264 r.NoError(m.Upsert(d1)) 265 266 // d2 conflict d1 267 d2.Name = d1.Name 268 r.Equal(action.ErrInvalidCanName, m.Upsert(d2)) 269 d2.Name = name2 270 d2.Operator = d1.Operator 271 r.Equal(ErrInvalidOperator, m.Upsert(d2)) 272 d2.Operator = op2 273 d2.SelfStakeBucketIdx = d1.SelfStakeBucketIdx 274 r.Equal(ErrInvalidSelfStkIndex, m.Upsert(d2)) 275 d2.SelfStakeBucketIdx = self2 276 // upsert(d2) 277 d2.Operator = identityset.Address(14 + i*2) 278 r.NoError(m.Upsert(d2)) 279 d2.Name = d2.Operator.String()[:12] 280 r.NoError(m.Upsert(d2)) 281 282 // new n1 conflict with existing 283 n1 := list[(u2+size/2)%size].Clone() 284 n1.Owner = identityset.Address(15 + i*2) 285 r.Equal(action.ErrInvalidCanName, m.Upsert(n1)) 286 n1.Name = name1 287 r.Equal(ErrInvalidOperator, m.Upsert(n1)) 288 n1.Operator = op1 289 r.Equal(ErrInvalidSelfStkIndex, m.Upsert(n1)) 290 n1.SelfStakeBucketIdx = uint64(size) 291 // upsert(n1) 292 r.NoError(m.Upsert(n1)) 293 294 // new n2 conflict with dirty d2 295 n2 := d2.Clone() 296 n2.Owner = identityset.Address(16 + i*2) 297 r.Equal(action.ErrInvalidCanName, m.Upsert(n2)) 298 n2.Name = name2 299 r.Equal(ErrInvalidOperator, m.Upsert(n2)) 300 n2.Operator = op2 301 r.Equal(ErrInvalidSelfStkIndex, m.Upsert(n2)) 302 n2.SelfStakeBucketIdx = uint64(size + 1) 303 // upsert(n2) 304 r.NoError(m.Upsert(n2)) 305 306 // verify conflict with n1 307 n2 = n1.Clone() 308 n2.Owner = identityset.Address(0) 309 r.Equal(action.ErrInvalidCanName, m.Upsert(n2)) 310 n2.Name = "noconflict" 311 r.Equal(ErrInvalidOperator, m.Upsert(n2)) 312 n2.Operator = identityset.Address(0) 313 r.Equal(ErrInvalidSelfStkIndex, m.Upsert(n2)) 314 315 // upsert a candidate w/o change is fine 316 r.NoError(m.Upsert(conflict)) 317 318 // there are 5 changes (2 dirty + 2 new + 1 w/o change) 319 var err error 320 list, err = testEqualAllCommit(r, m, list, 5, 2) 321 r.NoError(err) 322 323 // test candidate that does not exist 324 nonExistAddr := identityset.Address(0) 325 r.False(m.ContainsOwner(nonExistAddr)) 326 r.False(m.ContainsOperator(nonExistAddr)) 327 r.False(m.ContainsName("notexist")) 328 r.False(m.ContainsSelfStakingBucket(1000)) 329 } 330 } 331 332 func TestFixAlias(t *testing.T) { 333 r := require.New(t) 334 335 dk := protocol.NewDock() 336 view := protocol.View{} 337 338 for _, hasAlias := range []bool{false, true} { 339 // add 6 candidates into cand center 340 m, err := NewCandidateCenter(nil) 341 r.NoError(err) 342 for i, v := range testCandidates { 343 r.NoError(m.Upsert(testCandidates[i].d)) 344 r.True(m.ContainsName(v.d.Name)) 345 r.True(m.ContainsOperator(v.d.Operator)) 346 r.Equal(v.d, m.GetByName(v.d.Name)) 347 } 348 if hasAlias { 349 r.NoError(m.LegacyCommit()) 350 } else { 351 r.NoError(m.Commit()) 352 } 353 r.NoError(view.Write(_protocolID, m)) 354 355 // simulate handleCandidateUpdate: update name 356 center := candCenterFromNewCandidateStateManager(r, view, dk) 357 name := testCandidates[0].d.Name 358 nameAlias := center.GetByName(name) 359 nameAlias.Equal(testCandidates[0].d) 360 nameAlias.Name = "break" 361 { 362 r.NoError(center.Upsert(nameAlias)) 363 delta := center.Delta() 364 r.Equal(1, len(delta)) 365 r.NoError(dk.Load(_protocolID, _stakingCandCenter, &delta)) 366 } 367 368 center = candCenterFromNewCandidateStateManager(r, view, dk) 369 n := center.GetByName("break") 370 n.Equal(nameAlias) 371 r.True(center.ContainsName("break")) 372 // old name does not exist 373 r.Nil(center.GetByName(name)) 374 r.False(center.ContainsName(name)) 375 376 // simulate handleCandidateUpdate: update operator 377 op := testCandidates[1].d.Operator 378 opAlias := testCandidates[1].d.Clone() 379 opAlias.Operator = identityset.Address(17) 380 r.True(center.ContainsOperator(op)) 381 r.False(center.ContainsOperator(opAlias.Operator)) 382 { 383 r.NoError(center.Upsert(opAlias)) 384 delta := center.Delta() 385 r.Equal(2, len(delta)) 386 r.NoError(dk.Load(_protocolID, _stakingCandCenter, &delta)) 387 } 388 389 // verify cand center with name/op alias 390 center = candCenterFromNewCandidateStateManager(r, view, dk) 391 n = center.GetByName("break") 392 n.Equal(nameAlias) 393 r.True(center.ContainsName("break")) 394 // old name does not exist 395 r.Nil(center.GetByName(name)) 396 r.False(center.ContainsName(name)) 397 n = center.GetByOwner(testCandidates[1].d.Owner) 398 n.Equal(opAlias) 399 r.True(center.ContainsOperator(opAlias.Operator)) 400 // old operator does not exist 401 r.False(center.ContainsOperator(op)) 402 403 // cand center Commit() 404 { 405 if hasAlias { 406 r.NoError(center.LegacyCommit()) 407 } else { 408 r.NoError(center.Commit()) 409 } 410 r.NoError(view.Write(_protocolID, center)) 411 dk.Reset() 412 } 413 414 // verify cand center after Commit() 415 center = candCenterFromNewCandidateStateManager(r, view, dk) 416 n = center.GetByName("break") 417 n.Equal(nameAlias) 418 n = center.GetByOwner(testCandidates[1].d.Owner) 419 n.Equal(opAlias) 420 r.True(center.ContainsOperator(opAlias.Operator)) 421 if !hasAlias { 422 r.Nil(center.GetByName(name)) 423 r.False(center.ContainsName(name)) 424 r.False(center.ContainsOperator(op)) 425 } else { 426 // alias still exist in name/operator map 427 n = center.GetByName(name) 428 n.Equal(testCandidates[0].d) 429 r.True(center.ContainsName(name)) 430 r.True(center.ContainsOperator(op)) 431 } 432 } 433 } 434 435 func TestMultipleNonStakingCandidate(t *testing.T) { 436 r := require.New(t) 437 438 candStaked := &Candidate{ 439 Owner: identityset.Address(1), 440 Operator: identityset.Address(11), 441 Reward: identityset.Address(3), 442 Name: "self-staked", 443 Votes: unit.ConvertIotxToRau(1200000), 444 SelfStakeBucketIdx: 1, 445 SelfStake: unit.ConvertIotxToRau(1200000), 446 } 447 candNonStaked1 := &Candidate{ 448 Owner: identityset.Address(2), 449 Operator: identityset.Address(12), 450 Reward: identityset.Address(3), 451 Name: "non-self-staked1", 452 Votes: big.NewInt(0), 453 SelfStakeBucketIdx: candidateNoSelfStakeBucketIndex, 454 SelfStake: big.NewInt(0), 455 } 456 candNonStaked2 := &Candidate{ 457 Owner: identityset.Address(3), 458 Operator: identityset.Address(13), 459 Reward: identityset.Address(3), 460 Name: "non-self-staked2", 461 Votes: big.NewInt(0), 462 SelfStakeBucketIdx: candidateNoSelfStakeBucketIndex, 463 SelfStake: big.NewInt(0), 464 } 465 candNonStaked1ColOwner := &Candidate{ 466 Owner: identityset.Address(2), 467 Operator: identityset.Address(22), 468 Reward: identityset.Address(3), 469 Name: "non-self-staked1-col-owner", 470 Votes: big.NewInt(0), 471 SelfStakeBucketIdx: candidateNoSelfStakeBucketIndex, 472 SelfStake: big.NewInt(0), 473 } 474 candNonStaked1ColOpt := &Candidate{ 475 Owner: identityset.Address(20), 476 Operator: identityset.Address(12), 477 Reward: identityset.Address(3), 478 Name: "non-self-staked1-col-opt", 479 Votes: big.NewInt(0), 480 SelfStakeBucketIdx: candidateNoSelfStakeBucketIndex, 481 SelfStake: big.NewInt(0), 482 } 483 candNonStaked1ColName := &Candidate{ 484 Owner: identityset.Address(21), 485 Operator: identityset.Address(23), 486 Reward: identityset.Address(3), 487 Name: "non-self-staked1", 488 Votes: big.NewInt(0), 489 SelfStakeBucketIdx: candidateNoSelfStakeBucketIndex, 490 SelfStake: big.NewInt(0), 491 } 492 candStakedColBucket := &Candidate{ 493 Owner: identityset.Address(4), 494 Operator: identityset.Address(14), 495 Reward: identityset.Address(3), 496 Name: "self-staked-col-bucket", 497 Votes: unit.ConvertIotxToRau(1200000), 498 SelfStakeBucketIdx: 1, 499 SelfStake: unit.ConvertIotxToRau(1200000), 500 } 501 502 checkCandidates := func(candcenter *CandidateCenter, cands []*Candidate) { 503 r.True(testEqual(candcenter, CandidateList(cands))) 504 // commit 505 r.NoError(candcenter.Commit()) 506 r.True(testEqual(candcenter, CandidateList(cands))) 507 // from state manager 508 dk := protocol.NewDock() 509 view := protocol.View{} 510 r.NoError(view.Write(_protocolID, candcenter)) 511 dk.Reset() 512 candcenter = candCenterFromNewCandidateStateManager(r, view, dk) 513 r.True(testEqual(candcenter, CandidateList(cands))) 514 } 515 t.Run("nonstaked candidate not collision on bucket", func(t *testing.T) { 516 candcenter, err := NewCandidateCenter(nil) 517 r.NoError(err) 518 r.NoError(candcenter.Upsert(candNonStaked1)) 519 r.NoError(candcenter.Upsert(candNonStaked2)) 520 checkCandidates(candcenter, []*Candidate{candNonStaked1, candNonStaked2}) 521 }) 522 t.Run("staked candidate collision on bucket", func(t *testing.T) { 523 candcenter, err := NewCandidateCenter(nil) 524 r.NoError(err) 525 r.NoError(candcenter.Upsert(candStaked)) 526 r.ErrorIs(candcenter.Upsert(candStakedColBucket), ErrInvalidSelfStkIndex) 527 checkCandidates(candcenter, []*Candidate{candStaked}) 528 }) 529 t.Run("nonstaked candidate collision on operator", func(t *testing.T) { 530 candcenter, err := NewCandidateCenter(nil) 531 r.NoError(err) 532 r.NoError(candcenter.Upsert(candNonStaked1)) 533 r.ErrorIs(candcenter.Upsert(candNonStaked1ColOpt), ErrInvalidOperator) 534 checkCandidates(candcenter, []*Candidate{candNonStaked1}) 535 }) 536 t.Run("nonstaked candidate collision on name", func(t *testing.T) { 537 candcenter, err := NewCandidateCenter(nil) 538 r.NoError(err) 539 r.NoError(candcenter.Upsert(candNonStaked1)) 540 r.ErrorIs(candcenter.Upsert(candNonStaked1ColName), action.ErrInvalidCanName) 541 checkCandidates(candcenter, []*Candidate{candNonStaked1}) 542 }) 543 t.Run("nonstaked candidate update on owner", func(t *testing.T) { 544 candcenter, err := NewCandidateCenter(nil) 545 r.NoError(err) 546 r.NoError(candcenter.Upsert(candNonStaked1)) 547 r.NoError(candcenter.Upsert(candNonStaked1ColOwner)) 548 checkCandidates(candcenter, []*Candidate{candNonStaked1ColOwner}) 549 }) 550 t.Run("change bucket", func(t *testing.T) { 551 candcenter, err := NewCandidateCenter(nil) 552 r.NoError(err) 553 r.NoError(candcenter.Upsert(candNonStaked1)) 554 // settle self-stake bucket 555 candStaked := candNonStaked1.Clone() 556 candStaked.SelfStakeBucketIdx = 1 557 candStaked.SelfStake = unit.ConvertIotxToRau(1200000) 558 r.NoError(candcenter.Upsert(candStaked)) 559 // change self-stake bucket 560 candUpdated := candStaked.Clone() 561 candUpdated.SelfStakeBucketIdx = 2 562 r.NoError(candcenter.Upsert(candUpdated)) 563 checkCandidates(candcenter, []*Candidate{candUpdated}) 564 }) 565 } 566 567 func candCenterFromNewCandidateStateManager(r *require.Assertions, view protocol.View, dk protocol.Dock) *CandidateCenter { 568 // get cand center: csm.ConstructBaseView 569 v, err := view.Read(_protocolID) 570 r.NoError(err) 571 center := v.(*CandidateCenter).Base() 572 // get changes: csm.Sync() 573 delta := CandidateList{} 574 err = dk.Unload(_protocolID, _stakingCandCenter, &delta) 575 r.True(err == nil || err == protocol.ErrNoName) 576 r.NoError(center.SetDelta(delta)) 577 return center 578 }