aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/test/governance.go77
-rw-r--r--core/test/governance_test.go30
-rw-r--r--core/test/state.go15
3 files changed, 113 insertions, 9 deletions
diff --git a/core/test/governance.go b/core/test/governance.go
index 4a2b61d..f07e7da 100644
--- a/core/test/governance.go
+++ b/core/test/governance.go
@@ -19,6 +19,8 @@ package test
import (
"encoding/hex"
+ "errors"
+ "fmt"
"reflect"
"sort"
"sync"
@@ -33,10 +35,11 @@ import (
// Governance is an implementation of Goverance for testing purpose.
type Governance struct {
- configs []*types.Config
- nodeSets [][]crypto.PublicKey
- state *State
- lock sync.RWMutex
+ configs []*types.Config
+ nodeSets [][]crypto.PublicKey
+ state *State
+ pendingConfigChanges map[uint64]map[StateChangeType]interface{}
+ lock sync.RWMutex
}
// NewGovernance constructs a Governance instance.
@@ -47,7 +50,8 @@ func NewGovernance(genesisNodes []crypto.PublicKey,
// public class in another, I did this to make the range of
// modification smaller.
g = &Governance{
- state: NewState(genesisNodes, lambda, true),
+ pendingConfigChanges: make(map[uint64]map[StateChangeType]interface{}),
+ state: NewState(genesisNodes, lambda, true),
}
return
}
@@ -77,7 +81,7 @@ func (g *Governance) Configuration(round uint64) *types.Config {
}
g.lock.RLock()
defer g.lock.RUnlock()
- if round >= uint64(len(g.nodeSets)) {
+ if round >= uint64(len(g.configs)) {
return nil
}
return g.configs[round]
@@ -91,6 +95,17 @@ func (g *Governance) CRS(round uint64) common.Hash {
// NotifyRoundHeight notifies governace contract to snapshot config.
func (g *Governance) NotifyRoundHeight(round, height uint64) {
g.CatchUpWithRound(round)
+ // Apply change request for next round.
+ func() {
+ g.lock.Lock()
+ defer g.lock.Unlock()
+ for t, v := range g.pendingConfigChanges[round+1] {
+ if err := g.state.RequestChange(t, v); err != nil {
+ panic(err)
+ }
+ }
+ delete(g.pendingConfigChanges, round+1)
+ }()
}
// ProposeCRS propose a CRS.
@@ -202,6 +217,14 @@ func (g *Governance) Clone() *Governance {
copiedConfigs = append(copiedConfigs, c.Clone())
}
// Clone node sets.
+ copiedPendingChanges := make(map[uint64]map[StateChangeType]interface{})
+ for round, forRound := range g.pendingConfigChanges {
+ copiedForRound := make(map[StateChangeType]interface{})
+ for k, v := range forRound {
+ copiedForRound[k] = v
+ }
+ copiedPendingChanges[round] = copiedForRound
+ }
// NOTE: here I assume the key is from ecdsa.
copiedNodeSets := [][]crypto.PublicKey{}
for _, nodeSetForRound := range g.nodeSets {
@@ -215,10 +238,13 @@ func (g *Governance) Clone() *Governance {
}
copiedNodeSets = append(copiedNodeSets, copiedNodeSet)
}
+ // Clone pending changes.
+
return &Governance{
- configs: copiedConfigs,
- state: copiedState,
- nodeSets: copiedNodeSets,
+ configs: copiedConfigs,
+ state: copiedState,
+ nodeSets: copiedNodeSets,
+ pendingConfigChanges: copiedPendingChanges,
}
}
@@ -232,6 +258,10 @@ func (g *Governance) Equal(other *Governance, checkState bool) bool {
if len(g.nodeSets) != len(other.nodeSets) {
return false
}
+ // Check pending changes.
+ if !reflect.DeepEqual(g.pendingConfigChanges, other.pendingConfigChanges) {
+ return false
+ }
getSortedKeys := func(keys []crypto.PublicKey) (encoded []string) {
for _, key := range keys {
encoded = append(encoded, hex.EncodeToString(key.Bytes()))
@@ -260,3 +290,32 @@ func (g *Governance) Equal(other *Governance, checkState bool) bool {
}
return true
}
+
+// RegisterConfigChange tells this governance instance to request some
+// configuration change at some round.
+// NOTE: you can't request config change for round 0, 1, they are genesis
+// rounds.
+// NOTE: this function should be called before running.
+func (g *Governance) RegisterConfigChange(
+ round uint64, t StateChangeType, v interface{}) (err error) {
+ if t < StateChangeNumChains || t > StateChangeDKGSetSize {
+ return fmt.Errorf("state changes to register is not supported: %v", t)
+ }
+ if round < 2 {
+ return errors.New(
+ "attempt to register state change for genesis rounds")
+ }
+ g.lock.Lock()
+ defer g.lock.Unlock()
+ if round <= uint64(len(g.configs)) {
+ return errors.New(
+ "attempt to register state change for prepared rounds")
+ }
+ pendingChangesForRound, exists := g.pendingConfigChanges[round]
+ if !exists {
+ pendingChangesForRound = make(map[StateChangeType]interface{})
+ g.pendingConfigChanges[round] = pendingChangesForRound
+ }
+ pendingChangesForRound[t] = v
+ return nil
+}
diff --git a/core/test/governance_test.go b/core/test/governance_test.go
index 16de2a1..fe0dde7 100644
--- a/core/test/governance_test.go
+++ b/core/test/governance_test.go
@@ -59,6 +59,36 @@ func (s *GovernanceTestSuite) TestEqual() {
req.False(g1.Equal(g4, true))
}
+func (s *GovernanceTestSuite) TestRegisterChange() {
+ req := s.Require()
+ _, genesisNodes, err := NewKeys(20)
+ req.NoError(err)
+ g, err := NewGovernance(genesisNodes, 100*time.Millisecond)
+ req.NoError(err)
+ // Unable to register change for genesis round.
+ req.Error(g.RegisterConfigChange(0, StateChangeNumChains, uint32(32)))
+ // Make some round prepared.
+ g.CatchUpWithRound(4)
+ req.Equal(g.Configuration(4).NumChains, uint32(20))
+ // Unable to register change for prepared round.
+ req.Error(g.RegisterConfigChange(4, StateChangeNumChains, uint32(32)))
+ // Unable to register change for next notified round.
+ req.Error(g.RegisterConfigChange(5, StateChangeNumChains, uint32(32)))
+ // It's ok to make some change when condition is met.
+ req.NoError(g.RegisterConfigChange(6, StateChangeNumChains, uint32(32)))
+ req.NoError(g.RegisterConfigChange(7, StateChangeNumChains, uint32(40)))
+ // In local mode, state for round 6 would be ready after notified with
+ // round 5.
+ g.NotifyRoundHeight(5, 0)
+ // In local mode, state for round 7 would be ready after notified with
+ // round 6.
+ g.NotifyRoundHeight(6, 0)
+ // Notify governance to take a snapshot for round 7's configuration.
+ g.NotifyRoundHeight(7, 0)
+ req.Equal(g.Configuration(6).NumChains, uint32(32))
+ req.Equal(g.Configuration(7).NumChains, uint32(40))
+}
+
func TestGovernance(t *testing.T) {
suite.Run(t, new(GovernanceTestSuite))
}
diff --git a/core/test/state.go b/core/test/state.go
index 34c38bc..51c7ab7 100644
--- a/core/test/state.go
+++ b/core/test/state.go
@@ -180,6 +180,14 @@ func NewState(
}
}
+// SwitchToRemoteMode turn this State instance into remote mode: all changes
+// are pending, and need to be packed/unpacked to apply.
+func (s *State) SwitchToRemoteMode() {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+ s.local = false
+}
+
// Snapshot returns configration that could be snapshotted.
func (s *State) Snapshot() (*types.Config, []crypto.PublicKey) {
s.lock.RLock()
@@ -575,6 +583,13 @@ func (s *State) Apply(reqsAsBytes []byte) (err error) {
s.lock.Lock()
defer s.lock.Unlock()
for _, req := range reqs {
+ if err = s.isValidRequest(req); err != nil {
+ if err == ErrDuplicatedChange {
+ err = nil
+ continue
+ }
+ return
+ }
if err = s.applyRequest(req); err != nil {
return
}