aboutsummaryrefslogblamecommitdiffstats
path: root/core/consensus_test.go
blob: f677d009aeb4c805d8c3e5d9b65e71094f3ccf7a (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 core

import (
    "encoding/json"
    "testing"
    "time"

    "github.com/stretchr/testify/suite"

    "github.com/dexon-foundation/dexon-consensus/common"
    "github.com/dexon-foundation/dexon-consensus/core/crypto"
    "github.com/dexon-foundation/dexon-consensus/core/db"
    "github.com/dexon-foundation/dexon-consensus/core/test"
    "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"
)

// network implements core.Network.
type network struct {
    nID  types.NodeID
    conn *networkConnection
}

// PullBlocks tries to pull blocks from the DEXON network.
func (n *network) PullBlocks(common.Hashes) {
}

// PullVotes tries to pull votes from the DEXON network.
func (n *network) PullVotes(types.Position) {
}

// PullRandomness tries to pull randomness from the DEXON network.
func (n *network) PullRandomness(common.Hashes) {
}

// BroadcastVote broadcasts vote to all nodes in DEXON network.
func (n *network) BroadcastVote(vote *types.Vote) {
    n.conn.broadcast(n.nID, vote)
}

// BroadcastBlock broadcasts block to all nodes in DEXON network.
func (n *network) BroadcastBlock(block *types.Block) {
    n.conn.broadcast(n.nID, block)
}

// BroadcastAgreementResult broadcasts agreement result to DKG set.
func (n *network) BroadcastAgreementResult(
    randRequest *types.AgreementResult) {
    n.conn.broadcast(n.nID, randRequest)
}

// SendDKGPrivateShare sends PrivateShare to a DKG participant.
func (n *network) SendDKGPrivateShare(
    recv crypto.PublicKey, prvShare *typesDKG.PrivateShare) {
    n.conn.send(types.NewNodeID(recv), prvShare)
}

// BroadcastDKGPrivateShare broadcasts PrivateShare to all DKG participants.
func (n *network) BroadcastDKGPrivateShare(
    prvShare *typesDKG.PrivateShare) {
    n.conn.broadcast(n.nID, prvShare)
}

// BroadcastDKGPartialSignature broadcasts partialSignature to all
// DKG participants.
func (n *network) BroadcastDKGPartialSignature(
    psig *typesDKG.PartialSignature) {
    n.conn.broadcast(n.nID, psig)
}

// ReceiveChan returns a channel to receive messages from DEXON network.
func (n *network) ReceiveChan() <-chan interface{} {
    return make(chan interface{})
}

func (nc *networkConnection) broadcast(from types.NodeID, msg interface{}) {
    for nID := range nc.cons {
        if nID == from {
            continue
        }
        nc.send(nID, msg)
    }
}

func (nc *networkConnection) send(to types.NodeID, msg interface{}) {
    ch, exist := nc.cons[to]
    if !exist {
        return
    }
    msgCopy := msg
    // Clone msg if necessary.
    switch val := msg.(type) {
    case *types.Block:
        msgCopy = val.Clone()
    case *typesDKG.PrivateShare:
        // Use Marshal/Unmarshal to do deep copy.
        data, err := json.Marshal(val)
        if err != nil {
            panic(err)
        }
        valCopy := &typesDKG.PrivateShare{}
        if err := json.Unmarshal(data, valCopy); err != nil {
            panic(err)
        }
        msgCopy = valCopy
    }
    ch <- msgCopy
}

type networkConnection struct {
    s    *ConsensusTestSuite
    cons map[types.NodeID]chan interface{}
}

func (nc *networkConnection) newNetwork(nID types.NodeID) *network {
    return &network{
        nID:  nID,
        conn: nc,
    }
}

func (nc *networkConnection) setCon(nID types.NodeID, con *Consensus) {
    ch := make(chan interface{}, 1000)
    go func() {
        for {
            msg := <-ch
            var err error
            // Testify package does not support concurrent call.
            // Use panic() to detact error.
            switch val := msg.(type) {
            case *types.Block:
                err = con.preProcessBlock(val)
            case *types.Vote:
                err = con.ProcessVote(val)
            case *types.AgreementResult:
                err = con.ProcessAgreementResult(val)
            case *typesDKG.PrivateShare:
                err = con.cfgModule.processPrivateShare(val)
            case *typesDKG.PartialSignature:
                err = con.cfgModule.processPartialSignature(val)
            }
            if err != nil {
                panic(err)
            }
        }
    }()
    nc.cons[nID] = ch
}

type ConsensusTestSuite struct {
    suite.Suite
    conn *networkConnection
}

func (s *ConsensusTestSuite) newNetworkConnection() *networkConnection {
    return &networkConnection{
        s:    s,
        cons: make(map[types.NodeID]chan interface{}),
    }
}

func (s *ConsensusTestSuite) prepareConsensus(
    dMoment time.Time,
    gov *test.Governance,
    prvKey crypto.PrivateKey,
    conn *networkConnection) (
    *test.App, *Consensus) {

    app := test.NewApp(0, nil, nil)
    dbInst, err := db.NewMemBackedDB()
    s.Require().NoError(err)
    nID := types.NewNodeID(prvKey.PublicKey())
    network := conn.newNetwork(nID)
    con := NewConsensus(
        dMoment, app, gov, dbInst, network, prvKey, &common.NullLogger{})
    conn.setCon(nID, con)
    return app, con
}

func (s *ConsensusTestSuite) prepareConsensusWithDB(
    dMoment time.Time,
    gov *test.Governance,
    prvKey crypto.PrivateKey,
    conn *networkConnection,
    dbInst db.Database) (
    *test.App, *Consensus) {

    app := test.NewApp(0, nil, nil)
    nID := types.NewNodeID(prvKey.PublicKey())
    network := conn.newNetwork(nID)
    con := NewConsensus(
        dMoment, app, gov, dbInst, network, prvKey, &common.NullLogger{})
    conn.setCon(nID, con)
    return app, con
}

func (s *ConsensusTestSuite) TestRegisteredDKGRecover() {
    conn := s.newNetworkConnection()
    prvKeys, pubKeys, err := test.NewKeys(1)
    s.Require().NoError(err)
    gov, err := test.NewGovernance(test.NewState(DKGDelayRound,
        pubKeys, time.Second, &common.NullLogger{}, true), ConfigRoundShift)
    s.Require().NoError(err)
    gov.State().RequestChange(test.StateChangeRoundLength, uint64(200))
    dMoment := time.Now().UTC()
    dbInst, err := db.NewMemBackedDB()
    s.Require().NoError(err)
    _, con := s.prepareConsensusWithDB(dMoment, gov, prvKeys[0], conn, dbInst)

    s.Require().Nil(con.cfgModule.dkg)

    con.cfgModule.registerDKG(con.ctx, 0, 0, 10)
    con.cfgModule.dkgLock.Lock()
    defer con.cfgModule.dkgLock.Unlock()

    _, newCon := s.prepareConsensusWithDB(dMoment, gov, prvKeys[0], conn, dbInst)

    newCon.cfgModule.registerDKG(newCon.ctx, 0, 0, 10)
    newCon.cfgModule.dkgLock.Lock()
    defer newCon.cfgModule.dkgLock.Unlock()

    s.Require().NotNil(newCon.cfgModule.dkg)
    s.Require().True(newCon.cfgModule.dkg.prvShares.Equal(con.cfgModule.dkg.prvShares))
}

func (s *ConsensusTestSuite) TestDKGCRS() {
    n := 21
    lambda := 200 * time.Millisecond
    if testing.Short() {
        n = 7
        lambda = 100 * time.Millisecond
    }
    if isTravisCI() {
        lambda *= 5
    }
    conn := s.newNetworkConnection()
    prvKeys, pubKeys, err := test.NewKeys(n)
    s.Require().NoError(err)
    gov, err := test.NewGovernance(test.NewState(DKGDelayRound,
        pubKeys, lambda, &common.NullLogger{}, true), ConfigRoundShift)
    s.Require().NoError(err)
    gov.State().RequestChange(test.StateChangeRoundLength, uint64(200))
    cons := map[types.NodeID]*Consensus{}
    dMoment := time.Now().UTC()
    for _, key := range prvKeys {
        _, con := s.prepareConsensus(dMoment, gov, key, conn)
        nID := types.NewNodeID(key.PublicKey())
        cons[nID] = con
    }
    time.Sleep(gov.Configuration(0).MinBlockInterval * 4)
    for _, con := range cons {
        go con.runDKG(0, 0, 0, 0)
    }
    crsFinish := make(chan struct{}, len(cons))
    for _, con := range cons {
        go func(con *Consensus) {
            height := uint64(0)
        Loop:
            for {
                select {
                case <-crsFinish:
                    break Loop
                case <-time.After(lambda):
                }
                con.event.NotifyHeight(height)
                height++
            }
        }(con)
    }
    for _, con := range cons {
        func() {
            con.dkgReady.L.Lock()
            defer con.dkgReady.L.Unlock()
            for con.dkgRunning != 2 {
                con.dkgReady.Wait()
            }
        }()
    }
    for _, con := range cons {
        go func(con *Consensus) {
            con.runCRS(0, gov.CRS(0), false)
            crsFinish <- struct{}{}
        }(con)
    }
    s.NotNil(gov.CRS(1))
}

func (s *ConsensusTestSuite) TestSyncBA() {
    lambdaBA := time.Second
    conn := s.newNetworkConnection()
    prvKeys, pubKeys, err := test.NewKeys(4)
    s.Require().NoError(err)
    gov, err := test.NewGovernance(test.NewState(DKGDelayRound,
        pubKeys, lambdaBA, &common.NullLogger{}, true), ConfigRoundShift)
    s.Require().NoError(err)
    prvKey := prvKeys[0]
    _, con := s.prepareConsensus(time.Now().UTC(), gov, prvKey, conn)
    go con.Run()
    defer con.Stop()
    hash := common.NewRandomHash()
    signers := make([]*utils.Signer, 0, len(prvKeys))
    for _, prvKey := range prvKeys {
        signers = append(signers, utils.NewSigner(prvKey))
    }
    pos := types.Position{
        Round:  0,
        Height: 20,
    }
    baResult := &types.AgreementResult{
        BlockHash: hash,
        Position:  pos,
    }
    for _, signer := range signers {
        vote := types.NewVote(types.VoteCom, hash, 0)
        vote.Position = pos
        s.Require().NoError(signer.SignVote(vote))
        baResult.Votes = append(baResult.Votes, *vote)
    }
    // Make sure each agreement module is running. ProcessAgreementResult only
    // works properly when agreement module is running:
    //  - the bias for round begin time would be 4 * lambda.
    //  - the ticker is 1 lambdaa.
    time.Sleep(5 * lambdaBA)
    s.Require().NoError(con.ProcessAgreementResult(baResult))
    aID := con.baMgr.baModule.agreementID()
    s.Equal(pos, aID)

    // Negative cases are moved to TestVerifyAgreementResult in utils_test.go.
}

func (s *ConsensusTestSuite) TestInitialHeightEventTriggered() {
    // Initial block is the last block of corresponding round, in this case,
    // we should make sure all height event handlers could be triggered after
    // returned from Consensus.prepare().
    prvKeys, pubKeys, err := test.NewKeys(4)
    s.Require().NoError(err)
    // Prepare a governance instance, whose DKG-reset-count for round 2 is 1.
    gov, err := test.NewGovernance(test.NewState(DKGDelayRound,
        pubKeys, time.Second, &common.NullLogger{}, true), ConfigRoundShift)
    gov.State().RequestChange(test.StateChangeRoundLength, uint64(100))
    s.Require().NoError(err)
    gov.NotifyRound(2, 201)
    gov.NotifyRound(3, 301)
    hash := common.NewRandomHash()
    gov.ProposeCRS(2, hash[:])
    hash = common.NewRandomHash()
    gov.ResetDKG(hash[:])
    s.Require().Equal(gov.DKGResetCount(2), uint64(1))
    prvKey := prvKeys[0]
    initBlock := &types.Block{
        Hash:     common.NewRandomHash(),
        Position: types.Position{Round: 1, Height: 200},
    }
    dbInst, err := db.NewMemBackedDB()
    s.Require().NoError(err)
    nID := types.NewNodeID(prvKey.PublicKey())
    conn := s.newNetworkConnection()
    network := conn.newNetwork(nID)
    con, err := NewConsensusFromSyncer(
        initBlock,
        false,
        time.Now().UTC(),
        test.NewApp(0, nil, nil),
        gov,
        dbInst,
        network,
        prvKey,
        []*types.Block(nil),
        []interface{}(nil),
        &common.NullLogger{},
    )
    s.Require().NoError(err)
    // Here is the tricky part, check if block chain module can handle the
    // block with height == 200.
    s.Require().Equal(con.bcModule.configs[0].RoundID(), uint64(1))
    s.Require().Equal(con.bcModule.configs[0].RoundEndHeight(), uint64(301))
}

func TestConsensus(t *testing.T) {
    suite.Run(t, new(ConsensusTestSuite))
}