refactor from Binary centric model to global method model

This commit is contained in:
Jae Kwon
2014-09-03 20:41:57 -07:00
parent d0ec18dc16
commit e53b148acf
23 changed files with 624 additions and 692 deletions

127
state/state.go Normal file
View File

@@ -0,0 +1,127 @@
package state
import (
"bytes"
"sync"
"time"
. "github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/blocks"
db_ "github.com/tendermint/tendermint/db"
"github.com/tendermint/tendermint/merkle"
)
var (
stateKey = []byte("stateKey")
)
type State struct {
mtx sync.Mutex
db db_.Db
height uint32
commitTime time.Time
accounts merkle.Tree
validators *ValidatorSet
}
func LoadState(db db_.Db) *State {
s := &State{}
buf := db.Get(stateKey)
if len(buf) == 0 {
s.height = uint32(0)
s.commitTime = time.Unix(0, 0) // XXX BOOTSTRAP
s.accounts = merkle.NewIAVLTree(db) // XXX BOOTSTRAP
s.validators = NewValidatorSet(nil) // XXX BOOTSTRAP
} else {
reader := bytes.NewReader(buf)
var n int64
var err error
s.height = ReadUInt32(reader, &n, &err)
s.commitTime = ReadTime(reader, &n, &err)
accountsMerkleRoot := ReadByteSlice(reader, &n, &err)
s.accounts = merkle.NewIAVLTreeFromHash(db, accountsMerkleRoot)
s.validators = NewValidatorSet(nil)
for reader.Len() > 0 {
validator := ReadValidator(reader, &n, &err)
s.validators.Add(validator)
}
if err != nil {
panic(err)
}
}
return s
}
func (s *State) Save() {
s.mtx.Lock()
defer s.mtx.Unlock()
s.accounts.Save()
var buf bytes.Buffer
var n int64
var err error
WriteUInt32(&buf, s.height, &n, &err)
WriteTime(&buf, s.commitTime, &n, &err)
WriteByteSlice(&buf, s.accounts.Hash(), &n, &err)
for _, validator := range s.validators.Map() {
WriteBinary(&buf, validator, &n, &err)
}
if err != nil {
panic(err)
}
s.db.Set(stateKey, buf.Bytes())
}
func (s *State) Copy() *State {
s.mtx.Lock()
defer s.mtx.Unlock()
return &State{
db: s.db,
height: s.height,
commitTime: s.commitTime,
accounts: s.accounts.Copy(),
validators: s.validators.Copy(),
}
}
func (s *State) CommitTx(tx *Tx) error {
s.mtx.Lock()
defer s.mtx.Unlock()
// TODO commit the tx
panic("Implement CommitTx()")
return nil
}
func (s *State) CommitBlock(b *Block, commitTime time.Time) error {
s.mtx.Lock()
defer s.mtx.Unlock()
// TODO commit the txs
// XXX also increment validator accum.
panic("Implement CommitBlock()")
return nil
}
func (s *State) Height() uint32 {
s.mtx.Lock()
defer s.mtx.Unlock()
return s.height
}
func (s *State) CommitTime() time.Time {
s.mtx.Lock()
defer s.mtx.Unlock()
return s.commitTime
}
// The returned ValidatorSet gets mutated upon s.Commit*().
func (s *State) Validators() *ValidatorSet {
s.mtx.Lock()
defer s.mtx.Unlock()
return s.validators
}
func (s *State) Account(accountId uint64) *Account {
s.mtx.Lock()
defer s.mtx.Unlock()
// XXX: figure out a way to load an Account Binary type.
return s.accounts.Get(accountId)
}

View File

@@ -5,7 +5,7 @@ import (
. "github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/blocks"
//. "github.com/tendermint/tendermint/common"
. "github.com/tendermint/tendermint/common"
db_ "github.com/tendermint/tendermint/db"
)
@@ -20,15 +20,15 @@ type Validator struct {
}
// Used to persist the state of ConsensusStateControl.
func ReadValidator(r io.Reader) *Validator {
func ReadValidator(r io.Reader, n *int64, err *error) *Validator {
return &Validator{
Account: Account{
Id: Readuint64(r),
PubKey: ReadByteSlice(r),
Id: ReadUInt64(r, n, err),
PubKey: ReadByteSlice(r, n, err),
},
BondHeight: Readuint32(r),
VotingPower: Readuint64(r),
Accum: Readint64(r),
BondHeight: ReadUInt32(r, n, err),
VotingPower: ReadUInt64(r, n, err),
Accum: ReadInt64(r, n, err),
}
}
@@ -44,11 +44,11 @@ func (v *Validator) Copy() *Validator {
// Used to persist the state of ConsensusStateControl.
func (v *Validator) WriteTo(w io.Writer) (n int64, err error) {
n, err = WriteTo(UInt64(v.Id), w, n, err)
n, err = WriteTo(v.PubKey, w, n, err)
n, err = WriteTo(UInt32(v.BondHeight), w, n, err)
n, err = WriteTo(UInt64(v.VotingPower), w, n, err)
n, err = WriteTo(Int64(v.Accum), w, n, err)
WriteUInt64(w, v.Id, &n, &err)
WriteByteSlice(w, v.PubKey, &n, &err)
WriteUInt32(w, v.BondHeight, &n, &err)
WriteUInt64(w, v.VotingPower, &n, &err)
WriteInt64(w, v.Accum, &n, &err)
return
}
@@ -78,7 +78,7 @@ func NewValidatorSet(validators map[uint64]*Validator) *ValidatorSet {
validators = make(map[uint64]*Validator)
}
return &ValidatorSet{
valdiators: validators,
validators: validators,
}
}
@@ -104,7 +104,7 @@ func (v *ValidatorSet) IncrementAccum() {
func (v *ValidatorSet) Copy() *ValidatorSet {
mapCopy := map[uint64]*Validator{}
for _, val := range validators {
for _, val := range v.validators {
mapCopy[val.Id] = val.Copy()
}
return &ValidatorSet{
@@ -112,12 +112,12 @@ func (v *ValidatorSet) Copy() *ValidatorSet {
}
}
func (v *ValidatorSet) Add(validator *Valdaitor) {
func (v *ValidatorSet) Add(validator *Validator) {
v.validators[validator.Id] = validator
}
func (v *ValidatorSet) Get(id uint64) *Validator {
return v.validators[validator.Id]
return v.validators[id]
}
func (v *ValidatorSet) Map() map[uint64]*Validator {

207
state/vote.go Normal file
View File

@@ -0,0 +1,207 @@
package state
import (
"bytes"
"errors"
"fmt"
"io"
"sync"
. "github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/blocks"
"github.com/tendermint/tendermint/config"
)
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 map[uint64]*Validator
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 map[uint64]*Validator) *VoteSet {
totalVotingPower := uint64(0)
for _, val := range validators {
totalVotingPower += val.VotingPower
}
return &VoteSet{
height: height,
round: round,
type_: type_,
validators: validators,
votes: make(map[uint64]*Vote, len(validators)),
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[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
}