Skip to content

Commit

Permalink
indexer read
Browse files Browse the repository at this point in the history
  • Loading branch information
envestcc committed May 19, 2023
1 parent 955313e commit 37b2ea6
Show file tree
Hide file tree
Showing 3 changed files with 300 additions and 0 deletions.
19 changes: 19 additions & 0 deletions action/protocol/staking/contractstaking/bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
188 changes: 188 additions & 0 deletions action/protocol/staking/contractstaking/cache.go
Original file line number Diff line number Diff line change
@@ -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
}
93 changes: 93 additions & 0 deletions action/protocol/staking/contractstaking/indexer.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 37b2ea6

Please sign in to comment.