draft of consensus+state code, compiles.

This commit is contained in:
Jae Kwon
2014-09-04 03:32:38 -07:00
parent e53b148acf
commit f030c69495
13 changed files with 222 additions and 217 deletions
+141
View File
@@ -0,0 +1,141 @@
package consensus
import (
"bytes"
"errors"
"sync"
. "github.com/tendermint/tendermint/blocks"
. "github.com/tendermint/tendermint/state"
)
// Helper for keeping track of block parts.
type BlockPartSet struct {
mtx sync.Mutex
signer *Account
height uint32
round uint16 // Not used
total uint16
numParts uint16
parts []*BlockPart
_block *Block // cache
}
var (
ErrInvalidBlockPartSignature = errors.New("Invalid block part signature") // Peer gave us a fake part
ErrInvalidBlockPartConflict = errors.New("Invalid block part conflict") // Signer signed conflicting parts
)
// Signer may be nil if signer is unknown beforehand.
func NewBlockPartSet(height uint32, round uint16, signer *Account) *BlockPartSet {
return &BlockPartSet{
signer: signer,
height: height,
round: round,
}
}
// In the case where the signer wasn't known prior to NewBlockPartSet(),
// user should call SetSigner() prior to AddBlockPart().
func (bps *BlockPartSet) SetSigner(signer *Account) {
bps.mtx.Lock()
defer bps.mtx.Unlock()
if bps.signer != nil {
panic("BlockPartSet signer already set.")
}
bps.signer = signer
}
func (bps *BlockPartSet) BlockParts() []*BlockPart {
bps.mtx.Lock()
defer bps.mtx.Unlock()
return bps.parts
}
func (bps *BlockPartSet) BitArray() []byte {
bps.mtx.Lock()
defer bps.mtx.Unlock()
if bps.parts == nil {
return nil
}
bitArray := make([]byte, (len(bps.parts)+7)/8)
for i, part := range bps.parts {
if part != nil {
bitArray[i/8] |= 1 << uint(i%8)
}
}
return bitArray
}
// If the part isn't valid, returns an error.
// err can be ErrInvalidBlockPart[Conflict|Signature]
func (bps *BlockPartSet) AddBlockPart(part *BlockPart) (added bool, err error) {
bps.mtx.Lock()
defer bps.mtx.Unlock()
// If part is invalid, return an error.
/* XXX
err = part.ValidateWithSigner(bps.signer)
if err != nil {
return false, err
}
*/
if bps.parts == nil {
// First received part for this round.
bps.parts = make([]*BlockPart, part.Total)
bps.total = uint16(part.Total)
bps.parts[int(part.Index)] = part
bps.numParts++
return true, nil
} else {
// Check part.Index and part.Total
if uint16(part.Index) >= bps.total {
return false, ErrInvalidBlockPartConflict
}
if uint16(part.Total) != bps.total {
return false, ErrInvalidBlockPartConflict
}
// Check for existing parts.
existing := bps.parts[part.Index]
if existing != nil {
if bytes.Equal(existing.Bytes, part.Bytes) {
// Ignore duplicate
return false, nil
} else {
return false, ErrInvalidBlockPartConflict
}
} else {
bps.parts[int(part.Index)] = part
bps.numParts++
return true, nil
}
}
}
func (bps *BlockPartSet) IsComplete() bool {
bps.mtx.Lock()
defer bps.mtx.Unlock()
return bps.total > 0 && bps.total == bps.numParts
}
func (bps *BlockPartSet) Block() *Block {
if !bps.IsComplete() {
return nil
}
bps.mtx.Lock()
defer bps.mtx.Unlock()
if bps._block == nil {
blockBytes := []byte{}
for _, part := range bps.parts {
blockBytes = append(blockBytes, part.Bytes...)
}
var n int64
var err error
block := ReadBlock(bytes.NewReader(blockBytes), &n, &err)
bps._block = block
}
return bps._block
}
+35 -34
View File
@@ -320,7 +320,7 @@ func (cm *ConsensusManager) stageProposal(proposal *BlockPartSet) error {
if !proposal.IsComplete() {
return errors.New("Incomplete proposal BlockPartSet")
}
block, blockParts := blockPartSet.Block(), blockPartSet.BlockParts()
block := proposal.Block()
err := block.ValidateBasic()
if err != nil {
return err
@@ -332,7 +332,7 @@ func (cm *ConsensusManager) stageProposal(proposal *BlockPartSet) error {
cm.mtx.Unlock()
// Commit block onto the copied state.
err := stateCopy.CommitBlock(block, block.Header.Time) // NOTE: fake commit time.
err = stateCopy.CommitBlock(block, block.Header.Time) // NOTE: fake commit time.
if err != nil {
return err
}
@@ -340,7 +340,7 @@ func (cm *ConsensusManager) stageProposal(proposal *BlockPartSet) error {
// Looks good!
cm.mtx.Lock()
cm.stagedProposal = proposal
cm.stagedState = state
cm.stagedState = stateCopy
cm.mtx.Unlock()
return nil
}
@@ -379,7 +379,7 @@ func (cm *ConsensusManager) voteProposal(rs *RoundState) error {
return err
}
// Vote for block.
err := cm.signAndVote(&Vote{
err = cm.signAndVote(&Vote{
Height: rs.Height,
Round: rs.Round,
Type: VoteTypeBare,
@@ -788,25 +788,26 @@ func (ps *PeerState) ApplyVoteRankMessage(msg *VoteRankMessage) error {
// Messages
const (
msgTypeUnknown = Byte(0x00)
msgTypeBlockPart = Byte(0x10)
msgTypeKnownBlockParts = Byte(0x11)
msgTypeVote = Byte(0x20)
msgTypeVoteRank = Byte(0x21)
msgTypeUnknown = byte(0x00)
msgTypeBlockPart = byte(0x10)
msgTypeKnownBlockParts = byte(0x11)
msgTypeVote = byte(0x20)
msgTypeVoteRank = byte(0x21)
)
// TODO: check for unnecessary extra bytes at the end.
func decodeMessage(bz ByteSlice) (msg interface{}) {
func decodeMessage(bz []byte) (msg interface{}) {
n, err := new(int64), new(error)
// log.Debug("decoding msg bytes: %X", bz)
switch Byte(bz[0]) {
switch bz[0] {
case msgTypeBlockPart:
return readBlockPartMessage(bytes.NewReader(bz[1:]))
return readBlockPartMessage(bytes.NewReader(bz[1:]), n, err)
case msgTypeKnownBlockParts:
return readKnownBlockPartsMessage(bytes.NewReader(bz[1:]))
return readKnownBlockPartsMessage(bytes.NewReader(bz[1:]), n, err)
case msgTypeVote:
return ReadVote(bytes.NewReader(bz[1:]))
return ReadVote(bytes.NewReader(bz[1:]), n, err)
case msgTypeVoteRank:
return readVoteRankMessage(bytes.NewReader(bz[1:]))
return readVoteRankMessage(bytes.NewReader(bz[1:]), n, err)
default:
return nil
}
@@ -818,15 +819,15 @@ type BlockPartMessage struct {
BlockPart *BlockPart
}
func readBlockPartMessage(r io.Reader) *BlockPartMessage {
func readBlockPartMessage(r io.Reader, n *int64, err *error) *BlockPartMessage {
return &BlockPartMessage{
BlockPart: ReadBlockPart(r),
BlockPart: ReadBlockPart(r, n, err),
}
}
func (m *BlockPartMessage) WriteTo(w io.Writer) (n int64, err error) {
n, err = WriteTo(msgTypeBlockPart, w, n, err)
n, err = WriteTo(m.BlockPart, w, n, err)
WriteByte(w, msgTypeBlockPart, &n, &err)
WriteBinary(w, m.BlockPart, &n, &err)
return
}
@@ -839,22 +840,22 @@ func (m *BlockPartMessage) String() string {
type KnownBlockPartsMessage struct {
Height uint32
SecondsSinceStartTime uint32
BlockPartsBitArray ByteSlice
BlockPartsBitArray []byte
}
func readKnownBlockPartsMessage(r io.Reader) *KnownBlockPartsMessage {
func readKnownBlockPartsMessage(r io.Reader, n *int64, err *error) *KnownBlockPartsMessage {
return &KnownBlockPartsMessage{
Height: Readuint32(r),
SecondsSinceStartTime: Readuint32(r),
BlockPartsBitArray: ReadByteSlice(r),
Height: ReadUInt32(r, n, err),
SecondsSinceStartTime: ReadUInt32(r, n, err),
BlockPartsBitArray: ReadByteSlice(r, n, err),
}
}
func (m *KnownBlockPartsMessage) WriteTo(w io.Writer) (n int64, err error) {
n, err = WriteTo(msgTypeKnownBlockParts, w, n, err)
n, err = WriteTo(UInt32(m.Height), w, n, err)
n, err = WriteTo(UInt32(m.SecondsSinceStartTime), w, n, err)
n, err = WriteTo(m.BlockPartsBitArray, w, n, err)
WriteByte(w, msgTypeKnownBlockParts, &n, &err)
WriteUInt32(w, m.Height, &n, &err)
WriteUInt32(w, m.SecondsSinceStartTime, &n, &err)
WriteByteSlice(w, m.BlockPartsBitArray, &n, &err)
return
}
@@ -871,17 +872,17 @@ type VoteRankMessage struct {
Rank uint8
}
func readVoteRankMessage(r io.Reader) *VoteRankMessage {
func readVoteRankMessage(r io.Reader, n *int64, err *error) *VoteRankMessage {
return &VoteRankMessage{
ValidatorId: Readuint64(r),
Rank: Readuint8(r),
ValidatorId: ReadUInt64(r, n, err),
Rank: ReadUInt8(r, n, err),
}
}
func (m *VoteRankMessage) WriteTo(w io.Writer) (n int64, err error) {
n, err = WriteTo(msgTypeVoteRank, w, n, err)
n, err = WriteTo(UInt64(m.ValidatorId), w, n, err)
n, err = WriteTo(UInt8(m.Rank), w, n, err)
WriteByte(w, msgTypeVoteRank, &n, &err)
WriteUInt64(w, m.ValidatorId, &n, &err)
WriteUInt8(w, m.Rank, &n, &err)
return
}
+20
View File
@@ -0,0 +1,20 @@
package consensus
import (
db_ "github.com/tendermint/tendermint/db"
. "github.com/tendermint/tendermint/state"
)
//-----------------------------------------------------------------------------
// TODO: Ensure that double signing never happens via an external persistent check.
type PrivValidator struct {
PrivAccount
db *db_.LevelDB
}
// Modifies the vote object in memory.
// Double signing results in an error.
func (pv *PrivValidator) SignVote(vote *Vote) error {
return nil
}
+23 -26
View File
@@ -1,12 +1,9 @@
package consensus
import (
"bytes"
"sync"
"time"
. "github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/blocks"
. "github.com/tendermint/tendermint/common"
. "github.com/tendermint/tendermint/state"
)
@@ -18,12 +15,12 @@ var (
// Tracks consensus state across block heights and rounds.
type ConsensusState struct {
mtx sync.Mutex
height uint32 // Height we are working on.
validatorsR0 map[uint64]*Validator // A copy of the validators at round 0
lockedProposal *BlockPartSet // A BlockPartSet of the locked proposal.
startTime time.Time // Start of round 0 for this height.
commits *VoteSet // Commits for this height.
roundState *RoundState // The RoundState object for the current round.
height uint32 // Height we are working on.
validatorsR0 *ValidatorSet // A copy of the validators at round 0
lockedProposal *BlockPartSet // A BlockPartSet of the locked proposal.
startTime time.Time // Start of round 0 for this height.
commits *VoteSet // Commits for this height.
roundState *RoundState // The RoundState object for the current round.
}
func NewConsensusState(state *State) *ConsensusState {
@@ -70,7 +67,7 @@ func (cs *ConsensusState) Update(state *State) {
cs.height = stateHeight
cs.validatorsR0 = state.Validators().Copy() // NOTE: immutable.
cs.lockedProposal = nil
cs.startTime = state.commitTime // XXX is this what we want?
cs.startTime = state.CommitTime() // XXX is this what we want?
cs.commits = NewVoteSet(stateHeight, 0, VoteTypeCommit, cs.validatorsR0)
// Setup the roundState
@@ -94,20 +91,20 @@ func (cs *ConsensusState) SetupRound(round uint16) {
func (cs *ConsensusState) setupRound(round uint16) {
// Increment validator accums as necessary.
// We need to start with cs.validatorsR0 or cs.roundState.Validators
var validators map[uint64]*Validator = nil
var validators *ValidatorSet
var validatorsRound uint16
if cs.roundState == nil {
// We have no roundState so we start from validatorsR0 at round 0.
validators = copyValidators(cs.validatorsR0)
validators = cs.validatorsR0.Copy()
validatorsRound = 0
} else {
// We have a previous roundState so we start from that.
validators = copyValidators(cs.roundState.Validators)
validators = cs.roundState.Validators.Copy()
validatorsRound = cs.roundState.Round
}
// Increment all the way to round.
for r := validatorsRound; r < round; r++ {
incrementAccum(validators)
validators.IncrementAccum()
}
roundState := NewRoundState(cs.height, round, cs.startTime, validators, cs.commits)
@@ -128,25 +125,25 @@ const (
// RoundState encapsulates all the state needed to engage in the consensus protocol.
type RoundState struct {
Height uint32 // Immutable
Round uint16 // Immutable
StartTime time.Time // Time in which consensus started for this height.
Expires time.Time // Time after which this round is expired.
Proposer *Validator // The proposer to propose a block for this round.
Validators map[uint64]*Validator // All validators with modified accumPower for this round.
BlockPartSet *BlockPartSet // All block parts received for this round.
RoundBareVotes *VoteSet // All votes received for this round.
RoundPrecommits *VoteSet // All precommits received for this round.
Commits *VoteSet // A shared object for all commit votes of this height.
Height uint32 // Immutable
Round uint16 // Immutable
StartTime time.Time // Time in which consensus started for this height.
Expires time.Time // Time after which this round is expired.
Proposer *Validator // The proposer to propose a block for this round.
Validators *ValidatorSet // All validators with modified accumPower for this round.
BlockPartSet *BlockPartSet // All block parts received for this round.
RoundBareVotes *VoteSet // All votes received for this round.
RoundPrecommits *VoteSet // All precommits received for this round.
Commits *VoteSet // A shared object for all commit votes of this height.
mtx sync.Mutex
step uint8 // mutable
}
func NewRoundState(height uint32, round uint16, startTime time.Time,
validators map[uint64]*Validator, commits *VoteSet) *RoundState {
validators *ValidatorSet, commits *VoteSet) *RoundState {
proposer := getProposer(validators)
proposer := validators.GetProposer()
blockPartSet := NewBlockPartSet(height, round, &(proposer.Account))
roundBareVotes := NewVoteSet(height, round, VoteTypeBare, validators)
roundPrecommits := NewVoteSet(height, round, VoteTypePrecommit, validators)
+208
View File
@@ -0,0 +1,208 @@
package consensus
import (
"bytes"
"errors"
"fmt"
"io"
"sync"
. "github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/blocks"
"github.com/tendermint/tendermint/config"
. "github.com/tendermint/tendermint/state"
)
const (
VoteTypeBare = byte(0x00)
VoteTypePrecommit = byte(0x01)
VoteTypeCommit = byte(0x02)
)
var (
ErrVoteUnexpectedPhase = errors.New("Unexpected phase")
ErrVoteInvalidAccount = errors.New("Invalid round vote account")
ErrVoteInvalidSignature = errors.New("Invalid round vote signature")
ErrVoteInvalidHash = errors.New("Invalid hash")
ErrVoteConflictingSignature = errors.New("Conflicting round vote signature")
)
// Represents a bare, precommit, or commit vote for proposals.
type Vote struct {
Height uint32
Round uint16
Type byte
Hash []byte // empty if vote is nil.
Signature
}
func ReadVote(r io.Reader, n *int64, err *error) *Vote {
return &Vote{
Height: ReadUInt32(r, n, err),
Round: ReadUInt16(r, n, err),
Type: ReadByte(r, n, err),
Hash: ReadByteSlice(r, n, err),
Signature: ReadSignature(r, n, err),
}
}
func (v *Vote) WriteTo(w io.Writer) (n int64, err error) {
WriteUInt32(w, v.Height, &n, &err)
WriteUInt16(w, v.Round, &n, &err)
WriteByte(w, v.Type, &n, &err)
WriteByteSlice(w, v.Hash, &n, &err)
WriteBinary(w, v.Signature, &n, &err)
return
}
// This is the byteslice that validators should sign to signify a vote
// for the given proposal at given height & round.
// If hash is nil, the vote is a nil vote.
func (v *Vote) GetDocument() []byte {
switch v.Type {
case VoteTypeBare:
if len(v.Hash) == 0 {
doc := fmt.Sprintf("%v://consensus/%v/%v/b\nnil",
config.Config.Network, v.Height, v.Round)
return []byte(doc)
} else {
doc := fmt.Sprintf("%v://consensus/%v/%v/b\n%v",
config.Config.Network, v.Height, v.Round,
CalcBlockURI(v.Height, v.Hash))
return []byte(doc)
}
case VoteTypePrecommit:
if len(v.Hash) == 0 {
doc := fmt.Sprintf("%v://consensus/%v/%v/p\nnil",
config.Config.Network, v.Height, v.Round)
return []byte(doc)
} else {
doc := fmt.Sprintf("%v://consensus/%v/%v/p\n%v",
config.Config.Network, v.Height, v.Round,
CalcBlockURI(v.Height, v.Hash))
return []byte(doc)
}
case VoteTypeCommit:
if len(v.Hash) == 0 {
panic("Commit hash cannot be nil")
} else {
doc := fmt.Sprintf("%v://consensus/%v/c\n%v",
config.Config.Network, v.Height, // omit round info
CalcBlockURI(v.Height, v.Hash))
return []byte(doc)
}
default:
panic("Unknown vote type")
}
}
//-----------------------------------------------------------------------------
// VoteSet helps collect signatures from validators at each height+round
// for a predefined vote type.
type VoteSet struct {
mtx sync.Mutex
height uint32
round uint16
type_ byte
validators *ValidatorSet
votes map[uint64]*Vote
votesByHash map[string]uint64
totalVotes uint64
totalVotingPower uint64
}
// Constructs a new VoteSet struct used to accumulate votes for each round.
func NewVoteSet(height uint32, round uint16, type_ byte, validators *ValidatorSet) *VoteSet {
totalVotingPower := uint64(0)
for _, val := range validators.Map() {
totalVotingPower += val.VotingPower
}
return &VoteSet{
height: height,
round: round,
type_: type_,
validators: validators,
votes: make(map[uint64]*Vote, validators.Size()),
votesByHash: make(map[string]uint64),
totalVotes: 0,
totalVotingPower: totalVotingPower,
}
}
// True if added, false if not.
// Returns ErrVote[UnexpectedPhase|InvalidAccount|InvalidSignature|InvalidHash|ConflictingSignature]
func (vs *VoteSet) AddVote(vote *Vote) (bool, error) {
vs.mtx.Lock()
defer vs.mtx.Unlock()
// Make sure the phase matches.
if vote.Height != vs.height || vote.Round != vs.round || vote.Type != vs.type_ {
return false, ErrVoteUnexpectedPhase
}
val := vs.validators.Get(vote.SignerId)
// Ensure that signer is a validator.
if val == nil {
return false, ErrVoteInvalidAccount
}
// Check signature.
if !val.Verify(vote.GetDocument(), vote.Signature.Bytes) {
// Bad signature.
return false, ErrVoteInvalidSignature
}
// If vote already exists, return false.
if existingVote, ok := vs.votes[vote.SignerId]; ok {
if bytes.Equal(existingVote.Hash, vote.Hash) {
return false, nil
} else {
return false, ErrVoteConflictingSignature
}
}
vs.votes[vote.SignerId] = vote
vs.votesByHash[string(vote.Hash)] += val.VotingPower
vs.totalVotes += val.VotingPower
return true, nil
}
// Returns either a blockhash (or nil) that received +2/3 majority.
// If there exists no such majority, returns (nil, false).
func (vs *VoteSet) TwoThirdsMajority() (hash []byte, ok bool) {
vs.mtx.Lock()
defer vs.mtx.Unlock()
twoThirdsMajority := (vs.totalVotingPower*2 + 2) / 3
if vs.totalVotes < twoThirdsMajority {
return nil, false
}
for hash, votes := range vs.votesByHash {
if votes >= twoThirdsMajority {
if hash == "" {
return nil, true
} else {
return []byte(hash), true
}
}
}
return nil, false
}
// Returns blockhashes (or nil) that received a +1/3 majority.
// If there exists no such majority, returns nil.
func (vs *VoteSet) OneThirdMajority() (hashes []interface{}) {
vs.mtx.Lock()
defer vs.mtx.Unlock()
oneThirdMajority := (vs.totalVotingPower + 2) / 3
if vs.totalVotes < oneThirdMajority {
return nil
}
for hash, votes := range vs.votesByHash {
if votes >= oneThirdMajority {
if hash == "" {
hashes = append(hashes, nil)
} else {
hashes = append(hashes, []byte(hash))
}
}
}
return hashes
}