Documentation ¶
Index ¶
- Variables
- func EnsureVoteForBlock(vote *model.Vote, block *model.Block) error
- func NewBootstrapCombinedVoteProcessor(log zerolog.Logger, committee hotstuff.DynamicCommittee, block *model.Block, ...) (hotstuff.VerifyingVoteProcessor, error)
- func NewBootstrapStakingVoteProcessor(log zerolog.Logger, committee hotstuff.DynamicCommittee, block *model.Block, ...) (hotstuff.VerifyingVoteProcessor, error)
- func NewStateMachineFactory(log zerolog.Logger, notifier hotstuff.Consumer, ...) voteaggregator.NewCollectorFactoryMethod
- type CombinedVoteProcessorV2
- type CombinedVoteProcessorV3
- type NoopProcessor
- type StakingVoteProcessor
- type VerifyingVoteProcessorFactory
- type VoteCollector
- func (m *VoteCollector) AddVote(vote *model.Vote) error
- func (m *VoteCollector) ProcessBlock(proposal *model.Proposal) error
- func (m *VoteCollector) RegisterVoteConsumer(consumer hotstuff.VoteConsumer)
- func (m *VoteCollector) Status() hotstuff.VoteCollectorStatus
- func (m *VoteCollector) View() uint64
- type VoteProcessorFactory
- type VoteProcessorTestSuiteBase
- type VotesCache
- func (vc *VotesCache) AddVote(vote *model.Vote) error
- func (vc *VotesCache) All() []*model.Vote
- func (vc *VotesCache) GetVote(signerID flow.Identifier) (*model.Vote, bool)
- func (vc *VotesCache) RegisterVoteConsumer(consumer hotstuff.VoteConsumer)
- func (vc *VotesCache) Size() int
- func (vc *VotesCache) View() uint64
Constants ¶
This section is empty.
Variables ¶
var ( VoteForIncompatibleViewError = errors.New("vote for incompatible view") VoteForIncompatibleBlockError = errors.New("vote for incompatible block") )
var (
ErrDifferentCollectorState = errors.New("different state")
)
var ( // RepeatedVoteErr is emitted, when we receive a vote for the same block // from the same voter multiple times. This error does _not_ indicate // equivocation. RepeatedVoteErr = errors.New("duplicated vote") )
Functions ¶
func EnsureVoteForBlock ¶
EnsureVoteForBlock verifies that the vote is for the given block. Returns nil on success and sentinel errors:
- model.VoteForIncompatibleViewError if the vote is from a different view than block
- model.VoteForIncompatibleBlockError if the vote is from the same view as block but for a different blockID
func NewBootstrapCombinedVoteProcessor ¶
func NewBootstrapCombinedVoteProcessor(log zerolog.Logger, committee hotstuff.DynamicCommittee, block *model.Block, onQCCreated hotstuff.OnQCCreated) (hotstuff.VerifyingVoteProcessor, error)
NewBootstrapCombinedVoteProcessor directly creates a CombinedVoteProcessorV2, suitable for the collector's local cluster consensus. Intended use: only for bootstrapping. UNSAFE: the proposer vote for `block` is _not_ validated or included
func NewBootstrapStakingVoteProcessor ¶
func NewBootstrapStakingVoteProcessor(log zerolog.Logger, committee hotstuff.DynamicCommittee, block *model.Block, onQCCreated hotstuff.OnQCCreated) (hotstuff.VerifyingVoteProcessor, error)
NewBootstrapStakingVoteProcessor directly creates a `StakingVoteProcessor`, suitable for the collector's local cluster consensus. Intended use: only for bootstrapping. UNSAFE: the proposer vote for `block` is _not_ validated or included
func NewStateMachineFactory ¶
func NewStateMachineFactory( log zerolog.Logger, notifier hotstuff.Consumer, verifyingVoteProcessorFactory VerifyingVoteProcessorFactory, ) voteaggregator.NewCollectorFactoryMethod
Types ¶
type CombinedVoteProcessorV2 ¶
type CombinedVoteProcessorV2 struct {
// contains filtered or unexported fields
}
CombinedVoteProcessorV2 implements the hotstuff.VerifyingVoteProcessor interface. It processes votes from the main consensus committee, where participants must _always_ provide the staking signature as part of their vote and can _optionally_ also provide a random beacon signature. Through their staking signature, a participant always contributes to HotStuff's progress. Participation in the random beacon is optional (but encouraged). This allows nodes that failed the DKG to still contribute only to consensus (as fallback). CombinedVoteProcessorV2 is Concurrency safe.
func NewCombinedVoteProcessor ¶
func NewCombinedVoteProcessor(log zerolog.Logger, block *model.Block, stakingSigAggtor hotstuff.WeightedSignatureAggregator, rbRector hotstuff.RandomBeaconReconstructor, onQCCreated hotstuff.OnQCCreated, packer hotstuff.Packer, minRequiredWeight uint64, ) *CombinedVoteProcessorV2
func (*CombinedVoteProcessorV2) Block ¶
func (p *CombinedVoteProcessorV2) Block() *model.Block
Block returns block that is part of proposal that we are processing votes for.
func (*CombinedVoteProcessorV2) Process ¶
func (p *CombinedVoteProcessorV2) Process(vote *model.Vote) error
Process performs processing of single vote in concurrent safe way. This function is implemented to be called by multiple goroutines at the same time. Supports processing of both staking and random beacon signatures. Design of this function is event driven: as soon as we collect enough signatures to create a QC we will immediately do so and submit it via callback for further processing. Expected error returns during normal operations: * VoteForIncompatibleBlockError - submitted vote for incompatible block * VoteForIncompatibleViewError - submitted vote for incompatible view * model.InvalidVoteError - submitted vote with invalid signature * model.DuplicatedSignerError if the signer has been already added All other errors should be treated as exceptions.
Impossibility of vote double-counting: Our signature scheme requires _every_ vote to supply a staking signature. Therefore, the `stakingSigAggtor` has the set of _all_ signerIDs that have provided a valid vote. Hence, the `stakingSigAggtor` guarantees that only a single vote can be successfully added for each `signerID`, i.e. double-counting votes is impossible.
func (*CombinedVoteProcessorV2) Status ¶
func (p *CombinedVoteProcessorV2) Status() hotstuff.VoteCollectorStatus
Status returns status of this vote processor, it's always verifying.
type CombinedVoteProcessorV3 ¶
type CombinedVoteProcessorV3 struct {
// contains filtered or unexported fields
}
CombinedVoteProcessorV3 implements the hotstuff.VerifyingVoteProcessor interface. It processes votes from the main consensus committee, where participants vote in favour of a block by proving either their staking key signature or their random beacon signature. In the former case, the participant only contributes to HotStuff progress; while in the latter case, the voter also contributes to running the random beacon. Concurrency safe.
func (*CombinedVoteProcessorV3) Block ¶
func (p *CombinedVoteProcessorV3) Block() *model.Block
Block returns block that is part of proposal that we are processing votes for.
func (*CombinedVoteProcessorV3) Process ¶
func (p *CombinedVoteProcessorV3) Process(vote *model.Vote) error
Process performs processing of single vote in concurrent safe way. This function is implemented to be called by multiple goroutines at the same time. Supports processing of both staking and random beacon signatures. Design of this function is event driven: as soon as we collect enough signatures to create a QC we will immediately do so and submit it via callback for further processing. Expected error returns during normal operations: * VoteForIncompatibleBlockError - submitted vote for incompatible block * VoteForIncompatibleViewError - submitted vote for incompatible view * model.InvalidVoteError - submitted vote with invalid signature * model.DuplicatedSignerError - vote from a signer whose vote was previously already processed All other errors should be treated as exceptions.
CAUTION: implementation is NOT (yet) BFT Explanation: for correctness, we require that no voter can be counted repeatedly. However, CombinedVoteProcessorV3 relies on the `VoteCollector`'s `votesCache` filter out all votes but the first for every signerID. However, we have the edge case, where we still feed the proposers vote twice into the `VerifyingVoteProcessor` (once as part of a cached vote, once as an individual vote). This can be exploited by a byzantine proposer to be erroneously counted twice, which would lead to a safety fault.
TODO (suggestion): I think it would be worth-while to include a second `votesCache` into the `CombinedVoteProcessorV3`. Thereby, `CombinedVoteProcessorV3` inherently guarantees correctness of the QCs it produces without relying on external conditions (making the code more modular, less interdependent and thereby easier to maintain). The runtime overhead is marginal: For `votesCache` to add 500 votes (concurrently with 20 threads) takes about 0.25ms. This runtime overhead is neglectable and a good tradeoff for the gain in maintainability and code clarity.
func (*CombinedVoteProcessorV3) Status ¶
func (p *CombinedVoteProcessorV3) Status() hotstuff.VoteCollectorStatus
Status returns status of this vote processor, it's always verifying.
type NoopProcessor ¶
type NoopProcessor struct {
// contains filtered or unexported fields
}
NoopProcessor implements hotstuff.VoteProcessor. It drops all votes.
func NewNoopCollector ¶
func NewNoopCollector(status hotstuff.VoteCollectorStatus) *NoopProcessor
func (*NoopProcessor) Status ¶
func (c *NoopProcessor) Status() hotstuff.VoteCollectorStatus
type StakingVoteProcessor ¶
type StakingVoteProcessor struct {
// contains filtered or unexported fields
}
StakingVoteProcessor implements the hotstuff.VerifyingVoteProcessor interface. It processes hotstuff votes from a collector cluster, where participants vote in favour of a block by proving their staking key signature. Concurrency safe.
func (*StakingVoteProcessor) Block ¶
func (p *StakingVoteProcessor) Block() *model.Block
Block returns block that is part of proposal that we are processing votes for.
func (*StakingVoteProcessor) Process ¶
func (p *StakingVoteProcessor) Process(vote *model.Vote) error
Process performs processing of single vote in concurrent safe way. This function is implemented to be called by multiple goroutines at the same time. Supports processing of both staking and threshold signatures. Design of this function is event driven, as soon as we collect enough weight to create a QC we will immediately do this and submit it via callback for further processing. Expected error returns during normal operations: * VoteForIncompatibleBlockError - submitted vote for incompatible block * VoteForIncompatibleViewError - submitted vote for incompatible view * model.InvalidVoteError - submitted vote with invalid signature All other errors should be treated as exceptions.
func (*StakingVoteProcessor) Status ¶
func (p *StakingVoteProcessor) Status() hotstuff.VoteCollectorStatus
Status returns status of this vote processor, it's always verifying.
type VerifyingVoteProcessorFactory ¶
type VerifyingVoteProcessorFactory = func(log zerolog.Logger, proposal *model.Proposal) (hotstuff.VerifyingVoteProcessor, error)
VerifyingVoteProcessorFactory generates hotstuff.VerifyingVoteCollector instances
type VoteCollector ¶
VoteCollector implements a state machine for transition between different states of vote collector
func NewStateMachine ¶
func NewStateMachine( view uint64, log zerolog.Logger, workers hotstuff.Workers, notifier hotstuff.Consumer, verifyingVoteProcessorFactory VerifyingVoteProcessorFactory, ) *VoteCollector
func (*VoteCollector) AddVote ¶
func (m *VoteCollector) AddVote(vote *model.Vote) error
AddVote adds a vote to current vote collector All expected errors are handled via callbacks to notifier. Under normal execution only exceptions are propagated to caller.
func (*VoteCollector) ProcessBlock ¶
func (m *VoteCollector) ProcessBlock(proposal *model.Proposal) error
ProcessBlock performs validation of block signature and processes block with respected collector. In case we have received double proposal, we will stop attempting to build a QC for this view, because we don't want to build on any proposal from an equivocating primary. Note: slashing challenges for proposal equivocation are triggered by hotstuff.Forks, so we don't have to do anything else here.
The internal state change is implemented as an atomic compare-and-swap, i.e. the state transition is only executed if VoteCollector's internal state is equal to `expectedValue`. The implementation only allows the transitions
CachingVotes -> VerifyingVotes CachingVotes -> Invalid VerifyingVotes -> Invalid
func (*VoteCollector) RegisterVoteConsumer ¶
func (m *VoteCollector) RegisterVoteConsumer(consumer hotstuff.VoteConsumer)
RegisterVoteConsumer registers a VoteConsumer. Upon registration, the collector feeds all cached votes into the consumer in the order they arrived. CAUTION, VoteConsumer implementations must be
- NON-BLOCKING and consume the votes without noteworthy delay, and
- CONCURRENCY SAFE
func (*VoteCollector) Status ¶
func (m *VoteCollector) Status() hotstuff.VoteCollectorStatus
Status returns the status of underlying vote processor
func (*VoteCollector) View ¶
func (m *VoteCollector) View() uint64
View returns view associated with this collector
type VoteProcessorFactory ¶
type VoteProcessorFactory struct {
// contains filtered or unexported fields
}
VoteProcessorFactory implements `hotstuff.VoteProcessorFactory`. Its main purpose is to construct instances of VerifyingVoteProcessors for a given block proposal. VoteProcessorFactory * delegates the creation of the actual instances to baseFactory * adds the logic to verify the proposer's vote for its own block Thereby, VoteProcessorFactory guarantees that only proposals with valid proposer vote are accepted (as per API specification). Otherwise, an `model.InvalidBlockError` is returned.
func NewCombinedVoteProcessorFactory ¶
func NewCombinedVoteProcessorFactory(committee hotstuff.DynamicCommittee, onQCCreated hotstuff.OnQCCreated) *VoteProcessorFactory
NewCombinedVoteProcessorFactory implements hotstuff.VoteProcessorFactory fo participants of the Main Consensus committee.
With their vote, members of the main consensus committee can contribute to hotstuff and the random beacon. When a consensus participant signs with its random beacon key, it contributes to HotStuff consensus _and_ the Random Beacon. As a fallback, a consensus participant can sign with its staking key; thereby it contributes only to consensus but not the random beacon. There should be an economic incentive for the nodes to preferably sign with their random beacon key.
func NewStakingVoteProcessorFactory ¶
func NewStakingVoteProcessorFactory(committee hotstuff.DynamicCommittee, onQCCreated hotstuff.OnQCCreated) *VoteProcessorFactory
NewStakingVoteProcessorFactory implements hotstuff.VoteProcessorFactory for members of a collector cluster. For their cluster-local hotstuff, collectors only sign with their staking key.
func (*VoteProcessorFactory) Create ¶
func (f *VoteProcessorFactory) Create(log zerolog.Logger, proposal *model.Proposal) (hotstuff.VerifyingVoteProcessor, error)
Create instantiates a VerifyingVoteProcessor for the given block proposal. A VerifyingVoteProcessor are only created for proposals with valid proposer votes. Expected error returns during normal operations: * model.InvalidBlockError - proposal has invalid proposer vote
type VoteProcessorTestSuiteBase ¶
VoteProcessorTestSuiteBase is a helper structure which implements common logic between staking and combined vote processor test suites.
func (*VoteProcessorTestSuiteBase) SetupTest ¶
func (s *VoteProcessorTestSuiteBase) SetupTest()
type VotesCache ¶
type VotesCache struct {
// contains filtered or unexported fields
}
VotesCache maintains a _concurrency safe_ cache of votes for one particular view. The cache memorizes the order in which the votes were received. Votes are de-duplicated based on the following rules:
- Vor each voter (i.e. SignerID), we store the _first_ vote v0.
- For any subsequent vote v, we check whether v.BlockID == v0.BlockID. If this is the case, we consider the vote a duplicate and drop it. If v and v0 have different BlockIDs, the voter is equivocating and we return a model.DoubleVoteError
func NewVotesCache ¶
func NewVotesCache(view uint64) *VotesCache
NewVotesCache instantiates a VotesCache for the given view
func (*VotesCache) AddVote ¶
func (vc *VotesCache) AddVote(vote *model.Vote) error
AddVote stores a vote in the cache. The following errors are expected during normal operations:
- nil: if the vote was successfully added
- model.DoubleVoteError is returned if the voter is equivocating (i.e. voting in the same view for different blocks).
- RepeatedVoteErr is returned when adding a vote for the same block from the same voter multiple times.
- IncompatibleViewErr is returned if the vote is for a different view.
When AddVote returns an error, the vote is _not_ stored.
func (*VotesCache) All ¶
func (vc *VotesCache) All() []*model.Vote
All returns all currently cached votes. Concurrency safe.
func (*VotesCache) GetVote ¶
func (vc *VotesCache) GetVote(signerID flow.Identifier) (*model.Vote, bool)
GetVote returns the stored vote for the given `signerID`. Returns:
- (vote, true) if a vote from signerID is known
- (false, nil) no vote from signerID is known
func (*VotesCache) RegisterVoteConsumer ¶
func (vc *VotesCache) RegisterVoteConsumer(consumer hotstuff.VoteConsumer)
RegisterVoteConsumer registers a VoteConsumer. Upon registration, the cache feeds all cached votes into the consumer in the order they arrived. CAUTION: a consumer _must_ be non-blocking and consume the votes without noteworthy delay. Otherwise, consensus speed is impacted.
Expected usage patter: During happy-path operations, the block arrives in a timely manner. Hence, we expect that only a few votes are cached when a consumer is registered. For the purpose of forensics, we might register a consumer later, when already lots of votes are cached. However, this should be a rare occurrence (we except moderate performance overhead in this case).
func (*VotesCache) View ¶
func (vc *VotesCache) View() uint64