diff --git a/action/protocol/poll/nativestakingV2.go b/action/protocol/poll/nativestakingV2.go index 85b545c098..e548103f23 100644 --- a/action/protocol/poll/nativestakingV2.go +++ b/action/protocol/poll/nativestakingV2.go @@ -7,11 +7,12 @@ import ( "github.com/iotexproject/go-pkgs/hash" "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-election/util" + "github.com/iotexproject/iotex-core/action" "github.com/iotexproject/iotex-core/action/protocol" "github.com/iotexproject/iotex-core/action/protocol/staking" "github.com/iotexproject/iotex-core/state" - "github.com/iotexproject/iotex-election/util" ) type nativeStakingV2 struct { @@ -83,6 +84,7 @@ func (ns *nativeStakingV2) CalculateCandidatesByHeight(ctx context.Context, sr p return cands, err } bcCtx := protocol.MustGetBlockchainCtx(ctx) + // TODO: put candidates which will expired during current or next epoch behind the active candidates return ns.filterAndSortCandidatesByVoteScore(cands, bcCtx.Tip.Timestamp), nil } diff --git a/action/protocol/staking/candidate_statemanager.go b/action/protocol/staking/candidate_statemanager.go index e59742ea23..9cffd002a0 100644 --- a/action/protocol/staking/candidate_statemanager.go +++ b/action/protocol/staking/candidate_statemanager.go @@ -58,6 +58,13 @@ type ( DebitBucketPool(*big.Int, bool) error Commit(context.Context) error SM() protocol.StateManager + SR() protocol.StateReader + } + + // CandidiateStateCommon is the common interface for candidate state manager and reader + CandidiateStateCommon interface { + ContainsSelfStakingBucket(uint64) bool + SR() protocol.StateReader } candSM struct { @@ -106,6 +113,10 @@ func (csm *candSM) SM() protocol.StateManager { return csm.StateManager } +func (csm *candSM) SR() protocol.StateReader { + return csm.StateManager +} + // DirtyView is csm's current state, which reflects base view + applying delta saved in csm's dock func (csm *candSM) DirtyView() *ViewData { return &ViewData{ diff --git a/action/protocol/staking/candidate_statereader.go b/action/protocol/staking/candidate_statereader.go index 593499d3ff..1173e3b23e 100644 --- a/action/protocol/staking/candidate_statereader.go +++ b/action/protocol/staking/candidate_statereader.go @@ -65,6 +65,7 @@ type ( AllCandidates() CandidateList TotalStakedAmount() *big.Int ActiveBucketsCount() uint64 + ContainsSelfStakingBucket(index uint64) bool } candSR struct { @@ -110,6 +111,10 @@ func (c *candSR) AllCandidates() CandidateList { return c.view.candCenter.All() } +func (c *candSR) ContainsSelfStakingBucket(index uint64) bool { + return c.view.candCenter.ContainsSelfStakingBucket(index) +} + func (c *candSR) TotalStakedAmount() *big.Int { return c.view.bucketPool.Total() } diff --git a/action/protocol/staking/handlers_test.go b/action/protocol/staking/handlers_test.go index c7250757e1..4746fb5019 100644 --- a/action/protocol/staking/handlers_test.go +++ b/action/protocol/staking/handlers_test.go @@ -2810,7 +2810,7 @@ func TestProtocol_FetchBucketAndValidate(t *testing.T) { }) isSelfStake := true isSelfStakeErr := error(nil) - patches.ApplyFunc(isSelfStakeBucket, func(featureCtx protocol.FeatureCtx, csm CandidateStateManager, bucket *VoteBucket) (bool, error) { + patches.ApplyFunc(isSelfStakeBucket, func(featureCtx protocol.FeatureCtx, csm CandidiateStateCommon, bucket *VoteBucket) (bool, error) { return isSelfStake, isSelfStakeErr }) _, err = p.fetchBucketAndValidate(protocol.FeatureCtx{}, csm, identityset.Address(1), 1, false, false) @@ -2831,7 +2831,7 @@ func TestProtocol_FetchBucketAndValidate(t *testing.T) { Owner: identityset.Address(1), }, nil }) - patches.ApplyFunc(isSelfStakeBucket, func(featureCtx protocol.FeatureCtx, csm CandidateStateManager, bucket *VoteBucket) (bool, error) { + patches.ApplyFunc(isSelfStakeBucket, func(featureCtx protocol.FeatureCtx, csm CandidiateStateCommon, bucket *VoteBucket) (bool, error) { return false, nil }) defer patches.Reset() diff --git a/action/protocol/staking/protocol.go b/action/protocol/staking/protocol.go index b554deebb1..ca0808b0f6 100644 --- a/action/protocol/staking/protocol.go +++ b/action/protocol/staking/protocol.go @@ -56,8 +56,9 @@ const ( // Errors var ( - ErrWithdrawnBucket = errors.New("the bucket is already withdrawn") - TotalBucketKey = append([]byte{_const}, []byte("totalBucket")...) + ErrWithdrawnBucket = errors.New("the bucket is already withdrawn") + ErrEndorsementNotExist = errors.New("the endorsement does not exist") + TotalBucketKey = append([]byte{_const}, []byte("totalBucket")...) ) var ( @@ -472,6 +473,31 @@ func (p *Protocol) Validate(ctx context.Context, act action.Action, sr protocol. return nil } +func (p *Protocol) isActiveCandidate(ctx context.Context, csr CandidateStateReader, cand *Candidate) (bool, error) { + if cand.SelfStake.Cmp(p.config.RegistrationConsts.MinSelfStake) < 0 { + return false, nil + } + featureCtx := protocol.MustGetFeatureCtx(ctx) + if featureCtx.DisableDelegateEndorsement { + // before endorsement feature, candidates with enough amount must be active + return true, nil + } + bucket, err := csr.getBucket(cand.SelfStakeBucketIdx) + switch { + case errors.Cause(err) == state.ErrStateNotExist: + // endorse bucket has been withdrawn + return false, nil + case err != nil: + return false, errors.Wrapf(err, "failed to get bucket %d", cand.SelfStakeBucketIdx) + default: + } + selfStake, err := isSelfStakeBucket(featureCtx, csr, bucket) + if err != nil { + return false, errors.Wrapf(err, "failed to check self-stake bucket %d", cand.SelfStakeBucketIdx) + } + return selfStake, nil +} + // ActiveCandidates returns all active candidates in candidate center func (p *Protocol) ActiveCandidates(ctx context.Context, sr protocol.StateReader, height uint64) (state.CandidateList, error) { srHeight, err := sr.Height() @@ -496,7 +522,11 @@ func (p *Protocol) ActiveCandidates(ctx context.Context, sr protocol.StateReader } list[i].Votes.Add(list[i].Votes, contractVotes) } - if list[i].SelfStake.Cmp(p.config.RegistrationConsts.MinSelfStake) >= 0 { + active, err := p.isActiveCandidate(ctx, c, list[i]) + if err != nil { + return nil, err + } + if active { cand = append(cand, list[i]) } } @@ -686,9 +716,9 @@ func writeCandCenterStateToStateDB(sm protocol.StateManager, name, op, owners Ca } // isSelfStakeBucket returns true if the bucket is self-stake bucket and not expired -func isSelfStakeBucket(featureCtx protocol.FeatureCtx, csm CandidateStateManager, bucket *VoteBucket) (bool, error) { +func isSelfStakeBucket(featureCtx protocol.FeatureCtx, csc CandidiateStateCommon, bucket *VoteBucket) (bool, error) { // bucket index should be settled in one of candidates - selfStake := csm.ContainsSelfStakingBucket(bucket.Index) + selfStake := csc.ContainsSelfStakingBucket(bucket.Index) if featureCtx.DisableDelegateEndorsement || !selfStake { return selfStake, nil } @@ -698,7 +728,7 @@ func isSelfStakeBucket(featureCtx protocol.FeatureCtx, csm CandidateStateManager return !bucket.isUnstaked(), nil } // otherwise bucket should be an endorse bucket which is not expired - esm := NewEndorsementStateManager(csm.SM()) + esm := NewEndorsementStateReader(csc.SR()) height, err := esm.Height() if err != nil { return false, err