From 37b2ea66776b077d0f74c281296e3102dfae3331 Mon Sep 17 00:00:00 2001 From: envestcc Date: Fri, 19 May 2023 20:52:03 +0800 Subject: [PATCH] indexer read --- .../staking/contractstaking/bucket.go | 19 ++ .../protocol/staking/contractstaking/cache.go | 188 ++++++++++++++++++ .../staking/contractstaking/indexer.go | 93 +++++++++ 3 files changed, 300 insertions(+) create mode 100644 action/protocol/staking/contractstaking/cache.go create mode 100644 action/protocol/staking/contractstaking/indexer.go diff --git a/action/protocol/staking/contractstaking/bucket.go b/action/protocol/staking/contractstaking/bucket.go index 75548071ae..0e34336acc 100644 --- a/action/protocol/staking/contractstaking/bucket.go +++ b/action/protocol/staking/contractstaking/bucket.go @@ -24,3 +24,22 @@ type Bucket struct { AutoStake bool ContractAddress string } + +func assembleBucket(token uint64, bi *bucketInfo, bt *BucketType) (*Bucket, error) { + vb := Bucket{ + Index: token, + StakedAmount: bt.Amount, + StakedDurationBlockNumber: bt.Duration, + CreateBlockHeight: bi.CreatedAt, + StakeStartBlockHeight: bi.CreatedAt, + UnstakeStartBlockHeight: bi.UnstakedAt, + AutoStake: bi.UnlockedAt == maxBlockNumber, + Candidate: bi.Delegate, + Owner: bi.Owner, + ContractAddress: StakingContractAddress, + } + if bi.UnlockedAt != maxBlockNumber { + vb.StakeStartBlockHeight = bi.UnlockedAt + } + return &vb, nil +} diff --git a/action/protocol/staking/contractstaking/cache.go b/action/protocol/staking/contractstaking/cache.go new file mode 100644 index 0000000000..b3e1de5348 --- /dev/null +++ b/action/protocol/staking/contractstaking/cache.go @@ -0,0 +1,188 @@ +// Copyright (c) 2023 IoTeX Foundation +// This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability +// or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. +// This source code is governed by Apache License 2.0 that can be found in the LICENSE file. + +package contractstaking + +import ( + "math/big" + + "github.com/iotexproject/iotex-address/address" + "github.com/pkg/errors" +) + +type ( + contractStakingCache struct { + idBucketMap map[uint64]*bucketInfo // map[token]bucketInfo + candidateBucketMap map[string]map[uint64]bool // map[candidate]bucket + idBucketTypeMap map[uint64]*BucketType // map[token]BucketType + propertyBucketTypeMap map[int64]map[uint64]uint64 // map[amount][duration]index + height uint64 + totalBucketCount uint64 // total number of buckets including burned buckets + } +) + +var ( + // ErrBucketNotExist is the error when bucket does not exist + ErrBucketNotExist = errors.New("bucket does not exist") +) + +func newContractStakingCache() *contractStakingCache { + cache := &contractStakingCache{ + idBucketMap: make(map[uint64]*bucketInfo), + idBucketTypeMap: make(map[uint64]*BucketType), + propertyBucketTypeMap: make(map[int64]map[uint64]uint64), + candidateBucketMap: make(map[string]map[uint64]bool), + } + return cache +} + +func (s *contractStakingCache) GetHeight() uint64 { + return s.height +} + +func (s *contractStakingCache) GetCandidateVotes(candidate address.Address) *big.Int { + votes := big.NewInt(0) + m, ok := s.candidateBucketMap[candidate.String()] + if !ok { + return votes + } + for id, existed := range m { + if !existed { + continue + } + bi, ok := s.idBucketMap[id] + if !ok { + continue + } + if bi.UnstakedAt != maxBlockNumber { + continue + } + bt := s.mustGetBucketType(bi.TypeIndex) + votes.Add(votes, bt.Amount) + } + return votes +} + +func (s *contractStakingCache) GetBuckets() ([]*Bucket, error) { + vbs := []*Bucket{} + for id, bi := range s.getAllBucketInfo() { + bt := s.mustGetBucketType(bi.TypeIndex) + vb, err := assembleBucket(id, bi, bt) + if err != nil { + return nil, err + } + vbs = append(vbs, vb) + } + return vbs, nil +} + +func (s *contractStakingCache) GetBucket(id uint64) (*Bucket, error) { + return s.getBucket(id) +} + +func (s *contractStakingCache) GetBucketsByCandidate(candidate address.Address) ([]*Bucket, error) { + bucketMap := s.getBucketInfoByCandidate(candidate) + vbs := make([]*Bucket, 0, len(bucketMap)) + for id := range bucketMap { + vb, err := s.getBucket(id) + if err != nil { + return nil, err + } + vbs = append(vbs, vb) + } + return vbs, nil +} + +func (s *contractStakingCache) GetBucketsByIndices(indices []uint64) ([]*Bucket, error) { + vbs := make([]*Bucket, 0, len(indices)) + for _, id := range indices { + vb, err := s.getBucket(id) + if err != nil { + return nil, err + } + vbs = append(vbs, vb) + } + return vbs, nil +} + +func (s *contractStakingCache) GetTotalBucketCount() uint64 { + return s.totalBucketCount +} + +func (s *contractStakingCache) GetActiveBucketTypes() map[uint64]*BucketType { + m := make(map[uint64]*BucketType) + for k, v := range s.idBucketTypeMap { + if v.ActivatedAt != maxBlockNumber { + m[k] = v + } + } + return m +} + +func (s *contractStakingCache) getBucketTypeIndex(amount *big.Int, duration uint64) (uint64, bool) { + m, ok := s.propertyBucketTypeMap[amount.Int64()] + if !ok { + return 0, false + } + id, ok := m[duration] + return id, ok +} + +func (s *contractStakingCache) getBucketType(id uint64) (*BucketType, bool) { + bt, ok := s.idBucketTypeMap[id] + return bt, ok +} + +func (s *contractStakingCache) mustGetBucketType(id uint64) *BucketType { + bt, ok := s.idBucketTypeMap[id] + if !ok { + panic("bucket type not found") + } + return bt +} + +func (s *contractStakingCache) getBucketInfo(id uint64) (*bucketInfo, bool) { + bi, ok := s.idBucketMap[id] + return bi, ok +} + +func (s *contractStakingCache) mustGetBucketInfo(id uint64) *bucketInfo { + bt, ok := s.idBucketMap[id] + if !ok { + panic("bucket info not found") + } + return bt +} + +func (s *contractStakingCache) getBucket(id uint64) (*Bucket, error) { + bi, ok := s.getBucketInfo(id) + if !ok { + return nil, errors.Wrapf(ErrBucketNotExist, "id %d", id) + } + bt := s.mustGetBucketType(bi.TypeIndex) + return assembleBucket(id, bi, bt) +} + +func (s *contractStakingCache) getTotalBucketTypeCount() uint64 { + return uint64(len(s.idBucketTypeMap)) +} + +func (s *contractStakingCache) getAllBucketInfo() map[uint64]*bucketInfo { + m := make(map[uint64]*bucketInfo) + for k, v := range s.idBucketMap { + m[k] = v + } + return m +} + +func (s *contractStakingCache) getBucketInfoByCandidate(candidate address.Address) map[uint64]*bucketInfo { + m := make(map[uint64]*bucketInfo) + for k, v := range s.candidateBucketMap[candidate.String()] { + if v { + m[k] = s.idBucketMap[k] + } + } + return m +} diff --git a/action/protocol/staking/contractstaking/indexer.go b/action/protocol/staking/contractstaking/indexer.go new file mode 100644 index 0000000000..3a4c3164e4 --- /dev/null +++ b/action/protocol/staking/contractstaking/indexer.go @@ -0,0 +1,93 @@ +// Copyright (c) 2023 IoTeX Foundation +// This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability +// or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. +// This source code is governed by Apache License 2.0 that can be found in the LICENSE file. + +package contractstaking + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common/math" + "github.com/iotexproject/iotex-address/address" + + "github.com/iotexproject/iotex-core/db" +) + +const ( + // StakingContractAddress is the address of system staking contract + // TODO (iip-13): replace with the real system staking contract address + StakingContractAddress = "io19ys8f4uhwms6lq6ulexr5fwht9gsjes8mvuugd" + + maxBlockNumber uint64 = math.MaxUint64 +) + +type ( + // Indexer is the contract staking indexer + // Main functions: + // 1. handle contract staking contract events when new block comes to generate index data + // 2. provide query interface for contract staking index data + // Generate index data flow: + // block comes -> new dirty cache -> handle contract events -> update dirty cache -> merge dirty to clean cache + // Main Object: + // kvstore: persistent storage, used to initialize index cache at startup + // cache: in-memory index for clean data, used to query index data + // dirty: the cache to update during event processing, will be merged to clean cache after all events are processed. If errors occur during event processing, dirty cache will be discarded. + Indexer struct { + kvstore db.KVStore // persistent storage + cache *contractStakingCache // in-memory index for clean data + } +) + +// NewContractStakingIndexer creates a new contract staking indexer +func NewContractStakingIndexer(kvStore db.KVStore) *Indexer { + return &Indexer{ + kvstore: kvStore, + cache: newContractStakingCache(), + } +} + +// Height returns the tip block height +func (s *Indexer) Height() (uint64, error) { + return s.cache.GetHeight(), nil +} + +// CandidateVotes returns the candidate votes +func (s *Indexer) CandidateVotes(candidate address.Address) *big.Int { + return s.cache.GetCandidateVotes(candidate) +} + +// Buckets returns the buckets +func (s *Indexer) Buckets() ([]*Bucket, error) { + return s.cache.GetBuckets() +} + +// Bucket returns the bucket +func (s *Indexer) Bucket(id uint64) (*Bucket, error) { + return s.cache.GetBucket(id) +} + +// BucketsByIndices returns the buckets by indices +func (s *Indexer) BucketsByIndices(indices []uint64) ([]*Bucket, error) { + return s.cache.GetBucketsByIndices(indices) +} + +// BucketsByCandidate returns the buckets by candidate +func (s *Indexer) BucketsByCandidate(candidate address.Address) ([]*Bucket, error) { + return s.cache.GetBucketsByCandidate(candidate) +} + +// TotalBucketCount returns the total bucket count including active and burnt buckets +func (s *Indexer) TotalBucketCount() uint64 { + return s.cache.GetTotalBucketCount() +} + +// BucketTypes returns the active bucket types +func (s *Indexer) BucketTypes() ([]*BucketType, error) { + btMap := s.cache.GetActiveBucketTypes() + bts := make([]*BucketType, 0, len(btMap)) + for _, bt := range btMap { + bts = append(bts, bt) + } + return bts, nil +}