github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/staking/protocol_test.go (about) 1 // Copyright (c) 2020 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package staking 7 8 import ( 9 "context" 10 "math" 11 "math/big" 12 "testing" 13 "time" 14 15 "github.com/golang/mock/gomock" 16 "github.com/iotexproject/iotex-address/address" 17 "github.com/pkg/errors" 18 "github.com/stretchr/testify/require" 19 20 "github.com/iotexproject/iotex-core/action/protocol" 21 "github.com/iotexproject/iotex-core/action/protocol/rolldpos" 22 "github.com/iotexproject/iotex-core/blockchain/genesis" 23 "github.com/iotexproject/iotex-core/db" 24 "github.com/iotexproject/iotex-core/pkg/unit" 25 "github.com/iotexproject/iotex-core/state" 26 "github.com/iotexproject/iotex-core/test/identityset" 27 "github.com/iotexproject/iotex-core/testutil" 28 "github.com/iotexproject/iotex-core/testutil/testdb" 29 ) 30 31 func TestProtocol(t *testing.T) { 32 r := require.New(t) 33 34 // make sure the prefix stays constant, they affect the key to store objects to DB 35 r.Equal(byte(0), _const) 36 r.Equal(byte(1), _bucket) 37 r.Equal(byte(2), _voterIndex) 38 r.Equal(byte(3), _candIndex) 39 40 ctrl := gomock.NewController(t) 41 sm := testdb.NewMockStateManager(ctrl) 42 csr := newCandidateStateReader(sm) 43 csmTemp := newCandidateStateManager(sm) 44 _, err := sm.PutState( 45 &totalBucketCount{count: 0}, 46 protocol.NamespaceOption(_stakingNameSpace), 47 protocol.KeyOption(TotalBucketKey), 48 ) 49 r.NoError(err) 50 51 tests := []struct { 52 cand address.Address 53 owner address.Address 54 amount *big.Int 55 duration uint32 56 index uint64 57 }{ 58 { 59 identityset.Address(1), 60 identityset.Address(2), 61 big.NewInt(2100000000), 62 21, 63 0, 64 }, 65 { 66 identityset.Address(2), 67 identityset.Address(3), 68 big.NewInt(1400000000), 69 14, 70 1, 71 }, 72 { 73 identityset.Address(3), 74 identityset.Address(4), 75 big.NewInt(2500000000), 76 25, 77 2, 78 }, 79 { 80 identityset.Address(4), 81 identityset.Address(1), 82 big.NewInt(3100000000), 83 31, 84 3, 85 }, 86 } 87 88 // test loading with no candidate in stateDB 89 stk, err := NewProtocol(nil, &BuilderConfig{ 90 Staking: genesis.Default.Staking, 91 PersistStakingPatchBlock: math.MaxUint64, 92 }, nil, nil, genesis.Default.GreenlandBlockHeight) 93 r.NotNil(stk) 94 r.NoError(err) 95 buckets, _, err := csr.getAllBuckets() 96 r.NoError(err) 97 r.Equal(0, len(buckets)) 98 c, _, err := csr.getAllCandidates() 99 r.Equal(state.ErrStateNotExist, err) 100 r.Equal(0, len(c)) 101 102 // address package also defined protocol address, make sure they match 103 r.Equal(stk.addr.Bytes(), address.StakingProtocolAddrHash[:]) 104 stkAddr, err := address.FromString(address.StakingProtocolAddr) 105 r.NoError(err) 106 r.Equal(stk.addr.Bytes(), stkAddr.Bytes()) 107 108 // write a number of buckets into stateDB 109 for _, e := range tests { 110 vb := NewVoteBucket(e.cand, e.owner, e.amount, e.duration, time.Now(), true) 111 index, err := csmTemp.putBucketAndIndex(vb) 112 r.NoError(err) 113 r.Equal(index, vb.Index) 114 } 115 116 // load candidates from stateDB and verify 117 g := genesis.Default 118 g.QuebecBlockHeight = 1 119 ctx := genesis.WithGenesisContext(context.Background(), g) 120 ctx = protocol.WithFeatureWithHeightCtx(ctx) 121 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{BlockHeight: 10}) 122 ctx = protocol.WithFeatureCtx(ctx) 123 v, err := stk.Start(ctx, sm) 124 sm.WriteView(_protocolID, v) 125 r.NoError(err) 126 _, ok := v.(*ViewData) 127 r.True(ok) 128 129 csm, err := NewCandidateStateManager(sm, false) 130 r.NoError(err) 131 // load a number of candidates 132 for _, e := range testCandidates { 133 r.NoError(csm.Upsert(e.d)) 134 } 135 r.NoError(csm.Commit(ctx)) 136 for _, e := range testCandidates { 137 r.True(csm.ContainsOwner(e.d.Owner)) 138 r.True(csm.ContainsName(e.d.Name)) 139 r.True(csm.ContainsOperator(e.d.Operator)) 140 r.Equal(e.d, csm.GetByOwner(e.d.Owner)) 141 } 142 143 // active list should filter out 2 cands with not enough self-stake 144 h, _ := sm.Height() 145 cand, err := stk.ActiveCandidates(ctx, sm, h) 146 r.NoError(err) 147 r.Equal(len(testCandidates)-2, len(cand)) 148 for i := range cand { 149 c := testCandidates[i] 150 // index is the order of sorted list 151 e := cand[c.index] 152 r.Equal(e.Votes, c.d.Votes) 153 r.Equal(e.RewardAddress, c.d.Reward.String()) 154 r.Equal(string(e.CanName), c.d.Name) 155 r.True(c.d.SelfStake.Cmp(unit.ConvertIotxToRau(1200000)) >= 0) 156 } 157 158 // load all candidates from stateDB and verify 159 all, _, err := csr.getAllCandidates() 160 r.NoError(err) 161 r.Equal(len(testCandidates), len(all)) 162 for _, e := range testCandidates { 163 for i := range all { 164 if all[i].Name == e.d.Name { 165 r.Equal(e.d, all[i]) 166 break 167 } 168 } 169 } 170 171 // csm's candidate center should be identical to all candidates in stateDB 172 c1, err := all.toStateCandidateList() 173 r.NoError(err) 174 c2, err := csm.DirtyView().candCenter.All().toStateCandidateList() 175 r.NoError(err) 176 r.Equal(c1, c2) 177 178 // load buckets from stateDB and verify 179 buckets, _, err = csr.getAllBuckets() 180 r.NoError(err) 181 r.Equal(len(tests), len(buckets)) 182 // delete one bucket 183 r.NoError(csm.delBucket(1)) 184 buckets, _, err = csr.getAllBuckets() 185 r.NoError(csm.delBucket(1)) 186 buckets, _, err = csr.getAllBuckets() 187 for _, e := range tests { 188 for i := range buckets { 189 if buckets[i].StakedAmount == e.amount { 190 vb := NewVoteBucket(e.cand, e.owner, e.amount, e.duration, time.Now(), true) 191 r.Equal(vb, buckets[i]) 192 break 193 } 194 } 195 } 196 } 197 198 func TestCreatePreStates(t *testing.T) { 199 require := require.New(t) 200 ctrl := gomock.NewController(t) 201 sm := testdb.NewMockStateManager(ctrl) 202 p, err := NewProtocol(nil, &BuilderConfig{ 203 Staking: genesis.Default.Staking, 204 PersistStakingPatchBlock: math.MaxUint64, 205 }, nil, nil, genesis.Default.GreenlandBlockHeight, genesis.Default.GreenlandBlockHeight) 206 require.NoError(err) 207 ctx := protocol.WithBlockCtx( 208 genesis.WithGenesisContext(context.Background(), genesis.Default), 209 protocol.BlockCtx{ 210 BlockHeight: genesis.Default.GreenlandBlockHeight - 1, 211 }, 212 ) 213 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 214 v, err := p.Start(ctx, sm) 215 require.NoError(err) 216 require.NoError(sm.WriteView(_protocolID, v)) 217 csm, err := NewCandidateStateManager(sm, false) 218 require.NoError(err) 219 require.NotNil(csm) 220 _, err = NewCandidateStateManager(sm, true) 221 require.Error(err) 222 require.NoError(p.CreatePreStates(ctx, sm)) 223 _, err = sm.State(nil, protocol.NamespaceOption(_stakingNameSpace), protocol.KeyOption(_bucketPoolAddrKey)) 224 require.EqualError(errors.Cause(err), state.ErrStateNotExist.Error()) 225 ctx = protocol.WithBlockCtx( 226 ctx, 227 protocol.BlockCtx{ 228 BlockHeight: genesis.Default.GreenlandBlockHeight + 1, 229 }, 230 ) 231 require.NoError(p.CreatePreStates(ctx, sm)) 232 _, err = sm.State(nil, protocol.NamespaceOption(_stakingNameSpace), protocol.KeyOption(_bucketPoolAddrKey)) 233 require.EqualError(errors.Cause(err), state.ErrStateNotExist.Error()) 234 ctx = protocol.WithBlockCtx( 235 ctx, 236 protocol.BlockCtx{ 237 BlockHeight: genesis.Default.GreenlandBlockHeight, 238 }, 239 ) 240 require.NoError(p.CreatePreStates(ctx, sm)) 241 total := &totalAmount{} 242 _, err = sm.State(total, protocol.NamespaceOption(_stakingNameSpace), protocol.KeyOption(_bucketPoolAddrKey)) 243 require.NoError(err) 244 } 245 246 func Test_CreatePreStatesWithRegisterProtocol(t *testing.T) { 247 require := require.New(t) 248 ctrl := gomock.NewController(t) 249 sm := testdb.NewMockStateManager(ctrl) 250 251 testPath, err := testutil.PathOfTempFile("test-bucket") 252 require.NoError(err) 253 defer func() { 254 testutil.CleanupPath(testPath) 255 }() 256 257 cfg := db.DefaultConfig 258 cfg.DbPath = testPath 259 store := db.NewBoltDB(cfg) 260 cbi, err := NewStakingCandidatesBucketsIndexer(store) 261 require.NoError(err) 262 263 ctx := context.Background() 264 require.NoError(cbi.Start(ctx)) 265 p, err := NewProtocol(nil, &BuilderConfig{ 266 Staking: genesis.Default.Staking, 267 PersistStakingPatchBlock: math.MaxUint64, 268 }, cbi, nil, genesis.Default.GreenlandBlockHeight, genesis.Default.GreenlandBlockHeight) 269 require.NoError(err) 270 271 rol := rolldpos.NewProtocol(23, 4, 3) 272 reg := protocol.NewRegistry() 273 reg.Register("rolldpos", rol) 274 275 ctx = protocol.WithRegistry(ctx, reg) 276 ctx = protocol.WithBlockCtx( 277 genesis.WithGenesisContext(ctx, genesis.Default), 278 protocol.BlockCtx{ 279 BlockHeight: genesis.Default.GreenlandBlockHeight, 280 }, 281 ) 282 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 283 v, err := p.Start(ctx, sm) 284 require.NoError(err) 285 require.NoError(sm.WriteView(_protocolID, v)) 286 _, err = NewCandidateStateManager(sm, true) 287 require.Error(err) 288 289 require.NoError(p.CreatePreStates(ctx, sm)) 290 } 291 292 func Test_CreateGenesisStates(t *testing.T) { 293 require := require.New(t) 294 ctrl := gomock.NewController(t) 295 sm := testdb.NewMockStateManager(ctrl) 296 297 selfStake, _ := new(big.Int).SetString("1200000000000000000000000", 10) 298 cfg := genesis.Default.Staking 299 300 testBootstrapCandidates := []struct { 301 BootstrapCandidate []genesis.BootstrapCandidate 302 errStr string 303 }{ 304 { 305 []genesis.BootstrapCandidate{ 306 { 307 OwnerAddress: "xxxxxxxxxxxxxxxx", 308 OperatorAddress: identityset.Address(23).String(), 309 RewardAddress: identityset.Address(23).String(), 310 Name: "test1", 311 SelfStakingTokens: "test123", 312 }, 313 }, 314 "address length = 16, expecting 41", 315 }, 316 { 317 []genesis.BootstrapCandidate{ 318 { 319 OwnerAddress: identityset.Address(22).String(), 320 OperatorAddress: "xxxxxxxxxxxxxxxx", 321 RewardAddress: identityset.Address(23).String(), 322 Name: "test1", 323 SelfStakingTokens: selfStake.String(), 324 }, 325 }, 326 "address length = 16, expecting 41", 327 }, 328 { 329 []genesis.BootstrapCandidate{ 330 { 331 OwnerAddress: identityset.Address(22).String(), 332 OperatorAddress: identityset.Address(23).String(), 333 RewardAddress: "xxxxxxxxxxxxxxxx", 334 Name: "test1", 335 SelfStakingTokens: selfStake.String(), 336 }, 337 }, 338 "address length = 16, expecting 41", 339 }, 340 { 341 []genesis.BootstrapCandidate{ 342 { 343 OwnerAddress: identityset.Address(22).String(), 344 OperatorAddress: identityset.Address(23).String(), 345 RewardAddress: identityset.Address(23).String(), 346 Name: "test1", 347 SelfStakingTokens: "test123", 348 }, 349 }, 350 "invalid amount", 351 }, 352 { 353 []genesis.BootstrapCandidate{ 354 { 355 OwnerAddress: identityset.Address(22).String(), 356 OperatorAddress: identityset.Address(23).String(), 357 RewardAddress: identityset.Address(23).String(), 358 Name: "test1", 359 SelfStakingTokens: selfStake.String(), 360 }, 361 { 362 OwnerAddress: identityset.Address(24).String(), 363 OperatorAddress: identityset.Address(25).String(), 364 RewardAddress: identityset.Address(25).String(), 365 Name: "test2", 366 SelfStakingTokens: selfStake.String(), 367 }, 368 }, 369 "", 370 }, 371 } 372 ctx := protocol.WithBlockCtx( 373 genesis.WithGenesisContext(context.Background(), genesis.Default), 374 protocol.BlockCtx{ 375 BlockHeight: genesis.Default.GreenlandBlockHeight - 1, 376 }, 377 ) 378 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 379 for _, test := range testBootstrapCandidates { 380 cfg.BootstrapCandidates = test.BootstrapCandidate 381 p, err := NewProtocol(nil, &BuilderConfig{ 382 Staking: cfg, 383 PersistStakingPatchBlock: math.MaxUint64, 384 }, nil, nil, genesis.Default.GreenlandBlockHeight) 385 require.NoError(err) 386 387 v, err := p.Start(ctx, sm) 388 require.NoError(err) 389 require.NoError(sm.WriteView(_protocolID, v)) 390 391 err = p.CreateGenesisStates(ctx, sm) 392 if err != nil { 393 require.Contains(err.Error(), test.errStr) 394 } 395 } 396 } 397 398 func TestProtocol_ActiveCandidates(t *testing.T) { 399 require := require.New(t) 400 ctrl := gomock.NewController(t) 401 sm := testdb.NewMockStateManagerWithoutHeightFunc(ctrl) 402 csIndexer := NewMockContractStakingIndexer(ctrl) 403 404 selfStake, _ := new(big.Int).SetString("1200000000000000000000000", 10) 405 cfg := genesis.Default.Staking 406 cfg.BootstrapCandidates = []genesis.BootstrapCandidate{ 407 { 408 OwnerAddress: identityset.Address(22).String(), 409 OperatorAddress: identityset.Address(23).String(), 410 RewardAddress: identityset.Address(23).String(), 411 Name: "test1", 412 SelfStakingTokens: selfStake.String(), 413 }, 414 } 415 p, err := NewProtocol(nil, &BuilderConfig{ 416 Staking: cfg, 417 PersistStakingPatchBlock: math.MaxUint64, 418 }, nil, csIndexer, genesis.Default.GreenlandBlockHeight) 419 require.NoError(err) 420 421 blkHeight := genesis.Default.QuebecBlockHeight + 1 422 ctx := protocol.WithBlockCtx( 423 genesis.WithGenesisContext(context.Background(), genesis.Default), 424 protocol.BlockCtx{ 425 BlockHeight: blkHeight, 426 }, 427 ) 428 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 429 sm.EXPECT().Height().Return(blkHeight, nil).AnyTimes() 430 431 v, err := p.Start(ctx, sm) 432 require.NoError(err) 433 require.NoError(sm.WriteView(_protocolID, v)) 434 435 err = p.CreateGenesisStates(ctx, sm) 436 require.NoError(err) 437 438 var csIndexerHeight, csVotes uint64 439 csIndexer.EXPECT().CandidateVotes(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, ownerAddr address.Address, height uint64) (*big.Int, error) { 440 if height != csIndexerHeight { 441 return nil, errors.Errorf("invalid height") 442 } 443 return big.NewInt(int64(csVotes)), nil 444 }).AnyTimes() 445 446 t.Run("contract staking indexer falls behind", func(t *testing.T) { 447 csIndexerHeight = 10 448 _, err := p.ActiveCandidates(ctx, sm, 0) 449 require.ErrorContains(err, "invalid height") 450 }) 451 452 t.Run("contract staking indexer up to date", func(t *testing.T) { 453 csIndexerHeight = blkHeight - 1 454 csVotes = 0 455 cands, err := p.ActiveCandidates(ctx, sm, 0) 456 require.NoError(err) 457 require.Len(cands, 1) 458 originCandVotes := cands[0].Votes 459 csVotes = 100 460 cands, err = p.ActiveCandidates(ctx, sm, 0) 461 require.NoError(err) 462 require.Len(cands, 1) 463 require.EqualValues(100, cands[0].Votes.Sub(cands[0].Votes, originCandVotes).Uint64()) 464 }) 465 } 466 467 func TestIsSelfStakeBucket(t *testing.T) { 468 r := require.New(t) 469 ctrl := gomock.NewController(t) 470 471 featureCtxPostHF := protocol.FeatureCtx{DisableDelegateEndorsement: false} 472 featureCtxPreHF := protocol.FeatureCtx{DisableDelegateEndorsement: true} 473 t.Run("normal bucket", func(t *testing.T) { 474 bucketCfgs := []*bucketConfig{ 475 {identityset.Address(11), identityset.Address(11), "1200000000000000000000000", 100, true, false, nil, 0}, 476 {identityset.Address(11), identityset.Address(1), "1200000000000000000000000", 100, true, false, nil, 0}, 477 } 478 candCfgs := []*candidateConfig{} 479 sm, _, buckets, _ := initTestState(t, ctrl, bucketCfgs, candCfgs) 480 csm, err := NewCandidateStateManager(sm, false) 481 r.NoError(err) 482 selfStake, err := isSelfStakeBucket(featureCtxPreHF, csm, buckets[0]) 483 r.NoError(err) 484 r.False(selfStake) 485 selfStake, err = isSelfStakeBucket(featureCtxPostHF, csm, buckets[0]) 486 r.NoError(err) 487 r.False(selfStake) 488 }) 489 t.Run("self-stake bucket", func(t *testing.T) { 490 bucketCfgs := []*bucketConfig{ 491 {identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 100, true, true, nil, 0}, 492 } 493 candCfgs := []*candidateConfig{ 494 {identityset.Address(1), identityset.Address(11), identityset.Address(21), "cand1"}, 495 } 496 sm, _, buckets, _ := initTestState(t, ctrl, bucketCfgs, candCfgs) 497 csm, err := NewCandidateStateManager(sm, false) 498 r.NoError(err) 499 500 selfStake, err := isSelfStakeBucket(featureCtxPreHF, csm, buckets[0]) 501 r.NoError(err) 502 r.True(selfStake) 503 selfStake, err = isSelfStakeBucket(featureCtxPostHF, csm, buckets[0]) 504 r.NoError(err) 505 r.True(selfStake) 506 }) 507 t.Run("self-stake bucket unstaked", func(t *testing.T) { 508 bucketCfgs := []*bucketConfig{ 509 {identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 100, true, true, &timeBeforeBlockII, 0}, 510 } 511 candCfgs := []*candidateConfig{ 512 {identityset.Address(1), identityset.Address(11), identityset.Address(21), "cand1"}, 513 } 514 sm, _, buckets, _ := initTestState(t, ctrl, bucketCfgs, candCfgs) 515 csm, err := NewCandidateStateManager(sm, false) 516 r.NoError(err) 517 518 selfStake, err := isSelfStakeBucket(featureCtxPreHF, csm, buckets[0]) 519 r.NoError(err) 520 r.True(selfStake) 521 selfStake, err = isSelfStakeBucket(featureCtxPostHF, csm, buckets[0]) 522 r.NoError(err) 523 r.False(selfStake) 524 }) 525 t.Run("endorsed bucket but not self-staked", func(t *testing.T) { 526 bucketCfgs := []*bucketConfig{ 527 {identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 100, true, true, nil, 0}, 528 {identityset.Address(1), identityset.Address(2), "1200000000000000000000000", 100, true, false, nil, endorsementNotExpireHeight}, 529 } 530 candCfgs := []*candidateConfig{ 531 {identityset.Address(1), identityset.Address(11), identityset.Address(21), "cand1"}, 532 } 533 sm, _, buckets, _ := initTestState(t, ctrl, bucketCfgs, candCfgs) 534 csm, err := NewCandidateStateManager(sm, false) 535 r.NoError(err) 536 537 selfStake, err := isSelfStakeBucket(featureCtxPreHF, csm, buckets[1]) 538 r.NoError(err) 539 r.False(selfStake) 540 selfStake, err = isSelfStakeBucket(featureCtxPostHF, csm, buckets[1]) 541 r.NoError(err) 542 r.False(selfStake) 543 }) 544 t.Run("endorsed and self-staked", func(t *testing.T) { 545 bucketCfgs := []*bucketConfig{ 546 {identityset.Address(1), identityset.Address(2), "1200000000000000000000000", 100, true, true, nil, endorsementNotExpireHeight}, 547 } 548 candCfgs := []*candidateConfig{ 549 {identityset.Address(1), identityset.Address(11), identityset.Address(21), "cand1"}, 550 } 551 sm, _, buckets, _ := initTestState(t, ctrl, bucketCfgs, candCfgs) 552 csm, err := NewCandidateStateManager(sm, false) 553 r.NoError(err) 554 555 selfStake, err := isSelfStakeBucket(featureCtxPreHF, csm, buckets[0]) 556 r.NoError(err) 557 r.True(selfStake) 558 selfStake, err = isSelfStakeBucket(featureCtxPostHF, csm, buckets[0]) 559 r.NoError(err) 560 r.True(selfStake) 561 }) 562 t.Run("endorsement withdrawing", func(t *testing.T) { 563 bucketCfgs := []*bucketConfig{ 564 {identityset.Address(1), identityset.Address(2), "1200000000000000000000000", 100, true, true, nil, 10}, 565 } 566 candCfgs := []*candidateConfig{ 567 {identityset.Address(1), identityset.Address(11), identityset.Address(21), "cand1"}, 568 } 569 sm, _, buckets, _ := initTestStateWithHeight(t, ctrl, bucketCfgs, candCfgs, 0) 570 csm, err := NewCandidateStateManager(sm, false) 571 r.NoError(err) 572 573 selfStake, err := isSelfStakeBucket(featureCtxPreHF, csm, buckets[0]) 574 r.NoError(err) 575 r.True(selfStake) 576 selfStake, err = isSelfStakeBucket(featureCtxPostHF, csm, buckets[0]) 577 r.NoError(err) 578 r.True(selfStake) 579 }) 580 t.Run("endorsement expired", func(t *testing.T) { 581 bucketCfgs := []*bucketConfig{ 582 {identityset.Address(1), identityset.Address(2), "1200000000000000000000000", 100, true, true, nil, 1}, 583 } 584 candCfgs := []*candidateConfig{ 585 {identityset.Address(1), identityset.Address(11), identityset.Address(21), "cand1"}, 586 } 587 sm, _, buckets, _ := initTestStateWithHeight(t, ctrl, bucketCfgs, candCfgs, 2) 588 csm, err := NewCandidateStateManager(sm, false) 589 r.NoError(err) 590 selfStake, err := isSelfStakeBucket(featureCtxPreHF, csm, buckets[0]) 591 r.NoError(err) 592 r.True(selfStake) 593 selfStake, err = isSelfStakeBucket(featureCtxPostHF, csm, buckets[0]) 594 r.NoError(err) 595 r.False(selfStake) 596 }) 597 }