Skip to content

Commit

Permalink
Merge cfc602e into 75cb0a1
Browse files Browse the repository at this point in the history
  • Loading branch information
dustinxie committed Oct 14, 2022
2 parents 75cb0a1 + cfc602e commit 263ff82
Show file tree
Hide file tree
Showing 21 changed files with 442 additions and 44 deletions.
5 changes: 5 additions & 0 deletions action/protocol/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ type PreStatesCreator interface {
CreatePreStates(context.Context, StateManager) error
}

// PreCommitter performs pre-commit action of the protocol
type PreCommitter interface {
PreCommit(context.Context, StateManager) error
}

// Committer performs commit action of the protocol
type Committer interface {
Commit(context.Context, StateManager) error
Expand Down
13 changes: 13 additions & 0 deletions action/protocol/staking/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package staking

import "github.com/iotexproject/iotex-core/blockchain/genesis"

type (

// BuilderConfig returns the configuration of the builder
BuilderConfig struct {
Staking genesis.Staking
PersistStakingPatchBlock uint64
StakingPatchDir string
}
)
17 changes: 11 additions & 6 deletions action/protocol/staking/candidate_center.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
}

Expand Down Expand Up @@ -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()
Expand Down
99 changes: 99 additions & 0 deletions action/protocol/staking/candidate_center_extra.go
Original file line number Diff line number Diff line change
@@ -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
}
19 changes: 6 additions & 13 deletions action/protocol/staking/candidate_statemanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions action/protocol/staking/candidate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ func TestSer(t *testing.T) {
l1 := &CandidateList{}
r.NoError(l1.Deserialize(ser))
r.Equal(l, l1)

// empty CandidateList can successfully Serialize/Deserialize
var m CandidateList
ser, err = m.Serialize()
r.NoError(err)
r.Equal([]byte{}, ser)
var m1 CandidateList
r.NoError(m1.Deserialize(ser))
r.Nil(m1)
}

func TestClone(t *testing.T) {
Expand Down
2 changes: 2 additions & 0 deletions action/protocol/staking/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
11 changes: 9 additions & 2 deletions action/protocol/staking/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"context"
"encoding/hex"
"encoding/json"
"math"
"math/big"
"testing"
"time"
Expand Down Expand Up @@ -71,7 +72,10 @@ func TestProtocol_HandleCreateStake(t *testing.T) {
require.NoError(err)

// create protocol
p, err := NewProtocol(depositGas, genesis.Default.Staking, nil, genesis.Default.GreenlandBlockHeight)
p, err := NewProtocol(depositGas, &BuilderConfig{
Staking: genesis.Default.Staking,
PersistStakingPatchBlock: math.MaxUint64,
}, nil, genesis.Default.GreenlandBlockHeight)
require.NoError(err)

// set up candidate
Expand Down Expand Up @@ -2664,7 +2668,10 @@ func initAll(t *testing.T, ctrl *gomock.Controller) (protocol.StateManager, *Pro
require.NoError(err)

// create protocol
p, err := NewProtocol(depositGas, genesis.Default.Staking, nil, genesis.Default.GreenlandBlockHeight)
p, err := NewProtocol(depositGas, &BuilderConfig{
Staking: genesis.Default.Staking,
PersistStakingPatchBlock: math.MaxUint64,
}, nil, genesis.Default.GreenlandBlockHeight)
require.NoError(err)

// set up candidate
Expand Down
80 changes: 80 additions & 0 deletions action/protocol/staking/patchstore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) 2020 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

import (
"encoding/csv"
"encoding/hex"
"fmt"
"io"
"os"
"path/filepath"

"github.com/pkg/errors"
)

const (
_name = "name"
_operator = "operator"
)

// PatchStore is the patch store of staking protocol
type PatchStore struct {
dir string
}

// NewPatchStore creates a new staking patch store
func NewPatchStore(dir string) *PatchStore {
return &PatchStore{dir: dir}
}

func (store *PatchStore) pathOf(height uint64) string {
return filepath.Join(store.dir, fmt.Sprintf("%d.patch", height))
}

func (store *PatchStore) read(reader *csv.Reader) (CandidateList, error) {
record, err := reader.Read()
if err != nil {
return nil, err
}
if len(record) != 1 {
return nil, errors.Errorf("invalid record %+v", record)
}
data, err := hex.DecodeString(record[0])
if err != nil {
return nil, err
}
var list CandidateList
if err := list.Deserialize(data); err != nil {
return nil, err
}
return list, nil
}

// Read reads CandidateList by name and CandidateList by operator of given height
func (store *PatchStore) Read(height uint64) (CandidateList, CandidateList, CandidateList, error) {
file, err := os.Open(store.pathOf(height))
if err != nil {
return nil, nil, nil, err
}
reader := csv.NewReader(file)
reader.FieldsPerRecord = -1
listByName, err := store.read(reader)
if err != nil {
return nil, nil, nil, err
}
listByOperator, err := store.read(reader)
if err != nil {
return nil, nil, nil, err
}
listByOwner, err := store.read(reader)
if err != nil && err != io.EOF {
// io.EOF indicates an empty owner list
return nil, nil, nil, err
}
return listByName, listByOperator, listByOwner, nil
}
38 changes: 38 additions & 0 deletions action/protocol/staking/patchstore_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) 2019 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

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
)

func TestInvalidDirectory(t *testing.T) {
require := require.New(t)
dir := filepath.Join(t.TempDir(), "invalid")
_, err := os.Create(dir)
require.NoError(err)
_, _, _, err = NewPatchStore(dir).Read(0)
require.ErrorContains(err, "not a directory")
}

func TestInvalidDirectory2(t *testing.T) {
require := require.New(t)
dir := t.TempDir()
require.NoError(os.Remove(dir))
_, err := os.Stat(dir)
require.ErrorIs(err, os.ErrNotExist)
_, _, _, err = NewPatchStore(dir).Read(0)
require.ErrorContains(err, "no such file or directory")
}

func TestCorruptedData(t *testing.T) {
// TODO: add test for corrupted data
}
Loading

0 comments on commit 263ff82

Please sign in to comment.