aboutsummaryrefslogblamecommitdiffstats
path: root/core/test/governance.go
blob: e256504f2f554765c32ec7a95504f6adcbbcc099 (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 (
    "encoding/hex"
    "errors"
    "fmt"
    "reflect"
    "sort"
    "sync"

    "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"
)

// TODO(mission): add a method to compare config/crs between governance
//                instances.

// Governance is an implementation of Goverance for testing purpose.
type Governance struct {
    roundShift           uint64
    configs              []*types.Config
    nodeSets             [][]crypto.PublicKey
    roundBeginHeights    []uint64
    stateModule          *State
    networkModule        *Network
    pendingConfigChanges map[uint64]map[StateChangeType]interface{}
    prohibitedTypes      map[StateChangeType]struct{}
    lock                 sync.RWMutex
}

// NewGovernance constructs a Governance instance.
func NewGovernance(state *State, roundShift uint64) (g *Governance, err error) {
    // Setup a State instance.
    g = &Governance{
        roundShift:           roundShift,
        pendingConfigChanges: make(map[uint64]map[StateChangeType]interface{}),
        stateModule:          state,
        prohibitedTypes:      make(map[StateChangeType]struct{}),
        roundBeginHeights:    []uint64{0},
    }
    return
}

// NodeSet implements Governance interface to return current
// notary set.
func (g *Governance) NodeSet(round uint64) []crypto.PublicKey {
    if round == 0 || round == 1 {
        // Round 0, 1 are genesis round, their configs should be created
        // by default.
        g.CatchUpWithRound(round)
    }
    g.lock.RLock()
    defer g.lock.RUnlock()
    if round >= uint64(len(g.nodeSets)) {
        return nil
    }
    return g.nodeSets[round]
}

// Configuration returns the configuration at a given block height.
func (g *Governance) Configuration(round uint64) *types.Config {
    if round == 0 || round == 1 {
        // Round 0, 1 are genesis round, their configs should be created
        // by default.
        g.CatchUpWithRound(round)
    }
    g.lock.RLock()
    defer g.lock.RUnlock()
    if round >= uint64(len(g.configs)) {
        return nil
    }
    return g.configs[round]
}

// GetRoundHeight returns the begin height of a round.
func (g *Governance) GetRoundHeight(round uint64) uint64 {
    g.lock.RLock()
    defer g.lock.RUnlock()
    if round >= uint64(len(g.roundBeginHeights)) {
        panic(fmt.Errorf("round begin height is not ready: %d %d",
            round, len(g.roundBeginHeights)))
    }
    return g.roundBeginHeights[round]
}

// CRS returns the CRS for a given round.
func (g *Governance) CRS(round uint64) common.Hash {
    return g.stateModule.CRS(round)
}

// NotifyRound notifies governace contract to snapshot config, and broadcast
// pending state change requests for next round if any.
func (g *Governance) NotifyRound(round, beginHeight uint64) {
    // Snapshot configuration for the shifted round, this behavior is synced with
    // full node's implementation.
    shiftedRound := round + g.roundShift
    g.CatchUpWithRound(shiftedRound)
    // Apply change request for next round.
    func() {
        g.lock.Lock()
        defer g.lock.Unlock()
        // Check if there is any pending changes for previous rounds.
        for r := range g.pendingConfigChanges {
            if r < shiftedRound+1 {
                panic(fmt.Errorf("pending change no longer applied: %v, now: %v",
                    r, shiftedRound+1))
            }
        }
        for t, v := range g.pendingConfigChanges[shiftedRound+1] {
            if err := g.stateModule.RequestChange(t, v); err != nil {
                panic(err)
            }
        }
        delete(g.pendingConfigChanges, shiftedRound+1)
        g.broadcastPendingStateChanges()
        if round == uint64(len(g.roundBeginHeights)) {
            g.roundBeginHeights = append(g.roundBeginHeights, beginHeight)
        } else if round < uint64(len(g.roundBeginHeights)) {
            if beginHeight != g.roundBeginHeights[round] {
                panic(fmt.Errorf("mismatched round begin height: %d %d %d",
                    round, beginHeight, g.roundBeginHeights[round]))
            }
        } else {
            panic(fmt.Errorf("discontinuous round begin height: %d %d %d",
                round, beginHeight, len(g.roundBeginHeights)))
        }
    }()
}

// ProposeCRS propose a CRS.
func (g *Governance) ProposeCRS(round uint64, signedCRS []byte) {
    g.lock.Lock()
    defer g.lock.Unlock()
    crs := crypto.Keccak256Hash(signedCRS)
    if err := g.stateModule.ProposeCRS(round, crs); err != nil {
        // CRS can be proposed multiple times, other errors are not
        // accepted.
        if err != ErrDuplicatedChange {
            panic(err)
        }
        return
    }
    g.broadcastPendingStateChanges()
}

// AddDKGComplaint add a DKGComplaint.
func (g *Governance) AddDKGComplaint(complaint *typesDKG.Complaint) {
    if g.isProhibited(StateAddDKGComplaint) {
        return
    }
    if g.IsDKGFinal(complaint.Round) {
        return
    }
    if err := g.stateModule.RequestChange(
        StateAddDKGComplaint, complaint); err != nil {
        if err != ErrChangeWontApply {
            panic(err)
        }
    }
    g.broadcastPendingStateChanges()
}

// DKGComplaints returns the DKGComplaints of round.
func (g *Governance) DKGComplaints(round uint64) []*typesDKG.Complaint {
    return g.stateModule.DKGComplaints(round)
}

// AddDKGMasterPublicKey adds a DKGMasterPublicKey.
func (g *Governance) AddDKGMasterPublicKey(masterPublicKey *typesDKG.MasterPublicKey) {
    if g.isProhibited(StateAddDKGMasterPublicKey) {
        return
    }
    if g.IsDKGMPKReady(masterPublicKey.Round) {
        return
    }
    if err := g.stateModule.RequestChange(
        StateAddDKGMasterPublicKey, masterPublicKey); err != nil {
        if err != ErrChangeWontApply {
            panic(err)
        }
    }
    g.broadcastPendingStateChanges()
}

// DKGMasterPublicKeys returns the DKGMasterPublicKeys of round.
func (g *Governance) DKGMasterPublicKeys(
    round uint64) []*typesDKG.MasterPublicKey {
    return g.stateModule.DKGMasterPublicKeys(round)
}

// AddDKGMPKReady adds a DKG ready message.
func (g *Governance) AddDKGMPKReady(ready *typesDKG.MPKReady) {
    if err := g.stateModule.RequestChange(
        StateAddDKGMPKReady, ready); err != nil {
        if err != ErrChangeWontApply {
            panic(err)
        }
    }
    g.broadcastPendingStateChanges()
}

// IsDKGMPKReady checks if DKG is ready.
func (g *Governance) IsDKGMPKReady(round uint64) bool {
    if round == 0 || round == 1 {
        // Round 0, 1 are genesis round, their configs should be created
        // by default.
        g.CatchUpWithRound(round)
    }
    g.lock.RLock()
    defer g.lock.RUnlock()
    if round >= uint64(len(g.configs)) {
        return false
    }
    return g.stateModule.IsDKGMPKReady(round, int(g.configs[round].DKGSetSize)/3*2)
}

// AddDKGFinalize adds a DKG finalize message.
func (g *Governance) AddDKGFinalize(final *typesDKG.Finalize) {
    if g.isProhibited(StateAddDKGFinal) {
        return
    }
    if err := g.stateModule.RequestChange(StateAddDKGFinal, final); err != nil {
        if err != ErrChangeWontApply {
            panic(err)
        }
    }
    g.broadcastPendingStateChanges()
}

// IsDKGFinal checks if DKG is final.
func (g *Governance) IsDKGFinal(round uint64) bool {
    if round == 0 || round == 1 {
        // Round 0, 1 are genesis round, their configs should be created
        // by default.
        g.CatchUpWithRound(round)
    }
    g.lock.RLock()
    defer g.lock.RUnlock()
    if round >= uint64(len(g.configs)) {
        return false
    }
    return g.stateModule.IsDKGFinal(round, int(g.configs[round].DKGSetSize)/3*2)
}

// ReportForkVote reports a node for forking votes.
func (g *Governance) ReportForkVote(vote1, vote2 *types.Vote) {
}

// ReportForkBlock reports a node for forking blocks.
func (g *Governance) ReportForkBlock(block1, block2 *types.Block) {
}

// ResetDKG resets latest DKG data and propose new CRS.
func (g *Governance) ResetDKG(newSignedCRS []byte) {
    g.lock.Lock()
    defer g.lock.Unlock()
    crs := crypto.Keccak256Hash(newSignedCRS)
    if err := g.stateModule.RequestChange(StateResetDKG, crs); err != nil {
        // ResetDKG can be proposed multiple times, other errors are not
        // accepted.
        if err != ErrDuplicatedChange {
            panic(err)
        }
        return
    }
    g.broadcastPendingStateChanges()
}

// DKGResetCount returns the reset count for DKG of given round.
func (g *Governance) DKGResetCount(round uint64) uint64 {
    return g.stateModule.DKGResetCount(round)
}

//
// Test Utilities
//

type packedStateChanges []byte

// This method broadcasts pending state change requests in the underlying
// State instance, this behavior is to simulate tx-gossiping in full nodes.
func (g *Governance) broadcastPendingStateChanges() {
    if g.networkModule == nil {
        return
    }
    packed, err := g.stateModule.PackOwnRequests()
    if err != nil {
        panic(err)
    }
    if err := g.networkModule.Broadcast(packedStateChanges(packed)); err != nil {
        panic(err)
    }
}

// State allows to access embed State instance.
func (g *Governance) State() *State {
    return g.stateModule
}

// CatchUpWithRound attempts to perform state snapshot to
// provide configuration/nodeSet for round R.
func (g *Governance) CatchUpWithRound(round uint64) {
    if func() bool {
        g.lock.RLock()
        defer g.lock.RUnlock()
        return uint64(len(g.configs)) > round
    }() {
        return
    }
    g.lock.Lock()
    defer g.lock.Unlock()
    for uint64(len(g.configs)) <= round {
        config, nodeSet := g.stateModule.Snapshot()
        g.configs = append(g.configs, config)
        g.nodeSets = append(g.nodeSets, nodeSet)
    }
    if round >= 1 && len(g.roundBeginHeights) == 1 {
        // begin height of round 0 and round 1 should be ready, they won't be
        // afected by DKG reset mechanism.
        g.roundBeginHeights = append(g.roundBeginHeights,
            g.configs[0].RoundLength)
    }
}

// Clone a governance instance with replicate internal state.
func (g *Governance) Clone() *Governance {
    g.lock.RLock()
    defer g.lock.RUnlock()
    // Clone state.
    copiedState := g.stateModule.Clone()
    // Clone configs.
    copiedConfigs := []*types.Config{}
    for _, c := range g.configs {
        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 {
        copiedNodeSet := []crypto.PublicKey{}
        for _, node := range nodeSetForRound {
            pubKey, err := ecdsa.NewPublicKeyFromByteSlice(node.Bytes())
            if err != nil {
                panic(err)
            }
            copiedNodeSet = append(copiedNodeSet, pubKey)
        }
        copiedNodeSets = append(copiedNodeSets, copiedNodeSet)
    }
    // Clone prohibited flag.
    copiedProhibitedTypes := make(map[StateChangeType]struct{})
    for t := range g.prohibitedTypes {
        copiedProhibitedTypes[t] = struct{}{}
    }
    // Clone pending changes.
    return &Governance{
        roundShift:           g.roundShift,
        configs:              copiedConfigs,
        stateModule:          copiedState,
        nodeSets:             copiedNodeSets,
        pendingConfigChanges: copiedPendingChanges,
        prohibitedTypes:      copiedProhibitedTypes,
    }
}

// Equal checks equality between two Governance instances.
func (g *Governance) Equal(other *Governance, checkState bool) bool {
    // Check roundShift.
    if g.roundShift != other.roundShift {
        return false
    }
    // Check configs.
    if !reflect.DeepEqual(g.configs, other.configs) {
        return false
    }
    // Check node sets.
    if len(g.nodeSets) != len(other.nodeSets) {
        return false
    }
    // Check pending changes.
    if !reflect.DeepEqual(g.pendingConfigChanges, other.pendingConfigChanges) {
        return false
    }
    // Check prohibited types.
    if !reflect.DeepEqual(g.prohibitedTypes, other.prohibitedTypes) {
        return false
    }
    getSortedKeys := func(keys []crypto.PublicKey) (encoded []string) {
        for _, key := range keys {
            encoded = append(encoded, hex.EncodeToString(key.Bytes()))
        }
        sort.Strings(encoded)
        return
    }
    for round, nodeSetsForRound := range g.nodeSets {
        otherNodeSetsForRound := other.nodeSets[round]
        if len(nodeSetsForRound) != len(otherNodeSetsForRound) {
            return false
        }
        if !reflect.DeepEqual(
            getSortedKeys(nodeSetsForRound),
            getSortedKeys(otherNodeSetsForRound)) {
            return false
        }
    }
    // Check state if needed.
    //
    // While testing, it's expected that two governance instances contain
    // different state, only the snapshots (configs and node sets) are
    // essentially equal.
    if checkState {
        return g.stateModule.Equal(other.stateModule) == nil
    }
    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 < StateAddCRS || 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
}

// SwitchToRemoteMode would switch this governance instance to remote mode,
// which means: it will broadcast all changes from its underlying state
// instance.
func (g *Governance) SwitchToRemoteMode(n *Network) {
    if g.networkModule != nil {
        panic(errors.New("not in local mode before switching"))
    }
    g.stateModule.SwitchToRemoteMode()
    g.networkModule = n
    n.addStateModule(g.stateModule)
}

// Prohibit would prohibit DKG related state change requests.
//
// Note this method only prevents local modification, state changes related to
// DKG from others won't be prohibited.
func (g *Governance) Prohibit(t StateChangeType) {
    g.lock.Lock()
    defer g.lock.Unlock()
    switch t {
    case StateAddDKGMasterPublicKey, StateAddDKGFinal, StateAddDKGComplaint:
        g.prohibitedTypes[t] = struct{}{}
    default:
        panic(fmt.Errorf("unsupported state change type to prohibit: %s", t))
    }
}

// Unprohibit would unprohibit DKG related state change requests.
func (g *Governance) Unprohibit(t StateChangeType) {
    g.lock.Lock()
    defer g.lock.Unlock()
    delete(g.prohibitedTypes, t)
}

// isProhibited checks if a state change request is prohibited or not.
func (g *Governance) isProhibited(t StateChangeType) (prohibited bool) {
    g.lock.RLock()
    defer g.lock.RUnlock()
    _, prohibited = g.prohibitedTypes[t]
    return
}