Skip to content

Commit

Permalink
[staking] CandidateCenter Support for Missing and Changes of Self-Sta…
Browse files Browse the repository at this point in the history
…ke Bucket (#4060)
  • Loading branch information
envestcc authored Jan 30, 2024
1 parent fef8141 commit 56eeca0
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 6 deletions.
7 changes: 6 additions & 1 deletion action/protocol/staking/candidate.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ func (d *Candidate) Validate() error {
return nil
}

// isSelfStakeBucketSettled checks if self stake bucket is settled
func (d *Candidate) isSelfStakeBucketSettled() bool {
return d.SelfStakeBucketIdx != candidateNoSelfStakeBucketIndex
}

// Collision checks collsion of 2 candidates
func (d *Candidate) Collision(c *Candidate) error {
if address.Equal(d.Owner, c.Owner) {
Expand All @@ -105,7 +110,7 @@ func (d *Candidate) Collision(c *Candidate) error {
if address.Equal(c.Operator, d.Operator) {
return ErrInvalidOperator
}
if c.SelfStakeBucketIdx == d.SelfStakeBucketIdx {
if c.SelfStakeBucketIdx == d.SelfStakeBucketIdx && c.isSelfStakeBucketSettled() {
return ErrInvalidSelfStkIndex
}
return nil
Expand Down
9 changes: 6 additions & 3 deletions action/protocol/staking/candidate_center.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ func (cc *candChange) containsOperator(operator address.Address) bool {

func (cc *candChange) containsSelfStakingBucket(index uint64) bool {
for _, d := range cc.dirty {
if index == d.SelfStakeBucketIdx {
if d.isSelfStakeBucketSettled() && index == d.SelfStakeBucketIdx {
return true
}
}
Expand Down Expand Up @@ -398,7 +398,7 @@ func (cc *candChange) getByOwner(owner address.Address) *Candidate {

func (cc *candChange) getBySelfStakingIndex(index uint64) *Candidate {
for _, d := range cc.dirty {
if index == d.SelfStakeBucketIdx {
if d.isSelfStakeBucketSettled() && index == d.SelfStakeBucketIdx {
return d.Clone()
}
}
Expand Down Expand Up @@ -479,11 +479,14 @@ func (cb *candBase) commit(change *candChange, keepAliasBug bool) (int, error) {
if curr, ok := cb.ownerMap[d.Owner.String()]; ok {
delete(cb.nameMap, curr.Name)
delete(cb.operatorMap, curr.Operator.String())
delete(cb.selfStkBucketMap, curr.SelfStakeBucketIdx)
}
cb.ownerMap[d.Owner.String()] = d
cb.nameMap[d.Name] = d
cb.operatorMap[d.Operator.String()] = d
cb.selfStkBucketMap[d.SelfStakeBucketIdx] = d
if d.isSelfStakeBucketSettled() {
cb.selfStkBucketMap[d.SelfStakeBucketIdx] = d
}
}
}
return len(cb.ownerMap), nil
Expand Down
139 changes: 137 additions & 2 deletions action/protocol/staking/candidate_center_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import (

// testEqual verifies m contains exactly the list
func testEqual(m *CandidateCenter, l CandidateList) bool {
if m.All().Len() != len(l) {
return false
}
for _, v := range l {
d := m.GetByOwner(v.Owner)
if d == nil {
Expand All @@ -33,10 +36,10 @@ func testEqual(m *CandidateCenter, l CandidateList) bool {
}

d = m.GetBySelfStakingIndex(v.SelfStakeBucketIdx)
if d == nil {
if d == nil && v.isSelfStakeBucketSettled() {
return false
}
if !v.Equal(d) {
if d != nil && !v.Equal(d) {
return false
}
}
Expand Down Expand Up @@ -429,6 +432,138 @@ func TestFixAlias(t *testing.T) {
}
}

func TestMultipleNonStakingCandidate(t *testing.T) {
r := require.New(t)

candStaked := &Candidate{
Owner: identityset.Address(1),
Operator: identityset.Address(11),
Reward: identityset.Address(3),
Name: "self-staked",
Votes: unit.ConvertIotxToRau(1200000),
SelfStakeBucketIdx: 1,
SelfStake: unit.ConvertIotxToRau(1200000),
}
candNonStaked1 := &Candidate{
Owner: identityset.Address(2),
Operator: identityset.Address(12),
Reward: identityset.Address(3),
Name: "non-self-staked1",
Votes: big.NewInt(0),
SelfStakeBucketIdx: candidateNoSelfStakeBucketIndex,
SelfStake: big.NewInt(0),
}
candNonStaked2 := &Candidate{
Owner: identityset.Address(3),
Operator: identityset.Address(13),
Reward: identityset.Address(3),
Name: "non-self-staked2",
Votes: big.NewInt(0),
SelfStakeBucketIdx: candidateNoSelfStakeBucketIndex,
SelfStake: big.NewInt(0),
}
candNonStaked1ColOwner := &Candidate{
Owner: identityset.Address(2),
Operator: identityset.Address(22),
Reward: identityset.Address(3),
Name: "non-self-staked1-col-owner",
Votes: big.NewInt(0),
SelfStakeBucketIdx: candidateNoSelfStakeBucketIndex,
SelfStake: big.NewInt(0),
}
candNonStaked1ColOpt := &Candidate{
Owner: identityset.Address(20),
Operator: identityset.Address(12),
Reward: identityset.Address(3),
Name: "non-self-staked1-col-opt",
Votes: big.NewInt(0),
SelfStakeBucketIdx: candidateNoSelfStakeBucketIndex,
SelfStake: big.NewInt(0),
}
candNonStaked1ColName := &Candidate{
Owner: identityset.Address(21),
Operator: identityset.Address(23),
Reward: identityset.Address(3),
Name: "non-self-staked1",
Votes: big.NewInt(0),
SelfStakeBucketIdx: candidateNoSelfStakeBucketIndex,
SelfStake: big.NewInt(0),
}
candStakedColBucket := &Candidate{
Owner: identityset.Address(4),
Operator: identityset.Address(14),
Reward: identityset.Address(3),
Name: "self-staked-col-bucket",
Votes: unit.ConvertIotxToRau(1200000),
SelfStakeBucketIdx: 1,
SelfStake: unit.ConvertIotxToRau(1200000),
}

checkCandidates := func(candcenter *CandidateCenter, cands []*Candidate) {
r.True(testEqual(candcenter, CandidateList(cands)))
// commit
r.NoError(candcenter.Commit())
r.True(testEqual(candcenter, CandidateList(cands)))
// from state manager
dk := protocol.NewDock()
view := protocol.View{}
r.NoError(view.Write(_protocolID, candcenter))
dk.Reset()
candcenter = candCenterFromNewCandidateStateManager(r, view, dk)
r.True(testEqual(candcenter, CandidateList(cands)))
}
t.Run("nonstaked candidate not collision on bucket", func(t *testing.T) {
candcenter, err := NewCandidateCenter(nil)
r.NoError(err)
r.NoError(candcenter.Upsert(candNonStaked1))
r.NoError(candcenter.Upsert(candNonStaked2))
checkCandidates(candcenter, []*Candidate{candNonStaked1, candNonStaked2})
})
t.Run("staked candidate collision on bucket", func(t *testing.T) {
candcenter, err := NewCandidateCenter(nil)
r.NoError(err)
r.NoError(candcenter.Upsert(candStaked))
r.ErrorIs(candcenter.Upsert(candStakedColBucket), ErrInvalidSelfStkIndex)
checkCandidates(candcenter, []*Candidate{candStaked})
})
t.Run("nonstaked candidate collision on operator", func(t *testing.T) {
candcenter, err := NewCandidateCenter(nil)
r.NoError(err)
r.NoError(candcenter.Upsert(candNonStaked1))
r.ErrorIs(candcenter.Upsert(candNonStaked1ColOpt), ErrInvalidOperator)
checkCandidates(candcenter, []*Candidate{candNonStaked1})
})
t.Run("nonstaked candidate collision on name", func(t *testing.T) {
candcenter, err := NewCandidateCenter(nil)
r.NoError(err)
r.NoError(candcenter.Upsert(candNonStaked1))
r.ErrorIs(candcenter.Upsert(candNonStaked1ColName), action.ErrInvalidCanName)
checkCandidates(candcenter, []*Candidate{candNonStaked1})
})
t.Run("nonstaked candidate update on owner", func(t *testing.T) {
candcenter, err := NewCandidateCenter(nil)
r.NoError(err)
r.NoError(candcenter.Upsert(candNonStaked1))
r.NoError(candcenter.Upsert(candNonStaked1ColOwner))
checkCandidates(candcenter, []*Candidate{candNonStaked1ColOwner})
})
t.Run("change bucket", func(t *testing.T) {
candcenter, err := NewCandidateCenter(nil)
r.NoError(err)
r.NoError(candcenter.Upsert(candNonStaked1))
// settle self-stake bucket
candStaked := candNonStaked1.Clone()
candStaked.SelfStakeBucketIdx = 1
candStaked.SelfStake = unit.ConvertIotxToRau(1200000)
r.NoError(candcenter.Upsert(candStaked))
// change self-stake bucket
candUpdated := candStaked.Clone()
candUpdated.SelfStakeBucketIdx = 2
r.NoError(candcenter.Upsert(candUpdated))
checkCandidates(candcenter, []*Candidate{candUpdated})
})
}

func candCenterFromNewCandidateStateManager(r *require.Assertions, view protocol.View, dk protocol.Dock) *CandidateCenter {
// get cand center: csm.ConstructBaseView
v, err := view.Read(_protocolID)
Expand Down

0 comments on commit 56eeca0

Please sign in to comment.