9 "github.com/vapor/errors"
10 "github.com/vapor/protocol/state"
14 numOfConsensusNode = 21
15 roundVoteBlockNums = 1000
17 // BlockTimeInterval indicate product one block per 500 milliseconds
18 BlockTimeInterval = 500
23 errHasNoChanceProductBlock = errors.New("the node has no chance to product a block in this round of voting")
24 errNotFoundConsensusNode = errors.New("can not found consensus node")
25 errVoteResultIsNotfinalized = errors.New("vote result is not finalized")
26 errPublicKeyIsNotConsensusNode = errors.New("public key is not consensus node")
29 type consensusNode struct {
35 type consensusNodeSlice []*consensusNode
37 func (c consensusNodeSlice) Len() int { return len(c) }
38 func (c consensusNodeSlice) Less(i, j int) bool { return c[i].voteNum > c[j].voteNum }
39 func (c consensusNodeSlice) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
41 type consensusNodeManager struct {
42 consensusNodeMap map[string]*consensusNode
43 effectiveStartHeight uint64
45 blockIndex *state.BlockIndex
49 func newConsensusNodeManager(store Store, blockIndex *state.BlockIndex) *consensusNodeManager {
50 return &consensusNodeManager{
51 consensusNodeMap: make(map[string]*consensusNode),
52 effectiveStartHeight: 1,
54 blockIndex: blockIndex,
58 func (c *consensusNodeManager) getConsensusNode(height uint64, pubkey string) (*consensusNode, error) {
61 if height >= c.effectiveStartHeight+roundVoteBlockNums {
62 return nil, errors.New("the vote has not been completed for the specified block height ")
66 consensusNodeMap := c.consensusNodeMap
67 // query history vote result
68 if height < c.effectiveStartHeight {
69 consensusNodeMap, err = c.getConsensusNodesByVoteResult(height)
75 node, exist := consensusNodeMap[pubkey]
77 return node, errNotFoundConsensusNode
82 func (c *consensusNodeManager) isBlocker(height uint64, blockTimestamp uint64, pubkey string) (bool, error) {
83 prevVoteRoundLastBlock := c.blockIndex.NodeByHeight(height - 1)
84 startTimestamp := prevVoteRoundLastBlock.Timestamp + BlockTimeInterval
86 consensusNodeMap, err := c.getConsensusNodesByVoteResult(height)
91 blockerNode, exist := consensusNodeMap[pubkey]
96 begin := getLastBlockTimeInTimeRange(startTimestamp, blockTimestamp, blockerNode.order)
97 end := begin + BlockNumEachNode*BlockTimeInterval
98 return blockTimestamp >= begin && blockTimestamp < end, nil
101 func (c *consensusNodeManager) nextLeaderTimeRange(pubkey []byte, bestBlockTimestamp, bestBlockHeight uint64) (uint64, uint64, error) {
105 startHeight := c.effectiveStartHeight
106 prevRoundLastBlock := c.blockIndex.NodeByHeight(startHeight - 1)
107 startTime := prevRoundLastBlock.Timestamp + BlockTimeInterval
108 endTime := bestBlockTimestamp + (roundVoteBlockNums-bestBlockHeight%roundVoteBlockNums)*BlockTimeInterval
110 consensusNode, exist := c.consensusNodeMap[hex.EncodeToString(pubkey)]
112 return 0, 0, errPublicKeyIsNotConsensusNode
115 nextLeaderTime, err := nextLeaderTimeHelper(startTime, endTime, uint64(time.Now().UnixNano()/1e6), consensusNode.order)
120 return nextLeaderTime, nextLeaderTime + BlockNumEachNode*BlockTimeInterval, nil
123 func nextLeaderTimeHelper(startTime, endTime, now, nodeOrder uint64) (uint64, error) {
124 nextLeaderTimestamp := getLastBlockTimeInTimeRange(startTime, now, nodeOrder)
125 roundBlockTime := uint64(BlockNumEachNode * numOfConsensusNode * BlockTimeInterval)
127 if int64(now-nextLeaderTimestamp) >= BlockNumEachNode*BlockTimeInterval {
128 nextLeaderTimestamp += roundBlockTime
129 if nextLeaderTimestamp >= endTime {
130 return 0, errHasNoChanceProductBlock
134 return nextLeaderTimestamp, nil
137 // updateConsensusNodes used to update consensus node after each round of voting
138 func (c *consensusNodeManager) updateConsensusNodes(bestBlockHeight uint64) error {
142 consensusNodeMap, err := c.getConsensusNodesByVoteResult(bestBlockHeight)
143 if err != nil && err != errVoteResultIsNotfinalized {
147 if err == errVoteResultIsNotfinalized {
151 c.consensusNodeMap = consensusNodeMap
152 c.effectiveStartHeight = bestBlockHeight / roundVoteBlockNums * roundVoteBlockNums
156 func getLastBlockTimeInTimeRange(startTimestamp, endTimestamp, order uint64) uint64 {
157 // One round of product block time for all consensus nodes
158 roundBlockTime := uint64(BlockNumEachNode * numOfConsensusNode * BlockTimeInterval)
159 // The start time of the last round of product block
160 lastRoundStartTime := startTimestamp + (endTimestamp-startTimestamp)/roundBlockTime*roundBlockTime
161 // The time of product block of the consensus in last round
162 return lastRoundStartTime + order*(BlockNumEachNode*BlockTimeInterval)
165 func (c *consensusNodeManager) getConsensusNodesByVoteResult(blockHeight uint64) (map[string]*consensusNode, error) {
168 if blockHeight >= c.effectiveStartHeight+roundVoteBlockNums {
169 return nil, errors.New("the given block height is greater than current vote start height")
172 if blockHeight >= c.effectiveStartHeight {
173 return c.consensusNodeMap, nil
176 voteResult, err := c.store.GetVoteResult(blockHeight / roundVoteBlockNums)
181 if !voteResult.Finalized {
182 return nil, errVoteResultIsNotfinalized
185 var nodes []*consensusNode
186 for pubkey, voteNum := range voteResult.NumOfVote {
187 nodes = append(nodes, &consensusNode{
192 // In principle, there is no need to sort all voting nodes.
193 // if there is a performance problem, consider the optimization later.
194 // TODO not consider the same number of votes
195 sort.Sort(consensusNodeSlice(nodes))
197 result := make(map[string]*consensusNode)
198 for i := 0; i < numOfConsensusNode; i++ {
200 node.order = uint64(i)
201 result[node.pubkey] = node