gobft

package module
v0.0.0-...-5104f13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 31, 2019 License: MIT Imports: 13 Imported by: 1

README

gobft

gobft is a BFT library written in go. It's a re-implementation of tendermint bft. cmd-markdown-logo

Terminology

  • validator: all the nodes in a distributed system that participate in the consensus process

  • POL: proof of lock. A validator reaches POL for a certain proposal when it receives +2/3 prevote for this proposal and precommit it. Once it reaches POL, the validator can only prevote/precommit the POLed proposal in later rounds until it has a PoLC

  • PoLC: proof of lock change (unlock previous POLed proposal). A validator reaches PoLC when it has +2/3 prevote for a different proposal in a higher round.

  • To prevote or precommit something means to broadcast a prevote vote or precommit vote for something. e.g. prevotePOL means to broadcast a prevote vote for a POL proposal. prevoteNIL means to broadcast a prevote vote for nothing.

  • +2/3 means a proposal got the vote from more than 2/3 of all the validators in a certain round

  • 2/3 Any: more than 2/3 of all the validators broadcast their votes, yet no proposal reached +2/3 (i.e. the validators voted for different proposals)

    A validator is a node in a distributed system that participates in the bft consensus process. It proposes and votes for a certain proposal. Each validator should maintain a set of all the PubValidators so that it can verifies messages sent by other validators. Each validator should have exactly one PrivValidator which contains its private key so that it can sign a message. A validator can be a proposer, the rules of which validator becomes a valid proposer at a certain time is totally decided by user.

    To get more details of POL, please refer to tendermint.pdf chapter 3

Note

IPubValidator and IPrivValidator interface provides a abstract access of user-defined signature type(e.g. ecc or rsa)

In a nutshell, user should implement the following interface:

// ICommittee represents a validator group which contains all validators at
// a certain height. User should typically create a new ICommittee and register
// it to the bft core before starting a new height consensus process if
// validators need to be updated
type ICommittee interface {
   GetValidator(key message.PubKey) IPubValidator
   IsValidator(key message.PubKey) bool
   TotalVotingPower() int64

   GetCurrentProposer(round int) message.PubKey
   // DecidesProposal decides what will be proposed if this validator is the current
   // proposer.
   DecidesProposal() message.ProposedData

   // ValidateProposed validates the proposed data
   ValidateProposed(data message.ProposedData) bool

   // Commit defines the actions the users taken when consensus is reached
   Commit(p message.ProposedData) error

   GetAppState() *message.AppState
   // BroadCast sends msg to other validators
   BroadCast(msg message.ConsensusMessage) error
}

// IPubValidator verifies if a message is properly signed by the right validator
type IPubValidator interface {
   VerifySig(digest, signature []byte) bool
   GetPubKey() message.PubKey
   GetVotingPower() int64
   SetVotingPower(int64)
}

// IPrivValidator signs a message
type IPrivValidator interface {
   GetPubKey() message.PubKey
   Sign(digest []byte) []byte
}

Data flow and state transition

cmd-markdown-logo

TODO list

  • system halt and recover The whole network might halt and stop voting process under extreme circumstances. Taking the following scecario for example: There are 4 validators(A, B, C, D) in totally, in the first round of the vote, both A and B received +2/3 prevotes for proposal x. So they broadcasted precommits and locked on x. Meanwhile C and D didn't get +2/3 prevotes due to network issues so they broadcasted precommits for nil. No consensus can be reached in this round so all 4 validators started a new round. In this round, another validator was chosen as the current proposer and it proposed y. Since A is locked on x, A prevoted for x. C and D didn't lock on any previous proposal so they prevoted for y. Now say B is byzantine and it prevoted for y. Now C and D saw +2/3 prevotes for y and they both precommitted for y and locked on it. If any of prevotes for y didn't reach A due to network issue and B deliberately shut himself down, the rest 3 validators was locked on 2 different proposals and will never reach consensus.

The above example has 1 malicious node and 1 node with network failure so there're 2 byzantine validators out of 4, which is more than what bft protocol can handle. With 1 byzantine validator, bft can work well only if there's no message loss during network transmission. However it's too naive to make such assumption in real life. Additional mechanism should be introduce so that validators can detect the halt and recover from it.

  • message retransmission gobft requires that at least +2/3 of the vote messages are successfully delivered to at least +2/3 validators. Validators should be able to proactively request missing votes from other validators.

  • special handling when number of validators is < 3 The mininum requirement of validators now is 3.

Documentation

Index

Constants

View Source
const (
	RoundStepNewHeight      = RoundStepType(0x01) // Wait til CommitTime + timeoutCommit
	RoundStepNewRound       = RoundStepType(0x02) // Setup new round and go to RoundStepPropose
	RoundStepPropose        = RoundStepType(0x03) // Did propose, gossip proposal
	RoundStepPrevote        = RoundStepType(0x04) // Did prevote, gossip prevotes
	RoundStepPrevoteFetch   = RoundStepType(0x05) // Waiting for 2/3 Any prevotes, fetch votes every second
	RoundStepPrevoteWait    = RoundStepType(0x06) // Did receive any +2/3 prevotes, start timeout
	RoundStepPrecommit      = RoundStepType(0x07) // Did precommit, gossip precommits
	RoundStepPrecommitFetch = RoundStepType(0x08) // Waiting for 2/3 Any precommits
	RoundStepPrecommitWait  = RoundStepType(0x09) // Did receive any +2/3 precommits, start timeout
	RoundStepCommit         = RoundStepType(0x0a) // Entered commit state machine

)

RoundStepType

Variables

View Source
var (
	ErrVoteUnexpectedStep            = errors.New("Unexpected step")
	ErrVoteInvalidValidatorIndex     = errors.New("Invalid validator index")
	ErrVoteInvalidValidatorAddress   = errors.New("Invalid validator address")
	ErrVoteInvalidSignature          = errors.New("Invalid signature")
	ErrVoteInvalidBlockHash          = errors.New("Invalid block hash")
	ErrVoteNonDeterministicSignature = errors.New("Non-deterministic signature")
	ErrVoteNil                       = errors.New("Nil vote")
	ErrVoteMismatchedBase            = errors.New("Invalid base")
	ErrVoteHeightMismatch            = errors.New("Error vote height mismatch")
	ErrVoteInvalidRound              = errors.New("Invalid round")

	ErrInvalidProposer          = errors.New("Error invalid proposer")
	ErrInvalidProposalSignature = errors.New("Error invalid proposal signature")
	ErrInvalidProposalPOLRound  = errors.New("Error invalid proposal POL round")
	ErrAddingVote               = errors.New("Error adding vote")
)
View Source
var FetchInterval = time.Second
View Source
var (
	GotVoteFromUnwantedRoundError = errors.New("Peer has sent a vote that does not match our round for more than one round")
)

Functions

This section is empty.

Types

type Config

type Config struct {
	TimeoutPropose        time.Duration `mapstructure:"timeout_propose"`
	TimeoutProposeDelta   time.Duration `mapstructure:"timeout_propose_delta"`
	TimeoutPrevote        time.Duration `mapstructure:"timeout_prevote"`
	TimeoutPrevoteDelta   time.Duration `mapstructure:"timeout_prevote_delta"`
	TimeoutPrecommit      time.Duration `mapstructure:"timeout_precommit"`
	TimeoutPrecommitDelta time.Duration `mapstructure:"timeout_precommit_delta"`
	TimeoutCommit         time.Duration `mapstructure:"timeout_commit"`

	// Make progress as soon as we have all the precommits (as if TimeoutCommit = 0)
	SkipTimeoutCommit bool `mapstructure:"skip_timeout_commit"`
}

func DefaultConfig

func DefaultConfig() *Config

DefaultConfig returns a default configuration for the consensus service

func TestConfig

func TestConfig() *Config

TestConfig returns a configuration for testing the consensus service

func (*Config) Commit

func (cfg *Config) Commit(t time.Time) time.Time

Commit returns the amount of time to wait for straggler votes after receiving +2/3 precommits for a single block (ie. a commit).

func (*Config) Precommit

func (cfg *Config) Precommit(round int) time.Duration

Precommit returns the amount of time to wait for straggler votes after receiving any +2/3 precommits

func (*Config) Prevote

func (cfg *Config) Prevote(round int) time.Duration

Prevote returns the amount of time to wait for straggler votes after receiving any +2/3 prevotes

func (*Config) Propose

func (cfg *Config) Propose(round int) time.Duration

Propose returns the amount of time to wait for a proposal

func (*Config) ValidateBasic

func (cfg *Config) ValidateBasic() error

ValidateBasic performs basic validation (checking param bounds, etc.) and returns an error if any check fails.

type Core

type Core struct {
	RoundState

	sync.RWMutex
	sync.WaitGroup
	// contains filtered or unexported fields
}

func NewCore

func NewCore(vals custom.ICommittee, pVal custom.IPrivValidator) *Core

func (*Core) GetLastCommit

func (c *Core) GetLastCommit() *message.Commit

func (*Core) GetRoundState

func (c *Core) GetRoundState() *RoundState

GetRoundState returns a shallow copy of the internal consensus state.

func (*Core) RecvMsg

func (c *Core) RecvMsg(msg message.ConsensusMessage, p custom.IPeer) error

RecvMsg accepts a ConsensusMessage and delivers it to receiveRoutine

func (*Core) SetLogger

func (c *Core) SetLogger(lg *logrus.Logger)

func (*Core) SetName

func (c *Core) SetName(n string)

func (*Core) Start

func (c *Core) Start() error

func (*Core) Stop

func (c *Core) Stop() error

type ErrVoteConflictingVotes

type ErrVoteConflictingVotes struct {
}

func NewConflictingVoteError

func NewConflictingVoteError() *ErrVoteConflictingVotes

func (*ErrVoteConflictingVotes) Error

func (err *ErrVoteConflictingVotes) Error() string

type HeightVoteSet

type HeightVoteSet struct {
	// contains filtered or unexported fields
}

Keeps track of all VoteSets from round 0 to round 'round'.

Also keeps track of up to one RoundVoteSet greater than 'round' from each peer, to facilitate catchup syncing of commits.

A commit is +2/3 precommits for a block at a round, but which round is not known in advance, so when a peer provides a precommit for a round greater than mtx.round, we create a new entry in roundVoteSets but also remember the peer to prevent abuse.

func NewHeightVoteSet

func NewHeightVoteSet(height int64, valSet *Validators, b *message.ProposedData) *HeightVoteSet

func (*HeightVoteSet) AddVote

func (hvs *HeightVoteSet) AddVote(vote *message.Vote) (added bool, err error)

Duplicate votes return added=false, err=nil. By convention, peerID is "" if origin is self.

func (*HeightVoteSet) Height

func (hvs *HeightVoteSet) Height() int64

func (*HeightVoteSet) POLInfo

func (hvs *HeightVoteSet) POLInfo() (polRound int, polData message.ProposedData)

Last round and blockID that has +2/3 prevotes for a particular block or nil. Returns -1 if no such round exists.

func (*HeightVoteSet) Precommits

func (hvs *HeightVoteSet) Precommits(round int) *VoteSet

func (*HeightVoteSet) Prevotes

func (hvs *HeightVoteSet) Prevotes(round int) *VoteSet

func (*HeightVoteSet) Reset

func (hvs *HeightVoteSet) Reset(height int64, valSet *Validators, b *message.ProposedData)

func (*HeightVoteSet) Round

func (hvs *HeightVoteSet) Round() int

func (*HeightVoteSet) SetRound

func (hvs *HeightVoteSet) SetRound(round int)

Create more RoundVoteSets up to round.

func (*HeightVoteSet) String

func (hvs *HeightVoteSet) String() string

func (*HeightVoteSet) StringIndented

func (hvs *HeightVoteSet) StringIndented(indent string) string

type RoundState

type RoundState struct {
	Height     int64
	Round      int
	Step       RoundStepType
	StartTime  time.Time
	CommitTime time.Time // Subjective time when +2/3 precommits for Block at Round were found

	Proposal       *message.Vote
	LockedRound    int
	LockedProposal *message.Vote
	Votes          *HeightVoteSet
	CommitRound    int
	LastCommit     *VoteSet // Last precommits at Height-1
	// contains filtered or unexported fields
}

RoundState defines the internal consensus state. NOTE: Not thread safe. Should only be manipulated by functions downstream of the cs.receiveRoutine

func (*RoundState) String

func (rs *RoundState) String() string

String returns a string

func (*RoundState) StringIndented

func (rs *RoundState) StringIndented(indent string) string

StringIndented returns a string

func (*RoundState) StringShort

func (rs *RoundState) StringShort() string

StringShort returns a string

type RoundStepType

type RoundStepType uint8 // These must be numeric, ordered.

RoundStepType enumerates the state of the consensus state machine

func (RoundStepType) IsValid

func (rs RoundStepType) IsValid() bool

IsValid returns true if the step is valid, false if unknown/undefined.

func (RoundStepType) String

func (rs RoundStepType) String() string

String returns a string

type RoundVoteSet

type RoundVoteSet struct {
	Prevotes   *VoteSet
	Precommits *VoteSet
}

type StateSync

type StateSync struct {
	// contains filtered or unexported fields
}

func NewStateSync

func NewStateSync(c *Core) *StateSync

func (*StateSync) AddVote

func (s *StateSync) AddVote(v *message.Vote)

type TimeoutTicker

type TimeoutTicker interface {
	Start() error
	Stop() error
	Chan() <-chan timeoutInfo       // on which to receive a timeout
	ScheduleTimeout(ti timeoutInfo) // reset the timer
}

TimeoutTicker is a timer that schedules timeouts conditional on the height/round/step in the timeoutInfo. The timeoutInfo.Duration may be non-positive.

func NewTimeoutTicker

func NewTimeoutTicker(c *Core) TimeoutTicker

NewTimeoutTicker returns a new TimeoutTicker.

type Validators

type Validators struct {
	sync.RWMutex

	CustomValidators custom.ICommittee
	// contains filtered or unexported fields
}

func NewValidators

func NewValidators(val custom.ICommittee, pVal custom.IPrivValidator) *Validators

func (*Validators) GetSelfPubKey

func (v *Validators) GetSelfPubKey() message.PubKey

func (*Validators) GetTotalVotingPower

func (v *Validators) GetTotalVotingPower() int64

func (*Validators) GetValidatorNum

func (v *Validators) GetValidatorNum() int

func (*Validators) GetVotingPower

func (v *Validators) GetVotingPower(address *message.PubKey) int64

func (*Validators) Sign

func (v *Validators) Sign(msg message.ConsensusMessage)

func (*Validators) VerifySignature

func (v *Validators) VerifySignature(msg message.ConsensusMessage) bool

type VoteSet

type VoteSet struct {
	// contains filtered or unexported fields
}

VoteSet helps collect signatures from validators at each height+round for a predefined vote type.

We need VoteSet to be able to keep track of conflicting votes when validators double-sign. Yet, we can't keep track of *all* the votes seen, as that could be a DoS attack vector.

NOTE: Assumes that the sum total of voting power does not exceed MaxUInt64.

func NewVoteSet

func NewVoteSet(height int64, round int, type_ message.VoteType, valSet *Validators, b *message.ProposedData) *VoteSet

Constructs a new VoteSet struct used to accumulate votes for given height/round.

func (*VoteSet) AddVote

func (voteSet *VoteSet) AddVote(vote *message.Vote) (added bool, err error)

Returns added=true if vote is valid and new. Otherwise returns err=ErrVote[

UnexpectedStep | InvalidIndex | InvalidAddress |
InvalidSignature | InvalidBlockHash | ConflictingVotes ]

Duplicate votes return added=false, err=nil. Conflicting votes return added=*, err=ErrVoteConflictingVotes. NOTE: vote should not be mutated after adding. NOTE: VoteSet must not be nil NOTE: Vote must not be nil

func (*VoteSet) HasAll

func (voteSet *VoteSet) HasAll() bool

func (*VoteSet) HasTwoThirdsAny

func (voteSet *VoteSet) HasTwoThirdsAny() bool

func (*VoteSet) HasTwoThirdsMajority

func (voteSet *VoteSet) HasTwoThirdsMajority() bool

func (*VoteSet) Height

func (voteSet *VoteSet) Height() int64

func (*VoteSet) IsCommit

func (voteSet *VoteSet) IsCommit() bool

func (*VoteSet) MakeCommit

func (voteSet *VoteSet) MakeCommit() *message.Commit

func (*VoteSet) MakeFetchVotesReq

func (voteSet *VoteSet) MakeFetchVotesReq() *message.FetchVotesReq

func (*VoteSet) MakeFetchVotesRsp

func (voteSet *VoteSet) MakeFetchVotesRsp(req *message.FetchVotesReq) *message.FetchVotesRsp

func (*VoteSet) MinorQuorum

func (voteSet *VoteSet) MinorQuorum() (message.ProposedData, bool)

func (*VoteSet) Round

func (voteSet *VoteSet) Round() int

func (*VoteSet) String

func (voteSet *VoteSet) String() string

func (*VoteSet) StringIndented

func (voteSet *VoteSet) StringIndented(indent string) string

func (*VoteSet) TwoThirdsMajority

func (voteSet *VoteSet) TwoThirdsMajority() (proposed message.ProposedData, ok bool)

If there was a +2/3 majority for blockID, return blockID and true. Else, return the empty BlockID{} and false.

func (*VoteSet) Type

func (voteSet *VoteSet) Type() byte

Directories

Path Synopsis
mock
Package mock is a generated GoMock package.
Package mock is a generated GoMock package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL