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  }