diff --git a/action/protocol/staking/candidate_statereader.go b/action/protocol/staking/candidate_statereader.go index 79f75a2af9..593499d3ff 100644 --- a/action/protocol/staking/candidate_statereader.go +++ b/action/protocol/staking/candidate_statereader.go @@ -256,6 +256,23 @@ func (c *candSR) getBucketsWithIndices(indices BucketIndices) ([]*VoteBucket, er return buckets, nil } +// getExistingBucketsWithIndices returns buckets with given indices, if some of bucket +// does not exist, they will be ignored and return the rest of buckets +func (c *candSR) getExistingBucketsWithIndices(indices BucketIndices) ([]*VoteBucket, error) { + buckets := make([]*VoteBucket, 0, len(indices)) + for _, i := range indices { + b, err := c.getBucket(i) + if err != nil { + if errors.Is(err, state.ErrStateNotExist) { + continue + } + return buckets, err + } + buckets = append(buckets, b) + } + return buckets, nil +} + func (c *candSR) getBucketIndices(addr address.Address, prefix byte) (*BucketIndices, uint64, error) { var ( bis BucketIndices @@ -408,7 +425,7 @@ func (c *candSR) readStateBucketByIndices(ctx context.Context, req *iotexapi.Rea if err != nil { return &iotextypes.VoteBucketList{}, height, err } - buckets, err := c.getBucketsWithIndices(BucketIndices(req.GetIndex())) + buckets, err := c.getExistingBucketsWithIndices(BucketIndices(req.GetIndex())) if err != nil { return nil, height, err } diff --git a/blockindex/contractstaking/cache.go b/blockindex/contractstaking/cache.go index ae131388a7..6c328a432e 100644 --- a/blockindex/contractstaking/cache.go +++ b/blockindex/contractstaking/cache.go @@ -142,10 +142,9 @@ func (s *contractStakingCache) BucketsByIndices(indices []uint64) ([]*Bucket, er vbs := make([]*Bucket, 0, len(indices)) for _, id := range indices { vb, ok := s.getBucket(id) - if !ok { - return nil, errors.Wrapf(ErrBucketNotExist, "id %d", id) + if ok { + vbs = append(vbs, vb) } - vbs = append(vbs, vb) } return vbs, nil } diff --git a/blockindex/contractstaking/indexer_test.go b/blockindex/contractstaking/indexer_test.go index e580a4d9d1..c8d594871f 100644 --- a/blockindex/contractstaking/indexer_test.go +++ b/blockindex/contractstaking/indexer_test.go @@ -65,6 +65,7 @@ func TestContractStakingIndexerLoadCache(t *testing.T) { // create a stake height := uint64(1) + startHeight := uint64(1) handler := newContractStakingEventHandler(indexer.cache, height) activateBucketType(r, handler, 10, 100, height) owner := identityset.Address(0) @@ -77,7 +78,7 @@ func TestContractStakingIndexerLoadCache(t *testing.T) { r.NoError(indexer.Stop(context.Background())) // load cache from db - newIndexer, err := NewContractStakingIndexer(db.NewBoltDB(cfg), _testStakingContractAddress, 0) + newIndexer, err := NewContractStakingIndexer(db.NewBoltDB(cfg), _testStakingContractAddress, startHeight) r.NoError(err) r.NoError(newIndexer.Start(context.Background())) @@ -91,6 +92,7 @@ func TestContractStakingIndexerLoadCache(t *testing.T) { newHeight, err := newIndexer.Height() r.NoError(err) r.Equal(height, newHeight) + r.Equal(startHeight, newIndexer.StartHeight()) r.EqualValues(1, newIndexer.TotalBucketCount()) r.NoError(newIndexer.Stop(context.Background())) } @@ -429,6 +431,101 @@ func TestContractStakingIndexerChangeBucketType(t *testing.T) { }) } +func TestContractStakingIndexerReadBuckets(t *testing.T) { + r := require.New(t) + testDBPath, err := testutil.PathOfTempFile("staking.db") + r.NoError(err) + defer testutil.CleanupPath(testDBPath) + cfg := db.DefaultConfig + cfg.DbPath = testDBPath + kvStore := db.NewBoltDB(cfg) + indexer, err := NewContractStakingIndexer(kvStore, _testStakingContractAddress, 0) + r.NoError(err) + r.NoError(indexer.Start(context.Background())) + + // init bucket type + bucketTypeData := [][2]int64{ + {10, 10}, + {20, 10}, + {10, 100}, + {20, 100}, + } + height := uint64(1) + handler := newContractStakingEventHandler(indexer.cache, height) + for _, data := range bucketTypeData { + activateBucketType(r, handler, data[0], data[1], height) + } + err = indexer.commit(handler) + r.NoError(err) + + // stake + stakeData := []struct { + owner, delegate int + amount, duration uint64 + }{ + {1, 2, 10, 10}, + {1, 2, 20, 10}, + {1, 2, 10, 100}, + {1, 2, 20, 100}, + {1, 3, 10, 100}, + {1, 3, 20, 100}, + } + height++ + handler = newContractStakingEventHandler(indexer.cache, height) + for i, data := range stakeData { + stake(r, handler, identityset.Address(data.owner), identityset.Address(data.delegate), int64(i), int64(data.amount), int64(data.duration), height) + } + r.NoError(err) + r.NoError(indexer.commit(handler)) + + t.Run("Buckets", func(t *testing.T) { + buckets, err := indexer.Buckets() + r.NoError(err) + r.Len(buckets, len(stakeData)) + }) + + t.Run("BucketsByCandidate", func(t *testing.T) { + candidateMap := make(map[int]int) + for i := range stakeData { + candidateMap[stakeData[i].delegate]++ + } + for cand := range candidateMap { + buckets, err := indexer.BucketsByCandidate(identityset.Address(cand)) + r.NoError(err) + r.Len(buckets, candidateMap[cand]) + } + }) + + t.Run("BucketsByIndices", func(t *testing.T) { + indices := []uint64{0, 1, 2, 3, 4, 5, 6} + buckets, err := indexer.BucketsByIndices(indices) + r.NoError(err) + expectedLen := 0 + for _, idx := range indices { + if int(idx) < len(stakeData) { + expectedLen++ + } + } + r.Len(buckets, expectedLen) + }) + + t.Run("TotalBucketCount", func(t *testing.T) { + r.EqualValues(len(stakeData), indexer.TotalBucketCount()) + }) + + t.Run("CandidateVotes", func(t *testing.T) { + candidateMap := make(map[int]int64) + for i := range stakeData { + candidateMap[stakeData[i].delegate] += int64(stakeData[i].amount) + } + candidates := []int{1, 2, 3} + for _, cand := range candidates { + votes := candidateMap[cand] + r.EqualValues(votes, indexer.CandidateVotes(identityset.Address(cand)).Uint64()) + } + }) +} + func BenchmarkIndexer_PutBlockBeforeContractHeight(b *testing.B) { // Create a new Indexer with a contract height of 100 indexer := &Indexer{contractDeployHeight: 100}