func (n *Node) onTimeout() error {
	if !n.role.IsVoter("", n.epoch) {
		return nil
	}
	epoch := n.epoch
	if !n.role.IsVoter("", epoch) {
		return errors.Errorf("non-voter calls onTimeout at epoch %d", epoch)
	}
	epoch++
	if c, err := n.verifier.NewClockMsg(epoch); err != nil {
		logger.Error("[%s] a timeout happened but failed to create clock(%d); err=%s",
			n.loggingId, epoch, err)
		return err
	} else {
		n.client.Broadcast(c)
		logger.Info("[%s] broadcast clock(%d) due to timeout", n.loggingId, epoch)
		return nil
	}
}
Run in the worker goroutine
This function is only called on voters.
package consensus
import (
	"fmt"
	"sync"
	"thunder2/blockchain"
	"thunder2/lgr"
	"thunder2/network"
	"thunder2/utils"
	"github.com/petar/GoLLRB/llrb"
	"github.com/pkg/errors"
)
var logger = lgr.NewLgr("/consensus")
type Role int
const (
	RoleFullNode = Role(1 << 0)
	RoleProposer = Role(1 << 1)
	RoleVoter    = Role(1 << 2)
)
type NodeConfig struct {
	LoggingId  string
	K          uint32  
	Chain      blockchain.BlockChain
	NodeClient NodeClient
	Role       RoleAssigner
	Verifier   blockchain.Verifier
	Epoch      blockchain.Epoch
	Timer      Timer
}
type StartStopWaiter struct {
	mutex sync.Mutex
	stopChan chan interface{}
	stoppedChan chan interface{}
}
type NodeClient interface {
	Broadcast(m blockchain.Message)
	Reply(source *network.Message, m blockchain.Message)
	CatchUp(source *network.Message, sn blockchain.BlockSn)
	UpdateEpoch(cNota blockchain.ClockMsgNota)
}
type Node struct {
	StartStopWaiter
	loggingId string
	k         uint32  
	chain    blockchain.BlockChain
	client   NodeClient
	role     RoleAssigner
	verifier blockchain.Verifier
	epoch    blockchain.Epoch
	timer    Timer
	votes            map[blockchain.BlockSn]map[string]blockchain.Vote
	broadcastedNotas map[blockchain.BlockSn]bool
	clockMsgs map[string]blockchain.ClockMsg
	voted *llrb.LLRB
	workChan chan work
}
type BlockCreator int
const (
	BlockCreatedByOther = BlockCreator(0)
	BlockCreatedBySelf  = BlockCreator(1)
)
type work struct {
	blob interface{}
	context interface{}
	ch chan error
}
type notarizedBlock struct {
	notarization blockchain.Notarization
	block        blockchain.Block
}
type proposalContext struct {
	source  *network.Message
	creator BlockCreator
}
type RoleAssigner interface {
	IsProposer(id string, epoch blockchain.Epoch) bool
	IsPrimaryProposer(id string, epoch blockchain.Epoch) bool
	IsVoter(id string, epoch blockchain.Epoch) bool
	IsBootnode(id string) bool
	GetProposerId(epoch blockchain.Epoch) string
	GetVoterId(epoch blockchain.Epoch) string
	GetBootnodeId() string
	GetDefaultProposerId() string
	GetDefaultVoterId() string
	GetNumVoters(epoch blockchain.Epoch) int
}
type Item struct {
	key   blockchain.BlockSn
	value interface{}
}
func init() {
}
func (s *StartStopWaiter) Start(
	action func(chan interface{}) error, stoppedChan chan interface{},
) error {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	if s.stoppedChan != nil {
		return errors.Errorf("is still running")
	}
	s.stopChan = make(chan interface{})
	if err := action(s.stopChan); err != nil {
		return err
	}
	s.stoppedChan = stoppedChan
	return nil
}
func (s *StartStopWaiter) StopAndWait() error {
	if err := s.Stop(); err != nil {
		return err
	}
	return s.Wait()
}
func (s *StartStopWaiter) Stop() error {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	if s.stopChan == nil {
		return errors.Errorf("Has called Stop() before; isRunning=%t", s.stoppedChan != nil)
	}
	close(s.stopChan)
	s.stopChan = nil
	return nil
}
func (s *StartStopWaiter) Wait() error {
	s.mutex.Lock()
	ch := s.stoppedChan
	s.mutex.Unlock()
	if ch != nil {
		<-ch
	}
	s.mutex.Lock()
	s.stoppedChan = nil
	s.mutex.Unlock()
	return nil
}
func NewNode(cfg NodeConfig) Node {
	return Node{
		loggingId: cfg.LoggingId,
		k:         cfg.K,
		chain:     cfg.Chain,
		client:    cfg.NodeClient,
		role:      cfg.Role,
		verifier:  cfg.Verifier,
		epoch:     cfg.Epoch,
		timer:            cfg.Timer,
		votes:            make(map[blockchain.BlockSn]map[string]blockchain.Vote),
		broadcastedNotas: make(map[blockchain.BlockSn]bool),
		clockMsgs:        make(map[string]blockchain.ClockMsg),
		voted:            llrb.New(),
		workChan: make(chan work, 1024),
	}
}
func (n *Node) Start() error {
	stoppedChan := make(chan interface{})
	action := func(stopChan chan interface{}) error {
		go func() {
			n.handleEventLoop(stopChan, stoppedChan)
		}()
		return nil
	}
	return n.StartStopWaiter.Start(action, stoppedChan)
}
func (n *Node) handleEventLoop(
	stopChan chan interface{}, stoppedChan chan interface{}) {
	n.timer.Reset(n.epoch)
	for {
		var err error
		select {
		case <-stopChan:
			close(stoppedChan)
			return
		case <-n.timer.GetChannel():
			logger.Info("[%s] timeout at epoch %d", n.loggingId, n.epoch)
			if !n.role.IsVoter("", n.epoch) {
				break
			}
			if err = n.onTimeout(); err != nil {
				logger.Warn("[%s] %s", err)
			}
			n.timer.Reset(n.epoch)
		case w := <-n.workChan:
			switch v := w.blob.(type) {
			case blockchain.Epoch:
				if v > n.epoch {
					logger.Info("[%s] update epoch %d -> %d", n.loggingId, n.epoch, v)
					n.epoch = v
					n.timer.Reset(n.epoch)
				} else {
					logger.Info("[%s] skip update epoch %d <= %d", n.loggingId, n.epoch, v)
				}
			case blockchain.Block:
				creator, ok := w.context.(BlockCreator)
				if !ok {
					utils.Bug("context is invalid; type=%T (%v)", w.context, w.context)
				}
				err = n.onReceivedBlock(v, creator)
			case blockchain.Proposal:
				ctx := w.context.(proposalContext)
				err = n.onReceivedProposal(v, ctx)
			case blockchain.Vote:
				err = n.onReceivedVote(v)
			case blockchain.Notarization:
				err = n.onReceivedNotarization(v)
			case blockchain.ClockMsg:
				err = n.onReceivedClockMsg(v)
			case blockchain.ClockMsgNota:
				err = n.onReceivedClockMsgNota(v)
			case blockchain.FreshestNotarizedChainExtendedEvent:
				if v.Sn.Epoch >= n.epoch {
					n.timer.Reset(n.epoch)
				}
				err = n.onReceivedFreshestNotarizedChainExtendedEvent(v)
			case blockchain.FinalizedChainExtendedEvent:
				err = n.onReceivedFinalizedChainExtendedEvent(v)
			case notarizedBlock:
				err = n.onReceivedNotarizedBlock(v.notarization, v.block)
			default:
				err = errors.Errorf("unknown type %v", w.blob)
			}
			if w.ch != nil {
				if err != nil {
					logger.Warn("[%s] received err=%s", n.loggingId, err)
				}
				w.ch <- err
			}
		}
	}
}