aboutsummaryrefslogblamecommitdiffstats
path: root/core/agreement.go
blob: 262c5d5be21e459b1f04c43bec4c6e1d6c13fb3b (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 (
    "fmt"
    "math"
    "sync"
    "sync/atomic"
    "time"

    "github.com/dexon-foundation/dexon-consensus/common"
    "github.com/dexon-foundation/dexon-consensus/core/types"
    "github.com/dexon-foundation/dexon-consensus/core/utils"
)

// Errors for agreement module.
var (
    ErrInvalidVote            = fmt.Errorf("invalid vote")
    ErrNotInNotarySet         = fmt.Errorf("not in notary set")
    ErrIncorrectVoteSignature = fmt.Errorf("incorrect vote signature")
)

// ErrFork for fork error in agreement.
type ErrFork struct {
    nID      types.NodeID
    old, new common.Hash
}

func (e *ErrFork) Error() string {
    return fmt.Sprintf("fork is found for %s, old %s, new %s",
        e.nID.String(), e.old, e.new)
}

// ErrForkVote for fork vote error in agreement.
type ErrForkVote struct {
    nID      types.NodeID
    old, new *types.Vote
}

func (e *ErrForkVote) Error() string {
    return fmt.Sprintf("fork vote is found for %s, old %s, new %s",
        e.nID.String(), e.old, e.new)
}

func newVoteListMap() []map[types.NodeID]*types.Vote {
    listMap := make([]map[types.NodeID]*types.Vote, types.MaxVoteType)
    for idx := range listMap {
        listMap[idx] = make(map[types.NodeID]*types.Vote)
    }
    return listMap
}

// agreementReceiver is the interface receiving agreement event.
type agreementReceiver interface {
    ProposeVote(vote *types.Vote)
    ProposeBlock() common.Hash
    // ConfirmBlock is called with lock hold. User can safely use all data within
    // agreement module.
    ConfirmBlock(common.Hash, map[types.NodeID]*types.Vote)
    PullBlocks(common.Hashes)
}

type pendingBlock struct {
    block        *types.Block
    receivedTime time.Time
}

type pendingVote struct {
    vote         *types.Vote
    receivedTime time.Time
}

// agreementData is the data for agreementState.
type agreementData struct {
    recv agreementReceiver

    ID           types.NodeID
    leader       *leaderSelector
    lockValue    common.Hash
    lockRound    uint64
    period       uint64
    requiredVote int
    votes        map[uint64][]map[types.NodeID]*types.Vote
    lock         sync.RWMutex
    blocks       map[types.NodeID]*types.Block
    blocksLock   sync.Mutex
}

// agreement is the agreement protocal describe in the Crypto Shuffle Algorithm.
type agreement struct {
    state          agreementState
    data           *agreementData
    aID            *atomic.Value
    notarySet      map[types.NodeID]struct{}
    hasOutput      bool
    lock           sync.RWMutex
    pendingBlock   []pendingBlock
    pendingVote    []pendingVote
    candidateBlock map[common.Hash]*types.Block
    fastForward    chan uint64
    signer         *utils.Signer
}

// newAgreement creates a agreement instance.
func newAgreement(
    ID types.NodeID,
    recv agreementReceiver,
    leader *leaderSelector,
    signer *utils.Signer) *agreement {
    agreement := &agreement{
        data: &agreementData{
            recv:   recv,
            ID:     ID,
            leader: leader,
        },
        aID:            &atomic.Value{},
        candidateBlock: make(map[common.Hash]*types.Block),
        fastForward:    make(chan uint64, 1),
        signer:         signer,
    }
    agreement.stop()
    return agreement
}

// restart the agreement
func (a *agreement) restart(
    notarySet map[types.NodeID]struct{}, aID types.Position, crs common.Hash) {

    if !func() bool {
        a.lock.Lock()
        defer a.lock.Unlock()
        if !isStop(aID) {
            oldAID := a.agreementID()
            if !isStop(oldAID) && !aID.Newer(&oldAID) {
                return false
            }
        }
        a.data.lock.Lock()
        defer a.data.lock.Unlock()
        a.data.blocksLock.Lock()
        defer a.data.blocksLock.Unlock()
        a.data.votes = make(map[uint64][]map[types.NodeID]*types.Vote)
        a.data.votes[1] = newVoteListMap()
        a.data.period = 1
        a.data.blocks = make(map[types.NodeID]*types.Block)
        a.data.requiredVote = len(notarySet)/3*2 + 1
        a.data.leader.restart(crs)
        a.data.lockValue = nullBlockHash
        a.data.lockRound = 0
        a.fastForward = make(chan uint64, 1)
        a.hasOutput = false
        a.state = newInitialState(a.data)
        a.notarySet = notarySet
        a.candidateBlock = make(map[common.Hash]*types.Block)
        a.aID.Store(aID)
        return true
    }() {
        return
    }

    if isStop(aID) {
        return
    }

    expireTime := time.Now().Add(-10 * time.Second)
    replayBlock := make([]*types.Block, 0)
    func() {
        a.lock.Lock()
        defer a.lock.Unlock()
        newPendingBlock := make([]pendingBlock, 0)
        for _, pending := range a.pendingBlock {
            if aID.Newer(&pending.block.Position) {
                continue
            } else if pending.block.Position == aID {
                replayBlock = append(replayBlock, pending.block)
            } else if pending.receivedTime.After(expireTime) {
                newPendingBlock = append(newPendingBlock, pending)
            }
        }
        a.pendingBlock = newPendingBlock
    }()

    replayVote := make([]*types.Vote, 0)
    func() {
        a.lock.Lock()
        defer a.lock.Unlock()
        newPendingVote := make([]pendingVote, 0)
        for _, pending := range a.pendingVote {
            if aID.Newer(&pending.vote.Position) {
                continue
            } else if pending.vote.Position == aID {
                replayVote = append(replayVote, pending.vote)
            } else if pending.receivedTime.After(expireTime) {
                newPendingVote = append(newPendingVote, pending)
            }
        }
        a.pendingVote = newPendingVote
    }()

    for _, block := range replayBlock {
        a.processBlock(block)
    }

    for _, vote := range replayVote {
        a.processVote(vote)
    }
}

func (a *agreement) stop() {
    a.restart(make(map[types.NodeID]struct{}), types.Position{
        ChainID: math.MaxUint32,
    }, common.Hash{})
}

func isStop(aID types.Position) bool {
    return aID.ChainID == math.MaxUint32
}

// clocks returns how many time this state is required.
func (a *agreement) clocks() int {
    a.data.lock.RLock()
    defer a.data.lock.RUnlock()
    return a.state.clocks()
}

// pullVotes returns if current agreement requires more votes to continue.
func (a *agreement) pullVotes() bool {
    a.data.lock.RLock()
    defer a.data.lock.RUnlock()
    return a.state.state() == statePullVote ||
        (a.state.state() == statePreCommit && (a.data.period%3) == 0)
}

// agreementID returns the current agreementID.
func (a *agreement) agreementID() types.Position {
    return a.aID.Load().(types.Position)
}

// nextState is called at the specific clock time.
func (a *agreement) nextState() (err error) {
    if func() bool {
        a.lock.RLock()
        defer a.lock.RUnlock()
        return a.hasOutput
    }() {
        a.state = newSleepState(a.data)
        return
    }
    a.state, err = a.state.nextState()
    return
}

func (a *agreement) sanityCheck(vote *types.Vote) error {
    if vote.Type >= types.MaxVoteType {
        return ErrInvalidVote
    }
    if _, exist := a.notarySet[vote.ProposerID]; !exist {
        return ErrNotInNotarySet
    }
    ok, err := utils.VerifyVoteSignature(vote)
    if err != nil {
        return err
    }
    if !ok {
        return ErrIncorrectVoteSignature
    }
    return nil
}

func (a *agreement) checkForkVote(vote *types.Vote) error {
    a.data.lock.RLock()
    defer a.data.lock.RUnlock()
    if votes, exist := a.data.votes[vote.Period]; exist {
        if oldVote, exist := votes[vote.Type][vote.ProposerID]; exist {
            if vote.BlockHash != oldVote.BlockHash {
                return &ErrForkVote{vote.ProposerID, oldVote, vote}
            }
        }
    }
    return nil
}

// prepareVote prepares a vote.
func (a *agreement) prepareVote(vote *types.Vote) (err error) {
    vote.Position = a.agreementID()
    err = a.signer.SignVote(vote)
    return
}

// processVote is the entry point for processing Vote.
func (a *agreement) processVote(vote *types.Vote) error {
    a.lock.Lock()
    defer a.lock.Unlock()
    if err := a.sanityCheck(vote); err != nil {
        return err
    }
    aID := a.agreementID()
    // Agreement module has stopped.
    if isStop(aID) {
        return nil
    }
    if vote.Position != aID {
        if aID.Newer(&vote.Position) {
            return nil
        }
        a.pendingVote = append(a.pendingVote, pendingVote{
            vote:         vote,
            receivedTime: time.Now().UTC(),
        })
        return nil
    }
    if err := a.checkForkVote(vote); err != nil {
        return err
    }

    a.data.lock.Lock()
    defer a.data.lock.Unlock()
    if _, exist := a.data.votes[vote.Period]; !exist {
        a.data.votes[vote.Period] = newVoteListMap()
    }
    a.data.votes[vote.Period][vote.Type][vote.ProposerID] = vote
    if !a.hasOutput && vote.Type == types.VoteCom {
        if hash, ok := a.data.countVoteNoLock(vote.Period, vote.Type); ok &&
            hash != skipBlockHash {
            a.hasOutput = true
            a.data.recv.ConfirmBlock(hash,
                a.data.votes[vote.Period][types.VoteCom])
            return nil
        }
    } else if a.hasOutput {
        return nil
    }

    // Check if the agreement requires fast-forwarding.
    if len(a.fastForward) > 0 {
        return nil
    }
    if vote.Type == types.VotePreCom {
        if hash, ok := a.data.countVoteNoLock(vote.Period, vote.Type); ok &&
            hash != skipBlockHash {
            // Condition 1.
            if a.data.period >= vote.Period && vote.Period > a.data.lockRound &&
                vote.BlockHash != a.data.lockValue {
                a.data.lockValue = hash
                a.data.lockRound = vote.Period
                return nil
            }
            // Condition 2.
            if vote.Period > a.data.period {
                a.data.lockValue = hash
                a.data.lockRound = vote.Period
                a.fastForward <- vote.Period
                return nil
            }
        }
    }
    // Condition 3.
    if vote.Type == types.VoteCom && vote.Period >= a.data.period &&
        len(a.data.votes[vote.Period][types.VoteCom]) >= a.data.requiredVote {
        hashes := common.Hashes{}
        addPullBlocks := func(voteType types.VoteType) {
            for _, vote := range a.data.votes[vote.Period][voteType] {
                if vote.BlockHash == nullBlockHash || vote.BlockHash == skipBlockHash {
                    continue
                }
                if _, found := a.findCandidateBlockNoLock(vote.BlockHash); !found {
                    hashes = append(hashes, vote.BlockHash)
                }
            }
        }
        addPullBlocks(types.VoteInit)
        addPullBlocks(types.VotePreCom)
        addPullBlocks(types.VoteCom)
        if len(hashes) > 0 {
            a.data.recv.PullBlocks(hashes)
        }
        a.fastForward <- vote.Period + 1
        return nil
    }
    return nil
}

func (a *agreement) done() <-chan struct{} {
    a.lock.Lock()
    defer a.lock.Unlock()
    a.data.lock.Lock()
    defer a.data.lock.Unlock()
    ch := make(chan struct{}, 1)
    if a.hasOutput {
        ch <- struct{}{}
    } else {
        select {
        case period := <-a.fastForward:
            if period <= a.data.period {
                break
            }
            a.data.setPeriod(period)
            a.state = newPreCommitState(a.data)
            ch <- struct{}{}
        default:
        }
    }
    return ch
}

// processBlock is the entry point for processing Block.
func (a *agreement) processBlock(block *types.Block) error {
    a.lock.Lock()
    defer a.lock.Unlock()
    a.data.blocksLock.Lock()
    defer a.data.blocksLock.Unlock()

    aID := a.agreementID()
    if block.Position != aID {
        // Agreement module has stopped.
        if !isStop(aID) {
            if aID.Newer(&block.Position) {
                return nil
            }
        }
        a.pendingBlock = append(a.pendingBlock, pendingBlock{
            block:        block,
            receivedTime: time.Now().UTC(),
        })
        return nil
    }
    if b, exist := a.data.blocks[block.ProposerID]; exist {
        if b.Hash != block.Hash {
            return &ErrFork{block.ProposerID, b.Hash, block.Hash}
        }
        return nil
    }
    if err := a.data.leader.processBlock(block); err != nil {
        return err
    }
    a.data.blocks[block.ProposerID] = block
    a.addCandidateBlockNoLock(block)
    return nil
}

func (a *agreement) addCandidateBlock(block *types.Block) {
    a.lock.Lock()
    defer a.lock.Unlock()
    a.addCandidateBlockNoLock(block)
}

func (a *agreement) addCandidateBlockNoLock(block *types.Block) {
    a.candidateBlock[block.Hash] = block
}

func (a *agreement) findCandidateBlock(hash common.Hash) (*types.Block, bool) {
    a.lock.RLock()
    defer a.lock.RUnlock()
    return a.findCandidateBlockNoLock(hash)
}

func (a *agreement) findCandidateBlockNoLock(
    hash common.Hash) (*types.Block, bool) {
    b, e := a.candidateBlock[hash]
    return b, e
}
func (a *agreementData) countVote(period uint64, voteType types.VoteType) (
    blockHash common.Hash, ok bool) {
    a.lock.RLock()
    defer a.lock.RUnlock()
    return a.countVoteNoLock(period, voteType)
}

func (a *agreementData) countVoteNoLock(
    period uint64, voteType types.VoteType) (blockHash common.Hash, ok bool) {
    votes, exist := a.votes[period]
    if !exist {
        return
    }
    candidate := make(map[common.Hash]int)
    for _, vote := range votes[voteType] {
        if _, exist := candidate[vote.BlockHash]; !exist {
            candidate[vote.BlockHash] = 0
        }
        candidate[vote.BlockHash]++
    }
    for candidateHash, votes := range candidate {
        if votes >= a.requiredVote {
            blockHash = candidateHash
            ok = true
            return
        }
    }
    return
}

func (a *agreementData) setPeriod(period uint64) {
    for i := a.period + 1; i <= period; i++ {
        if _, exist := a.votes[i]; !exist {
            a.votes[i] = newVoteListMap()
        }
    }
    a.period = period
}