github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/staking/candidate_buckets_indexer.go (about) 1 // Copyright (c) 2020 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package staking 7 8 import ( 9 "context" 10 11 "github.com/pkg/errors" 12 "google.golang.org/protobuf/proto" 13 14 "github.com/iotexproject/go-pkgs/hash" 15 "github.com/iotexproject/iotex-proto/golang/iotextypes" 16 17 "github.com/iotexproject/iotex-core/db" 18 "github.com/iotexproject/iotex-core/db/batch" 19 "github.com/iotexproject/iotex-core/pkg/util/byteutil" 20 ) 21 22 const ( 23 // StakingCandidatesNamespace is a namespace to store candidates with epoch start height 24 StakingCandidatesNamespace = "stakingCandidates" 25 // StakingBucketsNamespace is a namespace to store vote buckets with epoch start height 26 StakingBucketsNamespace = "stakingBuckets" 27 // StakingMetaNamespace is a namespace to store metadata 28 StakingMetaNamespace = "stakingMeta" 29 ) 30 31 var ( 32 _candHeightKey = []byte("cht") 33 _bucketHeightKey = []byte("bht") 34 _latestCandidatesHash = []byte("lch") 35 _latestBucketsHash = []byte("lbh") 36 ) 37 38 // CandidatesBucketsIndexer is an indexer to store candidates by given height 39 type CandidatesBucketsIndexer struct { 40 latestCandidatesHeight uint64 41 latestBucketsHeight uint64 42 latestCandidatesHash hash.Hash160 43 latestBucketsHash hash.Hash160 44 kvStore db.KVStoreForRangeIndex 45 } 46 47 // NewStakingCandidatesBucketsIndexer creates a new StakingCandidatesIndexer 48 func NewStakingCandidatesBucketsIndexer(kv db.KVStoreForRangeIndex) (*CandidatesBucketsIndexer, error) { 49 if kv == nil { 50 return nil, ErrMissingField 51 } 52 return &CandidatesBucketsIndexer{ 53 kvStore: kv, 54 }, nil 55 } 56 57 // Start starts the indexer 58 func (cbi *CandidatesBucketsIndexer) Start(ctx context.Context) error { 59 if err := cbi.kvStore.Start(ctx); err != nil { 60 return err 61 } 62 ret, err := cbi.kvStore.Get(StakingMetaNamespace, _candHeightKey) 63 switch errors.Cause(err) { 64 case nil: 65 cbi.latestCandidatesHeight = byteutil.BytesToUint64BigEndian(ret) 66 case db.ErrNotExist: 67 cbi.latestCandidatesHeight = 0 68 default: 69 return err 70 } 71 72 ret, err = cbi.kvStore.Get(StakingMetaNamespace, _latestCandidatesHash) 73 switch errors.Cause(err) { 74 case nil: 75 cbi.latestCandidatesHash = hash.BytesToHash160(ret) 76 case db.ErrNotExist: 77 cbi.latestCandidatesHash = hash.ZeroHash160 78 default: 79 return err 80 } 81 82 ret, err = cbi.kvStore.Get(StakingMetaNamespace, _bucketHeightKey) 83 switch errors.Cause(err) { 84 case nil: 85 cbi.latestBucketsHeight = byteutil.BytesToUint64BigEndian(ret) 86 case db.ErrNotExist: 87 cbi.latestBucketsHeight = 0 88 default: 89 return err 90 } 91 92 ret, err = cbi.kvStore.Get(StakingMetaNamespace, _latestBucketsHash) 93 switch errors.Cause(err) { 94 case nil: 95 cbi.latestBucketsHash = hash.BytesToHash160(ret) 96 case db.ErrNotExist: 97 cbi.latestBucketsHash = hash.ZeroHash160 98 default: 99 return err 100 } 101 return nil 102 } 103 104 // Stop stops the indexer 105 func (cbi *CandidatesBucketsIndexer) Stop(ctx context.Context) error { 106 return cbi.kvStore.Stop(ctx) 107 } 108 109 // PutCandidates puts candidates into indexer 110 func (cbi *CandidatesBucketsIndexer) PutCandidates(height uint64, candidates *iotextypes.CandidateListV2) error { 111 candidatesBytes, err := proto.Marshal(candidates) 112 if err != nil { 113 return err 114 } 115 116 if err := cbi.putToIndexer(StakingCandidatesNamespace, height, candidatesBytes); err != nil { 117 return err 118 } 119 cbi.latestCandidatesHeight = height 120 return nil 121 } 122 123 // GetCandidates gets candidates from indexer given epoch start height 124 func (cbi *CandidatesBucketsIndexer) GetCandidates(height uint64, offset, limit uint32) (*iotextypes.CandidateListV2, uint64, error) { 125 if height > cbi.latestCandidatesHeight { 126 height = cbi.latestCandidatesHeight 127 } 128 candidateList := &iotextypes.CandidateListV2{} 129 ret, err := getFromIndexer(cbi.kvStore, StakingCandidatesNamespace, height) 130 cause := errors.Cause(err) 131 if cause == db.ErrNotExist || cause == db.ErrBucketNotExist { 132 return candidateList, height, nil 133 } 134 if err != nil { 135 return nil, height, err 136 } 137 if err := proto.Unmarshal(ret, candidateList); err != nil { 138 return nil, height, err 139 } 140 length := uint32(len(candidateList.Candidates)) 141 if offset >= length { 142 return &iotextypes.CandidateListV2{}, height, nil 143 } 144 end := offset + limit 145 if end > uint32(len(candidateList.Candidates)) { 146 end = uint32(len(candidateList.Candidates)) 147 } 148 candidateList.Candidates = candidateList.Candidates[offset:end] 149 return candidateList, height, nil 150 } 151 152 // PutBuckets puts vote buckets into indexer 153 func (cbi *CandidatesBucketsIndexer) PutBuckets(height uint64, buckets *iotextypes.VoteBucketList) error { 154 bucketsBytes, err := proto.Marshal(buckets) 155 if err != nil { 156 return err 157 } 158 159 if err := cbi.putToIndexer(StakingBucketsNamespace, height, bucketsBytes); err != nil { 160 return err 161 } 162 cbi.latestBucketsHeight = height 163 return nil 164 } 165 166 // GetBuckets gets vote buckets from indexer given epoch start height 167 func (cbi *CandidatesBucketsIndexer) GetBuckets(height uint64, offset, limit uint32) (*iotextypes.VoteBucketList, uint64, error) { 168 if height > cbi.latestBucketsHeight { 169 height = cbi.latestBucketsHeight 170 } 171 buckets := &iotextypes.VoteBucketList{} 172 ret, err := getFromIndexer(cbi.kvStore, StakingBucketsNamespace, height) 173 cause := errors.Cause(err) 174 if cause == db.ErrNotExist || cause == db.ErrBucketNotExist { 175 return buckets, height, nil 176 } 177 if err != nil { 178 return nil, height, err 179 } 180 if err := proto.Unmarshal(ret, buckets); err != nil { 181 return nil, height, err 182 } 183 length := uint32(len(buckets.Buckets)) 184 if offset >= length { 185 return &iotextypes.VoteBucketList{}, height, nil 186 } 187 end := offset + limit 188 if end > uint32(len(buckets.Buckets)) { 189 end = uint32(len(buckets.Buckets)) 190 } 191 buckets.Buckets = buckets.Buckets[offset:end] 192 return buckets, height, nil 193 } 194 195 func (cbi *CandidatesBucketsIndexer) putToIndexer(ns string, height uint64, data []byte) error { 196 var ( 197 h = hash.Hash160b(data) 198 dataExist bool 199 heightKey []byte 200 latestHash []byte 201 ) 202 switch ns { 203 case StakingCandidatesNamespace: 204 dataExist = (h == cbi.latestCandidatesHash) 205 heightKey = _candHeightKey 206 latestHash = _latestCandidatesHash 207 case StakingBucketsNamespace: 208 dataExist = (h == cbi.latestBucketsHash) 209 heightKey = _bucketHeightKey 210 latestHash = _latestBucketsHash 211 default: 212 return ErrTypeAssertion 213 } 214 215 heightBytes := byteutil.Uint64ToBytesBigEndian(height) 216 if dataExist { 217 // same bytes already exist, do nothing 218 return cbi.kvStore.Put(StakingMetaNamespace, heightKey, heightBytes) 219 } 220 221 // update latest height 222 b := batch.NewBatch() 223 b.Put(ns, heightBytes, data, "failed to write data bytes") 224 b.Put(StakingMetaNamespace, heightKey, heightBytes, "failed to update indexer height") 225 b.Put(StakingMetaNamespace, latestHash, h[:], "failed to update latest hash") 226 if err := cbi.kvStore.WriteBatch(b); err != nil { 227 return err 228 } 229 // update latest hash 230 if ns == StakingCandidatesNamespace { 231 cbi.latestCandidatesHash = h 232 } else { 233 cbi.latestBucketsHash = h 234 } 235 return nil 236 } 237 238 func getFromIndexer(kv db.KVStoreForRangeIndex, ns string, height uint64) ([]byte, error) { 239 b, err := kv.Get(ns, byteutil.Uint64ToBytesBigEndian(height)) 240 switch errors.Cause(err) { 241 case nil: 242 return b, nil 243 case db.ErrNotExist: 244 // height does not exist, fallback to previous height 245 return kv.SeekPrev([]byte(ns), height) 246 default: 247 return nil, err 248 } 249 }