aboutsummaryrefslogblamecommitdiffstats
path: root/core/test/state.go
blob: 05394ed0ab267a0a51eb6632d2af1fcfcc37bbee (plain) (tree)
1
2
3
4
5
6
7
8
9

                                                    
  
                                                                        



                                                                               
                                                                         




                                                                           
                                                      




                                  
               

                
                 


              




                                                                             

















                                                                               





















                                                                                    
























































                                                                            


                                                                                 








                                                                                  


                                                              





























                                                                                  
                                                                        
                                    
                                                                           
                                          
                                                                               


         







                                                                            


















                                                                









                                                                
                                         

                                                     
                                               

                                                     
                                        





















































                                                        

























































































































































































































































































                                                                                            





















                                                                        






                                                            


































































                                                                         
                                                         




















































                                                                                    
                                                         

                                                                      
                                                                       



                                                                           
                                                               

                                                                            
                                                                           


                                                                         
                                                         
                                                                   
                                                                                            



















































































                                                                                  
                                                                              

                                                      
                                                                                          

                                            
                                                                         

















                                                                               
                                                                   





                                               
                                                              

                                               
                                                                              







                                                                              
                                                                               





                                                                
                                                                           
                                              
                                                                   










                                                                               
// Copyright 2018 The dexon-consensus Authors
// This file is part of the dexon-consensus library.
//
// The dexon-consensus library is free software: you can redistribute it
// and/or modify it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// The dexon-consensus library is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
// General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the dexon-consensus library. If not, see
// <http://www.gnu.org/licenses/>.

package test

import (
    "bytes"
    "errors"
    "math"
    "reflect"
    "sync"
    "time"

    "github.com/dexon-foundation/dexon-consensus/common"
    "github.com/dexon-foundation/dexon-consensus/core/crypto"
    "github.com/dexon-foundation/dexon-consensus/core/crypto/ecdsa"
    "github.com/dexon-foundation/dexon-consensus/core/types"
    typesDKG "github.com/dexon-foundation/dexon-consensus/core/types/dkg"
    "github.com/dexon-foundation/dexon/rlp"
)

// StateChangeType is the type of state change request.
type StateChangeType uint8

var (
    // ErrDuplicatedChange means the change request is already applied.
    ErrDuplicatedChange = errors.New("duplicated change")
    // ErrForkedCRS means a different CRS for one round is proposed.
    ErrForkedCRS = errors.New("forked CRS")
    // ErrMissingPreviousCRS means previous CRS not found when
    // proposing a specific round of CRS.
    ErrMissingPreviousCRS = errors.New("missing previous CRS")
    // ErrUnknownStateChangeType means a StateChangeType is not recognized.
    ErrUnknownStateChangeType = errors.New("unknown state change type")
    // ErrProposerIsFinal means a proposer of one complaint is finalized.
    ErrProposerIsFinal = errors.New("proposer is final")
    // ErrStateConfigNotEqual means configuration part of two states is not
    // equal.
    ErrStateConfigNotEqual = errors.New("config not equal")
    // ErrStateLocalFlagNotEqual means local flag of two states is not equal.
    ErrStateLocalFlagNotEqual = errors.New("local flag not equal")
    // ErrStateNodeSetNotEqual means node sets of two states are not equal.
    ErrStateNodeSetNotEqual = errors.New("node set not equal")
    // ErrStateDKGComplaintsNotEqual means DKG complaints for two states are not
    // equal.
    ErrStateDKGComplaintsNotEqual = errors.New("dkg complaints not equal")
    // ErrStateDKGMasterPublicKeysNotEqual means DKG master public keys of two
    // states are not equal.
    ErrStateDKGMasterPublicKeysNotEqual = errors.New(
        "dkg master public keys not equal")
    // ErrStateDKGFinalsNotEqual means DKG finalizations of two states are not
    // equal.
    ErrStateDKGFinalsNotEqual = errors.New("dkg finalizations not equal")
    // ErrStateCRSsNotEqual means CRSs of two states are not equal.
    ErrStateCRSsNotEqual = errors.New("crs not equal")
    // ErrStatePendingChangesNotEqual means pending change requests of two
    // states are not equal.
    ErrStatePendingChangesNotEqual = errors.New("pending changes not equal")
)

// Types of state change.
const (
    StateChangeNothing StateChangeType = iota
    // DKG & CRS
    StateAddCRS
    StateAddDKGComplaint
    StateAddDKGMasterPublicKey
    StateAddDKGFinal
    // Configuration related.
    StateChangeNumChains
    StateChangeLambdaBA
    StateChangeLambdaDKG
    StateChangeRoundInterval
    StateChangeMinBlockInterval
    StateChangeMaxBlockInterval
    StateChangeK
    StateChangePhiRatio
    StateChangeNotarySetSize
    StateChangeDKGSetSize
    // Node set related.
    StateAddNode
)

type crsAdditionRequest struct {
    Round uint64      `json:"round"`
    CRS   common.Hash `json:"crs"`
}

// StateChangeRequest carries information of state change request.
type StateChangeRequest struct {
    Type    StateChangeType `json:"type"`
    Payload interface{}     `json:"payload"`
}

type rawStateChangeRequest struct {
    Type    StateChangeType
    Payload rlp.RawValue
}

// State emulates what the global state in governace contract on a fullnode.
type State struct {
    // Configuration related.
    numChains        uint32
    lambdaBA         time.Duration
    lambdaDKG        time.Duration
    k                int
    phiRatio         float32
    notarySetSize    uint32
    dkgSetSize       uint32
    roundInterval    time.Duration
    minBlockInterval time.Duration
    maxBlockInterval time.Duration
    // Nodes
    nodes map[types.NodeID]crypto.PublicKey
    // DKG & CRS
    dkgComplaints       map[uint64]map[types.NodeID][]*typesDKG.Complaint
    dkgMasterPublicKeys map[uint64]map[types.NodeID]*typesDKG.MasterPublicKey
    dkgFinals           map[uint64]map[types.NodeID]*typesDKG.Finalize
    crs                 []common.Hash
    // Other stuffs
    local bool
    lock  sync.RWMutex
    // ChangeRequest(s) are organized as map, indexed by type of state change.
    // For each time to apply state change, only the last request would be
    // applied.
    pendingChangedConfigs      map[StateChangeType]interface{}
    pendingNodes               [][]byte
    pendingDKGComplaints       []*typesDKG.Complaint
    pendingDKGFinals           []*typesDKG.Finalize
    pendingDKGMasterPublicKeys []*typesDKG.MasterPublicKey
    pendingCRS                 []*crsAdditionRequest
    pendingChangesLock         sync.Mutex
}

// NewState constructs an State instance with genesis information, including:
//  - node set
//  - crs
func NewState(
    nodePubKeys []crypto.PublicKey, lambda time.Duration, local bool) *State {
    nodes := make(map[types.NodeID]crypto.PublicKey)
    for _, key := range nodePubKeys {
        nodes[types.NewNodeID(key)] = key
    }
    genesisCRS := crypto.Keccak256Hash([]byte("__ DEXON"))
    return &State{
        local:                 local,
        numChains:             uint32(len(nodes)),
        lambdaBA:              lambda,
        lambdaDKG:             lambda * 10,
        roundInterval:         lambda * 10000,
        minBlockInterval:      time.Millisecond * 1,
        maxBlockInterval:      lambda * 8,
        crs:                   []common.Hash{genesisCRS},
        nodes:                 nodes,
        phiRatio:              0.667,
        k:                     0,
        notarySetSize:         uint32(len(nodes)),
        dkgSetSize:            uint32(len(nodes)),
        pendingChangedConfigs: make(map[StateChangeType]interface{}),
        dkgFinals: make(
            map[uint64]map[types.NodeID]*typesDKG.Finalize),
        dkgComplaints: make(
            map[uint64]map[types.NodeID][]*typesDKG.Complaint),
        dkgMasterPublicKeys: make(
            map[uint64]map[types.NodeID]*typesDKG.MasterPublicKey),
    }
}

// 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()
    defer s.lock.RUnlock()
    // Clone a node set.
    nodes := make([]crypto.PublicKey, 0, len(s.nodes))
    for _, key := range s.nodes {
        nodes = append(nodes, key)
    }
    return &types.Config{
        NumChains:        s.numChains,
        LambdaBA:         s.lambdaBA,
        LambdaDKG:        s.lambdaDKG,
        K:                s.k,
        PhiRatio:         s.phiRatio,
        NotarySetSize:    s.notarySetSize,
        DKGSetSize:       s.dkgSetSize,
        RoundInterval:    s.roundInterval,
        MinBlockInterval: s.minBlockInterval,
    }, nodes
}

func (s *State) unpackPayload(
    raw *rawStateChangeRequest) (v interface{}, err error) {
    switch raw.Type {
    case StateAddCRS:
        v = &crsAdditionRequest{}
        err = rlp.DecodeBytes(raw.Payload, v)
    case StateAddDKGComplaint:
        v = &typesDKG.Complaint{}
        err = rlp.DecodeBytes(raw.Payload, v)
    case StateAddDKGMasterPublicKey:
        v = &typesDKG.MasterPublicKey{}
        err = rlp.DecodeBytes(raw.Payload, v)
    case StateAddDKGFinal:
        v = &typesDKG.Finalize{}
        err = rlp.DecodeBytes(raw.Payload, v)
    case StateChangeNumChains:
        var tmp uint32
        err = rlp.DecodeBytes(raw.Payload, &tmp)
        v = tmp
    case StateChangeLambdaBA:
        var tmp uint64
        err = rlp.DecodeBytes(raw.Payload, &tmp)
        v = tmp
    case StateChangeLambdaDKG:
        var tmp uint64
        err = rlp.DecodeBytes(raw.Payload, &tmp)
        v = tmp
    case StateChangeRoundInterval:
        var tmp uint64
        err = rlp.DecodeBytes(raw.Payload, &tmp)
        v = tmp
    case StateChangeMinBlockInterval:
        var tmp uint64
        err = rlp.DecodeBytes(raw.Payload, &tmp)
        v = tmp
    case StateChangeMaxBlockInterval:
        var tmp uint64
        err = rlp.DecodeBytes(raw.Payload, &tmp)
        v = tmp
    case StateChangeK:
        var tmp uint64
        err = rlp.DecodeBytes(raw.Payload, &tmp)
        v = tmp
    case StateChangePhiRatio:
        var tmp uint32
        err = rlp.DecodeBytes(raw.Payload, &tmp)
        v = tmp
    case StateChangeNotarySetSize:
        var tmp uint32
        err = rlp.DecodeBytes(raw.Payload, &tmp)
        v = tmp
    case StateChangeDKGSetSize:
        var tmp uint32
        err = rlp.DecodeBytes(raw.Payload, &tmp)
        v = tmp
    case StateAddNode:
        var tmp []byte
        err = rlp.DecodeBytes(raw.Payload, &tmp)
        v = tmp
    default:
        err = ErrUnknownStateChangeType
    }
    if err != nil {
        return
    }
    return
}

func (s *State) cloneDKGComplaint(
    comp *typesDKG.Complaint) (copied *typesDKG.Complaint) {
    b, err := rlp.EncodeToBytes(comp)
    if err != nil {
        panic(err)
    }
    copied = &typesDKG.Complaint{}
    if err = rlp.DecodeBytes(b, copied); err != nil {
        panic(err)
    }
    return
}

func (s *State) cloneDKGMasterPublicKey(mpk *typesDKG.MasterPublicKey) (
    copied *typesDKG.MasterPublicKey) {
    b, err := rlp.EncodeToBytes(mpk)
    if err != nil {
        panic(err)
    }
    copied = typesDKG.NewMasterPublicKey()
    if err = rlp.DecodeBytes(b, copied); err != nil {
        panic(err)
    }
    return
}

func (s *State) cloneDKGFinalize(final *typesDKG.Finalize) (
    copied *typesDKG.Finalize) {
    b, err := rlp.EncodeToBytes(final)
    if err != nil {
        panic(err)
    }
    copied = &typesDKG.Finalize{}
    if err = rlp.DecodeBytes(b, copied); err != nil {
        panic(err)
    }
    return
}

// Equal checks equality between State instance.
func (s *State) Equal(other *State) error {
    // Check configuration part.
    configEqual := s.numChains == other.numChains &&
        s.lambdaBA == other.lambdaBA &&
        s.lambdaDKG == other.lambdaDKG &&
        s.k == other.k &&
        s.phiRatio == other.phiRatio &&
        s.notarySetSize == other.notarySetSize &&
        s.dkgSetSize == other.dkgSetSize &&
        s.roundInterval == other.roundInterval &&
        s.minBlockInterval == other.minBlockInterval &&
        s.maxBlockInterval == other.maxBlockInterval
    if !configEqual {
        return ErrStateConfigNotEqual
    }
    // Check local flag.
    if s.local != other.local {
        return ErrStateLocalFlagNotEqual
    }
    // Check node set.
    if len(s.nodes) != len(other.nodes) {
        return ErrStateNodeSetNotEqual
    }
    for nID, key := range s.nodes {
        otherKey, exists := other.nodes[nID]
        if !exists {
            return ErrStateNodeSetNotEqual
        }
        if bytes.Compare(key.Bytes(), otherKey.Bytes()) != 0 {
            return ErrStateNodeSetNotEqual
        }
    }
    // Check DKG Complaints, here I assume the addition sequence of complaints
    // proposed by one node would be identical on each node (this should be true
    // when state change requests are carried by blocks and executed in order).
    if len(s.dkgComplaints) != len(other.dkgComplaints) {
        return ErrStateDKGComplaintsNotEqual
    }
    for round, compsForRound := range s.dkgComplaints {
        otherCompsForRound, exists := other.dkgComplaints[round]
        if !exists {
            return ErrStateDKGComplaintsNotEqual
        }
        if len(compsForRound) != len(otherCompsForRound) {
            return ErrStateDKGComplaintsNotEqual
        }
        for nID, comps := range compsForRound {
            otherComps, exists := otherCompsForRound[nID]
            if !exists {
                return ErrStateDKGComplaintsNotEqual
            }
            if len(comps) != len(otherComps) {
                return ErrStateDKGComplaintsNotEqual
            }
            for idx, comp := range comps {
                if !comp.Equal(otherComps[idx]) {
                    return ErrStateDKGComplaintsNotEqual
                }
            }
        }
    }
    // Check DKG master public keys.
    if len(s.dkgMasterPublicKeys) != len(other.dkgMasterPublicKeys) {
        return ErrStateDKGMasterPublicKeysNotEqual
    }
    for round, mKeysForRound := range s.dkgMasterPublicKeys {
        otherMKeysForRound, exists := other.dkgMasterPublicKeys[round]
        if !exists {
            return ErrStateDKGMasterPublicKeysNotEqual
        }
        if len(mKeysForRound) != len(otherMKeysForRound) {
            return ErrStateDKGMasterPublicKeysNotEqual
        }
        for nID, mKey := range mKeysForRound {
            otherMKey, exists := otherMKeysForRound[nID]
            if !exists {
                return ErrStateDKGMasterPublicKeysNotEqual
            }
            if !mKey.Equal(otherMKey) {
                return ErrStateDKGMasterPublicKeysNotEqual
            }
        }
    }
    // Check DKG finals.
    if len(s.dkgFinals) != len(other.dkgFinals) {
        return ErrStateDKGFinalsNotEqual
    }
    for round, finalsForRound := range s.dkgFinals {
        otherFinalsForRound, exists := other.dkgFinals[round]
        if !exists {
            return ErrStateDKGFinalsNotEqual
        }
        if len(finalsForRound) != len(otherFinalsForRound) {
            return ErrStateDKGFinalsNotEqual
        }
        for nID, final := range finalsForRound {
            otherFinal, exists := otherFinalsForRound[nID]
            if !exists {
                return ErrStateDKGFinalsNotEqual
            }
            if !final.Equal(otherFinal) {
                return ErrStateDKGFinalsNotEqual
            }
        }
    }
    // Check CRS part.
    if len(s.crs) != len(other.crs) {
        return ErrStateCRSsNotEqual
    }
    for idx, crs := range s.crs {
        if crs != other.crs[idx] {
            return ErrStateCRSsNotEqual
        }
    }
    // Check pending changes.
    if !reflect.DeepEqual(
        s.pendingChangedConfigs, other.pendingChangedConfigs) {
        return ErrStatePendingChangesNotEqual
    }
    if !reflect.DeepEqual(s.pendingCRS, other.pendingCRS) {
        return ErrStatePendingChangesNotEqual
    }
    if !reflect.DeepEqual(s.pendingNodes, other.pendingNodes) {
        return ErrStatePendingChangesNotEqual
    }
    // Check pending DKG complaints.
    if len(s.pendingDKGComplaints) != len(other.pendingDKGComplaints) {
        return ErrStatePendingChangesNotEqual
    }
    for idx, comp := range s.pendingDKGComplaints {
        if !comp.Equal(other.pendingDKGComplaints[idx]) {
            return ErrStatePendingChangesNotEqual
        }
    }
    // Check pending DKG finals.
    if len(s.pendingDKGFinals) != len(other.pendingDKGFinals) {
        return ErrStatePendingChangesNotEqual
    }
    for idx, final := range s.pendingDKGFinals {
        if !final.Equal(other.pendingDKGFinals[idx]) {
            return ErrStatePendingChangesNotEqual
        }
    }
    // Check pending DKG Master public keys.
    if len(s.pendingDKGMasterPublicKeys) !=
        len(other.pendingDKGMasterPublicKeys) {
        return ErrStatePendingChangesNotEqual
    }
    for idx, mKey := range s.pendingDKGMasterPublicKeys {
        if !mKey.Equal(other.pendingDKGMasterPublicKeys[idx]) {
            return ErrStatePendingChangesNotEqual
        }
    }
    return nil
}

// Clone returns a copied State instance.
func (s *State) Clone() (copied *State) {
    // Clone configuration parts.
    copied = &State{
        numChains:        s.numChains,
        lambdaBA:         s.lambdaBA,
        lambdaDKG:        s.lambdaDKG,
        k:                s.k,
        phiRatio:         s.phiRatio,
        notarySetSize:    s.notarySetSize,
        dkgSetSize:       s.dkgSetSize,
        roundInterval:    s.roundInterval,
        minBlockInterval: s.minBlockInterval,
        maxBlockInterval: s.maxBlockInterval,
        local:            s.local,
        nodes:            make(map[types.NodeID]crypto.PublicKey),
        dkgComplaints: make(
            map[uint64]map[types.NodeID][]*typesDKG.Complaint),
        dkgMasterPublicKeys: make(
            map[uint64]map[types.NodeID]*typesDKG.MasterPublicKey),
        dkgFinals:             make(map[uint64]map[types.NodeID]*typesDKG.Finalize),
        pendingChangedConfigs: make(map[StateChangeType]interface{}),
    }
    // Nodes
    for nID, key := range s.nodes {
        copied.nodes[nID] = key
    }
    // DKG & CRS
    for round, complaintsForRound := range s.dkgComplaints {
        copied.dkgComplaints[round] =
            make(map[types.NodeID][]*typesDKG.Complaint)
        for nID, comps := range complaintsForRound {
            tmpComps := []*typesDKG.Complaint{}
            for _, comp := range comps {
                tmpComps = append(tmpComps, s.cloneDKGComplaint(comp))
            }
            copied.dkgComplaints[round][nID] = tmpComps
        }
    }
    for round, mKeysForRound := range s.dkgMasterPublicKeys {
        copied.dkgMasterPublicKeys[round] =
            make(map[types.NodeID]*typesDKG.MasterPublicKey)
        for nID, mKey := range mKeysForRound {
            copied.dkgMasterPublicKeys[round][nID] =
                s.cloneDKGMasterPublicKey(mKey)
        }
    }
    for round, finalsForRound := range s.dkgFinals {
        copied.dkgFinals[round] = make(map[types.NodeID]*typesDKG.Finalize)
        for nID, final := range finalsForRound {
            copied.dkgFinals[round][nID] = s.cloneDKGFinalize(final)
        }
    }
    for _, crs := range s.crs {
        copied.crs = append(copied.crs, crs)
    }
    // Pending Changes
    for t, v := range s.pendingChangedConfigs {
        copied.pendingChangedConfigs[t] = v
    }
    for _, bs := range s.pendingNodes {
        tmpBytes := make([]byte, len(bs))
        copy(tmpBytes, bs)
        copied.pendingNodes = append(copied.pendingNodes, tmpBytes)
    }
    for _, comp := range s.pendingDKGComplaints {
        copied.pendingDKGComplaints = append(
            copied.pendingDKGComplaints, s.cloneDKGComplaint(comp))
    }
    for _, final := range s.pendingDKGFinals {
        copied.pendingDKGFinals = append(
            copied.pendingDKGFinals, s.cloneDKGFinalize(final))
    }
    for _, mKey := range s.pendingDKGMasterPublicKeys {
        copied.pendingDKGMasterPublicKeys = append(
            copied.pendingDKGMasterPublicKeys, s.cloneDKGMasterPublicKey(mKey))
    }
    for _, req := range s.pendingCRS {
        copied.pendingCRS = append(copied.pendingCRS, &crsAdditionRequest{
            Round: req.Round,
            CRS:   req.CRS,
        })
    }
    return
}

// Apply change requests, this function would also
// be called when we extract these request from delivered blocks.
func (s *State) Apply(reqsAsBytes []byte) (err error) {
    // Try to unmarshal this byte stream into []*StateChangeRequest.
    rawReqs := []*rawStateChangeRequest{}
    if err = rlp.DecodeBytes(reqsAsBytes, &rawReqs); err != nil {
        return
    }
    var reqs []*StateChangeRequest
    for _, r := range rawReqs {
        var payload interface{}
        if payload, err = s.unpackPayload(r); err != nil {
            return
        }
        reqs = append(reqs, &StateChangeRequest{
            Type:    r.Type,
            Payload: payload,
        })
    }
    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
        }
    }
    return
}

// PackRequests pack current pending requests as byte slice, which
// could be sent as blocks' payload and unmarshall back to apply.
func (s *State) PackRequests() (b []byte, err error) {
    packed := []*StateChangeRequest{}
    s.pendingChangesLock.Lock()
    defer s.pendingChangesLock.Unlock()
    // Pack simple configuration changes first. There should be no
    // validity problems for those changes.
    for k, v := range s.pendingChangedConfigs {
        packed = append(packed, &StateChangeRequest{
            Type:    k,
            Payload: v,
        })
    }
    s.pendingChangedConfigs = make(map[StateChangeType]interface{})
    // For other changes, we need to check their validity.
    s.lock.RLock()
    defer s.lock.RUnlock()
    for _, bytesOfKey := range s.pendingNodes {
        packed = append(packed, &StateChangeRequest{
            Type:    StateAddNode,
            Payload: bytesOfKey,
        })
    }
    for _, comp := range s.pendingDKGComplaints {
        packed = append(packed, &StateChangeRequest{
            Type:    StateAddDKGComplaint,
            Payload: comp,
        })
    }
    for _, final := range s.pendingDKGFinals {
        packed = append(packed, &StateChangeRequest{
            Type:    StateAddDKGFinal,
            Payload: final,
        })
    }
    for _, masterPubKey := range s.pendingDKGMasterPublicKeys {
        packed = append(packed, &StateChangeRequest{
            Type:    StateAddDKGMasterPublicKey,
            Payload: masterPubKey,
        })
    }
    for _, crs := range s.pendingCRS {
        packed = append(packed, &StateChangeRequest{
            Type:    StateAddCRS,
            Payload: crs,
        })
    }
    if b, err = rlp.EncodeToBytes(packed); err != nil {
        return
    }
    return
}

// isValidRequest checks if this request is valid to proceed or not.
func (s *State) isValidRequest(req *StateChangeRequest) (err error) {
    // NOTE: there would be no lock in this helper, callers should be
    //       responsible for acquiring appropriate lock.
    switch req.Type {
    case StateAddDKGComplaint:
        comp := req.Payload.(*typesDKG.Complaint)
        // If we've received DKG final from that proposer, we would ignore
        // its complaint.
        if _, exists := s.dkgFinals[comp.Round][comp.ProposerID]; exists {
            return ErrProposerIsFinal
        }
        // If we've received identical complaint, ignore it.
        compForRound, exists := s.dkgComplaints[comp.Round]
        if !exists {
            break
        }
        comps, exists := compForRound[comp.ProposerID]
        if !exists {
            break
        }
        for _, tmpComp := range comps {
            if tmpComp == comp {
                return ErrDuplicatedChange
            }
        }
    case StateAddCRS:
        crsReq := req.Payload.(*crsAdditionRequest)
        if uint64(len(s.crs)) > crsReq.Round {
            if !s.crs[crsReq.Round].Equal(crsReq.CRS) {
                return ErrForkedCRS
            }
            return ErrDuplicatedChange
        } else if uint64(len(s.crs)) == crsReq.Round {
            return nil
        } else {
            return ErrMissingPreviousCRS
        }
    }
    return nil
}

// applyRequest applies a single StateChangeRequest.
func (s *State) applyRequest(req *StateChangeRequest) error {
    // NOTE: there would be no lock in this helper, callers should be
    //       responsible for acquiring appropriate lock.
    switch req.Type {
    case StateAddNode:
        pubKey, err := ecdsa.NewPublicKeyFromByteSlice(req.Payload.([]byte))
        if err != nil {
            return err
        }
        s.nodes[types.NewNodeID(pubKey)] = pubKey
    case StateAddCRS:
        crsRequest := req.Payload.(*crsAdditionRequest)
        if crsRequest.Round != uint64(len(s.crs)) {
            return ErrDuplicatedChange
        }
        s.crs = append(s.crs, crsRequest.CRS)
    case StateAddDKGComplaint:
        comp := req.Payload.(*typesDKG.Complaint)
        if _, exists := s.dkgComplaints[comp.Round]; !exists {
            s.dkgComplaints[comp.Round] = make(
                map[types.NodeID][]*typesDKG.Complaint)
        }
        s.dkgComplaints[comp.Round][comp.ProposerID] = append(
            s.dkgComplaints[comp.Round][comp.ProposerID], comp)
    case StateAddDKGMasterPublicKey:
        mKey := req.Payload.(*typesDKG.MasterPublicKey)
        if _, exists := s.dkgMasterPublicKeys[mKey.Round]; !exists {
            s.dkgMasterPublicKeys[mKey.Round] = make(
                map[types.NodeID]*typesDKG.MasterPublicKey)
        }
        s.dkgMasterPublicKeys[mKey.Round][mKey.ProposerID] = mKey
    case StateAddDKGFinal:
        final := req.Payload.(*typesDKG.Finalize)
        if _, exists := s.dkgFinals[final.Round]; !exists {
            s.dkgFinals[final.Round] = make(map[types.NodeID]*typesDKG.Finalize)
        }
        s.dkgFinals[final.Round][final.ProposerID] = final
    case StateChangeNumChains:
        s.numChains = req.Payload.(uint32)
    case StateChangeLambdaBA:
        s.lambdaBA = time.Duration(req.Payload.(uint64))
    case StateChangeLambdaDKG:
        s.lambdaDKG = time.Duration(req.Payload.(uint64))
    case StateChangeRoundInterval:
        s.roundInterval = time.Duration(req.Payload.(uint64))
    case StateChangeMinBlockInterval:
        s.minBlockInterval = time.Duration(req.Payload.(uint64))
    case StateChangeMaxBlockInterval:
        s.maxBlockInterval = time.Duration(req.Payload.(uint64))
    case StateChangeK:
        s.k = int(req.Payload.(uint64))
    case StateChangePhiRatio:
        s.phiRatio = math.Float32frombits(req.Payload.(uint32))
    case StateChangeNotarySetSize:
        s.notarySetSize = req.Payload.(uint32)
    case StateChangeDKGSetSize:
        s.dkgSetSize = req.Payload.(uint32)
    default:
        return errors.New("you are definitely kidding me")
    }
    return nil
}

// ProposeCRS propose a new CRS for a specific round.
func (s *State) ProposeCRS(round uint64, crs common.Hash) (err error) {
    err = s.RequestChange(StateAddCRS, &crsAdditionRequest{
        Round: round,
        CRS:   crs,
    })
    return
}

// RequestChange submits a state change request.
func (s *State) RequestChange(
    t StateChangeType, payload interface{}) (err error) {
    // Patch input parameter's type.
    switch t {
    case StateAddNode:
        payload = payload.(crypto.PublicKey).Bytes()
    case StateChangeLambdaBA,
        StateChangeLambdaDKG,
        StateChangeRoundInterval,
        StateChangeMinBlockInterval,
        StateChangeMaxBlockInterval:
        payload = uint64(payload.(time.Duration))
    case StateChangeK:
        payload = uint64(payload.(int))
    case StateChangePhiRatio:
        payload = math.Float32bits(payload.(float32))
    }
    req := &StateChangeRequest{
        Type:    t,
        Payload: payload,
    }
    if s.local {
        err = func() error {
            s.lock.Lock()
            defer s.lock.Unlock()
            if err := s.isValidRequest(req); err != nil {
                return err
            }
            return s.applyRequest(req)
        }()
        return
    }
    s.lock.RLock()
    defer s.lock.RUnlock()
    if err = s.isValidRequest(req); err != nil {
        return
    }
    s.pendingChangesLock.Lock()
    defer s.pendingChangesLock.Unlock()
    switch t {
    case StateAddNode:
        s.pendingNodes = append(s.pendingNodes, payload.([]byte))
    case StateAddCRS:
        s.pendingCRS = append(s.pendingCRS, payload.(*crsAdditionRequest))
    case StateAddDKGComplaint:
        s.pendingDKGComplaints = append(
            s.pendingDKGComplaints, payload.(*typesDKG.Complaint))
    case StateAddDKGMasterPublicKey:
        s.pendingDKGMasterPublicKeys = append(
            s.pendingDKGMasterPublicKeys, payload.(*typesDKG.MasterPublicKey))
    case StateAddDKGFinal:
        s.pendingDKGFinals = append(
            s.pendingDKGFinals, payload.(*typesDKG.Finalize))
    default:
        s.pendingChangedConfigs[t] = payload
    }
    return
}

// CRS access crs proposed for that round.
func (s *State) CRS(round uint64) common.Hash {
    s.lock.RLock()
    defer s.lock.RUnlock()
    if round >= uint64(len(s.crs)) {
        return common.Hash{}
    }
    return s.crs[round]
}

// DKGComplaints access current received dkg complaints for that round.
// This information won't be snapshot, thus can't be cached in test.Governance.
func (s *State) DKGComplaints(round uint64) []*typesDKG.Complaint {
    s.lock.RLock()
    defer s.lock.RUnlock()
    comps, exists := s.dkgComplaints[round]
    if !exists {
        return nil
    }
    tmpComps := make([]*typesDKG.Complaint, 0, len(comps))
    for _, compProp := range comps {
        for _, comp := range compProp {
            tmpComps = append(tmpComps, s.cloneDKGComplaint(comp))
        }
    }
    return tmpComps
}

// DKGMasterPublicKeys access current received dkg master public keys for that
// round. This information won't be snapshot, thus can't be cached in
// test.Governance.
func (s *State) DKGMasterPublicKeys(round uint64) []*typesDKG.MasterPublicKey {
    s.lock.RLock()
    defer s.lock.RUnlock()
    masterPublicKeys, exists := s.dkgMasterPublicKeys[round]
    if !exists {
        return nil
    }
    mpks := make([]*typesDKG.MasterPublicKey, 0, len(masterPublicKeys))
    for _, mpk := range masterPublicKeys {
        mpks = append(mpks, s.cloneDKGMasterPublicKey(mpk))
    }
    return mpks
}

// IsDKGFinal checks if current received dkg finals exceeds threshold.
// This information won't be snapshot, thus can't be cached in test.Governance.
func (s *State) IsDKGFinal(round uint64, threshold int) bool {
    s.lock.RLock()
    defer s.lock.RUnlock()
    return len(s.dkgFinals[round]) > threshold
}