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  }