aboutsummaryrefslogblamecommitdiffstats
path: root/core/test/state_test.go
blob: 63f9d27b42fbe245d3b3e9ddce3d79fce9ee9c15 (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 (
    "sort"
    "testing"
    "time"

    "github.com/dexon-foundation/dexon-consensus/common"
    "github.com/dexon-foundation/dexon-consensus/core/crypto"
    "github.com/dexon-foundation/dexon-consensus/core/crypto/dkg"
    "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-consensus/core/utils"
    "github.com/stretchr/testify/suite"
)

type StateTestSuite struct {
    suite.Suite
}

func (s *StateTestSuite) newDKGMasterPublicKey(
    round uint64, reset uint64) *typesDKG.MasterPublicKey {
    prvKey, err := ecdsa.NewPrivateKey()
    s.Require().NoError(err)
    pubKey := prvKey.PublicKey()
    nodeID := types.NewNodeID(pubKey)
    _, pubShare := dkg.NewPrivateKeyShares(3)
    dID, err := dkg.BytesID(nodeID.Hash[:])
    s.Require().NoError(err)
    return &typesDKG.MasterPublicKey{
        ProposerID:      nodeID,
        Round:           round,
        Reset:           reset,
        DKGID:           dID,
        PublicKeyShares: *pubShare.Move(),
    }
}

func (s *StateTestSuite) newDKGComplaint(
    round uint64, reset uint64) *typesDKG.Complaint {
    prvKey, err := ecdsa.NewPrivateKey()
    s.Require().NoError(err)
    nodeID := types.NewNodeID(prvKey.PublicKey())
    comp := &typesDKG.Complaint{
        Round: round,
        Reset: reset,
        PrivateShare: typesDKG.PrivateShare{
            ProposerID:   nodeID,
            ReceiverID:   nodeID,
            Round:        round,
            Reset:        reset,
            PrivateShare: *dkg.NewPrivateKey(),
        },
    }
    signer := utils.NewSigner(prvKey)
    s.Require().NoError(signer.SignDKGComplaint(comp))
    s.Require().NoError(signer.SignDKGPrivateShare(&comp.PrivateShare))
    s.Require().False(comp.IsNack())
    return comp
}

func (s *StateTestSuite) newDKGMPKReady(
    round uint64, reset uint64) *typesDKG.MPKReady {
    prvKey, err := ecdsa.NewPrivateKey()
    s.Require().NoError(err)
    ready := &typesDKG.MPKReady{Round: round, Reset: reset}
    s.Require().NoError(utils.NewSigner(prvKey).SignDKGMPKReady(ready))
    return ready
}
func (s *StateTestSuite) newDKGFinal(
    round uint64, reset uint64) *typesDKG.Finalize {
    prvKey, err := ecdsa.NewPrivateKey()
    s.Require().NoError(err)
    final := &typesDKG.Finalize{Round: round, Reset: reset}
    s.Require().NoError(utils.NewSigner(prvKey).SignDKGFinalize(final))
    return final
}

func (s *StateTestSuite) compareNodes(node1, node2 []crypto.PublicKey) bool {
    id1 := common.Hashes{}
    for _, n := range node1 {
        id1 = append(id1, types.NewNodeID(n).Hash)
    }
    sort.Sort(id1)
    id2 := common.Hashes{}
    for _, n := range node2 {
        id2 = append(id2, types.NewNodeID(n).Hash)
    }
    sort.Sort(id2)
    if len(id1) != len(id2) {
        return false
    }
    for idx, id := range id1 {
        if id != id2[idx] {
            return false
        }
    }
    return true
}

func (s *StateTestSuite) findNode(
    nodes []crypto.PublicKey, node crypto.PublicKey) bool {
    nodeID := types.NewNodeID(node)
    for _, n := range nodes {
        nID := types.NewNodeID(n)
        if nID == nodeID {
            return true
        }
    }
    return false
}

func (s *StateTestSuite) makeDKGChanges(
    st *State,
    masterPubKey *typesDKG.MasterPublicKey,
    ready *typesDKG.MPKReady,
    complaint *typesDKG.Complaint,
    final *typesDKG.Finalize) {
    s.Require().NoError(st.RequestChange(StateAddDKGMasterPublicKey,
        masterPubKey))
    s.Require().NoError(st.RequestChange(StateAddDKGMPKReady, ready))
    s.Require().NoError(st.RequestChange(StateAddDKGComplaint, complaint))
    s.Require().NoError(st.RequestChange(StateAddDKGFinal, final))
}

func (s *StateTestSuite) makeConfigChanges(st *State) {
    st.RequestChange(StateChangeLambdaBA, time.Nanosecond)
    st.RequestChange(StateChangeLambdaDKG, time.Millisecond)
    st.RequestChange(StateChangeRoundLength, uint64(1001))
    st.RequestChange(StateChangeMinBlockInterval, time.Second)
    st.RequestChange(StateChangeNotarySetSize, uint32(5))
}

func (s *StateTestSuite) checkConfigChanges(config *types.Config) {
    req := s.Require()
    req.Equal(config.LambdaBA, time.Nanosecond)
    req.Equal(config.LambdaDKG, time.Millisecond)
    req.Equal(config.RoundLength, uint64(1001))
    req.Equal(config.MinBlockInterval, time.Second)
    req.Equal(config.NotarySetSize, uint32(5))
}

func (s *StateTestSuite) TestEqual() {
    var (
        req    = s.Require()
        lambda = 250 * time.Millisecond
    )
    _, genesisNodes, err := NewKeys(20)
    req.NoError(err)
    st := NewState(1, genesisNodes, lambda, &common.NullLogger{}, true)
    req.NoError(st.Equal(st))
    // One node is missing.
    st1 := NewState(1, genesisNodes, lambda, &common.NullLogger{}, true)
    for nID := range st1.nodes {
        delete(st1.nodes, nID)
        break
    }
    req.Equal(st.Equal(st1), ErrStateNodeSetNotEqual)
    // Make some changes.
    st2 := st.Clone()
    req.NoError(st.Equal(st2))
    s.makeConfigChanges(st)
    req.EqualError(ErrStateConfigNotEqual, st.Equal(st2).Error())
    req.NoError(st.ProposeCRS(2, common.NewRandomHash()))
    req.NoError(st.RequestChange(StateResetDKG, common.NewRandomHash()))
    masterPubKey := s.newDKGMasterPublicKey(2, 1)
    ready := s.newDKGMPKReady(2, 1)
    comp := s.newDKGComplaint(2, 1)
    final := s.newDKGFinal(2, 1)
    s.makeDKGChanges(st, masterPubKey, ready, comp, final)
    // Remove dkg complaints from cloned one to check if equal.
    st3 := st.Clone()
    req.NoError(st.Equal(st3))
    delete(st3.dkgComplaints, uint64(2))
    req.EqualError(ErrStateDKGComplaintsNotEqual, st.Equal(st3).Error())
    // Remove dkg master public key from cloned one to check if equal.
    st4 := st.Clone()
    req.NoError(st.Equal(st4))
    delete(st4.dkgMasterPublicKeys, uint64(2))
    req.EqualError(ErrStateDKGMasterPublicKeysNotEqual, st.Equal(st4).Error())
    // Remove dkg ready from cloned one to check if equal.
    st4a := st.Clone()
    req.NoError(st.Equal(st4a))
    delete(st4a.dkgReadys, uint64(2))
    req.EqualError(ErrStateDKGMPKReadysNotEqual, st.Equal(st4a).Error())
    // Remove dkg finalize from cloned one to check if equal.
    st5 := st.Clone()
    req.NoError(st.Equal(st5))
    delete(st5.dkgFinals, uint64(2))
    req.EqualError(ErrStateDKGFinalsNotEqual, st.Equal(st5).Error())
    // Remove dkgResetCount from cloned one to check if equal.
    st6 := st.Clone()
    req.NoError(st.Equal(st6))
    delete(st6.dkgResetCount, uint64(2))
    req.EqualError(ErrStateDKGResetCountNotEqual, st.Equal(st6).Error())

    // Switch to remote mode.
    st.SwitchToRemoteMode()
    // Make some change.
    req.NoError(st.RequestChange(StateChangeNotarySetSize, uint32(100)))
    str := st.Clone()
    req.NoError(st.Equal(str))
    // Remove the pending change, should not be equal.
    req.Len(str.ownRequests, 1)
    for k := range str.ownRequests {
        delete(str.ownRequests, k)
    }
    req.EqualError(ErrStatePendingChangesNotEqual, st.Equal(str).Error())
}

func (s *StateTestSuite) TestPendingChangesEqual() {
    var (
        req    = s.Require()
        lambda = 250 * time.Millisecond
    )
    // Setup a non-local mode State instance.
    _, genesisNodes, err := NewKeys(20)
    req.NoError(err)
    st := NewState(1, genesisNodes, lambda, &common.NullLogger{}, false)
    req.NoError(st.Equal(st))
    // Apply some changes.
    s.makeConfigChanges(st)
    crs := common.NewRandomHash()
    req.NoError(st.ProposeCRS(2, crs))
    masterPubKey := s.newDKGMasterPublicKey(2, 0)
    ready := s.newDKGMPKReady(2, 0)
    comp := s.newDKGComplaint(2, 0)
    final := s.newDKGFinal(2, 0)
    s.makeDKGChanges(st, masterPubKey, ready, comp, final)
}

func (s *StateTestSuite) TestLocalMode() {
    // Test State with local mode.
    var (
        req    = s.Require()
        lambda = 250 * time.Millisecond
    )
    _, genesisNodes, err := NewKeys(20)
    req.NoError(err)
    st := NewState(1, genesisNodes, lambda, &common.NullLogger{}, true)
    config1, nodes1 := st.Snapshot()
    req.True(s.compareNodes(genesisNodes, nodes1))
    // Check settings of config1 affected by genesisNodes and lambda.
    req.Equal(config1.LambdaBA, lambda)
    req.Equal(config1.LambdaDKG, lambda*10)
    req.Equal(config1.RoundLength, uint64(1000))
    req.Equal(config1.NotarySetSize, uint32(len(genesisNodes)))
    // Request some changes, every fields for config should be affected.
    s.makeConfigChanges(st)
    // Add new node.
    prvKey, err := ecdsa.NewPrivateKey()
    req.NoError(err)
    pubKey := prvKey.PublicKey()
    st.RequestChange(StateAddNode, pubKey)
    config2, newNodes := st.Snapshot()
    // Check if config changes are applied.
    s.checkConfigChanges(config2)
    // Check if new node is added.
    req.True(s.findNode(newNodes, pubKey))
    // Test adding CRS.
    crs := common.NewRandomHash()
    req.NoError(st.ProposeCRS(2, crs))
    req.Equal(st.CRS(2), crs)
    // Test adding node set, DKG complaints, final, master public key.
    // Make sure everything is empty before changed.
    req.Empty(st.DKGMasterPublicKeys(2))
    req.False(st.IsDKGMPKReady(2, 1))
    req.Empty(st.DKGComplaints(2))
    req.False(st.IsDKGFinal(2, 1))
    // Add DKG stuffs.
    masterPubKey := s.newDKGMasterPublicKey(2, 0)
    ready := s.newDKGMPKReady(2, 0)
    comp := s.newDKGComplaint(2, 0)
    final := s.newDKGFinal(2, 0)
    s.makeDKGChanges(st, masterPubKey, ready, comp, final)
    // Check DKGMasterPublicKeys.
    masterKeyForRound := st.DKGMasterPublicKeys(2)
    req.Len(masterKeyForRound, 1)
    req.True(masterKeyForRound[0].Equal(masterPubKey))
    // Check IsDKGMPKReady.
    req.True(st.IsDKGMPKReady(2, 1))
    // Check DKGComplaints.
    compForRound := st.DKGComplaints(2)
    req.Len(compForRound, 1)
    req.True(compForRound[0].Equal(comp))
    // Check IsDKGFinal.
    req.True(st.IsDKGFinal(2, 1))
    // Test ResetDKG.
    crs = common.NewRandomHash()
    req.NoError(st.RequestChange(StateResetDKG, crs))
    req.Equal(st.CRS(2), crs)
    // Make sure all DKG fields are cleared.
    req.Empty(st.DKGMasterPublicKeys(2))
    req.False(st.IsDKGMPKReady(2, 1))
    req.Empty(st.DKGComplaints(2))
    req.False(st.IsDKGFinal(2, 1))
}

func (s *StateTestSuite) TestPacking() {
    // Make sure everything works when requests are packing
    // and unpacked to apply.
    var (
        req    = s.Require()
        lambda = 250 * time.Millisecond
    )
    packAndApply := func(st *State) {
        // In remote mode, we need to manually convert own requests to global ones.
        _, err := st.PackOwnRequests()
        req.NoError(err)
        // Pack changes into bytes.
        b, err := st.PackRequests()
        req.NoError(err)
        req.NotEmpty(b)
        // Apply those bytes back.
        req.NoError(st.Apply(b))
    }
    // Make config changes.
    _, genesisNodes, err := NewKeys(20)
    req.NoError(err)
    st := NewState(1, genesisNodes, lambda, &common.NullLogger{}, false)
    s.makeConfigChanges(st)
    // Add new CRS.
    crs := common.NewRandomHash()
    req.NoError(st.ProposeCRS(2, crs))
    // Add new node.
    prvKey, err := ecdsa.NewPrivateKey()
    req.NoError(err)
    pubKey := prvKey.PublicKey()
    st.RequestChange(StateAddNode, pubKey)
    // Add DKG stuffs.
    masterPubKey := s.newDKGMasterPublicKey(2, 0)
    ready := s.newDKGMPKReady(2, 0)
    comp := s.newDKGComplaint(2, 0)
    final := s.newDKGFinal(2, 0)
    s.makeDKGChanges(st, masterPubKey, ready, comp, final)
    // Make sure everything is empty before changed.
    req.Empty(st.DKGMasterPublicKeys(2))
    req.False(st.IsDKGMPKReady(2, 1))
    req.Empty(st.DKGComplaints(2))
    req.False(st.IsDKGFinal(2, 1))
    packAndApply(st)
    // Check if configs are changed.
    config, nodes := st.Snapshot()
    s.checkConfigChanges(config)
    // Check if CRS is added.
    req.Equal(st.CRS(2), crs)
    // Check if new node is added.
    req.True(s.findNode(nodes, pubKey))
    // Check DKGMasterPublicKeys.
    masterKeyForRound := st.DKGMasterPublicKeys(2)
    req.Len(masterKeyForRound, 1)
    req.True(masterKeyForRound[0].Equal(masterPubKey))
    // Check DKGComplaints.
    compForRound := st.DKGComplaints(2)
    req.Len(compForRound, 1)
    req.True(compForRound[0].Equal(comp))
    // Check IsDKGMPKReady.
    req.True(st.IsDKGMPKReady(2, 1))
    // Check IsDKGFinal.
    req.True(st.IsDKGFinal(2, 1))

    // Test ResetDKG.
    crs = common.NewRandomHash()
    req.NoError(st.RequestChange(StateResetDKG, crs))
    packAndApply(st)
    req.Equal(st.CRS(2), crs)
    // Make sure all DKG fields are cleared.
    req.Empty(st.DKGMasterPublicKeys(2))
    req.False(st.IsDKGMPKReady(2, 1))
    req.Empty(st.DKGComplaints(2))
    req.False(st.IsDKGFinal(2, 1))
}

func (s *StateTestSuite) TestRequestBroadcastAndPack() {
    // This test case aims to demonstrate this scenario:
    // - a change request is pending at one node.
    // - that request can be packed by PackOwnRequests and sent to other nodes.
    // - when some other node allow to propose a block, it will pack all those
    //   'own' requests from others into the block's payload.
    // - when all nodes receive that block, all pending requests (including
    //   those 'own' requests) would be cleaned.
    var (
        req    = s.Require()
        lambda = 250 * time.Millisecond
    )
    _, genesisNodes, err := NewKeys(20)
    req.NoError(err)
    st := NewState(1, genesisNodes, lambda, &common.NullLogger{}, false)
    st1 := NewState(1, genesisNodes, lambda, &common.NullLogger{}, false)
    req.NoError(st.Equal(st1))
    // Make configuration changes.
    s.makeConfigChanges(st)
    // Add new CRS.
    crs := common.NewRandomHash()
    req.NoError(st.ProposeCRS(2, crs))
    // Add new node.
    prvKey, err := ecdsa.NewPrivateKey()
    req.NoError(err)
    pubKey := prvKey.PublicKey()
    st.RequestChange(StateAddNode, pubKey)
    // Add DKG stuffs.
    masterPubKey := s.newDKGMasterPublicKey(2, 0)
    ready := s.newDKGMPKReady(2, 0)
    comp := s.newDKGComplaint(2, 0)
    final := s.newDKGFinal(2, 0)
    s.makeDKGChanges(st, masterPubKey, ready, comp, final)
    // Pack those changes into a byte stream, and pass it to other State
    // instance.
    packed, err := st.PackOwnRequests()
    req.NoError(err)
    req.NotEmpty(packed)
    // The second attempt to pack would get empty result.
    emptyPackedAsByte, err := st.PackOwnRequests()
    req.NoError(err)
    emptyPacked, err := st.unpackRequests(emptyPackedAsByte)
    req.NoError(err)
    req.Empty(emptyPacked)
    // Pass it to others.
    req.NoError(st1.AddRequestsFromOthers(packed))
    // These two instance are equal now, because their pending change requests
    // are synced.
    req.NoError(st.Equal(st1))
    // Make them apply those pending changes.
    applyChangesForRemoteState := func(s *State) {
        p, err := s.PackRequests()
        req.NoError(err)
        req.NotEmpty(p)
        req.NoError(s.Apply(p))
    }
    applyChangesForRemoteState(st)
    applyChangesForRemoteState(st1)
    // They should be equal after applying those changes.
    req.NoError(st.Equal(st1))
}

func (s *StateTestSuite) TestUnmatchedResetCount() {
    _, genesisNodes, err := NewKeys(20)
    s.Require().NoError(err)
    st := NewState(1, genesisNodes, 100*time.Millisecond,
        &common.NullLogger{}, true)
    // Make sure the case in older version without reset won't fail.
    mpk := s.newDKGMasterPublicKey(1, 0)
    ready := s.newDKGMPKReady(1, 0)
    comp := s.newDKGComplaint(1, 0)
    final := s.newDKGFinal(1, 0)
    s.Require().NoError(st.RequestChange(StateAddDKGMasterPublicKey, mpk))
    s.Require().NoError(st.RequestChange(StateAddDKGMPKReady, ready))
    s.Require().NoError(st.RequestChange(StateAddDKGComplaint, comp))
    s.Require().NoError(st.RequestChange(StateAddDKGFinal, final))
    // Make round 1 reset twice.
    s.Require().NoError(st.RequestChange(StateResetDKG, common.NewRandomHash()))
    s.Require().NoError(st.RequestChange(StateResetDKG, common.NewRandomHash()))
    s.Require().Equal(st.dkgResetCount[1], uint64(2))
    s.Require().EqualError(ErrChangeWontApply, st.RequestChange(
        StateAddDKGMasterPublicKey, mpk).Error())
    s.Require().EqualError(ErrChangeWontApply, st.RequestChange(
        StateAddDKGMPKReady, ready).Error())
    s.Require().EqualError(ErrChangeWontApply, st.RequestChange(
        StateAddDKGComplaint, comp).Error())
    s.Require().EqualError(ErrChangeWontApply, st.RequestChange(
        StateAddDKGFinal, final).Error())
    mpk = s.newDKGMasterPublicKey(1, 2)
    ready = s.newDKGMPKReady(1, 2)
    comp = s.newDKGComplaint(1, 2)
    final = s.newDKGFinal(1, 2)
    s.Require().NoError(st.RequestChange(StateAddDKGMasterPublicKey, mpk))
    s.Require().NoError(st.RequestChange(StateAddDKGMPKReady, ready))
    s.Require().NoError(st.RequestChange(StateAddDKGComplaint, comp))
    s.Require().NoError(st.RequestChange(StateAddDKGFinal, final))
}

func TestState(t *testing.T) {
    suite.Run(t, new(StateTestSuite))
}