Skip to content

Commit 4a324c8

Browse files
authored
add staking indexer (iotexproject#2319)
* add staking indexer
1 parent 7394593 commit 4a324c8

File tree

7 files changed

+236
-37
lines changed

7 files changed

+236
-37
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright (c) 2020 IoTeX Foundation
2+
// This is an alpha (internal) release and is not suitable for production. This source code is provided 'as is' and no
3+
// warranties are given as to title or non-infringement, merchantability or fitness for purpose and, to the extent
4+
// permitted by law, all liability for your use of the code is disclaimed. This source code is governed by Apache
5+
// License 2.0 that can be found in the LICENSE file.
6+
7+
package staking
8+
9+
import (
10+
"context"
11+
12+
"github.com/gogo/protobuf/proto"
13+
"github.com/pkg/errors"
14+
15+
"github.com/iotexproject/iotex-proto/golang/iotextypes"
16+
17+
"github.com/iotexproject/iotex-core/db"
18+
"github.com/iotexproject/iotex-core/pkg/util/byteutil"
19+
)
20+
21+
const (
22+
// StakingCandidatesNamespace is a namespace to store candidates with epoch start height
23+
StakingCandidatesNamespace = "stakingCandidates"
24+
// StakingBucketsNamespace is a namespace to store vote buckets with epoch start height
25+
StakingBucketsNamespace = "stakingBuckets"
26+
)
27+
28+
// CandidatesBucketsIndexer is an indexer to store candidates by given height
29+
type CandidatesBucketsIndexer struct {
30+
kvStore db.KVStore
31+
}
32+
33+
// NewStakingCandidatesBucketsIndexer creates a new StakingCandidatesIndexer
34+
func NewStakingCandidatesBucketsIndexer(kv db.KVStore) (*CandidatesBucketsIndexer, error) {
35+
if kv == nil {
36+
return nil, ErrMissingField
37+
}
38+
return &CandidatesBucketsIndexer{
39+
kvStore: kv,
40+
}, nil
41+
}
42+
43+
// Start starts the indexer
44+
func (cbi *CandidatesBucketsIndexer) Start(ctx context.Context) error {
45+
return cbi.kvStore.Start(ctx)
46+
}
47+
48+
// Stop stops the indexer
49+
func (cbi *CandidatesBucketsIndexer) Stop(ctx context.Context) error {
50+
return cbi.kvStore.Stop(ctx)
51+
}
52+
53+
// PutCandidates puts candidates into indexer
54+
func (cbi *CandidatesBucketsIndexer) PutCandidates(height uint64, candidates *iotextypes.CandidateListV2) error {
55+
candidatesBytes, err := proto.Marshal(candidates)
56+
if err != nil {
57+
return err
58+
}
59+
return cbi.kvStore.Put(StakingCandidatesNamespace, byteutil.Uint64ToBytesBigEndian(height), candidatesBytes)
60+
}
61+
62+
// GetCandidates gets candidates from indexer given epoch start height
63+
func (cbi *CandidatesBucketsIndexer) GetCandidates(height uint64, offset, limit uint32) ([]byte, error) {
64+
candidateList := &iotextypes.CandidateListV2{}
65+
ret, err := cbi.kvStore.Get(StakingCandidatesNamespace, byteutil.Uint64ToBytesBigEndian(height))
66+
if errors.Cause(err) == db.ErrNotExist {
67+
return proto.Marshal(candidateList)
68+
}
69+
if err != nil {
70+
return nil, err
71+
}
72+
if err := proto.Unmarshal(ret, candidateList); err != nil {
73+
return nil, err
74+
}
75+
length := uint32(len(candidateList.Candidates))
76+
if offset >= length {
77+
return proto.Marshal(&iotextypes.CandidateListV2{})
78+
}
79+
end := offset + limit
80+
if end > uint32(len(candidateList.Candidates)) {
81+
end = uint32(len(candidateList.Candidates))
82+
}
83+
candidateList.Candidates = candidateList.Candidates[offset:end]
84+
return proto.Marshal(candidateList)
85+
}
86+
87+
// PutBuckets puts vote buckets into indexer
88+
func (cbi *CandidatesBucketsIndexer) PutBuckets(height uint64, buckets *iotextypes.VoteBucketList) error {
89+
bucketsBytes, err := proto.Marshal(buckets)
90+
if err != nil {
91+
return err
92+
}
93+
return cbi.kvStore.Put(StakingBucketsNamespace, byteutil.Uint64ToBytesBigEndian(height), bucketsBytes)
94+
}
95+
96+
// GetBuckets gets vote buckets from indexer given epoch start height
97+
func (cbi *CandidatesBucketsIndexer) GetBuckets(height uint64, offset, limit uint32) ([]byte, error) {
98+
buckets := &iotextypes.VoteBucketList{}
99+
ret, err := cbi.kvStore.Get(StakingBucketsNamespace, byteutil.Uint64ToBytesBigEndian(height))
100+
if errors.Cause(err) == db.ErrNotExist {
101+
return proto.Marshal(buckets)
102+
}
103+
if err != nil {
104+
return nil, err
105+
}
106+
if err := proto.Unmarshal(ret, buckets); err != nil {
107+
return nil, err
108+
}
109+
length := uint32(len(buckets.Buckets))
110+
if offset >= length {
111+
return proto.Marshal(&iotextypes.VoteBucketList{})
112+
}
113+
end := offset + limit
114+
if end > uint32(len(buckets.Buckets)) {
115+
end = uint32(len(buckets.Buckets))
116+
}
117+
buckets.Buckets = buckets.Buckets[offset:end]
118+
return proto.Marshal(buckets)
119+
}

action/protocol/staking/handlers_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func TestProtocol_HandleCreateStake(t *testing.T) {
7070
require.NoError(err)
7171

7272
// create protocol
73-
p, err := NewProtocol(depositGas, genesis.Default.Staking)
73+
p, err := NewProtocol(depositGas, genesis.Default.Staking, nil)
7474
require.NoError(err)
7575

7676
// set up candidate
@@ -2557,7 +2557,7 @@ func initAll(t *testing.T, ctrl *gomock.Controller) (protocol.StateManager, *Pro
25572557
require.NoError(err)
25582558

25592559
// create protocol
2560-
p, err := NewProtocol(depositGas, genesis.Default.Staking)
2560+
p, err := NewProtocol(depositGas, genesis.Default.Staking, nil)
25612561
require.NoError(err)
25622562

25632563
// set up candidate

action/protocol/staking/protocol.go

+65-10
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/iotexproject/iotex-core/action"
2424
"github.com/iotexproject/iotex-core/action/protocol"
2525
accountutil "github.com/iotexproject/iotex-core/action/protocol/account/util"
26+
"github.com/iotexproject/iotex-core/action/protocol/rolldpos"
2627
"github.com/iotexproject/iotex-core/blockchain/genesis"
2728
"github.com/iotexproject/iotex-core/config"
2829
"github.com/iotexproject/iotex-core/pkg/log"
@@ -65,10 +66,11 @@ type (
6566

6667
// Protocol defines the protocol of handling staking
6768
Protocol struct {
68-
addr address.Address
69-
depositGas DepositGas
70-
config Configuration
71-
hu config.HeightUpgrade
69+
addr address.Address
70+
depositGas DepositGas
71+
config Configuration
72+
hu config.HeightUpgrade
73+
candBucketsIndexer *CandidatesBucketsIndexer
7274
}
7375

7476
// Configuration is the staking protocol configuration.
@@ -85,7 +87,7 @@ type (
8587
)
8688

8789
// NewProtocol instantiates the protocol of staking
88-
func NewProtocol(depositGas DepositGas, cfg genesis.Staking) (*Protocol, error) {
90+
func NewProtocol(depositGas DepositGas, cfg genesis.Staking, candBucketsIndexer *CandidatesBucketsIndexer) (*Protocol, error) {
8991
h := hash.Hash160b([]byte(protocolID))
9092
addr, err := address.FromBytes(h[:])
9193
if err != nil {
@@ -119,7 +121,8 @@ func NewProtocol(depositGas DepositGas, cfg genesis.Staking) (*Protocol, error)
119121
MinStakeAmount: minStakeAmount,
120122
BootstrapCandidates: cfg.BootstrapCandidates,
121123
},
122-
depositGas: depositGas,
124+
depositGas: depositGas,
125+
candBucketsIndexer: candBucketsIndexer,
123126
}, nil
124127
}
125128

@@ -206,15 +209,51 @@ func (p *Protocol) CreatePreStates(ctx context.Context, sm protocol.StateManager
206209
bcCtx := protocol.MustGetBlockchainCtx(ctx)
207210
blkCtx := protocol.MustGetBlockCtx(ctx)
208211
hu := config.NewHeightUpgrade(&bcCtx.Genesis)
209-
if blkCtx.BlockHeight != hu.GreenlandBlockHeight() {
212+
if blkCtx.BlockHeight == hu.GreenlandBlockHeight() {
213+
csr, err := ConstructBaseView(sm)
214+
if err != nil {
215+
return err
216+
}
217+
if _, err = sm.PutState(csr.BaseView().bucketPool.total, protocol.NamespaceOption(StakingNameSpace), protocol.KeyOption(bucketPoolAddrKey)); err != nil {
218+
return err
219+
}
220+
}
221+
222+
if p.candBucketsIndexer == nil {
210223
return nil
211224
}
212-
csr, err := ConstructBaseView(sm)
225+
rp := rolldpos.MustGetProtocol(protocol.MustGetRegistry(ctx))
226+
currentEpochNum := rp.GetEpochNum(blkCtx.BlockHeight)
227+
if currentEpochNum == 0 {
228+
return nil
229+
}
230+
epochStartHeight := rp.GetEpochHeight(currentEpochNum)
231+
if epochStartHeight != blkCtx.BlockHeight || hu.IsPre(config.Fairbank, epochStartHeight) {
232+
return nil
233+
}
234+
235+
return p.handleStakingIndexer(rp.GetEpochHeight(currentEpochNum-1), sm)
236+
}
237+
238+
func (p *Protocol) handleStakingIndexer(epochStartHeight uint64, sm protocol.StateManager) error {
239+
allBuckets, _, err := getAllBuckets(sm)
240+
if err != nil && errors.Cause(err) != state.ErrStateNotExist {
241+
return err
242+
}
243+
buckets, err := toIoTeXTypesVoteBucketList(allBuckets)
213244
if err != nil {
214245
return err
215246
}
216-
_, err = sm.PutState(csr.BaseView().bucketPool.total, protocol.NamespaceOption(StakingNameSpace), protocol.KeyOption(bucketPoolAddrKey))
217-
return err
247+
err = p.candBucketsIndexer.PutBuckets(epochStartHeight, buckets)
248+
if err != nil {
249+
return err
250+
}
251+
all, _, err := getAllCandidates(sm)
252+
if err != nil && errors.Cause(err) != state.ErrStateNotExist {
253+
return err
254+
}
255+
candidateList := toIoTeXTypesCandidateListV2(all)
256+
return p.candBucketsIndexer.PutCandidates(epochStartHeight, candidateList)
218257
}
219258

220259
// Commit commits the last change
@@ -370,12 +409,24 @@ func (p *Protocol) ReadState(ctx context.Context, sr protocol.StateReader, metho
370409
return nil, 0, err
371410
}
372411

412+
// get height arg
413+
inputHeight, err := sr.Height()
414+
if err != nil {
415+
return nil, 0, err
416+
}
417+
rp := rolldpos.MustGetProtocol(protocol.MustGetRegistry(ctx))
418+
epochStartHeight := rp.GetEpochHeight(rp.GetEpochNum(inputHeight))
419+
373420
var (
374421
height uint64
375422
resp proto.Message
376423
)
377424
switch m.GetMethod() {
378425
case iotexapi.ReadStakingDataMethod_BUCKETS:
426+
if epochStartHeight != 0 && p.candBucketsIndexer != nil {
427+
ret, err := p.candBucketsIndexer.GetBuckets(epochStartHeight, r.GetBuckets().GetPagination().GetOffset(), r.GetBuckets().GetPagination().GetLimit())
428+
return ret, epochStartHeight, err
429+
}
379430
resp, height, err = readStateBuckets(ctx, sr, r.GetBuckets())
380431
case iotexapi.ReadStakingDataMethod_BUCKETS_BY_VOTER:
381432
resp, height, err = readStateBucketsByVoter(ctx, sr, r.GetBucketsByVoter())
@@ -384,6 +435,10 @@ func (p *Protocol) ReadState(ctx context.Context, sr protocol.StateReader, metho
384435
case iotexapi.ReadStakingDataMethod_BUCKETS_BY_INDEXES:
385436
resp, height, err = readStateBucketByIndices(ctx, sr, r.GetBucketsByIndexes())
386437
case iotexapi.ReadStakingDataMethod_CANDIDATES:
438+
if epochStartHeight != 0 && p.candBucketsIndexer != nil {
439+
ret, err := p.candBucketsIndexer.GetCandidates(epochStartHeight, r.GetCandidates().GetPagination().GetOffset(), r.GetCandidates().GetPagination().GetLimit())
440+
return ret, epochStartHeight, err
441+
}
387442
resp, height, err = readStateCandidates(ctx, csr, r.GetCandidates())
388443
case iotexapi.ReadStakingDataMethod_CANDIDATE_BY_NAME:
389444
resp, height, err = readStateCandidateByName(ctx, csr, r.GetCandidateByName())

action/protocol/staking/protocol_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func TestProtocol(t *testing.T) {
8383
}
8484

8585
// test loading with no candidate in stateDB
86-
stk, err := NewProtocol(nil, genesis.Default.Staking)
86+
stk, err := NewProtocol(nil, genesis.Default.Staking, nil)
8787
r.NotNil(stk)
8888
r.NoError(err)
8989
buckets, _, err := getAllBuckets(sm)
@@ -188,7 +188,7 @@ func TestCreatePreStates(t *testing.T) {
188188
ctrl := gomock.NewController(t)
189189
defer ctrl.Finish()
190190
sm := testdb.NewMockStateManager(ctrl)
191-
p, err := NewProtocol(nil, genesis.Default.Staking)
191+
p, err := NewProtocol(nil, genesis.Default.Staking, nil)
192192
require.NoError(err)
193193
ctx := protocol.WithBlockCtx(
194194
protocol.WithBlockchainCtx(

action/protocol/staking/validations_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func TestIsValidCandidateName(t *testing.T) {
6464

6565
func initTestProtocol(t *testing.T) (*Protocol, []*Candidate) {
6666
require := require.New(t)
67-
p, err := NewProtocol(nil, genesis.Default.Staking)
67+
p, err := NewProtocol(nil, genesis.Default.Staking, nil)
6868
require.NoError(err)
6969

7070
var cans []*Candidate

0 commit comments

Comments
 (0)