|
|
|
// Consensus package implements the Cosi PBFT consensus
|
|
|
|
package consensus // consensus
|
|
|
|
|
|
|
|
import (
|
|
|
|
"harmony-benchmark/p2p"
|
|
|
|
"regexp"
|
|
|
|
"log"
|
|
|
|
"strconv"
|
|
|
|
"harmony-benchmark/message"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Consensus data containing all info related to one consensus process
|
|
|
|
type Consensus struct {
|
|
|
|
state ConsensusState
|
|
|
|
// Signatures collected from validators
|
|
|
|
commits map[string]string
|
|
|
|
// Signatures collected from validators
|
|
|
|
responses map[string]string
|
|
|
|
// Actual block data to reach consensus on
|
|
|
|
data string
|
|
|
|
// List of validators
|
|
|
|
validators []p2p.Peer
|
|
|
|
// Leader
|
|
|
|
leader p2p.Peer
|
|
|
|
// private key of current node
|
|
|
|
priKey string
|
|
|
|
// Whether I am leader. False means I am validator
|
|
|
|
IsLeader bool
|
|
|
|
// Leader or validator Id - 2 byte
|
|
|
|
nodeId uint16
|
|
|
|
// Consensus Id (View Id) - 4 byte
|
|
|
|
consensusId uint32
|
|
|
|
// Blockhash - 32 byte
|
|
|
|
blockHash []byte
|
|
|
|
// BlockHeader to run consensus on
|
|
|
|
blockHeader []byte
|
|
|
|
|
|
|
|
// Signal channel for starting a new consensus process
|
|
|
|
ReadySignal chan int
|
|
|
|
|
|
|
|
//// Network related fields
|
|
|
|
msgCategory byte
|
|
|
|
actionType byte
|
|
|
|
}
|
|
|
|
|
|
|
|
// Consensus state enum for both leader and validator
|
|
|
|
// States for leader:
|
|
|
|
// READY, ANNOUNCE_DONE, CHALLENGE_DONE, FINISHED
|
|
|
|
// States for validator:
|
|
|
|
// READY, COMMIT_DONE, RESPONSE_DONE, FINISHED
|
|
|
|
type ConsensusState int
|
|
|
|
|
|
|
|
const (
|
|
|
|
READY ConsensusState = iota
|
|
|
|
ANNOUNCE_DONE
|
|
|
|
COMMIT_DONE
|
|
|
|
CHALLENGE_DONE
|
|
|
|
RESPONSE_DONE
|
|
|
|
FINISHED
|
|
|
|
)
|
|
|
|
|
|
|
|
// Returns string name for the ConsensusState enum
|
|
|
|
func (state ConsensusState) String() string {
|
|
|
|
names := [...]string{
|
|
|
|
"READY",
|
|
|
|
"ANNOUNCE_DONE",
|
|
|
|
"COMMIT_DONE",
|
|
|
|
"CHALLENGE_DONE",
|
|
|
|
"RESPONSE_DONE",
|
|
|
|
"FINISHED"}
|
|
|
|
|
|
|
|
if state < READY || state > RESPONSE_DONE {
|
|
|
|
return "Unknown"
|
|
|
|
}
|
|
|
|
return names[state]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new Consensus object
|
|
|
|
func NewConsensus(ip, port string, peers []p2p.Peer, leader p2p.Peer) Consensus {
|
|
|
|
// The first Ip, port passed will be leader.
|
|
|
|
consensus := Consensus{}
|
|
|
|
peer := p2p.Peer{Port: port, Ip: ip}
|
|
|
|
Peers := peers
|
|
|
|
leaderPeer := leader
|
|
|
|
if leaderPeer == peer {
|
|
|
|
consensus.IsLeader = true
|
|
|
|
} else {
|
|
|
|
consensus.IsLeader = false
|
|
|
|
}
|
|
|
|
consensus.commits = make(map[string]string)
|
|
|
|
consensus.responses = make(map[string]string)
|
|
|
|
consensus.leader = leaderPeer
|
|
|
|
consensus.validators = Peers
|
|
|
|
|
|
|
|
consensus.priKey = ip + ":" + port // use ip:port as unique key for now
|
|
|
|
|
|
|
|
reg, err := regexp.Compile("[^0-9]+")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
consensus.consensusId = 0
|
|
|
|
|
|
|
|
// For now use socket address as 16 byte Id
|
|
|
|
// TODO: populate with correct Id
|
|
|
|
socketId := reg.ReplaceAllString(consensus.priKey, "")
|
|
|
|
value, err := strconv.Atoi(socketId)
|
|
|
|
consensus.nodeId = uint16(value)
|
|
|
|
|
|
|
|
if consensus.IsLeader {
|
|
|
|
consensus.ReadySignal = make(chan int)
|
|
|
|
// send a signal to indicate it's ready to run consensus
|
|
|
|
go func() {
|
|
|
|
consensus.ReadySignal <- 1
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
consensus.msgCategory = byte(message.COMMITTEE)
|
|
|
|
consensus.actionType = byte(message.CONSENSUS)
|
|
|
|
return consensus
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Reset the state of the consensus
|
|
|
|
func (consensus *Consensus) ResetState() {
|
|
|
|
consensus.state = READY
|
|
|
|
consensus.commits = make(map[string]string)
|
|
|
|
consensus.responses = make(map[string]string)
|
|
|
|
}
|