From 6f3b8fdcdbf3a93cc021ce8cf7c39e6016ac2b38 Mon Sep 17 00:00:00 2001 From: envestcc Date: Tue, 22 Aug 2023 17:14:17 +0800 Subject: [PATCH 1/6] add height limit for contract staking indexer --- .../protocol/staking/contractstake_indexer.go | 14 ++-- action/protocol/staking/protocol.go | 6 +- .../protocol/staking/staking_statereader.go | 37 ++++++---- blockindex/contractstaking/cache.go | 72 ++++++++++++++----- blockindex/contractstaking/dirty_cache.go | 4 +- blockindex/contractstaking/indexer.go | 31 ++++---- 6 files changed, 111 insertions(+), 53 deletions(-) diff --git a/action/protocol/staking/contractstake_indexer.go b/action/protocol/staking/contractstake_indexer.go index 6278e5dd0a..88f771ed78 100644 --- a/action/protocol/staking/contractstake_indexer.go +++ b/action/protocol/staking/contractstake_indexer.go @@ -17,16 +17,18 @@ type ( ContractStakingIndexer interface { // CandidateVotes returns the total staked votes of a candidate // candidate identified by owner address - CandidateVotes(ownerAddr address.Address) *big.Int + CandidateVotes(ownerAddr address.Address, height uint64) (*big.Int, error) // Buckets returns active buckets - Buckets() ([]*VoteBucket, error) + Buckets(height uint64) ([]*VoteBucket, error) // BucketsByIndices returns active buckets by indices - BucketsByIndices([]uint64) ([]*VoteBucket, error) + BucketsByIndices([]uint64, uint64) ([]*VoteBucket, error) // BucketsByCandidate returns active buckets by candidate - BucketsByCandidate(ownerAddr address.Address) ([]*VoteBucket, error) + BucketsByCandidate(ownerAddr address.Address, height uint64) ([]*VoteBucket, error) // TotalBucketCount returns the total number of buckets including burned buckets - TotalBucketCount() uint64 + TotalBucketCount(height uint64) (uint64, error) // BucketTypes returns the active bucket types - BucketTypes() ([]*ContractStakingBucketType, error) + BucketTypes(height uint64) ([]*ContractStakingBucketType, error) + // Height returns the indexer height + Height() (uint64, error) } ) diff --git a/action/protocol/staking/protocol.go b/action/protocol/staking/protocol.go index c743db7142..315f1b334c 100644 --- a/action/protocol/staking/protocol.go +++ b/action/protocol/staking/protocol.go @@ -476,7 +476,11 @@ func (p *Protocol) ActiveCandidates(ctx context.Context, sr protocol.StateReader featureCtx := protocol.MustGetFeatureCtx(ctx) for i := range list { if p.contractStakingIndexer != nil && featureCtx.AddContractStakingVotes { - list[i].Votes.Add(list[i].Votes, p.contractStakingIndexer.CandidateVotes(list[i].Owner)) + contractVotes, err := p.contractStakingIndexer.CandidateVotes(list[i].Owner, height) + if err != nil { + return nil, errors.Wrap(err, "failed to get CandidateVotes from contractStakingIndexer") + } + list[i].Votes.Add(list[i].Votes, contractVotes) } if list[i].SelfStake.Cmp(p.config.RegistrationConsts.MinSelfStake) >= 0 { cand = append(cand, list[i]) diff --git a/action/protocol/staking/staking_statereader.go b/action/protocol/staking/staking_statereader.go index 683aeafc07..89a36b5b8b 100644 --- a/action/protocol/staking/staking_statereader.go +++ b/action/protocol/staking/staking_statereader.go @@ -73,7 +73,7 @@ func (c *compositeStakingStateReader) readStateBuckets(ctx context.Context, req } // read LSD buckets - lsdBuckets, err := c.contractIndexer.Buckets() + lsdBuckets, err := c.contractIndexer.Buckets(inputHeight) if err != nil { return nil, 0, err } @@ -99,7 +99,7 @@ func (c *compositeStakingStateReader) readStateBucketsByVoter(ctx context.Contex } // read LSD buckets - lsdBuckets, err := c.contractIndexer.Buckets() + lsdBuckets, err := c.contractIndexer.Buckets(height) if err != nil { return nil, 0, err } @@ -131,7 +131,7 @@ func (c *compositeStakingStateReader) readStateBucketsByCandidate(ctx context.Co if candidate == nil { return &iotextypes.VoteBucketList{}, height, nil } - lsdBuckets, err := c.contractIndexer.BucketsByCandidate(candidate.Owner) + lsdBuckets, err := c.contractIndexer.BucketsByCandidate(candidate.Owner, height) if err != nil { return nil, 0, err } @@ -156,7 +156,7 @@ func (c *compositeStakingStateReader) readStateBucketByIndices(ctx context.Conte } // read LSD buckets - lsdBuckets, err := c.contractIndexer.BucketsByIndices(req.GetIndex()) + lsdBuckets, err := c.contractIndexer.BucketsByIndices(req.GetIndex(), height) if err != nil { return nil, 0, err } @@ -177,12 +177,16 @@ func (c *compositeStakingStateReader) readStateBucketCount(ctx context.Context, if !c.isContractStakingEnabled() { return bucketCnt, height, nil } - buckets, err := c.contractIndexer.Buckets() + buckets, err := c.contractIndexer.Buckets(height) if err != nil { return nil, 0, err } bucketCnt.Active += uint64(len(buckets)) - bucketCnt.Total += c.contractIndexer.TotalBucketCount() + tbc, err := c.contractIndexer.TotalBucketCount(height) + if err != nil { + return nil, 0, err + } + bucketCnt.Total += tbc return bucketCnt, height, nil } @@ -220,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); err != nil { + if err = addContractStakingVotes(candidate, c.contractIndexer, height); err != nil { return nil, 0, err } } @@ -238,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); err != nil { + if err := addContractStakingVotes(candidate, c.contractIndexer, height); err != nil { return nil, 0, err } return candidate, height, nil @@ -255,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); err != nil { + if err := addContractStakingVotes(candidate, c.contractIndexer, height); err != nil { return nil, 0, err } return candidate, height, nil @@ -275,7 +279,7 @@ func (c *compositeStakingStateReader) readStateTotalStakingAmount(ctx context.Co return accountMeta, height, nil } // add contract staking amount - buckets, err := c.contractIndexer.Buckets() + buckets, err := c.contractIndexer.Buckets(height) if err != nil { return nil, 0, err } @@ -291,7 +295,8 @@ func (c *compositeStakingStateReader) readStateContractStakingBucketTypes(ctx co if !c.isContractStakingEnabled() { return &iotextypes.ContractStakingBucketTypeList{}, c.nativeSR.Height(), nil } - bts, err := c.contractIndexer.BucketTypes() + height := c.nativeSR.Height() + bts, err := c.contractIndexer.BucketTypes(height) if err != nil { return nil, 0, err } @@ -302,14 +307,14 @@ func (c *compositeStakingStateReader) readStateContractStakingBucketTypes(ctx co StakedDuration: uint32(bt.Duration), }) } - return &iotextypes.ContractStakingBucketTypeList{BucketTypes: pbBts}, c.nativeSR.Height(), nil + return &iotextypes.ContractStakingBucketTypeList{BucketTypes: pbBts}, height, nil } func (c *compositeStakingStateReader) isContractStakingEnabled() bool { return c.contractIndexer != nil } -func addContractStakingVotes(candidate *iotextypes.CandidateV2, contractStakingSR ContractStakingIndexer) error { +func addContractStakingVotes(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) @@ -318,7 +323,11 @@ func addContractStakingVotes(candidate *iotextypes.CandidateV2, contractStakingS if err != nil { return err } - votes.Add(votes, contractStakingSR.CandidateVotes(addr)) + contractVotes, err := contractStakingSR.CandidateVotes(addr, height) + if err != nil { + return err + } + votes.Add(votes, contractVotes) candidate.TotalWeightedVotes = votes.String() return nil } diff --git a/blockindex/contractstaking/cache.go b/blockindex/contractstaking/cache.go index 1db1f4d15a..124862cccb 100644 --- a/blockindex/contractstaking/cache.go +++ b/blockindex/contractstaking/cache.go @@ -32,6 +32,8 @@ type ( var ( // ErrBucketNotExist is the error when bucket does not exist ErrBucketNotExist = errors.New("bucket does not exist") + // ErrInvalidHeight is the error when height is invalid + ErrInvalidHeight = errors.New("invalid height") ) func newContractStakingCache(contractAddr string) *contractStakingCache { @@ -50,14 +52,17 @@ func (s *contractStakingCache) Height() uint64 { return s.height } -func (s *contractStakingCache) CandidateVotes(candidate address.Address) *big.Int { +func (s *contractStakingCache) CandidateVotes(candidate address.Address, height uint64) (*big.Int, error) { s.mutex.RLock() defer s.mutex.RUnlock() + if err := s.validateHeight(height); err != nil { + return nil, err + } votes := big.NewInt(0) m, ok := s.candidateBucketMap[candidate.String()] if !ok { - return votes + return votes, nil } for id, existed := range m { if !existed { @@ -71,27 +76,35 @@ func (s *contractStakingCache) CandidateVotes(candidate address.Address) *big.In bt := s.mustGetBucketType(bi.TypeIndex) votes.Add(votes, bt.Amount) } - return votes + return votes, nil } -func (s *contractStakingCache) Buckets() []*Bucket { +func (s *contractStakingCache) Buckets(height uint64) ([]*Bucket, error) { s.mutex.RLock() defer s.mutex.RUnlock() + if err := s.validateHeight(height); err != nil { + return nil, err + } + vbs := []*Bucket{} for id, bi := range s.bucketInfoMap { bt := s.mustGetBucketType(bi.TypeIndex) vb := assembleBucket(id, bi.clone(), bt, s.contractAddress) vbs = append(vbs, vb) } - return vbs + return vbs, nil } -func (s *contractStakingCache) Bucket(id uint64) (*Bucket, bool) { +func (s *contractStakingCache) Bucket(id, height uint64) (*Bucket, bool, error) { s.mutex.RLock() defer s.mutex.RUnlock() - return s.getBucket(id) + if err := s.validateHeight(height); err != nil { + return nil, false, err + } + bt, ok := s.getBucket(id) + return bt, ok, nil } func (s *contractStakingCache) BucketInfo(id uint64) (*bucketInfo, bool) { @@ -122,23 +135,29 @@ func (s *contractStakingCache) BucketType(id uint64) (*BucketType, bool) { return s.getBucketType(id) } -func (s *contractStakingCache) BucketsByCandidate(candidate address.Address) []*Bucket { +func (s *contractStakingCache) BucketsByCandidate(candidate address.Address, height uint64) ([]*Bucket, error) { s.mutex.RLock() defer s.mutex.RUnlock() + if err := s.validateHeight(height); err != nil { + return nil, err + } bucketMap := s.candidateBucketMap[candidate.String()] vbs := make([]*Bucket, 0, len(bucketMap)) for id := range bucketMap { vb := s.mustGetBucket(id) vbs = append(vbs, vb) } - return vbs + return vbs, nil } -func (s *contractStakingCache) BucketsByIndices(indices []uint64) ([]*Bucket, error) { +func (s *contractStakingCache) BucketsByIndices(indices []uint64, height uint64) ([]*Bucket, error) { s.mutex.RLock() defer s.mutex.RUnlock() + if err := s.validateHeight(height); err != nil { + return nil, err + } vbs := make([]*Bucket, 0, len(indices)) for _, id := range indices { vb, ok := s.getBucket(id) @@ -149,24 +168,30 @@ func (s *contractStakingCache) BucketsByIndices(indices []uint64) ([]*Bucket, er return vbs, nil } -func (s *contractStakingCache) TotalBucketCount() uint64 { +func (s *contractStakingCache) TotalBucketCount(height uint64) (uint64, error) { s.mutex.RLock() defer s.mutex.RUnlock() - return s.totalBucketCount + if err := s.validateHeight(height); err != nil { + return 0, err + } + return s.totalBucketCount, nil } -func (s *contractStakingCache) ActiveBucketTypes() map[uint64]*BucketType { +func (s *contractStakingCache) ActiveBucketTypes(height uint64) (map[uint64]*BucketType, error) { s.mutex.RLock() defer s.mutex.RUnlock() + if err := s.validateHeight(height); err != nil { + return nil, err + } m := make(map[uint64]*BucketType) for k, v := range s.bucketTypeMap { if v.ActivatedAt != maxBlockNumber { m[k] = v.Clone() } } - return m + return m, nil } func (s *contractStakingCache) PutBucketType(id uint64, bt *BucketType) { @@ -213,11 +238,14 @@ func (s *contractStakingCache) MatchBucketType(amount *big.Int, duration uint64) return id, s.mustGetBucketType(id), true } -func (s *contractStakingCache) BucketTypeCount() uint64 { +func (s *contractStakingCache) BucketTypeCount(height uint64) (uint64, error) { s.mutex.RLock() defer s.mutex.RUnlock() - return uint64(len(s.bucketTypeMap)) + if err := s.validateHeight(height); err != nil { + return 0, err + } + return uint64(len(s.bucketTypeMap)), nil } func (s *contractStakingCache) LoadFromDB(kvstore db.KVStore) error { @@ -421,3 +449,15 @@ func (s *contractStakingCache) mergeDelta(delta *contractStakingDelta) error { } return nil } + +func (s *contractStakingCache) validateHeight(height uint64) error { + // means latest height + if height == 0 { + return nil + } + // indexer only store latest height + if height != s.height { + return errors.Wrapf(ErrInvalidHeight, "expected %d, actual %d", s.height, height) + } + return nil +} diff --git a/blockindex/contractstaking/dirty_cache.go b/blockindex/contractstaking/dirty_cache.go index 222b097d67..d5f0b9db03 100644 --- a/blockindex/contractstaking/dirty_cache.go +++ b/blockindex/contractstaking/dirty_cache.go @@ -105,7 +105,7 @@ func (dirty *contractStakingDirty) finalize() (batch.KVStoreBatch, *contractStak func (dirty *contractStakingDirty) finalizeBatch() batch.KVStoreBatch { dirty.once.Do(func() { - total := dirty.clean.TotalBucketCount() + dirty.delta.AddedBucketCnt() + total := dirty.clean.totalBucketCount + dirty.delta.AddedBucketCnt() dirty.batch.Put(_StakingNS, _stakingTotalBucketCountKey, byteutil.Uint64ToBytesBigEndian(total), "failed to put total bucket count") }) return dirty.batch @@ -125,7 +125,7 @@ func (dirty *contractStakingDirty) matchBucketType(amount *big.Int, duration uin } func (dirty *contractStakingDirty) getBucketTypeCount() uint64 { - return dirty.clean.BucketTypeCount() + dirty.delta.AddedBucketTypeCnt() + return uint64(len(dirty.clean.bucketTypeMap)) + dirty.delta.AddedBucketTypeCnt() } func (dirty *contractStakingDirty) updateBucketType(id uint64, bt *BucketType) error { diff --git a/blockindex/contractstaking/indexer.go b/blockindex/contractstaking/indexer.go index 9acd097637..67fcbe14a7 100644 --- a/blockindex/contractstaking/indexer.go +++ b/blockindex/contractstaking/indexer.go @@ -80,38 +80,41 @@ func (s *Indexer) StartHeight() uint64 { } // CandidateVotes returns the candidate votes -func (s *Indexer) CandidateVotes(candidate address.Address) *big.Int { - return s.cache.CandidateVotes(candidate) +func (s *Indexer) CandidateVotes(candidate address.Address, height uint64) (*big.Int, error) { + return s.cache.CandidateVotes(candidate, height) } // Buckets returns the buckets -func (s *Indexer) Buckets() ([]*Bucket, error) { - return s.cache.Buckets(), nil +func (s *Indexer) Buckets(height uint64) ([]*Bucket, error) { + return s.cache.Buckets(height) } // Bucket returns the bucket -func (s *Indexer) Bucket(id uint64) (*Bucket, bool) { - return s.cache.Bucket(id) +func (s *Indexer) Bucket(id uint64, height uint64) (*Bucket, bool, error) { + return s.cache.Bucket(id, height) } // BucketsByIndices returns the buckets by indices -func (s *Indexer) BucketsByIndices(indices []uint64) ([]*Bucket, error) { - return s.cache.BucketsByIndices(indices) +func (s *Indexer) BucketsByIndices(indices []uint64, height uint64) ([]*Bucket, error) { + return s.cache.BucketsByIndices(indices, height) } // BucketsByCandidate returns the buckets by candidate -func (s *Indexer) BucketsByCandidate(candidate address.Address) ([]*Bucket, error) { - return s.cache.BucketsByCandidate(candidate), nil +func (s *Indexer) BucketsByCandidate(candidate address.Address, height uint64) ([]*Bucket, error) { + return s.cache.BucketsByCandidate(candidate, height) } // TotalBucketCount returns the total bucket count including active and burnt buckets -func (s *Indexer) TotalBucketCount() uint64 { - return s.cache.TotalBucketCount() +func (s *Indexer) TotalBucketCount(height uint64) (uint64, error) { + return s.cache.TotalBucketCount(height) } // BucketTypes returns the active bucket types -func (s *Indexer) BucketTypes() ([]*BucketType, error) { - btMap := s.cache.ActiveBucketTypes() +func (s *Indexer) BucketTypes(height uint64) ([]*BucketType, error) { + btMap, err := s.cache.ActiveBucketTypes(height) + if err != nil { + return nil, err + } bts := make([]*BucketType, 0, len(btMap)) for _, bt := range btMap { bts = append(bts, bt) From a5fcd3afd718cff23464af5ed69a5a1538034cd6 Mon Sep 17 00:00:00 2001 From: envestcc Date: Tue, 22 Aug 2023 17:18:09 +0800 Subject: [PATCH 2/6] update tests --- .../staking/contractstake_indexer_mock.go | 69 +++-- .../staking/staking_statereader_test.go | 26 +- blockindex/contractstaking/cache_test.go | 243 ++++++++++++------ blockindex/contractstaking/indexer_test.go | 227 +++++++++++----- e2etest/contract_staking_test.go | 98 ++++--- 5 files changed, 447 insertions(+), 216 deletions(-) diff --git a/action/protocol/staking/contractstake_indexer_mock.go b/action/protocol/staking/contractstake_indexer_mock.go index ebb96270a2..4f930296a5 100644 --- a/action/protocol/staking/contractstake_indexer_mock.go +++ b/action/protocol/staking/contractstake_indexer_mock.go @@ -36,89 +36,106 @@ func (m *MockContractStakingIndexer) EXPECT() *MockContractStakingIndexerMockRec } // BucketTypes mocks base method. -func (m *MockContractStakingIndexer) BucketTypes() ([]*ContractStakingBucketType, error) { +func (m *MockContractStakingIndexer) BucketTypes(height uint64) ([]*ContractStakingBucketType, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BucketTypes") + ret := m.ctrl.Call(m, "BucketTypes", height) ret0, _ := ret[0].([]*ContractStakingBucketType) ret1, _ := ret[1].(error) return ret0, ret1 } // BucketTypes indicates an expected call of BucketTypes. -func (mr *MockContractStakingIndexerMockRecorder) BucketTypes() *gomock.Call { +func (mr *MockContractStakingIndexerMockRecorder) BucketTypes(height interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BucketTypes", reflect.TypeOf((*MockContractStakingIndexer)(nil).BucketTypes)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BucketTypes", reflect.TypeOf((*MockContractStakingIndexer)(nil).BucketTypes), height) } // Buckets mocks base method. -func (m *MockContractStakingIndexer) Buckets() ([]*VoteBucket, error) { +func (m *MockContractStakingIndexer) Buckets(height uint64) ([]*VoteBucket, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Buckets") + ret := m.ctrl.Call(m, "Buckets", height) ret0, _ := ret[0].([]*VoteBucket) ret1, _ := ret[1].(error) return ret0, ret1 } // Buckets indicates an expected call of Buckets. -func (mr *MockContractStakingIndexerMockRecorder) Buckets() *gomock.Call { +func (mr *MockContractStakingIndexerMockRecorder) Buckets(height interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Buckets", reflect.TypeOf((*MockContractStakingIndexer)(nil).Buckets)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Buckets", reflect.TypeOf((*MockContractStakingIndexer)(nil).Buckets), height) } // BucketsByCandidate mocks base method. -func (m *MockContractStakingIndexer) BucketsByCandidate(ownerAddr address.Address) ([]*VoteBucket, error) { +func (m *MockContractStakingIndexer) BucketsByCandidate(ownerAddr address.Address, height uint64) ([]*VoteBucket, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BucketsByCandidate", ownerAddr) + ret := m.ctrl.Call(m, "BucketsByCandidate", ownerAddr, height) ret0, _ := ret[0].([]*VoteBucket) ret1, _ := ret[1].(error) return ret0, ret1 } // BucketsByCandidate indicates an expected call of BucketsByCandidate. -func (mr *MockContractStakingIndexerMockRecorder) BucketsByCandidate(ownerAddr interface{}) *gomock.Call { +func (mr *MockContractStakingIndexerMockRecorder) BucketsByCandidate(ownerAddr, height interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BucketsByCandidate", reflect.TypeOf((*MockContractStakingIndexer)(nil).BucketsByCandidate), ownerAddr) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BucketsByCandidate", reflect.TypeOf((*MockContractStakingIndexer)(nil).BucketsByCandidate), ownerAddr, height) } // BucketsByIndices mocks base method. -func (m *MockContractStakingIndexer) BucketsByIndices(arg0 []uint64) ([]*VoteBucket, error) { +func (m *MockContractStakingIndexer) BucketsByIndices(arg0 []uint64, arg1 uint64) ([]*VoteBucket, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BucketsByIndices", arg0) + ret := m.ctrl.Call(m, "BucketsByIndices", arg0, arg1) ret0, _ := ret[0].([]*VoteBucket) ret1, _ := ret[1].(error) return ret0, ret1 } // BucketsByIndices indicates an expected call of BucketsByIndices. -func (mr *MockContractStakingIndexerMockRecorder) BucketsByIndices(arg0 interface{}) *gomock.Call { +func (mr *MockContractStakingIndexerMockRecorder) BucketsByIndices(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BucketsByIndices", reflect.TypeOf((*MockContractStakingIndexer)(nil).BucketsByIndices), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BucketsByIndices", reflect.TypeOf((*MockContractStakingIndexer)(nil).BucketsByIndices), arg0, arg1) } // CandidateVotes mocks base method. -func (m *MockContractStakingIndexer) CandidateVotes(ownerAddr address.Address) *big.Int { +func (m *MockContractStakingIndexer) CandidateVotes(ownerAddr address.Address, height uint64) (*big.Int, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CandidateVotes", ownerAddr) + ret := m.ctrl.Call(m, "CandidateVotes", ownerAddr, height) ret0, _ := ret[0].(*big.Int) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // CandidateVotes indicates an expected call of CandidateVotes. -func (mr *MockContractStakingIndexerMockRecorder) CandidateVotes(ownerAddr interface{}) *gomock.Call { +func (mr *MockContractStakingIndexerMockRecorder) CandidateVotes(ownerAddr, height interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CandidateVotes", reflect.TypeOf((*MockContractStakingIndexer)(nil).CandidateVotes), ownerAddr) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CandidateVotes", reflect.TypeOf((*MockContractStakingIndexer)(nil).CandidateVotes), ownerAddr, height) +} + +// Height mocks base method. +func (m *MockContractStakingIndexer) Height() (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Height") + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Height indicates an expected call of Height. +func (mr *MockContractStakingIndexerMockRecorder) Height() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Height", reflect.TypeOf((*MockContractStakingIndexer)(nil).Height)) } // TotalBucketCount mocks base method. -func (m *MockContractStakingIndexer) TotalBucketCount() uint64 { +func (m *MockContractStakingIndexer) TotalBucketCount(height uint64) (uint64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "TotalBucketCount") + ret := m.ctrl.Call(m, "TotalBucketCount", height) ret0, _ := ret[0].(uint64) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // TotalBucketCount indicates an expected call of TotalBucketCount. -func (mr *MockContractStakingIndexerMockRecorder) TotalBucketCount() *gomock.Call { +func (mr *MockContractStakingIndexerMockRecorder) TotalBucketCount(height interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TotalBucketCount", reflect.TypeOf((*MockContractStakingIndexer)(nil).TotalBucketCount)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TotalBucketCount", reflect.TypeOf((*MockContractStakingIndexer)(nil).TotalBucketCount), height) } diff --git a/action/protocol/staking/staking_statereader_test.go b/action/protocol/staking/staking_statereader_test.go index 7693507c51..c29a7a8d03 100644 --- a/action/protocol/staking/staking_statereader_test.go +++ b/action/protocol/staking/staking_statereader_test.go @@ -123,7 +123,7 @@ func TestStakingStateReader(t *testing.T) { sf.EXPECT().ReadView(gomock.Any()).Return(testNativeData, nil).Times(1) contractIndexer := NewMockContractStakingIndexer(ctrl) - contractIndexer.EXPECT().Buckets().Return(testContractBuckets, nil).AnyTimes() + contractIndexer.EXPECT().Buckets(gomock.Any()).Return(testContractBuckets, nil).AnyTimes() stakeSR, err := newCompositeStakingStateReader(contractIndexer, nil, sf) r.NoError(err) @@ -241,7 +241,7 @@ func TestStakingStateReader(t *testing.T) { *arg0R = totalBucketCount{count: 1} return uint64(1), nil }).Times(1) - contractIndexer.EXPECT().BucketsByCandidate(gomock.Any()).DoAndReturn(func(arg0 address.Address) ([]*VoteBucket, error) { + contractIndexer.EXPECT().BucketsByCandidate(gomock.Any(), gomock.Any()).DoAndReturn(func(arg0 address.Address, arg1 uint64) ([]*VoteBucket, error) { buckets := []*VoteBucket{} for i := range testContractBuckets { if testContractBuckets[i].Candidate.String() == arg0.String() { @@ -285,7 +285,7 @@ func TestStakingStateReader(t *testing.T) { *arg0R = totalBucketCount{count: 1} return uint64(1), nil }).Times(1) - contractIndexer.EXPECT().BucketsByIndices(gomock.Any()).DoAndReturn(func(arg0 []uint64) ([]*VoteBucket, error) { + contractIndexer.EXPECT().BucketsByIndices(gomock.Any(), gomock.Any()).DoAndReturn(func(arg0 []uint64, arg1 uint64) ([]*VoteBucket, error) { buckets := []*VoteBucket{} for i := range arg0 { buckets = append(buckets, testContractBuckets[arg0[i]]) @@ -318,7 +318,7 @@ func TestStakingStateReader(t *testing.T) { *arg0R = totalBucketCount{count: 1} return uint64(1), nil }).Times(1) - contractIndexer.EXPECT().TotalBucketCount().Return(uint64(len(testContractBuckets))).Times(1) + contractIndexer.EXPECT().TotalBucketCount(gomock.Any()).Return(uint64(len(testContractBuckets)), nil).Times(1) cfg := genesis.Default cfg.GreenlandBlockHeight = 0 ctx = genesis.WithGenesisContext(ctx, cfg) @@ -332,13 +332,13 @@ func TestStakingStateReader(t *testing.T) { }) t.Run("readStateCandidates", func(t *testing.T) { _, contractIndexer, stakeSR, ctx, r := prepare(t) - contractIndexer.EXPECT().CandidateVotes(gomock.Any()).DoAndReturn(func(ownerAddr address.Address) *big.Int { + contractIndexer.EXPECT().CandidateVotes(gomock.Any(), gomock.Any()).DoAndReturn(func(ownerAddr address.Address, height uint64) (*big.Int, error) { for _, b := range testContractBuckets { if b.Owner.String() == ownerAddr.String() { - return b.StakedAmount + return b.StakedAmount, nil } } - return big.NewInt(0) + return big.NewInt(0), nil }).MinTimes(1) req := &iotexapi.ReadStakingDataRequest_Candidates{ Pagination: &iotexapi.PaginationParam{ @@ -362,13 +362,13 @@ func TestStakingStateReader(t *testing.T) { }) t.Run("readStateCandidateByName", func(t *testing.T) { _, contractIndexer, stakeSR, ctx, r := prepare(t) - contractIndexer.EXPECT().CandidateVotes(gomock.Any()).DoAndReturn(func(ownerAddr address.Address) *big.Int { + contractIndexer.EXPECT().CandidateVotes(gomock.Any(), gomock.Any()).DoAndReturn(func(ownerAddr address.Address, height uint64) (*big.Int, error) { for _, b := range testContractBuckets { if b.Owner.String() == ownerAddr.String() { - return b.StakedAmount + return b.StakedAmount, nil } } - return big.NewInt(0) + return big.NewInt(0), nil }).MinTimes(1) req := &iotexapi.ReadStakingDataRequest_CandidateByName{ CandName: "cand1", @@ -385,13 +385,13 @@ func TestStakingStateReader(t *testing.T) { }) t.Run("readStateCandidateByAddress", func(t *testing.T) { _, contractIndexer, stakeSR, ctx, r := prepare(t) - contractIndexer.EXPECT().CandidateVotes(gomock.Any()).DoAndReturn(func(ownerAddr address.Address) *big.Int { + contractIndexer.EXPECT().CandidateVotes(gomock.Any(), gomock.Any()).DoAndReturn(func(ownerAddr address.Address, height uint64) (*big.Int, error) { for _, b := range testContractBuckets { if b.Owner.String() == ownerAddr.String() { - return b.StakedAmount + return b.StakedAmount, nil } } - return big.NewInt(0) + return big.NewInt(0), nil }).MinTimes(1) req := &iotexapi.ReadStakingDataRequest_CandidateByAddress{ OwnerAddr: identityset.Address(1).String(), diff --git a/blockindex/contractstaking/cache_test.go b/blockindex/contractstaking/cache_test.go index 58227c249a..76f7ef0713 100644 --- a/blockindex/contractstaking/cache_test.go +++ b/blockindex/contractstaking/cache_test.go @@ -7,6 +7,8 @@ import ( "github.com/stretchr/testify/require" + "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/action/protocol/staking" "github.com/iotexproject/iotex-core/config" "github.com/iotexproject/iotex-core/db" @@ -15,63 +17,69 @@ 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) + r.NoError(err) + r.EqualValues(expectVotes, votes.Int64()) +} + func TestContractStakingCache_CandidateVotes(t *testing.T) { require := require.New(t) cache := newContractStakingCache("") // no bucket - require.EqualValues(0, cache.CandidateVotes(identityset.Address(1)).Int64()) + checkCacheCandidateVotes(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)}) - require.EqualValues(100, cache.CandidateVotes(identityset.Address(1)).Int64()) + checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 100) // two buckets cache.PutBucketInfo(2, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) - require.EqualValues(200, cache.CandidateVotes(identityset.Address(1)).Int64()) + checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 200) // 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)}) - require.EqualValues(200, cache.CandidateVotes(identityset.Address(1)).Int64()) - require.EqualValues(100, cache.CandidateVotes(identityset.Address(3)).Int64()) + checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 200) + checkCacheCandidateVotes(require, cache, 0, identityset.Address(3), 100) // 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)}) - require.EqualValues(300, cache.CandidateVotes(identityset.Address(1)).Int64()) + checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 300) // 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)}) - require.EqualValues(500, cache.CandidateVotes(identityset.Address(1)).Int64()) + checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 500) // 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)}) - require.EqualValues(800, cache.CandidateVotes(identityset.Address(1)).Int64()) + checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 800) // 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)}) - require.EqualValues(800, cache.CandidateVotes(identityset.Address(1)).Int64()) + checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 800) // 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)}) - require.EqualValues(900, cache.CandidateVotes(identityset.Address(1)).Int64()) + checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 900) // 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)}) - require.EqualValues(800, cache.CandidateVotes(identityset.Address(1)).Int64()) - require.EqualValues(200, cache.CandidateVotes(identityset.Address(3)).Int64()) + checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 800) + checkCacheCandidateVotes(require, cache, 0, identityset.Address(3), 200) // 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)}) - require.EqualValues(800, cache.CandidateVotes(identityset.Address(1)).Int64()) - require.EqualValues(200, cache.CandidateVotes(identityset.Address(3)).Int64()) + checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 800) + checkCacheCandidateVotes(require, cache, 0, identityset.Address(3), 200) // 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)}) - require.EqualValues(800, cache.CandidateVotes(identityset.Address(1)).Int64()) - require.EqualValues(300, cache.CandidateVotes(identityset.Address(3)).Int64()) + checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 800) + checkCacheCandidateVotes(require, cache, 0, identityset.Address(3), 300) } func TestContractStakingCache_Buckets(t *testing.T) { @@ -79,54 +87,71 @@ func TestContractStakingCache_Buckets(t *testing.T) { contractAddr := identityset.Address(27).String() cache := newContractStakingCache(contractAddr) + height := uint64(0) // no bucket - require.Empty(cache.Buckets()) + bts, err := cache.Buckets(height) + require.NoError(err) + require.Empty(bts) // add 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)}) - buckets := cache.Buckets() + buckets, err := cache.Buckets(height) + require.NoError(err) require.Len(buckets, 1) checkVoteBucket(require, buckets[0], 1, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) - bucket, ok := cache.Bucket(1) + bucket, ok, err := cache.Bucket(1, height) + require.NoError(err) require.True(ok) checkVoteBucket(require, bucket, 1, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) // add one bucket with different index cache.PutBucketType(2, &BucketType{Amount: big.NewInt(200), Duration: 200, ActivatedAt: 1}) cache.PutBucketInfo(2, &bucketInfo{TypeIndex: 2, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(3), Owner: identityset.Address(4)}) - bucketMaps := bucketsToMap(cache.Buckets()) + bts, err = cache.Buckets(height) + require.NoError(err) + bucketMaps := bucketsToMap(bts) require.Len(bucketMaps, 2) checkVoteBucket(require, bucketMaps[1], 1, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) checkVoteBucket(require, bucketMaps[2], 2, identityset.Address(3).String(), identityset.Address(4).String(), 200, 200, 1, 1, maxBlockNumber, true, contractAddr) - bucket, ok = cache.Bucket(1) + bucket, ok, err = cache.Bucket(1, height) + require.NoError(err) require.True(ok) checkVoteBucket(require, bucket, 1, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) - bucket, ok = cache.Bucket(2) + bucket, ok, err = cache.Bucket(2, height) + require.NoError(err) require.True(ok) checkVoteBucket(require, bucket, 2, identityset.Address(3).String(), identityset.Address(4).String(), 200, 200, 1, 1, maxBlockNumber, true, contractAddr) // update delegate of bucket 2 cache.PutBucketInfo(2, &bucketInfo{TypeIndex: 2, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(5), Owner: identityset.Address(4)}) - bucketMaps = bucketsToMap(cache.Buckets()) + bts, err = cache.Buckets(height) + require.NoError(err) + bucketMaps = bucketsToMap(bts) require.Len(bucketMaps, 2) checkVoteBucket(require, bucketMaps[1], 1, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) checkVoteBucket(require, bucketMaps[2], 2, identityset.Address(5).String(), identityset.Address(4).String(), 200, 200, 1, 1, maxBlockNumber, true, contractAddr) - bucket, ok = cache.Bucket(1) + bucket, ok, err = cache.Bucket(1, height) + require.NoError(err) require.True(ok) checkVoteBucket(require, bucket, 1, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) - bucket, ok = cache.Bucket(2) + bucket, ok, err = cache.Bucket(2, height) + require.NoError(err) require.True(ok) checkVoteBucket(require, bucket, 2, identityset.Address(5).String(), identityset.Address(4).String(), 200, 200, 1, 1, maxBlockNumber, true, contractAddr) // delete bucket 1 cache.DeleteBucketInfo(1) - bucketMaps = bucketsToMap(cache.Buckets()) + bts, err = cache.Buckets(height) + require.NoError(err) + bucketMaps = bucketsToMap(bts) require.Len(bucketMaps, 1) checkVoteBucket(require, bucketMaps[2], 2, identityset.Address(5).String(), identityset.Address(4).String(), 200, 200, 1, 1, maxBlockNumber, true, contractAddr) - _, ok = cache.Bucket(1) + _, ok, err = cache.Bucket(1, height) + require.NoError(err) require.False(ok) - bucket, ok = cache.Bucket(2) + bucket, ok, err = cache.Bucket(2, height) + require.NoError(err) require.True(ok) checkVoteBucket(require, bucket, 2, identityset.Address(5).String(), identityset.Address(4).String(), 200, 200, 1, 1, maxBlockNumber, true, contractAddr) } @@ -136,49 +161,67 @@ func TestContractStakingCache_BucketsByCandidate(t *testing.T) { contractAddr := identityset.Address(27).String() cache := newContractStakingCache(contractAddr) + height := uint64(0) // no bucket - buckets := cache.BucketsByCandidate(identityset.Address(1)) + buckets, err := cache.BucketsByCandidate(identityset.Address(1), height) + require.NoError(err) require.Len(buckets, 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)}) - bucketMaps := bucketsToMap(cache.BucketsByCandidate(identityset.Address(1))) + bts, err := cache.BucketsByCandidate(identityset.Address(1), height) + require.NoError(err) + bucketMaps := bucketsToMap(bts) require.Len(bucketMaps, 1) checkVoteBucket(require, bucketMaps[1], 1, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) // two buckets cache.PutBucketInfo(2, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) - bucketMaps = bucketsToMap(cache.BucketsByCandidate(identityset.Address(1))) + bts, err = cache.BucketsByCandidate(identityset.Address(1), height) + require.NoError(err) + bucketMaps = bucketsToMap(bts) require.Len(bucketMaps, 2) checkVoteBucket(require, bucketMaps[1], 1, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) checkVoteBucket(require, bucketMaps[2], 2, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) // 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)}) - bucketMaps = bucketsToMap(cache.BucketsByCandidate(identityset.Address(1))) + bts, err = cache.BucketsByCandidate(identityset.Address(1), height) + require.NoError(err) + bucketMaps = bucketsToMap(bts) require.Len(bucketMaps, 2) require.Nil(bucketMaps[3]) - bucketMaps = bucketsToMap(cache.BucketsByCandidate(identityset.Address(3))) + bts, err = cache.BucketsByCandidate(identityset.Address(3), height) + require.NoError(err) + bucketMaps = bucketsToMap(bts) require.Len(bucketMaps, 1) checkVoteBucket(require, bucketMaps[3], 3, identityset.Address(3).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) // 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)}) - bucketMaps = bucketsToMap(cache.BucketsByCandidate(identityset.Address(1))) + bts, err = cache.BucketsByCandidate(identityset.Address(1), height) + require.NoError(err) + bucketMaps = bucketsToMap(bts) require.Len(bucketMaps, 1) require.Nil(bucketMaps[1]) checkVoteBucket(require, bucketMaps[2], 2, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) - bucketMaps = bucketsToMap(cache.BucketsByCandidate(identityset.Address(3))) + bts, err = cache.BucketsByCandidate(identityset.Address(3), height) + require.NoError(err) + bucketMaps = bucketsToMap(bts) require.Len(bucketMaps, 2) checkVoteBucket(require, bucketMaps[1], 1, identityset.Address(3).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) checkVoteBucket(require, bucketMaps[3], 3, identityset.Address(3).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) // delete bucket 2 cache.DeleteBucketInfo(2) - bucketMaps = bucketsToMap(cache.BucketsByCandidate(identityset.Address(1))) + bts, err = cache.BucketsByCandidate(identityset.Address(1), height) + require.NoError(err) + bucketMaps = bucketsToMap(bts) require.Len(bucketMaps, 0) - bucketMaps = bucketsToMap(cache.BucketsByCandidate(identityset.Address(3))) + bts, err = cache.BucketsByCandidate(identityset.Address(3), height) + require.NoError(err) + bucketMaps = bucketsToMap(bts) require.Len(bucketMaps, 2) checkVoteBucket(require, bucketMaps[1], 1, identityset.Address(3).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) checkVoteBucket(require, bucketMaps[3], 3, identityset.Address(3).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) @@ -190,15 +233,16 @@ func TestContractStakingCache_BucketsByIndices(t *testing.T) { contractAddr := identityset.Address(27).String() cache := newContractStakingCache(contractAddr) + height := uint64(0) // no bucket - buckets, err := cache.BucketsByIndices([]uint64{1}) + buckets, err := cache.BucketsByIndices([]uint64{1}, height) require.NoError(err) require.Len(buckets, 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)}) - buckets, err = cache.BucketsByIndices([]uint64{1}) + buckets, err = cache.BucketsByIndices([]uint64{1}, height) require.NoError(err) require.Len(buckets, 1) bucketMaps := bucketsToMap(buckets) @@ -206,7 +250,7 @@ func TestContractStakingCache_BucketsByIndices(t *testing.T) { // two buckets cache.PutBucketInfo(2, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) - buckets, err = cache.BucketsByIndices([]uint64{1, 2}) + buckets, err = cache.BucketsByIndices([]uint64{1, 2}, height) require.NoError(err) require.Len(buckets, 2) bucketMaps = bucketsToMap(buckets) @@ -214,12 +258,12 @@ func TestContractStakingCache_BucketsByIndices(t *testing.T) { checkVoteBucket(require, bucketMaps[2], 2, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) // one bucket not found - buckets, err = cache.BucketsByIndices([]uint64{3}) + buckets, err = cache.BucketsByIndices([]uint64{3}, height) require.NoError(err) require.Len(buckets, 0) // one bucket found, one not found - buckets, err = cache.BucketsByIndices([]uint64{1, 3}) + buckets, err = cache.BucketsByIndices([]uint64{1, 3}, height) require.NoError(err) require.Len(buckets, 1) bucketMaps = bucketsToMap(buckets) @@ -227,7 +271,7 @@ func TestContractStakingCache_BucketsByIndices(t *testing.T) { // delete bucket 1 cache.DeleteBucketInfo(1) - buckets, err = cache.BucketsByIndices([]uint64{1}) + buckets, err = cache.BucketsByIndices([]uint64{1}, height) require.NoError(err) require.Len(buckets, 0) } @@ -236,32 +280,45 @@ func TestContractStakingCache_TotalBucketCount(t *testing.T) { require := require.New(t) cache := newContractStakingCache("") + height := uint64(0) // no bucket - require.EqualValues(0, cache.TotalBucketCount()) + tbc, err := cache.TotalBucketCount(height) + require.NoError(err) + require.EqualValues(0, tbc) // one bucket cache.putTotalBucketCount(1) - require.EqualValues(1, cache.TotalBucketCount()) + tbc, err = cache.TotalBucketCount(height) + require.NoError(err) + require.EqualValues(1, tbc) // two buckets cache.putTotalBucketCount(2) - require.EqualValues(2, cache.TotalBucketCount()) + tbc, err = cache.TotalBucketCount(height) + require.NoError(err) + require.EqualValues(2, tbc) // delete bucket 1 cache.DeleteBucketInfo(1) - require.EqualValues(2, cache.TotalBucketCount()) + tbc, err = cache.TotalBucketCount(height) + require.NoError(err) + require.EqualValues(2, tbc) } func TestContractStakingCache_ActiveBucketTypes(t *testing.T) { require := require.New(t) cache := newContractStakingCache("") + height := uint64(0) // no bucket type - require.Empty(cache.ActiveBucketTypes()) + abt, err := cache.ActiveBucketTypes(height) + require.NoError(err) + require.Empty(abt) // one bucket type cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) - activeBucketTypes := cache.ActiveBucketTypes() + activeBucketTypes, err := cache.ActiveBucketTypes(height) + require.NoError(err) require.Len(activeBucketTypes, 1) require.EqualValues(100, activeBucketTypes[1].Amount.Int64()) require.EqualValues(100, activeBucketTypes[1].Duration) @@ -269,7 +326,8 @@ func TestContractStakingCache_ActiveBucketTypes(t *testing.T) { // two bucket types cache.PutBucketType(2, &BucketType{Amount: big.NewInt(200), Duration: 200, ActivatedAt: 2}) - activeBucketTypes = cache.ActiveBucketTypes() + activeBucketTypes, err = cache.ActiveBucketTypes(height) + require.NoError(err) require.Len(activeBucketTypes, 2) require.EqualValues(100, activeBucketTypes[1].Amount.Int64()) require.EqualValues(100, activeBucketTypes[1].Duration) @@ -280,7 +338,8 @@ func TestContractStakingCache_ActiveBucketTypes(t *testing.T) { // add one inactive bucket type cache.PutBucketType(3, &BucketType{Amount: big.NewInt(300), Duration: 300, ActivatedAt: maxBlockNumber}) - activeBucketTypes = cache.ActiveBucketTypes() + activeBucketTypes, err = cache.ActiveBucketTypes(height) + require.NoError(err) require.Len(activeBucketTypes, 2) require.EqualValues(100, activeBucketTypes[1].Amount.Int64()) require.EqualValues(100, activeBucketTypes[1].Duration) @@ -291,7 +350,8 @@ func TestContractStakingCache_ActiveBucketTypes(t *testing.T) { // deactivate bucket type 1 cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: maxBlockNumber}) - activeBucketTypes = cache.ActiveBucketTypes() + activeBucketTypes, err = cache.ActiveBucketTypes(height) + require.NoError(err) require.Len(activeBucketTypes, 1) require.EqualValues(200, activeBucketTypes[2].Amount.Int64()) require.EqualValues(200, activeBucketTypes[2].Duration) @@ -299,7 +359,8 @@ func TestContractStakingCache_ActiveBucketTypes(t *testing.T) { // reactivate bucket type 1 cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) - activeBucketTypes = cache.ActiveBucketTypes() + activeBucketTypes, err = cache.ActiveBucketTypes(height) + require.NoError(err) require.Len(activeBucketTypes, 2) require.EqualValues(100, activeBucketTypes[1].Amount.Int64()) require.EqualValues(100, activeBucketTypes[1].Duration) @@ -321,7 +382,8 @@ func TestContractStakingCache_Merge(t *testing.T) { err := cache.Merge(delta, height) require.NoError(err) // check that bucket type was added to cache - activeBucketTypes := cache.ActiveBucketTypes() + activeBucketTypes, err := cache.ActiveBucketTypes(height) + require.NoError(err) require.Len(activeBucketTypes, 1) require.EqualValues(100, activeBucketTypes[1].Amount.Int64()) require.EqualValues(100, activeBucketTypes[1].Duration) @@ -335,7 +397,9 @@ 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 - require.EqualValues(100, cache.CandidateVotes(identityset.Address(1)).Int64()) + votes, err := cache.CandidateVotes(identityset.Address(1), height) + require.NoError(err) + require.EqualValues(100, votes.Int64()) // create delta with updated bucket delegate delta = newContractStakingDelta() @@ -344,8 +408,12 @@ 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 - require.EqualValues(0, cache.CandidateVotes(identityset.Address(1)).Int64()) - require.EqualValues(100, cache.CandidateVotes(identityset.Address(3)).Int64()) + votes, err = cache.CandidateVotes(identityset.Address(1), height) + require.NoError(err) + require.EqualValues(0, votes.Int64()) + votes, err = cache.CandidateVotes(identityset.Address(3), height) + require.NoError(err) + require.EqualValues(100, votes.Int64()) // create delta with deleted bucket delta = newContractStakingDelta() @@ -354,7 +422,9 @@ 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 - require.EqualValues(0, cache.CandidateVotes(identityset.Address(3)).Int64()) + votes, err = cache.CandidateVotes(identityset.Address(3), height) + require.NoError(err) + require.EqualValues(0, votes.Int64()) } func TestContractStakingCache_MatchBucketType(t *testing.T) { @@ -396,20 +466,29 @@ func TestContractStakingCache_BucketTypeCount(t *testing.T) { require := require.New(t) cache := newContractStakingCache("") + height := uint64(0) // no bucket type - require.EqualValues(0, cache.BucketTypeCount()) + btc, err := cache.BucketTypeCount(height) + require.NoError(err) + require.EqualValues(0, btc) // one bucket type cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) - require.EqualValues(1, cache.BucketTypeCount()) + btc, err = cache.BucketTypeCount(height) + require.NoError(err) + require.EqualValues(1, btc) // two bucket types cache.PutBucketType(2, &BucketType{Amount: big.NewInt(200), Duration: 200, ActivatedAt: 2}) - require.EqualValues(2, cache.BucketTypeCount()) + btc, err = cache.BucketTypeCount(height) + require.NoError(err) + require.EqualValues(2, btc) // deactivate bucket type 1 cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: maxBlockNumber}) - require.EqualValues(2, cache.BucketTypeCount()) + btc, err = cache.BucketTypeCount(height) + require.NoError(err) + require.EqualValues(2, btc) } func TestContractStakingCache_LoadFromDB(t *testing.T) { @@ -426,20 +505,34 @@ func TestContractStakingCache_LoadFromDB(t *testing.T) { require.NoError(kvstore.Start(context.Background())) defer kvstore.Stop(context.Background()) + height := uint64(0) err = cache.LoadFromDB(kvstore) require.NoError(err) - require.Equal(uint64(0), cache.TotalBucketCount()) - require.Equal(0, len(cache.Buckets())) - require.EqualValues(0, cache.BucketTypeCount()) + tbc, err := cache.TotalBucketCount(height) + require.NoError(err) + require.Equal(uint64(0), tbc) + bts, err := cache.Buckets(height) + require.NoError(err) + require.Equal(0, len(bts)) + btc, err := cache.BucketTypeCount(height) + require.NoError(err) + require.EqualValues(0, btc) // load from db with height and total bucket count - kvstore.Put(_StakingNS, _stakingHeightKey, byteutil.Uint64ToBytesBigEndian(12345)) + height = 12345 + kvstore.Put(_StakingNS, _stakingHeightKey, byteutil.Uint64ToBytesBigEndian(height)) kvstore.Put(_StakingNS, _stakingTotalBucketCountKey, byteutil.Uint64ToBytesBigEndian(10)) err = cache.LoadFromDB(kvstore) require.NoError(err) - require.Equal(uint64(10), cache.TotalBucketCount()) - require.Equal(0, len(cache.Buckets())) - require.EqualValues(0, cache.BucketTypeCount()) + tbc, err = cache.TotalBucketCount(height) + require.NoError(err) + require.Equal(uint64(10), tbc) + bts, err = cache.Buckets(height) + require.NoError(err) + require.Equal(0, len(bts)) + btc, err = cache.BucketTypeCount(height) + require.NoError(err) + require.EqualValues(0, btc) // load from db with bucket bucketInfo := &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)} @@ -448,12 +541,18 @@ func TestContractStakingCache_LoadFromDB(t *testing.T) { kvstore.Put(_StakingBucketTypeNS, byteutil.Uint64ToBytesBigEndian(1), bucketType.Serialize()) err = cache.LoadFromDB(kvstore) require.NoError(err) - require.Equal(uint64(10), cache.TotalBucketCount()) + tbc, err = cache.TotalBucketCount(height) + require.NoError(err) + require.Equal(uint64(10), tbc) bi, ok := cache.BucketInfo(1) require.True(ok) - require.Equal(1, len(cache.Buckets())) + bts, err = cache.Buckets(height) + require.NoError(err) + require.Equal(1, len(bts)) require.Equal(bucketInfo, bi) - require.EqualValues(1, cache.BucketTypeCount()) + btc, err = cache.BucketTypeCount(height) + require.NoError(err) + require.EqualValues(1, btc) id, bt, ok := cache.MatchBucketType(big.NewInt(100), 100) require.True(ok) require.EqualValues(1, id) diff --git a/blockindex/contractstaking/indexer_test.go b/blockindex/contractstaking/indexer_test.go index 4650c07718..3098abd514 100644 --- a/blockindex/contractstaking/indexer_test.go +++ b/blockindex/contractstaking/indexer_test.go @@ -76,10 +76,12 @@ func TestContractStakingIndexerLoadCache(t *testing.T) { stake(r, handler, owner, delegate, 1, 10, 100, height) err = indexer.commit(handler, height) r.NoError(err) - buckets, err := indexer.Buckets() + buckets, err := indexer.Buckets(height) r.NoError(err) r.EqualValues(1, len(buckets)) - r.EqualValues(1, indexer.TotalBucketCount()) + tbc, err := indexer.TotalBucketCount(height) + r.EqualValues(1, tbc) + r.NoError(err) h, err := indexer.Height() r.NoError(err) r.EqualValues(height, h) @@ -92,7 +94,7 @@ func TestContractStakingIndexerLoadCache(t *testing.T) { r.NoError(newIndexer.Start(context.Background())) // check cache - newBuckets, err := newIndexer.Buckets() + newBuckets, err := newIndexer.Buckets(height) r.NoError(err) r.Equal(len(buckets), len(newBuckets)) for i := range buckets { @@ -102,7 +104,9 @@ func TestContractStakingIndexerLoadCache(t *testing.T) { r.NoError(err) r.Equal(height, newHeight) r.Equal(startHeight, newIndexer.StartHeight()) - r.EqualValues(1, newIndexer.TotalBucketCount()) + tbc, err = newIndexer.TotalBucketCount(height) + r.EqualValues(1, tbc) + r.NoError(err) r.NoError(newIndexer.Stop(context.Background())) } @@ -155,16 +159,16 @@ func TestContractStakingIndexerThreadSafe(t *testing.T) { go func() { defer wait.Done() for i := 0; i < 1000; i++ { - _, err := indexer.Buckets() + _, err := indexer.Buckets(0) r.NoError(err) - _, err = indexer.BucketTypes() + _, err = indexer.BucketTypes(0) r.NoError(err) - _, err = indexer.BucketsByCandidate(delegate) + _, err = indexer.BucketsByCandidate(delegate, 0) r.NoError(err) - indexer.CandidateVotes(delegate) + indexer.CandidateVotes(delegate, 0) _, err = indexer.Height() r.NoError(err) - indexer.TotalBucketCount() + indexer.TotalBucketCount(0) } }() } @@ -224,7 +228,7 @@ func TestContractStakingIndexerBucketType(t *testing.T) { } err = indexer.commit(handler, height) r.NoError(err) - bucketTypes, err := indexer.BucketTypes() + bucketTypes, err := indexer.BucketTypes(height) r.NoError(err) r.Equal(len(bucketTypeData), len(bucketTypes)) for _, bt := range bucketTypes { @@ -240,7 +244,7 @@ func TestContractStakingIndexerBucketType(t *testing.T) { } err = indexer.commit(handler, height) r.NoError(err) - bucketTypes, err = indexer.BucketTypes() + bucketTypes, err = indexer.BucketTypes(height) r.NoError(err) r.Equal(len(bucketTypeData)-2, len(bucketTypes)) for _, bt := range bucketTypes { @@ -256,7 +260,7 @@ func TestContractStakingIndexerBucketType(t *testing.T) { } err = indexer.commit(handler, height) r.NoError(err) - bucketTypes, err = indexer.BucketTypes() + bucketTypes, err = indexer.BucketTypes(height) r.NoError(err) r.Equal(len(bucketTypeData), len(bucketTypes)) for _, bt := range bucketTypes { @@ -307,7 +311,8 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { stake(r, handler, owner, delegate, 1, 10, 100, height) r.NoError(err) r.NoError(indexer.commit(handler, height)) - bucket, ok := indexer.Bucket(1) + bucket, ok, err := indexer.Bucket(1, height) + r.NoError(err) r.True(ok) r.EqualValues(1, bucket.Index) r.EqualValues(owner, bucket.Owner) @@ -319,8 +324,12 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { r.EqualValues(height, bucket.CreateBlockHeight) r.EqualValues(maxBlockNumber, bucket.UnstakeStartBlockHeight) r.EqualValues(_testStakingContractAddress, bucket.ContractAddress) - r.EqualValues(10, indexer.CandidateVotes(delegate).Uint64()) - r.EqualValues(1, indexer.TotalBucketCount()) + votes, err := indexer.CandidateVotes(delegate, height) + r.NoError(err) + r.EqualValues(10, votes.Uint64()) + tbc, err := indexer.TotalBucketCount(height) + r.NoError(err) + r.EqualValues(1, tbc) // transfer newOwner := identityset.Address(2) @@ -328,7 +337,8 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { handler = newContractStakingEventHandler(indexer.cache) transfer(r, handler, newOwner, int64(bucket.Index)) r.NoError(indexer.commit(handler, height)) - bucket, ok = indexer.Bucket(bucket.Index) + bucket, ok, err = indexer.Bucket(bucket.Index, height) + r.NoError(err) r.True(ok) r.EqualValues(newOwner, bucket.Owner) @@ -337,7 +347,8 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { handler = newContractStakingEventHandler(indexer.cache) unlock(r, handler, int64(bucket.Index), height) r.NoError(indexer.commit(handler, height)) - bucket, ok = indexer.Bucket(bucket.Index) + bucket, ok, err = indexer.Bucket(bucket.Index, height) + r.NoError(err) r.True(ok) r.EqualValues(1, bucket.Index) r.EqualValues(newOwner, bucket.Owner) @@ -349,15 +360,20 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { r.EqualValues(createHeight, bucket.CreateBlockHeight) r.EqualValues(maxBlockNumber, bucket.UnstakeStartBlockHeight) r.EqualValues(_testStakingContractAddress, bucket.ContractAddress) - r.EqualValues(10, indexer.CandidateVotes(delegate).Uint64()) - r.EqualValues(1, indexer.TotalBucketCount()) + votes, err = indexer.CandidateVotes(delegate, height) + r.NoError(err) + r.EqualValues(10, votes.Uint64()) + tbc, err = indexer.TotalBucketCount(height) + r.NoError(err) + r.EqualValues(1, tbc) // lock again height++ handler = newContractStakingEventHandler(indexer.cache) lock(r, handler, int64(bucket.Index), int64(10)) r.NoError(indexer.commit(handler, height)) - bucket, ok = indexer.Bucket(bucket.Index) + bucket, ok, err = indexer.Bucket(bucket.Index, height) + r.NoError(err) r.True(ok) r.EqualValues(1, bucket.Index) r.EqualValues(newOwner, bucket.Owner) @@ -369,8 +385,12 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { r.EqualValues(createHeight, bucket.CreateBlockHeight) r.EqualValues(maxBlockNumber, bucket.UnstakeStartBlockHeight) r.EqualValues(_testStakingContractAddress, bucket.ContractAddress) - r.EqualValues(10, indexer.CandidateVotes(delegate).Uint64()) - r.EqualValues(1, indexer.TotalBucketCount()) + votes, err = indexer.CandidateVotes(delegate, height) + r.NoError(err) + r.EqualValues(10, votes.Uint64()) + tbc, err = indexer.TotalBucketCount(height) + r.NoError(err) + r.EqualValues(1, tbc) // unstake height++ @@ -378,7 +398,8 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { unlock(r, handler, int64(bucket.Index), height) unstake(r, handler, int64(bucket.Index), height) r.NoError(indexer.commit(handler, height)) - bucket, ok = indexer.Bucket(bucket.Index) + bucket, ok, err = indexer.Bucket(bucket.Index, height) + r.NoError(err) r.True(ok) r.EqualValues(1, bucket.Index) r.EqualValues(newOwner, bucket.Owner) @@ -390,18 +411,27 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { r.EqualValues(createHeight, bucket.CreateBlockHeight) r.EqualValues(height, bucket.UnstakeStartBlockHeight) r.EqualValues(_testStakingContractAddress, bucket.ContractAddress) - r.EqualValues(0, indexer.CandidateVotes(delegate).Uint64()) - r.EqualValues(1, indexer.TotalBucketCount()) + votes, err = indexer.CandidateVotes(delegate, height) + r.NoError(err) + r.EqualValues(0, votes.Uint64()) + tbc, err = indexer.TotalBucketCount(height) + r.NoError(err) + r.EqualValues(1, tbc) // withdraw height++ handler = newContractStakingEventHandler(indexer.cache) withdraw(r, handler, int64(bucket.Index)) r.NoError(indexer.commit(handler, height)) - bucket, ok = indexer.Bucket(bucket.Index) + bucket, ok, err = indexer.Bucket(bucket.Index, height) + r.NoError(err) r.False(ok) - r.EqualValues(0, indexer.CandidateVotes(delegate).Uint64()) - r.EqualValues(1, indexer.TotalBucketCount()) + votes, err = indexer.CandidateVotes(delegate, height) + r.NoError(err) + r.EqualValues(0, votes.Uint64()) + tbc, err = indexer.TotalBucketCount(height) + r.NoError(err) + r.EqualValues(1, tbc) } func TestContractStakingIndexerChangeBucketType(t *testing.T) { @@ -439,12 +469,14 @@ func TestContractStakingIndexerChangeBucketType(t *testing.T) { stake(r, handler, owner, delegate, 1, 10, 100, height) r.NoError(err) r.NoError(indexer.commit(handler, height)) - bucket, ok := indexer.Bucket(1) + bucket, ok, err := indexer.Bucket(1, height) + r.NoError(err) r.True(ok) expandBucketType(r, handler, int64(bucket.Index), 20, 100) r.NoError(indexer.commit(handler, height)) - bucket, ok = indexer.Bucket(bucket.Index) + bucket, ok, err = indexer.Bucket(bucket.Index, height) + r.NoError(err) r.True(ok) r.EqualValues(20, bucket.StakedAmount.Int64()) r.EqualValues(100, bucket.StakedDurationBlockNumber) @@ -499,7 +531,7 @@ func TestContractStakingIndexerReadBuckets(t *testing.T) { r.NoError(indexer.commit(handler, height)) t.Run("Buckets", func(t *testing.T) { - buckets, err := indexer.Buckets() + buckets, err := indexer.Buckets(height) r.NoError(err) r.Len(buckets, len(stakeData)) }) @@ -510,7 +542,7 @@ func TestContractStakingIndexerReadBuckets(t *testing.T) { candidateMap[stakeData[i].delegate]++ } for cand := range candidateMap { - buckets, err := indexer.BucketsByCandidate(identityset.Address(cand)) + buckets, err := indexer.BucketsByCandidate(identityset.Address(cand), height) r.NoError(err) r.Len(buckets, candidateMap[cand]) } @@ -518,7 +550,7 @@ func TestContractStakingIndexerReadBuckets(t *testing.T) { t.Run("BucketsByIndices", func(t *testing.T) { indices := []uint64{0, 1, 2, 3, 4, 5, 6} - buckets, err := indexer.BucketsByIndices(indices) + buckets, err := indexer.BucketsByIndices(indices, height) r.NoError(err) expectedLen := 0 for _, idx := range indices { @@ -530,7 +562,9 @@ func TestContractStakingIndexerReadBuckets(t *testing.T) { }) t.Run("TotalBucketCount", func(t *testing.T) { - r.EqualValues(len(stakeData), indexer.TotalBucketCount()) + tbc, err := indexer.TotalBucketCount(height) + r.NoError(err) + r.EqualValues(len(stakeData), tbc) }) t.Run("CandidateVotes", func(t *testing.T) { @@ -541,7 +575,9 @@ func TestContractStakingIndexerReadBuckets(t *testing.T) { candidates := []int{1, 2, 3} for _, cand := range candidates { votes := candidateMap[cand] - r.EqualValues(votes, indexer.CandidateVotes(identityset.Address(cand)).Uint64()) + cvotes, err := indexer.CandidateVotes(identityset.Address(cand), height) + r.NoError(err) + r.EqualValues(votes, cvotes.Uint64()) } }) } @@ -571,27 +607,39 @@ func TestContractStakingIndexerCacheClean(t *testing.T) { stake(r, handler, owner, delegate1, 2, 20, 20, height) stake(r, handler, owner, delegate2, 3, 20, 20, height) stake(r, handler, owner, delegate2, 4, 20, 20, height) - r.Len(indexer.cache.ActiveBucketTypes(), 0) - r.Len(indexer.cache.Buckets(), 0) + abt, err := indexer.cache.ActiveBucketTypes(height - 1) + r.NoError(err) + r.Len(abt, 0) + bts, err := indexer.cache.Buckets(height - 1) + r.NoError(err) + r.Len(bts, 0) r.NoError(indexer.commit(handler, height)) - r.Len(indexer.cache.ActiveBucketTypes(), 2) - r.Len(indexer.cache.Buckets(), 4) + abt, err = indexer.cache.ActiveBucketTypes(height) + r.NoError(err) + r.Len(abt, 2) + bts, err = indexer.cache.Buckets(height) + r.NoError(err) + r.Len(bts, 4) height++ handler = newContractStakingEventHandler(indexer.cache) changeDelegate(r, handler, delegate1, 3) transfer(r, handler, delegate1, 1) - bt, ok := indexer.Bucket(3) + bt, ok, err := indexer.Bucket(3, height-1) + r.NoError(err) r.True(ok) r.Equal(delegate2.String(), bt.Candidate.String()) - bt, ok = indexer.Bucket(1) + bt, ok, err = indexer.Bucket(1, height-1) + r.NoError(err) r.True(ok) r.Equal(owner.String(), bt.Owner.String()) r.NoError(indexer.commit(handler, height)) - bt, ok = indexer.Bucket(3) + bt, ok, err = indexer.Bucket(3, height) + r.NoError(err) r.True(ok) r.Equal(delegate1.String(), bt.Candidate.String()) - bt, ok = indexer.Bucket(1) + bt, ok, err = indexer.Bucket(1, height) + r.NoError(err) r.True(ok) r.Equal(delegate1.String(), bt.Owner.String()) } @@ -624,17 +672,26 @@ 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)) - r.EqualValues(30, indexer.CandidateVotes(delegate1).Uint64()) - r.EqualValues(40, indexer.CandidateVotes(delegate2).Uint64()) - r.EqualValues(0, indexer.CandidateVotes(owner).Uint64()) + votes, err := indexer.CandidateVotes(delegate1, height) + r.NoError(err) + r.EqualValues(30, votes.Uint64()) + votes, err = indexer.CandidateVotes(delegate2, height) + r.NoError(err) + r.EqualValues(40, votes.Uint64()) + votes, err = indexer.CandidateVotes(owner, height) + r.EqualValues(0, votes.Uint64()) // change delegate bucket 3 to delegate1 height++ handler = newContractStakingEventHandler(indexer.cache) changeDelegate(r, handler, delegate1, 3) r.NoError(indexer.commit(handler, height)) - r.EqualValues(50, indexer.CandidateVotes(delegate1).Uint64()) - r.EqualValues(20, indexer.CandidateVotes(delegate2).Uint64()) + votes, err = indexer.CandidateVotes(delegate1, height) + r.NoError(err) + r.EqualValues(50, votes.Uint64()) + votes, err = indexer.CandidateVotes(delegate2, height) + r.NoError(err) + r.EqualValues(20, votes.Uint64()) // unlock bucket 1 & 4 height++ @@ -642,8 +699,12 @@ func TestContractStakingIndexerVotes(t *testing.T) { unlock(r, handler, 1, height) unlock(r, handler, 4, height) r.NoError(indexer.commit(handler, height)) - r.EqualValues(50, indexer.CandidateVotes(delegate1).Uint64()) - r.EqualValues(20, indexer.CandidateVotes(delegate2).Uint64()) + votes, err = indexer.CandidateVotes(delegate1, height) + r.NoError(err) + r.EqualValues(50, votes.Uint64()) + votes, err = indexer.CandidateVotes(delegate2, height) + r.NoError(err) + r.EqualValues(20, votes.Uint64()) // unstake bucket 1 & lock 4 height++ @@ -651,24 +712,36 @@ func TestContractStakingIndexerVotes(t *testing.T) { unstake(r, handler, 1, height) lock(r, handler, 4, 20) r.NoError(indexer.commit(handler, height)) - r.EqualValues(40, indexer.CandidateVotes(delegate1).Uint64()) - r.EqualValues(20, indexer.CandidateVotes(delegate2).Uint64()) + votes, err = indexer.CandidateVotes(delegate1, height) + r.NoError(err) + r.EqualValues(40, votes.Uint64()) + votes, err = indexer.CandidateVotes(delegate2, height) + r.NoError(err) + r.EqualValues(20, votes.Uint64()) // expand bucket 2 height++ handler = newContractStakingEventHandler(indexer.cache) expandBucketType(r, handler, 2, 30, 20) r.NoError(indexer.commit(handler, height)) - r.EqualValues(50, indexer.CandidateVotes(delegate1).Uint64()) - r.EqualValues(20, indexer.CandidateVotes(delegate2).Uint64()) + votes, err = indexer.CandidateVotes(delegate1, height) + r.NoError(err) + r.EqualValues(50, votes.Uint64()) + votes, err = indexer.CandidateVotes(delegate2, height) + r.NoError(err) + r.EqualValues(20, votes.Uint64()) // transfer bucket 4 height++ handler = newContractStakingEventHandler(indexer.cache) transfer(r, handler, delegate2, 4) r.NoError(indexer.commit(handler, height)) - r.EqualValues(50, indexer.CandidateVotes(delegate1).Uint64()) - r.EqualValues(20, indexer.CandidateVotes(delegate2).Uint64()) + votes, err = indexer.CandidateVotes(delegate1, height) + r.NoError(err) + r.EqualValues(50, votes.Uint64()) + votes, err = indexer.CandidateVotes(delegate2, height) + r.NoError(err) + r.EqualValues(20, votes.Uint64()) // create bucket 5, 6, 7 height++ @@ -677,16 +750,24 @@ 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)) - r.EqualValues(50, indexer.CandidateVotes(delegate1).Uint64()) - r.EqualValues(80, indexer.CandidateVotes(delegate2).Uint64()) + votes, err = indexer.CandidateVotes(delegate1, height) + r.NoError(err) + r.EqualValues(50, votes.Uint64()) + votes, err = indexer.CandidateVotes(delegate2, height) + r.NoError(err) + r.EqualValues(80, votes.Uint64()) // merge bucket 5, 6, 7 height++ handler = newContractStakingEventHandler(indexer.cache) mergeBuckets(r, handler, []int64{5, 6, 7}, 60, 20) r.NoError(indexer.commit(handler, height)) - r.EqualValues(50, indexer.CandidateVotes(delegate1).Uint64()) - r.EqualValues(80, indexer.CandidateVotes(delegate2).Uint64()) + votes, err = indexer.CandidateVotes(delegate1, height) + r.NoError(err) + r.EqualValues(50, votes.Uint64()) + votes, err = indexer.CandidateVotes(delegate2, height) + r.NoError(err) + r.EqualValues(80, votes.Uint64()) // unlock & unstake 5 height++ @@ -694,8 +775,12 @@ func TestContractStakingIndexerVotes(t *testing.T) { unlock(r, handler, 5, height) unstake(r, handler, 5, height) r.NoError(indexer.commit(handler, height)) - r.EqualValues(50, indexer.CandidateVotes(delegate1).Uint64()) - r.EqualValues(20, indexer.CandidateVotes(delegate2).Uint64()) + votes, err = indexer.CandidateVotes(delegate1, height) + r.NoError(err) + r.EqualValues(50, votes.Uint64()) + votes, err = indexer.CandidateVotes(delegate2, height) + r.NoError(err) + r.EqualValues(20, votes.Uint64()) // create & merge bucket 8, 9, 10 height++ @@ -705,8 +790,12 @@ 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)) - r.EqualValues(110, indexer.CandidateVotes(delegate1).Uint64()) - r.EqualValues(20, indexer.CandidateVotes(delegate2).Uint64()) + votes, err = indexer.CandidateVotes(delegate1, height) + r.NoError(err) + r.EqualValues(110, votes.Uint64()) + votes, err = indexer.CandidateVotes(delegate2, height) + r.NoError(err) + r.EqualValues(20, votes.Uint64()) t.Run("Height", func(t *testing.T) { h, err := indexer.Height() @@ -715,7 +804,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { }) t.Run("BucketTypes", func(t *testing.T) { - bts, err := indexer.BucketTypes() + bts, err := indexer.BucketTypes(height) r.NoError(err) r.Len(bts, 4) slices.SortFunc(bts, func(i, j *staking.ContractStakingBucketType) bool { @@ -732,7 +821,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { }) t.Run("Buckets", func(t *testing.T) { - bts, err := indexer.Buckets() + bts, err := indexer.Buckets(height) r.NoError(err) r.Len(bts, 6) slices.SortFunc(bts, func(i, j *staking.VoteBucket) bool { @@ -802,7 +891,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { }) t.Run("BucketsByCandidate", func(t *testing.T) { - d1Bts, err := indexer.BucketsByCandidate(delegate1) + d1Bts, err := indexer.BucketsByCandidate(delegate1, height) r.NoError(err) r.Len(d1Bts, 4) slices.SortFunc(d1Bts, func(i, j *staking.VoteBucket) bool { @@ -812,7 +901,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { r.EqualValues(2, d1Bts[1].Index) r.EqualValues(3, d1Bts[2].Index) r.EqualValues(8, d1Bts[3].Index) - d2Bts, err := indexer.BucketsByCandidate(delegate2) + d2Bts, err := indexer.BucketsByCandidate(delegate2, height) r.NoError(err) r.Len(d2Bts, 2) slices.SortFunc(d2Bts, func(i, j *staking.VoteBucket) bool { @@ -823,7 +912,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { }) t.Run("BucketsByIndices", func(t *testing.T) { - bts, err := indexer.BucketsByIndices([]uint64{1, 2, 3, 4, 5, 8}) + bts, err := indexer.BucketsByIndices([]uint64{1, 2, 3, 4, 5, 8}, height) r.NoError(err) r.Len(bts, 6) }) diff --git a/e2etest/contract_staking_test.go b/e2etest/contract_staking_test.go index 1cb074c7f1..9d4b20ec70 100644 --- a/e2etest/contract_staking_test.go +++ b/e2etest/contract_staking_test.go @@ -1342,7 +1342,7 @@ func TestContractStaking(t *testing.T) { receipts, blk := writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) r.Len(receipts, 1) r.EqualValues(iotextypes.ReceiptStatus_Success, receipts[0].Status) - buckets, err := indexer.Buckets() + buckets, err := indexer.Buckets(blk.Height()) r.NoError(err) slices.SortFunc(buckets, func(i, j *contractstaking.Bucket) bool { return i.Index < j.Index @@ -1358,10 +1358,14 @@ func TestContractStaking(t *testing.T) { r.EqualValues(blk.Height(), bt.CreateBlockHeight) r.EqualValues(blk.Height(), bt.StakeStartBlockHeight) r.True(bt.UnstakeStartBlockHeight == math.MaxUint64) - r.EqualValues(10, indexer.CandidateVotes(identityset.Address(delegateIdx)).Int64()) - r.EqualValues(1, indexer.TotalBucketCount()) + votes, err := indexer.CandidateVotes(identityset.Address(delegateIdx), blk.Height()) + r.NoError(err) + r.EqualValues(10, votes.Int64()) + tbc, err := indexer.TotalBucketCount(blk.Height()) + r.NoError(err) + r.EqualValues(1, tbc) r.EqualValues(contractAddresses, bt.ContractAddress) - buckets, err = indexer.BucketsByCandidate(identityset.Address(delegateIdx)) + buckets, err = indexer.BucketsByCandidate(identityset.Address(delegateIdx), blk.Height()) r.NoError(err) r.Len(buckets, 1) r.EqualValues(bt, buckets[0]) @@ -1381,11 +1385,15 @@ func TestContractStaking(t *testing.T) { r.Len(receipts, 1) r.EqualValues("", receipts[0].ExecutionRevertMsg()) r.EqualValues(iotextypes.ReceiptStatus_Success, receipts[0].Status) - bt, ok := indexer.Bucket(uint64(tokenID)) + bt, ok, err := indexer.Bucket(uint64(tokenID), blk.Height()) + r.NoError(err) r.True(ok) r.EqualValues(blk.Height(), bt.StakeStartBlockHeight) - r.EqualValues(10, indexer.CandidateVotes(identityset.Address(delegateIdx)).Int64()) - r.EqualValues(1, indexer.TotalBucketCount()) + votes, err := indexer.CandidateVotes(identityset.Address(delegateIdx), blk.Height()) + r.NoError(err) + r.EqualValues(10, votes.Int64()) + tbc, err := indexer.TotalBucketCount(blk.Height()) + r.EqualValues(1, tbc) t.Run("unstake", func(t *testing.T) { jumpBlocks(bc, 10, r) @@ -1403,11 +1411,16 @@ func TestContractStaking(t *testing.T) { r.Len(receipts, 1) r.EqualValues("", receipts[0].ExecutionRevertMsg()) r.EqualValues(iotextypes.ReceiptStatus_Success, receipts[0].Status) - bt, ok := indexer.Bucket(uint64(tokenID)) + bt, ok, err := indexer.Bucket(uint64(tokenID), blk.Height()) + r.NoError(err) r.True(ok) r.EqualValues(blk.Height(), bt.UnstakeStartBlockHeight) - r.EqualValues(0, indexer.CandidateVotes(identityset.Address(delegateIdx)).Int64()) - r.EqualValues(1, indexer.TotalBucketCount()) + votes, err := indexer.CandidateVotes(identityset.Address(delegateIdx), blk.Height()) + r.NoError(err) + r.EqualValues(0, votes.Int64()) + tbc, err := indexer.TotalBucketCount(blk.Height()) + r.NoError(err) + r.EqualValues(1, tbc) t.Run("withdraw", func(t *testing.T) { // freeze blocks are changed to 10 in test @@ -1425,13 +1438,16 @@ func TestContractStaking(t *testing.T) { gasPrice: big.NewInt(0), sk: identityset.PrivateKey(adminID), } - receipts, _ = writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) + receipts, blk = writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) r.Len(receipts, 1) r.EqualValues("", receipts[0].ExecutionRevertMsg()) r.EqualValues(iotextypes.ReceiptStatus_Success, receipts[0].Status) - bt, ok = indexer.Bucket(uint64(tokenID)) + bt, ok, err = indexer.Bucket(uint64(tokenID), blk.Height()) + r.NoError(err) r.False(ok) - r.EqualValues(1, indexer.TotalBucketCount()) + tbc, err := indexer.TotalBucketCount(blk.Height()) + r.NoError(err) + r.EqualValues(1, tbc) t.Run("cannot withdraw again", func(t *testing.T) { data, err := lsdABI.Pack("withdraw", big.NewInt(int64(tokenID)), addr) @@ -1518,11 +1534,12 @@ func TestContractStaking(t *testing.T) { gasPrice: big.NewInt(0), sk: identityset.PrivateKey(adminID), } - receipts, _ := writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) + receipts, blk := writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) r.Len(receipts, 1) r.EqualValues("", receipts[0].ExecutionRevertMsg()) r.EqualValues(iotextypes.ReceiptStatus_Success, receipts[0].Status) - bt, ok := indexer.Bucket(uint64(tokenID)) + bt, ok, err := indexer.Bucket(uint64(tokenID), blk.Height()) + r.NoError(err) r.True(ok) r.True(bt.AutoStake) }) @@ -1545,12 +1562,12 @@ func TestContractStaking(t *testing.T) { } params = append(params, ¶m) } - receipts, _ := writeContract(bc, sf, dao, ap, params, r) + receipts, blk := writeContract(bc, sf, dao, ap, params, r) r.Len(receipts, len(params)) for _, receipt := range receipts { r.EqualValues(iotextypes.ReceiptStatus_Success, receipt.Status) } - buckets, err := indexer.Buckets() + buckets, err := indexer.Buckets(blk.Height()) r.NoError(err) slices.SortFunc(buckets, func(i, j *contractstaking.Bucket) bool { return i.Index < j.Index @@ -1572,17 +1589,19 @@ func TestContractStaking(t *testing.T) { gasPrice: big.NewInt(0), sk: identityset.PrivateKey(adminID), } - receipts, _ = writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) + receipts, blk = writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) r.Len(receipts, 1) r.EqualValues("", receipts[0].ExecutionRevertMsg()) r.EqualValues(iotextypes.ReceiptStatus_Success, receipts[0].Status) for i := range newBuckets { if i == 0 { - bt, ok := indexer.Bucket(uint64(newBuckets[i].Index)) + bt, ok, err := indexer.Bucket(uint64(newBuckets[i].Index), blk.Height()) + r.NoError(err) r.True(ok) r.EqualValues(100, bt.StakedDurationBlockNumber) } else { - _, ok := indexer.Bucket(uint64(newBuckets[i].Index)) + _, ok, err := indexer.Bucket(uint64(newBuckets[i].Index), blk.Height()) + r.NoError(err) r.False(ok) } } @@ -1622,10 +1641,11 @@ func TestContractStaking(t *testing.T) { gasPrice: big.NewInt(0), sk: identityset.PrivateKey(adminID), } - receipts, _ = writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) + receipts, blk := writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) r.Len(receipts, 1) r.EqualValues(iotextypes.ReceiptStatus_Success, receipts[0].Status) - bt, ok := indexer.Bucket(uint64(tokenID)) + bt, ok, err := indexer.Bucket(uint64(tokenID), blk.Height()) + r.NoError(err) r.True(ok) r.EqualValues(100, bt.StakedDurationBlockNumber) }) @@ -1644,11 +1664,12 @@ func TestContractStaking(t *testing.T) { gasPrice: big.NewInt(0), sk: identityset.PrivateKey(adminID), } - receipts, _ = writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) + receipts, blk := writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) r.Len(receipts, 1) r.EqualValues("", receipts[0].ExecutionRevertMsg()) r.EqualValues(iotextypes.ReceiptStatus_Success, receipts[0].Status) - bt, ok := indexer.Bucket(uint64(tokenID)) + bt, ok, err := indexer.Bucket(uint64(tokenID), blk.Height()) + r.NoError(err) r.True(ok) r.EqualValues(100, bt.StakedAmount.Int64()) }) @@ -1725,11 +1746,12 @@ func TestContractStaking(t *testing.T) { gasPrice: big.NewInt(0), sk: identityset.PrivateKey(adminID), } - receipts, _ = writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) + receipts, blk := writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) r.Len(receipts, 1) r.EqualValues("", receipts[0].ExecutionRevertMsg()) r.EqualValues(iotextypes.ReceiptStatus_Success, receipts[0].Status) - bt, ok := indexer.Bucket(uint64(tokenID)) + bt, ok, err := indexer.Bucket(uint64(tokenID), blk.Height()) + r.NoError(err) r.True(ok) r.EqualValues(100, bt.StakedAmount.Int64()) r.EqualValues(100, bt.StakedDurationBlockNumber) @@ -1748,11 +1770,12 @@ func TestContractStaking(t *testing.T) { gasPrice: big.NewInt(0), sk: identityset.PrivateKey(adminID), } - receipts, _ = writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) + receipts, blk := writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) r.Len(receipts, 1) r.EqualValues("", receipts[0].ExecutionRevertMsg()) r.EqualValues(iotextypes.ReceiptStatus_Success, receipts[0].Status) - bt, ok := indexer.Bucket(uint64(tokenID)) + bt, ok, err := indexer.Bucket(uint64(tokenID), blk.Height()) + r.NoError(err) r.True(ok) r.EqualValues(10, bt.StakedAmount.Int64()) r.EqualValues(10, bt.StakedDurationBlockNumber) @@ -1777,11 +1800,12 @@ func TestContractStaking(t *testing.T) { gasPrice: big.NewInt(0), sk: identityset.PrivateKey(adminID), } - receipts, _ = writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) + receipts, blk := writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) r.Len(receipts, 1) r.EqualValues("", receipts[0].ExecutionRevertMsg()) r.EqualValues(iotextypes.ReceiptStatus_Success, receipts[0].Status) - bt, ok := indexer.Bucket(uint64(tokenID)) + bt, ok, err := indexer.Bucket(uint64(tokenID), blk.Height()) + r.NoError(err) r.True(ok) r.EqualValues(identityset.Address(delegateIdx).String(), bt.Candidate.String()) }) @@ -1805,11 +1829,12 @@ func TestContractStaking(t *testing.T) { gasPrice: big.NewInt(0), sk: identityset.PrivateKey(adminID), } - receipts, _ = writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) + receipts, blk := writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) r.Len(receipts, 1) r.EqualValues("", receipts[0].ExecutionRevertMsg()) r.EqualValues(iotextypes.ReceiptStatus_Success, receipts[0].Status) - bt, ok := indexer.Bucket(uint64(tokenID)) + bt, ok, err := indexer.Bucket(uint64(tokenID), blk.Height()) + r.NoError(err) r.True(ok) r.EqualValues(identityset.Address(newOwnerIdx).String(), bt.Owner.String()) }) @@ -1832,11 +1857,12 @@ func TestContractStaking(t *testing.T) { gasPrice: big.NewInt(0), sk: identityset.PrivateKey(adminID), } - receipts, _ = writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) + receipts, blk := writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) r.Len(receipts, 1) r.EqualValues("", receipts[0].ExecutionRevertMsg()) r.EqualValues(iotextypes.ReceiptStatus_Success, receipts[0].Status) - bt, ok := indexer.Bucket(uint64(tokenID)) + bt, ok, err := indexer.Bucket(uint64(tokenID), blk.Height()) + r.NoError(err) r.True(ok) r.EqualValues(identityset.Address(newOwnerIdx).String(), bt.Owner.String()) }) @@ -2070,10 +2096,10 @@ func stake(lsdABI abi.ABI, bc blockchain.Blockchain, sf factory.Factory, dao blo gasPrice: big.NewInt(0), sk: identityset.PrivateKey(_adminID), } - receipts, _ := writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) + receipts, blk := writeContract(bc, sf, dao, ap, []*callParam{¶m}, r) r.Len(receipts, 1) r.EqualValues(iotextypes.ReceiptStatus_Success, receipts[0].Status) - buckets, err := indexer.Buckets() + buckets, err := indexer.Buckets(blk.Height()) r.NoError(err) slices.SortFunc(buckets, func(i, j *contractstaking.Bucket) bool { return i.Index < j.Index From 0f88d0ed5cbc823a63f995c1337060b56e2badc1 Mon Sep 17 00:00:00 2001 From: envestcc Date: Wed, 23 Aug 2023 22:38:46 +0800 Subject: [PATCH 3/6] address comments --- action/protocol/staking/protocol.go | 9 ++- action/protocol/staking/protocol_test.go | 69 ++++++++++++++++++++++ blockindex/contractstaking/cache.go | 7 ++- blockindex/contractstaking/dirty_cache.go | 6 +- blockindex/contractstaking/indexer_test.go | 39 ++++++++++++ 5 files changed, 125 insertions(+), 5 deletions(-) diff --git a/action/protocol/staking/protocol.go b/action/protocol/staking/protocol.go index 315f1b334c..1d11941607 100644 --- a/action/protocol/staking/protocol.go +++ b/action/protocol/staking/protocol.go @@ -467,6 +467,10 @@ func (p *Protocol) Validate(ctx context.Context, act action.Action, sr protocol. // ActiveCandidates returns all active candidates in candidate center func (p *Protocol) ActiveCandidates(ctx context.Context, sr protocol.StateReader, height uint64) (state.CandidateList, error) { + height, err := sr.Height() + if err != nil { + return nil, errors.Wrap(err, "failed to get StateReader height") + } c, err := ConstructBaseView(sr) if err != nil { return nil, errors.Wrap(err, "failed to get ActiveCandidates") @@ -476,7 +480,10 @@ func (p *Protocol) ActiveCandidates(ctx context.Context, sr protocol.StateReader featureCtx := protocol.MustGetFeatureCtx(ctx) for i := range list { if p.contractStakingIndexer != nil && featureCtx.AddContractStakingVotes { - contractVotes, err := p.contractStakingIndexer.CandidateVotes(list[i].Owner, height) + // 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, height-1) if err != nil { return nil, errors.Wrap(err, "failed to get CandidateVotes from contractStakingIndexer") } diff --git a/action/protocol/staking/protocol_test.go b/action/protocol/staking/protocol_test.go index 168634da0a..86ba704151 100644 --- a/action/protocol/staking/protocol_test.go +++ b/action/protocol/staking/protocol_test.go @@ -394,3 +394,72 @@ func Test_CreateGenesisStates(t *testing.T) { } } } + +func TestProtocol_ActiveCandidates(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + sm := testdb.NewMockStateManagerWithoutHeightFunc(ctrl) + csIndexer := NewMockContractStakingIndexer(ctrl) + + selfStake, _ := new(big.Int).SetString("1200000000000000000000000", 10) + cfg := genesis.Default.Staking + cfg.BootstrapCandidates = []genesis.BootstrapCandidate{ + { + OwnerAddress: identityset.Address(22).String(), + OperatorAddress: identityset.Address(23).String(), + RewardAddress: identityset.Address(23).String(), + Name: "test1", + SelfStakingTokens: selfStake.String(), + }, + } + p, err := NewProtocol(nil, &BuilderConfig{ + Staking: cfg, + PersistStakingPatchBlock: math.MaxUint64, + }, nil, csIndexer, genesis.Default.GreenlandBlockHeight) + require.NoError(err) + + blkHeight := genesis.Default.QuebecBlockHeight + 1 + ctx := protocol.WithBlockCtx( + genesis.WithGenesisContext(context.Background(), genesis.Default), + protocol.BlockCtx{ + BlockHeight: blkHeight, + }, + ) + ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) + sm.EXPECT().Height().Return(blkHeight, nil).AnyTimes() + + v, err := p.Start(ctx, sm) + require.NoError(err) + require.NoError(sm.WriteView(_protocolID, v)) + + err = p.CreateGenesisStates(ctx, sm) + require.NoError(err) + + var csIndexerHeight, csVotes uint64 + csIndexer.EXPECT().CandidateVotes(gomock.Any(), gomock.Any()).DoAndReturn(func(ownerAddr address.Address, height uint64) (*big.Int, error) { + if height != csIndexerHeight { + return nil, errors.Errorf("invalid height") + } + return big.NewInt(int64(csVotes)), nil + }).AnyTimes() + + t.Run("contract staking indexer falls behind", func(t *testing.T) { + csIndexerHeight = 10 + _, err := p.ActiveCandidates(ctx, sm, 0) + require.ErrorContains(err, "invalid height") + }) + + t.Run("contract staking indexer up to date", func(t *testing.T) { + csIndexerHeight = blkHeight - 1 + csVotes = 0 + cands, err := p.ActiveCandidates(ctx, sm, 0) + require.NoError(err) + require.Len(cands, 1) + originCandVotes := cands[0].Votes + csVotes = 100 + cands, err = p.ActiveCandidates(ctx, sm, 0) + require.NoError(err) + require.Len(cands, 1) + require.EqualValues(100, cands[0].Votes.Sub(cands[0].Votes, originCandVotes).Uint64()) + }) +} diff --git a/blockindex/contractstaking/cache.go b/blockindex/contractstaking/cache.go index 124862cccb..d7fe07d975 100644 --- a/blockindex/contractstaking/cache.go +++ b/blockindex/contractstaking/cache.go @@ -455,8 +455,11 @@ func (s *contractStakingCache) validateHeight(height uint64) error { if height == 0 { return nil } - // indexer only store latest height - if height != s.height { + // Currently, historical block data query is not supported. + // However, the latest data is actually returned when querying historical block data, for the following reasons: + // 1. to maintain compatibility with the current code's invocation of ActiveCandidate + // 2. to cause consensus errors when the indexer is lagging behind + if height > s.height { return errors.Wrapf(ErrInvalidHeight, "expected %d, actual %d", s.height, height) } return nil diff --git a/blockindex/contractstaking/dirty_cache.go b/blockindex/contractstaking/dirty_cache.go index d5f0b9db03..5dbf0796f9 100644 --- a/blockindex/contractstaking/dirty_cache.go +++ b/blockindex/contractstaking/dirty_cache.go @@ -105,7 +105,8 @@ func (dirty *contractStakingDirty) finalize() (batch.KVStoreBatch, *contractStak func (dirty *contractStakingDirty) finalizeBatch() batch.KVStoreBatch { dirty.once.Do(func() { - total := dirty.clean.totalBucketCount + dirty.delta.AddedBucketCnt() + tbc, _ := dirty.clean.TotalBucketCount(0) + total := tbc + dirty.delta.AddedBucketCnt() dirty.batch.Put(_StakingNS, _stakingTotalBucketCountKey, byteutil.Uint64ToBytesBigEndian(total), "failed to put total bucket count") }) return dirty.batch @@ -125,7 +126,8 @@ func (dirty *contractStakingDirty) matchBucketType(amount *big.Int, duration uin } func (dirty *contractStakingDirty) getBucketTypeCount() uint64 { - return uint64(len(dirty.clean.bucketTypeMap)) + dirty.delta.AddedBucketTypeCnt() + btc, _ := dirty.clean.BucketTypeCount(0) + return uint64(btc) + dirty.delta.AddedBucketTypeCnt() } func (dirty *contractStakingDirty) updateBucketType(id uint64, bt *BucketType) error { diff --git a/blockindex/contractstaking/indexer_test.go b/blockindex/contractstaking/indexer_test.go index 3098abd514..4ffe29e910 100644 --- a/blockindex/contractstaking/indexer_test.go +++ b/blockindex/contractstaking/indexer_test.go @@ -916,6 +916,45 @@ func TestContractStakingIndexerVotes(t *testing.T) { r.NoError(err) r.Len(bts, 6) }) + + t.Run("heightRestriction", func(t *testing.T) { + cases := []struct { + height uint64 + valid bool + }{ + {0, true}, + {height - 1, true}, + {height, true}, + {height + 1, false}, + } + noErr := func(args ...interface{}) { + r.Nil(args[len(args)-1]) + } + hasErr := func(args ...interface{}) { + err := args[len(args)-1].(error) + r.ErrorIs(err, ErrInvalidHeight) + } + for i := range cases { + h := cases[i].height + if cases[i].valid { + noErr(indexer.Buckets(h)) + noErr(indexer.BucketTypes(h)) + noErr(indexer.BucketsByCandidate(delegate1, h)) + noErr(indexer.BucketsByIndices([]uint64{1, 2, 3, 4, 5, 8}, h)) + noErr(indexer.CandidateVotes(delegate1, h)) + noErr(indexer.Bucket(1, h)) + noErr(indexer.TotalBucketCount(h)) + } else { + hasErr(indexer.Buckets(h)) + hasErr(indexer.BucketTypes(h)) + hasErr(indexer.BucketsByCandidate(delegate1, h)) + hasErr(indexer.BucketsByIndices([]uint64{1, 2, 3, 4, 5, 8}, h)) + hasErr(indexer.CandidateVotes(delegate1, h)) + hasErr(indexer.Bucket(1, h)) + hasErr(indexer.TotalBucketCount(h)) + } + } + }) } func TestIndexer_PutBlock(t *testing.T) { From 07e537b6afcdb1cf68a6f036bac3b4a5c0b1d49b Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 24 Aug 2023 10:26:53 +0800 Subject: [PATCH 4/6] address comments --- action/protocol/staking/protocol.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/action/protocol/staking/protocol.go b/action/protocol/staking/protocol.go index 1d11941607..31201f4fb8 100644 --- a/action/protocol/staking/protocol.go +++ b/action/protocol/staking/protocol.go @@ -467,7 +467,7 @@ func (p *Protocol) Validate(ctx context.Context, act action.Action, sr protocol. // ActiveCandidates returns all active candidates in candidate center func (p *Protocol) ActiveCandidates(ctx context.Context, sr protocol.StateReader, height uint64) (state.CandidateList, error) { - height, err := sr.Height() + srHeight, err := sr.Height() if err != nil { return nil, errors.Wrap(err, "failed to get StateReader height") } @@ -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, height-1) + contractVotes, err := p.contractStakingIndexer.CandidateVotes(list[i].Owner, srHeight-1) if err != nil { return nil, errors.Wrap(err, "failed to get CandidateVotes from contractStakingIndexer") } From f10a94889dd762135aecbe2dbbc8335cab171f2f Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 24 Aug 2023 12:34:12 +0800 Subject: [PATCH 5/6] address comments --- action/protocol/staking/contractstake_indexer.go | 2 -- .../staking/contractstake_indexer_mock.go | 15 --------------- 2 files changed, 17 deletions(-) diff --git a/action/protocol/staking/contractstake_indexer.go b/action/protocol/staking/contractstake_indexer.go index 88f771ed78..12af9256f6 100644 --- a/action/protocol/staking/contractstake_indexer.go +++ b/action/protocol/staking/contractstake_indexer.go @@ -28,7 +28,5 @@ type ( TotalBucketCount(height uint64) (uint64, error) // BucketTypes returns the active bucket types BucketTypes(height uint64) ([]*ContractStakingBucketType, error) - // Height returns the indexer height - Height() (uint64, error) } ) diff --git a/action/protocol/staking/contractstake_indexer_mock.go b/action/protocol/staking/contractstake_indexer_mock.go index 4f930296a5..390fb27268 100644 --- a/action/protocol/staking/contractstake_indexer_mock.go +++ b/action/protocol/staking/contractstake_indexer_mock.go @@ -110,21 +110,6 @@ func (mr *MockContractStakingIndexerMockRecorder) CandidateVotes(ownerAddr, heig return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CandidateVotes", reflect.TypeOf((*MockContractStakingIndexer)(nil).CandidateVotes), ownerAddr, height) } -// Height mocks base method. -func (m *MockContractStakingIndexer) Height() (uint64, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Height") - ret0, _ := ret[0].(uint64) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Height indicates an expected call of Height. -func (mr *MockContractStakingIndexerMockRecorder) Height() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Height", reflect.TypeOf((*MockContractStakingIndexer)(nil).Height)) -} - // TotalBucketCount mocks base method. func (m *MockContractStakingIndexer) TotalBucketCount(height uint64) (uint64, error) { m.ctrl.T.Helper() From 4ab8f36d57a82069fb14cf46aa6f238f8574fd2a Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 24 Aug 2023 13:10:21 +0800 Subject: [PATCH 6/6] address comments --- blockindex/contractstaking/indexer_test.go | 49 ++++++++++++---------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/blockindex/contractstaking/indexer_test.go b/blockindex/contractstaking/indexer_test.go index 4ffe29e910..aaebac04cf 100644 --- a/blockindex/contractstaking/indexer_test.go +++ b/blockindex/contractstaking/indexer_test.go @@ -927,31 +927,38 @@ func TestContractStakingIndexerVotes(t *testing.T) { {height, true}, {height + 1, false}, } - noErr := func(args ...interface{}) { - r.Nil(args[len(args)-1]) - } - hasErr := func(args ...interface{}) { - err := args[len(args)-1].(error) - r.ErrorIs(err, ErrInvalidHeight) - } for i := range cases { h := cases[i].height if cases[i].valid { - noErr(indexer.Buckets(h)) - noErr(indexer.BucketTypes(h)) - noErr(indexer.BucketsByCandidate(delegate1, h)) - noErr(indexer.BucketsByIndices([]uint64{1, 2, 3, 4, 5, 8}, h)) - noErr(indexer.CandidateVotes(delegate1, h)) - noErr(indexer.Bucket(1, h)) - noErr(indexer.TotalBucketCount(h)) + _, err = indexer.Buckets(h) + r.NoError(err) + _, err = indexer.BucketTypes(h) + r.NoError(err) + _, err = indexer.BucketsByCandidate(delegate1, h) + r.NoError(err) + _, err = indexer.BucketsByIndices([]uint64{1, 2, 3, 4, 5, 8}, h) + r.NoError(err) + _, err = indexer.CandidateVotes(delegate1, h) + r.NoError(err) + _, _, err = indexer.Bucket(1, h) + r.NoError(err) + _, err = indexer.TotalBucketCount(h) + r.NoError(err) } else { - hasErr(indexer.Buckets(h)) - hasErr(indexer.BucketTypes(h)) - hasErr(indexer.BucketsByCandidate(delegate1, h)) - hasErr(indexer.BucketsByIndices([]uint64{1, 2, 3, 4, 5, 8}, h)) - hasErr(indexer.CandidateVotes(delegate1, h)) - hasErr(indexer.Bucket(1, h)) - hasErr(indexer.TotalBucketCount(h)) + _, err = indexer.Buckets(h) + r.ErrorIs(err, ErrInvalidHeight) + _, err = indexer.BucketTypes(h) + r.ErrorIs(err, ErrInvalidHeight) + _, err = indexer.BucketsByCandidate(delegate1, h) + r.ErrorIs(err, ErrInvalidHeight) + _, err = indexer.BucketsByIndices([]uint64{1, 2, 3, 4, 5, 8}, h) + r.ErrorIs(err, ErrInvalidHeight) + _, err = indexer.CandidateVotes(delegate1, h) + r.ErrorIs(err, ErrInvalidHeight) + _, _, err = indexer.Bucket(1, h) + r.ErrorIs(err, ErrInvalidHeight) + _, err = indexer.TotalBucketCount(h) + r.ErrorIs(err, ErrInvalidHeight) } } })