github.com/iotexproject/iotex-core@v1.14.1-rc1/blockindex/contractstaking/cache.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 contractstaking
     7  
     8  import (
     9  	"context"
    10  	"math/big"
    11  	"sync"
    12  
    13  	"github.com/iotexproject/iotex-address/address"
    14  	"github.com/pkg/errors"
    15  
    16  	"github.com/iotexproject/iotex-core/action/protocol"
    17  	"github.com/iotexproject/iotex-core/db"
    18  	"github.com/iotexproject/iotex-core/pkg/util/byteutil"
    19  )
    20  
    21  type (
    22  	contractStakingCache struct {
    23  		bucketInfoMap         map[uint64]*bucketInfo      // map[token]bucketInfo
    24  		candidateBucketMap    map[string]map[uint64]bool  // map[candidate]bucket
    25  		bucketTypeMap         map[uint64]*BucketType      // map[bucketTypeId]BucketType
    26  		propertyBucketTypeMap map[int64]map[uint64]uint64 // map[amount][duration]index
    27  		totalBucketCount      uint64                      // total number of buckets including burned buckets
    28  		height                uint64                      // current block height, it's put in cache for consistency on merge
    29  		mutex                 sync.RWMutex                // a RW mutex for the cache to protect concurrent access
    30  		config                Config
    31  	}
    32  )
    33  
    34  var (
    35  	// ErrBucketNotExist is the error when bucket does not exist
    36  	ErrBucketNotExist = errors.New("bucket does not exist")
    37  	// ErrInvalidHeight is the error when height is invalid
    38  	ErrInvalidHeight = errors.New("invalid height")
    39  )
    40  
    41  func newContractStakingCache(config Config) *contractStakingCache {
    42  	return &contractStakingCache{
    43  		bucketInfoMap:         make(map[uint64]*bucketInfo),
    44  		bucketTypeMap:         make(map[uint64]*BucketType),
    45  		propertyBucketTypeMap: make(map[int64]map[uint64]uint64),
    46  		candidateBucketMap:    make(map[string]map[uint64]bool),
    47  		config:                config,
    48  	}
    49  }
    50  
    51  func (s *contractStakingCache) Height() uint64 {
    52  	s.mutex.RLock()
    53  	defer s.mutex.RUnlock()
    54  	return s.height
    55  }
    56  
    57  func (s *contractStakingCache) CandidateVotes(ctx context.Context, candidate address.Address, height uint64) (*big.Int, error) {
    58  	s.mutex.RLock()
    59  	defer s.mutex.RUnlock()
    60  
    61  	if err := s.validateHeight(height); err != nil {
    62  		return nil, err
    63  	}
    64  	votes := big.NewInt(0)
    65  	m, ok := s.candidateBucketMap[candidate.String()]
    66  	if !ok {
    67  		return votes, nil
    68  	}
    69  	featureCtx := protocol.MustGetFeatureCtx(ctx)
    70  	for id, existed := range m {
    71  		if !existed {
    72  			continue
    73  		}
    74  		bi := s.mustGetBucketInfo(id)
    75  		// only count the bucket that is not unstaked
    76  		if bi.UnstakedAt != maxBlockNumber {
    77  			continue
    78  		}
    79  		bt := s.mustGetBucketType(bi.TypeIndex)
    80  		if featureCtx.FixContractStakingWeightedVotes {
    81  			votes.Add(votes, s.config.CalculateVoteWeight(assembleBucket(id, bi, bt, s.config.ContractAddress, s.config.BlockInterval)))
    82  		} else {
    83  			votes.Add(votes, bt.Amount)
    84  		}
    85  	}
    86  	return votes, nil
    87  }
    88  
    89  func (s *contractStakingCache) Buckets(height uint64) ([]*Bucket, error) {
    90  	s.mutex.RLock()
    91  	defer s.mutex.RUnlock()
    92  
    93  	if err := s.validateHeight(height); err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	vbs := []*Bucket{}
    98  	for id, bi := range s.bucketInfoMap {
    99  		bt := s.mustGetBucketType(bi.TypeIndex)
   100  		vb := assembleBucket(id, bi.clone(), bt, s.config.ContractAddress, s.config.BlockInterval)
   101  		vbs = append(vbs, vb)
   102  	}
   103  	return vbs, nil
   104  }
   105  
   106  func (s *contractStakingCache) Bucket(id, height uint64) (*Bucket, bool, error) {
   107  	s.mutex.RLock()
   108  	defer s.mutex.RUnlock()
   109  
   110  	if err := s.validateHeight(height); err != nil {
   111  		return nil, false, err
   112  	}
   113  	bt, ok := s.getBucket(id)
   114  	return bt, ok, nil
   115  }
   116  
   117  func (s *contractStakingCache) BucketInfo(id uint64) (*bucketInfo, bool) {
   118  	s.mutex.RLock()
   119  	defer s.mutex.RUnlock()
   120  
   121  	return s.getBucketInfo(id)
   122  }
   123  
   124  func (s *contractStakingCache) MustGetBucketInfo(id uint64) *bucketInfo {
   125  	s.mutex.RLock()
   126  	defer s.mutex.RUnlock()
   127  
   128  	return s.mustGetBucketInfo(id)
   129  }
   130  
   131  func (s *contractStakingCache) MustGetBucketType(id uint64) *BucketType {
   132  	s.mutex.RLock()
   133  	defer s.mutex.RUnlock()
   134  
   135  	return s.mustGetBucketType(id)
   136  }
   137  
   138  func (s *contractStakingCache) BucketType(id uint64) (*BucketType, bool) {
   139  	s.mutex.RLock()
   140  	defer s.mutex.RUnlock()
   141  
   142  	return s.getBucketType(id)
   143  }
   144  
   145  func (s *contractStakingCache) BucketsByCandidate(candidate address.Address, height uint64) ([]*Bucket, error) {
   146  	s.mutex.RLock()
   147  	defer s.mutex.RUnlock()
   148  
   149  	if err := s.validateHeight(height); err != nil {
   150  		return nil, err
   151  	}
   152  	bucketMap := s.candidateBucketMap[candidate.String()]
   153  	vbs := make([]*Bucket, 0, len(bucketMap))
   154  	for id := range bucketMap {
   155  		vb := s.mustGetBucket(id)
   156  		vbs = append(vbs, vb)
   157  	}
   158  	return vbs, nil
   159  }
   160  
   161  func (s *contractStakingCache) BucketsByIndices(indices []uint64, height uint64) ([]*Bucket, error) {
   162  	s.mutex.RLock()
   163  	defer s.mutex.RUnlock()
   164  
   165  	if err := s.validateHeight(height); err != nil {
   166  		return nil, err
   167  	}
   168  	vbs := make([]*Bucket, 0, len(indices))
   169  	for _, id := range indices {
   170  		vb, ok := s.getBucket(id)
   171  		if ok {
   172  			vbs = append(vbs, vb)
   173  		}
   174  	}
   175  	return vbs, nil
   176  }
   177  
   178  func (s *contractStakingCache) TotalBucketCount(height uint64) (uint64, error) {
   179  	s.mutex.RLock()
   180  	defer s.mutex.RUnlock()
   181  
   182  	if err := s.validateHeight(height); err != nil {
   183  		return 0, err
   184  	}
   185  	return s.totalBucketCount, nil
   186  }
   187  
   188  func (s *contractStakingCache) ActiveBucketTypes(height uint64) (map[uint64]*BucketType, error) {
   189  	s.mutex.RLock()
   190  	defer s.mutex.RUnlock()
   191  
   192  	if err := s.validateHeight(height); err != nil {
   193  		return nil, err
   194  	}
   195  	m := make(map[uint64]*BucketType)
   196  	for k, v := range s.bucketTypeMap {
   197  		if v.ActivatedAt != maxBlockNumber {
   198  			m[k] = v.Clone()
   199  		}
   200  	}
   201  	return m, nil
   202  }
   203  
   204  func (s *contractStakingCache) PutBucketType(id uint64, bt *BucketType) {
   205  	s.mutex.Lock()
   206  	defer s.mutex.Unlock()
   207  
   208  	s.putBucketType(id, bt)
   209  }
   210  
   211  func (s *contractStakingCache) PutBucketInfo(id uint64, bi *bucketInfo) {
   212  	s.mutex.Lock()
   213  	defer s.mutex.Unlock()
   214  
   215  	s.putBucketInfo(id, bi)
   216  }
   217  
   218  func (s *contractStakingCache) DeleteBucketInfo(id uint64) {
   219  	s.mutex.Lock()
   220  	defer s.mutex.Unlock()
   221  
   222  	s.deleteBucketInfo(id)
   223  }
   224  
   225  func (s *contractStakingCache) Merge(delta *contractStakingDelta, height uint64) error {
   226  	s.mutex.Lock()
   227  	defer s.mutex.Unlock()
   228  
   229  	if err := s.mergeDelta(delta); err != nil {
   230  		return err
   231  	}
   232  	s.putHeight(height)
   233  	s.putTotalBucketCount(s.totalBucketCount + delta.AddedBucketCnt())
   234  	return nil
   235  }
   236  
   237  func (s *contractStakingCache) MatchBucketType(amount *big.Int, duration uint64) (uint64, *BucketType, bool) {
   238  	s.mutex.RLock()
   239  	defer s.mutex.RUnlock()
   240  
   241  	id, ok := s.getBucketTypeIndex(amount, duration)
   242  	if !ok {
   243  		return 0, nil, false
   244  	}
   245  	return id, s.mustGetBucketType(id), true
   246  }
   247  
   248  func (s *contractStakingCache) BucketTypeCount(height uint64) (uint64, error) {
   249  	s.mutex.RLock()
   250  	defer s.mutex.RUnlock()
   251  
   252  	if err := s.validateHeight(height); err != nil {
   253  		return 0, err
   254  	}
   255  	return uint64(len(s.bucketTypeMap)), nil
   256  }
   257  
   258  func (s *contractStakingCache) LoadFromDB(kvstore db.KVStore) error {
   259  	s.mutex.Lock()
   260  	defer s.mutex.Unlock()
   261  
   262  	// load height
   263  	var height uint64
   264  	h, err := kvstore.Get(_StakingNS, _stakingHeightKey)
   265  	if err != nil {
   266  		if !errors.Is(err, db.ErrNotExist) {
   267  			return err
   268  		}
   269  		height = 0
   270  	} else {
   271  		height = byteutil.BytesToUint64BigEndian(h)
   272  
   273  	}
   274  	s.putHeight(height)
   275  
   276  	// load total bucket count
   277  	var totalBucketCount uint64
   278  	tbc, err := kvstore.Get(_StakingNS, _stakingTotalBucketCountKey)
   279  	if err != nil {
   280  		if !errors.Is(err, db.ErrNotExist) {
   281  			return err
   282  		}
   283  		totalBucketCount = 0
   284  	} else {
   285  		totalBucketCount = byteutil.BytesToUint64BigEndian(tbc)
   286  	}
   287  	s.putTotalBucketCount(totalBucketCount)
   288  
   289  	// load bucket info
   290  	ks, vs, err := kvstore.Filter(_StakingBucketInfoNS, func(k, v []byte) bool { return true }, nil, nil)
   291  	if err != nil && !errors.Is(err, db.ErrBucketNotExist) {
   292  		return err
   293  	}
   294  	for i := range vs {
   295  		var b bucketInfo
   296  		if err := b.Deserialize(vs[i]); err != nil {
   297  			return err
   298  		}
   299  		s.putBucketInfo(byteutil.BytesToUint64BigEndian(ks[i]), &b)
   300  	}
   301  
   302  	// load bucket type
   303  	ks, vs, err = kvstore.Filter(_StakingBucketTypeNS, func(k, v []byte) bool { return true }, nil, nil)
   304  	if err != nil && !errors.Is(err, db.ErrBucketNotExist) {
   305  		return err
   306  	}
   307  	for i := range vs {
   308  		var b BucketType
   309  		if err := b.Deserialize(vs[i]); err != nil {
   310  			return err
   311  		}
   312  		s.putBucketType(byteutil.BytesToUint64BigEndian(ks[i]), &b)
   313  	}
   314  	return nil
   315  }
   316  
   317  func (s *contractStakingCache) getBucketTypeIndex(amount *big.Int, duration uint64) (uint64, bool) {
   318  	m, ok := s.propertyBucketTypeMap[amount.Int64()]
   319  	if !ok {
   320  		return 0, false
   321  	}
   322  	id, ok := m[duration]
   323  	return id, ok
   324  }
   325  
   326  func (s *contractStakingCache) getBucketType(id uint64) (*BucketType, bool) {
   327  	bt, ok := s.bucketTypeMap[id]
   328  	if !ok {
   329  		return nil, false
   330  	}
   331  	return bt.Clone(), ok
   332  }
   333  
   334  func (s *contractStakingCache) mustGetBucketType(id uint64) *BucketType {
   335  	bt, ok := s.getBucketType(id)
   336  	if !ok {
   337  		panic("bucket type not found")
   338  	}
   339  	return bt
   340  }
   341  
   342  func (s *contractStakingCache) getBucketInfo(id uint64) (*bucketInfo, bool) {
   343  	bi, ok := s.bucketInfoMap[id]
   344  	if !ok {
   345  		return nil, false
   346  	}
   347  	return bi.clone(), ok
   348  }
   349  
   350  func (s *contractStakingCache) mustGetBucketInfo(id uint64) *bucketInfo {
   351  	bt, ok := s.getBucketInfo(id)
   352  	if !ok {
   353  		panic("bucket info not found")
   354  	}
   355  	return bt
   356  }
   357  
   358  func (s *contractStakingCache) mustGetBucket(id uint64) *Bucket {
   359  	bi := s.mustGetBucketInfo(id)
   360  	bt := s.mustGetBucketType(bi.TypeIndex)
   361  	return assembleBucket(id, bi, bt, s.config.ContractAddress, s.config.BlockInterval)
   362  }
   363  
   364  func (s *contractStakingCache) getBucket(id uint64) (*Bucket, bool) {
   365  	bi, ok := s.getBucketInfo(id)
   366  	if !ok {
   367  		return nil, false
   368  	}
   369  	bt := s.mustGetBucketType(bi.TypeIndex)
   370  	return assembleBucket(id, bi, bt, s.config.ContractAddress, s.config.BlockInterval), true
   371  }
   372  
   373  func (s *contractStakingCache) putBucketType(id uint64, bt *BucketType) {
   374  	// remove old bucket map
   375  	if oldBt, existed := s.bucketTypeMap[id]; existed {
   376  		amount := oldBt.Amount.Int64()
   377  		if _, existed := s.propertyBucketTypeMap[amount]; existed {
   378  			delete(s.propertyBucketTypeMap[amount], oldBt.Duration)
   379  			if len(s.propertyBucketTypeMap[amount]) == 0 {
   380  				delete(s.propertyBucketTypeMap, amount)
   381  			}
   382  		}
   383  	}
   384  	// add new bucket map
   385  	s.bucketTypeMap[id] = bt
   386  	amount := bt.Amount.Int64()
   387  	m, ok := s.propertyBucketTypeMap[amount]
   388  	if !ok {
   389  		s.propertyBucketTypeMap[amount] = make(map[uint64]uint64)
   390  		m = s.propertyBucketTypeMap[amount]
   391  	}
   392  	m[bt.Duration] = id
   393  }
   394  
   395  func (s *contractStakingCache) putBucketInfo(id uint64, bi *bucketInfo) {
   396  	oldBi := s.bucketInfoMap[id]
   397  	s.bucketInfoMap[id] = bi
   398  	// update candidate bucket map
   399  	newDelegate := bi.Delegate.String()
   400  	if _, ok := s.candidateBucketMap[newDelegate]; !ok {
   401  		s.candidateBucketMap[newDelegate] = make(map[uint64]bool)
   402  	}
   403  	s.candidateBucketMap[newDelegate][id] = true
   404  	// delete old candidate bucket map
   405  	if oldBi == nil {
   406  		return
   407  	}
   408  	oldDelegate := oldBi.Delegate.String()
   409  	if oldDelegate == newDelegate {
   410  		return
   411  	}
   412  	delete(s.candidateBucketMap[oldDelegate], id)
   413  	if len(s.candidateBucketMap[oldDelegate]) == 0 {
   414  		delete(s.candidateBucketMap, oldDelegate)
   415  	}
   416  }
   417  
   418  func (s *contractStakingCache) deleteBucketInfo(id uint64) {
   419  	bi, ok := s.bucketInfoMap[id]
   420  	if !ok {
   421  		return
   422  	}
   423  	delete(s.bucketInfoMap, id)
   424  	if _, ok := s.candidateBucketMap[bi.Delegate.String()]; !ok {
   425  		return
   426  	}
   427  	delete(s.candidateBucketMap[bi.Delegate.String()], id)
   428  }
   429  
   430  func (s *contractStakingCache) putTotalBucketCount(count uint64) {
   431  	s.totalBucketCount = count
   432  }
   433  
   434  func (s *contractStakingCache) putHeight(height uint64) {
   435  	s.height = height
   436  }
   437  
   438  func (s *contractStakingCache) mergeDelta(delta *contractStakingDelta) error {
   439  	if delta == nil {
   440  		return errors.New("invalid contract staking delta")
   441  	}
   442  	for state, btMap := range delta.BucketTypeDelta() {
   443  		if state == deltaStateAdded || state == deltaStateModified {
   444  			for id, bt := range btMap {
   445  				s.putBucketType(id, bt)
   446  			}
   447  		}
   448  	}
   449  	for state, biMap := range delta.BucketInfoDelta() {
   450  		if state == deltaStateAdded || state == deltaStateModified {
   451  			for id, bi := range biMap {
   452  				s.putBucketInfo(id, bi)
   453  			}
   454  		} else if state == deltaStateRemoved {
   455  			for id := range biMap {
   456  				s.deleteBucketInfo(id)
   457  			}
   458  		}
   459  	}
   460  	return nil
   461  }
   462  
   463  func (s *contractStakingCache) validateHeight(height uint64) error {
   464  	// means latest height
   465  	if height == 0 {
   466  		return nil
   467  	}
   468  	// Currently, historical block data query is not supported.
   469  	// However, the latest data is actually returned when querying historical block data, for the following reasons:
   470  	//	1. to maintain compatibility with the current code's invocation of ActiveCandidate
   471  	//	2. to cause consensus errors when the indexer is lagging behind
   472  	if height > s.height {
   473  		return errors.Wrapf(ErrInvalidHeight, "expected %d, actual %d", s.height, height)
   474  	}
   475  	return nil
   476  }