Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions action/protocol/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ type (
CalculateProbationList CheckFunc
LoadCandidatesLegacy CheckFunc
CandCenterHasAlias CheckFunc
CandidateWithoutIdentity CheckFunc
}
)

Expand Down Expand Up @@ -391,6 +392,9 @@ func WithFeatureWithHeightCtx(ctx context.Context) context.Context {
CandCenterHasAlias: func(height uint64) bool {
return !g.IsOkhotsk(height)
},
CandidateWithoutIdentity: func(height uint64) bool {
return !g.IsToBeEnabled(height)
},
},
)
}
Expand Down
4 changes: 2 additions & 2 deletions action/protocol/poll/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func NewProtocol(
candidateIndexer *CandidateIndexer,
readContract ReadContract,
getCandidates GetCandidates,
getprobationList GetProbationList,
getProbationList GetProbationList,
getUnproductiveDelegate GetUnproductiveDelegate,
electionCommittee committee.Committee,
stakingProto *staking.Protocol,
Expand All @@ -155,7 +155,7 @@ func NewProtocol(
slasher, err = NewSlasher(
productivity,
getCandidates,
getprobationList,
getProbationList,
getUnproductiveDelegate,
candidateIndexer,
genesisConfig.NumCandidateDelegates,
Expand Down
27 changes: 22 additions & 5 deletions action/protocol/poll/slasher.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@
return nil, uint64(0), errors.Wrapf(err, "failed to get probation list at height %d", targetEpochStartHeight)
}
// recalculate the voting power for probationlist delegates
filteredCandidate, err := filterCandidates(candidates, unqualifiedList, targetEpochStartHeight)
filteredCandidate, err := filterCandidates(candidates, unqualifiedList, targetEpochStartHeight, featureWithHeightCtx.CandidateWithoutIdentity(targetEpochStartHeight))
if err != nil {
return nil, uint64(0), err
}
Expand Down Expand Up @@ -334,7 +334,7 @@
return nil, err
}
// recalculate the voting power for probationlist delegates
return filterCandidates(candidates, probationList, epochStartHeight)
return filterCandidates(candidates, probationList, epochStartHeight, featureWithHeightCtx.CandidateWithoutIdentity(epochStartHeight))
}

// GetBPFromIndexer returns BP list from indexer
Expand Down Expand Up @@ -488,7 +488,7 @@
return nextProbationlist, setUnproductiveDelegates(sm, upd)
}

func (sh *Slasher) calculateUnproductiveDelegates(ctx context.Context, sr protocol.StateReader) (map[string]uint64, error) {

Check failure on line 491 in action/protocol/poll/slasher.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 16 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=iotexproject_iotex-core&issues=AZri-e-ipDw-T5GgKRcI&open=AZri-e-ipDw-T5GgKRcI&pullRequest=4760
blkCtx := protocol.MustGetBlockCtx(ctx)
bcCtx := protocol.MustGetBlockchainCtx(ctx)
featureCtx := protocol.MustGetFeatureCtx(ctx)
Expand Down Expand Up @@ -520,11 +520,12 @@
} else {
produce[blkCtx.Producer.String()] = 1
}

producerToOwner := make(map[string]string)
for _, abp := range delegates {
if _, ok := produce[abp.Address]; !ok {
produce[abp.Address] = 0
}
producerToOwner[abp.Address] = abp.Identity
}
unqualified := make(map[string]uint64, 0)
expectedNumBlks := numBlks / uint64(len(produce))
Expand All @@ -533,7 +534,18 @@
unqualified[addr] = expectedNumBlks - actualNumBlks
}
}
return unqualified, nil
if protocol.MustGetFeatureWithHeightCtx(ctx).CandidateWithoutIdentity(blkCtx.BlockHeight) {
return unqualified, nil
}
retval := make(map[string]uint64, len(unqualified))
for addr, count := range unqualified {
if owner, ok := producerToOwner[addr]; ok {
retval[owner] = count
} else {
retval[addr] = count
}
}
return retval, nil
}

func (sh *Slasher) updateCurrentBlockMeta(ctx context.Context, sm protocol.StateManager) error {
Expand Down Expand Up @@ -595,13 +607,18 @@
candidates state.CandidateList,
unqualifiedList *vote.ProbationList,
epochStartHeight uint64,
usingOperatorAddr bool,
) (state.CandidateList, error) {
candidatesMap := make(map[string]*state.Candidate)
updatedVotingPower := make(map[string]*big.Int)
intensityRate := float64(uint32(100)-unqualifiedList.IntensityRate) / float64(100)
for _, cand := range candidates {
filterCand := cand.Clone()
if _, ok := unqualifiedList.ProbationInfo[cand.Address]; ok {
id := cand.Address
if !usingOperatorAddr {
id = cand.Identity
}
if _, ok := unqualifiedList.ProbationInfo[id]; ok {
// if it is an unqualified delegate, multiply the voting power with probation intensity rate
votingPower := new(big.Float).SetInt(filterCand.Votes)
filterCand.Votes, _ = votingPower.Mul(votingPower, big.NewFloat(intensityRate)).Int(nil)
Expand Down
40 changes: 31 additions & 9 deletions action/protocol/rewarding/reward.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,12 +516,29 @@ func (p *Protocol) slashDelegate(
candidate *state.Candidate,
amount *big.Int,
) (*action.Log, error) {
candidateAddr, err := address.FromString(candidate.Address)
if err != nil {
return nil, err
}
if err := stakingProtocol.SlashCandidate(ctx, sm, candidateAddr, amount); err != nil {
return nil, errors.Wrapf(err, "failed to slash candidate %s", candidate.Address)
var candidateAddr address.Address
var err error
switch {
case protocol.MustGetFeatureCtx(ctx).CandidateSlashByOwner || !protocol.MustGetFeatureWithHeightCtx(ctx).CandidateWithoutIdentity(blockHeight):
if candidate.Identity != "" {
candidateAddr, err = address.FromString(candidate.Identity)
if err != nil {
return nil, err
}
if err := stakingProtocol.SlashCandidateByID(ctx, sm, candidateAddr, amount); err != nil {
return nil, errors.Wrapf(err, "failed to slash candidate %s", candidate.Identity)
}
break
}
fallthrough
default:
candidateAddr, err = address.FromString(candidate.Address)
if err != nil {
return nil, err
}
if err := stakingProtocol.SlashCandidateByOperator(ctx, sm, candidateAddr, amount); err != nil {
return nil, errors.Wrapf(err, "failed to slash candidate %s", candidate.Address)
}
}
data, err := p.encodeRewardLog(rewardingpb.RewardLog_UNPRODUCTIVE_SLASH, candidateAddr.String(), amount)
if err != nil {
Expand Down Expand Up @@ -558,8 +575,13 @@ func (p *Protocol) slashUqd(
slashLogs := make([]*action.Log, 0)
snapshot := view.Snapshot()
fCtx := protocol.MustGetFeatureCtx(ctx)
usingOperator := protocol.MustGetFeatureWithHeightCtx(ctx).CandidateWithoutIdentity(blockHeight)
for _, candidate := range candidates {
if missed, ok := uqdMap[candidate.Address]; ok {
id := candidate.Identity
if usingOperator {
id = candidate.Address
}
if missed, ok := uqdMap[id]; ok {
if missed == 0 {
// hard probation, no slash
continue
Expand All @@ -571,10 +593,10 @@ func (p *Protocol) slashUqd(
slashLogs = append(slashLogs, actLog)
totalSlashAmount.Add(totalSlashAmount, amount)
case staking.ErrNoSelfStakeBucket:
log.S().Errorf("Candidate %s doesn't have self-stake bucket, no slash", candidate.Address)
log.S().Errorf("Candidate %s doesn't have self-stake bucket, no slash", id)
case staking.ErrCandidateNotExist:
if !fCtx.CandidateSlashByOwner {
log.S().Errorf("Candidate %s doesn't exist, ignore slash", candidate.Address)
log.S().Errorf("Candidate %s doesn't exist, ignore slash", id)
continue
}
fallthrough
Expand Down
12 changes: 8 additions & 4 deletions action/protocol/staking/candidate.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,14 +341,18 @@ func (d *Candidate) toIoTeXTypes() *iotextypes.CandidateV2 {
}
}

func (d *Candidate) toStateCandidate() *state.Candidate {
return &state.Candidate{
func (d *Candidate) toStateCandidate(ignoreIdentity bool) *state.Candidate {
c := &state.Candidate{
Address: d.Operator.String(), // state need candidate operator not owner address
Votes: new(big.Int).Set(d.Votes),
RewardAddress: d.Reward.String(),
CanName: []byte(d.Name),
BLSPubKey: d.BLSPubKey,
}
if !ignoreIdentity {
c.Identity = d.GetIdentifier().String()
}
return c
}

func (l CandidateList) Len() int { return len(l) }
Expand Down Expand Up @@ -457,10 +461,10 @@ func (l *CandidateList) Decode(keys [][]byte, gvs []systemcontracts.GenericValue
return nil
}

func (l CandidateList) toStateCandidateList() (state.CandidateList, error) {
func (l CandidateList) toStateCandidateList(ignoreIdentity bool) (state.CandidateList, error) {
list := make(state.CandidateList, 0, len(l))
for _, c := range l {
list = append(list, c.toStateCandidate())
list = append(list, c.toStateCandidate(ignoreIdentity))
}
sort.Sort(list)
return list, nil
Expand Down
4 changes: 2 additions & 2 deletions action/protocol/staking/candidate_center_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func testEqualAllCommit(r *require.Assertions, m *CandidateCenter, old Candidate
list := m.All()
r.Equal(size+increase, len(list))
r.Equal(size+increase, m.Size())
all, err := list.toStateCandidateList()
all, err := list.toStateCandidateList(true)
r.NoError(err)

// number of changed cand = change
Expand All @@ -76,7 +76,7 @@ func testEqualAllCommit(r *require.Assertions, m *CandidateCenter, old Candidate
list = m.All()
r.Equal(size+increase, len(list))
r.Equal(size+increase, m.Size())
all1, err := list.toStateCandidateList()
all1, err := list.toStateCandidateList(true)
r.NoError(err)
r.Equal(all, all1)
return list, nil
Expand Down
23 changes: 22 additions & 1 deletion action/protocol/staking/candidate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ func TestSer(t *testing.T) {
r.NoError(l1.Deserialize(ser))
r.Equal(l, l1)

sl, err := l.toStateCandidateList(true)
r.NoError(err)
r.Equal(l.Len(), len(sl))
for _, sc := range sl {
r.Equal("", sc.Identity)
}
sl, err = l.toStateCandidateList(false)
r.NoError(err)
r.Equal(l.Len(), len(sl))
for _, sc := range sl {
r.NotEqual("", sc.Identity)
}

// empty CandidateList can successfully Serialize/Deserialize
var m CandidateList
ser, err = m.Serialize()
Expand Down Expand Up @@ -102,7 +115,15 @@ func TestClone(t *testing.T) {
d.Identifier = identityset.Address(3)
r.Equal(d.Identifier, d.GetIdentifier())

c := d.toStateCandidate()
c := d.toStateCandidate(false)
r.Equal(d.GetIdentifier().String(), c.Identity)
r.Equal(d.Owner.String(), c.Address)
r.Equal(d.Reward.String(), c.RewardAddress)
r.Equal(d.Votes, c.Votes)
r.Equal(d.Name, string(c.CanName))

c = d.toStateCandidate(true)
r.Equal("", c.Identity)
r.Equal(d.Owner.String(), c.Address)
r.Equal(d.Reward.String(), c.RewardAddress)
r.Equal(d.Votes, c.Votes)
Expand Down
37 changes: 26 additions & 11 deletions action/protocol/staking/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,28 +405,43 @@
return errors.Wrap(csm.Commit(ctx), "failed to commit candidate change in CreateGenesisStates")
}

func (p *Protocol) SlashCandidate(
func (p *Protocol) SlashCandidateByOperator(
ctx context.Context,
sm protocol.StateManager,
operator address.Address,
amount *big.Int,
) error {
if amount == nil || amount.Sign() <= 0 {
return errors.New("nil or non-positive amount")
csm, err := NewCandidateStateManager(sm)
if err != nil {
return errors.Wrap(err, "failed to create candidate state manager")
}
return p.slashCandidate(ctx, csm, csm.GetByOperator(operator), amount)
}

func (p *Protocol) SlashCandidateByID(
ctx context.Context,
sm protocol.StateManager,
id address.Address,
amount *big.Int,
) error {
csm, err := NewCandidateStateManager(sm)
if err != nil {
return errors.Wrap(err, "failed to create candidate state manager")
}
fCtx := protocol.MustGetFeatureCtx(ctx)
var candidate *Candidate
if fCtx.CandidateSlashByOwner {
candidate = csm.GetByIdentifier(operator)
} else {
candidate = csm.GetByOperator(operator)
return p.slashCandidate(ctx, csm, csm.GetByIdentifier(id), amount)
}

func (p *Protocol) slashCandidate(
ctx context.Context,

Check warning on line 435 in action/protocol/staking/protocol.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unused parameter 'ctx' should be removed.

See more on https://sonarcloud.io/project/issues?id=iotexproject_iotex-core&issues=AZsMhqimenflJW7xzsvn&open=AZsMhqimenflJW7xzsvn&pullRequest=4760
csm CandidateStateManager,
candidate *Candidate,
amount *big.Int,
) error {
if amount == nil || amount.Sign() <= 0 {
return errors.New("nil or non-positive amount")
}
if candidate == nil {
return errors.Wrapf(ErrCandidateNotExist, "candidate operator %s does not exist", operator.String())
return ErrCandidateNotExist
}
if candidate.SelfStakeBucketIdx == candidateNoSelfStakeBucketIndex {
return errors.Wrap(ErrNoSelfStakeBucket, "failed to slash candidate")
Expand Down Expand Up @@ -864,7 +879,7 @@
cand = append(cand, list[i])
}
}
return cand.toStateCandidateList()
return cand.toStateCandidateList(protocol.MustGetFeatureWithHeightCtx(ctx).CandidateWithoutIdentity(height))
}

// ReadState read the state on blockchain via protocol
Expand Down
10 changes: 8 additions & 2 deletions action/protocol/staking/protocol_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,9 @@ func TestProtocol(t *testing.T) {
}

// csm's candidate center should be identical to all candidates in stateDB
c1, err := all.toStateCandidateList()
c1, err := all.toStateCandidateList(true)
r.NoError(err)
c2, err := csm.DirtyView().candCenter.All().toStateCandidateList()
c2, err := csm.DirtyView().candCenter.All().toStateCandidateList(true)
r.NoError(err)
r.Equal(c1, c2)

Expand Down Expand Up @@ -669,6 +669,7 @@ func TestSlashCandidate(t *testing.T) {
BlockHeight: 100,
})
ctx = protocol.WithFeatureCtx(ctx)
ctx = protocol.WithFeatureWithHeightCtx(ctx)

t.Run("nil amount", func(t *testing.T) {
err := p.SlashCandidate(ctx, sm, owner, nil)
Expand Down Expand Up @@ -741,5 +742,10 @@ func TestSlashCandidate(t *testing.T) {
)
require.NoError(err)
require.Equal(1, len(cl))
require.Equal(cl[0].Identity, "")
cl, err = p.ActiveCandidates(ctx, sm, genesis.Default.ToBeEnabledBlockHeight)
require.NoError(err)
require.Equal(1, len(cl))
require.Equal(cl[0].Identity, owner.String())
})
}
2 changes: 2 additions & 0 deletions e2etest/contract_staking_v2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2065,6 +2065,7 @@ func checkStakingViewInit(test *e2etest, require *require.Assertions) {
BlockTimeStamp: tipHeader.Timestamp(),
})
ctx = protocol.WithFeatureCtx(ctx)
ctx = protocol.WithFeatureWithHeightCtx(ctx)
cands, err := stk.ActiveCandidates(ctx, test.cs.StateFactory(), 0)
require.NoError(err)

Expand Down Expand Up @@ -2097,6 +2098,7 @@ func checkStakingVoteView(test *e2etest, require *require.Assertions, candName s
})
ctx = genesis.WithGenesisContext(ctx, test.cfg.Genesis)
ctx = protocol.WithFeatureCtx(ctx)
ctx = protocol.WithFeatureWithHeightCtx(ctx)
cands, err := stkPtl.ActiveCandidates(ctx, test.cs.StateFactory(), 0)
require.NoError(err)
cand1 := slices.IndexFunc(cands, func(c *state.Candidate) bool {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ require (
github.com/iotexproject/iotex-address v0.2.9-0.20251203033311-6e8aa4fd43ef
github.com/iotexproject/iotex-antenna-go/v2 v2.6.4
github.com/iotexproject/iotex-election v0.3.8-0.20251015031218-8df952babca1
github.com/iotexproject/iotex-proto v0.6.5
github.com/iotexproject/iotex-proto v0.6.6-0.20251119052518-636e321b7e33
github.com/ipfs/go-ipfs-api v0.7.0
github.com/libp2p/go-libp2p v0.39.0
github.com/mackerelio/go-osstat v0.2.4
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -668,8 +668,8 @@ github.com/iotexproject/iotex-antenna-go/v2 v2.6.4 h1:7e0VyBDFT+iqwvr/BIk38yf7nC
github.com/iotexproject/iotex-antenna-go/v2 v2.6.4/go.mod h1:L6AzDHo2TBFDAPA3ly+/PCS4JSX2g3zzhwV8RGQsTDI=
github.com/iotexproject/iotex-election v0.3.8-0.20251015031218-8df952babca1 h1:jPLni/qKAnxv87HMCutde2tP9JmfWuLZgGpB4OArQGM=
github.com/iotexproject/iotex-election v0.3.8-0.20251015031218-8df952babca1/go.mod h1:w9HriT1coMRbuknaSD2xqiOqDTnowBDzvFZv8tg1j2M=
github.com/iotexproject/iotex-proto v0.6.5 h1:EyzwDXYtGWvJ/qnYCwhqypOjpEAQPvESo+EdPcUJPE0=
github.com/iotexproject/iotex-proto v0.6.5/go.mod h1:OOXZIG6Q9tInog8Y5zzEJQsDv9IaG/xxpDtl4KzdWZs=
github.com/iotexproject/iotex-proto v0.6.6-0.20251119052518-636e321b7e33 h1:zMcBndFDAbu0eutHEP2VuaeSZ53HXvc4BwOnhn2YvYk=
github.com/iotexproject/iotex-proto v0.6.6-0.20251119052518-636e321b7e33/go.mod h1:OOXZIG6Q9tInog8Y5zzEJQsDv9IaG/xxpDtl4KzdWZs=
github.com/ipfs/boxo v0.27.2 h1:sGo4KdwBaMjdBjH08lqPJyt27Z4CO6sugne3ryX513s=
github.com/ipfs/boxo v0.27.2/go.mod h1:qEIRrGNr0bitDedTCzyzBHxzNWqYmyuHgK8LG9Q83EM=
github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs=
Expand Down
Loading
Loading