From bd7b5e43519c3fd400aa76bc25fb2f6c359e911e Mon Sep 17 00:00:00 2001 From: Chen Chen <34592639+envestcc@users.noreply.github.com> Date: Fri, 13 Oct 2023 23:38:50 +0800 Subject: [PATCH] [staking] fix weighted votes of contract staking bucket (#3936) --- action/protocol/context.go | 2 + .../protocol/staking/contractstake_indexer.go | 3 +- .../staking/contractstake_indexer_mock.go | 9 +- action/protocol/staking/protocol.go | 4 +- action/protocol/staking/protocol_test.go | 2 +- .../protocol/staking/staking_statereader.go | 11 +- .../staking/staking_statereader_test.go | 6 +- action/protocol/staking/vote_bucket.go | 9 +- action/protocol/staking/vote_bucket_test.go | 172 ++++++++++++++++ action/protocol/staking/vote_reviser.go | 4 +- action/protocol/staking/vote_reviser_test.go | 4 +- blockindex/contractstaking/bucket.go | 5 +- blockindex/contractstaking/cache.go | 23 ++- blockindex/contractstaking/cache_test.go | 65 ++++-- blockindex/contractstaking/delta_cache.go | 6 +- .../contractstaking/dirty_cache_test.go | 13 +- blockindex/contractstaking/indexer.go | 53 +++-- blockindex/contractstaking/indexer_test.go | 186 +++++++++++++----- chainservice/builder.go | 12 +- e2etest/contract_staking_test.go | 36 +++- 20 files changed, 497 insertions(+), 128 deletions(-) diff --git a/action/protocol/context.go b/action/protocol/context.go index 380f4883ac..dfef265c1f 100644 --- a/action/protocol/context.go +++ b/action/protocol/context.go @@ -114,6 +114,7 @@ type ( AllowCorrectChainIDOnly bool AddContractStakingVotes bool SharedGasWithDapp bool + FixContractStakingWeightedVotes bool } // FeatureWithHeightCtx provides feature check functions. @@ -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), }, ) } diff --git a/action/protocol/staking/contractstake_indexer.go b/action/protocol/staking/contractstake_indexer.go index 12af9256f6..70cbb65cce 100644 --- a/action/protocol/staking/contractstake_indexer.go +++ b/action/protocol/staking/contractstake_indexer.go @@ -6,6 +6,7 @@ package staking import ( + "context" "math/big" "github.com/iotexproject/iotex-address/address" @@ -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 diff --git a/action/protocol/staking/contractstake_indexer_mock.go b/action/protocol/staking/contractstake_indexer_mock.go index 390fb27268..b99d326915 100644 --- a/action/protocol/staking/contractstake_indexer_mock.go +++ b/action/protocol/staking/contractstake_indexer_mock.go @@ -5,6 +5,7 @@ package staking import ( + context "context" big "math/big" reflect "reflect" @@ -96,18 +97,18 @@ func (mr *MockContractStakingIndexerMockRecorder) BucketsByIndices(arg0, arg1 in } // CandidateVotes mocks base method. -func (m *MockContractStakingIndexer) CandidateVotes(ownerAddr address.Address, height uint64) (*big.Int, error) { +func (m *MockContractStakingIndexer) CandidateVotes(ctx context.Context, ownerAddr address.Address, height uint64) (*big.Int, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CandidateVotes", ownerAddr, height) + ret := m.ctrl.Call(m, "CandidateVotes", ctx, ownerAddr, height) ret0, _ := ret[0].(*big.Int) ret1, _ := ret[1].(error) return ret0, ret1 } // CandidateVotes indicates an expected call of CandidateVotes. -func (mr *MockContractStakingIndexerMockRecorder) CandidateVotes(ownerAddr, height interface{}) *gomock.Call { +func (mr *MockContractStakingIndexerMockRecorder) CandidateVotes(ctx, ownerAddr, height interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CandidateVotes", reflect.TypeOf((*MockContractStakingIndexer)(nil).CandidateVotes), ownerAddr, height) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CandidateVotes", reflect.TypeOf((*MockContractStakingIndexer)(nil).CandidateVotes), ctx, ownerAddr, height) } // TotalBucketCount mocks base method. diff --git a/action/protocol/staking/protocol.go b/action/protocol/staking/protocol.go index 31201f4fb8..13b9180293 100644 --- a/action/protocol/staking/protocol.go +++ b/action/protocol/staking/protocol.go @@ -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") } @@ -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 diff --git a/action/protocol/staking/protocol_test.go b/action/protocol/staking/protocol_test.go index 86ba704151..1e14b73e48 100644 --- a/action/protocol/staking/protocol_test.go +++ b/action/protocol/staking/protocol_test.go @@ -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") } diff --git a/action/protocol/staking/staking_statereader.go b/action/protocol/staking/staking_statereader.go index 89a36b5b8b..339565c6ee 100644 --- a/action/protocol/staking/staking_statereader.go +++ b/action/protocol/staking/staking_statereader.go @@ -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 } } @@ -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 @@ -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 @@ -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) @@ -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 } diff --git a/action/protocol/staking/staking_statereader_test.go b/action/protocol/staking/staking_statereader_test.go index c29a7a8d03..82bc5aa263 100644 --- a/action/protocol/staking/staking_statereader_test.go +++ b/action/protocol/staking/staking_statereader_test.go @@ -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 @@ -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 @@ -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 diff --git a/action/protocol/staking/vote_bucket.go b/action/protocol/staking/vote_bucket.go index 0578f1a85b..33fbedea87 100644 --- a/action/protocol/staking/vote_bucket.go +++ b/action/protocol/staking/vote_bucket.go @@ -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) @@ -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 diff --git a/action/protocol/staking/vote_bucket_test.go b/action/protocol/staking/vote_bucket_test.go index 6b012283e1..40750d83b2 100644 --- a/action/protocol/staking/vote_bucket_test.go +++ b/action/protocol/staking/vote_bucket_test.go @@ -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" @@ -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) + }) + } +} diff --git a/action/protocol/staking/vote_reviser.go b/action/protocol/staking/vote_reviser.go index d61b3e11f7..5f14912a35 100644 --- a/action/protocol/staking/vote_reviser.go +++ b/action/protocol/staking/vote_reviser.go @@ -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()), @@ -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)) } } diff --git a/action/protocol/staking/vote_reviser_test.go b/action/protocol/staking/vote_reviser_test.go index b6691c8bec..f6cfe376ca 100644 --- a/action/protocol/staking/vote_reviser_test.go +++ b/action/protocol/staking/vote_reviser_test.go @@ -198,10 +198,10 @@ func TestVoteReviser(t *testing.T) { if address.Equal(v.cand, c.Owner) && v.index != c.SelfStakeBucketIdx { bucket, err := csr.getBucket(v.index) r.NoError(err) - total := calculateVoteWeight(cv, bucket, false) + total := CalculateVoteWeight(cv, bucket, false) bucket, err = csr.getBucket(c.SelfStakeBucketIdx) r.NoError(err) - total.Add(total, calculateVoteWeight(cv, bucket, true)) + total.Add(total, CalculateVoteWeight(cv, bucket, true)) r.Equal(0, total.Cmp(c.Votes)) break } diff --git a/blockindex/contractstaking/bucket.go b/blockindex/contractstaking/bucket.go index 045b96d749..a5a028139a 100644 --- a/blockindex/contractstaking/bucket.go +++ b/blockindex/contractstaking/bucket.go @@ -6,16 +6,19 @@ package contractstaking import ( + "time" + "github.com/iotexproject/iotex-core/action/protocol/staking" ) // Bucket defines the bucket struct for contract staking type Bucket = staking.VoteBucket -func assembleBucket(token uint64, bi *bucketInfo, bt *BucketType, contractAddr string) *Bucket { +func assembleBucket(token uint64, bi *bucketInfo, bt *BucketType, contractAddr string, blockInterval time.Duration) *Bucket { vb := Bucket{ Index: token, StakedAmount: bt.Amount, + StakedDuration: time.Duration(bt.Duration) * blockInterval, StakedDurationBlockNumber: bt.Duration, CreateBlockHeight: bi.CreatedAt, StakeStartBlockHeight: bi.CreatedAt, diff --git a/blockindex/contractstaking/cache.go b/blockindex/contractstaking/cache.go index d7fe07d975..b25d017f8b 100644 --- a/blockindex/contractstaking/cache.go +++ b/blockindex/contractstaking/cache.go @@ -6,12 +6,14 @@ package contractstaking import ( + "context" "math/big" "sync" "github.com/iotexproject/iotex-address/address" "github.com/pkg/errors" + "github.com/iotexproject/iotex-core/action/protocol" "github.com/iotexproject/iotex-core/db" "github.com/iotexproject/iotex-core/pkg/util/byteutil" ) @@ -24,8 +26,8 @@ type ( propertyBucketTypeMap map[int64]map[uint64]uint64 // map[amount][duration]index totalBucketCount uint64 // total number of buckets including burned buckets height uint64 // current block height, it's put in cache for consistency on merge - contractAddress string // contract address for the bucket mutex sync.RWMutex // a RW mutex for the cache to protect concurrent access + config Config } ) @@ -36,13 +38,13 @@ var ( ErrInvalidHeight = errors.New("invalid height") ) -func newContractStakingCache(contractAddr string) *contractStakingCache { +func newContractStakingCache(config Config) *contractStakingCache { return &contractStakingCache{ bucketInfoMap: make(map[uint64]*bucketInfo), bucketTypeMap: make(map[uint64]*BucketType), propertyBucketTypeMap: make(map[int64]map[uint64]uint64), candidateBucketMap: make(map[string]map[uint64]bool), - contractAddress: contractAddr, + config: config, } } @@ -52,7 +54,7 @@ func (s *contractStakingCache) Height() uint64 { return s.height } -func (s *contractStakingCache) CandidateVotes(candidate address.Address, height uint64) (*big.Int, error) { +func (s *contractStakingCache) CandidateVotes(ctx context.Context, candidate address.Address, height uint64) (*big.Int, error) { s.mutex.RLock() defer s.mutex.RUnlock() @@ -64,6 +66,7 @@ func (s *contractStakingCache) CandidateVotes(candidate address.Address, height if !ok { return votes, nil } + featureCtx := protocol.MustGetFeatureCtx(ctx) for id, existed := range m { if !existed { continue @@ -74,7 +77,11 @@ func (s *contractStakingCache) CandidateVotes(candidate address.Address, height continue } bt := s.mustGetBucketType(bi.TypeIndex) - votes.Add(votes, bt.Amount) + if featureCtx.FixContractStakingWeightedVotes { + votes.Add(votes, s.config.CalculateVoteWeight(assembleBucket(id, bi, bt, s.config.ContractAddress, s.config.BlockInterval))) + } else { + votes.Add(votes, bt.Amount) + } } return votes, nil } @@ -90,7 +97,7 @@ func (s *contractStakingCache) Buckets(height uint64) ([]*Bucket, error) { vbs := []*Bucket{} for id, bi := range s.bucketInfoMap { bt := s.mustGetBucketType(bi.TypeIndex) - vb := assembleBucket(id, bi.clone(), bt, s.contractAddress) + vb := assembleBucket(id, bi.clone(), bt, s.config.ContractAddress, s.config.BlockInterval) vbs = append(vbs, vb) } return vbs, nil @@ -351,7 +358,7 @@ func (s *contractStakingCache) mustGetBucketInfo(id uint64) *bucketInfo { func (s *contractStakingCache) mustGetBucket(id uint64) *Bucket { bi := s.mustGetBucketInfo(id) bt := s.mustGetBucketType(bi.TypeIndex) - return assembleBucket(id, bi, bt, s.contractAddress) + return assembleBucket(id, bi, bt, s.config.ContractAddress, s.config.BlockInterval) } func (s *contractStakingCache) getBucket(id uint64) (*Bucket, bool) { @@ -360,7 +367,7 @@ func (s *contractStakingCache) getBucket(id uint64) (*Bucket, bool) { return nil, false } bt := s.mustGetBucketType(bi.TypeIndex) - return assembleBucket(id, bi, bt, s.contractAddress), true + return assembleBucket(id, bi, bt, s.config.ContractAddress, s.config.BlockInterval), true } func (s *contractStakingCache) putBucketType(id uint64, bt *BucketType) { diff --git a/blockindex/contractstaking/cache_test.go b/blockindex/contractstaking/cache_test.go index 76f7ef0713..9d8eba2639 100644 --- a/blockindex/contractstaking/cache_test.go +++ b/blockindex/contractstaking/cache_test.go @@ -9,7 +9,9 @@ import ( "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/action/protocol" "github.com/iotexproject/iotex-core/action/protocol/staking" + "github.com/iotexproject/iotex-core/blockchain/genesis" "github.com/iotexproject/iotex-core/config" "github.com/iotexproject/iotex-core/db" "github.com/iotexproject/iotex-core/pkg/util/byteutil" @@ -17,75 +19,103 @@ import ( "github.com/iotexproject/iotex-core/testutil" ) -func checkCacheCandidateVotes(r *require.Assertions, cache *contractStakingCache, height uint64, addr address.Address, expectVotes int64) { - votes, err := cache.CandidateVotes(addr, height) +func _checkCacheCandidateVotes(ctx context.Context, r *require.Assertions, cache *contractStakingCache, height uint64, addr address.Address, expectVotes int64) { + votes, err := cache.CandidateVotes(ctx, addr, height) r.NoError(err) r.EqualValues(expectVotes, votes.Int64()) } +func calculateVoteWeightGen(c genesis.VoteWeightCalConsts) calculateVoteWeightFunc { + return func(v *Bucket) *big.Int { + return staking.CalculateVoteWeight(c, v, false) + } +} + func TestContractStakingCache_CandidateVotes(t *testing.T) { + checkCacheCandidateVotesGen := func(ctx context.Context) func(r *require.Assertions, cache *contractStakingCache, height uint64, addr address.Address, expectVotes int64) { + return func(r *require.Assertions, cache *contractStakingCache, height uint64, addr address.Address, expectVotes int64) { + _checkCacheCandidateVotes(ctx, r, cache, height, addr, expectVotes) + } + } require := require.New(t) - cache := newContractStakingCache("") - + cache := newContractStakingCache(Config{ContractAddress: identityset.Address(1).String(), CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) + checkCacheCandidateVotes := checkCacheCandidateVotesGen(protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{BlockHeight: 1}))) + checkCacheCandidateVotesAfterRedsea := checkCacheCandidateVotesGen(protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{BlockHeight: genesis.Default.RedseaBlockHeight}))) // no bucket checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 0) + checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 0) // one bucket cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) cache.PutBucketInfo(1, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 100) + checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 103) // two buckets cache.PutBucketInfo(2, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 200) + checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 206) // add one bucket with different delegate cache.PutBucketInfo(3, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(3), Owner: identityset.Address(2)}) checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 200) checkCacheCandidateVotes(require, cache, 0, identityset.Address(3), 100) + checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 206) + checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(3), 103) // add one bucket with different owner cache.PutBucketInfo(4, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(4)}) checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 300) + checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 309) // add one bucket with different amount cache.PutBucketType(2, &BucketType{Amount: big.NewInt(200), Duration: 100, ActivatedAt: 1}) cache.PutBucketInfo(5, &bucketInfo{TypeIndex: 2, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 500) + checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 516) // add one bucket with different duration cache.PutBucketType(3, &BucketType{Amount: big.NewInt(300), Duration: 200, ActivatedAt: 1}) cache.PutBucketInfo(6, &bucketInfo{TypeIndex: 3, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 800) + checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 827) // add one bucket that is unstaked cache.PutBucketInfo(7, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: 1, UnstakedAt: 1, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 800) + checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 827) // add one bucket that is unlocked and staked cache.PutBucketInfo(8, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: 100, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 900) + checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 927) // change delegate of bucket 1 cache.PutBucketInfo(1, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(3), Owner: identityset.Address(2)}) checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 800) checkCacheCandidateVotes(require, cache, 0, identityset.Address(3), 200) + checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 824) + checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(3), 206) // change owner of bucket 1 cache.PutBucketInfo(1, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(3), Owner: identityset.Address(4)}) checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 800) checkCacheCandidateVotes(require, cache, 0, identityset.Address(3), 200) + checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 824) + checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(3), 206) // change amount of bucket 1 cache.putBucketInfo(1, &bucketInfo{TypeIndex: 2, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(3), Owner: identityset.Address(4)}) checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 800) checkCacheCandidateVotes(require, cache, 0, identityset.Address(3), 300) + checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 824) + checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(3), 310) } func TestContractStakingCache_Buckets(t *testing.T) { require := require.New(t) contractAddr := identityset.Address(27).String() - cache := newContractStakingCache(contractAddr) + cache := newContractStakingCache(Config{ContractAddress: contractAddr, CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) height := uint64(0) // no bucket @@ -159,7 +189,7 @@ func TestContractStakingCache_Buckets(t *testing.T) { func TestContractStakingCache_BucketsByCandidate(t *testing.T) { require := require.New(t) contractAddr := identityset.Address(27).String() - cache := newContractStakingCache(contractAddr) + cache := newContractStakingCache(Config{ContractAddress: contractAddr, CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) height := uint64(0) // no bucket @@ -231,7 +261,7 @@ func TestContractStakingCache_BucketsByCandidate(t *testing.T) { func TestContractStakingCache_BucketsByIndices(t *testing.T) { require := require.New(t) contractAddr := identityset.Address(27).String() - cache := newContractStakingCache(contractAddr) + cache := newContractStakingCache(Config{ContractAddress: contractAddr, CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) height := uint64(0) // no bucket @@ -278,7 +308,7 @@ func TestContractStakingCache_BucketsByIndices(t *testing.T) { func TestContractStakingCache_TotalBucketCount(t *testing.T) { require := require.New(t) - cache := newContractStakingCache("") + cache := newContractStakingCache(Config{ContractAddress: identityset.Address(27).String(), CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) height := uint64(0) // no bucket @@ -307,7 +337,7 @@ func TestContractStakingCache_TotalBucketCount(t *testing.T) { func TestContractStakingCache_ActiveBucketTypes(t *testing.T) { require := require.New(t) - cache := newContractStakingCache("") + cache := newContractStakingCache(Config{ContractAddress: identityset.Address(27).String(), CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) height := uint64(0) // no bucket type @@ -372,8 +402,9 @@ func TestContractStakingCache_ActiveBucketTypes(t *testing.T) { func TestContractStakingCache_Merge(t *testing.T) { require := require.New(t) - cache := newContractStakingCache("") + cache := newContractStakingCache(Config{ContractAddress: identityset.Address(27).String(), CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) height := uint64(1) + ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{BlockHeight: height})) // create delta with one bucket type delta := newContractStakingDelta() @@ -397,7 +428,7 @@ func TestContractStakingCache_Merge(t *testing.T) { err = cache.Merge(delta, height) require.NoError(err) // check that bucket was added to cache and vote count is correct - votes, err := cache.CandidateVotes(identityset.Address(1), height) + votes, err := cache.CandidateVotes(ctx, identityset.Address(1), height) require.NoError(err) require.EqualValues(100, votes.Int64()) @@ -408,10 +439,10 @@ func TestContractStakingCache_Merge(t *testing.T) { err = cache.Merge(delta, height) require.NoError(err) // check that bucket delegate was updated and vote count is correct - votes, err = cache.CandidateVotes(identityset.Address(1), height) + votes, err = cache.CandidateVotes(ctx, identityset.Address(1), height) require.NoError(err) require.EqualValues(0, votes.Int64()) - votes, err = cache.CandidateVotes(identityset.Address(3), height) + votes, err = cache.CandidateVotes(ctx, identityset.Address(3), height) require.NoError(err) require.EqualValues(100, votes.Int64()) @@ -422,14 +453,14 @@ func TestContractStakingCache_Merge(t *testing.T) { err = cache.Merge(delta, height) require.NoError(err) // check that bucket was deleted from cache and vote count is 0 - votes, err = cache.CandidateVotes(identityset.Address(3), height) + votes, err = cache.CandidateVotes(ctx, identityset.Address(3), height) require.NoError(err) require.EqualValues(0, votes.Int64()) } func TestContractStakingCache_MatchBucketType(t *testing.T) { require := require.New(t) - cache := newContractStakingCache("") + cache := newContractStakingCache(Config{ContractAddress: identityset.Address(27).String(), CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) // no bucket types _, bucketType, ok := cache.MatchBucketType(big.NewInt(100), 100) @@ -464,7 +495,7 @@ func TestContractStakingCache_MatchBucketType(t *testing.T) { func TestContractStakingCache_BucketTypeCount(t *testing.T) { require := require.New(t) - cache := newContractStakingCache("") + cache := newContractStakingCache(Config{ContractAddress: identityset.Address(27).String(), CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) height := uint64(0) // no bucket type @@ -493,7 +524,7 @@ func TestContractStakingCache_BucketTypeCount(t *testing.T) { func TestContractStakingCache_LoadFromDB(t *testing.T) { require := require.New(t) - cache := newContractStakingCache("") + cache := newContractStakingCache(Config{ContractAddress: identityset.Address(27).String(), CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) // load from empty db path, err := testutil.PathOfTempFile("staking.db") diff --git a/blockindex/contractstaking/delta_cache.go b/blockindex/contractstaking/delta_cache.go index eb4a7aca68..293a9534e7 100644 --- a/blockindex/contractstaking/delta_cache.go +++ b/blockindex/contractstaking/delta_cache.go @@ -5,7 +5,9 @@ package contractstaking -import "math/big" +import ( + "math/big" +) type ( contractStakingDelta struct { @@ -18,7 +20,7 @@ type ( func newContractStakingDelta() *contractStakingDelta { return &contractStakingDelta{ - cache: newContractStakingCache(""), + cache: newContractStakingCache(Config{}), bucketTypeDeltaState: make(map[uint64]deltaState), bucketInfoDeltaState: make(map[uint64]deltaState), } diff --git a/blockindex/contractstaking/dirty_cache_test.go b/blockindex/contractstaking/dirty_cache_test.go index 0b501bb597..c19ffbf7ed 100644 --- a/blockindex/contractstaking/dirty_cache_test.go +++ b/blockindex/contractstaking/dirty_cache_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/iotexproject/iotex-core/blockchain/genesis" "github.com/iotexproject/iotex-core/db/batch" "github.com/iotexproject/iotex-core/pkg/util/byteutil" "github.com/iotexproject/iotex-core/test/identityset" @@ -13,7 +14,7 @@ import ( func TestContractStakingDirty_getBucketType(t *testing.T) { require := require.New(t) - clean := newContractStakingCache("") + clean := newContractStakingCache(Config{CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts)}) dirty := newContractStakingDirty(clean) // no bucket type @@ -40,7 +41,7 @@ func TestContractStakingDirty_getBucketType(t *testing.T) { func TestContractStakingDirty_getBucketInfo(t *testing.T) { require := require.New(t) - clean := newContractStakingCache("") + clean := newContractStakingCache(Config{CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts)}) dirty := newContractStakingDirty(clean) // no bucket info @@ -92,7 +93,7 @@ func TestContractStakingDirty_getBucketInfo(t *testing.T) { func TestContractStakingDirty_matchBucketType(t *testing.T) { require := require.New(t) - clean := newContractStakingCache("") + clean := newContractStakingCache(Config{CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts)}) dirty := newContractStakingDirty(clean) // no bucket type @@ -122,7 +123,7 @@ func TestContractStakingDirty_matchBucketType(t *testing.T) { func TestContractStakingDirty_getBucketTypeCount(t *testing.T) { require := require.New(t) - clean := newContractStakingCache("") + clean := newContractStakingCache(Config{CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts)}) dirty := newContractStakingDirty(clean) // no bucket type @@ -142,7 +143,7 @@ func TestContractStakingDirty_getBucketTypeCount(t *testing.T) { func TestContractStakingDirty_finalize(t *testing.T) { require := require.New(t) - clean := newContractStakingCache("") + clean := newContractStakingCache(Config{CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts)}) dirty := newContractStakingDirty(clean) // no dirty data @@ -204,7 +205,7 @@ func TestContractStakingDirty_finalize(t *testing.T) { func TestContractStakingDirty_noSideEffectOnClean(t *testing.T) { require := require.New(t) - clean := newContractStakingCache("") + clean := newContractStakingCache(Config{CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts)}) dirty := newContractStakingDirty(clean) // add bucket type to dirty cache diff --git a/blockindex/contractstaking/indexer.go b/blockindex/contractstaking/indexer.go index 8f5004c630..cd9a3dac2e 100644 --- a/blockindex/contractstaking/indexer.go +++ b/blockindex/contractstaking/indexer.go @@ -8,6 +8,7 @@ package contractstaking import ( "context" "math/big" + "time" "github.com/ethereum/go-ethereum/common/math" "github.com/iotexproject/iotex-address/address" @@ -29,26 +30,38 @@ type ( // 1. handle contract staking contract events when new block comes to generate index data // 2. provide query interface for contract staking index data Indexer struct { - kvstore db.KVStore // persistent storage, used to initialize index cache at startup - cache *contractStakingCache // in-memory index for clean data, used to query index data - contractAddress string // stake contract address - contractDeployHeight uint64 // height of the contract deployment + kvstore db.KVStore // persistent storage, used to initialize index cache at startup + cache *contractStakingCache // in-memory index for clean data, used to query index data + config Config // indexer config } + + // Config is the config for contract staking indexer + Config struct { + ContractAddress string // stake contract ContractAddress + ContractDeployHeight uint64 // height of the contract deployment + // TODO: move calculateVoteWeightFunc out of config + CalculateVoteWeight calculateVoteWeightFunc // calculate vote weight function + BlockInterval time.Duration // block produce interval + } + + calculateVoteWeightFunc func(v *Bucket) *big.Int ) // NewContractStakingIndexer creates a new contract staking indexer -func NewContractStakingIndexer(kvStore db.KVStore, contractAddr string, contractDeployHeight uint64) (*Indexer, error) { +func NewContractStakingIndexer(kvStore db.KVStore, config Config) (*Indexer, error) { if kvStore == nil { return nil, errors.New("kv store is nil") } - if _, err := address.FromString(contractAddr); err != nil { - return nil, errors.Wrapf(err, "invalid contract address %s", contractAddr) + if _, err := address.FromString(config.ContractAddress); err != nil { + return nil, errors.Wrapf(err, "invalid contract address %s", config.ContractAddress) + } + if config.CalculateVoteWeight == nil { + return nil, errors.New("calculate vote weight function is nil") } return &Indexer{ - kvstore: kvStore, - cache: newContractStakingCache(contractAddr), - contractAddress: contractAddr, - contractDeployHeight: contractDeployHeight, + kvstore: kvStore, + cache: newContractStakingCache(config), + config: config, }, nil } @@ -65,7 +78,7 @@ func (s *Indexer) Stop(ctx context.Context) error { if err := s.kvstore.Stop(ctx); err != nil { return err } - s.cache = newContractStakingCache(s.contractAddress) + s.cache = newContractStakingCache(s.config) return nil } @@ -76,15 +89,15 @@ func (s *Indexer) Height() (uint64, error) { // StartHeight returns the start height of the indexer func (s *Indexer) StartHeight() uint64 { - return s.contractDeployHeight + return s.config.ContractDeployHeight } // CandidateVotes returns the candidate votes -func (s *Indexer) CandidateVotes(candidate address.Address, height uint64) (*big.Int, error) { +func (s *Indexer) CandidateVotes(ctx context.Context, candidate address.Address, height uint64) (*big.Int, error) { if s.isIgnored(height) { return big.NewInt(0), nil } - return s.cache.CandidateVotes(candidate, height) + return s.cache.CandidateVotes(ctx, candidate, height) } // Buckets returns the buckets @@ -146,8 +159,8 @@ func (s *Indexer) BucketTypes(height uint64) ([]*BucketType, error) { // PutBlock puts a block into indexer func (s *Indexer) PutBlock(ctx context.Context, blk *block.Block) error { expectHeight := s.cache.Height() + 1 - if expectHeight < s.contractDeployHeight { - expectHeight = s.contractDeployHeight + if expectHeight < s.config.ContractDeployHeight { + expectHeight = s.config.ContractDeployHeight } if blk.Height() < expectHeight { return nil @@ -164,7 +177,7 @@ func (s *Indexer) PutBlock(ctx context.Context, blk *block.Block) error { continue } for _, log := range receipt.Logs() { - if log.Address != s.contractAddress { + if log.Address != s.config.ContractAddress { continue } if err := handler.HandleEvent(ctx, blk, log); err != nil { @@ -199,7 +212,7 @@ func (s *Indexer) commit(handler *contractStakingEventHandler, height uint64) er } func (s *Indexer) reloadCache() error { - s.cache = newContractStakingCache(s.contractAddress) + s.cache = newContractStakingCache(s.config) return s.loadFromDB() } @@ -211,5 +224,5 @@ func (s *Indexer) loadFromDB() error { // it aims to be compatible with blocks between feature hard-fork and contract deployed // read interface should return empty result instead of invalid height error if it returns true func (s *Indexer) isIgnored(height uint64) bool { - return height < s.contractDeployHeight + return height < s.config.ContractDeployHeight } diff --git a/blockindex/contractstaking/indexer_test.go b/blockindex/contractstaking/indexer_test.go index c263cbb640..09b6e6c062 100644 --- a/blockindex/contractstaking/indexer_test.go +++ b/blockindex/contractstaking/indexer_test.go @@ -18,9 +18,12 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/exp/slices" + "github.com/iotexproject/iotex-core/action/protocol" "github.com/iotexproject/iotex-core/action/protocol/staking" "github.com/iotexproject/iotex-core/blockchain/block" + "github.com/iotexproject/iotex-core/blockchain/genesis" "github.com/iotexproject/iotex-core/config" + "github.com/iotexproject/iotex-core/consensus/consensusfsm" "github.com/iotexproject/iotex-core/db" "github.com/iotexproject/iotex-core/test/identityset" "github.com/iotexproject/iotex-core/testutil" @@ -30,18 +33,32 @@ const ( _testStakingContractAddress = "io19ys8f4uhwms6lq6ulexr5fwht9gsjes8mvuugd" ) +var ( + _blockInterval = consensusfsm.DefaultDardanellesUpgradeConfig.BlockInterval +) + func TestNewContractStakingIndexer(t *testing.T) { r := require.New(t) t.Run("kvStore is nil", func(t *testing.T) { - _, err := NewContractStakingIndexer(nil, "io19ys8f4uhwms6lq6ulexr5fwht9gsjes8mvuugd", 0) + _, err := NewContractStakingIndexer(nil, Config{ + ContractAddress: "io19ys8f4uhwms6lq6ulexr5fwht9gsjes8mvuugd", + ContractDeployHeight: 0, + CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), + BlockInterval: _blockInterval, + }) r.Error(err) r.Contains(err.Error(), "kv store is nil") }) t.Run("invalid contract address", func(t *testing.T) { kvStore := db.NewMemKVStore() - _, err := NewContractStakingIndexer(kvStore, "invalid address", 0) + _, err := NewContractStakingIndexer(kvStore, Config{ + ContractAddress: "invalid address", + ContractDeployHeight: 0, + CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), + BlockInterval: _blockInterval, + }) r.Error(err) r.Contains(err.Error(), "invalid contract address") }) @@ -49,7 +66,12 @@ func TestNewContractStakingIndexer(t *testing.T) { t.Run("valid input", func(t *testing.T) { contractAddr, err := address.FromString("io19ys8f4uhwms6lq6ulexr5fwht9gsjes8mvuugd") r.NoError(err) - indexer, err := NewContractStakingIndexer(db.NewMemKVStore(), contractAddr.String(), 0) + indexer, err := NewContractStakingIndexer(db.NewMemKVStore(), Config{ + ContractAddress: contractAddr.String(), + ContractDeployHeight: 0, + CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), + BlockInterval: _blockInterval, + }) r.NoError(err) r.NotNil(indexer) }) @@ -63,7 +85,12 @@ func TestContractStakingIndexerLoadCache(t *testing.T) { cfg := db.DefaultConfig cfg.DbPath = testDBPath kvStore := db.NewBoltDB(cfg) - indexer, err := NewContractStakingIndexer(kvStore, _testStakingContractAddress, 0) + indexer, err := NewContractStakingIndexer(kvStore, Config{ + ContractAddress: _testStakingContractAddress, + ContractDeployHeight: 0, + CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), + BlockInterval: _blockInterval, + }) r.NoError(err) r.NoError(indexer.Start(context.Background())) @@ -90,7 +117,12 @@ func TestContractStakingIndexerLoadCache(t *testing.T) { r.NoError(indexer.Stop(context.Background())) // load cache from db - newIndexer, err := NewContractStakingIndexer(db.NewBoltDB(cfg), _testStakingContractAddress, startHeight) + newIndexer, err := NewContractStakingIndexer(db.NewBoltDB(cfg), Config{ + ContractAddress: _testStakingContractAddress, + ContractDeployHeight: startHeight, + CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), + BlockInterval: _blockInterval, + }) r.NoError(err) r.NoError(newIndexer.Start(context.Background())) @@ -119,7 +151,12 @@ func TestContractStakingIndexerDirty(t *testing.T) { cfg := db.DefaultConfig cfg.DbPath = testDBPath kvStore := db.NewBoltDB(cfg) - indexer, err := NewContractStakingIndexer(kvStore, _testStakingContractAddress, 0) + indexer, err := NewContractStakingIndexer(kvStore, Config{ + ContractAddress: _testStakingContractAddress, + ContractDeployHeight: 0, + CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), + BlockInterval: _blockInterval, + }) r.NoError(err) r.NoError(indexer.Start(context.Background())) @@ -147,7 +184,12 @@ func TestContractStakingIndexerThreadSafe(t *testing.T) { cfg := db.DefaultConfig cfg.DbPath = testDBPath kvStore := db.NewBoltDB(cfg) - indexer, err := NewContractStakingIndexer(kvStore, _testStakingContractAddress, 0) + indexer, err := NewContractStakingIndexer(kvStore, Config{ + ContractAddress: _testStakingContractAddress, + ContractDeployHeight: 0, + CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), + BlockInterval: _blockInterval, + }) r.NoError(err) r.NoError(indexer.Start(context.Background())) @@ -155,6 +197,7 @@ func TestContractStakingIndexerThreadSafe(t *testing.T) { wait.Add(6) owner := identityset.Address(0) delegate := identityset.Address(1) + ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{BlockHeight: 1})) // read concurrently for i := 0; i < 5; i++ { go func() { @@ -166,7 +209,7 @@ func TestContractStakingIndexerThreadSafe(t *testing.T) { r.NoError(err) _, err = indexer.BucketsByCandidate(delegate, 0) r.NoError(err) - indexer.CandidateVotes(delegate, 0) + indexer.CandidateVotes(ctx, delegate, 0) _, err = indexer.Height() r.NoError(err) indexer.TotalBucketCount(0) @@ -201,7 +244,12 @@ func TestContractStakingIndexerBucketType(t *testing.T) { cfg := db.DefaultConfig cfg.DbPath = testDBPath kvStore := db.NewBoltDB(cfg) - indexer, err := NewContractStakingIndexer(kvStore, _testStakingContractAddress, 0) + indexer, err := NewContractStakingIndexer(kvStore, Config{ + ContractAddress: _testStakingContractAddress, + ContractDeployHeight: 0, + CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), + BlockInterval: _blockInterval, + }) r.NoError(err) r.NoError(indexer.Start(context.Background())) @@ -284,7 +332,12 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { cfg := db.DefaultConfig cfg.DbPath = testDBPath kvStore := db.NewBoltDB(cfg) - indexer, err := NewContractStakingIndexer(kvStore, _testStakingContractAddress, 0) + indexer, err := NewContractStakingIndexer(kvStore, Config{ + ContractAddress: _testStakingContractAddress, + ContractDeployHeight: 0, + CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), + BlockInterval: _blockInterval, + }) r.NoError(err) r.NoError(indexer.Start(context.Background())) @@ -302,6 +355,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { } err = indexer.commit(handler, height) r.NoError(err) + ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{BlockHeight: 1})) // stake owner := identityset.Address(0) @@ -325,7 +379,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { r.EqualValues(height, bucket.CreateBlockHeight) r.EqualValues(maxBlockNumber, bucket.UnstakeStartBlockHeight) r.EqualValues(_testStakingContractAddress, bucket.ContractAddress) - votes, err := indexer.CandidateVotes(delegate, height) + votes, err := indexer.CandidateVotes(ctx, delegate, height) r.NoError(err) r.EqualValues(10, votes.Uint64()) tbc, err := indexer.TotalBucketCount(height) @@ -361,7 +415,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { r.EqualValues(createHeight, bucket.CreateBlockHeight) r.EqualValues(maxBlockNumber, bucket.UnstakeStartBlockHeight) r.EqualValues(_testStakingContractAddress, bucket.ContractAddress) - votes, err = indexer.CandidateVotes(delegate, height) + votes, err = indexer.CandidateVotes(ctx, delegate, height) r.NoError(err) r.EqualValues(10, votes.Uint64()) tbc, err = indexer.TotalBucketCount(height) @@ -386,7 +440,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { r.EqualValues(createHeight, bucket.CreateBlockHeight) r.EqualValues(maxBlockNumber, bucket.UnstakeStartBlockHeight) r.EqualValues(_testStakingContractAddress, bucket.ContractAddress) - votes, err = indexer.CandidateVotes(delegate, height) + votes, err = indexer.CandidateVotes(ctx, delegate, height) r.NoError(err) r.EqualValues(10, votes.Uint64()) tbc, err = indexer.TotalBucketCount(height) @@ -412,7 +466,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { r.EqualValues(createHeight, bucket.CreateBlockHeight) r.EqualValues(height, bucket.UnstakeStartBlockHeight) r.EqualValues(_testStakingContractAddress, bucket.ContractAddress) - votes, err = indexer.CandidateVotes(delegate, height) + votes, err = indexer.CandidateVotes(ctx, delegate, height) r.NoError(err) r.EqualValues(0, votes.Uint64()) tbc, err = indexer.TotalBucketCount(height) @@ -427,7 +481,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { bucket, ok, err = indexer.Bucket(bucket.Index, height) r.NoError(err) r.False(ok) - votes, err = indexer.CandidateVotes(delegate, height) + votes, err = indexer.CandidateVotes(ctx, delegate, height) r.NoError(err) r.EqualValues(0, votes.Uint64()) tbc, err = indexer.TotalBucketCount(height) @@ -443,7 +497,12 @@ func TestContractStakingIndexerChangeBucketType(t *testing.T) { cfg := db.DefaultConfig cfg.DbPath = testDBPath kvStore := db.NewBoltDB(cfg) - indexer, err := NewContractStakingIndexer(kvStore, _testStakingContractAddress, 0) + indexer, err := NewContractStakingIndexer(kvStore, Config{ + ContractAddress: _testStakingContractAddress, + ContractDeployHeight: 0, + CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), + BlockInterval: _blockInterval, + }) r.NoError(err) r.NoError(indexer.Start(context.Background())) @@ -492,7 +551,12 @@ func TestContractStakingIndexerReadBuckets(t *testing.T) { cfg := db.DefaultConfig cfg.DbPath = testDBPath kvStore := db.NewBoltDB(cfg) - indexer, err := NewContractStakingIndexer(kvStore, _testStakingContractAddress, 0) + indexer, err := NewContractStakingIndexer(kvStore, Config{ + ContractAddress: _testStakingContractAddress, + ContractDeployHeight: 0, + CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), + BlockInterval: _blockInterval, + }) r.NoError(err) r.NoError(indexer.Start(context.Background())) @@ -569,6 +633,7 @@ func TestContractStakingIndexerReadBuckets(t *testing.T) { }) t.Run("CandidateVotes", func(t *testing.T) { + ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{BlockHeight: 1})) candidateMap := make(map[int]int64) for i := range stakeData { candidateMap[stakeData[i].delegate] += int64(stakeData[i].amount) @@ -576,7 +641,7 @@ func TestContractStakingIndexerReadBuckets(t *testing.T) { candidates := []int{1, 2, 3} for _, cand := range candidates { votes := candidateMap[cand] - cvotes, err := indexer.CandidateVotes(identityset.Address(cand), height) + cvotes, err := indexer.CandidateVotes(ctx, identityset.Address(cand), height) r.NoError(err) r.EqualValues(votes, cvotes.Uint64()) } @@ -591,7 +656,12 @@ func TestContractStakingIndexerCacheClean(t *testing.T) { cfg := db.DefaultConfig cfg.DbPath = testDBPath kvStore := db.NewBoltDB(cfg) - indexer, err := NewContractStakingIndexer(kvStore, _testStakingContractAddress, 0) + indexer, err := NewContractStakingIndexer(kvStore, Config{ + ContractAddress: _testStakingContractAddress, + ContractDeployHeight: 0, + CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), + BlockInterval: _blockInterval, + }) r.NoError(err) r.NoError(indexer.Start(context.Background())) @@ -653,9 +723,15 @@ func TestContractStakingIndexerVotes(t *testing.T) { cfg := db.DefaultConfig cfg.DbPath = testDBPath kvStore := db.NewBoltDB(cfg) - indexer, err := NewContractStakingIndexer(kvStore, _testStakingContractAddress, 0) + indexer, err := NewContractStakingIndexer(kvStore, Config{ + ContractAddress: _testStakingContractAddress, + ContractDeployHeight: 0, + CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), + BlockInterval: _blockInterval, + }) r.NoError(err) r.NoError(indexer.Start(context.Background())) + ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{BlockHeight: 1})) // init bucket type height := uint64(1) @@ -673,13 +749,13 @@ func TestContractStakingIndexerVotes(t *testing.T) { stake(r, handler, owner, delegate2, 3, 20, 20, height) stake(r, handler, owner, delegate2, 4, 20, 20, height) r.NoError(indexer.commit(handler, height)) - votes, err := indexer.CandidateVotes(delegate1, height) + votes, err := indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(30, votes.Uint64()) - votes, err = indexer.CandidateVotes(delegate2, height) + votes, err = indexer.CandidateVotes(ctx, delegate2, height) r.NoError(err) r.EqualValues(40, votes.Uint64()) - votes, err = indexer.CandidateVotes(owner, height) + votes, err = indexer.CandidateVotes(ctx, owner, height) r.EqualValues(0, votes.Uint64()) // change delegate bucket 3 to delegate1 @@ -687,10 +763,10 @@ func TestContractStakingIndexerVotes(t *testing.T) { handler = newContractStakingEventHandler(indexer.cache) changeDelegate(r, handler, delegate1, 3) r.NoError(indexer.commit(handler, height)) - votes, err = indexer.CandidateVotes(delegate1, height) + votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) - votes, err = indexer.CandidateVotes(delegate2, height) + votes, err = indexer.CandidateVotes(ctx, delegate2, height) r.NoError(err) r.EqualValues(20, votes.Uint64()) @@ -700,10 +776,10 @@ func TestContractStakingIndexerVotes(t *testing.T) { unlock(r, handler, 1, height) unlock(r, handler, 4, height) r.NoError(indexer.commit(handler, height)) - votes, err = indexer.CandidateVotes(delegate1, height) + votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) - votes, err = indexer.CandidateVotes(delegate2, height) + votes, err = indexer.CandidateVotes(ctx, delegate2, height) r.NoError(err) r.EqualValues(20, votes.Uint64()) @@ -713,10 +789,10 @@ func TestContractStakingIndexerVotes(t *testing.T) { unstake(r, handler, 1, height) lock(r, handler, 4, 20) r.NoError(indexer.commit(handler, height)) - votes, err = indexer.CandidateVotes(delegate1, height) + votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(40, votes.Uint64()) - votes, err = indexer.CandidateVotes(delegate2, height) + votes, err = indexer.CandidateVotes(ctx, delegate2, height) r.NoError(err) r.EqualValues(20, votes.Uint64()) @@ -725,10 +801,10 @@ func TestContractStakingIndexerVotes(t *testing.T) { handler = newContractStakingEventHandler(indexer.cache) expandBucketType(r, handler, 2, 30, 20) r.NoError(indexer.commit(handler, height)) - votes, err = indexer.CandidateVotes(delegate1, height) + votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) - votes, err = indexer.CandidateVotes(delegate2, height) + votes, err = indexer.CandidateVotes(ctx, delegate2, height) r.NoError(err) r.EqualValues(20, votes.Uint64()) @@ -737,10 +813,10 @@ func TestContractStakingIndexerVotes(t *testing.T) { handler = newContractStakingEventHandler(indexer.cache) transfer(r, handler, delegate2, 4) r.NoError(indexer.commit(handler, height)) - votes, err = indexer.CandidateVotes(delegate1, height) + votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) - votes, err = indexer.CandidateVotes(delegate2, height) + votes, err = indexer.CandidateVotes(ctx, delegate2, height) r.NoError(err) r.EqualValues(20, votes.Uint64()) @@ -751,10 +827,10 @@ func TestContractStakingIndexerVotes(t *testing.T) { stake(r, handler, owner, delegate2, 6, 20, 20, height) stake(r, handler, owner, delegate2, 7, 20, 20, height) r.NoError(indexer.commit(handler, height)) - votes, err = indexer.CandidateVotes(delegate1, height) + votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) - votes, err = indexer.CandidateVotes(delegate2, height) + votes, err = indexer.CandidateVotes(ctx, delegate2, height) r.NoError(err) r.EqualValues(80, votes.Uint64()) @@ -763,10 +839,10 @@ func TestContractStakingIndexerVotes(t *testing.T) { handler = newContractStakingEventHandler(indexer.cache) mergeBuckets(r, handler, []int64{5, 6, 7}, 60, 20) r.NoError(indexer.commit(handler, height)) - votes, err = indexer.CandidateVotes(delegate1, height) + votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) - votes, err = indexer.CandidateVotes(delegate2, height) + votes, err = indexer.CandidateVotes(ctx, delegate2, height) r.NoError(err) r.EqualValues(80, votes.Uint64()) @@ -776,10 +852,10 @@ func TestContractStakingIndexerVotes(t *testing.T) { unlock(r, handler, 5, height) unstake(r, handler, 5, height) r.NoError(indexer.commit(handler, height)) - votes, err = indexer.CandidateVotes(delegate1, height) + votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) - votes, err = indexer.CandidateVotes(delegate2, height) + votes, err = indexer.CandidateVotes(ctx, delegate2, height) r.NoError(err) r.EqualValues(20, votes.Uint64()) @@ -791,10 +867,10 @@ func TestContractStakingIndexerVotes(t *testing.T) { stake(r, handler, owner, delegate2, 10, 20, 20, height) mergeBuckets(r, handler, []int64{8, 9, 10}, 60, 20) r.NoError(indexer.commit(handler, height)) - votes, err = indexer.CandidateVotes(delegate1, height) + votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(110, votes.Uint64()) - votes, err = indexer.CandidateVotes(delegate2, height) + votes, err = indexer.CandidateVotes(ctx, delegate2, height) r.NoError(err) r.EqualValues(20, votes.Uint64()) @@ -834,6 +910,12 @@ func TestContractStakingIndexerVotes(t *testing.T) { r.EqualValues(4, bts[3].Index) r.EqualValues(5, bts[4].Index) r.EqualValues(8, bts[5].Index) + r.EqualValues(10*_blockInterval, bts[0].StakedDuration) + r.EqualValues(20*_blockInterval, bts[1].StakedDuration) + r.EqualValues(20*_blockInterval, bts[2].StakedDuration) + r.EqualValues(20*_blockInterval, bts[3].StakedDuration) + r.EqualValues(20*_blockInterval, bts[4].StakedDuration) + r.EqualValues(20*_blockInterval, bts[5].StakedDuration) r.EqualValues(10, bts[0].StakedDurationBlockNumber) r.EqualValues(20, bts[1].StakedDurationBlockNumber) r.EqualValues(20, bts[2].StakedDurationBlockNumber) @@ -883,7 +965,6 @@ func TestContractStakingIndexerVotes(t *testing.T) { r.EqualValues(9, bts[4].UnstakeStartBlockHeight) r.EqualValues(maxBlockNumber, bts[5].UnstakeStartBlockHeight) for _, b := range bts { - r.EqualValues(0, b.StakedDuration) r.EqualValues(time.Time{}, b.CreateTime) r.EqualValues(time.Time{}, b.StakeStartTime) r.EqualValues(time.Time{}, b.UnstakeStartTime) @@ -956,7 +1037,12 @@ func TestIndexer_ReadHeightRestriction(t *testing.T) { dbPath, err := testutil.PathOfTempFile("db") r.NoError(err) cfg.DbPath = dbPath - indexer, err := NewContractStakingIndexer(db.NewBoltDB(cfg), identityset.Address(1).String(), startHeight) + indexer, err := NewContractStakingIndexer(db.NewBoltDB(cfg), Config{ + ContractAddress: identityset.Address(1).String(), + ContractDeployHeight: startHeight, + CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), + BlockInterval: _blockInterval, + }) r.NoError(err) r.NoError(indexer.Start(context.Background())) defer func() { @@ -965,6 +1051,7 @@ func TestIndexer_ReadHeightRestriction(t *testing.T) { }() indexer.cache.putHeight(height) // check read api + ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{BlockHeight: 1})) h := c.readHeight delegate := identityset.Address(1) if c.valid { @@ -976,7 +1063,7 @@ func TestIndexer_ReadHeightRestriction(t *testing.T) { r.NoError(err) _, err = indexer.BucketsByIndices([]uint64{1, 2, 3, 4, 5, 8}, h) r.NoError(err) - _, err = indexer.CandidateVotes(delegate, h) + _, err = indexer.CandidateVotes(ctx, delegate, h) r.NoError(err) _, _, err = indexer.Bucket(1, h) r.NoError(err) @@ -991,7 +1078,7 @@ func TestIndexer_ReadHeightRestriction(t *testing.T) { r.ErrorIs(err, ErrInvalidHeight) _, err = indexer.BucketsByIndices([]uint64{1, 2, 3, 4, 5, 8}, h) r.ErrorIs(err, ErrInvalidHeight) - _, err = indexer.CandidateVotes(delegate, h) + _, err = indexer.CandidateVotes(ctx, delegate, h) r.ErrorIs(err, ErrInvalidHeight) _, _, err = indexer.Bucket(1, h) r.ErrorIs(err, ErrInvalidHeight) @@ -1035,7 +1122,12 @@ func TestIndexer_PutBlock(t *testing.T) { dbPath, err := testutil.PathOfTempFile("db") r.NoError(err) cfg.DbPath = dbPath - indexer, err := NewContractStakingIndexer(db.NewBoltDB(cfg), identityset.Address(1).String(), startHeight) + indexer, err := NewContractStakingIndexer(db.NewBoltDB(cfg), Config{ + ContractAddress: identityset.Address(1).String(), + ContractDeployHeight: startHeight, + CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), + BlockInterval: _blockInterval, + }) r.NoError(err) r.NoError(indexer.Start(context.Background())) defer func() { @@ -1064,7 +1156,7 @@ func TestIndexer_PutBlock(t *testing.T) { func BenchmarkIndexer_PutBlockBeforeContractHeight(b *testing.B) { // Create a new Indexer with a contract height of 100 - indexer := &Indexer{contractDeployHeight: 100} + indexer := &Indexer{config: Config{ContractDeployHeight: 100}} // Create a mock block with a height of 50 blk := &block.Block{} diff --git a/chainservice/builder.go b/chainservice/builder.go index 0a4deaaea7..9ccca8adc8 100644 --- a/chainservice/builder.go +++ b/chainservice/builder.go @@ -318,7 +318,17 @@ func (builder *Builder) buildContractStakingIndexer(forTest bool) error { } dbConfig := builder.cfg.DB dbConfig.DbPath = builder.cfg.Chain.ContractStakingIndexDBPath - indexer, err := contractstaking.NewContractStakingIndexer(db.NewBoltDB(dbConfig), builder.cfg.Genesis.SystemStakingContractAddress, builder.cfg.Genesis.SystemStakingContractHeight) + voteCalcConsts := builder.cfg.Genesis.VoteWeightCalConsts + indexer, err := contractstaking.NewContractStakingIndexer( + db.NewBoltDB(dbConfig), + contractstaking.Config{ + ContractAddress: builder.cfg.Genesis.SystemStakingContractAddress, + ContractDeployHeight: builder.cfg.Genesis.SystemStakingContractHeight, + CalculateVoteWeight: func(v *staking.VoteBucket) *big.Int { + return staking.CalculateVoteWeight(voteCalcConsts, v, false) + }, + BlockInterval: builder.cfg.DardanellesUpgrade.BlockInterval, + }) if err != nil { return err } diff --git a/e2etest/contract_staking_test.go b/e2etest/contract_staking_test.go index 9d4b20ec70..3dbade68ea 100644 --- a/e2etest/contract_staking_test.go +++ b/e2etest/contract_staking_test.go @@ -23,6 +23,7 @@ import ( "github.com/iotexproject/iotex-core/action/protocol/execution" "github.com/iotexproject/iotex-core/action/protocol/rewarding" "github.com/iotexproject/iotex-core/action/protocol/rolldpos" + "github.com/iotexproject/iotex-core/action/protocol/staking" "github.com/iotexproject/iotex-core/actpool" "github.com/iotexproject/iotex-core/blockchain" "github.com/iotexproject/iotex-core/blockchain/block" @@ -31,6 +32,7 @@ import ( "github.com/iotexproject/iotex-core/blockindex" "github.com/iotexproject/iotex-core/blockindex/contractstaking" "github.com/iotexproject/iotex-core/config" + "github.com/iotexproject/iotex-core/consensus/consensusfsm" "github.com/iotexproject/iotex-core/db" "github.com/iotexproject/iotex-core/state/factory" "github.com/iotexproject/iotex-core/test/identityset" @@ -1247,6 +1249,7 @@ const ( ]` _stakingContractAddress = "io19ys8f4uhwms6lq6ulexr5fwht9gsjes8mvuugd" _adminID = 22 + _testRedseaBlockHeight = 100 ) var ( @@ -1258,6 +1261,7 @@ var ( common.BytesToAddress(identityset.Address(4).Bytes()), common.BytesToAddress(identityset.Address(5).Bytes()), common.BytesToAddress(identityset.Address(6).Bytes()), + common.BytesToAddress(identityset.Address(7).Bytes()), } ) @@ -1301,6 +1305,7 @@ func TestContractStaking(t *testing.T) { {10, 10}, {100, 100}, {100, 10}, + {10000, 30 * 17280}, } params := []*callParam{} for i := range bucketTypes { @@ -1358,7 +1363,8 @@ func TestContractStaking(t *testing.T) { r.EqualValues(blk.Height(), bt.CreateBlockHeight) r.EqualValues(blk.Height(), bt.StakeStartBlockHeight) r.True(bt.UnstakeStartBlockHeight == math.MaxUint64) - votes, err := indexer.CandidateVotes(identityset.Address(delegateIdx), blk.Height()) + ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{BlockHeight: 1})) + votes, err := indexer.CandidateVotes(ctx, identityset.Address(delegateIdx), blk.Height()) r.NoError(err) r.EqualValues(10, votes.Int64()) tbc, err := indexer.TotalBucketCount(blk.Height()) @@ -1389,7 +1395,8 @@ func TestContractStaking(t *testing.T) { r.NoError(err) r.True(ok) r.EqualValues(blk.Height(), bt.StakeStartBlockHeight) - votes, err := indexer.CandidateVotes(identityset.Address(delegateIdx), blk.Height()) + ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{BlockHeight: 1})) + votes, err := indexer.CandidateVotes(ctx, identityset.Address(delegateIdx), blk.Height()) r.NoError(err) r.EqualValues(10, votes.Int64()) tbc, err := indexer.TotalBucketCount(blk.Height()) @@ -1415,7 +1422,8 @@ func TestContractStaking(t *testing.T) { r.NoError(err) r.True(ok) r.EqualValues(blk.Height(), bt.UnstakeStartBlockHeight) - votes, err := indexer.CandidateVotes(identityset.Address(delegateIdx), blk.Height()) + ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{BlockHeight: 1})) + votes, err := indexer.CandidateVotes(ctx, identityset.Address(delegateIdx), blk.Height()) r.NoError(err) r.EqualValues(0, votes.Int64()) tbc, err := indexer.TotalBucketCount(blk.Height()) @@ -1868,6 +1876,18 @@ func TestContractStaking(t *testing.T) { }) }) + t.Run("afterRedsea", func(t *testing.T) { + jumpBlocks(bc, _testRedseaBlockHeight, r) + t.Run("weightedVotes", func(t *testing.T) { + simpleStake(_delegates[7], big.NewInt(10000), big.NewInt(30*17280)) + height, err := indexer.Height() + r.NoError(err) + ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(ctx, protocol.BlockCtx{BlockHeight: height})) + votes, err := indexer.CandidateVotes(ctx, identityset.Address(7), height) + r.NoError(err) + r.EqualValues(12245, votes.Int64()) + }) + }) } func prepareContractStakingBlockchain(ctx context.Context, cfg config.Config, r *require.Assertions) (blockchain.Blockchain, factory.Factory, blockdao.BlockDAO, actpool.ActPool, *contractstaking.Indexer) { @@ -1900,6 +1920,7 @@ func prepareContractStakingBlockchain(ctx context.Context, cfg config.Config, r cfg.Genesis.FairbankBlockHeight = 0 cfg.Genesis.GreenlandBlockHeight = 0 cfg.Genesis.IcelandBlockHeight = 0 + cfg.Genesis.RedseaBlockHeight = _testRedseaBlockHeight // London is enabled at okhotsk height cfg.Genesis.Blockchain.JutlandBlockHeight = 0 @@ -1938,7 +1959,14 @@ func prepareContractStakingBlockchain(ctx context.Context, cfg config.Config, r r.NoError(err) cc := cfg.DB cc.DbPath = testContractStakeIndexerPath - contractStakeIndexer, err := contractstaking.NewContractStakingIndexer(db.NewBoltDB(cc), _stakingContractAddress, 0) + contractStakeIndexer, err := contractstaking.NewContractStakingIndexer(db.NewBoltDB(cc), contractstaking.Config{ + ContractAddress: _stakingContractAddress, + ContractDeployHeight: 0, + CalculateVoteWeight: func(v *staking.VoteBucket) *big.Int { + return staking.CalculateVoteWeight(genesis.Default.VoteWeightCalConsts, v, false) + }, + BlockInterval: consensusfsm.DefaultDardanellesUpgradeConfig.BlockInterval, + }) r.NoError(err) // create BlockDAO dao := blockdao.NewBlockDAOInMemForTest([]blockdao.BlockIndexer{sf, indexer, contractStakeIndexer})