github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/staking/staking_statereader.go (about) 1 // Copyright (c) 2023 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 "math/big" 11 12 "github.com/iotexproject/iotex-address/address" 13 "github.com/iotexproject/iotex-proto/golang/iotexapi" 14 "github.com/iotexproject/iotex-proto/golang/iotextypes" 15 "github.com/pkg/errors" 16 17 "github.com/iotexproject/iotex-core/action/protocol" 18 "github.com/iotexproject/iotex-core/action/protocol/rolldpos" 19 ) 20 21 type ( 22 // compositeStakingStateReader is the compositive staking state reader, which combine native and contract staking 23 compositeStakingStateReader struct { 24 contractIndexer ContractStakingIndexer 25 nativeIndexer *CandidatesBucketsIndexer 26 nativeSR CandidateStateReader 27 } 28 ) 29 30 // newCompositeStakingStateReader creates a new compositive staking state reader 31 func newCompositeStakingStateReader(contractIndexer ContractStakingIndexer, nativeIndexer *CandidatesBucketsIndexer, sr protocol.StateReader) (*compositeStakingStateReader, error) { 32 nativeSR, err := ConstructBaseView(sr) 33 if err != nil { 34 return nil, err 35 } 36 return &compositeStakingStateReader{ 37 contractIndexer: contractIndexer, 38 nativeIndexer: nativeIndexer, 39 nativeSR: nativeSR, 40 }, nil 41 } 42 43 func (c *compositeStakingStateReader) readStateBuckets(ctx context.Context, req *iotexapi.ReadStakingDataRequest_VoteBuckets) (*iotextypes.VoteBucketList, uint64, error) { 44 // get height arg 45 inputHeight, err := c.nativeSR.SR().Height() 46 if err != nil { 47 return nil, 0, err 48 } 49 rp := rolldpos.MustGetProtocol(protocol.MustGetRegistry(ctx)) 50 epochStartHeight := rp.GetEpochHeight(rp.GetEpochNum(inputHeight)) 51 52 var ( 53 buckets *iotextypes.VoteBucketList 54 height uint64 55 ) 56 if epochStartHeight != 0 && c.nativeIndexer != nil { 57 // read native buckets from indexer 58 buckets, height, err = c.nativeIndexer.GetBuckets(epochStartHeight, req.GetPagination().GetOffset(), req.GetPagination().GetLimit()) 59 if err != nil { 60 return nil, 0, err 61 } 62 } else { 63 // read native buckets from state 64 buckets, height, err = c.nativeSR.readStateBuckets(ctx, req) 65 if err != nil { 66 return nil, 0, err 67 } 68 } 69 70 if !c.isContractStakingEnabled() { 71 buckets.Buckets = getPageOfArray(buckets.Buckets, int(req.GetPagination().GetOffset()), int(req.GetPagination().GetLimit())) 72 return buckets, height, nil 73 } 74 75 // read LSD buckets 76 lsdBuckets, err := c.contractIndexer.Buckets(inputHeight) 77 if err != nil { 78 return nil, 0, err 79 } 80 lsdIoTeXBuckets, err := toIoTeXTypesVoteBucketList(c.nativeSR.SR(), lsdBuckets) 81 if err != nil { 82 return nil, 0, err 83 } 84 // merge native and LSD buckets 85 buckets.Buckets = append(buckets.Buckets, lsdIoTeXBuckets.Buckets...) 86 buckets.Buckets = getPageOfArray(buckets.Buckets, int(req.GetPagination().GetOffset()), int(req.GetPagination().GetLimit())) 87 return buckets, height, err 88 } 89 90 func (c *compositeStakingStateReader) readStateBucketsByVoter(ctx context.Context, req *iotexapi.ReadStakingDataRequest_VoteBucketsByVoter) (*iotextypes.VoteBucketList, uint64, error) { 91 // read native buckets 92 buckets, height, err := c.nativeSR.readStateBucketsByVoter(ctx, req) 93 if err != nil { 94 return nil, 0, err 95 } 96 if !c.isContractStakingEnabled() { 97 buckets.Buckets = getPageOfArray(buckets.Buckets, int(req.GetPagination().GetOffset()), int(req.GetPagination().GetLimit())) 98 return buckets, height, err 99 } 100 101 // read LSD buckets 102 lsdBuckets, err := c.contractIndexer.Buckets(height) 103 if err != nil { 104 return nil, 0, err 105 } 106 lsdBuckets = filterBucketsByVoter(lsdBuckets, req.GetVoterAddress()) 107 lsdIoTeXBuckets, err := toIoTeXTypesVoteBucketList(c.nativeSR.SR(), lsdBuckets) 108 if err != nil { 109 return nil, 0, err 110 } 111 // merge native and LSD buckets 112 buckets.Buckets = append(buckets.Buckets, lsdIoTeXBuckets.Buckets...) 113 buckets.Buckets = getPageOfArray(buckets.Buckets, int(req.GetPagination().GetOffset()), int(req.GetPagination().GetLimit())) 114 return buckets, height, err 115 } 116 117 func (c *compositeStakingStateReader) readStateBucketsByCandidate(ctx context.Context, req *iotexapi.ReadStakingDataRequest_VoteBucketsByCandidate) (*iotextypes.VoteBucketList, uint64, error) { 118 // read native buckets 119 buckets, height, err := c.nativeSR.readStateBucketsByCandidate(ctx, req) 120 if err != nil { 121 return nil, 0, err 122 } 123 124 if !c.isContractStakingEnabled() { 125 buckets.Buckets = getPageOfArray(buckets.Buckets, int(req.GetPagination().GetOffset()), int(req.GetPagination().GetLimit())) 126 return buckets, height, err 127 } 128 129 // read LSD buckets 130 candidate := c.nativeSR.GetCandidateByName(req.GetCandName()) 131 if candidate == nil { 132 return &iotextypes.VoteBucketList{}, height, nil 133 } 134 lsdBuckets, err := c.contractIndexer.BucketsByCandidate(candidate.Owner, height) 135 if err != nil { 136 return nil, 0, err 137 } 138 lsdIoTeXBuckets, err := toIoTeXTypesVoteBucketList(c.nativeSR.SR(), lsdBuckets) 139 if err != nil { 140 return nil, 0, err 141 } 142 // merge native and LSD buckets 143 buckets.Buckets = append(buckets.Buckets, lsdIoTeXBuckets.Buckets...) 144 buckets.Buckets = getPageOfArray(buckets.Buckets, int(req.GetPagination().GetOffset()), int(req.GetPagination().GetLimit())) 145 return buckets, height, err 146 } 147 148 func (c *compositeStakingStateReader) readStateBucketByIndices(ctx context.Context, req *iotexapi.ReadStakingDataRequest_VoteBucketsByIndexes) (*iotextypes.VoteBucketList, uint64, error) { 149 // read native buckets 150 buckets, height, err := c.nativeSR.readStateBucketByIndices(ctx, req) 151 if err != nil { 152 return nil, 0, err 153 } 154 if !c.isContractStakingEnabled() { 155 return buckets, height, nil 156 } 157 158 // read LSD buckets 159 lsdBuckets, err := c.contractIndexer.BucketsByIndices(req.GetIndex(), height) 160 if err != nil { 161 return nil, 0, err 162 } 163 lsbIoTeXBuckets, err := toIoTeXTypesVoteBucketList(c.nativeSR.SR(), lsdBuckets) 164 if err != nil { 165 return nil, 0, err 166 } 167 // merge native and LSD buckets 168 buckets.Buckets = append(buckets.Buckets, lsbIoTeXBuckets.Buckets...) 169 return buckets, height, nil 170 } 171 172 func (c *compositeStakingStateReader) readStateBucketCount(ctx context.Context, req *iotexapi.ReadStakingDataRequest_BucketsCount) (*iotextypes.BucketsCount, uint64, error) { 173 bucketCnt, height, err := c.nativeSR.readStateBucketCount(ctx, req) 174 if err != nil { 175 return nil, 0, err 176 } 177 if !c.isContractStakingEnabled() { 178 return bucketCnt, height, nil 179 } 180 buckets, err := c.contractIndexer.Buckets(height) 181 if err != nil { 182 return nil, 0, err 183 } 184 bucketCnt.Active += uint64(len(buckets)) 185 tbc, err := c.contractIndexer.TotalBucketCount(height) 186 if err != nil { 187 return nil, 0, err 188 } 189 bucketCnt.Total += tbc 190 return bucketCnt, height, nil 191 } 192 193 func (c *compositeStakingStateReader) readStateCandidates(ctx context.Context, req *iotexapi.ReadStakingDataRequest_Candidates) (*iotextypes.CandidateListV2, uint64, error) { 194 // get height arg 195 inputHeight, err := c.nativeSR.SR().Height() 196 if err != nil { 197 return nil, 0, err 198 } 199 rp := rolldpos.MustGetProtocol(protocol.MustGetRegistry(ctx)) 200 epochStartHeight := rp.GetEpochHeight(rp.GetEpochNum(inputHeight)) 201 202 // read native candidates 203 var ( 204 candidates *iotextypes.CandidateListV2 205 height uint64 206 ) 207 if epochStartHeight != 0 && c.nativeIndexer != nil { 208 // read candidates from indexer 209 candidates, height, err = c.nativeIndexer.GetCandidates(epochStartHeight, req.GetPagination().GetOffset(), req.GetPagination().GetLimit()) 210 if err != nil { 211 return nil, 0, err 212 } 213 } else { 214 // read candidates from native state 215 candidates, height, err = c.nativeSR.readStateCandidates(ctx, req) 216 if err != nil { 217 return nil, 0, err 218 } 219 } 220 if !protocol.MustGetFeatureCtx(ctx).AddContractStakingVotes { 221 return candidates, height, nil 222 } 223 if !c.isContractStakingEnabled() { 224 return candidates, height, nil 225 } 226 for _, candidate := range candidates.Candidates { 227 if err = addContractStakingVotes(ctx, candidate, c.contractIndexer, height); err != nil { 228 return nil, 0, err 229 } 230 } 231 return candidates, height, nil 232 } 233 234 func (c *compositeStakingStateReader) readStateCandidateByName(ctx context.Context, req *iotexapi.ReadStakingDataRequest_CandidateByName) (*iotextypes.CandidateV2, uint64, error) { 235 candidate, height, err := c.nativeSR.readStateCandidateByName(ctx, req) 236 if err != nil { 237 return nil, 0, err 238 } 239 if !c.isContractStakingEnabled() { 240 return candidate, height, nil 241 } 242 if !protocol.MustGetFeatureCtx(ctx).AddContractStakingVotes { 243 return candidate, height, nil 244 } 245 if err := addContractStakingVotes(ctx, candidate, c.contractIndexer, height); err != nil { 246 return nil, 0, err 247 } 248 return candidate, height, nil 249 } 250 251 func (c *compositeStakingStateReader) readStateCandidateByAddress(ctx context.Context, req *iotexapi.ReadStakingDataRequest_CandidateByAddress) (*iotextypes.CandidateV2, uint64, error) { 252 candidate, height, err := c.nativeSR.readStateCandidateByAddress(ctx, req) 253 if err != nil { 254 return nil, 0, err 255 } 256 if !c.isContractStakingEnabled() { 257 return candidate, height, nil 258 } 259 if !protocol.MustGetFeatureCtx(ctx).AddContractStakingVotes { 260 return candidate, height, nil 261 } 262 if err := addContractStakingVotes(ctx, candidate, c.contractIndexer, height); err != nil { 263 return nil, 0, err 264 } 265 return candidate, height, nil 266 } 267 268 func (c *compositeStakingStateReader) readStateTotalStakingAmount(ctx context.Context, _ *iotexapi.ReadStakingDataRequest_TotalStakingAmount) (*iotextypes.AccountMeta, uint64, error) { 269 // read native total staking amount 270 accountMeta, height, err := c.nativeSR.readStateTotalStakingAmount(ctx, nil) 271 if err != nil { 272 return nil, 0, err 273 } 274 amount, ok := big.NewInt(0).SetString(accountMeta.Balance, 10) 275 if !ok { 276 return nil, 0, errors.Errorf("invalid balance %s", accountMeta.Balance) 277 } 278 if !c.isContractStakingEnabled() { 279 return accountMeta, height, nil 280 } 281 // add contract staking amount 282 buckets, err := c.contractIndexer.Buckets(height) 283 if err != nil { 284 return nil, 0, err 285 } 286 for _, bucket := range buckets { 287 amount.Add(amount, bucket.StakedAmount) 288 } 289 290 accountMeta.Balance = amount.String() 291 return accountMeta, height, nil 292 } 293 294 func (c *compositeStakingStateReader) readStateContractStakingBucketTypes(ctx context.Context, _ *iotexapi.ReadStakingDataRequest_ContractStakingBucketTypes) (*iotextypes.ContractStakingBucketTypeList, uint64, error) { 295 if !c.isContractStakingEnabled() { 296 return &iotextypes.ContractStakingBucketTypeList{}, c.nativeSR.Height(), nil 297 } 298 height := c.nativeSR.Height() 299 bts, err := c.contractIndexer.BucketTypes(height) 300 if err != nil { 301 return nil, 0, err 302 } 303 pbBts := make([]*iotextypes.ContractStakingBucketType, 0, len(bts)) 304 for _, bt := range bts { 305 pbBts = append(pbBts, &iotextypes.ContractStakingBucketType{ 306 StakedAmount: bt.Amount.String(), 307 StakedDuration: uint32(bt.Duration), 308 }) 309 } 310 return &iotextypes.ContractStakingBucketTypeList{BucketTypes: pbBts}, height, nil 311 } 312 313 func (c *compositeStakingStateReader) isContractStakingEnabled() bool { 314 return c.contractIndexer != nil 315 } 316 317 // TODO: move into compositeStakingStateReader 318 func addContractStakingVotes(ctx context.Context, candidate *iotextypes.CandidateV2, contractStakingSR ContractStakingIndexer, height uint64) error { 319 votes, ok := big.NewInt(0).SetString(candidate.TotalWeightedVotes, 10) 320 if !ok { 321 return errors.Errorf("invalid total weighted votes %s", candidate.TotalWeightedVotes) 322 } 323 addr, err := address.FromString(candidate.OwnerAddress) 324 if err != nil { 325 return err 326 } 327 contractVotes, err := contractStakingSR.CandidateVotes(ctx, addr, height) 328 if err != nil { 329 return err 330 } 331 votes.Add(votes, contractVotes) 332 candidate.TotalWeightedVotes = votes.String() 333 return nil 334 } 335 336 func filterBucketsByVoter(buckets []*VoteBucket, voterAddress string) []*VoteBucket { 337 var filtered []*VoteBucket 338 for _, bucket := range buckets { 339 if bucket.Owner.String() == voterAddress { 340 filtered = append(filtered, bucket) 341 } 342 } 343 return filtered 344 }