diff --git a/action/protocol/staking/candidate_center.go b/action/protocol/staking/candidate_center.go index f991cfd296..6b110b4c3e 100644 --- a/action/protocol/staking/candidate_center.go +++ b/action/protocol/staking/candidate_center.go @@ -27,6 +27,7 @@ type ( ownerMap map[string]*Candidate operatorMap map[string]*Candidate selfStkBucketMap map[uint64]*Candidate + owners CandidateList } // CandidateCenter is a struct to manage the candidates @@ -58,12 +59,7 @@ func NewCandidateCenter(all CandidateList) (*CandidateCenter, error) { } c := CandidateCenter{ - base: &candBase{ - nameMap: make(map[string]*Candidate), - ownerMap: make(map[string]*Candidate), - operatorMap: make(map[string]*Candidate), - selfStkBucketMap: make(map[uint64]*Candidate), - }, + base: newCandBase(), change: delta, } @@ -415,6 +411,15 @@ func (cc *candChange) delete(owner address.Address) { // candBase funcs //====================================== +func newCandBase() *candBase { + return &candBase{ + nameMap: make(map[string]*Candidate), + ownerMap: make(map[string]*Candidate), + operatorMap: make(map[string]*Candidate), + selfStkBucketMap: make(map[uint64]*Candidate), + } +} + func (cb *candBase) size() int { cb.lock.RLock() defer cb.lock.RUnlock() diff --git a/action/protocol/staking/candidate_center_extra.go b/action/protocol/staking/candidate_center_extra.go new file mode 100644 index 0000000000..30e9554dd1 --- /dev/null +++ b/action/protocol/staking/candidate_center_extra.go @@ -0,0 +1,99 @@ +// Copyright (c) 2022 IoTeX Foundation +// This is an alpha (internal) release and is not suitable for production. 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 staking + +func (cb *candBase) clone() *candBase { + cb.lock.RLock() + defer cb.lock.RUnlock() + clone := newCandBase() + for name, cand := range cb.nameMap { + clone.nameMap[name] = cand.Clone() + } + for owner, cand := range cb.ownerMap { + clone.ownerMap[owner] = cand.Clone() + } + for operator, cand := range cb.operatorMap { + clone.operatorMap[operator] = cand.Clone() + } + for bucket, cand := range cb.selfStkBucketMap { + clone.selfStkBucketMap[bucket] = cand.Clone() + } + if len(cb.owners) > 0 { + for _, cand := range cb.owners { + clone.owners = append(clone.owners, cand.Clone()) + } + } + return clone +} + +func (cb *candBase) candsInNameMap() CandidateList { + cb.lock.RLock() + defer cb.lock.RUnlock() + if len(cb.nameMap) == 0 { + return nil + } + + list := make(CandidateList, 0, len(cb.nameMap)) + for _, d := range cb.nameMap { + list = append(list, d.Clone()) + } + return list +} + +func (cb *candBase) candsInOperatorMap() CandidateList { + cb.lock.RLock() + defer cb.lock.RUnlock() + if len(cb.operatorMap) == 0 { + return nil + } + + list := make(CandidateList, 0, len(cb.operatorMap)) + for _, d := range cb.operatorMap { + list = append(list, d.Clone()) + } + return list +} + +func (cb *candBase) ownersList() CandidateList { + cb.lock.RLock() + defer cb.lock.RUnlock() + return cb.owners +} + +func (cb *candBase) recordOwner(c *Candidate) { + cb.lock.Lock() + defer cb.lock.Unlock() + for i, d := range cb.owners { + if d.Owner.String() == c.Owner.String() { + cb.owners[i] = c.Clone() + return + } + } + // this is a new candidate + cb.owners = append(cb.owners, c.Clone()) +} + +func (cb *candBase) loadNameOperatorMapOwnerList(name, op, owners CandidateList) error { + cb.lock.Lock() + defer cb.lock.Unlock() + cb.nameMap = make(map[string]*Candidate) + for _, d := range name { + if err := d.Validate(); err != nil { + return err + } + cb.nameMap[d.Name] = d + } + cb.operatorMap = make(map[string]*Candidate) + for _, d := range op { + if err := d.Validate(); err != nil { + return err + } + cb.operatorMap[d.Operator.String()] = d + } + cb.owners = owners + return nil +} diff --git a/action/protocol/staking/candidate_statemanager.go b/action/protocol/staking/candidate_statemanager.go index 950e680a18..7ce38605f4 100644 --- a/action/protocol/staking/candidate_statemanager.go +++ b/action/protocol/staking/candidate_statemanager.go @@ -30,17 +30,15 @@ type ( delBucket(index uint64) error putBucketAndIndex(bucket *VoteBucket) (uint64, error) delBucketAndIndex(owner, cand address.Address, index uint64) error - putBucketIndex(addr address.Address, prefix byte, index uint64) error - delBucketIndex(addr address.Address, prefix byte, index uint64) error } // CandidateSet related to setting candidates CandidateSet interface { - putCandidate(d *Candidate) error - delCandidate(name address.Address) error - putVoterBucketIndex(addr address.Address, index uint64) error - delVoterBucketIndex(addr address.Address, index uint64) error - putCandBucketIndex(addr address.Address, index uint64) error - delCandBucketIndex(addr address.Address, index uint64) error + putCandidate(*Candidate) error + delCandidate(address.Address) error + putVoterBucketIndex(address.Address, uint64) error + delVoterBucketIndex(address.Address, uint64) error + putCandBucketIndex(address.Address, uint64) error + delCandBucketIndex(address.Address, uint64) error } // CandidateStateManager is candidate state manager on top of StateManager CandidateStateManager interface { @@ -55,7 +53,6 @@ type ( ContainsSelfStakingBucket(uint64) bool GetByName(string) *Candidate GetByOwner(address.Address) *Candidate - GetBySelfStakingIndex(uint64) *Candidate Upsert(*Candidate) error CreditBucketPool(*big.Int) error DebitBucketPool(*big.Int, bool) error @@ -141,10 +138,6 @@ func (csm *candSM) GetByOwner(addr address.Address) *Candidate { return csm.candCenter.GetByOwner(addr) } -func (csm *candSM) GetBySelfStakingIndex(index uint64) *Candidate { - return csm.candCenter.GetBySelfStakingIndex(index) -} - // Upsert writes the candidate into state manager and cand center func (csm *candSM) Upsert(d *Candidate) error { if err := csm.candCenter.Upsert(d); err != nil { diff --git a/action/protocol/staking/handlers.go b/action/protocol/staking/handlers.go index 9a9ad805b0..f7861fc390 100644 --- a/action/protocol/staking/handlers.go +++ b/action/protocol/staking/handlers.go @@ -684,6 +684,7 @@ func (p *Protocol) handleCandidateRegister(ctx context.Context, act *action.Cand if err := csm.Upsert(c); err != nil { return log, nil, csmErrorToHandleError(owner.String(), err) } + csm.DirtyView().candCenter.base.recordOwner(c) // update bucket pool if err := csm.DebitBucketPool(act.Amount(), true); err != nil { @@ -763,6 +764,7 @@ func (p *Protocol) handleCandidateUpdate(ctx context.Context, act *action.Candid if err := csm.Upsert(c); err != nil { return log, csmErrorToHandleError(c.Owner.String(), err) } + csm.DirtyView().candCenter.base.recordOwner(c) log.AddAddress(actCtx.Caller) return log, nil diff --git a/action/protocol/staking/protocol.go b/action/protocol/staking/protocol.go index 496aa1b710..363fdd8c28 100644 --- a/action/protocol/staking/protocol.go +++ b/action/protocol/staking/protocol.go @@ -39,6 +39,9 @@ const ( // _candidateNameSpace is the bucket name for candidate state _candidateNameSpace = "Candidate" + + // CandsMapNS is the bucket name to store candidate map + CandsMapNS = "CandsMap" ) const ( @@ -57,6 +60,12 @@ var ( TotalBucketKey = append([]byte{_const}, []byte("totalBucket")...) ) +var ( + _nameKey = []byte("name") + _operatorKey = []byte("operator") + _ownerKey = []byte("owner") +) + type ( // ReceiptError indicates a non-critical error with corresponding receipt status ReceiptError interface { @@ -166,6 +175,16 @@ func (p *Protocol) Start(ctx context.Context, sr protocol.StateReader) (interfac if err != nil { return nil, errors.Wrap(err, "failed to start staking protocol") } + + if p.needToReadCandsMap(height) { + name, operator, owners, err := readCandCenterStateFromStateDB(sr, height) + if err != nil { + return nil, errors.Wrap(err, "failed to read name/operator map") + } + if err = c.candCenter.base.loadNameOperatorMapOwnerList(name, operator, owners); err != nil { + return nil, errors.Wrap(err, "failed to load name/operator map to cand center") + } + } return c, nil } @@ -293,6 +312,36 @@ func (p *Protocol) handleStakingIndexer(epochStartHeight uint64, sm protocol.Sta return p.candBucketsIndexer.PutCandidates(epochStartHeight, candidateList) } +// PreCommit preforms pre-commit +func (p *Protocol) PreCommit(ctx context.Context, sm protocol.StateManager) error { + height, err := sm.Height() + if err != nil { + return err + } + if !p.needToWriteCandsMap(height) { + return nil + } + + featureWithHeightCtx := protocol.MustGetFeatureWithHeightCtx(ctx) + csm, err := NewCandidateStateManager(sm, featureWithHeightCtx.ReadStateFromDB(height)) + if err != nil { + return err + } + cc := csm.DirtyView().candCenter + base := cc.base.clone() + if _, err = base.commit(cc.change); err != nil { + return errors.Wrap(err, "failed to apply candidate change in pre-commit") + } + // persist nameMap/operatorMap and ownerList to stateDB + name := base.candsInNameMap() + op := base.candsInOperatorMap() + owners := base.ownersList() + if len(name) == 0 || len(op) == 0 { + return ErrNilParameters + } + return errors.Wrap(p.writeCandCenterStateToStateDB(sm, name, op, owners), "failed to write name/operator map to stateDB") +} + // Commit commits the last change func (p *Protocol) Commit(ctx context.Context, sm protocol.StateManager) error { featureWithHeightCtx := protocol.MustGetFeatureWithHeightCtx(ctx) @@ -543,3 +592,38 @@ func (p *Protocol) settleAction( r.AddLogs(logs...).AddTransactionLogs(depositLog).AddTransactionLogs(tLogs...) return &r, nil } + +func (p *Protocol) needToReadCandsMap(height uint64) bool { + return height > p.config.PersistStakingPatchBlock +} + +func (p *Protocol) needToWriteCandsMap(height uint64) bool { + return height >= p.config.PersistStakingPatchBlock +} + +func readCandCenterStateFromStateDB(sr protocol.StateReader, height uint64) (CandidateList, CandidateList, CandidateList, error) { + var ( + name, operator, owner CandidateList + ) + if _, err := sr.State(&name, protocol.NamespaceOption(CandsMapNS), protocol.KeyOption(_nameKey)); err != nil { + return nil, nil, nil, err + } + if _, err := sr.State(&operator, protocol.NamespaceOption(CandsMapNS), protocol.KeyOption(_operatorKey)); err != nil { + return nil, nil, nil, err + } + if _, err := sr.State(&owner, protocol.NamespaceOption(CandsMapNS), protocol.KeyOption(_ownerKey)); err != nil { + return nil, nil, nil, err + } + return name, operator, owner, nil +} + +func (p *Protocol) writeCandCenterStateToStateDB(sm protocol.StateManager, name, op, owners CandidateList) error { + if _, err := sm.PutState(name, protocol.NamespaceOption(CandsMapNS), protocol.KeyOption(_nameKey)); err != nil { + return err + } + if _, err := sm.PutState(op, protocol.NamespaceOption(CandsMapNS), protocol.KeyOption(_operatorKey)); err != nil { + return err + } + _, err := sm.PutState(owners, protocol.NamespaceOption(CandsMapNS), protocol.KeyOption(_ownerKey)) + return err +} diff --git a/state/factory/factory.go b/state/factory/factory.go index 738be7d14e..7dd01f2a31 100644 --- a/state/factory/factory.go +++ b/state/factory/factory.go @@ -24,6 +24,7 @@ import ( "github.com/iotexproject/iotex-core/action" "github.com/iotexproject/iotex-core/action/protocol" "github.com/iotexproject/iotex-core/action/protocol/execution/evm" + "github.com/iotexproject/iotex-core/action/protocol/staking" "github.com/iotexproject/iotex-core/actpool" "github.com/iotexproject/iotex-core/blockchain" "github.com/iotexproject/iotex-core/blockchain/block" @@ -296,7 +297,7 @@ func (sf *factory) flusherOptions(preEaster bool) []db.KVStoreFlusherOption { if wi.Namespace() == ArchiveTrieNamespace { return true } - if wi.Namespace() != evm.CodeKVNameSpace { + if wi.Namespace() != evm.CodeKVNameSpace && wi.Namespace() != staking.CandsMapNS { return false } return preEaster diff --git a/state/factory/statedb.go b/state/factory/statedb.go index 3693cac57d..12f57cb864 100644 --- a/state/factory/statedb.go +++ b/state/factory/statedb.go @@ -23,6 +23,7 @@ import ( "github.com/iotexproject/iotex-core/action" "github.com/iotexproject/iotex-core/action/protocol" "github.com/iotexproject/iotex-core/action/protocol/execution/evm" + "github.com/iotexproject/iotex-core/action/protocol/staking" "github.com/iotexproject/iotex-core/actpool" "github.com/iotexproject/iotex-core/blockchain/block" "github.com/iotexproject/iotex-core/blockchain/genesis" @@ -430,7 +431,7 @@ func (sdb *stateDB) flusherOptions(preEaster bool) []db.KVStoreFlusherOption { return append( opts, db.SerializeFilterOption(func(wi *batch.WriteInfo) bool { - return wi.Namespace() == evm.CodeKVNameSpace + return wi.Namespace() == evm.CodeKVNameSpace || wi.Namespace() == staking.CandsMapNS }), ) }