Skip to content

Commit

Permalink
[staking] fix weighted votes of contract staking bucket (#3936)
Browse files Browse the repository at this point in the history
  • Loading branch information
envestcc authored Oct 13, 2023
1 parent 304f804 commit bd7b5e4
Show file tree
Hide file tree
Showing 20 changed files with 497 additions and 128 deletions.
2 changes: 2 additions & 0 deletions action/protocol/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ type (
AllowCorrectChainIDOnly bool
AddContractStakingVotes bool
SharedGasWithDapp bool
FixContractStakingWeightedVotes bool
}

// FeatureWithHeightCtx provides feature check functions.
Expand Down Expand Up @@ -253,6 +254,7 @@ func WithFeatureCtx(ctx context.Context) context.Context {
AllowCorrectChainIDOnly: g.IsQuebec(height),
AddContractStakingVotes: g.IsQuebec(height),
SharedGasWithDapp: g.IsToBeEnabled(height),
FixContractStakingWeightedVotes: g.IsRedsea(height),
},
)
}
Expand Down
3 changes: 2 additions & 1 deletion action/protocol/staking/contractstake_indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package staking

import (
"context"
"math/big"

"github.com/iotexproject/iotex-address/address"
Expand All @@ -17,7 +18,7 @@ type (
ContractStakingIndexer interface {
// CandidateVotes returns the total staked votes of a candidate
// candidate identified by owner address
CandidateVotes(ownerAddr address.Address, height uint64) (*big.Int, error)
CandidateVotes(ctx context.Context, ownerAddr address.Address, height uint64) (*big.Int, error)
// Buckets returns active buckets
Buckets(height uint64) ([]*VoteBucket, error)
// BucketsByIndices returns active buckets by indices
Expand Down
9 changes: 5 additions & 4 deletions action/protocol/staking/contractstake_indexer_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions action/protocol/staking/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ func (p *Protocol) ActiveCandidates(ctx context.Context, sr protocol.StateReader
// specifying the height param instead of query latest from indexer directly, aims to cause error when indexer falls behind
// currently there are two possible sr (i.e. factory or workingSet), it means the height could be chain height or current block height
// using height-1 will cover the two scenario while detect whether the indexer is lagging behind
contractVotes, err := p.contractStakingIndexer.CandidateVotes(list[i].Owner, srHeight-1)
contractVotes, err := p.contractStakingIndexer.CandidateVotes(ctx, list[i].Owner, srHeight-1)
if err != nil {
return nil, errors.Wrap(err, "failed to get CandidateVotes from contractStakingIndexer")
}
Expand Down Expand Up @@ -598,7 +598,7 @@ func (p *Protocol) Name() string {
}

func (p *Protocol) calculateVoteWeight(v *VoteBucket, selfStake bool) *big.Int {
return calculateVoteWeight(p.config.VoteWeightCalConsts, v, selfStake)
return CalculateVoteWeight(p.config.VoteWeightCalConsts, v, selfStake)
}

// settleAccount deposits gas fee and updates caller's nonce
Expand Down
2 changes: 1 addition & 1 deletion action/protocol/staking/protocol_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ func TestProtocol_ActiveCandidates(t *testing.T) {
require.NoError(err)

var csIndexerHeight, csVotes uint64
csIndexer.EXPECT().CandidateVotes(gomock.Any(), gomock.Any()).DoAndReturn(func(ownerAddr address.Address, height uint64) (*big.Int, error) {
csIndexer.EXPECT().CandidateVotes(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, ownerAddr address.Address, height uint64) (*big.Int, error) {
if height != csIndexerHeight {
return nil, errors.Errorf("invalid height")
}
Expand Down
11 changes: 6 additions & 5 deletions action/protocol/staking/staking_statereader.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ func (c *compositeStakingStateReader) readStateCandidates(ctx context.Context, r
return candidates, height, nil
}
for _, candidate := range candidates.Candidates {
if err = addContractStakingVotes(candidate, c.contractIndexer, height); err != nil {
if err = addContractStakingVotes(ctx, candidate, c.contractIndexer, height); err != nil {
return nil, 0, err
}
}
Expand All @@ -242,7 +242,7 @@ func (c *compositeStakingStateReader) readStateCandidateByName(ctx context.Conte
if !protocol.MustGetFeatureCtx(ctx).AddContractStakingVotes {
return candidate, height, nil
}
if err := addContractStakingVotes(candidate, c.contractIndexer, height); err != nil {
if err := addContractStakingVotes(ctx, candidate, c.contractIndexer, height); err != nil {
return nil, 0, err
}
return candidate, height, nil
Expand All @@ -259,7 +259,7 @@ func (c *compositeStakingStateReader) readStateCandidateByAddress(ctx context.Co
if !protocol.MustGetFeatureCtx(ctx).AddContractStakingVotes {
return candidate, height, nil
}
if err := addContractStakingVotes(candidate, c.contractIndexer, height); err != nil {
if err := addContractStakingVotes(ctx, candidate, c.contractIndexer, height); err != nil {
return nil, 0, err
}
return candidate, height, nil
Expand Down Expand Up @@ -314,7 +314,8 @@ func (c *compositeStakingStateReader) isContractStakingEnabled() bool {
return c.contractIndexer != nil
}

func addContractStakingVotes(candidate *iotextypes.CandidateV2, contractStakingSR ContractStakingIndexer, height uint64) error {
// TODO: move into compositeStakingStateReader
func addContractStakingVotes(ctx context.Context, candidate *iotextypes.CandidateV2, contractStakingSR ContractStakingIndexer, height uint64) error {
votes, ok := big.NewInt(0).SetString(candidate.TotalWeightedVotes, 10)
if !ok {
return errors.Errorf("invalid total weighted votes %s", candidate.TotalWeightedVotes)
Expand All @@ -323,7 +324,7 @@ func addContractStakingVotes(candidate *iotextypes.CandidateV2, contractStakingS
if err != nil {
return err
}
contractVotes, err := contractStakingSR.CandidateVotes(addr, height)
contractVotes, err := contractStakingSR.CandidateVotes(ctx, addr, height)
if err != nil {
return err
}
Expand Down
6 changes: 3 additions & 3 deletions action/protocol/staking/staking_statereader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ func TestStakingStateReader(t *testing.T) {
})
t.Run("readStateCandidates", func(t *testing.T) {
_, contractIndexer, stakeSR, ctx, r := prepare(t)
contractIndexer.EXPECT().CandidateVotes(gomock.Any(), gomock.Any()).DoAndReturn(func(ownerAddr address.Address, height uint64) (*big.Int, error) {
contractIndexer.EXPECT().CandidateVotes(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, ownerAddr address.Address, height uint64) (*big.Int, error) {
for _, b := range testContractBuckets {
if b.Owner.String() == ownerAddr.String() {
return b.StakedAmount, nil
Expand Down Expand Up @@ -362,7 +362,7 @@ func TestStakingStateReader(t *testing.T) {
})
t.Run("readStateCandidateByName", func(t *testing.T) {
_, contractIndexer, stakeSR, ctx, r := prepare(t)
contractIndexer.EXPECT().CandidateVotes(gomock.Any(), gomock.Any()).DoAndReturn(func(ownerAddr address.Address, height uint64) (*big.Int, error) {
contractIndexer.EXPECT().CandidateVotes(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, ownerAddr address.Address, height uint64) (*big.Int, error) {
for _, b := range testContractBuckets {
if b.Owner.String() == ownerAddr.String() {
return b.StakedAmount, nil
Expand All @@ -385,7 +385,7 @@ func TestStakingStateReader(t *testing.T) {
})
t.Run("readStateCandidateByAddress", func(t *testing.T) {
_, contractIndexer, stakeSR, ctx, r := prepare(t)
contractIndexer.EXPECT().CandidateVotes(gomock.Any(), gomock.Any()).DoAndReturn(func(ownerAddr address.Address, height uint64) (*big.Int, error) {
contractIndexer.EXPECT().CandidateVotes(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, ownerAddr address.Address, height uint64) (*big.Int, error) {
for _, b := range testContractBuckets {
if b.Owner.String() == ownerAddr.String() {
return b.StakedAmount, nil
Expand Down
9 changes: 7 additions & 2 deletions action/protocol/staking/vote_bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,16 @@ func (vb *VoteBucket) Serialize() ([]byte, error) {
}

func (vb *VoteBucket) isUnstaked() bool {
if vb.ContractAddress == "" {
if vb.isNative() {
return vb.UnstakeStartTime.After(vb.StakeStartTime)
}
return vb.UnstakeStartBlockHeight < maxBlockNumber
}

func (vb *VoteBucket) isNative() bool {
return vb.ContractAddress == ""
}

// Deserialize deserializes bytes into bucket count
func (tc *totalBucketCount) Deserialize(data []byte) error {
tc.count = byteutil.BytesToUint64BigEndian(data)
Expand All @@ -213,7 +217,8 @@ func bucketKey(index uint64) []byte {
return append(key, byteutil.Uint64ToBytesBigEndian(index)...)
}

func calculateVoteWeight(c genesis.VoteWeightCalConsts, v *VoteBucket, selfStake bool) *big.Int {
// CalculateVoteWeight calculates the vote weight
func CalculateVoteWeight(c genesis.VoteWeightCalConsts, v *VoteBucket, selfStake bool) *big.Int {
remainingTime := v.StakedDuration.Seconds()
weight := float64(1)
var m float64
Expand Down
172 changes: 172 additions & 0 deletions action/protocol/staking/vote_bucket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
"github.com/stretchr/testify/require"

"github.com/iotexproject/iotex-core/action/protocol"
"github.com/iotexproject/iotex-core/blockchain/genesis"
"github.com/iotexproject/iotex-core/consensus/consensusfsm"
"github.com/iotexproject/iotex-core/state"
"github.com/iotexproject/iotex-core/test/identityset"
"github.com/iotexproject/iotex-core/testutil/testdb"
Expand Down Expand Up @@ -102,3 +104,173 @@ func TestGetPutStaking(t *testing.T) {
require.Equal(state.ErrStateNotExist, errors.Cause(err))
}
}

func TestCalculateVoteWeight(t *testing.T) {
// Define test cases
blockInterval := consensusfsm.DefaultDardanellesUpgradeConfig.BlockInterval
consts := genesis.Default.VoteWeightCalConsts
tests := []struct {
name string
consts genesis.VoteWeightCalConsts
voteBucket *VoteBucket
selfStake bool
expected *big.Int
}{
{
name: "Native, auto-stake enabled, self-stake enabled",
consts: consts,
voteBucket: &VoteBucket{
StakedDuration: time.Duration(100) * 24 * time.Hour,
AutoStake: true,
StakedAmount: big.NewInt(100),
},
selfStake: true,
expected: big.NewInt(136),
},
{
name: "Native, auto-stake enabled, self-stake disabled",
consts: consts,
voteBucket: &VoteBucket{
StakedDuration: time.Duration(100) * 24 * time.Hour,
AutoStake: true,
StakedAmount: big.NewInt(100),
},
selfStake: false,
expected: big.NewInt(129),
},
{
name: "Native, auto-stake disabled, self-stake enabled",
consts: consts,
voteBucket: &VoteBucket{
StakedDuration: time.Duration(100) * 24 * time.Hour,
AutoStake: false,
StakedAmount: big.NewInt(100),
},
selfStake: true,
expected: big.NewInt(125),
},
{
name: "Native, auto-stake disabled, self-stake disabled",
consts: consts,
voteBucket: &VoteBucket{
StakedDuration: time.Duration(100) * 24 * time.Hour,
AutoStake: false,
StakedAmount: big.NewInt(100),
},
selfStake: false,
expected: big.NewInt(125),
},
{
name: "NFT, auto-stake enabled",
consts: consts,
voteBucket: &VoteBucket{
StakedDuration: 30 * 17280 * blockInterval,
StakedDurationBlockNumber: 30 * 17280,
AutoStake: true,
StakedAmount: big.NewInt(10000),
ContractAddress: identityset.Address(1).String(),
},
selfStake: false,
expected: big.NewInt(12245),
},
{
name: "NFT, auto-stake disabled",
consts: consts,
voteBucket: &VoteBucket{
StakedDuration: 30 * 17280 * blockInterval,
StakedDurationBlockNumber: 30 * 17280,
AutoStake: false,
StakedAmount: big.NewInt(10000),
ContractAddress: identityset.Address(1).String(),
},
selfStake: false,
expected: big.NewInt(11865),
},
{
name: "NFT-I, auto-stake enabled",
consts: consts,
voteBucket: &VoteBucket{
StakedDuration: 91 * 17280 * blockInterval,
StakedDurationBlockNumber: 91 * 17280,
AutoStake: true,
StakedAmount: big.NewInt(10000),
ContractAddress: identityset.Address(1).String(),
},
selfStake: false,
expected: big.NewInt(12854),
},
{
name: "NFT-I, auto-stake disabled",
consts: consts,
voteBucket: &VoteBucket{
StakedDuration: 91 * 17280 * blockInterval,
StakedDurationBlockNumber: 91 * 17280,
AutoStake: false,
StakedAmount: big.NewInt(10000),
ContractAddress: identityset.Address(1).String(),
},
selfStake: true,
expected: big.NewInt(12474),
},
{
name: "NFT-II, auto-stake enabled",
consts: consts,
voteBucket: &VoteBucket{
StakedDuration: 91 * 17280 * blockInterval,
StakedDurationBlockNumber: 91 * 17280,
AutoStake: true,
StakedAmount: big.NewInt(100000),
ContractAddress: identityset.Address(1).String(),
},
selfStake: false,
expected: big.NewInt(128543),
},
{
name: "NFT-II, auto-stake disabled",
consts: consts,
voteBucket: &VoteBucket{
StakedDuration: 91 * 17280 * blockInterval,
StakedDurationBlockNumber: 91 * 17280,
AutoStake: false,
StakedAmount: big.NewInt(100000),
ContractAddress: identityset.Address(1).String(),
},
selfStake: true,
expected: big.NewInt(124741),
},
{
name: "NFT-III, auto-stake enabled",
consts: consts,
voteBucket: &VoteBucket{
StakedDuration: 2 * 17280 * blockInterval,
StakedDurationBlockNumber: 2 * 17280,
AutoStake: true,
StakedAmount: big.NewInt(1000),
ContractAddress: identityset.Address(1).String(),
},
selfStake: false,
expected: big.NewInt(1076),
},
{
name: "NFT-III, auto-stake disabled",
consts: consts,
voteBucket: &VoteBucket{
StakedDuration: 2 * 17280 * blockInterval,
StakedDurationBlockNumber: 2 * 17280,
AutoStake: false,
StakedAmount: big.NewInt(1000),
ContractAddress: identityset.Address(1).String(),
},
selfStake: true,
expected: big.NewInt(1038),
},
}

// Run tests
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual := CalculateVoteWeight(tt.consts, tt.voteBucket, tt.selfStake)
require.Equal(t, tt.expected, actual)
})
}
}
4 changes: 2 additions & 2 deletions action/protocol/staking/vote_reviser.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func (vr *VoteReviser) calculateVoteWeight(csm CandidateStateManager) (Candidate
}

if cand.SelfStakeBucketIdx == bucket.Index {
if err = cand.AddVote(calculateVoteWeight(vr.c, bucket, true)); err != nil {
if err = cand.AddVote(CalculateVoteWeight(vr.c, bucket, true)); err != nil {
log.L().Error("failed to add vote for candidate",
zap.Uint64("bucket index", bucket.Index),
zap.String("candidate", bucket.Candidate.String()),
Expand All @@ -146,7 +146,7 @@ func (vr *VoteReviser) calculateVoteWeight(csm CandidateStateManager) (Candidate
}
cand.SelfStake = bucket.StakedAmount
} else {
_ = cand.AddVote(calculateVoteWeight(vr.c, bucket, false))
_ = cand.AddVote(CalculateVoteWeight(vr.c, bucket, false))
}
}

Expand Down
Loading

0 comments on commit bd7b5e4

Please sign in to comment.