github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/staking/handler_candidate_endorsement_test.go (about) 1 package staking 2 3 import ( 4 "context" 5 "fmt" 6 "math/big" 7 "testing" 8 9 "github.com/golang/mock/gomock" 10 "github.com/iotexproject/iotex-address/address" 11 "github.com/iotexproject/iotex-proto/golang/iotextypes" 12 "github.com/mohae/deepcopy" 13 "github.com/pkg/errors" 14 "github.com/stretchr/testify/require" 15 16 "github.com/iotexproject/iotex-core/action" 17 "github.com/iotexproject/iotex-core/action/protocol" 18 accountutil "github.com/iotexproject/iotex-core/action/protocol/account/util" 19 "github.com/iotexproject/iotex-core/blockchain/genesis" 20 "github.com/iotexproject/iotex-core/pkg/unit" 21 "github.com/iotexproject/iotex-core/state" 22 "github.com/iotexproject/iotex-core/test/identityset" 23 ) 24 25 type appendAction struct { 26 act func() action.Action 27 status iotextypes.ReceiptStatus 28 validator func(t *testing.T) 29 } 30 31 func TestProtocol_HandleCandidateEndorsement(t *testing.T) { 32 require := require.New(t) 33 ctrl := gomock.NewController(t) 34 initBucketCfgs := []*bucketConfig{ 35 {identityset.Address(1), identityset.Address(1), "1", 1, true, false, nil, 0}, 36 {identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 30, true, false, nil, 0}, 37 {identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 30, true, false, &timeBeforeBlockII, 0}, 38 {identityset.Address(2), identityset.Address(1), "1200000000000000000000000", 30, true, true, nil, endorsementNotExpireHeight}, 39 {identityset.Address(1), identityset.Address(2), "1200000000000000000000000", 30, true, false, nil, 0}, 40 {identityset.Address(2), identityset.Address(1), "1200000000000000000000000", 30, true, false, nil, 0}, 41 {identityset.Address(2), identityset.Address(2), "1200000000000000000000000", 30, true, true, nil, 0}, 42 {identityset.Address(1), identityset.Address(2), "1200000000000000000000000", 91, true, false, nil, endorsementNotExpireHeight}, 43 {identityset.Address(1), identityset.Address(2), "1200000000000000000000000", 91, true, false, nil, 1}, 44 {identityset.Address(2), identityset.Address(2), "1200000000000000000000000", 91, true, false, nil, 0}, 45 {identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 30, false, false, nil, 0}, 46 {identityset.Address(2), identityset.Address(1), "1200000000000000000000000", 30, true, true, nil, endorsementNotExpireHeight}, 47 {identityset.Address(2), identityset.Address(1), "1200000000000000000000000", 30, true, true, nil, 10}, 48 {identityset.Address(2), identityset.Address(1), "1200000000000000000000000", 30, true, true, nil, 1}, 49 } 50 initCandidateCfgs := []*candidateConfig{ 51 {identityset.Address(1), identityset.Address(7), identityset.Address(1), "test1"}, 52 {identityset.Address(2), identityset.Address(8), identityset.Address(1), "test2"}, 53 {identityset.Address(3), identityset.Address(9), identityset.Address(11), "test3"}, 54 } 55 initTestStateFromIds := func(bucketCfgIdx, candCfgIds []uint64) (protocol.StateManager, *Protocol, []*VoteBucket, []*Candidate) { 56 bucketCfgs := []*bucketConfig{} 57 for _, idx := range bucketCfgIdx { 58 bucketCfgs = append(bucketCfgs, initBucketCfgs[idx]) 59 } 60 candCfgs := []*candidateConfig{} 61 for _, idx := range candCfgIds { 62 candCfgs = append(candCfgs, initCandidateCfgs[idx]) 63 } 64 return initTestState(t, ctrl, bucketCfgs, candCfgs) 65 } 66 sm, p, _, _ := initTestState(t, ctrl, initBucketCfgs, initCandidateCfgs) 67 68 tests := []struct { 69 name string 70 // params 71 initBucketCfgIds []uint64 72 initCandidateCfgIds []uint64 73 initBalance int64 74 caller address.Address 75 nonce uint64 76 gasLimit uint64 77 blkGasLimit uint64 78 gasPrice *big.Int 79 bucketID uint64 80 endorse bool 81 newProtocol bool 82 append *appendAction 83 // expect 84 err error 85 status iotextypes.ReceiptStatus 86 expectCandidates []expectCandidate 87 expectBuckets []expectBucket 88 }{ 89 { 90 "endorse candidate with invalid bucket index", 91 []uint64{0, 1}, 92 []uint64{0, 1}, 93 1300000, 94 identityset.Address(1), 95 1, 96 uint64(1000000), 97 uint64(1000000), 98 big.NewInt(1000), 99 2, 100 true, 101 true, 102 nil, 103 nil, 104 iotextypes.ReceiptStatus_ErrInvalidBucketIndex, 105 []expectCandidate{}, 106 nil, 107 }, 108 { 109 "endorse candidate with invalid bucket owner", 110 []uint64{0, 1}, 111 []uint64{0, 1}, 112 1300000, 113 identityset.Address(2), 114 1, 115 uint64(1000000), 116 uint64(1000000), 117 big.NewInt(1000), 118 1, 119 true, 120 true, 121 nil, 122 nil, 123 iotextypes.ReceiptStatus_ErrUnauthorizedOperator, 124 []expectCandidate{}, 125 nil, 126 }, 127 { 128 "endorse candidate with invalid bucket amount", 129 []uint64{0, 1}, 130 []uint64{0, 1}, 131 1000, 132 identityset.Address(1), 133 1, 134 uint64(1000000), 135 uint64(1000000), 136 big.NewInt(1000), 137 0, 138 true, 139 true, 140 nil, 141 nil, 142 iotextypes.ReceiptStatus_ErrInvalidBucketAmount, 143 []expectCandidate{}, 144 nil, 145 }, 146 { 147 "endorse candidate with self-staked bucket", 148 []uint64{0, 3}, 149 []uint64{0, 1}, 150 1300000, 151 identityset.Address(1), 152 1, 153 uint64(1000000), 154 uint64(1000000), 155 big.NewInt(1000), 156 1, 157 true, 158 true, 159 nil, 160 nil, 161 iotextypes.ReceiptStatus_ErrInvalidBucketType, 162 []expectCandidate{}, 163 nil, 164 }, 165 { 166 "endorse candidate with invalid bucket candidate", 167 []uint64{0, 4}, 168 []uint64{0, 1}, 169 1300000, 170 identityset.Address(1), 171 1, 172 uint64(1000000), 173 uint64(1000000), 174 big.NewInt(1000), 175 1, 176 true, 177 true, 178 nil, 179 nil, 180 iotextypes.ReceiptStatus_ErrUnauthorizedOperator, 181 []expectCandidate{}, 182 nil, 183 }, 184 { 185 "endorse candidate with endorsed bucket", 186 []uint64{0, 7}, 187 []uint64{0, 1}, 188 1300000, 189 identityset.Address(2), 190 1, 191 uint64(1000000), 192 uint64(1000000), 193 big.NewInt(1000), 194 1, 195 true, 196 true, 197 nil, 198 nil, 199 iotextypes.ReceiptStatus_ErrInvalidBucketType, 200 []expectCandidate{}, 201 nil, 202 }, 203 { 204 "endorse candidate with unstaked bucket", 205 []uint64{0, 2}, 206 []uint64{0, 1}, 207 1300000, 208 identityset.Address(1), 209 1, 210 uint64(1000000), 211 uint64(1000000), 212 big.NewInt(1000), 213 1, 214 true, 215 true, 216 nil, 217 nil, 218 iotextypes.ReceiptStatus_ErrInvalidBucketType, 219 []expectCandidate{}, 220 nil, 221 }, 222 { 223 "endorse candidate with expired endorsement", 224 []uint64{0, 8}, 225 []uint64{0, 1}, 226 1300000, 227 identityset.Address(2), 228 1, 229 uint64(1000000), 230 uint64(1000000), 231 big.NewInt(1000), 232 1, 233 true, 234 true, 235 nil, 236 nil, 237 iotextypes.ReceiptStatus_Success, 238 []expectCandidate{ 239 {identityset.Address(1), candidateNoSelfStakeBucketIndex, "0", "1542516163985454635820817"}, 240 }, 241 []expectBucket{ 242 {0, identityset.Address(1), false, 0}, 243 {1, identityset.Address(1), true, endorsementNotExpireHeight}, 244 }, 245 }, 246 { 247 "endorse candidate with valid bucket", 248 []uint64{0, 1}, 249 []uint64{0, 1}, 250 1300000, 251 identityset.Address(1), 252 1, 253 uint64(1000000), 254 uint64(1000000), 255 big.NewInt(1000), 256 1, 257 true, 258 true, 259 nil, 260 nil, 261 iotextypes.ReceiptStatus_Success, 262 []expectCandidate{}, 263 nil, 264 }, 265 { 266 "once endorsed, bucket cannot be unstaked", 267 []uint64{0, 10}, 268 []uint64{0, 1}, 269 1300000, 270 identityset.Address(1), 271 1, 272 uint64(1000000), 273 uint64(1000000), 274 big.NewInt(1000), 275 1, 276 true, 277 true, 278 &appendAction{ 279 func() action.Action { 280 act, err := action.NewUnstake(0, 1, []byte{}, uint64(1000000), big.NewInt(1000)) 281 require.NoError(err) 282 return act 283 }, 284 iotextypes.ReceiptStatus_ErrInvalidBucketType, 285 nil, 286 }, 287 nil, 288 iotextypes.ReceiptStatus_Success, 289 []expectCandidate{}, 290 nil, 291 }, 292 { 293 "once endorsed, bucket cannot be change candidate", 294 []uint64{0, 10}, 295 []uint64{0, 1, 2}, 296 1300000, 297 identityset.Address(1), 298 1, 299 uint64(1000000), 300 uint64(1000000), 301 big.NewInt(1000), 302 1, 303 true, 304 true, 305 &appendAction{ 306 func() action.Action { 307 act, err := action.NewChangeCandidate(0, "test3", 1, []byte{}, uint64(1000000), big.NewInt(1000)) 308 require.NoError(err) 309 return act 310 }, 311 iotextypes.ReceiptStatus_ErrInvalidBucketType, //todo fix 312 nil, 313 }, 314 nil, 315 iotextypes.ReceiptStatus_Success, 316 []expectCandidate{}, 317 nil, 318 }, 319 { 320 "unendorse a valid bucket", 321 []uint64{0, 9}, 322 []uint64{0, 1}, 323 1300000, 324 identityset.Address(2), 325 1, 326 uint64(1000000), 327 uint64(1000000), 328 big.NewInt(1000), 329 1, 330 true, 331 true, 332 &appendAction{ 333 func() action.Action { 334 act := action.NewCandidateEndorsement(0, uint64(1000000), big.NewInt(1000), 1, false) 335 return act 336 }, 337 iotextypes.ReceiptStatus_Success, 338 func(t *testing.T) { 339 csm, err := NewCandidateStateManager(sm, false) 340 require.NoError(err) 341 esm := NewEndorsementStateManager(csm.SM()) 342 bucket, err := csm.getBucket(1) 343 require.NoError(err) 344 endorsement, err := esm.Get(bucket.Index) 345 require.NoError(err) 346 require.NotNil(endorsement) 347 require.Equal(uint64(1), endorsement.ExpireHeight) 348 }, 349 }, 350 nil, 351 iotextypes.ReceiptStatus_Success, 352 []expectCandidate{}, 353 nil, 354 }, 355 { 356 "unendorse a self-staked bucket", 357 []uint64{0, 10}, 358 []uint64{0, 1}, 359 1300000, 360 identityset.Address(1), 361 1, 362 uint64(1000000), 363 uint64(1000000), 364 big.NewInt(1000), 365 1, 366 true, 367 true, 368 &appendAction{ 369 func() action.Action { 370 act := action.NewCandidateEndorsement(0, uint64(1000000), big.NewInt(1000), 1, false) 371 return act 372 }, 373 iotextypes.ReceiptStatus_Success, 374 func(t *testing.T) { 375 csm, err := NewCandidateStateManager(sm, false) 376 require.NoError(err) 377 esm := NewEndorsementStateManager(csm.SM()) 378 bucket, err := csm.getBucket(1) 379 require.NoError(err) 380 endorsement, err := esm.Get(bucket.Index) 381 require.NoError(err) 382 require.NotNil(endorsement) 383 require.Equal(uint64(1), endorsement.ExpireHeight) 384 }, 385 }, 386 nil, 387 iotextypes.ReceiptStatus_Success, 388 []expectCandidate{}, 389 nil, 390 }, 391 { 392 "endorsement withdraw if bucket is self-staked but without endorsement", 393 []uint64{6}, 394 []uint64{1}, 395 1300000, 396 identityset.Address(2), 397 1, 398 uint64(1000000), 399 uint64(1000000), 400 big.NewInt(1000), 401 0, 402 false, 403 true, 404 nil, 405 nil, 406 iotextypes.ReceiptStatus_ErrInvalidBucketType, 407 []expectCandidate{}, 408 nil, 409 }, 410 { 411 "endorsement withdraw if bucket is self-staked by endorsement", 412 []uint64{11}, 413 []uint64{0, 1}, 414 1300000, 415 identityset.Address(1), 416 1, 417 uint64(1000000), 418 uint64(1000000), 419 big.NewInt(1000), 420 0, 421 false, 422 true, 423 nil, 424 nil, 425 iotextypes.ReceiptStatus_Success, 426 []expectCandidate{ 427 {identityset.Address(2), 0, "1200000000000000000000000", "1469480667073232815766914"}, 428 }, 429 []expectBucket{ 430 {0, identityset.Address(2), true, 17281}, 431 }, 432 }, 433 { 434 "endorsement withdraw if bucket is endorsement withdrawing", 435 []uint64{12}, 436 []uint64{0, 1}, 437 1300000, 438 identityset.Address(1), 439 1, 440 uint64(1000000), 441 uint64(1000000), 442 big.NewInt(1000), 443 0, 444 false, 445 true, 446 nil, 447 nil, 448 iotextypes.ReceiptStatus_ErrInvalidBucketType, 449 nil, 450 nil, 451 }, 452 { 453 "endorsement withdraw if bucket is endorsement expired", 454 []uint64{13}, 455 []uint64{0, 1}, 456 1300000, 457 identityset.Address(1), 458 1, 459 uint64(1000000), 460 uint64(1000000), 461 big.NewInt(1000), 462 0, 463 false, 464 true, 465 nil, 466 nil, 467 iotextypes.ReceiptStatus_ErrInvalidBucketType, 468 nil, 469 nil, 470 }, 471 { 472 "endorsement withdraw if bucket without endorsement", 473 []uint64{0}, 474 []uint64{0, 1}, 475 1300000, 476 identityset.Address(1), 477 1, 478 uint64(1000000), 479 uint64(1000000), 480 big.NewInt(1000), 481 0, 482 false, 483 true, 484 nil, 485 nil, 486 iotextypes.ReceiptStatus_ErrInvalidBucketType, 487 nil, 488 nil, 489 }, 490 } 491 492 for _, test := range tests { 493 t.Run(test.name, func(t *testing.T) { 494 nonce := test.nonce 495 if test.newProtocol { 496 sm, p, _, _ = initTestStateFromIds(test.initBucketCfgIds, test.initCandidateCfgIds) 497 } 498 require.NoError(setupAccount(sm, test.caller, test.initBalance)) 499 act := action.NewCandidateEndorsement(nonce, test.gasLimit, test.gasPrice, test.bucketID, test.endorse) 500 IntrinsicGas, _ := act.IntrinsicGas() 501 ctx := protocol.WithActionCtx(context.Background(), protocol.ActionCtx{ 502 Caller: test.caller, 503 GasPrice: test.gasPrice, 504 IntrinsicGas: IntrinsicGas, 505 Nonce: nonce, 506 }) 507 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 508 BlockHeight: 1, 509 BlockTimeStamp: timeBlock, 510 GasLimit: test.blkGasLimit, 511 }) 512 cfg := deepcopy.Copy(genesis.Default).(genesis.Genesis) 513 cfg.TsunamiBlockHeight = 1 514 ctx = genesis.WithGenesisContext(ctx, cfg) 515 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 516 require.Equal(test.err, errors.Cause(p.Validate(ctx, act, sm))) 517 if test.err != nil { 518 return 519 } 520 r, err := p.Handle(ctx, act, sm) 521 require.NoError(err) 522 if r != nil { 523 require.Equal(uint64(test.status), r.Status) 524 } else { 525 require.Equal(test.status, iotextypes.ReceiptStatus_Failure) 526 } 527 var appendIntrinsicGas uint64 528 if test.append != nil { 529 nonce = nonce + 1 530 appendIntrinsicGas, _ = act.IntrinsicGas() 531 ctx := protocol.WithActionCtx(context.Background(), protocol.ActionCtx{ 532 Caller: test.caller, 533 GasPrice: test.gasPrice, 534 IntrinsicGas: IntrinsicGas, 535 Nonce: nonce, 536 }) 537 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 538 BlockHeight: 1, 539 BlockTimeStamp: timeBlock, 540 GasLimit: test.blkGasLimit, 541 }) 542 ctx = genesis.WithGenesisContext(ctx, cfg) 543 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 544 r, err = p.Handle(ctx, test.append.act(), sm) 545 require.NoError(err) 546 if r != nil { 547 require.Equal(uint64(test.append.status), r.Status, fmt.Sprintf("except :%d, actual:%d", test.append.status, r.Status)) 548 if test.append.validator != nil { 549 test.append.validator(t) 550 } 551 } else { 552 require.Equal(test.status, iotextypes.ReceiptStatus_Failure) 553 } 554 } 555 556 if test.err == nil && test.status == iotextypes.ReceiptStatus_Success { 557 // check candidate 558 csm, err := NewCandidateStateManager(sm, false) 559 require.NoError(err) 560 for _, expectCand := range test.expectCandidates { 561 candidate := csm.GetByOwner(expectCand.owner) 562 require.NotNil(candidate) 563 require.Equal(expectCand.candSelfStakeIndex, candidate.SelfStakeBucketIdx) 564 require.Equal(expectCand.candSelfStakeAmountStr, candidate.SelfStake.String()) 565 require.Equal(expectCand.candVoteStr, candidate.Votes.String()) 566 } 567 // check buckets 568 esm := NewEndorsementStateManager(csm.SM()) 569 for _, expectBkt := range test.expectBuckets { 570 bkt, err := csm.getBucket(expectBkt.id) 571 require.NoError(err) 572 require.Equal(expectBkt.candidate, bkt.Candidate) 573 endorse, err := esm.Get(expectBkt.id) 574 if expectBkt.hasEndorsement { 575 require.NoError(err) 576 require.EqualValues(expectBkt.endorsementExpireHeight, endorse.ExpireHeight) 577 } else { 578 require.ErrorIs(err, state.ErrStateNotExist) 579 } 580 } 581 582 // test staker's account 583 caller, err := accountutil.LoadAccount(sm, test.caller) 584 require.NoError(err) 585 actCost, err := act.Cost() 586 actCost.Add(actCost, big.NewInt(0).Mul(test.gasPrice, big.NewInt(0).SetUint64(appendIntrinsicGas))) 587 require.NoError(err) 588 total := big.NewInt(0) 589 require.Equal(unit.ConvertIotxToRau(test.initBalance), total.Add(total, caller.Balance).Add(total, actCost)) 590 require.Equal(nonce+1, caller.PendingNonce()) 591 } 592 }) 593 } 594 }