wip: conversion of hotstuff from flow into Q-oriented model

This commit is contained in:
Cassandra Heart 2025-10-29 02:27:37 -05:00
parent 4df761de20
commit d71b0538f2
No known key found for this signature in database
GPG Key ID: 371083BFA6C240AA
153 changed files with 19268 additions and 2871 deletions

18
consensus/.mockery.yaml Normal file
View File

@ -0,0 +1,18 @@
dir: "{{.InterfaceDir}}/mock"
outpkg: "mock"
filename: "{{.InterfaceName | snakecase}}.go"
mockname: "{{.InterfaceName}}"
all: True
with-expecter: False
include-auto-generated: False
disable-func-mocks: True
fail-on-missing: True
disable-version-string: True
resolve-type-alias: False
packages:
source.quilibrium.com/quilibrium/monorepo/consensus:
config:
dir: "mocks"
outpkg: "mocks"

View File

@ -1,300 +1,4 @@
# Consensus State Machine
A generic, extensible state machine implementation for building Byzantine Fault
Tolerant (BFT) consensus protocols. This library provides a framework for
implementing round-based consensus algorithms with cryptographic proofs.
## Overview
The state machine manages consensus engine state transitions through a
well-defined set of states and events. It supports generic type parameters to
allow different implementations of state data, votes, peer identities, and
collected mutations.
## Features
- **Generic Implementation**: Supports custom types for state data, votes, peer
IDs, and collected data
- **Byzantine Fault Tolerance**: Provides BFT consensus with < 1/3 byzantine
nodes, flexible to other probabilistic BFT implementations
- **Round-based Consensus**: Implements a round-based state transition pattern
- **Pluggable Providers**: Extensible through provider interfaces for different
consensus behaviors
- **Event-driven Architecture**: State transitions triggered by events with
optional guard conditions
- **Concurrent Safe**: Thread-safe implementation with proper mutex usage
- **Timeout Support**: Configurable timeouts for each state with automatic
transitions
- **Transition Listeners**: Observable state transitions for monitoring and
debugging
## Core Concepts
### States
The state machine progresses through the following states:
1. **StateStopped**: Initial state, engine is not running
2. **StateStarting**: Engine is initializing
3. **StateLoading**: Loading data and syncing with network
4. **StateCollecting**: Collecting data/mutations for consensus round
5. **StateLivenessCheck**: Checking peer liveness before proving
6. **StateProving**: Generating cryptographic proof (leader only)
7. **StatePublishing**: Publishing proposed state
8. **StateVoting**: Voting on proposals
9. **StateFinalizing**: Finalizing consensus round
10. **StateVerifying**: Verifying and publishing results
11. **StateStopping**: Engine is shutting down
### Events
Events trigger state transitions:
- `EventStart`, `EventStop`: Lifecycle events
- `EventSyncComplete`: Synchronization finished
- `EventCollectionDone`: Mutation collection complete
- `EventLivenessCheckReceived`: Peer liveness confirmed
- `EventProverSignal`: Leader selection complete
- `EventProofComplete`: Proof generation finished
- `EventProposalReceived`: New proposal received
- `EventVoteReceived`: Vote received
- `EventQuorumReached`: Voting quorum achieved
- `EventConfirmationReceived`: State confirmation received
- And more...
### Type Constraints
All generic type parameters must implement the `Unique` interface:
```go
type Unique interface {
Identity() Identity // Returns a unique string identifier
}
```
## Provider Interfaces
### SyncProvider
Handles initial state synchronization:
```go
type SyncProvider[StateT Unique] interface {
Synchronize(
existing *StateT,
ctx context.Context,
) (<-chan *StateT, <-chan error)
}
```
### VotingProvider
Manages the voting process:
```go
type VotingProvider[StateT Unique, VoteT Unique, PeerIDT Unique] interface {
SendProposal(proposal *StateT, ctx context.Context) error
DecideAndSendVote(
proposals map[Identity]*StateT,
ctx context.Context,
) (PeerIDT, *VoteT, error)
IsQuorum(votes map[Identity]*VoteT, ctx context.Context) (bool, error)
FinalizeVotes(
proposals map[Identity]*StateT,
votes map[Identity]*VoteT,
ctx context.Context,
) (*StateT, PeerIDT, error)
SendConfirmation(finalized *StateT, ctx context.Context) error
}
```
### LeaderProvider
Handles leader selection and proof generation:
```go
type LeaderProvider[
StateT Unique,
PeerIDT Unique,
CollectedT Unique,
] interface {
GetNextLeaders(prior *StateT, ctx context.Context) ([]PeerIDT, error)
ProveNextState(
prior *StateT,
collected CollectedT,
ctx context.Context,
) (*StateT, error)
}
```
### LivenessProvider
Manages peer liveness checks:
```go
type LivenessProvider[
StateT Unique,
PeerIDT Unique,
CollectedT Unique,
] interface {
Collect(ctx context.Context) (CollectedT, error)
SendLiveness(prior *StateT, collected CollectedT, ctx context.Context) error
}
```
## Usage
### Basic Setup
```go
// Define your types implementing Unique
type MyState struct {
Round uint64
Hash string
}
func (s MyState) Identity() string { return s.Hash }
type MyVote struct {
Voter string
Value bool
}
func (v MyVote) Identity() string { return v.Voter }
type MyPeerID struct {
ID string
}
func (p MyPeerID) Identity() string { return p.ID }
type MyCollected struct {
Data []byte
}
func (c MyCollected) Identity() string { return string(c.Data) }
// Implement providers
syncProvider := &MySyncProvider{}
votingProvider := &MyVotingProvider{}
leaderProvider := &MyLeaderProvider{}
livenessProvider := &MyLivenessProvider{}
// Create state machine
sm := consensus.NewStateMachine[MyState, MyVote, MyPeerID, MyCollected](
MyPeerID{ID: "node1"}, // This node's ID
&MyState{Round: 0, Hash: "genesis"}, // Initial state
true, // shouldEmitReceiveEventsOnSends
3, // minimumProvers
syncProvider,
votingProvider,
leaderProvider,
livenessProvider,
nil, // Optional trace logger
)
// Add transition listener
sm.AddListener(&MyTransitionListener{})
// Start the state machine
if err := sm.Start(); err != nil {
log.Fatal(err)
}
// Receive external events
sm.ReceiveProposal(peer, proposal)
sm.ReceiveVote(voter, vote)
sm.ReceiveLivenessCheck(peer, collected)
sm.ReceiveConfirmation(peer, confirmation)
// Stop the state machine
if err := sm.Stop(); err != nil {
log.Fatal(err)
}
```
### Implementing Providers
See the `example/generic_consensus_example.go` for a complete working example
with mock provider implementations.
## State Flow
The typical consensus flow:
1. **Start****Starting** → **Loading**
2. **Loading**: Synchronize with network
3. **Collecting**: Gather mutations/changes
4. **LivenessCheck**: Verify peer availability
5. **Proving**: Leader generates proof
6. **Publishing**: Leader publishes proposal
7. **Voting**: All nodes vote on proposals
8. **Finalizing**: Aggregate votes and determine outcome
9. **Verifying**: Confirm and apply state changes
10. Loop back to **Collecting** for next round
## Configuration
### Constructor Parameters
- `id`: This node's peer ID
- `initialState`: Starting state (can be nil)
- `shouldEmitReceiveEventsOnSends`: Whether to emit receive events for own
messages
- `minimumProvers`: Minimum number of active provers required
- `traceLogger`: Optional logger for debugging state transitions
### State Timeouts
Each state can have a configured timeout that triggers an automatic transition:
- **Starting**: 1 second → `EventInitComplete`
- **Loading**: 10 minutes → `EventSyncComplete`
- **Collecting**: 1 second → `EventCollectionDone`
- **LivenessCheck**: 1 second → `EventLivenessTimeout`
- **Proving**: 120 seconds → `EventPublishTimeout`
- **Publishing**: 1 second → `EventPublishTimeout`
- **Voting**: 10 seconds → `EventVotingTimeout`
- **Finalizing**: 1 second → `EventAggregationDone`
- **Verifying**: 1 second → `EventVerificationDone`
- **Stopping**: 30 seconds → `EventCleanupComplete`
## Thread Safety
The state machine is thread-safe. All public methods properly handle concurrent
access through mutex locks. State behaviors run in separate goroutines with
proper cancellation support.
## Error Handling
- Provider errors are logged but don't crash the state machine
- The state machine continues operating and may retry operations
- Critical errors during state transitions are returned to callers
- Use the `TraceLogger` interface for debugging
## Best Practices
1. **Message Isolation**: When implementing providers, always deep-copy data
before sending to prevent shared state between state machine and other
handlers
2. **Nil Handling**: Provider implementations should handle nil prior states
gracefully
3. **Context Usage**: Respect context cancellation in long-running operations
4. **Quorum Size**: Set appropriate quorum size based on your network (typically
2f+1 for f failures)
5. **Timeout Configuration**: Adjust timeouts based on network conditions and
proof generation time
## Example
See `example/generic_consensus_example.go` for a complete working example
demonstrating:
- Mock provider implementations
- Multi-node consensus network
- Byzantine node behavior
- Message passing between nodes
- State transition monitoring
## Testing
The package includes comprehensive tests in `state_machine_test.go` covering:
- State transitions
- Event handling
- Concurrent operations
- Byzantine scenarios
- Timeout behavior
Consensus State Machine is being swapped out with a fork of the HotStuff implementation by Flow.
This will be updated with appropriate license details when the fork work has finished.

View File

@ -0,0 +1,77 @@
package consensus
import (
"context"
"time"
)
type BackoffTimer struct {
timeoutCh chan time.Time
cancel context.CancelFunc
fail uint64
}
func NewBackoffTimer() *BackoffTimer {
t := make(chan time.Time)
close(t)
return &BackoffTimer{
timeoutCh: t,
cancel: func() {},
fail: 0,
}
}
func (t *BackoffTimer) TimeoutCh() <-chan time.Time {
return t.timeoutCh
}
func (t *BackoffTimer) Start(
ctx context.Context,
) (start, end time.Time) {
t.cancel()
t.timeoutCh = make(chan time.Time)
ctx, cancelFn := context.WithCancel(ctx)
t.cancel = cancelFn
go rebroadcastTimeout(ctx, t.timeoutCh)
start = time.Now().UTC()
end = start.Add(time.Duration(min(t.fail, 10)+10) * time.Second)
return start, end
}
func (t *BackoffTimer) ReceiveTimeout() {
if t.fail < 10 {
t.fail++
}
}
func (t *BackoffTimer) ReceiveSuccess() {
if t.fail > 0 {
t.fail--
}
}
func rebroadcastTimeout(ctx context.Context, timeoutCh chan<- time.Time) {
timeout := time.NewTimer(20 * time.Second)
select {
case t := <-timeout.C:
timeoutCh <- t
case <-ctx.Done():
timeout.Stop()
return
}
rebroadcast := time.NewTicker(1 * time.Second)
for {
select {
case t := <-rebroadcast.C:
timeoutCh <- t
case <-ctx.Done():
rebroadcast.Stop()
return
}
}
}

View File

@ -0,0 +1,154 @@
package consensus
import "source.quilibrium.com/quilibrium/monorepo/consensus/models"
// A committee provides a subset of the protocol.State, which is restricted to
// exactly those nodes that participate in the current HotStuff instance: the
// state of all legitimate HotStuff participants for the specified rank.
// Legitimate HotStuff participants have NON-ZERO WEIGHT.
//
// For the purposes of validating votes, timeouts, quorum certificates, and
// timeout certificates we consider a committee which is static over the course
// of an epoch. Although committee members may be ejected, or have their weight
// change during an epoch, we ignore these changes. For these purposes we use
// the Replicas and *ByEpoch methods.
//
// When validating proposals, we take into account changes to the committee
// during the course of an epoch. In particular, if a node is ejected, we will
// immediately reject all future proposals from that node. For these purposes we
// use the DynamicCommittee and *ByState methods.
// Replicas defines the consensus committee for the purposes of validating
// votes, timeouts, quorum certificates, and timeout certificates. Any consensus
// committee member who was authorized to contribute to consensus AT THE
// BEGINNING of the epoch may produce valid votes and timeouts for the entire
// epoch, even if they are later ejected. So for validating votes/timeouts we
// use *ByEpoch methods.
//
// Since the voter committee is considered static over an epoch:
// - we can query identities by rank
// - we don't need the full state ancestry prior to validating messages
type Replicas interface {
// LeaderForRank returns the identity of the leader for a given rank.
// CAUTION: per liveness requirement of HotStuff, the leader must be
// fork-independent. Therefore, a node retains its proposer rank
// slots even if it is slashed. Its proposal is simply considered
// invalid, as it is not from a legitimate participant.
// Returns the following expected errors for invalid inputs:
// - model.ErrRankUnknown if no epoch containing the given rank is
// known
LeaderForRank(rank uint64) (models.Identity, error)
// QuorumThresholdForRank returns the minimum total weight for a supermajority
// at the given rank. This weight threshold is computed using the total weight
// of the initial committee and is static over the course of an epoch.
// Returns the following expected errors for invalid inputs:
// - model.ErrRankUnknown if no epoch containing the given rank is
// known
QuorumThresholdForRank(rank uint64) (uint64, error)
// TimeoutThresholdForRank returns the minimum total weight of observed
// timeout states required to safely timeout for the given rank. This weight
// threshold is computed using the total weight of the initial committee and
// is static over the course of an epoch.
// Returns the following expected errors for invalid inputs:
// - model.ErrRankUnknown if no epoch containing the given rank is
// known
TimeoutThresholdForRank(rank uint64) (uint64, error)
// Self returns our own node identifier.
// TODO: ultimately, the own identity of the node is necessary for signing.
// Ideally, we would move the method for checking whether an Identifier
// refers to this node to the signer. This would require some
// refactoring of EventHandler (postponed to later)
Self() models.Identity
// IdentitiesByRank returns a list of the legitimate HotStuff participants
// for the epoch given by the input rank.
// The returned list of HotStuff participants:
// - contains nodes that are allowed to submit votes or timeouts within the
// given epoch (un-ejected, non-zero weight at the beginning of the epoch)
// - is ordered in the canonical order
// - contains no duplicates.
//
// CAUTION: DO NOT use this method for validating state proposals.
//
// Returns the following expected errors for invalid inputs:
// - model.ErrRankUnknown if no epoch containing the given rank is
// known
//
IdentitiesByRank(rank uint64) ([]models.WeightedIdentity, error)
// IdentityByEpoch returns the full Identity for specified HotStuff
// participant. The node must be a legitimate HotStuff participant with
// NON-ZERO WEIGHT at the specified state.
//
// ERROR conditions:
// - model.InvalidSignerError if participantID does NOT correspond to an
// authorized HotStuff participant at the specified state.
//
// Returns the following expected errors for invalid inputs:
// - model.ErrRankUnknown if no epoch containing the given rank is
// known
//
IdentityByRank(
rank uint64,
participantID models.Identity,
) (models.WeightedIdentity, error)
}
// DynamicCommittee extends Replicas to provide the consensus committee for the
// purposes of validating proposals. The proposer committee reflects
// state-to-state changes in the identity table to support immediately rejecting
// proposals from nodes after they are ejected. For validating proposals, we use
// *ByState methods.
//
// Since the proposer committee can change at any state:
// - we query by state ID
// - we must have incorporated the full state ancestry prior to validating
// messages
type DynamicCommittee interface {
Replicas
// IdentitiesByState returns a list of the legitimate HotStuff participants
// for the given state. The returned list of HotStuff participants:
// - contains nodes that are allowed to submit proposals, votes, and
// timeouts (un-ejected, non-zero weight at current state)
// - is ordered in the canonical order
// - contains no duplicates.
//
// ERROR conditions:
// - state.ErrUnknownSnapshotReference if the stateID is for an unknown state
IdentitiesByState(stateID models.Identity) ([]models.WeightedIdentity, error)
// IdentityByState returns the full Identity for specified HotStuff
// participant. The node must be a legitimate HotStuff participant with
// NON-ZERO WEIGHT at the specified state.
// ERROR conditions:
// - model.InvalidSignerError if participantID does NOT correspond to an
// authorized HotStuff participant at the specified state.
// - state.ErrUnknownSnapshotReference if the stateID is for an unknown state
IdentityByState(
stateID models.Identity,
participantID models.Identity,
) (*models.WeightedIdentity, error)
}
// StateSignerDecoder defines how to convert the ParentSignerIndices field
// within a particular state header to the identifiers of the nodes which signed
// the state.
type StateSignerDecoder[StateT models.Unique] interface {
// DecodeSignerIDs decodes the signer indices from the given state header into
// full node IDs.
// Note: A state header contains a quorum certificate for its parent, which
// proves that the consensus committee has reached agreement on validity of
// parent state. Consequently, the returned IdentifierList contains the
// consensus participants that signed the parent state.
// Expected Error returns during normal operations:
// - signature.InvalidSignerIndicesError if signer indices included in the
// header do not encode a valid subset of the consensus committee
DecodeSignerIDs(
state *models.State[StateT],
) ([]models.WeightedIdentity, error)
}

View File

@ -0,0 +1,453 @@
package consensus
import (
"time"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// ProposalViolationConsumer consumes outbound notifications about
// HotStuff-protocol violations. Such notifications are produced by the active
// consensus participants and consensus follower.
//
// Implementations must:
// - be concurrency safe
// - be non-blocking
// - handle repetition of the same events (with some processing overhead).
type ProposalViolationConsumer[
StateT models.Unique,
VoteT models.Unique,
] interface {
// OnInvalidStateDetected notifications are produced by components that have
// detected that a state proposal is invalid and need to report it. Most of
// the time such state can be detected by calling Validator.ValidateProposal.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking;
// and must handle repetition of the same events (with some processing
// overhead).
OnInvalidStateDetected(err *models.InvalidProposalError[StateT, VoteT])
// OnDoubleProposeDetected notifications are produced by the Finalization
// Logic whenever a double state proposal (equivocation) was detected.
// Equivocation occurs when the same leader proposes two different states for
// the same rank.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking;
// and must handle repetition of the same events (with some processing
// overhead).
OnDoubleProposeDetected(*models.State[StateT], *models.State[StateT])
}
// VoteAggregationViolationConsumer consumes outbound notifications about
// HotStuff-protocol violations specifically invalid votes during processing.
// Such notifications are produced by the Vote Aggregation logic.
//
// Implementations must:
// - be concurrency safe
// - be non-blocking
// - handle repetition of the same events (with some processing overhead).
type VoteAggregationViolationConsumer[
StateT models.Unique,
VoteT models.Unique,
] interface {
// OnDoubleVotingDetected notifications are produced by the Vote Aggregation
// logic whenever a double voting (same voter voting for different states at
// the same rank) was detected.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnDoubleVotingDetected(*VoteT, *VoteT)
// OnInvalidVoteDetected notifications are produced by the Vote Aggregation
// logic whenever an invalid vote was detected.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnInvalidVoteDetected(err models.InvalidVoteError[VoteT])
// OnVoteForInvalidStateDetected notifications are produced by the Vote
// Aggregation logic whenever vote for invalid proposal was detected.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnVoteForInvalidStateDetected(
vote *VoteT,
invalidProposal *models.SignedProposal[StateT, VoteT],
)
}
// TimeoutAggregationViolationConsumer consumes outbound notifications about
// Active Pacemaker violations specifically invalid timeouts during processing.
// Such notifications are produced by the Timeout Aggregation logic.
//
// Implementations must:
// - be concurrency safe
// - be non-blocking
// - handle repetition of the same events (with some processing overhead).
type TimeoutAggregationViolationConsumer[VoteT models.Unique] interface {
// OnDoubleTimeoutDetected notifications are produced by the Timeout
// Aggregation logic whenever a double timeout (same replica producing two
// different timeouts at the same rank) was detected.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnDoubleTimeoutDetected(
*models.TimeoutState[VoteT],
*models.TimeoutState[VoteT],
)
// OnInvalidTimeoutDetected notifications are produced by the Timeout
// Aggregation logic whenever an invalid timeout was detected.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnInvalidTimeoutDetected(err models.InvalidTimeoutError[VoteT])
}
// FinalizationConsumer consumes outbound notifications produced by the logic
// tracking forks and finalization. Such notifications are produced by the
// active consensus participants, and generally potentially relevant to the
// larger node. The notifications are emitted in the order in which the
// finalization algorithm makes the respective steps.
//
// Implementations must:
// - be concurrency safe
// - be non-blocking
// - handle repetition of the same events (with some processing overhead).
type FinalizationConsumer[StateT models.Unique] interface {
// OnStateIncorporated notifications are produced by the Finalization Logic
// whenever a state is incorporated into the consensus state.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnStateIncorporated(*models.State[StateT])
// OnFinalizedState notifications are produced by the Finalization Logic
// whenever a state has been finalized. They are emitted in the order the
// states are finalized.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnFinalizedState(*models.State[StateT])
}
// ParticipantConsumer consumes outbound notifications produced by consensus
// participants actively proposing states, voting, collecting & aggregating
// votes to QCs, and participating in the pacemaker (sending timeouts,
// collecting & aggregating timeouts to TCs).
// Implementations must:
// - be concurrency safe
// - be non-blocking
// - handle repetition of the same events (with some processing overhead).
type ParticipantConsumer[
StateT models.Unique,
VoteT models.Unique,
] interface {
// OnEventProcessed notifications are produced by the EventHandler when it is
// done processing and hands control back to the EventLoop to wait for the
// next event.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnEventProcessed()
// OnStart notifications are produced by the EventHandler when it starts
// blocks recovery and prepares for handling incoming events from EventLoop.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnStart(currentRank uint64)
// OnReceiveProposal notifications are produced by the EventHandler when it
// starts processing a state.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnReceiveProposal(
currentRank uint64,
proposal *models.SignedProposal[StateT, VoteT],
)
// OnReceiveQuorumCertificate notifications are produced by the EventHandler
// when it starts processing a QuorumCertificate [QC] constructed by the
// node's internal vote aggregator.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnReceiveQuorumCertificate(currentRank uint64, qc models.QuorumCertificate)
// OnReceiveTimeoutCertificate notifications are produced by the EventHandler
// when it starts processing a TimeoutCertificate [TC] constructed by the
// node's internal timeout aggregator.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnReceiveTimeoutCertificate(currentRank uint64, tc models.TimeoutCertificate)
// OnPartialTimeoutCertificate notifications are produced by the EventHandler
// when it starts processing partial TC constructed by local timeout
// aggregator.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnPartialTimeoutCertificate(
currentRank uint64,
partialTimeoutCertificate *PartialTimeoutCertificateCreated,
)
// OnLocalTimeout notifications are produced by the EventHandler when it
// reacts to expiry of round duration timer. Such a notification indicates
// that the Pacemaker's timeout was processed by the system.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnLocalTimeout(currentRank uint64)
// OnRankChange notifications are produced by Pacemaker when it transitions to
// a new rank based on processing a QC or TC. The arguments specify the
// oldRank (first argument), and the newRank to which the Pacemaker
// transitioned (second argument).
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnRankChange(oldRank, newRank uint64)
// OnQuorumCertificateTriggeredRankChange notifications are produced by
// Pacemaker when it moves to a new rank based on processing a QC. The
// arguments specify the qc (first argument), which triggered the rank change,
// and the newRank to which the Pacemaker transitioned (second argument).
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking;
// and must handle repetition of the same events (with some processing
// overhead).
OnQuorumCertificateTriggeredRankChange(
oldRank uint64,
newRank uint64,
qc models.QuorumCertificate,
)
// OnTimeoutCertificateTriggeredRankChange notifications are produced by
// Pacemaker when it moves to a new rank based on processing a TC. The
// arguments specify the tc (first argument), which triggered the rank change,
// and the newRank to which the Pacemaker transitioned (second argument).
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnTimeoutCertificateTriggeredRankChange(
oldRank uint64,
newRank uint64,
tc models.TimeoutCertificate,
)
// OnStartingTimeout notifications are produced by Pacemaker. Such a
// notification indicates that the Pacemaker is now waiting for the system to
// (receive and) process states or votes. The specific timeout type is
// contained in the TimerInfo.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnStartingTimeout(startTime, endTime time.Time)
// OnCurrentRankDetails notifications are produced by the EventHandler during
// the course of a rank with auxiliary information. These notifications are
// generally not produced for all ranks (for example skipped ranks). These
// notifications are guaranteed to be produced for all ranks we enter after
// fully processing a message.
// Example 1:
// - We are in rank 8. We process a QC with rank 10, causing us to enter
// rank 11.
// - Then this notification will be produced for rank 11.
// Example 2:
// - We are in rank 8. We process a proposal with rank 10, which contains a
// TC for rank 9 and TC.NewestQC for rank 8.
// - The QC would allow us to enter rank 9 and the TC would allow us to
// enter rank 10, so after fully processing the message we are in rank 10.
// - Then this notification will be produced for rank 10, but not rank 9
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnCurrentRankDetails(
currentRank, finalizedRank uint64,
currentLeader models.Identity,
)
}
// VoteCollectorConsumer consumes outbound notifications produced by HotStuff's
// vote aggregation component. These events are primarily intended for the
// HotStuff-internal state machine (EventHandler), but might also be relevant to
// the larger node in which HotStuff is running.
//
// Implementations must:
// - be concurrency safe
// - be non-blocking
// - handle repetition of the same events (with some processing overhead).
type VoteCollectorConsumer[VoteT models.Unique] interface {
// OnQuorumCertificateConstructedFromVotes notifications are produced by the
// VoteAggregator component, whenever it constructs a QC from votes.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnQuorumCertificateConstructedFromVotes(models.QuorumCertificate)
// OnVoteProcessed notifications are produced by the Vote Aggregation logic,
// each time we successfully ingest a valid vote.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnVoteProcessed(vote *VoteT)
}
// TimeoutCollectorConsumer consumes outbound notifications produced by
// HotStuff's timeout aggregation component. These events are primarily intended
// for the HotStuff-internal state machine (EventHandler), but might also be
// relevant to the larger node in which HotStuff is running.
//
// Caution: the events are not strictly ordered by increasing ranks! The
// notifications are emitted by concurrent processing logic. Over larger time
// scales, the emitted events are for statistically increasing ranks. However,
// on short time scales there are _no_ monotonicity guarantees w.r.t. the
// events' ranks.
//
// Implementations must:
// - be concurrency safe
// - be non-blocking
// - handle repetition of the same events (with some processing overhead).
type TimeoutCollectorConsumer[VoteT models.Unique] interface {
// OnTimeoutCertificateConstructedFromTimeouts notifications are produced by
// the TimeoutProcessor component, whenever it constructs a TC based on
// TimeoutStates from a supermajority of consensus participants.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnTimeoutCertificateConstructedFromTimeouts(
certificate models.TimeoutCertificate,
)
// OnPartialTimeoutCertificateCreated notifications are produced by the
// TimeoutProcessor component, whenever it collected TimeoutStates from a
// superminority of consensus participants for a specific rank. Along with the
// rank, it reports the newest QC and TC (for previous rank) discovered in
// process of timeout collection. Per convention, the newest QC is never nil,
// while the TC for the previous rank might be nil.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnPartialTimeoutCertificateCreated(
rank uint64,
newestQC models.QuorumCertificate,
lastRankTC models.TimeoutCertificate,
)
// OnNewQuorumCertificateDiscovered notifications are produced by the
// TimeoutCollector component, whenever it discovers new QC included in
// timeout state.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnNewQuorumCertificateDiscovered(certificate models.QuorumCertificate)
// OnNewTimeoutCertificateDiscovered notifications are produced by the
// TimeoutCollector component, whenever it discovers new TC included in
// timeout state.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnNewTimeoutCertificateDiscovered(certificate models.TimeoutCertificate)
// OnTimeoutProcessed notifications are produced by the Timeout Aggregation
// logic, each time we successfully ingest a valid timeout.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnTimeoutProcessed(timeout *models.TimeoutState[VoteT])
}
// CommunicatorConsumer consumes outbound notifications produced by HotStuff and
// it's components. Notifications allow the HotStuff core algorithm to
// communicate with the other actors of the consensus process.
// Implementations must:
// - be concurrency safe
// - be non-blocking
// - handle repetition of the same events (with some processing overhead).
type CommunicatorConsumer[StateT models.Unique, VoteT models.Unique] interface {
// OnOwnVote notifies about intent to send a vote for the given parameters to
// the specified recipient.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnOwnVote(vote *VoteT, recipientID models.Identity)
// OnOwnTimeout notifies about intent to broadcast the given timeout
// state to all actors of the consensus process.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking; and must handle
// repetition of the same events (with some processing overhead).
OnOwnTimeout(timeout *models.TimeoutState[VoteT])
// OnOwnProposal notifies about intent to broadcast the given state proposal
// to all actors of the consensus process. delay is to hold the proposal
// before broadcasting it. Useful to control the state production rate.
// Prerequisites:
// Implementation must be concurrency safe; Non-blocking;
// and must handle repetition of the same events (with some processing
// overhead).
OnOwnProposal(
proposal *models.SignedProposal[StateT, VoteT],
targetPublicationTime time.Time,
)
}
// FollowerConsumer consumes outbound notifications produced by consensus
// followers. It is a subset of the notifications produced by consensus
// participants.
// Implementations must:
// - be concurrency safe
// - be non-blocking
// - handle repetition of the same events (with some processing overhead).
type FollowerConsumer[StateT models.Unique, VoteT models.Unique] interface {
ProposalViolationConsumer[StateT, VoteT]
FinalizationConsumer[StateT]
}
// Consumer consumes outbound notifications produced by consensus participants.
// Notifications are consensus-internal state changes which are potentially
// relevant to the larger node in which HotStuff is running. The notifications
// are emitted in the order in which the HotStuff algorithm makes the respective
// steps.
//
// Implementations must:
// - be concurrency safe
// - be non-blocking
// - handle repetition of the same events (with some processing overhead).
type Consumer[StateT models.Unique, VoteT models.Unique] interface {
FollowerConsumer[StateT, VoteT]
CommunicatorConsumer[StateT, VoteT]
ParticipantConsumer[StateT, VoteT]
}
// VoteAggregationConsumer consumes outbound notifications produced by Vote
// Aggregation logic. It is a subset of the notifications produced by consensus
// participants.
// Implementations must:
// - be concurrency safe
// - be non-blocking
// - handle repetition of the same events (with some processing overhead).
type VoteAggregationConsumer[
StateT models.Unique,
VoteT models.Unique,
] interface {
VoteAggregationViolationConsumer[StateT, VoteT]
VoteCollectorConsumer[VoteT]
}
// TimeoutAggregationConsumer consumes outbound notifications produced by Vote
// Aggregation logic. It is a subset of the notifications produced by consensus
// participants.
// Implementations must:
// - be concurrency safe
// - be non-blocking
// - handle repetition of the same events (with some processing overhead).
type TimeoutAggregationConsumer[VoteT models.Unique] interface {
TimeoutAggregationViolationConsumer[VoteT]
TimeoutCollectorConsumer[VoteT]
}

View File

@ -0,0 +1,82 @@
package consensus
import (
"context"
"time"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// PartialTimeoutCertificateCreated represents a notification emitted by the
// TimeoutProcessor component, whenever it has collected TimeoutStates from a
// superminority of consensus participants for a specific rank. Along with the
// rank, it reports the newest QuorumCertificate and TimeoutCertificate (for
// previous rank) discovered during timeout collection. Per convention, the
// newest QuorumCertificate is never nil, while the TimeoutCertificate for the
// previous rank might be nil.
type PartialTimeoutCertificateCreated struct {
Rank uint64
NewestQuorumCertificate models.QuorumCertificate
PriorRankTimeoutCertificate models.TimeoutCertificate
}
// EventHandler runs a state machine to process proposals, QuorumCertificate and
// local timeouts. Not concurrency safe.
type EventHandler[StateT models.Unique, VoteT models.Unique] interface {
// OnReceiveQuorumCertificate processes a valid quorumCertificate constructed
// by internal vote aggregator or discovered in TimeoutState. All inputs
// should be validated before feeding into this function. Assuming trusted
// data. No errors are expected during normal operation.
OnReceiveQuorumCertificate(quorumCertificate models.QuorumCertificate) error
// OnReceiveTimeoutCertificate processes a valid timeoutCertificate
// constructed by internal timeout aggregator, discovered in TimeoutState or
// broadcast over the network. All inputs should be validated before feeding
// into this function. Assuming trusted data. No errors are expected during
// normal operation.
OnReceiveTimeoutCertificate(
timeoutCertificate models.TimeoutCertificate,
) error
// OnReceiveProposal processes a state proposal received from another HotStuff
// consensus participant. All inputs should be validated before feeding into
// this function. Assuming trusted data. No errors are expected during normal
// operation.
OnReceiveProposal(proposal *models.SignedProposal[StateT, VoteT]) error
// OnLocalTimeout handles a local timeout event by creating a
// models.TimeoutState and broadcasting it. No errors are expected during
// normal operation.
OnLocalTimeout() error
// OnPartialTimeoutCertificateCreated handles notification produces by the
// internal timeout aggregator. If the notification is for the current rank,
// a corresponding models.TimeoutState is broadcast to the consensus
// committee. No errors are expected during normal operation.
OnPartialTimeoutCertificateCreated(
partialTimeoutCertificate *PartialTimeoutCertificateCreated,
) error
// TimeoutChannel returns a channel that sends a signal on timeout.
TimeoutChannel() <-chan time.Time
// Start starts the event handler. No errors are expected during normal
// operation.
// CAUTION: EventHandler is not concurrency safe. The Start method must be
// executed by the same goroutine that also calls the other business logic
// methods, or concurrency safety has to be implemented externally.
Start(ctx context.Context) error
}
// EventLoop performs buffer and processing of incoming proposals and QCs.
type EventLoop[StateT models.Unique, VoteT models.Unique] interface {
TimeoutCollectorConsumer[VoteT]
VoteCollectorConsumer[VoteT]
SubmitProposal(proposal *models.SignedProposal[StateT, VoteT])
}
// FollowerLoop only follows certified states, does not actively process the
// collection of proposals and QC/TCs.
type FollowerLoop[StateT models.Unique, VoteT models.Unique] interface {
AddCertifiedState(certifiedState *models.CertifiedState[StateT])
}

View File

@ -0,0 +1,23 @@
package consensus
import "source.quilibrium.com/quilibrium/monorepo/consensus/models"
// Finalizer is used by the consensus algorithm to inform other components for
// (such as the protocol state) about finalization of states.
//
// Since we have two different protocol states: one for the main consensus,
// the other for the collection cluster consensus, the Finalizer interface
// allows the two different protocol states to provide different implementations
// for updating its state when a state has been finalized.
//
// Updating the protocol state should always succeed when the data is
// consistent. However, in case the protocol state is corrupted, error should be
// returned and the consensus algorithm should halt. So the error returned from
// MakeFinal is for the protocol state to report exceptions.
type Finalizer interface {
// MakeFinal will declare a state and all of its ancestors as finalized, which
// makes it an immutable part of the time reel. Returning an error indicates
// some fatal condition and will cause the finalization logic to terminate.
MakeFinal(stateID models.Identity) error
}

View File

@ -0,0 +1,106 @@
package consensus
import "source.quilibrium.com/quilibrium/monorepo/consensus/models"
// FinalityProof represents a finality proof for a State. By convention, a
// FinalityProof is immutable. Finality in Jolteon/HotStuff is determined by the
// 2-chain rule:
//
// There exists a _certified_ state C, such that State.Rank + 1 = C.Rank
type FinalityProof[StateT models.Unique] struct {
State *models.State[StateT]
CertifiedChild *models.CertifiedState[StateT]
}
// Forks maintains an in-memory data-structure of all states whose rank-number
// is larger or equal to the latest finalized state. The latest finalized state
// is defined as the finalized state with the largest rank number. When adding
// states, Forks automatically updates its internal state (including finalized
// states). Furthermore, states whose rank number is smaller than the latest
// finalized state are pruned automatically.
//
// PREREQUISITES:
// Forks expects that only states are added that can be connected to its latest
// finalized state (without missing interim ancestors). If this condition is
// violated, Forks will raise an error and ignore the state.
type Forks[StateT models.Unique] interface {
// GetStatesForRank returns all known states for the given rank
GetStatesForRank(rank uint64) []*models.State[StateT]
// GetState returns (*model.State, true) if the state with the specified
// id was found and (nil, false) otherwise.
GetState(stateID models.Identity) (*models.State[StateT], bool)
// FinalizedRank returns the largest rank number where a finalized state is
// known
FinalizedRank() uint64
// FinalizedState returns the finalized state with the largest rank number
FinalizedState() *models.State[StateT]
// FinalityProof returns the latest finalized state and a certified child from
// the subsequent rank, which proves finality.
// CAUTION: method returns (nil, false), when Forks has not yet finalized any
// states beyond the finalized root state it was initialized with.
FinalityProof() (*FinalityProof[StateT], bool)
// AddValidatedState appends the validated state to the tree of pending
// states and updates the latest finalized state (if applicable). Unless the
// parent is below the pruning threshold (latest finalized rank), we require
// that the parent is already stored in Forks. Calling this method with
// previously processed states leaves the consensus state invariant (though,
// it will potentially cause some duplicate processing).
// Notes:
// - Method `AddCertifiedState(..)` should be used preferably, if a QC
// certifying `state` is already known. This is generally the case for the
// consensus follower.
// - Method `AddValidatedState` is intended for active consensus
// participants, which fully validate states (incl. payload), i.e. QCs are
// processed as part of validated proposals.
//
// Possible error returns:
// - model.MissingStateError if the parent does not exist in the forest (but
// is above the pruned rank). From the perspective of Forks, this error is
// benign (no-op).
// - model.InvalidStateError if the state is invalid (see
// `Forks.EnsureStateIsValidExtension` for details). From the perspective
// of Forks, this error is benign (no-op). However, we assume all states
// are fully verified, i.e. they should satisfy all consistency
// requirements. Hence, this error is likely an indicator of a bug in the
// compliance layer.
// - model.ByzantineThresholdExceededError if conflicting QCs or conflicting
// finalized states have been detected (violating a foundational consensus
// guarantees). This indicates that there are 1/3+ Byzantine nodes
// (weighted by seniority) in the network, breaking the safety guarantees
// of HotStuff (or there is a critical bug / data corruption). Forks
// cannot recover from this exception.
// - All other errors are potential symptoms of bugs or state corruption.
AddValidatedState(proposal *models.State[StateT]) error
// AddCertifiedState appends the given certified state to the tree of pending
// states and updates the latest finalized state (if finalization progressed).
// Unless the parent is below the pruning threshold (latest finalized rank),
// we require that the parent is already stored in Forks. Calling this method
// with previously processed states leaves the consensus state invariant
// (though, it will potentially cause some duplicate processing).
//
// Possible error returns:
// - model.MissingStateError if the parent does not exist in the forest (but
// is above the pruned rank). From the perspective of Forks, this error is
// benign (no-op).
// - model.InvalidStateError if the state is invalid (see
// `Forks.EnsureStateIsValidExtension` for details). From the perspective
// of Forks, this error is benign (no-op). However, we assume all states
// are fully verified, i.e. they should satisfy all consistency
// requirements. Hence, this error is likely an indicator of a bug in the
// compliance layer.
// - model.ByzantineThresholdExceededError if conflicting QCs or conflicting
// finalized states have been detected (violating a foundational consensus
// guarantees). This indicates that there are 1/3+ Byzantine nodes
// (weighted by seniority) in the network, breaking the safety guarantees
// of HotStuff (or there is a critical bug / data corruption). Forks
// cannot recover from this exception.
// - All other errors are potential symptoms of bugs or state corruption.
AddCertifiedState(certifiedState *models.CertifiedState[StateT]) error
}

View File

@ -0,0 +1,29 @@
package consensus
import (
"context"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// LeaderProvider handles leader selection. State is provided, if relevant to
// the upstream consensus engine.
type LeaderProvider[
StateT models.Unique,
PeerIDT models.Unique,
CollectedT models.Unique,
] interface {
// GetNextLeaders returns a list of node indices, in priority order. Note that
// it is assumed that if no error is returned, GetNextLeaders should produce
// a non-empty list. If a list of size smaller than minimumProvers is
// provided, the liveness check will loop until the list is greater than that.
GetNextLeaders(ctx context.Context, prior *StateT) ([]PeerIDT, error)
// ProveNextState prepares a non-finalized new state from the prior, to be
// proposed and voted upon. Provided context may be canceled, should be used
// to halt long-running prover operations.
ProveNextState(
ctx context.Context,
filter []byte,
priorState models.Identity,
) (*StateT, error)
}

View File

@ -0,0 +1,25 @@
package consensus
import (
"context"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// LivenessProvider handles liveness announcements ahead of proving, to
// pre-emptively choose the next prover. In expected leader scenarios, this
// enables a peer to determine if an honest next prover is offline, so that it
// can publish the next state without waiting.
type LivenessProvider[
StateT models.Unique,
PeerIDT models.Unique,
CollectedT models.Unique,
] interface {
// Collect returns the collected mutation operations ahead of liveness
// announcements.
Collect(ctx context.Context) (CollectedT, error)
// SendLiveness announces liveness ahead of the next prover deterimination and
// subsequent proving. Provides prior state and collected mutation operations
// if relevant.
SendLiveness(ctx context.Context, prior *StateT, collected CollectedT) error
}

View File

@ -0,0 +1,65 @@
package consensus
import (
"context"
"time"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// Pacemaker defines a standard set of methods for handling pacemaker behaviors
// in the consensus engine.
type Pacemaker interface {
ProposalDurationProvider
// CurrentRank returns the current rank
CurrentRank() uint64
// LatestQuorumCertificate returns the latest quorum certificate seen.
LatestQuorumCertificate() models.QuorumCertificate
// PriorRankTimeoutCertificate returns the prior rank's timeout certificate,
// if it exists.
PriorRankTimeoutCertificate() models.TimeoutCertificate
// ReceiveQuorumCertificate handles an incoming quorum certificate, advancing
// to a new rank if applicable.
ReceiveQuorumCertificate(
quorumCertificate models.QuorumCertificate,
) (*models.NextRank, error)
// ReceiveTimeoutCertificate handles an incoming timeout certificate,
// advancing to a new rank if applicable.
ReceiveTimeoutCertificate(
timeoutCertificate models.TimeoutCertificate,
) (*models.NextRank, error)
// TimeoutCh provides a channel for timing out on the current rank.
TimeoutCh() <-chan time.Time
// Start starts the pacemaker, takes a cancellable context.
Start(ctx context.Context) error
}
// ProposalDurationProvider generates the target publication time for state
// proposals.
type ProposalDurationProvider interface {
// TargetPublicationTime is intended to be called by the EventHandler,
// whenever it wants to publish a new proposal. The event handler inputs
// - proposalRank: the rank it is proposing for,
// - timeRankEntered: the time when the EventHandler entered this rank
// - parentStateId: the ID of the parent state, which the EventHandler is
// building on
// TargetPublicationTime returns the time stamp when the new proposal should
// be broadcasted. For a given rank where we are the primary, suppose the
// actual time we are done building our proposal is P:
// - if P < TargetPublicationTime(..), then the EventHandler should wait
// until `TargetPublicationTime` to broadcast the proposal
// - if P >= TargetPublicationTime(..), then the EventHandler should
// immediately broadcast the proposal
//
// Note: Technically, our metrics capture the publication delay relative to
// this function's _latest_ call. Currently, the EventHandler is the only
// caller of this function, and only calls it once.
//
// Concurrency safe.
TargetPublicationTime(
proposalRank uint64,
timeRankEntered time.Time,
parentStateId models.Identity,
) time.Time
}

View File

@ -0,0 +1,25 @@
package consensus
import "source.quilibrium.com/quilibrium/monorepo/consensus/models"
// StateProducer is responsible for producing new state proposals. It is a
// service component to HotStuff's main state machine (implemented in the
// EventHandler). The StateProducer's central purpose is to mediate concurrent
// signing requests to its embedded `hotstuff.SafetyRules` during state
// production. The actual work of producing a state proposal is delegated to the
// embedded `consensus.LeaderProvider`.
type StateProducer[StateT models.Unique, VoteT models.Unique] interface {
// MakeStateProposal builds a new HotStuff state proposal using the given
// rank, the given quorum certificate for its parent and [optionally] a
// timeout certificate for last rank (could be nil).
// Error Returns:
// - model.NoVoteError if it is not safe for us to vote (our proposal
// includes our vote) for this rank. This can happen if we have already
// proposed or timed out this rank.
// - generic error in case of unexpected failure
MakeStateProposal(
rank uint64,
qc models.QuorumCertificate,
lastRankTC models.TimeoutCertificate,
) (*models.SignedProposal[StateT, VoteT], error)
}

View File

@ -0,0 +1,73 @@
package consensus
import "source.quilibrium.com/quilibrium/monorepo/consensus/models"
// SafetyRules enforces all consensus rules that guarantee safety. It produces
// votes for the given states or TimeoutState for the given ranks, only if all
// safety rules are satisfied. In particular, SafetyRules guarantees a
// foundational security theorem for HotStuff, which we utilize also outside of
// consensus (e.g. queuing pending states for execution, verification, sealing
// etc):
//
// THEOREM: For each rank, there can be at most 1 certified state.
//
// Implementations are generally *not* concurrency safe.
type SafetyRules[StateT models.Unique, VoteT models.Unique] interface {
// ProduceVote takes a state proposal and current rank, and decides whether to
// vote for the state. Voting is deterministic, i.e. voting for same proposal
// will always result in the same vote.
// Returns:
// * (vote, nil): On the _first_ state for the current rank that is safe to
// vote for. Subsequently, voter does _not_ vote for any _other_ state with
// the same (or lower) rank. SafetyRules internally caches and persists its
// latest vote. As long as the SafetyRules' internal state remains
// unchanged, ProduceVote will return its cached for identical inputs.
// * (nil, model.NoVoteError): If the safety module decides that it is not
// safe to vote for the given state. This is a sentinel error and
// _expected_ during normal operation.
// All other errors are unexpected and potential symptoms of uncovered edge
// cases or corrupted internal state (fatal).
ProduceVote(
proposal *models.SignedProposal[StateT, VoteT],
curRank uint64,
) (*VoteT, error)
// ProduceTimeout takes current rank, highest locally known QC and TC
// (optional, must be nil if and only if QC is for previous rank) and decides
// whether to produce timeout for current rank.
// Returns:
// * (timeout, nil): It is safe to timeout for current rank using newestQC
// and lastRankTC.
// * (nil, model.NoTimeoutError): If replica is not part of the authorized
// consensus committee (anymore) and therefore is not authorized to produce
// a valid timeout state. This sentinel error is _expected_ during normal
// operation, e.g. during the grace-period after Epoch switchover or after
// the replica self-ejected.
// All other errors are unexpected and potential symptoms of uncovered edge
// cases or corrupted internal state (fatal).
ProduceTimeout(
curRank uint64,
newestQC models.QuorumCertificate,
lastRankTC models.TimeoutCertificate,
) (*models.TimeoutState[VoteT], error)
// SignOwnProposal takes an unsigned state proposal and produces a vote for
// it. Vote is a cryptographic commitment to the proposal. By adding the vote
// to an unsigned proposal, the caller constructs a signed state proposal.
// This method has to be used only by the leader, which must be the proposer
// of the state (or an exception is returned).
// Implementors must guarantee that:
// - vote on the proposal satisfies safety rules
// - maximum one proposal is signed per rank
// Returns:
// * (vote, nil): the passed unsigned proposal is a valid one, and it's safe
// to make a proposal. Subsequently, leader does _not_ produce any _other_
// proposal with the same (or lower) rank.
// * (nil, model.NoVoteError): according to HotStuff's Safety Rules, it is
// not safe to sign the given proposal. This could happen because we have
// already proposed or timed out for the given rank. This is a sentinel
// error and _expected_ during normal operation.
// All other errors are unexpected and potential symptoms of uncovered edge
// cases or corrupted internal state (fatal).
SignOwnProposal(unsignedProposal *models.Proposal[StateT]) (*VoteT, error)
}

View File

@ -0,0 +1,161 @@
package consensus
import (
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// WeightedSignatureAggregator aggregates signatures of the same signature
// scheme and the same message from different signers. The public keys and
// message are agreed upon upfront. It is also recommended to only aggregate
// signatures generated with keys representing equivalent security-bit level.
// Furthermore, a weight [unsigned int64] is assigned to each signer ID. The
// WeightedSignatureAggregator internally tracks the total weight of all
// collected signatures. Implementations must be concurrency safe.
type WeightedSignatureAggregator interface {
// Verify verifies the signature under the stored public keys and message.
// Expected errors during normal operations:
// - model.InvalidSignerError if signerID is invalid (not a consensus
// participant)
// - model.ErrInvalidSignature if signerID is valid but signature is
// cryptographically invalid
Verify(signerID models.Identity, sig []byte) error
// TrustedAdd adds a signature to the internal set of signatures and adds the
// signer's weight to the total collected weight, iff the signature is _not_ a
// duplicate. The total weight of all collected signatures (excluding
// duplicates) is returned regardless of any returned error.
// Expected errors during normal operations:
// - model.InvalidSignerError if signerID is invalid (not a consensus
// participant)
// - model.DuplicatedSignerError if the signer has been already added
TrustedAdd(signerID models.Identity, sig []byte) (
totalWeight uint64,
exception error,
)
// TotalWeight returns the total weight presented by the collected signatures.
TotalWeight() uint64
// Aggregate aggregates the signatures and returns the aggregated signature.
// The function performs a final verification and errors if the aggregated
// signature is invalid. This is required for the function safety since
// `TrustedAdd` allows adding invalid signatures.
// The function errors with:
// - model.InsufficientSignaturesError if no signatures have been added yet
// - model.InvalidSignatureIncludedError if:
// -- some signature(s), included via TrustedAdd, fail to deserialize
// (regardless of the aggregated public key)
// -- or all signatures deserialize correctly but some signature(s),
// included via TrustedAdd, are invalid (while aggregated public key is
// valid)
// - model.InvalidAggregatedKeyError if all signatures deserialize correctly
// but the signer's proving public keys sum up to an invalid key (BLS
// identity public key). Any aggregated signature would fail the
// cryptographic verification under the identity public key and therefore
// such signature is considered invalid. Such scenario can only happen if
// proving public keys of signers were forged to add up to the identity
// public key. Under the assumption that all proving key PoPs are valid,
// this error case can only happen if all signers are malicious and
// colluding. If there is at least one honest signer, there is a
// negligible probability that the aggregated key is identity.
//
// The function is thread-safe.
Aggregate() ([]models.WeightedIdentity, models.AggregatedSignature, error)
}
// TimeoutSignatureAggregator aggregates timeout signatures for one particular
// rank. When instantiating a TimeoutSignatureAggregator, the following
// information is supplied:
// - The rank for which the aggregator collects timeouts.
// - For each replicas that is authorized to send a timeout at this particular
// rank: the node ID, public proving keys, and weight
//
// Timeouts for other ranks or from non-authorized replicas are rejected.
// In their TimeoutStates, replicas include a signature over the pair (rank,
// newestQCRank), where `rank` is the rank number the timeout is for and
// `newestQCRank` is the rank of the newest QC known to the replica.
// TimeoutSignatureAggregator collects these signatures, internally tracks the
// total weight of all collected signatures. Note that in general the signed
// messages are different, which makes the aggregation a comparatively expensive
// operation. Upon calling `Aggregate`, the TimeoutSignatureAggregator
// aggregates all valid signatures collected up to this point. The aggregate
// signature is guaranteed to be correct, as only valid signatures are accepted
// as inputs.
// TimeoutSignatureAggregator internally tracks the total weight of all
// collected signatures. Implementations must be concurrency safe.
type TimeoutSignatureAggregator interface {
// VerifyAndAdd verifies the signature under the stored public keys and adds
// the signature and the corresponding highest QC to the internal set.
// Internal set and collected weight is modified iff signature _is_ valid.
// The total weight of all collected signatures (excluding duplicates) is
// returned regardless of any returned error.
// Expected errors during normal operations:
// - model.InvalidSignerError if signerID is invalid (not a consensus
// participant)
// - model.DuplicatedSignerError if the signer has been already added
// - model.ErrInvalidSignature if signerID is valid but signature is
// cryptographically invalid
VerifyAndAdd(
signerID models.Identity,
sig []byte,
newestQCRank uint64,
) (totalWeight uint64, exception error)
// TotalWeight returns the total weight presented by the collected signatures.
TotalWeight() uint64
// Rank returns the rank that this instance is aggregating signatures for.
Rank() uint64
// Aggregate aggregates the signatures and returns with additional data.
// Aggregated signature will be returned as SigData of timeout certificate.
// Caller can be sure that resulting signature is valid.
// Expected errors during normal operations:
// - model.InsufficientSignaturesError if no signatures have been added yet
Aggregate() (
signersInfo []TimeoutSignerInfo,
aggregatedSig models.AggregatedSignature,
exception error,
)
}
// TimeoutSignerInfo is a helper structure that stores the QC ranks that each
// signer contributed to a TC. Used as result of
// TimeoutSignatureAggregator.Aggregate()
type TimeoutSignerInfo struct {
NewestQCRank uint64
Signer models.Identity
}
// StateSignatureData is an intermediate struct for Packer to pack the
// aggregated signature data into raw bytes or unpack from raw bytes.
type StateSignatureData struct {
Signers []models.WeightedIdentity
Signature []byte
}
// Packer packs aggregated signature data into raw bytes to be used in state
// header.
type Packer interface {
// Pack serializes the provided StateSignatureData into a precursor format of
// a QC. rank is the rank of the state that the aggregated signature is for.
// sig is the aggregated signature data.
// Expected error returns during normal operations:
// * none; all errors are symptoms of inconsistent input data or corrupted
// internal state.
Pack(rank uint64, sig *StateSignatureData) (
signerIndices []byte,
sigData []byte,
err error,
)
// Unpack de-serializes the provided signature data.
// sig is the aggregated signature data
// It returns:
// - (sigData, nil) if successfully unpacked the signature data
// - (nil, model.InvalidFormatError) if failed to unpack the signature data
Unpack(signerIdentities []models.WeightedIdentity, sigData []byte) (
*StateSignatureData,
error,
)
}

View File

@ -0,0 +1,39 @@
package consensus
import (
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// Signer is responsible for creating votes, proposals for a given state.
type Signer[StateT models.Unique, VoteT models.Unique] interface {
// CreateVote creates a vote for the given state. No error returns are
// expected during normal operations (incl. presence of byz. actors).
CreateVote(state *models.State[StateT]) (*VoteT, error)
// CreateTimeout creates a timeout for given rank. No errors return are
// expected during normal operations(incl presence of byz. actors).
CreateTimeout(
curView uint64,
newestQC models.QuorumCertificate,
previousRankTimeoutCert models.TimeoutCertificate,
) (*models.TimeoutState[VoteT], error)
}
type SignatureAggregator interface {
VerifySignatureMultiMessage(
publicKeys [][]byte,
signature []byte,
messages [][]byte,
context []byte,
) bool
VerifySignatureRaw(
publicKey []byte,
signature []byte,
message []byte,
context []byte,
) bool
Aggregate(
publicKeys [][]byte,
signatures [][]byte,
) (models.AggregatedSignature, error)
}

View File

@ -0,0 +1,18 @@
package consensus
import "source.quilibrium.com/quilibrium/monorepo/consensus/models"
// ConsensusStore defines the methods required for internal state that should
// persist between restarts of the consensus engine.
type ConsensusStore[VoteT models.Unique] interface {
ReadOnlyConsensusStore[VoteT]
PutConsensusState(state *models.ConsensusState[VoteT]) error
PutLivenessState(state *models.LivenessState) error
}
// ReadOnlyConsensusStore defines the methods required for reading internal
// state persisted between restarts of the consensus engine.
type ReadOnlyConsensusStore[VoteT models.Unique] interface {
GetConsensusState() (*models.ConsensusState[VoteT], error)
GetLivenessState() (*models.LivenessState, error)
}

View File

@ -0,0 +1,20 @@
package consensus
import (
"context"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// SyncProvider handles synchronization management
type SyncProvider[StateT models.Unique] interface {
// Performs synchronization to set internal state. Note that it is assumed
// that errors are transient and synchronization should be reattempted on
// failure. If some other process for synchronization is used and this should
// be bypassed, send nil on the error channel. Provided context may be
// canceled, should be used to halt long-running sync operations.
Synchronize(
ctx context.Context,
existing *StateT,
) (<-chan *StateT, <-chan error)
}

View File

@ -0,0 +1,128 @@
package consensus
import (
"context"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// TimeoutAggregator verifies and aggregates timeout states to build timeout
// certificates [TCs]. When enough timeout states are collected, it builds a TC
// and sends it to the EventLoop TimeoutAggregator also detects protocol
// violation, including invalid timeouts, double timeout, etc and notifies a
// HotStuff consumer for slashing.
type TimeoutAggregator[VoteT models.Unique] interface {
Start(ctx context.Context) error
// AddTimeout verifies and aggregates a timeout state.
// This method can be called concurrently, timeouts will be queued and
// processed asynchronously.
AddTimeout(timeoutState *models.TimeoutState[VoteT])
// PruneUpToRank deletes all `TimeoutCollector`s _below_ to the given rank, as
// well as related indices. We only retain and process `TimeoutCollector`s,
// whose rank is equal or larger than `lowestRetainedRank`. If
// `lowestRetainedRank` is smaller than the previous value, the previous value
// is kept and the method call is a NoOp. This value should be set to the
// latest active rank maintained by `Pacemaker`.
PruneUpToRank(lowestRetainedRank uint64)
}
// TimeoutCollector collects all timeout states for a specified rank. On the
// happy path, it generates a TimeoutCertificate when enough timeouts have been
// collected. The TimeoutCollector is a higher-level structure that orchestrates
// deduplication, caching and processing of timeouts, delegating those tasks to
// underlying modules (such as TimeoutProcessor). Implementations of
// TimeoutCollector must be concurrency safe.
type TimeoutCollector[VoteT models.Unique] interface {
// AddTimeout adds a Timeout State to the collector. When TSs from
// strictly more than 1/3 of consensus participants (measured by weight) were
// collected, the callback for partial TC will be triggered. After collecting
// TSs from a supermajority, a TC will be created and passed to the EventLoop.
// Expected error returns during normal operations:
// * timeoutcollector.ErrTimeoutForIncompatibleRank - submitted timeout for
// incompatible rank
// All other exceptions are symptoms of potential state corruption.
AddTimeout(timeoutState *models.TimeoutState[VoteT]) error
// Rank returns the rank that this instance is collecting timeouts for.
// This method is useful when adding the newly created timeout collector to
// timeout collectors map.
Rank() uint64
}
// TimeoutProcessor ingests Timeout States for a particular rank. It
// implements the algorithms for validating TSs, orchestrates their low-level
// aggregation and emits `OnPartialTcCreated` and `OnTcConstructedFromTimeouts`
// notifications. TimeoutProcessor cannot deduplicate TSs (this should be
// handled by the higher-level TimeoutCollector) and errors instead. Depending
// on their implementation, a TimeoutProcessor might drop timeouts or attempt to
// construct a TC.
type TimeoutProcessor[VoteT models.Unique] interface {
// Process performs processing of single timeout state. This function is safe
// to call from multiple goroutines. Expected error returns during normal
// operations:
// * timeoutcollector.ErrTimeoutForIncompatibleRank - submitted timeout for
// incompatible rank
// * models.InvalidTimeoutError - submitted invalid timeout(invalid structure
// or invalid signature)
// * models.DuplicatedSignerError if a timeout from the same signer was
// previously already added. It does _not necessarily_ imply that the
// timeout is invalid or the sender is equivocating.
// All other errors should be treated as exceptions.
Process(timeout *models.TimeoutState[VoteT]) error
}
// TimeoutCollectorFactory performs creation of TimeoutCollector for a given
// rank
type TimeoutCollectorFactory[VoteT models.Unique] interface {
// Create is a factory method to generate a TimeoutCollector for a given rank
// Expected error returns during normal operations:
// * models.ErrRankUnknown no epoch containing the given rank is known
// All other errors should be treated as exceptions.
Create(rank uint64) (TimeoutCollector[VoteT], error)
}
// TimeoutProcessorFactory performs creation of TimeoutProcessor for a given
// rank
type TimeoutProcessorFactory[VoteT models.Unique] interface {
// Create is a factory method to generate a TimeoutProcessor for a given rank
// Expected error returns during normal operations:
// * models.ErrRankUnknown no epoch containing the given rank is known
// All other errors should be treated as exceptions.
Create(rank uint64) (TimeoutProcessor[VoteT], error)
}
// TimeoutCollectors encapsulates the functionality to generate, store and prune
// `TimeoutCollector` instances (one per rank). Its main purpose is to provide a
// higher-level API to `TimeoutAggregator` for managing and interacting with the
// rank-specific `TimeoutCollector` instances. Implementations are concurrency
// safe.
type TimeoutCollectors[VoteT models.Unique] interface {
// GetOrCreateCollector retrieves the TimeoutCollector for the specified
// rank or creates one if none exists. When creating a timeout collector,
// the rank is used to query the consensus committee for the respective
// Epoch the rank belongs to.
// It returns:
// - (collector, true, nil) if no collector can be found by the rank, and a
// new collector was created.
// - (collector, false, nil) if the collector can be found by the rank.
// - (nil, false, error) if running into any exception creating the timeout
// collector.
// Expected error returns during normal operations:
// * models.BelowPrunedThresholdError if rank is below the pruning threshold
// * models.ErrRankUnknown if rank is not yet pruned but no epoch containing
// the given rank is known
GetOrCreateCollector(rank uint64) (
collector TimeoutCollector[VoteT],
created bool,
err error,
)
// PruneUpToRank prunes the timeout collectors with ranks _below_ the given
// value, i.e. we only retain and process timeout collectors, whose ranks are
// equal or larger than `lowestRetainedRank`. If `lowestRetainedRank` is
// smaller than the previous value, the previous value is kept and the method
// call is a NoOp.
PruneUpToRank(lowestRetainedRank uint64)
}

View File

@ -0,0 +1,12 @@
package consensus
// TraceLogger defines a simple tracing interface
type TraceLogger interface {
Trace(message string)
Error(message string, err error)
}
type nilTracer struct{}
func (nilTracer) Trace(message string) {}
func (nilTracer) Error(message string, err error) {}

View File

@ -0,0 +1,32 @@
package consensus
import (
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// Validator provides functions to validate QuorumCertificate, proposals and
// votes.
type Validator[StateT models.Unique, VoteT models.Unique] interface {
// ValidateQuorumCertificate checks the validity of a QuorumCertificate.
// During normal operations, the following error returns are expected:
// * models.InvalidQuorumCertificateError if the QuorumCertificate is invalid
ValidateQuorumCertificate(qc models.QuorumCertificate) error
// ValidateTimeoutCertificate checks the validity of a TimeoutCertificate.
// During normal operations, the following error returns are expected:
// * models.InvalidTimeoutCertificateError if the TimeoutCertificate is
// invalid
ValidateTimeoutCertificate(tc models.TimeoutCertificate) error
// ValidateProposal checks the validity of a proposal.
// During normal operations, the following error returns are expected:
// * models.InvalidProposalError if the state is invalid
ValidateProposal(proposal *models.SignedProposal[StateT, VoteT]) error
// ValidateVote checks the validity of a vote.
// Returns the full entity for the voter. During normal operations,
// the following errors are expected:
// * models.InvalidVoteError for invalid votes
ValidateVote(vote *VoteT) (*models.WeightedIdentity, error)
}

View File

@ -0,0 +1,45 @@
package consensus
import "source.quilibrium.com/quilibrium/monorepo/consensus/models"
// Verifier is the component responsible for the cryptographic integrity of
// votes, proposals and QC's against the state they are signing.
type Verifier[VoteT models.Unique] interface {
// VerifyVote checks the cryptographic validity of a vote's `SigData` w.r.t.
// the rank and stateID. It is the responsibility of the calling code to
// ensure that `voter` is authorized to vote.
// Return values:
// * nil if `sigData` is cryptographically valid
// * models.InvalidFormatError if the signature has an incompatible format.
// * models.ErrInvalidSignature is the signature is invalid
// * unexpected errors should be treated as symptoms of bugs or uncovered
// edge cases in the logic (i.e. as fatal)
VerifyVote(vote *VoteT) error
// VerifyQC checks the cryptographic validity of a QC's `SigData` w.r.t. the
// given rank and stateID. It is the responsibility of the calling code to
// ensure that all `signers` are authorized, without duplicates.
// Return values:
// * nil if `sigData` is cryptographically valid
// * models.InvalidFormatError if `sigData` has an incompatible format
// * models.InsufficientSignaturesError if `signers is empty.
// Depending on the order of checks in the higher-level logic this error
// might be an indicator of a external byzantine input or an internal bug.
// * models.ErrInvalidSignature if a signature is invalid
// * unexpected errors should be treated as symptoms of bugs or uncovered
// edge cases in the logic (i.e. as fatal)
VerifyQuorumCertificate(quorumCertificate models.QuorumCertificate) error
// VerifyTimeoutCertificate checks cryptographic validity of the TC's
// `sigData` w.r.t. the given rank. It is the responsibility of the calling
// code to ensure that all `signers` are authorized, without duplicates.
// Return values:
// * nil if `sigData` is cryptographically valid
// * models.InsufficientSignaturesError if `signers is empty.
// * models.InvalidFormatError if `signers`/`highQCViews` have differing
// lengths
// * models.ErrInvalidSignature if a signature is invalid
// * unexpected errors should be treated as symptoms of bugs or uncovered
// edge cases in the logic (i.e. as fatal)
VerifyTimeoutCertificate(timeoutCertificate models.TimeoutCertificate) error
}

View File

@ -0,0 +1,63 @@
package consensus
import (
"context"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// VotingProvider handles voting logic by deferring decisions, collection, and
// state finalization to an outside implementation.
type VotingProvider[
StateT models.Unique,
VoteT models.Unique,
PeerIDT models.Unique,
] interface {
// Sends a proposal for voting.
SendProposal(ctx context.Context, proposal *StateT) error
// SignVote signs a proposal, produces an output vote for aggregation and
// broadcasting.
SignVote(
ctx context.Context,
state *models.State[StateT],
) (*VoteT, error)
// SignVote signs a proposal, produces an output vote for aggregation and
// broadcasting.
SignTimeoutVote(
ctx context.Context,
filter []byte,
currentRank uint64,
newestQuorumCertificateRank uint64,
) (*VoteT, error)
FinalizeQuorumCertificate(
ctx context.Context,
state *models.State[StateT],
aggregatedSignature models.AggregatedSignature,
) (models.QuorumCertificate, error)
// Produces a timeout certificate
FinalizeTimeout(
ctx context.Context,
filter []byte,
rank uint64,
latestQuorumCertificateRanks []uint64,
aggregatedSignature models.AggregatedSignature,
) (models.TimeoutCertificate, error)
// Re-publishes a vote message, used to help lagging peers catch up.
SendVote(ctx context.Context, vote *VoteT) (PeerIDT, error)
// IsQuorum returns a response indicating whether or not quorum has been
// reached.
IsQuorum(
ctx context.Context,
proposalVotes map[models.Identity]*VoteT,
) (bool, error)
// FinalizeVotes performs any folding of proposed state required from VoteT
// onto StateT, proposed states and votes matched by PeerIDT, returns
// finalized state, chosen proposer PeerIDT.
FinalizeVotes(
ctx context.Context,
proposals map[models.Identity]*StateT,
proposalVotes map[models.Identity]*VoteT,
) (*StateT, PeerIDT, error)
// SendConfirmation sends confirmation of the finalized state.
SendConfirmation(ctx context.Context, finalized *StateT) error
}

View File

@ -0,0 +1,10 @@
package consensus
// WeightProvider defines the methods for handling weighted differentiation of
// voters, such as seniority, or stake.
type WeightProvider interface {
// GetWeightForBitmask returns the total weight of the given bitmask for the
// prover set under the filter. Bitmask is expected to be in ascending ring
// order.
GetWeightForBitmask(filter []byte, bitmask []byte) uint64
}

View File

@ -0,0 +1,688 @@
package eventhandler
import (
"context"
"errors"
"fmt"
"time"
"source.quilibrium.com/quilibrium/monorepo/consensus"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// EventHandler is the main handler for individual events that trigger state
// transition. It exposes API to handle one event at a time synchronously.
// EventHandler is *not concurrency safe*. Please use the EventLoop to ensure
// that only a single go-routine executes the EventHandler's algorithms.
// EventHandler is implemented in event-driven way, it reacts to incoming events
// and performs certain actions. It doesn't perform any actions on its own.
// There are 3 main responsibilities of EventHandler, vote, propose, timeout.
// There are specific scenarios that lead to each of those actions.
// - create vote: voting logic is triggered by OnReceiveProposal, after
// receiving proposal we have all required information to create a valid
// vote. Compliance engine makes sure that we receive proposals, whose
// parents are known. Creating a vote can be triggered ONLY by receiving
// proposal.
// - create timeout: creating models.TimeoutState is triggered by
// OnLocalTimeout, after reaching deadline for current round. EventHandler
// gets notified about it and has to create a models.TimeoutState and
// broadcast it to other replicas. Creating a TO can be triggered by
// reaching round deadline or triggered as part of Bracha broadcast when
// superminority of replicas have contributed to TC creation and created a
// partial TC.
// - create a proposal: proposing logic is more complicated. Creating a
// proposal is triggered by the EventHandler receiving a QC or TC that
// induces a rank change to a rank where the replica is primary. As an edge
// case, the EventHandler can receive a QC or TC that triggers the rank
// change, but we can't create a proposal in case we are missing parent
// state the newest QC refers to. In case we already have the QC, but are
// still missing the respective parent, OnReceiveProposal can trigger the
// proposing logic as well, but only when receiving proposal for rank lower
// than active rank. To summarize, to make a valid proposal for rank N we
// need to have a QC or TC for N-1 and know the proposal with stateID
// NewestQC.Identifier.
//
// Not concurrency safe.
type EventHandler[
StateT models.Unique,
VoteT models.Unique,
PeerIDT models.Unique,
CollectedT models.Unique,
] struct {
tracer consensus.TraceLogger
paceMaker consensus.Pacemaker
stateProducer consensus.StateProducer[StateT, VoteT]
forks consensus.Forks[StateT]
store consensus.ConsensusStore[VoteT]
committee consensus.Replicas
safetyRules consensus.SafetyRules[StateT, VoteT]
notifier consensus.Consumer[StateT, VoteT]
}
var _ consensus.EventHandler[*nilUnique, *nilUnique] = (*EventHandler[
*nilUnique, *nilUnique, *nilUnique, *nilUnique,
])(nil)
// NewEventHandler creates an EventHandler instance with initial components.
func NewEventHandler[
StateT models.Unique,
VoteT models.Unique,
PeerIDT models.Unique,
CollectedT models.Unique,
](
paceMaker consensus.Pacemaker,
stateProducer consensus.StateProducer[StateT, VoteT],
forks consensus.Forks[StateT],
store consensus.ConsensusStore[VoteT],
committee consensus.Replicas,
safetyRules consensus.SafetyRules[StateT, VoteT],
notifier consensus.Consumer[StateT, VoteT],
tracer consensus.TraceLogger,
) (*EventHandler[StateT, VoteT, PeerIDT, CollectedT], error) {
e := &EventHandler[StateT, VoteT, PeerIDT, CollectedT]{
paceMaker: paceMaker,
stateProducer: stateProducer,
forks: forks,
store: store,
safetyRules: safetyRules,
committee: committee,
notifier: notifier,
tracer: tracer,
}
return e, nil
}
// OnReceiveQuorumCertificate processes a valid qc constructed by internal vote
// aggregator or discovered in TimeoutState. All inputs should be validated
// before feeding into this function. Assuming trusted data. No errors are
// expected during normal operation.
func (e *EventHandler[
StateT,
VoteT,
PeerIDT,
CollectedT,
]) OnReceiveQuorumCertificate(qc models.QuorumCertificate) error {
curRank := e.paceMaker.CurrentRank()
e.tracer.Trace("received QC")
e.notifier.OnReceiveQuorumCertificate(curRank, qc)
defer e.notifier.OnEventProcessed()
newRankEvent, err := e.paceMaker.ReceiveQuorumCertificate(qc)
if err != nil {
return fmt.Errorf("could not process QC: %w", err)
}
if newRankEvent == nil {
e.tracer.Trace("QC didn't trigger rank change, nothing to do")
return nil
}
// current rank has changed, go to new rank
e.tracer.Trace("QC triggered rank change, starting new rank now")
return e.proposeForNewRankIfPrimary()
}
// OnReceiveTimeoutCertificate processes a valid tc constructed by internal timeout aggregator,
// discovered in TimeoutState or broadcast over the network.
// All inputs should be validated before feeding into this function. Assuming
// trusted data. No errors are expected during normal operation.
func (e *EventHandler[
StateT,
VoteT,
PeerIDT,
CollectedT,
]) OnReceiveTimeoutCertificate(tc models.TimeoutCertificate) error {
curRank := e.paceMaker.CurrentRank()
e.tracer.Trace("received TC")
e.notifier.OnReceiveTimeoutCertificate(curRank, tc)
defer e.notifier.OnEventProcessed()
newRankEvent, err := e.paceMaker.ReceiveTimeoutCertificate(tc)
if err != nil {
return fmt.Errorf("could not process TC for rank %d: %w", tc.GetRank(), err)
}
if newRankEvent == nil {
e.tracer.Trace("TC didn't trigger rank change, nothing to do")
return nil
}
// current rank has changed, go to new rank
e.tracer.Trace("TC triggered rank change, starting new rank now")
return e.proposeForNewRankIfPrimary()
}
// OnReceiveProposal processes a state proposal received from another HotStuff
// consensus participant.
// All inputs should be validated before feeding into this function. Assuming
// trusted data. No errors are expected during normal operation.
func (e *EventHandler[
StateT,
VoteT,
PeerIDT,
CollectedT,
]) OnReceiveProposal(proposal *models.SignedProposal[StateT, VoteT]) error {
state := proposal.State
curRank := e.paceMaker.CurrentRank()
e.tracer.Trace("proposal received from compliance engine")
e.notifier.OnReceiveProposal(curRank, proposal)
defer e.notifier.OnEventProcessed()
// ignore stale proposals
if (*state).Rank < e.forks.FinalizedRank() {
e.tracer.Trace("stale proposal")
return nil
}
// store the state.
err := e.forks.AddValidatedState(proposal.State)
if err != nil {
return fmt.Errorf(
"cannot add proposal to forks (%x): %w",
state.Identifier,
err,
)
}
_, err = e.paceMaker.ReceiveQuorumCertificate(
proposal.State.ParentQuorumCertificate,
)
if err != nil {
return fmt.Errorf(
"could not process QC for state %x: %w",
state.Identifier,
err,
)
}
_, err = e.paceMaker.ReceiveTimeoutCertificate(
proposal.PreviousRankTimeoutCertificate,
)
if err != nil {
return fmt.Errorf(
"could not process TC for state %x: %w",
state.Identifier,
err,
)
}
// if the state is for the current rank, then try voting for this state
err = e.processStateForCurrentRank(proposal)
if err != nil {
return fmt.Errorf("failed processing current state: %w", err)
}
e.tracer.Trace("proposal processed from compliance engine")
// nothing to do if this proposal is for current rank
if proposal.State.Rank == e.paceMaker.CurrentRank() {
return nil
}
return e.proposeForNewRankIfPrimary()
}
// TimeoutChannel returns the channel for subscribing the waiting timeout on
// receiving state or votes for the current rank.
func (e *EventHandler[
StateT,
VoteT,
PeerIDT,
CollectedT,
]) TimeoutChannel() <-chan time.Time {
return e.paceMaker.TimeoutCh()
}
// OnLocalTimeout handles a local timeout event by creating a
// models.TimeoutState and broadcasting it. No errors are expected during normal
// operation.
func (e *EventHandler[
StateT,
VoteT,
PeerIDT,
CollectedT,
]) OnLocalTimeout() error {
curRank := e.paceMaker.CurrentRank()
e.tracer.Trace("timeout received from event loop")
e.notifier.OnLocalTimeout(curRank)
defer e.notifier.OnEventProcessed()
err := e.broadcastTimeoutStateIfAuthorized()
if err != nil {
return fmt.Errorf(
"unexpected exception while processing timeout in rank %d: %w",
curRank,
err,
)
}
return nil
}
// OnPartialTcCreated handles notification produces by the internal timeout
// aggregator. If the notification is for the current rank, a corresponding
// models.TimeoutState is broadcast to the consensus committee. No errors are
// expected during normal operation.
func (e *EventHandler[
StateT,
VoteT,
PeerIDT,
CollectedT,
]) OnPartialTimeoutCertificateCreated(
partialTC *consensus.PartialTimeoutCertificateCreated,
) error {
curRank := e.paceMaker.CurrentRank()
previousRankTimeoutCert := partialTC.PriorRankTimeoutCertificate
e.tracer.Trace("constructed partial TC")
e.notifier.OnPartialTimeoutCertificate(curRank, partialTC)
defer e.notifier.OnEventProcessed()
// process QC, this might trigger rank change
_, err := e.paceMaker.ReceiveQuorumCertificate(
partialTC.NewestQuorumCertificate,
)
if err != nil {
return fmt.Errorf("could not process newest QC: %w", err)
}
// process TC, this might trigger rank change
_, err = e.paceMaker.ReceiveTimeoutCertificate(previousRankTimeoutCert)
if err != nil {
return fmt.Errorf(
"could not process TC for rank %d: %w",
previousRankTimeoutCert.GetRank(),
err,
)
}
// NOTE: in other cases when we have observed a rank change we will trigger
// proposing logic, this is desired logic for handling proposal, QC and TC.
// However, observing a partial TC means that superminority have timed out and
// there was at least one honest replica in that set. Honest replicas will
// never vote after timing out for current rank meaning we won't be able to
// collect supermajority of votes for a proposal made after observing partial
// TC.
// by definition, we are allowed to produce timeout state if we have received
// partial TC for current rank
if e.paceMaker.CurrentRank() != partialTC.Rank {
return nil
}
e.tracer.Trace("partial TC generated for current rank, broadcasting timeout")
err = e.broadcastTimeoutStateIfAuthorized()
if err != nil {
return fmt.Errorf(
"unexpected exception while processing partial TC in rank %d: %w",
partialTC.Rank,
err,
)
}
return nil
}
// Start starts the event handler. No errors are expected during normal
// operation. CAUTION: EventHandler is not concurrency safe. The Start method
// must be executed by the same goroutine that also calls the other business
// logic methods, or concurrency safety has to be implemented externally.
func (e *EventHandler[
StateT,
VoteT,
PeerIDT,
CollectedT,
]) Start(ctx context.Context) error {
e.notifier.OnStart(e.paceMaker.CurrentRank())
defer e.notifier.OnEventProcessed()
e.paceMaker.Start(ctx)
err := e.proposeForNewRankIfPrimary()
if err != nil {
return fmt.Errorf("could not start new rank: %w", err)
}
return nil
}
// broadcastTimeoutStateIfAuthorized attempts to generate a
// models.TimeoutState, adds it to `timeoutAggregator` and broadcasts it to the
// consensus commettee. We check, whether this node, at the current rank, is
// part of the consensus committee. Otherwise, this method is functionally a
// no-op. For example, right after an epoch switchover a consensus node might
// still be online but not part of the _active_ consensus committee anymore.
// Consequently, it should not broadcast timeouts anymore. No errors are
// expected during normal operation.
func (e *EventHandler[
StateT,
VoteT,
PeerIDT,
CollectedT,
]) broadcastTimeoutStateIfAuthorized() error {
curRank := e.paceMaker.CurrentRank()
newestQC := e.paceMaker.LatestQuorumCertificate()
previousRankTimeoutCert := e.paceMaker.PriorRankTimeoutCertificate()
if newestQC.GetRank()+1 == curRank {
// in case last rank has ended with QC and TC, make sure that only QC is
// included otherwise such timeout is invalid. This case is possible if TC
// has included QC with the same rank as the TC itself, meaning that
// newestQC.Rank == previousRankTimeoutCert.Rank
previousRankTimeoutCert = nil
}
timeout, err := e.safetyRules.ProduceTimeout(
curRank,
newestQC,
previousRankTimeoutCert,
)
if err != nil {
if models.IsNoTimeoutError(err) {
e.tracer.Error(
"not generating timeout as this node is not part of the active committee",
err,
)
return nil
}
return fmt.Errorf("could not produce timeout: %w", err)
}
// raise a notification to broadcast timeout
e.notifier.OnOwnTimeout(timeout)
e.tracer.Trace("broadcast TimeoutState done")
return nil
}
// proposeForNewRankIfPrimary will only be called when we may be able to propose
// a state, after processing a new event.
// - after entering a new rank as a result of processing a QC or TC, then we
// may propose for the newly entered rank
// - after receiving a proposal (but not changing rank), if that proposal is
// referenced by our highest known QC, and the proposal was previously
// unknown, then we can propose a state in the current rank
//
// Enforced INVARIANTS:
// - There will at most be `OnOwnProposal` notification emitted for ranks
// where this node is the leader, and none if another node is the leader.
// This holds irrespective of restarts. Formally, this prevents proposal
// equivocation.
//
// It reads the current rank, and generates a proposal if we are the leader.
// No errors are expected during normal operation.
func (e *EventHandler[
StateT,
VoteT,
PeerIDT,
CollectedT,
]) proposeForNewRankIfPrimary() error {
start := time.Now() // track the start time
curRank := e.paceMaker.CurrentRank()
currentLeader, err := e.committee.LeaderForRank(curRank)
if err != nil {
return fmt.Errorf(
"failed to determine primary for new rank %d: %w",
curRank,
err,
)
}
finalizedRank := e.forks.FinalizedRank()
e.notifier.OnCurrentRankDetails(curRank, finalizedRank, currentLeader)
// check that I am the primary for this rank
if e.committee.Self() != currentLeader {
return nil
}
// attempt to generate proposal:
newestQC := e.paceMaker.LatestQuorumCertificate()
previousRankTimeoutCert := e.paceMaker.PriorRankTimeoutCertificate()
_, found := e.forks.GetState(newestQC.GetSelector())
if !found {
// we don't know anything about state referenced by our newest QC, in this
// case we can't create a valid proposal since we can't guarantee validity
// of state payload.
e.tracer.Trace("haven't synced the latest state yet; can't propose")
return nil
}
e.tracer.Trace("generating proposal as leader")
// Sanity checks to make sure that resulting proposal is valid:
// In its proposal, the leader for rank N needs to present evidence that it
// has legitimately entered rank N. As evidence, we include a QC or TC for
// rank N-1, which should always be available as the PaceMaker advances to
// rank N only after observing a QC or TC from rank N-1. Moreover, QC and TC
// are always processed together. As EventHandler is strictly single-threaded
// without reentrancy, we must have a QC or TC for the prior rank (curRank-1).
// Failing one of these sanity checks is a symptom of state corruption or a
// severe implementation bug.
if newestQC.GetRank()+1 != curRank {
if previousRankTimeoutCert == nil {
return fmt.Errorf("possible state corruption, expected previousRankTimeoutCert to be not nil")
}
if previousRankTimeoutCert.GetRank()+1 != curRank {
return fmt.Errorf(
"possible state corruption, don't have QC(rank=%d) and TC(rank=%d) for previous rank(currentRank=%d)",
newestQC.GetRank(),
previousRankTimeoutCert.GetRank(),
curRank,
)
}
} else {
// In case last rank has ended with QC and TC, make sure that only QC is
// included, otherwise such proposal is invalid. This case is possible if TC
// has included QC with the same rank as the TC itself, meaning that
// newestQC.Rank == previousRankTimeoutCert.Rank
previousRankTimeoutCert = nil
}
// Construct Own SignedProposal
// CAUTION, design constraints:
// (i) We cannot process our own proposal within the `EventHandler` right
// away.
// (ii) We cannot add our own proposal to Forks here right away.
// (iii) Metrics for the PaceMaker/CruiseControl assume that the EventHandler
// is the only caller of `TargetPublicationTime`. Technically,
// `TargetPublicationTime` records the publication delay relative to
// its _latest_ call.
//
// To satisfy all constraints, we construct the proposal here and query
// (once!) its `TargetPublicationTime`. Though, we do _not_ process our own
// states right away and instead ingest them into the EventHandler the same
// way as proposals from other consensus participants. Specifically, on the
// path through the HotStuff state machine leading to state construction, the
// node's own proposal is largely ephemeral. The proposal is handed to the
// `MessageHub` (via the `OnOwnProposal` notification including the
// `TargetPublicationTime`). The `MessageHub` waits until
// `TargetPublicationTime` and only then broadcast the proposal and puts it
// into the EventLoop's queue for inbound states. This is exactly the same way
// as proposals from other nodes are ingested by the `EventHandler`, except
// that we are skipping the ComplianceEngine (assuming that our own proposals
// are protocol-compliant).
//
// Context:
// • On constraint (i): We want to support consensus committees only
// consisting of a *single* node. If the EventHandler internally processed
// the state right away via a direct message call, the call-stack would be
// ever-growing and the node would crash eventually (we experienced this
// with a very early HotStuff implementation). Specifically, if we wanted
// to process the state directly without taking a detour through the
// EventLoop's inbound queue, we would call `OnReceiveProposal` here. The
// function `OnReceiveProposal` would then end up calling
// `proposeForNewRankIfPrimary` (this function) to generate the next
// proposal, which again would result in calling `OnReceiveProposal` and so
// on so forth until the call stack or memory limit is reached and the node
// crashes. This is only a problem for consensus committees of size 1.
// • On constraint (ii): When adding a proposal to Forks, Forks emits a
// `StateIncorporatedEvent` notification, which is observed by Cruise
// Control and would change its state. However, note that Cruise Control
// is trying to estimate the point in time when _other_ nodes are observing
// the proposal. The time when we broadcast the proposal (i.e.
// `TargetPublicationTime`) is a reasonably good estimator, but *not* the
// time the proposer constructed the state (because there is potentially
// still a significant wait until `TargetPublicationTime`).
//
// The current approach is for a node to process its own proposals at the same
// time and through the same code path as proposals from other nodes. This
// satisfies constraints (i) and (ii) and generates very strong consistency,
// from a software design perspective.
// Just hypothetically, if we changed Cruise Control to be notified about
// own state proposals _only_ when they are broadcast (satisfying constraint
// (ii) without relying on the EventHandler), then we could add a proposal to
// Forks here right away. Nevertheless, the restriction remains that we cannot
// process that proposal right away within the EventHandler and instead need
// to put it into the EventLoop's inbound queue to support consensus
// committees of size 1.
stateProposal, err := e.stateProducer.MakeStateProposal(
curRank,
newestQC,
previousRankTimeoutCert,
)
if err != nil {
if models.IsNoVoteError(err) {
e.tracer.Error(
"aborting state proposal to prevent equivocation (likely re-entered proposal logic due to crash)",
err,
)
return nil
}
return fmt.Errorf(
"can not make state proposal for curRank %v: %w",
curRank,
err,
)
}
targetPublicationTime := e.paceMaker.TargetPublicationTime(
stateProposal.State.Rank,
start,
stateProposal.State.ParentQuorumCertificate.GetSelector(),
) // determine target publication time
e.tracer.Trace("forwarding proposal to communicator for broadcasting")
// emit notification with own proposal (also triggers broadcast)
e.notifier.OnOwnProposal(stateProposal, targetPublicationTime)
return nil
}
// processStateForCurrentRank processes the state for the current rank.
// It is called AFTER the state has been stored or found in Forks
// It checks whether to vote for this state.
// No errors are expected during normal operation.
func (e *EventHandler[
StateT,
VoteT,
PeerIDT,
CollectedT,
]) processStateForCurrentRank(
proposal *models.SignedProposal[StateT, VoteT],
) error {
// sanity check that state is really for the current rank:
curRank := e.paceMaker.CurrentRank()
state := proposal.State
if state.Rank != curRank {
// ignore outdated proposals in case we have moved forward
return nil
}
// leader (node ID) for next rank
nextLeader, err := e.committee.LeaderForRank(curRank + 1)
if errors.Is(err, models.ErrRankUnknown) {
// We are attempting process a state in an unknown rank
// This should never happen, because:
// * the compliance layer ensures proposals are passed to the event loop
// strictly after their parent
// * the protocol state ensures that, before incorporating the first state
// of an rank R, either R is known or we have triggered fallback mode - in
// either case the current rank is known
return fmt.Errorf("attempting to process a state for unknown rank")
}
if err != nil {
return fmt.Errorf(
"failed to determine primary for next rank %d: %w",
curRank+1,
err,
)
}
// safetyRules performs all the checks to decide whether to vote for this
// state or not.
err = e.ownVote(proposal, curRank, nextLeader)
if err != nil {
return fmt.Errorf("unexpected error in voting logic: %w", err)
}
return nil
}
// ownVote generates and forwards the own vote, if we decide to vote.
// Any errors are potential symptoms of uncovered edge cases or corrupted
// internal state (fatal). No errors are expected during normal operation.
func (e *EventHandler[
StateT,
VoteT,
PeerIDT,
CollectedT,
]) ownVote(
proposal *models.SignedProposal[StateT, VoteT],
curRank uint64,
nextLeader models.Identity,
) error {
_, found := e.forks.GetState(
proposal.State.ParentQuorumCertificate.GetSelector(),
)
if !found {
// we don't have parent for this proposal, we can't vote since we can't
// guarantee validity of proposals payload. Strictly speaking this shouldn't
// ever happen because compliance engine makes sure that we receive
// proposals with valid parents.
return fmt.Errorf(
"won't vote for proposal, no parent state for this proposal",
)
}
// safetyRules performs all the checks to decide whether to vote for this
// state or not.
ownVote, err := e.safetyRules.ProduceVote(proposal, curRank)
if err != nil {
if !models.IsNoVoteError(err) {
// unknown error, exit the event loop
return fmt.Errorf("could not produce vote: %w", err)
}
e.tracer.Trace("should not vote for this state")
return nil
}
e.tracer.Trace("forwarding vote to compliance engine")
e.notifier.OnOwnVote(ownVote, nextLeader)
return nil
}
// Type used to satisfy generic arguments in compiler time type assertion check
type nilUnique struct{}
// GetSignature implements models.Unique.
func (n *nilUnique) GetSignature() []byte {
panic("unimplemented")
}
// GetTimestamp implements models.Unique.
func (n *nilUnique) GetTimestamp() uint64 {
panic("unimplemented")
}
// Source implements models.Unique.
func (n *nilUnique) Source() models.Identity {
panic("unimplemented")
}
// Clone implements models.Unique.
func (n *nilUnique) Clone() models.Unique {
panic("unimplemented")
}
// GetRank implements models.Unique.
func (n *nilUnique) GetRank() uint64 {
panic("unimplemented")
}
// Identity implements models.Unique.
func (n *nilUnique) Identity() models.Identity {
panic("unimplemented")
}
var _ models.Unique = (*nilUnique)(nil)

View File

@ -0,0 +1,346 @@
package eventloop
import (
"context"
"fmt"
"time"
"source.quilibrium.com/quilibrium/monorepo/consensus"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
"source.quilibrium.com/quilibrium/monorepo/consensus/tracker"
)
// queuedProposal is a helper structure that is used to transmit proposal in
// channel it contains an attached insertionTime that is used to measure how
// long we have waited between queening proposal and actually processing by
// `EventHandler`.
type queuedProposal[StateT models.Unique, VoteT models.Unique] struct {
proposal *models.SignedProposal[StateT, VoteT]
insertionTime time.Time
}
// EventLoop buffers all incoming events to the hotstuff EventHandler, and feeds
// EventHandler one event at a time.
type EventLoop[StateT models.Unique, VoteT models.Unique] struct {
ctx context.Context
eventHandler consensus.EventHandler[StateT, VoteT]
proposals chan queuedProposal[StateT, VoteT]
newestSubmittedTc *tracker.NewestTCTracker
newestSubmittedQc *tracker.NewestQCTracker
newestSubmittedPartialTc *tracker.NewestPartialTcTracker
tcSubmittedNotifier chan struct{}
qcSubmittedNotifier chan struct{}
partialTcCreatedNotifier chan struct{}
startTime time.Time
tracer consensus.TraceLogger
}
var _ consensus.EventLoop[*nilUnique, *nilUnique] = (*EventLoop[*nilUnique, *nilUnique])(nil)
// NewEventLoop creates an instance of EventLoop.
func NewEventLoop[StateT models.Unique, VoteT models.Unique](
tracer consensus.TraceLogger,
eventHandler consensus.EventHandler[StateT, VoteT],
startTime time.Time,
) (*EventLoop[StateT, VoteT], error) {
// we will use a buffered channel to avoid blocking of caller
// we can't afford to drop messages since it undermines liveness, but we also
// want to avoid blocking of compliance engine. We assume that we should be
// able to process proposals faster than compliance engine feeds them, worst
// case we will fill the buffer and state compliance engine worker but that
// should happen only if compliance engine receives large number of states in
// short period of time(when catching up for instance).
proposals := make(chan queuedProposal[StateT, VoteT], 1000)
el := &EventLoop[StateT, VoteT]{
tracer: tracer,
eventHandler: eventHandler,
proposals: proposals,
tcSubmittedNotifier: make(chan struct{}, 1),
qcSubmittedNotifier: make(chan struct{}, 1),
partialTcCreatedNotifier: make(chan struct{}, 1),
newestSubmittedTc: tracker.NewNewestTCTracker(),
newestSubmittedQc: tracker.NewNewestQCTracker(),
newestSubmittedPartialTc: tracker.NewNewestPartialTcTracker(),
startTime: startTime,
}
return el, nil
}
func (el *EventLoop[StateT, VoteT]) Start(ctx context.Context) error {
el.ctx = ctx
select {
case <-ctx.Done():
return nil
case <-time.After(time.Until(el.startTime)):
el.tracer.Trace("starting event loop")
err := el.loop(ctx)
if err != nil {
el.tracer.Error("irrecoverable event loop error", err)
return err
}
}
return nil
}
// loop executes the core HotStuff logic in a single thread. It picks inputs
// from the various inbound channels and executes the EventHandler's respective
// method for processing this input. During normal operations, the EventHandler
// is not expected to return any errors, as all inputs are assumed to be fully
// validated (or produced by trusted components within the node). Therefore,
// any error is a symptom of state corruption, bugs or violation of API
// contracts. In all cases, continuing operations is not an option, i.e. we exit
// the event loop and return an exception.
func (el *EventLoop[StateT, VoteT]) loop(ctx context.Context) error {
err := el.eventHandler.Start(ctx)
if err != nil {
return fmt.Errorf("could not start event handler: %w", err)
}
shutdownSignaled := ctx.Done()
timeoutCertificates := el.tcSubmittedNotifier
quorumCertificates := el.qcSubmittedNotifier
partialTCs := el.partialTcCreatedNotifier
for {
// Giving timeout events the priority to be processed first.
// This is to prevent attacks from malicious nodes that attempt
// to block honest nodes' pacemaker from progressing by sending
// other events.
timeoutChannel := el.eventHandler.TimeoutChannel()
// the first select makes sure we process timeouts with priority
select {
// if we receive the shutdown signal, exit the loop
case <-shutdownSignaled:
return nil
// processing timeout or partial TC event are top priority since
// they allow node to contribute to TC aggregation when replicas can't
// make progress on happy path
case <-timeoutChannel:
err = el.eventHandler.OnLocalTimeout()
if err != nil {
return fmt.Errorf("could not process timeout: %w", err)
}
// At this point, we have received and processed an event from the timeout
// channel. A timeout also means that we have made progress. A new timeout
// will have been started and el.eventHandler.TimeoutChannel() will be a
// NEW channel (for the just-started timeout). Very important to start the
// for loop from the beginning, to continue the with the new timeout
// channel!
continue
case <-partialTCs:
err = el.eventHandler.OnPartialTimeoutCertificateCreated(
el.newestSubmittedPartialTc.NewestPartialTc(),
)
if err != nil {
return fmt.Errorf("could not process partial created TC event: %w", err)
}
// At this point, we have received and processed partial TC event, it
// could have resulted in several scenarios:
// 1. a rank change with potential voting or proposal creation
// 2. a created and broadcast timeout state
// 3. QC and TC didn't result in rank change and no timeout was created
// since we have already timed out or the partial TC was created for rank
// different from current one.
continue
default:
// fall through to non-priority events
}
// select for state headers/QCs here
select {
// same as before
case <-shutdownSignaled:
return nil
// same as before
case <-timeoutChannel:
err = el.eventHandler.OnLocalTimeout()
if err != nil {
return fmt.Errorf("could not process timeout: %w", err)
}
// if we have a new proposal, process it
case queuedItem := <-el.proposals:
proposal := queuedItem.proposal
err = el.eventHandler.OnReceiveProposal(proposal)
if err != nil {
return fmt.Errorf(
"could not process proposal %v: %w",
proposal.State.Identifier,
err,
)
}
el.tracer.Trace("state proposal has been processed successfully")
// if we have a new QC, process it
case <-quorumCertificates:
err = el.eventHandler.OnReceiveQuorumCertificate(
*el.newestSubmittedQc.NewestQC(),
)
if err != nil {
return fmt.Errorf("could not process QC: %w", err)
}
// if we have a new TC, process it
case <-timeoutCertificates:
err = el.eventHandler.OnReceiveTimeoutCertificate(
*el.newestSubmittedTc.NewestTC(),
)
if err != nil {
return fmt.Errorf("could not process TC: %w", err)
}
case <-partialTCs:
err = el.eventHandler.OnPartialTimeoutCertificateCreated(
el.newestSubmittedPartialTc.NewestPartialTc(),
)
if err != nil {
return fmt.Errorf("could no process partial created TC event: %w", err)
}
}
}
}
// SubmitProposal pushes the received state to the proposals channel
func (el *EventLoop[StateT, VoteT]) SubmitProposal(
proposal *models.SignedProposal[StateT, VoteT],
) {
queueItem := queuedProposal[StateT, VoteT]{
proposal: proposal,
insertionTime: time.Now(),
}
select {
case el.proposals <- queueItem:
case <-el.ctx.Done():
return
}
}
// onTrustedQC pushes the received QC (which MUST be validated) to the
// quorumCertificates channel
func (el *EventLoop[StateT, VoteT]) onTrustedQC(qc *models.QuorumCertificate) {
if el.newestSubmittedQc.Track(qc) {
el.qcSubmittedNotifier <- struct{}{}
}
}
// onTrustedTC pushes the received TC (which MUST be validated) to the
// timeoutCertificates channel
func (el *EventLoop[StateT, VoteT]) onTrustedTC(tc *models.TimeoutCertificate) {
if el.newestSubmittedTc.Track(tc) {
el.tcSubmittedNotifier <- struct{}{}
} else {
qc := (*tc).GetLatestQuorumCert()
if el.newestSubmittedQc.Track(&qc) {
el.qcSubmittedNotifier <- struct{}{}
}
}
}
// OnTimeoutCertificateConstructedFromTimeouts pushes the received TC to the
// timeoutCertificates channel
func (el *EventLoop[StateT, VoteT]) OnTimeoutCertificateConstructedFromTimeouts(
tc models.TimeoutCertificate,
) {
el.onTrustedTC(&tc)
}
// OnPartialTimeoutCertificateCreated created a consensus.PartialTcCreated
// payload and pushes it into partialTcCreated buffered channel for further
// processing by EventHandler. Since we use buffered channel this function can
// block if buffer is full.
func (el *EventLoop[StateT, VoteT]) OnPartialTimeoutCertificateCreated(
rank uint64,
newestQC models.QuorumCertificate,
previousRankTimeoutCert models.TimeoutCertificate,
) {
event := &consensus.PartialTimeoutCertificateCreated{
Rank: rank,
NewestQuorumCertificate: newestQC,
PriorRankTimeoutCertificate: previousRankTimeoutCert,
}
if el.newestSubmittedPartialTc.Track(event) {
el.partialTcCreatedNotifier <- struct{}{}
}
}
// OnNewQuorumCertificateDiscovered pushes already validated QCs that were
// submitted from TimeoutAggregator to the event handler
func (el *EventLoop[StateT, VoteT]) OnNewQuorumCertificateDiscovered(
qc models.QuorumCertificate,
) {
el.onTrustedQC(&qc)
}
// OnNewTimeoutCertificateDiscovered pushes already validated TCs that were
// submitted from TimeoutAggregator to the event handler
func (el *EventLoop[StateT, VoteT]) OnNewTimeoutCertificateDiscovered(
tc models.TimeoutCertificate,
) {
el.onTrustedTC(&tc)
}
// OnQuorumCertificateConstructedFromVotes implements
// consensus.VoteCollectorConsumer and pushes received qc into processing
// pipeline.
func (el *EventLoop[StateT, VoteT]) OnQuorumCertificateConstructedFromVotes(
qc models.QuorumCertificate,
) {
el.onTrustedQC(&qc)
}
// OnTimeoutProcessed implements consensus.TimeoutCollectorConsumer and is no-op
func (el *EventLoop[StateT, VoteT]) OnTimeoutProcessed(
timeout *models.TimeoutState[VoteT],
) {
}
// OnVoteProcessed implements consensus.VoteCollectorConsumer and is no-op
func (el *EventLoop[StateT, VoteT]) OnVoteProcessed(vote *VoteT) {}
// Type used to satisfy generic arguments in compiler time type assertion check
type nilUnique struct{}
// GetSignature implements models.Unique.
func (n *nilUnique) GetSignature() []byte {
panic("unimplemented")
}
// GetTimestamp implements models.Unique.
func (n *nilUnique) GetTimestamp() uint64 {
panic("unimplemented")
}
// Source implements models.Unique.
func (n *nilUnique) Source() models.Identity {
panic("unimplemented")
}
// Clone implements models.Unique.
func (n *nilUnique) Clone() models.Unique {
panic("unimplemented")
}
// GetRank implements models.Unique.
func (n *nilUnique) GetRank() uint64 {
panic("unimplemented")
}
// Identity implements models.Unique.
func (n *nilUnique) Identity() models.Identity {
panic("unimplemented")
}
var _ models.Unique = (*nilUnique)(nil)

View File

@ -0,0 +1,394 @@
package forest
import (
"fmt"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// LevelledForest contains multiple trees (which is a potentially disconnected
// planar graph). Each vertex in the graph has a level and a hash. A vertex can
// only have one parent, which must have strictly smaller level. A vertex can
// have multiple children, all with strictly larger level.
// A LevelledForest provides the ability to prune all vertices up to a specific
// level. A tree whose root is below the pruning threshold might decompose into
// multiple disconnected subtrees as a result of pruning.
// By design, the LevelledForest does _not_ touch the parent information for
// vertices that are on the lowest retained level. Thereby, it is possible to
// initialize the LevelledForest with a root vertex at the lowest retained
// level, without this root needing to have a parent. Furthermore, the root
// vertex can be at level 0 and in absence of a parent still satisfy the
// condition that any parent must be of lower level (mathematical principle of
// acuous truth) without the implementation needing to worry about unsigned
// integer underflow.
//
// LevelledForest is NOT safe for concurrent use by multiple goroutines.
type LevelledForest struct {
vertices VertexSet
verticesAtLevel map[uint64]VertexList
size uint64
LowestLevel uint64
}
type VertexList []*vertexContainer
type VertexSet map[models.Identity]*vertexContainer
// vertexContainer holds information about a tree vertex. Internally, we
// distinguish between
// - FULL container: has non-nil value for vertex.
// Used for vertices, which have been added to the tree.
// - EMPTY container: has NIL value for vertex.
// Used for vertices, which have NOT been added to the tree, but are
// referenced by vertices in the tree. An empty container is converted to a
// full container when the respective vertex is added to the tree
type vertexContainer struct {
id models.Identity
level uint64
children VertexList
// the following are only set if the state is actually known
vertex Vertex
}
// NewLevelledForest initializes a LevelledForest
func NewLevelledForest(lowestLevel uint64) *LevelledForest {
return &LevelledForest{
vertices: make(VertexSet),
verticesAtLevel: make(map[uint64]VertexList),
LowestLevel: lowestLevel,
}
}
// PruneUpToLevel prunes all states UP TO but NOT INCLUDING `level`.
func (f *LevelledForest) PruneUpToLevel(level uint64) error {
if level < f.LowestLevel {
return fmt.Errorf(
"new lowest level %d cannot be smaller than previous last retained level %d",
level,
f.LowestLevel,
)
}
if len(f.vertices) == 0 {
f.LowestLevel = level
return nil
}
elementsPruned := 0
// to optimize the pruning large level-ranges, we compare:
// * the number of levels for which we have stored vertex containers:
// len(f.verticesAtLevel)
// * the number of levels that need to be pruned: level-f.LowestLevel
// We iterate over the dimension which is smaller.
if uint64(len(f.verticesAtLevel)) < level-f.LowestLevel {
for l, vertices := range f.verticesAtLevel {
if l < level {
for _, v := range vertices {
if !f.isEmptyContainer(v) {
elementsPruned++
}
delete(f.vertices, v.id)
}
delete(f.verticesAtLevel, l)
}
}
} else {
for l := f.LowestLevel; l < level; l++ {
verticesAtLevel := f.verticesAtLevel[l]
for _, v := range verticesAtLevel {
if !f.isEmptyContainer(v) {
elementsPruned++
}
delete(f.vertices, v.id)
}
delete(f.verticesAtLevel, l)
}
}
f.LowestLevel = level
f.size -= uint64(elementsPruned)
return nil
}
// HasVertex returns true iff full vertex exists.
func (f *LevelledForest) HasVertex(id models.Identity) bool {
container, exists := f.vertices[id]
return exists && !f.isEmptyContainer(container)
}
// isEmptyContainer returns true iff vertexContainer container is empty, i.e.
// full vertex itself has not been added
func (f *LevelledForest) isEmptyContainer(
vertexContainer *vertexContainer,
) bool {
return vertexContainer.vertex == nil
}
// GetVertex returns (<full vertex>, true) if the vertex with `id` and `level`
// was found (nil, false) if full vertex is unknown
func (f *LevelledForest) GetVertex(id models.Identity) (Vertex, bool) {
container, exists := f.vertices[id]
if !exists || f.isEmptyContainer(container) {
return nil, false
}
return container.vertex, true
}
// GetSize returns the total number of vertices above the lowest pruned level.
// Note this call is not concurrent-safe, caller is responsible to ensure
// concurrency safety.
func (f *LevelledForest) GetSize() uint64 {
return f.size
}
// GetChildren returns a VertexIterator to iterate over the children
// An empty VertexIterator is returned, if no vertices are known whose parent is
// `id`.
func (f *LevelledForest) GetChildren(id models.Identity) VertexIterator {
// if vertex does not exist, container will be nil
if container, ok := f.vertices[id]; ok {
return newVertexIterator(container.children)
}
return newVertexIterator(nil) // VertexIterator gracefully handles nil slices
}
// GetNumberOfChildren returns number of children of given vertex
func (f *LevelledForest) GetNumberOfChildren(id models.Identity) int {
// if vertex does not exist, container is the default zero value for
// vertexContainer, which contains a nil-slice for its children
container := f.vertices[id]
num := 0
for _, child := range container.children {
if child.vertex != nil {
num++
}
}
return num
}
// GetVerticesAtLevel returns a VertexIterator to iterate over the Vertices at
// the specified level. An empty VertexIterator is returned, if no vertices are
// known at the specified level. If `level` is already pruned, an empty
// VertexIterator is returned.
func (f *LevelledForest) GetVerticesAtLevel(level uint64) VertexIterator {
return newVertexIterator(f.verticesAtLevel[level])
}
// GetNumberOfVerticesAtLevel returns the number of full vertices at given
// level. A full vertex is a vertex that was explicitly added to the forest. In
// contrast, an empty vertex container represents a vertex that is _referenced_
// as parent by one or more full vertices, but has not been added itself to the
// forest. We only count vertices that have been explicitly added to the forest
// and not yet pruned. (In comparision, we do _not_ count vertices that are
// _referenced_ as parent by vertices, but have not been added themselves).
func (f *LevelledForest) GetNumberOfVerticesAtLevel(level uint64) int {
num := 0
for _, container := range f.verticesAtLevel[level] {
if !f.isEmptyContainer(container) {
num++
}
}
return num
}
// AddVertex adds vertex to forest if vertex is within non-pruned levels
// Handles repeated addition of same vertex (keeps first added vertex).
// If vertex is at or below pruning level: method is NoOp.
// UNVALIDATED:
// requires that vertex would pass validity check LevelledForest.VerifyVertex(vertex).
func (f *LevelledForest) AddVertex(vertex Vertex) {
if vertex.Level() < f.LowestLevel {
return
}
container := f.getOrCreateVertexContainer(vertex.VertexID(), vertex.Level())
if !f.isEmptyContainer(container) { // the vertex was already stored
return
}
// container is empty, i.e. full vertex is new and should be stored in container
container.vertex = vertex // add vertex to container
f.registerWithParent(container)
f.size += 1
}
// registerWithParent retrieves the parent and registers the given vertex as a
// child. For a state, whose level equal to the pruning threshold, we do not
// inspect the parent at all. Thereby, this implementation can gracefully handle
// the corner case where the tree has a defined end vertex (distinct root). This
// is commonly the case in statechain (genesis, or spork root state).
// Mathematically, this means that this library can also represent bounded
// trees.
func (f *LevelledForest) registerWithParent(vertexContainer *vertexContainer) {
// caution, necessary for handling bounded trees:
// For root vertex (genesis state) the rank is _exactly_ at LowestLevel. For
// these states, a parent does not exist. In the implementation, we
// deliberately do not call the `Parent()` method, as its output is
// conceptually undefined. Thereby, we can gracefully handle the corner case
// of
// vertex.level = vertex.Parent().Level = LowestLevel = 0
if vertexContainer.level <= f.LowestLevel { // check (a)
return
}
_, parentView := vertexContainer.vertex.Parent()
if parentView < f.LowestLevel {
return
}
parentContainer := f.getOrCreateVertexContainer(
vertexContainer.vertex.Parent(),
)
parentContainer.children = append(parentContainer.children, vertexContainer)
}
// getOrCreateVertexContainer returns the vertexContainer if there exists one
// or creates a new vertexContainer and adds it to the internal data structures.
// (i.e. there exists an empty or full container with the same id but different
// level).
func (f *LevelledForest) getOrCreateVertexContainer(
id models.Identity,
level uint64,
) *vertexContainer {
container, exists := f.vertices[id]
if !exists {
container = &vertexContainer{
id: id,
level: level,
}
f.vertices[container.id] = container
vertices := f.verticesAtLevel[container.level]
f.verticesAtLevel[container.level] = append(vertices, container)
}
return container
}
// VerifyVertex verifies that adding vertex `v` would yield a valid Levelled
// Forest. Specifically, we verify that _all_ of the following conditions are
// satisfied:
//
// 1. `v.Level()` must be strictly larger than the level that `v` reports
// for its parent (maintains an acyclic graph).
//
// 2. If a vertex with the same ID as `v.VertexID()` exists in the graph or is
// referenced by another vertex within the graph, the level must be
// identical. (In other words, we don't have vertices with the same ID but
// different level)
//
// 3. Let `ParentLevel`, `ParentID` denote the level, ID that `v` reports for
// its parent. If a vertex with `ParentID` exists (or is referenced by other
// vertices as their parent), we require that the respective level is
// identical to `ParentLevel`.
//
// Notes:
// - If `v.Level()` has already been pruned, adding it to the forest is a
// NoOp. Hence, any vertex with level below the pruning threshold
// automatically passes.
// - By design, the LevelledForest does _not_ touch the parent information for
// vertices that are on the lowest retained level. Thereby, it is possible
// to initialize the LevelledForest with a root vertex at the lowest
// retained level, without this root needing to have a parent. Furthermore,
// the root vertex can be at level 0 and in absence of a parent still
// satisfy the condition that any parent must be of lower level
// (mathematical principle of vacuous truth) without the implementation
// needing to worry about unsigned integer underflow.
//
// Error returns:
// - InvalidVertexError if the input vertex is invalid for insertion to the
// forest.
func (f *LevelledForest) VerifyVertex(v Vertex) error {
if v.Level() < f.LowestLevel {
return nil
}
storedContainer, haveVertexContainer := f.vertices[v.VertexID()]
if !haveVertexContainer { // have no vertex with same id stored
// the only thing remaining to check is the parent information
return f.ensureConsistentParent(v)
}
// Found a vertex container, i.e. `v` already exists, or it is referenced by
// some other vertex. In all cases, `v.Level()` should match the
// vertexContainer's information
if v.Level() != storedContainer.level {
return NewInvalidVertexErrorf(
v,
"level conflicts with stored vertex with same id (%d!=%d)",
v.Level(),
storedContainer.level,
)
}
// vertex container is empty, i.e. `v` is referenced by some other vertex as
// its parent:
if f.isEmptyContainer(storedContainer) {
// the only thing remaining to check is the parent information
return f.ensureConsistentParent(v)
}
// vertex container holds a vertex with the same ID as `v`:
// The parent information from vertexContainer has already been checked for
// consistency. So we simply compare with the existing vertex for
// inconsistencies
// the vertex is at or below the lowest retained level, so we can't check the
// parent (it's pruned)
if v.Level() == f.LowestLevel {
return nil
}
newParentId, newParentLevel := v.Parent()
storedParentId, storedParentLevel := storedContainer.vertex.Parent()
if newParentId != storedParentId {
return NewInvalidVertexErrorf(
v,
"parent ID conflicts with stored parent (%x!=%x)",
newParentId,
storedParentId,
)
}
if newParentLevel != storedParentLevel {
return NewInvalidVertexErrorf(
v,
"parent level conflicts with stored parent (%d!=%d)",
newParentLevel,
storedParentLevel,
)
}
// all _relevant_ fields identical
return nil
}
// ensureConsistentParent verifies that vertex.Parent() is consistent with
// current forest.
// Returns InvalidVertexError if:
// * there is a parent with the same ID but different level;
// * the parent's level is _not_ smaller than the vertex's level
func (f *LevelledForest) ensureConsistentParent(vertex Vertex) error {
if vertex.Level() <= f.LowestLevel {
// the vertex is at or below the lowest retained level, so we can't check
// the parent (it's pruned)
return nil
}
// verify parent
parentID, parentLevel := vertex.Parent()
if !(vertex.Level() > parentLevel) {
return NewInvalidVertexErrorf(
vertex,
"vertex parent level (%d) must be smaller than proposed vertex level (%d)",
parentLevel,
vertex.Level(),
)
}
storedParent, haveParentStored := f.GetVertex(parentID)
if !haveParentStored {
return nil
}
if storedParent.Level() != parentLevel {
return NewInvalidVertexErrorf(
vertex,
"parent level conflicts with stored parent (%d!=%d)",
parentLevel,
storedParent.Level(),
)
}
return nil
}

103
consensus/forest/vertex.go Normal file
View File

@ -0,0 +1,103 @@
package forest
import (
"errors"
"fmt"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
type Vertex interface {
// VertexID returns the vertex's ID (in most cases its hash)
VertexID() models.Identity
// Level returns the vertex's level
Level() uint64
// Parent returns the parent's (level, ID)
Parent() (models.Identity, uint64)
}
// VertexToString returns a string representation of the vertex.
func VertexToString(v Vertex) string {
parentID, parentLevel := v.Parent()
return fmt.Sprintf(
"<id=%x level=%d parent_id=%d parent_level=%d>",
v.VertexID(),
v.Level(),
parentID,
parentLevel,
)
}
// VertexIterator is a stateful iterator for VertexList.
// Internally operates directly on the Vertex Containers
// It has one-element look ahead for skipping empty vertex containers.
type VertexIterator struct {
data VertexList
idx int
next Vertex
}
func (it *VertexIterator) preLoad() {
for it.idx < len(it.data) {
v := it.data[it.idx].vertex
it.idx++
if v != nil {
it.next = v
return
}
}
it.next = nil
}
// NextVertex returns the next Vertex or nil if there is none
func (it *VertexIterator) NextVertex() Vertex {
res := it.next
it.preLoad()
return res
}
// HasNext returns true if and only if there is a next Vertex
func (it *VertexIterator) HasNext() bool {
return it.next != nil
}
func newVertexIterator(vertexList VertexList) VertexIterator {
it := VertexIterator{
data: vertexList,
}
it.preLoad()
return it
}
// InvalidVertexError indicates that a proposed vertex is invalid for insertion
// to the forest.
type InvalidVertexError struct {
// Vertex is the invalid vertex
Vertex Vertex
// msg provides additional context
msg string
}
func (err InvalidVertexError) Error() string {
return fmt.Sprintf(
"invalid vertex %s: %s",
VertexToString(err.Vertex),
err.msg,
)
}
func IsInvalidVertexError(err error) bool {
var target InvalidVertexError
return errors.As(err, &target)
}
func NewInvalidVertexErrorf(
vertex Vertex,
msg string,
args ...interface{},
) InvalidVertexError {
return InvalidVertexError{
Vertex: vertex,
msg: fmt.Sprintf(msg, args...),
}
}

688
consensus/forks/forks.go Normal file
View File

@ -0,0 +1,688 @@
package forks
import (
"fmt"
"source.quilibrium.com/quilibrium/monorepo/consensus"
"source.quilibrium.com/quilibrium/monorepo/consensus/forest"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// Forks enforces structural validity of the consensus state and implements
// finalization rules as defined in Jolteon consensus
// https://arxiv.org/abs/2106.10362 The same approach has later been adopted by
// the Diem team resulting in DiemBFT v4:
// https://developers.diem.com/papers/diem-consensus-state-machine-replication-in-the-diem-blockchain/2021-08-17.pdf
// Forks is NOT safe for concurrent use by multiple goroutines.
type Forks[StateT models.Unique, VoteT models.Unique] struct {
finalizationCallback consensus.Finalizer
notifier consensus.FollowerConsumer[StateT, VoteT]
forest forest.LevelledForest
trustedRoot *models.CertifiedState[StateT]
// finalityProof holds the latest finalized state including the certified
// child as proof of finality. CAUTION: is nil, when Forks has not yet
// finalized any states beyond the finalized root state it was initialized
// with
finalityProof *consensus.FinalityProof[StateT]
}
var _ consensus.Forks[*nilUnique] = (*Forks[*nilUnique, *nilUnique])(nil)
func NewForks[StateT models.Unique, VoteT models.Unique](
trustedRoot *models.CertifiedState[StateT],
finalizationCallback consensus.Finalizer,
notifier consensus.FollowerConsumer[StateT, VoteT],
) (*Forks[StateT, VoteT], error) {
if (trustedRoot.State.Identifier != trustedRoot.CertifyingQuorumCertificate.GetSelector()) ||
(trustedRoot.State.Rank != trustedRoot.CertifyingQuorumCertificate.GetRank()) {
return nil,
models.NewConfigurationErrorf(
"invalid root: root QC is not pointing to root state",
)
}
forks := Forks[StateT, VoteT]{
finalizationCallback: finalizationCallback,
notifier: notifier,
forest: *forest.NewLevelledForest(trustedRoot.State.Rank),
trustedRoot: trustedRoot,
finalityProof: nil,
}
// verify and add root state to levelled forest
err := forks.EnsureStateIsValidExtension(trustedRoot.State)
if err != nil {
return nil, fmt.Errorf(
"invalid root state %v: %w",
trustedRoot.Identifier(),
err,
)
}
forks.forest.AddVertex(ToStateContainer2[StateT](trustedRoot.State))
return &forks, nil
}
// FinalizedRank returns the largest rank number where a finalized state is
// known
func (f *Forks[StateT, VoteT]) FinalizedRank() uint64 {
if f.finalityProof == nil {
return f.trustedRoot.State.Rank
}
return f.finalityProof.State.Rank
}
// FinalizedState returns the finalized state with the largest rank number
func (f *Forks[StateT, VoteT]) FinalizedState() *models.State[StateT] {
if f.finalityProof == nil {
return f.trustedRoot.State
}
return f.finalityProof.State
}
// FinalityProof returns the latest finalized state and a certified child from
// the subsequent rank, which proves finality.
// CAUTION: method returns (nil, false), when Forks has not yet finalized any
// states beyond the finalized root state it was initialized with.
func (f *Forks[StateT, VoteT]) FinalityProof() (
*consensus.FinalityProof[StateT],
bool,
) {
return f.finalityProof, f.finalityProof != nil
}
// GetState returns (*models.State, true) if the state with the specified
// id was found and (nil, false) otherwise.
func (f *Forks[StateT, VoteT]) GetState(stateID models.Identity) (
*models.State[StateT],
bool,
) {
stateContainer, hasState := f.forest.GetVertex(stateID)
if !hasState {
return nil, false
}
return stateContainer.(*StateContainer[StateT]).GetState(), true
}
// GetStatesForRank returns all known states for the given rank
func (f *Forks[StateT, VoteT]) GetStatesForRank(
rank uint64,
) []*models.State[StateT] {
vertexIterator := f.forest.GetVerticesAtLevel(rank)
// in the vast majority of cases, there will only be one proposal for a
// particular rank
states := make([]*models.State[StateT], 0, 1)
for vertexIterator.HasNext() {
v := vertexIterator.NextVertex()
states = append(states, v.(*StateContainer[StateT]).GetState())
}
return states
}
// IsKnownState checks whether state is known.
func (f *Forks[StateT, VoteT]) IsKnownState(stateID models.Identity) bool {
_, hasState := f.forest.GetVertex(stateID)
return hasState
}
// IsProcessingNeeded determines whether the given state needs processing,
// based on the state's rank and hash.
// Returns false if any of the following conditions applies
// - state rank is _below_ the most recently finalized state
// - the state already exists in the consensus state
//
// UNVALIDATED: expects state to pass Forks.EnsureStateIsValidExtension(state)
func (f *Forks[StateT, VoteT]) IsProcessingNeeded(state *models.State[StateT]) bool {
if state.Rank < f.FinalizedRank() || f.IsKnownState(state.Identifier) {
return false
}
return true
}
// EnsureStateIsValidExtension checks that the given state is a valid extension
// to the tree of states already stored (no state modifications). Specifically,
// the following conditions are enforced, which are critical to the correctness
// of Forks:
//
// 1. If a state with the same ID is already stored, their ranks must be
// identical.
// 2. The state's rank must be strictly larger than the rank of its parent.
// 3. The parent must already be stored (or below the pruning height).
//
// Exclusions to these rules (by design):
// Let W denote the rank of state's parent (i.e. W := state.QC.Rank) and F the
// latest finalized rank.
//
// (i) If state.Rank < F, adding the state would be a no-op. Such states are
// considered compatible (principle of vacuous truth), i.e. we skip
// checking 1, 2, 3.
// (ii) If state.Rank == F, we do not inspect the QC / parent at all (skip 2
// and 3). This exception is important for compatability with genesis or
// spork-root states, which do not contain a QC.
// (iii) If state.Rank > F, but state.QC.Rank < F the parent has already been
// pruned. In this case, we omit rule 3. (principle of vacuous truth
// applied to the parent)
//
// We assume that all states are fully verified. A valid state must satisfy all
// consistency requirements; otherwise we have a bug in the compliance layer.
//
// Error returns:
// - models.MissingStateError if the parent of the input proposal does not
// exist in the forest (but is above the pruned rank). Represents violation
// of condition 3.
// - models.InvalidStateError if the state violates condition 1. or 2.
// - generic error in case of unexpected bug or internal state corruption
func (f *Forks[StateT, VoteT]) EnsureStateIsValidExtension(
state *models.State[StateT],
) error {
if state.Rank < f.forest.LowestLevel { // exclusion (i)
return nil
}
// LevelledForest enforces conditions 1. and 2. including the respective
// exclusions (ii) and (iii).
stateContainer := ToStateContainer2[StateT](state)
err := f.forest.VerifyVertex(stateContainer)
if err != nil {
if forest.IsInvalidVertexError(err) {
return models.NewInvalidStateErrorf(
state,
"not a valid vertex for state tree: %w",
err,
)
}
return fmt.Errorf(
"state tree generated unexpected error validating vertex: %w",
err,
)
}
// Condition 3:
// LevelledForest implements a more generalized algorithm that also works for
// disjoint graphs. Therefore, LevelledForest _not_ enforce condition 3. Here,
// we additionally require that the pending states form a tree (connected
// graph), i.e. we need to enforce condition 3
if (state.Rank == f.forest.LowestLevel) ||
(state.ParentQuorumCertificate.GetRank() < f.forest.LowestLevel) { // exclusion (ii) and (iii)
return nil
}
// For a state whose parent is _not_ below the pruning height, we expect the
// parent to be known.
_, isParentKnown := f.forest.GetVertex(
state.ParentQuorumCertificate.GetSelector(),
)
if !isParentKnown { // missing parent
return models.MissingStateError{
Rank: state.ParentQuorumCertificate.GetRank(),
Identifier: state.ParentQuorumCertificate.GetSelector(),
}
}
return nil
}
// AddCertifiedState[StateT] appends the given certified state to the tree of
// pending states and updates the latest finalized state (if finalization
// progressed). Unless the parent is below the pruning threshold (latest
// finalized rank), we require that the parent is already stored in Forks.
// Calling this method with previously processed states leaves the consensus
// state invariant (though, it will potentially cause some duplicate
// processing).
//
// Possible error returns:
// - models.MissingStateError if the parent does not exist in the forest (but
// is above the pruned rank). From the perspective of Forks, this error is
// benign (no-op).
// - models.InvalidStateError if the state is invalid (see
// `Forks.EnsureStateIsValidExtension` for details). From the perspective of
// Forks, this error is benign (no-op). However, we assume all states are
// fully verified, i.e. they should satisfy all consistency requirements.
// Hence, this error is likely an indicator of a bug in the compliance
// layer.
// - models.ByzantineThresholdExceededError if conflicting QCs or conflicting
// finalized states have been detected (violating a foundational consensus
// guarantees). This indicates that there are 1/3+ Byzantine nodes (weighted
// by seniority) in the network, breaking the safety guarantees of HotStuff
// (or there is a critical bug / data corruption). Forks cannot recover from
// this exception.
// - All other errors are potential symptoms of bugs or state corruption.
func (f *Forks[StateT, VoteT]) AddCertifiedState(
certifiedState *models.CertifiedState[StateT],
) error {
if !f.IsProcessingNeeded(certifiedState.State) {
return nil
}
// Check proposal for byzantine evidence, store it and emit
// `OnStateIncorporated` notification. Note: `checkForByzantineEvidence` only
// inspects the state, but _not_ its certifying QC. Hence, we have to
// additionally check here, whether the certifying QC conflicts with any known
// QCs.
err := f.checkForByzantineEvidence(certifiedState.State)
if err != nil {
return fmt.Errorf(
"cannot check for Byzantine evidence in certified state %v: %w",
certifiedState.State.Identifier,
err,
)
}
err = f.checkForConflictingQCs(&certifiedState.CertifyingQuorumCertificate)
if err != nil {
return fmt.Errorf(
"certifying QC for state %v failed check for conflicts: %w",
certifiedState.State.Identifier,
err,
)
}
f.forest.AddVertex(ToStateContainer2[StateT](certifiedState.State))
f.notifier.OnStateIncorporated(certifiedState.State)
// Update finality status:
err = f.checkForAdvancingFinalization(certifiedState)
if err != nil {
return fmt.Errorf("updating finalization failed: %w", err)
}
return nil
}
// AddValidatedState appends the validated state to the tree of pending
// states and updates the latest finalized state (if applicable). Unless the
// parent is below the pruning threshold (latest finalized rank), we require
// that the parent is already stored in Forks. Calling this method with
// previously processed states leaves the consensus state invariant (though, it
// will potentially cause some duplicate processing).
// Notes:
// - Method `AddCertifiedState[StateT](..)` should be used preferably, if a QC
// certifying `state` is already known. This is generally the case for the
// consensus follower. Method `AddValidatedState` is intended for active
// consensus participants, which fully validate states (incl. payload), i.e.
// QCs are processed as part of validated proposals.
//
// Possible error returns:
// - models.MissingStateError if the parent does not exist in the forest (but
// is above the pruned rank). From the perspective of Forks, this error is
// benign (no-op).
// - models.InvalidStateError if the state is invalid (see
// `Forks.EnsureStateIsValidExtension` for details). From the perspective of
// Forks, this error is benign (no-op). However, we assume all states are
// fully verified, i.e. they should satisfy all consistency requirements.
// Hence, this error is likely an indicator of a bug in the compliance
// layer.
// - models.ByzantineThresholdExceededError if conflicting QCs or conflicting
// finalized states have been detected (violating a foundational consensus
// guarantees). This indicates that there are 1/3+ Byzantine nodes (weighted
// by seniority) in the network, breaking the safety guarantees of HotStuff
// (or there is a critical bug / data corruption). Forks cannot recover from
// this exception.
// - All other errors are potential symptoms of bugs or state corruption.
func (f *Forks[StateT, VoteT]) AddValidatedState(
proposal *models.State[StateT],
) error {
if !f.IsProcessingNeeded(proposal) {
return nil
}
// Check proposal for byzantine evidence, store it and emit
// `OnStateIncorporated` notification:
err := f.checkForByzantineEvidence(proposal)
if err != nil {
return fmt.Errorf(
"cannot check Byzantine evidence for state %v: %w",
proposal.Identifier,
err,
)
}
f.forest.AddVertex(ToStateContainer2[StateT](proposal))
f.notifier.OnStateIncorporated(proposal)
// Update finality status: In the implementation, our notion of finality is
// based on certified states.
// The certified parent essentially combines the parent, with the QC contained
// in state, to drive finalization.
parent, found := f.GetState(proposal.ParentQuorumCertificate.GetSelector())
if !found {
// Not finding the parent means it is already pruned; hence this state does
// not change the finalization state.
return nil
}
certifiedParent, err := models.NewCertifiedState[StateT](
parent,
proposal.ParentQuorumCertificate,
)
if err != nil {
return fmt.Errorf(
"mismatching QC with parent (corrupted Forks state):%w",
err,
)
}
err = f.checkForAdvancingFinalization(&certifiedParent)
if err != nil {
return fmt.Errorf("updating finalization failed: %w", err)
}
return nil
}
// checkForByzantineEvidence inspects whether the given `state` together with
// the already known information yields evidence of byzantine behaviour.
// Furthermore, the method enforces that `state` is a valid extension of the
// tree of pending states. If the state is a double proposal, we emit an
// `OnStateIncorporated` notification. Though, provided the state is a valid
// extension of the state tree by itself, it passes this method without an
// error.
//
// Possible error returns:
// - models.MissingStateError if the parent does not exist in the forest (but
// is above the pruned rank). From the perspective of Forks, this error is
// benign (no-op).
// - models.InvalidStateError if the state is invalid (see
// `Forks.EnsureStateIsValidExtension` for details). From the perspective of
// Forks, this error is benign (no-op). However, we assume all states are
// fully verified, i.e. they should satisfy all consistency requirements.
// Hence, this error is likely an indicator of a bug in the compliance
// layer.
// - models.ByzantineThresholdExceededError if conflicting QCs have been
// detected. Forks cannot recover from this exception.
// - All other errors are potential symptoms of bugs or state corruption.
func (f *Forks[StateT, VoteT]) checkForByzantineEvidence(
state *models.State[StateT],
) error {
err := f.EnsureStateIsValidExtension(state)
if err != nil {
return fmt.Errorf("consistency check on state failed: %w", err)
}
err = f.checkForConflictingQCs(&state.ParentQuorumCertificate)
if err != nil {
return fmt.Errorf("checking QC for conflicts failed: %w", err)
}
f.checkForDoubleProposal(state)
return nil
}
// checkForConflictingQCs checks if QC conflicts with a stored Quorum
// Certificate. In case a conflicting QC is found, an
// ByzantineThresholdExceededError is returned. Two Quorum Certificates q1 and
// q2 are defined as conflicting iff:
//
// q1.Rank == q2.Rank AND q1.Identifier ≠ q2.Identifier
//
// This means there are two Quorums for conflicting states at the same rank.
// Per 'Observation 1' from the Jolteon paper https://arxiv.org/pdf/2106.10362v1.pdf,
// two conflicting QCs can exist if and only if the Byzantine threshold is
// exceeded.
// Error returns:
// - models.ByzantineThresholdExceededError if conflicting QCs have been
// detected. Forks cannot recover from this exception.
// - All other errors are potential symptoms of bugs or state corruption.
func (f *Forks[StateT, VoteT]) checkForConflictingQCs(
qc *models.QuorumCertificate,
) error {
it := f.forest.GetVerticesAtLevel((*qc).GetRank())
for it.HasNext() {
otherState := it.NextVertex() // by construction, must have same rank as qc.Rank
if (*qc).GetSelector() != otherState.VertexID() {
// * we have just found another state at the same rank number as qc.Rank
// but with different hash
// * if this state has a child c, this child will have
// c.qc.rank = parentRank
// c.qc.ID != parentIdentifier
// => conflicting qc
otherChildren := f.forest.GetChildren(otherState.VertexID())
if otherChildren.HasNext() {
otherChild := otherChildren.NextVertex().(*StateContainer[StateT]).GetState()
conflictingQC := otherChild.ParentQuorumCertificate
return models.ByzantineThresholdExceededError{Evidence: fmt.Sprintf(
"conflicting QCs at rank %d: %v and %v",
(*qc).GetRank(), (*qc).GetSelector(), conflictingQC.GetSelector(),
)}
}
}
}
return nil
}
// checkForDoubleProposal checks if the input proposal is a double proposal.
// A double proposal occurs when two proposals with the same rank exist in
// Forks. If there is a double proposal, notifier.OnDoubleProposeDetected is
// triggered.
func (f *Forks[StateT, VoteT]) checkForDoubleProposal(
state *models.State[StateT],
) {
it := f.forest.GetVerticesAtLevel(state.Rank)
for it.HasNext() {
otherVertex := it.NextVertex() // by construction, must have same rank as state
otherState := otherVertex.(*StateContainer[StateT]).GetState()
if state.Identifier != otherState.Identifier {
f.notifier.OnDoubleProposeDetected(state, otherState)
}
}
}
// checkForAdvancingFinalization checks whether observing certifiedState leads
// to progress of finalization. This function should be called every time a new
// state is added to Forks. If the new state is the head of a 2-chain satisfying
// the finalization rule, we update `Forks.finalityProof` to the new latest
// finalized state. Calling this method with previously-processed states leaves
// the consensus state invariant.
// UNVALIDATED: assumes that relevant state properties are consistent with
// previous states
// Error returns:
// - models.MissingStateError if the parent does not exist in the forest (but
// is above the pruned rank). From the perspective of Forks, this error is
// benign (no-op).
// - models.ByzantineThresholdExceededError in case we detect a finalization
// fork (violating a foundational consensus guarantee). This indicates that
// there are 1/3+ Byzantine nodes (weighted by seniority) in the network,
// breaking the safety guarantees of HotStuff (or there is a critical bug /
// data corruption). Forks cannot recover from this exception.
// - generic error in case of unexpected bug or internal state corruption
func (f *Forks[StateT, VoteT]) checkForAdvancingFinalization(
certifiedState *models.CertifiedState[StateT],
) error {
// We prune all states in forest which are below the most recently finalized
// state. Hence, we have a pruned ancestry if and only if either of the
// following conditions applies:
// (a) If a state's parent rank (i.e. state.QC.Rank) is below the most
// recently finalized state.
// (b) If a state's rank is equal to the most recently finalized state.
// Caution:
// * Under normal operation, case (b) is covered by the logic for case (a)
// * However, the existence of a genesis state requires handling case (b)
// explicitly:
// The root state is specified and trusted by the node operator. If the root
// state is the genesis state, it might not contain a QC pointing to a
// parent (as there is no parent). In this case, condition (a) cannot be
// evaluated.
lastFinalizedRank := f.FinalizedRank()
if (certifiedState.Rank() <= lastFinalizedRank) ||
(certifiedState.State.ParentQuorumCertificate.GetRank() < lastFinalizedRank) {
// Repeated states are expected during normal operations. We enter this code
// state if and only if the parent's rank is _below_ the last finalized
// state. It is straight forward to show:
// Lemma: Let B be a state whose 2-chain reaches beyond the last finalized
// state => B will not update the locked or finalized state
return nil
}
// retrieve parent; always expected to succeed, because we passed the checks
// above
qcForParent := certifiedState.State.ParentQuorumCertificate
parentVertex, parentStateKnown := f.forest.GetVertex(
qcForParent.GetSelector(),
)
if !parentStateKnown {
return models.MissingStateError{
Rank: qcForParent.GetRank(),
Identifier: qcForParent.GetSelector(),
}
}
parentState := parentVertex.(*StateContainer[StateT]).GetState()
// Note: we assume that all stored states pass
// Forks.EnsureStateIsValidExtension(state); specifically, that state's
// RankNumber is strictly monotonically increasing which is enforced by
// LevelledForest.VerifyVertex(...)
// We denote:
// * a DIRECT 1-chain as '<-'
// * a general 1-chain as '<~' (direct or indirect)
// Jolteon's rule for finalizing `parentState` is
// parentState <- State <~ certifyingQC (i.e. a DIRECT 1-chain PLUS
// ╰─────────────────────╯ any 1-chain)
// certifiedState
// Hence, we can finalize `parentState` as head of a 2-chain,
// if and only if `State.Rank` is exactly 1 higher than the rank of
// `parentState`
if parentState.Rank+1 != certifiedState.Rank() {
return nil
}
// `parentState` is now finalized:
// * While Forks is single-threaded, there is still the possibility of
// reentrancy. Specifically, the consumers of our finalization events are
// served by the goroutine executing Forks. It is conceivable that a
// consumer might access Forks and query the latest finalization proof.
// This would be legal, if the component supplying the goroutine to Forks
// also consumes the notifications.
// * Therefore, for API safety, we want to first update Fork's
// `finalityProof` before we emit any notifications.
// Advancing finalization step (i): we collect all states for finalization (no
// notifications are emitted)
statesToBeFinalized, err := f.collectStatesForFinalization(&qcForParent)
if err != nil {
return fmt.Errorf(
"advancing finalization to state %v from rank %d failed: %w",
qcForParent.GetSelector(),
qcForParent.GetRank(),
err,
)
}
// Advancing finalization step (ii): update `finalityProof` and prune
// `LevelledForest`
f.finalityProof = &consensus.FinalityProof[StateT]{
State: parentState,
CertifiedChild: certifiedState,
}
err = f.forest.PruneUpToLevel(f.FinalizedRank())
if err != nil {
return fmt.Errorf("pruning levelled forest failed unexpectedly: %w", err)
}
// Advancing finalization step (iii): iterate over the states from (i) and
// emit finalization events
for _, b := range statesToBeFinalized {
// first notify other critical components about finalized state - all errors
// returned here are fatal exceptions
err = f.finalizationCallback.MakeFinal(b.Identifier)
if err != nil {
return fmt.Errorf("finalization error in other component: %w", err)
}
// notify less important components about finalized state
f.notifier.OnFinalizedState(b)
}
return nil
}
// collectStatesForFinalization collects and returns all newly finalized states
// up to (and including) the state pointed to by `qc`. The states are listed in
// order of increasing height.
// Error returns:
// - models.ByzantineThresholdExceededError in case we detect a finalization
// fork (violating a foundational consensus guarantee). This indicates that
// there are 1/3+ Byzantine nodes (weighted by seniority) in the network,
// breaking the safety guarantees of HotStuff (or there is a critical bug /
// data corruption). Forks cannot recover from this exception.
// - generic error in case of bug or internal state corruption
func (f *Forks[StateT, VoteT]) collectStatesForFinalization(
qc *models.QuorumCertificate,
) ([]*models.State[StateT], error) {
lastFinalized := f.FinalizedState()
if (*qc).GetRank() < lastFinalized.Rank {
return nil, models.ByzantineThresholdExceededError{Evidence: fmt.Sprintf(
"finalizing state with rank %d which is lower than previously finalized state at rank %d",
(*qc).GetRank(), lastFinalized.Rank,
)}
}
if (*qc).GetRank() == lastFinalized.Rank { // no new states to be finalized
return nil, nil
}
// Collect all states that are pending finalization in slice. While we crawl
// the states starting from the newest finalized state backwards (decreasing
// ranks), we would like to return them in order of _increasing_ rank.
// Therefore, we fill the slice starting with the highest index.
l := (*qc).GetRank() - lastFinalized.Rank // l is an upper limit to the number of states that can be maximally finalized
statesToBeFinalized := make([]*models.State[StateT], l)
for (*qc).GetRank() > lastFinalized.Rank {
b, ok := f.GetState((*qc).GetSelector())
if !ok {
return nil, fmt.Errorf(
"failed to get state (rank=%d, stateID=%x) for finalization",
(*qc).GetRank(),
(*qc).GetSelector(),
)
}
l--
statesToBeFinalized[l] = b
qc = &b.ParentQuorumCertificate // move to parent
}
// Now, `l` is the index where we stored the oldest state that should be
// finalized. Note that `l` might be larger than zero, if some ranks have no
// finalized states. Hence, `statesToBeFinalized` might start with nil
// entries, which we remove:
statesToBeFinalized = statesToBeFinalized[l:]
// qc should now point to the latest finalized state. Otherwise, the
// consensus committee is compromised (or we have a critical internal bug).
if (*qc).GetRank() < lastFinalized.Rank {
return nil, models.ByzantineThresholdExceededError{Evidence: fmt.Sprintf(
"finalizing state with rank %d which is lower than previously finalized state at rank %d",
(*qc).GetRank(), lastFinalized.Rank,
)}
}
if (*qc).GetRank() == lastFinalized.Rank &&
lastFinalized.Identifier != (*qc).GetSelector() {
return nil, models.ByzantineThresholdExceededError{Evidence: fmt.Sprintf(
"finalizing states with rank %d at conflicting forks: %x and %x",
(*qc).GetRank(), (*qc).GetSelector(), lastFinalized.Identifier,
)}
}
return statesToBeFinalized, nil
}
// Type used to satisfy generic arguments in compiler time type assertion check
type nilUnique struct{}
// GetSignature implements models.Unique.
func (n *nilUnique) GetSignature() []byte {
panic("unimplemented")
}
// GetTimestamp implements models.Unique.
func (n *nilUnique) GetTimestamp() uint64 {
panic("unimplemented")
}
// Source implements models.Unique.
func (n *nilUnique) Source() models.Identity {
panic("unimplemented")
}
// Clone implements models.Unique.
func (n *nilUnique) Clone() models.Unique {
panic("unimplemented")
}
// GetRank implements models.Unique.
func (n *nilUnique) GetRank() uint64 {
panic("unimplemented")
}
// Identity implements models.Unique.
func (n *nilUnique) Identity() models.Identity {
panic("unimplemented")
}
var _ models.Unique = (*nilUnique)(nil)

View File

@ -0,0 +1,77 @@
package forks
import (
"source.quilibrium.com/quilibrium/monorepo/consensus/forest"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// StateContainer wraps a state proposal to implement forest.Vertex
// so the proposal can be stored in forest.LevelledForest
type StateContainer[StateT models.Unique] models.State[StateT]
var _ forest.Vertex = (*StateContainer[*nilUnique])(nil)
func ToStateContainer2[StateT models.Unique](
state *models.State[StateT],
) *StateContainer[StateT] {
return (*StateContainer[StateT])(state)
}
func (b *StateContainer[StateT]) GetState() *models.State[StateT] {
return (*models.State[StateT])(b)
}
// Functions implementing forest.Vertex
func (b *StateContainer[StateT]) VertexID() models.Identity {
return b.Identifier
}
func (b *StateContainer[StateT]) Level() uint64 {
return b.Rank
}
func (b *StateContainer[StateT]) Parent() (models.Identity, uint64) {
// Caution: not all states have a QC for the parent, such as the spork root
// states. Per API contract, we are obliged to return a value to prevent
// panics during logging. (see vertex `forest.VertexToString` method).
if b.ParentQuorumCertificate == nil {
return "", 0
}
return b.ParentQuorumCertificate.GetSelector(),
b.ParentQuorumCertificate.GetRank()
}
// Type used to satisfy generic arguments in compiler time type assertion check
type nilUnique struct{}
// GetSignature implements models.Unique.
func (n *nilUnique) GetSignature() []byte {
panic("unimplemented")
}
// GetTimestamp implements models.Unique.
func (n *nilUnique) GetTimestamp() uint64 {
panic("unimplemented")
}
// Source implements models.Unique.
func (n *nilUnique) Source() models.Identity {
panic("unimplemented")
}
// Clone implements models.Unique.
func (n *nilUnique) Clone() models.Unique {
panic("unimplemented")
}
// GetRank implements models.Unique.
func (n *nilUnique) GetRank() uint64 {
panic("unimplemented")
}
// Identity implements models.Unique.
func (n *nilUnique) Identity() models.Identity {
panic("unimplemented")
}
var _ models.Unique = (*nilUnique)(nil)

View File

@ -1,16 +1,9 @@
module source.quilibrium.com/quilibrium/monorepo/consensus
go 1.23.0
go 1.23.2
toolchain go1.23.4
replace source.quilibrium.com/quilibrium/monorepo/protobufs => ../protobufs
replace source.quilibrium.com/quilibrium/monorepo/types => ../types
replace source.quilibrium.com/quilibrium/monorepo/config => ../config
replace source.quilibrium.com/quilibrium/monorepo/utils => ../utils
replace github.com/multiformats/go-multiaddr => ../go-multiaddr
@ -20,13 +13,36 @@ replace github.com/libp2p/go-libp2p => ../go-libp2p
replace github.com/libp2p/go-libp2p-kad-dht => ../go-libp2p-kad-dht
replace source.quilibrium.com/quilibrium/monorepo/go-libp2p-blossomsub => ../go-libp2p-blossomsub
require go.uber.org/zap v1.27.0
require (
github.com/stretchr/testify v1.10.0 // indirect
github.com/stretchr/testify v1.10.0
github.com/cloudflare/circl v1.6.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
github.com/iden3/go-iden3-crypto v0.0.17 // indirect
github.com/ipfs/go-cid v0.5.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/multiformats/go-base32 v0.1.0 // indirect
github.com/multiformats/go-base36 v0.2.0 // indirect
github.com/multiformats/go-multiaddr v0.16.1 // indirect
github.com/multiformats/go-multibase v0.2.0 // indirect
github.com/multiformats/go-multicodec v0.9.1 // indirect
github.com/multiformats/go-multihash v0.2.3 // indirect
github.com/multiformats/go-varint v0.0.7 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
)
require github.com/pkg/errors v0.9.1
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/grpc v1.72.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
lukechampine.com/blake3 v1.4.1 // indirect
github.com/pkg/errors v0.9.1
)

View File

@ -1,16 +1,92 @@
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
github.com/iden3/go-iden3-crypto v0.0.17 h1:NdkceRLJo/pI4UpcjVah4lN/a3yzxRUGXqxbWcYh9mY=
github.com/iden3/go-iden3-crypto v0.0.17/go.mod h1:dLpM4vEPJ3nDHzhWFXDjzkn1qHoBeOT/3UEhXsEsP3E=
github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg=
github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
github.com/multiformats/go-multicodec v0.9.1 h1:x/Fuxr7ZuR4jJV4Os5g444F7xC4XmyUaT/FWtE+9Zjo=
github.com/multiformats/go-multicodec v0.9.1/go.mod h1:LLWNMtyV5ithSBUo3vFIMaeDy+h3EbkMTek1m+Fybbo=
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 h1:bsqhLWFR6G6xiQcb+JoGqdKdRU6WzPWmK8E0jxTjzo4=
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=

View File

@ -0,0 +1,120 @@
package helper
import (
"bytes"
crand "crypto/rand"
"math/rand"
"time"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
type TestAggregatedSignature struct {
Signature []byte
PublicKey []byte
Bitmask []byte
}
func (t *TestAggregatedSignature) GetSignature() []byte {
return t.Signature
}
func (t *TestAggregatedSignature) GetPublicKey() []byte {
return t.PublicKey
}
func (t *TestAggregatedSignature) GetBitmask() []byte {
return t.Bitmask
}
type TestQuorumCertificate struct {
Filter []byte
Rank uint64
FrameNumber uint64
Selector models.Identity
Timestamp int64
AggregatedSignature models.AggregatedSignature
}
func (t *TestQuorumCertificate) GetFilter() []byte {
return t.Filter
}
func (t *TestQuorumCertificate) GetRank() uint64 {
return t.Rank
}
func (t *TestQuorumCertificate) GetFrameNumber() uint64 {
return t.FrameNumber
}
func (t *TestQuorumCertificate) GetSelector() models.Identity {
return t.Selector
}
func (t *TestQuorumCertificate) GetTimestamp() int64 {
return t.Timestamp
}
func (t *TestQuorumCertificate) GetAggregatedSignature() models.AggregatedSignature {
return t.AggregatedSignature
}
func (t *TestQuorumCertificate) Equals(other models.QuorumCertificate) bool {
return bytes.Equal(t.Filter, other.GetFilter()) &&
t.Rank == other.GetRank() &&
t.FrameNumber == other.GetFrameNumber() &&
t.Selector == other.GetSelector() &&
t.Timestamp == other.GetTimestamp() &&
bytes.Equal(
t.AggregatedSignature.GetBitmask(),
other.GetAggregatedSignature().GetBitmask(),
) &&
bytes.Equal(
t.AggregatedSignature.GetPublicKey(),
other.GetAggregatedSignature().GetPublicKey(),
) &&
bytes.Equal(
t.AggregatedSignature.GetSignature(),
other.GetAggregatedSignature().GetSignature(),
)
}
func MakeQC(options ...func(*TestQuorumCertificate)) models.QuorumCertificate {
s := make([]byte, 32)
crand.Read(s)
qc := &TestQuorumCertificate{
Rank: rand.Uint64(),
FrameNumber: rand.Uint64() + 1,
Selector: string(s),
Timestamp: time.Now().UnixMilli(),
AggregatedSignature: &TestAggregatedSignature{
PublicKey: make([]byte, 585),
Signature: make([]byte, 74),
Bitmask: []byte{0x01},
},
}
for _, option := range options {
option(qc)
}
return qc
}
func WithQCState[StateT models.Unique](state *models.State[StateT]) func(TestQuorumCertificate) {
return func(qc TestQuorumCertificate) {
qc.Rank = state.Rank
qc.Selector = state.Identifier
}
}
func WithQCSigners(signerIndices []byte) func(TestQuorumCertificate) {
return func(qc TestQuorumCertificate) {
qc.AggregatedSignature.(*TestAggregatedSignature).Bitmask = signerIndices
}
}
func WithQCRank(rank uint64) func(*TestQuorumCertificate) {
return func(qc *TestQuorumCertificate) {
qc.Rank = rank
}
}

112
consensus/helper/state.go Normal file
View File

@ -0,0 +1,112 @@
package helper
import (
crand "crypto/rand"
"math/rand"
"time"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
func MakeIdentity() models.Identity {
s := make([]byte, 32)
crand.Read(s)
return models.Identity(s)
}
func MakeState[StateT models.Unique](options ...func(*models.State[StateT])) *models.State[StateT] {
rank := rand.Uint64()
state := models.State[StateT]{
Rank: rank,
Identifier: MakeIdentity(),
ProposerID: MakeIdentity(),
Timestamp: uint64(time.Now().UnixMilli()),
ParentQuorumCertificate: MakeQC(WithQCRank(rank - 1)),
}
for _, option := range options {
option(&state)
}
return &state
}
func WithStateRank[StateT models.Unique](rank uint64) func(*models.State[StateT]) {
return func(state *models.State[StateT]) {
state.Rank = rank
}
}
func WithStateProposer[StateT models.Unique](proposerID models.Identity) func(*models.State[StateT]) {
return func(state *models.State[StateT]) {
state.ProposerID = proposerID
}
}
func WithParentState[StateT models.Unique](parent *models.State[StateT]) func(*models.State[StateT]) {
return func(state *models.State[StateT]) {
state.ParentQuorumCertificate.(*TestQuorumCertificate).Selector = parent.Identifier
state.ParentQuorumCertificate.(*TestQuorumCertificate).Rank = parent.Rank
}
}
func WithParentSigners[StateT models.Unique](signerIndices []byte) func(*models.State[StateT]) {
return func(state *models.State[StateT]) {
state.ParentQuorumCertificate.(*TestQuorumCertificate).AggregatedSignature.(*TestAggregatedSignature).Bitmask = signerIndices
}
}
func WithStateQC[StateT models.Unique](qc models.QuorumCertificate) func(*models.State[StateT]) {
return func(state *models.State[StateT]) {
state.ParentQuorumCertificate = qc
}
}
func MakeVote[VoteT models.Unique]() *VoteT {
return new(VoteT)
}
func MakeSignedProposal[StateT models.Unique, VoteT models.Unique](options ...func(*models.SignedProposal[StateT, VoteT])) *models.SignedProposal[StateT, VoteT] {
proposal := &models.SignedProposal[StateT, VoteT]{
Proposal: *MakeProposal[StateT](),
Vote: MakeVote[VoteT](),
}
for _, option := range options {
option(proposal)
}
return proposal
}
func MakeProposal[StateT models.Unique](options ...func(*models.Proposal[StateT])) *models.Proposal[StateT] {
proposal := &models.Proposal[StateT]{
State: MakeState[StateT](),
PreviousRankTimeoutCertificate: nil,
}
for _, option := range options {
option(proposal)
}
return proposal
}
func WithProposal[StateT models.Unique, VoteT models.Unique](proposal *models.Proposal[StateT]) func(*models.SignedProposal[StateT, VoteT]) {
return func(signedProposal *models.SignedProposal[StateT, VoteT]) {
signedProposal.Proposal = *proposal
}
}
func WithState[StateT models.Unique](state *models.State[StateT]) func(*models.Proposal[StateT]) {
return func(proposal *models.Proposal[StateT]) {
proposal.State = state
}
}
func WithVote[StateT models.Unique, VoteT models.Unique](vote *VoteT) func(*models.SignedProposal[StateT, VoteT]) {
return func(proposal *models.SignedProposal[StateT, VoteT]) {
proposal.Vote = vote
}
}
func WithPreviousRankTimeoutCertificate[StateT models.Unique](previousRankTimeoutCert models.TimeoutCertificate) func(*models.Proposal[StateT]) {
return func(proposal *models.Proposal[StateT]) {
proposal.PreviousRankTimeoutCertificate = previousRankTimeoutCert
}
}

View File

@ -0,0 +1,153 @@
package helper
import (
"bytes"
crand "crypto/rand"
"math/rand"
"slices"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
type TestTimeoutCertificate struct {
Filter []byte
Rank uint64
LatestRanks []uint64
LatestQuorumCert models.QuorumCertificate
AggregatedSignature models.AggregatedSignature
}
func (t *TestTimeoutCertificate) GetFilter() []byte {
return t.Filter
}
func (t *TestTimeoutCertificate) GetRank() uint64 {
return t.Rank
}
func (t *TestTimeoutCertificate) GetLatestRanks() []uint64 {
return t.LatestRanks
}
func (t *TestTimeoutCertificate) GetLatestQuorumCert() models.QuorumCertificate {
return t.LatestQuorumCert
}
func (t *TestTimeoutCertificate) GetAggregatedSignature() models.AggregatedSignature {
return t.AggregatedSignature
}
func (t *TestTimeoutCertificate) Equals(other models.TimeoutCertificate) bool {
return bytes.Equal(t.Filter, other.GetFilter()) &&
t.Rank == other.GetRank() &&
slices.Equal(t.LatestRanks, other.GetLatestRanks()) &&
t.LatestQuorumCert.Equals(other.GetLatestQuorumCert()) &&
bytes.Equal(
t.AggregatedSignature.GetBitmask(),
other.GetAggregatedSignature().GetBitmask(),
) &&
bytes.Equal(
t.AggregatedSignature.GetPublicKey(),
other.GetAggregatedSignature().GetPublicKey(),
) &&
bytes.Equal(
t.AggregatedSignature.GetSignature(),
other.GetAggregatedSignature().GetSignature(),
)
}
func MakeTC(options ...func(*TestTimeoutCertificate)) models.TimeoutCertificate {
tcRank := rand.Uint64()
s := make([]byte, 32)
crand.Read(s)
qc := MakeQC(WithQCRank(tcRank - 1))
highQCRanks := make([]uint64, 3)
for i := range highQCRanks {
highQCRanks[i] = qc.GetRank()
}
tc := &TestTimeoutCertificate{
Rank: tcRank,
LatestQuorumCert: qc,
LatestRanks: highQCRanks,
AggregatedSignature: &TestAggregatedSignature{
Signature: make([]byte, 74),
PublicKey: make([]byte, 585),
Bitmask: []byte{0x01},
},
}
for _, option := range options {
option(tc)
}
return tc
}
func WithTCNewestQC(qc models.QuorumCertificate) func(*TestTimeoutCertificate) {
return func(tc *TestTimeoutCertificate) {
tc.LatestQuorumCert = qc
tc.LatestRanks = []uint64{qc.GetRank()}
}
}
func WithTCSigners(signerIndices []byte) func(*TestTimeoutCertificate) {
return func(tc *TestTimeoutCertificate) {
tc.AggregatedSignature.(*TestAggregatedSignature).Bitmask = signerIndices
}
}
func WithTCRank(rank uint64) func(*TestTimeoutCertificate) {
return func(tc *TestTimeoutCertificate) {
tc.Rank = rank
}
}
func WithTCHighQCRanks(highQCRanks []uint64) func(*TestTimeoutCertificate) {
return func(tc *TestTimeoutCertificate) {
tc.LatestRanks = highQCRanks
}
}
func TimeoutStateFixture[VoteT models.Unique](
opts ...func(TimeoutState *models.TimeoutState[VoteT]),
) *models.TimeoutState[VoteT] {
timeoutRank := uint64(rand.Uint32())
newestQC := MakeQC(WithQCRank(timeoutRank - 10))
timeout := &models.TimeoutState[VoteT]{
Rank: timeoutRank,
LatestQuorumCertificate: newestQC,
PriorRankTimeoutCertificate: MakeTC(
WithTCRank(timeoutRank-1),
WithTCNewestQC(MakeQC(WithQCRank(newestQC.GetRank()))),
),
}
for _, opt := range opts {
opt(timeout)
}
return timeout
}
func WithTimeoutNewestQC[VoteT models.Unique](
newestQC models.QuorumCertificate,
) func(*models.TimeoutState[VoteT]) {
return func(timeout *models.TimeoutState[VoteT]) {
timeout.LatestQuorumCertificate = newestQC
}
}
func WithTimeoutPreviousRankTimeoutCertificate[VoteT models.Unique](
previousRankTimeoutCert models.TimeoutCertificate,
) func(*models.TimeoutState[VoteT]) {
return func(timeout *models.TimeoutState[VoteT]) {
timeout.PriorRankTimeoutCertificate = previousRankTimeoutCert
}
}
func WithTimeoutStateRank[VoteT models.Unique](
rank uint64,
) func(*models.TimeoutState[VoteT]) {
return func(timeout *models.TimeoutState[VoteT]) {
timeout.Rank = rank
}
}

View File

@ -0,0 +1,44 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
time "time"
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// CommunicatorConsumer is an autogenerated mock type for the CommunicatorConsumer type
type CommunicatorConsumer[StateT models.Unique, VoteT models.Unique] struct {
mock.Mock
}
// OnOwnProposal provides a mock function with given fields: proposal, targetPublicationTime
func (_m *CommunicatorConsumer[StateT, VoteT]) OnOwnProposal(proposal *models.SignedProposal[StateT, VoteT], targetPublicationTime time.Time) {
_m.Called(proposal, targetPublicationTime)
}
// OnOwnTimeout provides a mock function with given fields: timeout
func (_m *CommunicatorConsumer[StateT, VoteT]) OnOwnTimeout(timeout *models.TimeoutState[VoteT]) {
_m.Called(timeout)
}
// OnOwnVote provides a mock function with given fields: vote, recipientID
func (_m *CommunicatorConsumer[StateT, VoteT]) OnOwnVote(vote *VoteT, recipientID models.Identity) {
_m.Called(vote, recipientID)
}
// NewCommunicatorConsumer creates a new instance of CommunicatorConsumer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewCommunicatorConsumer[StateT models.Unique, VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *CommunicatorConsumer[StateT, VoteT] {
mock := &CommunicatorConsumer[StateT, VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,123 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// ConsensusStore is an autogenerated mock type for the ConsensusStore type
type ConsensusStore[VoteT models.Unique] struct {
mock.Mock
}
// GetConsensusState provides a mock function with no fields
func (_m *ConsensusStore[VoteT]) GetConsensusState() (*models.ConsensusState[VoteT], error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for GetConsensusState")
}
var r0 *models.ConsensusState[VoteT]
var r1 error
if rf, ok := ret.Get(0).(func() (*models.ConsensusState[VoteT], error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() *models.ConsensusState[VoteT]); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.ConsensusState[VoteT])
}
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetLivenessState provides a mock function with no fields
func (_m *ConsensusStore[VoteT]) GetLivenessState() (*models.LivenessState, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for GetLivenessState")
}
var r0 *models.LivenessState
var r1 error
if rf, ok := ret.Get(0).(func() (*models.LivenessState, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() *models.LivenessState); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.LivenessState)
}
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// PutConsensusState provides a mock function with given fields: state
func (_m *ConsensusStore[VoteT]) PutConsensusState(state *models.ConsensusState[VoteT]) error {
ret := _m.Called(state)
if len(ret) == 0 {
panic("no return value specified for PutConsensusState")
}
var r0 error
if rf, ok := ret.Get(0).(func(*models.ConsensusState[VoteT]) error); ok {
r0 = rf(state)
} else {
r0 = ret.Error(0)
}
return r0
}
// PutLivenessState provides a mock function with given fields: state
func (_m *ConsensusStore[VoteT]) PutLivenessState(state *models.LivenessState) error {
ret := _m.Called(state)
if len(ret) == 0 {
panic("no return value specified for PutLivenessState")
}
var r0 error
if rf, ok := ret.Get(0).(func(*models.LivenessState) error); ok {
r0 = rf(state)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewConsensusStore creates a new instance of ConsensusStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewConsensusStore[VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *ConsensusStore[VoteT] {
mock := &ConsensusStore[VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

126
consensus/mocks/consumer.go Normal file
View File

@ -0,0 +1,126 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
consensus "source.quilibrium.com/quilibrium/monorepo/consensus"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
time "time"
)
// Consumer is an autogenerated mock type for the Consumer type
type Consumer[StateT models.Unique, VoteT models.Unique] struct {
mock.Mock
}
// OnCurrentRankDetails provides a mock function with given fields: currentRank, finalizedRank, currentLeader
func (_m *Consumer[StateT, VoteT]) OnCurrentRankDetails(currentRank uint64, finalizedRank uint64, currentLeader models.Identity) {
_m.Called(currentRank, finalizedRank, currentLeader)
}
// OnDoubleProposeDetected provides a mock function with given fields: _a0, _a1
func (_m *Consumer[StateT, VoteT]) OnDoubleProposeDetected(_a0 *models.State[StateT], _a1 *models.State[StateT]) {
_m.Called(_a0, _a1)
}
// OnEventProcessed provides a mock function with no fields
func (_m *Consumer[StateT, VoteT]) OnEventProcessed() {
_m.Called()
}
// OnFinalizedState provides a mock function with given fields: _a0
func (_m *Consumer[StateT, VoteT]) OnFinalizedState(_a0 *models.State[StateT]) {
_m.Called(_a0)
}
// OnInvalidStateDetected provides a mock function with given fields: err
func (_m *Consumer[StateT, VoteT]) OnInvalidStateDetected(err *models.InvalidProposalError[StateT, VoteT]) {
_m.Called(err)
}
// OnLocalTimeout provides a mock function with given fields: currentRank
func (_m *Consumer[StateT, VoteT]) OnLocalTimeout(currentRank uint64) {
_m.Called(currentRank)
}
// OnOwnProposal provides a mock function with given fields: proposal, targetPublicationTime
func (_m *Consumer[StateT, VoteT]) OnOwnProposal(proposal *models.SignedProposal[StateT, VoteT], targetPublicationTime time.Time) {
_m.Called(proposal, targetPublicationTime)
}
// OnOwnTimeout provides a mock function with given fields: timeout
func (_m *Consumer[StateT, VoteT]) OnOwnTimeout(timeout *models.TimeoutState[VoteT]) {
_m.Called(timeout)
}
// OnOwnVote provides a mock function with given fields: vote, recipientID
func (_m *Consumer[StateT, VoteT]) OnOwnVote(vote *VoteT, recipientID models.Identity) {
_m.Called(vote, recipientID)
}
// OnPartialTimeoutCertificate provides a mock function with given fields: currentRank, partialTimeoutCertificate
func (_m *Consumer[StateT, VoteT]) OnPartialTimeoutCertificate(currentRank uint64, partialTimeoutCertificate *consensus.PartialTimeoutCertificateCreated) {
_m.Called(currentRank, partialTimeoutCertificate)
}
// OnQuorumCertificateTriggeredRankChange provides a mock function with given fields: oldRank, newRank, qc
func (_m *Consumer[StateT, VoteT]) OnQuorumCertificateTriggeredRankChange(oldRank uint64, newRank uint64, qc models.QuorumCertificate) {
_m.Called(oldRank, newRank, qc)
}
// OnRankChange provides a mock function with given fields: oldRank, newRank
func (_m *Consumer[StateT, VoteT]) OnRankChange(oldRank uint64, newRank uint64) {
_m.Called(oldRank, newRank)
}
// OnReceiveProposal provides a mock function with given fields: currentRank, proposal
func (_m *Consumer[StateT, VoteT]) OnReceiveProposal(currentRank uint64, proposal *models.SignedProposal[StateT, VoteT]) {
_m.Called(currentRank, proposal)
}
// OnReceiveQuorumCertificate provides a mock function with given fields: currentRank, qc
func (_m *Consumer[StateT, VoteT]) OnReceiveQuorumCertificate(currentRank uint64, qc models.QuorumCertificate) {
_m.Called(currentRank, qc)
}
// OnReceiveTimeoutCertificate provides a mock function with given fields: currentRank, tc
func (_m *Consumer[StateT, VoteT]) OnReceiveTimeoutCertificate(currentRank uint64, tc models.TimeoutCertificate) {
_m.Called(currentRank, tc)
}
// OnStart provides a mock function with given fields: currentRank
func (_m *Consumer[StateT, VoteT]) OnStart(currentRank uint64) {
_m.Called(currentRank)
}
// OnStartingTimeout provides a mock function with given fields: startTime, endTime
func (_m *Consumer[StateT, VoteT]) OnStartingTimeout(startTime time.Time, endTime time.Time) {
_m.Called(startTime, endTime)
}
// OnStateIncorporated provides a mock function with given fields: _a0
func (_m *Consumer[StateT, VoteT]) OnStateIncorporated(_a0 *models.State[StateT]) {
_m.Called(_a0)
}
// OnTimeoutCertificateTriggeredRankChange provides a mock function with given fields: oldRank, newRank, tc
func (_m *Consumer[StateT, VoteT]) OnTimeoutCertificateTriggeredRankChange(oldRank uint64, newRank uint64, tc models.TimeoutCertificate) {
_m.Called(oldRank, newRank, tc)
}
// NewConsumer creates a new instance of Consumer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewConsumer[StateT models.Unique, VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *Consumer[StateT, VoteT] {
mock := &Consumer[StateT, VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,249 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// DynamicCommittee is an autogenerated mock type for the DynamicCommittee type
type DynamicCommittee struct {
mock.Mock
}
// IdentitiesByRank provides a mock function with given fields: rank
func (_m *DynamicCommittee) IdentitiesByRank(rank uint64) ([]models.WeightedIdentity, error) {
ret := _m.Called(rank)
if len(ret) == 0 {
panic("no return value specified for IdentitiesByRank")
}
var r0 []models.WeightedIdentity
var r1 error
if rf, ok := ret.Get(0).(func(uint64) ([]models.WeightedIdentity, error)); ok {
return rf(rank)
}
if rf, ok := ret.Get(0).(func(uint64) []models.WeightedIdentity); ok {
r0 = rf(rank)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]models.WeightedIdentity)
}
}
if rf, ok := ret.Get(1).(func(uint64) error); ok {
r1 = rf(rank)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// IdentitiesByState provides a mock function with given fields: stateID
func (_m *DynamicCommittee) IdentitiesByState(stateID models.Identity) ([]models.WeightedIdentity, error) {
ret := _m.Called(stateID)
if len(ret) == 0 {
panic("no return value specified for IdentitiesByState")
}
var r0 []models.WeightedIdentity
var r1 error
if rf, ok := ret.Get(0).(func(models.Identity) ([]models.WeightedIdentity, error)); ok {
return rf(stateID)
}
if rf, ok := ret.Get(0).(func(models.Identity) []models.WeightedIdentity); ok {
r0 = rf(stateID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]models.WeightedIdentity)
}
}
if rf, ok := ret.Get(1).(func(models.Identity) error); ok {
r1 = rf(stateID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// IdentityByRank provides a mock function with given fields: rank, participantID
func (_m *DynamicCommittee) IdentityByRank(rank uint64, participantID models.Identity) (models.WeightedIdentity, error) {
ret := _m.Called(rank, participantID)
if len(ret) == 0 {
panic("no return value specified for IdentityByRank")
}
var r0 models.WeightedIdentity
var r1 error
if rf, ok := ret.Get(0).(func(uint64, models.Identity) (models.WeightedIdentity, error)); ok {
return rf(rank, participantID)
}
if rf, ok := ret.Get(0).(func(uint64, models.Identity) models.WeightedIdentity); ok {
r0 = rf(rank, participantID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(models.WeightedIdentity)
}
}
if rf, ok := ret.Get(1).(func(uint64, models.Identity) error); ok {
r1 = rf(rank, participantID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// IdentityByState provides a mock function with given fields: stateID, participantID
func (_m *DynamicCommittee) IdentityByState(stateID models.Identity, participantID models.Identity) (*models.WeightedIdentity, error) {
ret := _m.Called(stateID, participantID)
if len(ret) == 0 {
panic("no return value specified for IdentityByState")
}
var r0 *models.WeightedIdentity
var r1 error
if rf, ok := ret.Get(0).(func(models.Identity, models.Identity) (*models.WeightedIdentity, error)); ok {
return rf(stateID, participantID)
}
if rf, ok := ret.Get(0).(func(models.Identity, models.Identity) *models.WeightedIdentity); ok {
r0 = rf(stateID, participantID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.WeightedIdentity)
}
}
if rf, ok := ret.Get(1).(func(models.Identity, models.Identity) error); ok {
r1 = rf(stateID, participantID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// LeaderForRank provides a mock function with given fields: rank
func (_m *DynamicCommittee) LeaderForRank(rank uint64) (models.Identity, error) {
ret := _m.Called(rank)
if len(ret) == 0 {
panic("no return value specified for LeaderForRank")
}
var r0 models.Identity
var r1 error
if rf, ok := ret.Get(0).(func(uint64) (models.Identity, error)); ok {
return rf(rank)
}
if rf, ok := ret.Get(0).(func(uint64) models.Identity); ok {
r0 = rf(rank)
} else {
r0 = ret.Get(0).(models.Identity)
}
if rf, ok := ret.Get(1).(func(uint64) error); ok {
r1 = rf(rank)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// QuorumThresholdForRank provides a mock function with given fields: rank
func (_m *DynamicCommittee) QuorumThresholdForRank(rank uint64) (uint64, error) {
ret := _m.Called(rank)
if len(ret) == 0 {
panic("no return value specified for QuorumThresholdForRank")
}
var r0 uint64
var r1 error
if rf, ok := ret.Get(0).(func(uint64) (uint64, error)); ok {
return rf(rank)
}
if rf, ok := ret.Get(0).(func(uint64) uint64); ok {
r0 = rf(rank)
} else {
r0 = ret.Get(0).(uint64)
}
if rf, ok := ret.Get(1).(func(uint64) error); ok {
r1 = rf(rank)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Self provides a mock function with no fields
func (_m *DynamicCommittee) Self() models.Identity {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Self")
}
var r0 models.Identity
if rf, ok := ret.Get(0).(func() models.Identity); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(models.Identity)
}
return r0
}
// TimeoutThresholdForRank provides a mock function with given fields: rank
func (_m *DynamicCommittee) TimeoutThresholdForRank(rank uint64) (uint64, error) {
ret := _m.Called(rank)
if len(ret) == 0 {
panic("no return value specified for TimeoutThresholdForRank")
}
var r0 uint64
var r1 error
if rf, ok := ret.Get(0).(func(uint64) (uint64, error)); ok {
return rf(rank)
}
if rf, ok := ret.Get(0).(func(uint64) uint64); ok {
r0 = rf(rank)
} else {
r0 = ret.Get(0).(uint64)
}
if rf, ok := ret.Get(1).(func(uint64) error); ok {
r1 = rf(rank)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewDynamicCommittee creates a new instance of DynamicCommittee. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewDynamicCommittee(t interface {
mock.TestingT
Cleanup(func())
}) *DynamicCommittee {
mock := &DynamicCommittee{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,162 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
context "context"
consensus "source.quilibrium.com/quilibrium/monorepo/consensus"
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
time "time"
)
// EventHandler is an autogenerated mock type for the EventHandler type
type EventHandler[StateT models.Unique, VoteT models.Unique] struct {
mock.Mock
}
// OnLocalTimeout provides a mock function with no fields
func (_m *EventHandler[StateT, VoteT]) OnLocalTimeout() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for OnLocalTimeout")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// OnPartialTimeoutCertificateCreated provides a mock function with given fields: partialTimeoutCertificate
func (_m *EventHandler[StateT, VoteT]) OnPartialTimeoutCertificateCreated(partialTimeoutCertificate *consensus.PartialTimeoutCertificateCreated) error {
ret := _m.Called(partialTimeoutCertificate)
if len(ret) == 0 {
panic("no return value specified for OnPartialTimeoutCertificateCreated")
}
var r0 error
if rf, ok := ret.Get(0).(func(*consensus.PartialTimeoutCertificateCreated) error); ok {
r0 = rf(partialTimeoutCertificate)
} else {
r0 = ret.Error(0)
}
return r0
}
// OnReceiveProposal provides a mock function with given fields: proposal
func (_m *EventHandler[StateT, VoteT]) OnReceiveProposal(proposal *models.SignedProposal[StateT, VoteT]) error {
ret := _m.Called(proposal)
if len(ret) == 0 {
panic("no return value specified for OnReceiveProposal")
}
var r0 error
if rf, ok := ret.Get(0).(func(*models.SignedProposal[StateT, VoteT]) error); ok {
r0 = rf(proposal)
} else {
r0 = ret.Error(0)
}
return r0
}
// OnReceiveQuorumCertificate provides a mock function with given fields: quorumCertificate
func (_m *EventHandler[StateT, VoteT]) OnReceiveQuorumCertificate(quorumCertificate models.QuorumCertificate) error {
ret := _m.Called(quorumCertificate)
if len(ret) == 0 {
panic("no return value specified for OnReceiveQuorumCertificate")
}
var r0 error
if rf, ok := ret.Get(0).(func(models.QuorumCertificate) error); ok {
r0 = rf(quorumCertificate)
} else {
r0 = ret.Error(0)
}
return r0
}
// OnReceiveTimeoutCertificate provides a mock function with given fields: timeoutCertificate
func (_m *EventHandler[StateT, VoteT]) OnReceiveTimeoutCertificate(timeoutCertificate models.TimeoutCertificate) error {
ret := _m.Called(timeoutCertificate)
if len(ret) == 0 {
panic("no return value specified for OnReceiveTimeoutCertificate")
}
var r0 error
if rf, ok := ret.Get(0).(func(models.TimeoutCertificate) error); ok {
r0 = rf(timeoutCertificate)
} else {
r0 = ret.Error(0)
}
return r0
}
// Start provides a mock function with given fields: ctx
func (_m *EventHandler[StateT, VoteT]) Start(ctx context.Context) error {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for Start")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
r0 = rf(ctx)
} else {
r0 = ret.Error(0)
}
return r0
}
// TimeoutChannel provides a mock function with no fields
func (_m *EventHandler[StateT, VoteT]) TimeoutChannel() <-chan time.Time {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for TimeoutChannel")
}
var r0 <-chan time.Time
if rf, ok := ret.Get(0).(func() <-chan time.Time); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(<-chan time.Time)
}
}
return r0
}
// NewEventHandler creates a new instance of EventHandler. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewEventHandler[StateT models.Unique, VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *EventHandler[StateT, VoteT] {
mock := &EventHandler[StateT, VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,67 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// EventLoop is an autogenerated mock type for the EventLoop type
type EventLoop[StateT models.Unique, VoteT models.Unique] struct {
mock.Mock
}
// OnNewQuorumCertificateDiscovered provides a mock function with given fields: certificate
func (_m *EventLoop[StateT, VoteT]) OnNewQuorumCertificateDiscovered(certificate models.QuorumCertificate) {
_m.Called(certificate)
}
// OnNewTimeoutCertificateDiscovered provides a mock function with given fields: certificate
func (_m *EventLoop[StateT, VoteT]) OnNewTimeoutCertificateDiscovered(certificate models.TimeoutCertificate) {
_m.Called(certificate)
}
// OnPartialTimeoutCertificateCreated provides a mock function with given fields: rank, newestQC, lastRankTC
func (_m *EventLoop[StateT, VoteT]) OnPartialTimeoutCertificateCreated(rank uint64, newestQC models.QuorumCertificate, lastRankTC models.TimeoutCertificate) {
_m.Called(rank, newestQC, lastRankTC)
}
// OnQuorumCertificateConstructedFromVotes provides a mock function with given fields: _a0
func (_m *EventLoop[StateT, VoteT]) OnQuorumCertificateConstructedFromVotes(_a0 models.QuorumCertificate) {
_m.Called(_a0)
}
// OnTimeoutCertificateConstructedFromTimeouts provides a mock function with given fields: certificate
func (_m *EventLoop[StateT, VoteT]) OnTimeoutCertificateConstructedFromTimeouts(certificate models.TimeoutCertificate) {
_m.Called(certificate)
}
// OnTimeoutProcessed provides a mock function with given fields: timeout
func (_m *EventLoop[StateT, VoteT]) OnTimeoutProcessed(timeout *models.TimeoutState[VoteT]) {
_m.Called(timeout)
}
// OnVoteProcessed provides a mock function with given fields: vote
func (_m *EventLoop[StateT, VoteT]) OnVoteProcessed(vote *VoteT) {
_m.Called(vote)
}
// SubmitProposal provides a mock function with given fields: proposal
func (_m *EventLoop[StateT, VoteT]) SubmitProposal(proposal *models.SignedProposal[StateT, VoteT]) {
_m.Called(proposal)
}
// NewEventLoop creates a new instance of EventLoop. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewEventLoop[StateT models.Unique, VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *EventLoop[StateT, VoteT] {
mock := &EventLoop[StateT, VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,37 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// FinalizationConsumer is an autogenerated mock type for the FinalizationConsumer type
type FinalizationConsumer[StateT models.Unique] struct {
mock.Mock
}
// OnFinalizedState provides a mock function with given fields: _a0
func (_m *FinalizationConsumer[StateT]) OnFinalizedState(_a0 *models.State[StateT]) {
_m.Called(_a0)
}
// OnStateIncorporated provides a mock function with given fields: _a0
func (_m *FinalizationConsumer[StateT]) OnStateIncorporated(_a0 *models.State[StateT]) {
_m.Called(_a0)
}
// NewFinalizationConsumer creates a new instance of FinalizationConsumer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewFinalizationConsumer[StateT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *FinalizationConsumer[StateT] {
mock := &FinalizationConsumer[StateT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,45 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// Finalizer is an autogenerated mock type for the Finalizer type
type Finalizer struct {
mock.Mock
}
// MakeFinal provides a mock function with given fields: stateID
func (_m *Finalizer) MakeFinal(stateID models.Identity) error {
ret := _m.Called(stateID)
if len(ret) == 0 {
panic("no return value specified for MakeFinal")
}
var r0 error
if rf, ok := ret.Get(0).(func(models.Identity) error); ok {
r0 = rf(stateID)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewFinalizer creates a new instance of Finalizer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewFinalizer(t interface {
mock.TestingT
Cleanup(func())
}) *Finalizer {
mock := &Finalizer{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,47 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// FollowerConsumer is an autogenerated mock type for the FollowerConsumer type
type FollowerConsumer[StateT models.Unique, VoteT models.Unique] struct {
mock.Mock
}
// OnDoubleProposeDetected provides a mock function with given fields: _a0, _a1
func (_m *FollowerConsumer[StateT, VoteT]) OnDoubleProposeDetected(_a0 *models.State[StateT], _a1 *models.State[StateT]) {
_m.Called(_a0, _a1)
}
// OnFinalizedState provides a mock function with given fields: _a0
func (_m *FollowerConsumer[StateT, VoteT]) OnFinalizedState(_a0 *models.State[StateT]) {
_m.Called(_a0)
}
// OnInvalidStateDetected provides a mock function with given fields: err
func (_m *FollowerConsumer[StateT, VoteT]) OnInvalidStateDetected(err *models.InvalidProposalError[StateT, VoteT]) {
_m.Called(err)
}
// OnStateIncorporated provides a mock function with given fields: _a0
func (_m *FollowerConsumer[StateT, VoteT]) OnStateIncorporated(_a0 *models.State[StateT]) {
_m.Called(_a0)
}
// NewFollowerConsumer creates a new instance of FollowerConsumer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewFollowerConsumer[StateT models.Unique, VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *FollowerConsumer[StateT, VoteT] {
mock := &FollowerConsumer[StateT, VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,32 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// FollowerLoop is an autogenerated mock type for the FollowerLoop type
type FollowerLoop[StateT models.Unique, VoteT models.Unique] struct {
mock.Mock
}
// AddCertifiedState provides a mock function with given fields: certifiedState
func (_m *FollowerLoop[StateT, VoteT]) AddCertifiedState(certifiedState *models.CertifiedState[StateT]) {
_m.Called(certifiedState)
}
// NewFollowerLoop creates a new instance of FollowerLoop. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewFollowerLoop[StateT models.Unique, VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *FollowerLoop[StateT, VoteT] {
mock := &FollowerLoop[StateT, VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

183
consensus/mocks/forks.go Normal file
View File

@ -0,0 +1,183 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
consensus "source.quilibrium.com/quilibrium/monorepo/consensus"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// Forks is an autogenerated mock type for the Forks type
type Forks[StateT models.Unique] struct {
mock.Mock
}
// AddCertifiedState provides a mock function with given fields: certifiedState
func (_m *Forks[StateT]) AddCertifiedState(certifiedState *models.CertifiedState[StateT]) error {
ret := _m.Called(certifiedState)
if len(ret) == 0 {
panic("no return value specified for AddCertifiedState")
}
var r0 error
if rf, ok := ret.Get(0).(func(*models.CertifiedState[StateT]) error); ok {
r0 = rf(certifiedState)
} else {
r0 = ret.Error(0)
}
return r0
}
// AddValidatedState provides a mock function with given fields: proposal
func (_m *Forks[StateT]) AddValidatedState(proposal *models.State[StateT]) error {
ret := _m.Called(proposal)
if len(ret) == 0 {
panic("no return value specified for AddValidatedState")
}
var r0 error
if rf, ok := ret.Get(0).(func(*models.State[StateT]) error); ok {
r0 = rf(proposal)
} else {
r0 = ret.Error(0)
}
return r0
}
// FinalityProof provides a mock function with no fields
func (_m *Forks[StateT]) FinalityProof() (*consensus.FinalityProof[StateT], bool) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for FinalityProof")
}
var r0 *consensus.FinalityProof[StateT]
var r1 bool
if rf, ok := ret.Get(0).(func() (*consensus.FinalityProof[StateT], bool)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() *consensus.FinalityProof[StateT]); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*consensus.FinalityProof[StateT])
}
}
if rf, ok := ret.Get(1).(func() bool); ok {
r1 = rf()
} else {
r1 = ret.Get(1).(bool)
}
return r0, r1
}
// FinalizedRank provides a mock function with no fields
func (_m *Forks[StateT]) FinalizedRank() uint64 {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for FinalizedRank")
}
var r0 uint64
if rf, ok := ret.Get(0).(func() uint64); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(uint64)
}
return r0
}
// FinalizedState provides a mock function with no fields
func (_m *Forks[StateT]) FinalizedState() *models.State[StateT] {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for FinalizedState")
}
var r0 *models.State[StateT]
if rf, ok := ret.Get(0).(func() *models.State[StateT]); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.State[StateT])
}
}
return r0
}
// GetState provides a mock function with given fields: stateID
func (_m *Forks[StateT]) GetState(stateID models.Identity) (*models.State[StateT], bool) {
ret := _m.Called(stateID)
if len(ret) == 0 {
panic("no return value specified for GetState")
}
var r0 *models.State[StateT]
var r1 bool
if rf, ok := ret.Get(0).(func(models.Identity) (*models.State[StateT], bool)); ok {
return rf(stateID)
}
if rf, ok := ret.Get(0).(func(models.Identity) *models.State[StateT]); ok {
r0 = rf(stateID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.State[StateT])
}
}
if rf, ok := ret.Get(1).(func(models.Identity) bool); ok {
r1 = rf(stateID)
} else {
r1 = ret.Get(1).(bool)
}
return r0, r1
}
// GetStatesForRank provides a mock function with given fields: rank
func (_m *Forks[StateT]) GetStatesForRank(rank uint64) []*models.State[StateT] {
ret := _m.Called(rank)
if len(ret) == 0 {
panic("no return value specified for GetStatesForRank")
}
var r0 []*models.State[StateT]
if rf, ok := ret.Get(0).(func(uint64) []*models.State[StateT]); ok {
r0 = rf(rank)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.State[StateT])
}
}
return r0
}
// NewForks creates a new instance of Forks. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewForks[StateT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *Forks[StateT] {
mock := &Forks[StateT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,89 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// LeaderProvider is an autogenerated mock type for the LeaderProvider type
type LeaderProvider[StateT models.Unique, PeerIDT models.Unique, CollectedT models.Unique] struct {
mock.Mock
}
// GetNextLeaders provides a mock function with given fields: ctx, prior
func (_m *LeaderProvider[StateT, PeerIDT, CollectedT]) GetNextLeaders(ctx context.Context, prior *StateT) ([]PeerIDT, error) {
ret := _m.Called(ctx, prior)
if len(ret) == 0 {
panic("no return value specified for GetNextLeaders")
}
var r0 []PeerIDT
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *StateT) ([]PeerIDT, error)); ok {
return rf(ctx, prior)
}
if rf, ok := ret.Get(0).(func(context.Context, *StateT) []PeerIDT); ok {
r0 = rf(ctx, prior)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]PeerIDT)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *StateT) error); ok {
r1 = rf(ctx, prior)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ProveNextState provides a mock function with given fields: ctx, filter, priorState
func (_m *LeaderProvider[StateT, PeerIDT, CollectedT]) ProveNextState(ctx context.Context, filter []byte, priorState models.Identity) (*StateT, error) {
ret := _m.Called(ctx, filter, priorState)
if len(ret) == 0 {
panic("no return value specified for ProveNextState")
}
var r0 *StateT
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, []byte, models.Identity) (*StateT, error)); ok {
return rf(ctx, filter, priorState)
}
if rf, ok := ret.Get(0).(func(context.Context, []byte, models.Identity) *StateT); ok {
r0 = rf(ctx, filter, priorState)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*StateT)
}
}
if rf, ok := ret.Get(1).(func(context.Context, []byte, models.Identity) error); ok {
r1 = rf(ctx, filter, priorState)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewLeaderProvider creates a new instance of LeaderProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewLeaderProvider[StateT models.Unique, PeerIDT models.Unique, CollectedT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *LeaderProvider[StateT, PeerIDT, CollectedT] {
mock := &LeaderProvider[StateT, PeerIDT, CollectedT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,77 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// LivenessProvider is an autogenerated mock type for the LivenessProvider type
type LivenessProvider[StateT models.Unique, PeerIDT models.Unique, CollectedT models.Unique] struct {
mock.Mock
}
// Collect provides a mock function with given fields: ctx
func (_m *LivenessProvider[StateT, PeerIDT, CollectedT]) Collect(ctx context.Context) (CollectedT, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for Collect")
}
var r0 CollectedT
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) (CollectedT, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) CollectedT); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(CollectedT)
}
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SendLiveness provides a mock function with given fields: ctx, prior, collected
func (_m *LivenessProvider[StateT, PeerIDT, CollectedT]) SendLiveness(ctx context.Context, prior *StateT, collected CollectedT) error {
ret := _m.Called(ctx, prior, collected)
if len(ret) == 0 {
panic("no return value specified for SendLiveness")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *StateT, CollectedT) error); ok {
r0 = rf(ctx, prior, collected)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewLivenessProvider creates a new instance of LivenessProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewLivenessProvider[StateT models.Unique, PeerIDT models.Unique, CollectedT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *LivenessProvider[StateT, PeerIDT, CollectedT] {
mock := &LivenessProvider[StateT, PeerIDT, CollectedT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,205 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
time "time"
)
// Pacemaker is an autogenerated mock type for the Pacemaker type
type Pacemaker struct {
mock.Mock
}
// CurrentRank provides a mock function with no fields
func (_m *Pacemaker) CurrentRank() uint64 {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for CurrentRank")
}
var r0 uint64
if rf, ok := ret.Get(0).(func() uint64); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(uint64)
}
return r0
}
// LatestQuorumCertificate provides a mock function with no fields
func (_m *Pacemaker) LatestQuorumCertificate() models.QuorumCertificate {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for LatestQuorumCertificate")
}
var r0 models.QuorumCertificate
if rf, ok := ret.Get(0).(func() models.QuorumCertificate); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(models.QuorumCertificate)
}
}
return r0
}
// PriorRankTimeoutCertificate provides a mock function with no fields
func (_m *Pacemaker) PriorRankTimeoutCertificate() models.TimeoutCertificate {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for PriorRankTimeoutCertificate")
}
var r0 models.TimeoutCertificate
if rf, ok := ret.Get(0).(func() models.TimeoutCertificate); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(models.TimeoutCertificate)
}
}
return r0
}
// ReceiveQuorumCertificate provides a mock function with given fields: quorumCertificate
func (_m *Pacemaker) ReceiveQuorumCertificate(quorumCertificate models.QuorumCertificate) (*models.NextRank, error) {
ret := _m.Called(quorumCertificate)
if len(ret) == 0 {
panic("no return value specified for ReceiveQuorumCertificate")
}
var r0 *models.NextRank
var r1 error
if rf, ok := ret.Get(0).(func(models.QuorumCertificate) (*models.NextRank, error)); ok {
return rf(quorumCertificate)
}
if rf, ok := ret.Get(0).(func(models.QuorumCertificate) *models.NextRank); ok {
r0 = rf(quorumCertificate)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.NextRank)
}
}
if rf, ok := ret.Get(1).(func(models.QuorumCertificate) error); ok {
r1 = rf(quorumCertificate)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ReceiveTimeoutCertificate provides a mock function with given fields: timeoutCertificate
func (_m *Pacemaker) ReceiveTimeoutCertificate(timeoutCertificate models.TimeoutCertificate) (*models.NextRank, error) {
ret := _m.Called(timeoutCertificate)
if len(ret) == 0 {
panic("no return value specified for ReceiveTimeoutCertificate")
}
var r0 *models.NextRank
var r1 error
if rf, ok := ret.Get(0).(func(models.TimeoutCertificate) (*models.NextRank, error)); ok {
return rf(timeoutCertificate)
}
if rf, ok := ret.Get(0).(func(models.TimeoutCertificate) *models.NextRank); ok {
r0 = rf(timeoutCertificate)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.NextRank)
}
}
if rf, ok := ret.Get(1).(func(models.TimeoutCertificate) error); ok {
r1 = rf(timeoutCertificate)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Start provides a mock function with given fields: ctx
func (_m *Pacemaker) Start(ctx context.Context) error {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for Start")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
r0 = rf(ctx)
} else {
r0 = ret.Error(0)
}
return r0
}
// TargetPublicationTime provides a mock function with given fields: proposalRank, timeRankEntered, parentStateId
func (_m *Pacemaker) TargetPublicationTime(proposalRank uint64, timeRankEntered time.Time, parentStateId models.Identity) time.Time {
ret := _m.Called(proposalRank, timeRankEntered, parentStateId)
if len(ret) == 0 {
panic("no return value specified for TargetPublicationTime")
}
var r0 time.Time
if rf, ok := ret.Get(0).(func(uint64, time.Time, models.Identity) time.Time); ok {
r0 = rf(proposalRank, timeRankEntered, parentStateId)
} else {
r0 = ret.Get(0).(time.Time)
}
return r0
}
// TimeoutCh provides a mock function with no fields
func (_m *Pacemaker) TimeoutCh() <-chan time.Time {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for TimeoutCh")
}
var r0 <-chan time.Time
if rf, ok := ret.Get(0).(func() <-chan time.Time); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(<-chan time.Time)
}
}
return r0
}
// NewPacemaker creates a new instance of Pacemaker. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewPacemaker(t interface {
mock.TestingT
Cleanup(func())
}) *Pacemaker {
mock := &Pacemaker{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

98
consensus/mocks/packer.go Normal file
View File

@ -0,0 +1,98 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
consensus "source.quilibrium.com/quilibrium/monorepo/consensus"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// Packer is an autogenerated mock type for the Packer type
type Packer struct {
mock.Mock
}
// Pack provides a mock function with given fields: rank, sig
func (_m *Packer) Pack(rank uint64, sig *consensus.StateSignatureData) ([]byte, []byte, error) {
ret := _m.Called(rank, sig)
if len(ret) == 0 {
panic("no return value specified for Pack")
}
var r0 []byte
var r1 []byte
var r2 error
if rf, ok := ret.Get(0).(func(uint64, *consensus.StateSignatureData) ([]byte, []byte, error)); ok {
return rf(rank, sig)
}
if rf, ok := ret.Get(0).(func(uint64, *consensus.StateSignatureData) []byte); ok {
r0 = rf(rank, sig)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]byte)
}
}
if rf, ok := ret.Get(1).(func(uint64, *consensus.StateSignatureData) []byte); ok {
r1 = rf(rank, sig)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).([]byte)
}
}
if rf, ok := ret.Get(2).(func(uint64, *consensus.StateSignatureData) error); ok {
r2 = rf(rank, sig)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// Unpack provides a mock function with given fields: signerIdentities, sigData
func (_m *Packer) Unpack(signerIdentities []models.WeightedIdentity, sigData []byte) (*consensus.StateSignatureData, error) {
ret := _m.Called(signerIdentities, sigData)
if len(ret) == 0 {
panic("no return value specified for Unpack")
}
var r0 *consensus.StateSignatureData
var r1 error
if rf, ok := ret.Get(0).(func([]models.WeightedIdentity, []byte) (*consensus.StateSignatureData, error)); ok {
return rf(signerIdentities, sigData)
}
if rf, ok := ret.Get(0).(func([]models.WeightedIdentity, []byte) *consensus.StateSignatureData); ok {
r0 = rf(signerIdentities, sigData)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*consensus.StateSignatureData)
}
}
if rf, ok := ret.Get(1).(func([]models.WeightedIdentity, []byte) error); ok {
r1 = rf(signerIdentities, sigData)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewPacker creates a new instance of Packer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewPacker(t interface {
mock.TestingT
Cleanup(func())
}) *Packer {
mock := &Packer{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,91 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
consensus "source.quilibrium.com/quilibrium/monorepo/consensus"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
time "time"
)
// ParticipantConsumer is an autogenerated mock type for the ParticipantConsumer type
type ParticipantConsumer[StateT models.Unique, VoteT models.Unique] struct {
mock.Mock
}
// OnCurrentRankDetails provides a mock function with given fields: currentRank, finalizedRank, currentLeader
func (_m *ParticipantConsumer[StateT, VoteT]) OnCurrentRankDetails(currentRank uint64, finalizedRank uint64, currentLeader models.Identity) {
_m.Called(currentRank, finalizedRank, currentLeader)
}
// OnEventProcessed provides a mock function with no fields
func (_m *ParticipantConsumer[StateT, VoteT]) OnEventProcessed() {
_m.Called()
}
// OnLocalTimeout provides a mock function with given fields: currentRank
func (_m *ParticipantConsumer[StateT, VoteT]) OnLocalTimeout(currentRank uint64) {
_m.Called(currentRank)
}
// OnPartialTimeoutCertificate provides a mock function with given fields: currentRank, partialTimeoutCertificate
func (_m *ParticipantConsumer[StateT, VoteT]) OnPartialTimeoutCertificate(currentRank uint64, partialTimeoutCertificate *consensus.PartialTimeoutCertificateCreated) {
_m.Called(currentRank, partialTimeoutCertificate)
}
// OnQuorumCertificateTriggeredRankChange provides a mock function with given fields: oldRank, newRank, qc
func (_m *ParticipantConsumer[StateT, VoteT]) OnQuorumCertificateTriggeredRankChange(oldRank uint64, newRank uint64, qc models.QuorumCertificate) {
_m.Called(oldRank, newRank, qc)
}
// OnRankChange provides a mock function with given fields: oldRank, newRank
func (_m *ParticipantConsumer[StateT, VoteT]) OnRankChange(oldRank uint64, newRank uint64) {
_m.Called(oldRank, newRank)
}
// OnReceiveProposal provides a mock function with given fields: currentRank, proposal
func (_m *ParticipantConsumer[StateT, VoteT]) OnReceiveProposal(currentRank uint64, proposal *models.SignedProposal[StateT, VoteT]) {
_m.Called(currentRank, proposal)
}
// OnReceiveQuorumCertificate provides a mock function with given fields: currentRank, qc
func (_m *ParticipantConsumer[StateT, VoteT]) OnReceiveQuorumCertificate(currentRank uint64, qc models.QuorumCertificate) {
_m.Called(currentRank, qc)
}
// OnReceiveTimeoutCertificate provides a mock function with given fields: currentRank, tc
func (_m *ParticipantConsumer[StateT, VoteT]) OnReceiveTimeoutCertificate(currentRank uint64, tc models.TimeoutCertificate) {
_m.Called(currentRank, tc)
}
// OnStart provides a mock function with given fields: currentRank
func (_m *ParticipantConsumer[StateT, VoteT]) OnStart(currentRank uint64) {
_m.Called(currentRank)
}
// OnStartingTimeout provides a mock function with given fields: startTime, endTime
func (_m *ParticipantConsumer[StateT, VoteT]) OnStartingTimeout(startTime time.Time, endTime time.Time) {
_m.Called(startTime, endTime)
}
// OnTimeoutCertificateTriggeredRankChange provides a mock function with given fields: oldRank, newRank, tc
func (_m *ParticipantConsumer[StateT, VoteT]) OnTimeoutCertificateTriggeredRankChange(oldRank uint64, newRank uint64, tc models.TimeoutCertificate) {
_m.Called(oldRank, newRank, tc)
}
// NewParticipantConsumer creates a new instance of ParticipantConsumer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewParticipantConsumer[StateT models.Unique, VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *ParticipantConsumer[StateT, VoteT] {
mock := &ParticipantConsumer[StateT, VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,47 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
time "time"
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// ProposalDurationProvider is an autogenerated mock type for the ProposalDurationProvider type
type ProposalDurationProvider struct {
mock.Mock
}
// TargetPublicationTime provides a mock function with given fields: proposalRank, timeRankEntered, parentStateId
func (_m *ProposalDurationProvider) TargetPublicationTime(proposalRank uint64, timeRankEntered time.Time, parentStateId models.Identity) time.Time {
ret := _m.Called(proposalRank, timeRankEntered, parentStateId)
if len(ret) == 0 {
panic("no return value specified for TargetPublicationTime")
}
var r0 time.Time
if rf, ok := ret.Get(0).(func(uint64, time.Time, models.Identity) time.Time); ok {
r0 = rf(proposalRank, timeRankEntered, parentStateId)
} else {
r0 = ret.Get(0).(time.Time)
}
return r0
}
// NewProposalDurationProvider creates a new instance of ProposalDurationProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewProposalDurationProvider(t interface {
mock.TestingT
Cleanup(func())
}) *ProposalDurationProvider {
mock := &ProposalDurationProvider{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,37 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// ProposalViolationConsumer is an autogenerated mock type for the ProposalViolationConsumer type
type ProposalViolationConsumer[StateT models.Unique, VoteT models.Unique] struct {
mock.Mock
}
// OnDoubleProposeDetected provides a mock function with given fields: _a0, _a1
func (_m *ProposalViolationConsumer[StateT, VoteT]) OnDoubleProposeDetected(_a0 *models.State[StateT], _a1 *models.State[StateT]) {
_m.Called(_a0, _a1)
}
// OnInvalidStateDetected provides a mock function with given fields: err
func (_m *ProposalViolationConsumer[StateT, VoteT]) OnInvalidStateDetected(err *models.InvalidProposalError[StateT, VoteT]) {
_m.Called(err)
}
// NewProposalViolationConsumer creates a new instance of ProposalViolationConsumer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewProposalViolationConsumer[StateT models.Unique, VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *ProposalViolationConsumer[StateT, VoteT] {
mock := &ProposalViolationConsumer[StateT, VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,87 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// ReadOnlyConsensusStore is an autogenerated mock type for the ReadOnlyConsensusStore type
type ReadOnlyConsensusStore[VoteT models.Unique] struct {
mock.Mock
}
// GetConsensusState provides a mock function with no fields
func (_m *ReadOnlyConsensusStore[VoteT]) GetConsensusState() (*models.ConsensusState[VoteT], error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for GetConsensusState")
}
var r0 *models.ConsensusState[VoteT]
var r1 error
if rf, ok := ret.Get(0).(func() (*models.ConsensusState[VoteT], error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() *models.ConsensusState[VoteT]); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.ConsensusState[VoteT])
}
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetLivenessState provides a mock function with no fields
func (_m *ReadOnlyConsensusStore[VoteT]) GetLivenessState() (*models.LivenessState, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for GetLivenessState")
}
var r0 *models.LivenessState
var r1 error
if rf, ok := ret.Get(0).(func() (*models.LivenessState, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() *models.LivenessState); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.LivenessState)
}
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewReadOnlyConsensusStore creates a new instance of ReadOnlyConsensusStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewReadOnlyConsensusStore[VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *ReadOnlyConsensusStore[VoteT] {
mock := &ReadOnlyConsensusStore[VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

189
consensus/mocks/replicas.go Normal file
View File

@ -0,0 +1,189 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// Replicas is an autogenerated mock type for the Replicas type
type Replicas struct {
mock.Mock
}
// IdentitiesByRank provides a mock function with given fields: rank
func (_m *Replicas) IdentitiesByRank(rank uint64) ([]models.WeightedIdentity, error) {
ret := _m.Called(rank)
if len(ret) == 0 {
panic("no return value specified for IdentitiesByRank")
}
var r0 []models.WeightedIdentity
var r1 error
if rf, ok := ret.Get(0).(func(uint64) ([]models.WeightedIdentity, error)); ok {
return rf(rank)
}
if rf, ok := ret.Get(0).(func(uint64) []models.WeightedIdentity); ok {
r0 = rf(rank)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]models.WeightedIdentity)
}
}
if rf, ok := ret.Get(1).(func(uint64) error); ok {
r1 = rf(rank)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// IdentityByRank provides a mock function with given fields: rank, participantID
func (_m *Replicas) IdentityByRank(rank uint64, participantID models.Identity) (models.WeightedIdentity, error) {
ret := _m.Called(rank, participantID)
if len(ret) == 0 {
panic("no return value specified for IdentityByRank")
}
var r0 models.WeightedIdentity
var r1 error
if rf, ok := ret.Get(0).(func(uint64, models.Identity) (models.WeightedIdentity, error)); ok {
return rf(rank, participantID)
}
if rf, ok := ret.Get(0).(func(uint64, models.Identity) models.WeightedIdentity); ok {
r0 = rf(rank, participantID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(models.WeightedIdentity)
}
}
if rf, ok := ret.Get(1).(func(uint64, models.Identity) error); ok {
r1 = rf(rank, participantID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// LeaderForRank provides a mock function with given fields: rank
func (_m *Replicas) LeaderForRank(rank uint64) (models.Identity, error) {
ret := _m.Called(rank)
if len(ret) == 0 {
panic("no return value specified for LeaderForRank")
}
var r0 models.Identity
var r1 error
if rf, ok := ret.Get(0).(func(uint64) (models.Identity, error)); ok {
return rf(rank)
}
if rf, ok := ret.Get(0).(func(uint64) models.Identity); ok {
r0 = rf(rank)
} else {
r0 = ret.Get(0).(models.Identity)
}
if rf, ok := ret.Get(1).(func(uint64) error); ok {
r1 = rf(rank)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// QuorumThresholdForRank provides a mock function with given fields: rank
func (_m *Replicas) QuorumThresholdForRank(rank uint64) (uint64, error) {
ret := _m.Called(rank)
if len(ret) == 0 {
panic("no return value specified for QuorumThresholdForRank")
}
var r0 uint64
var r1 error
if rf, ok := ret.Get(0).(func(uint64) (uint64, error)); ok {
return rf(rank)
}
if rf, ok := ret.Get(0).(func(uint64) uint64); ok {
r0 = rf(rank)
} else {
r0 = ret.Get(0).(uint64)
}
if rf, ok := ret.Get(1).(func(uint64) error); ok {
r1 = rf(rank)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Self provides a mock function with no fields
func (_m *Replicas) Self() models.Identity {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Self")
}
var r0 models.Identity
if rf, ok := ret.Get(0).(func() models.Identity); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(models.Identity)
}
return r0
}
// TimeoutThresholdForRank provides a mock function with given fields: rank
func (_m *Replicas) TimeoutThresholdForRank(rank uint64) (uint64, error) {
ret := _m.Called(rank)
if len(ret) == 0 {
panic("no return value specified for TimeoutThresholdForRank")
}
var r0 uint64
var r1 error
if rf, ok := ret.Get(0).(func(uint64) (uint64, error)); ok {
return rf(rank)
}
if rf, ok := ret.Get(0).(func(uint64) uint64); ok {
r0 = rf(rank)
} else {
r0 = ret.Get(0).(uint64)
}
if rf, ok := ret.Get(1).(func(uint64) error); ok {
r1 = rf(rank)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewReplicas creates a new instance of Replicas. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewReplicas(t interface {
mock.TestingT
Cleanup(func())
}) *Replicas {
mock := &Replicas{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,117 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// SafetyRules is an autogenerated mock type for the SafetyRules type
type SafetyRules[StateT models.Unique, VoteT models.Unique] struct {
mock.Mock
}
// ProduceTimeout provides a mock function with given fields: curRank, newestQC, lastRankTC
func (_m *SafetyRules[StateT, VoteT]) ProduceTimeout(curRank uint64, newestQC models.QuorumCertificate, lastRankTC models.TimeoutCertificate) (*models.TimeoutState[VoteT], error) {
ret := _m.Called(curRank, newestQC, lastRankTC)
if len(ret) == 0 {
panic("no return value specified for ProduceTimeout")
}
var r0 *models.TimeoutState[VoteT]
var r1 error
if rf, ok := ret.Get(0).(func(uint64, models.QuorumCertificate, models.TimeoutCertificate) (*models.TimeoutState[VoteT], error)); ok {
return rf(curRank, newestQC, lastRankTC)
}
if rf, ok := ret.Get(0).(func(uint64, models.QuorumCertificate, models.TimeoutCertificate) *models.TimeoutState[VoteT]); ok {
r0 = rf(curRank, newestQC, lastRankTC)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.TimeoutState[VoteT])
}
}
if rf, ok := ret.Get(1).(func(uint64, models.QuorumCertificate, models.TimeoutCertificate) error); ok {
r1 = rf(curRank, newestQC, lastRankTC)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ProduceVote provides a mock function with given fields: proposal, curRank
func (_m *SafetyRules[StateT, VoteT]) ProduceVote(proposal *models.SignedProposal[StateT, VoteT], curRank uint64) (*VoteT, error) {
ret := _m.Called(proposal, curRank)
if len(ret) == 0 {
panic("no return value specified for ProduceVote")
}
var r0 *VoteT
var r1 error
if rf, ok := ret.Get(0).(func(*models.SignedProposal[StateT, VoteT], uint64) (*VoteT, error)); ok {
return rf(proposal, curRank)
}
if rf, ok := ret.Get(0).(func(*models.SignedProposal[StateT, VoteT], uint64) *VoteT); ok {
r0 = rf(proposal, curRank)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*VoteT)
}
}
if rf, ok := ret.Get(1).(func(*models.SignedProposal[StateT, VoteT], uint64) error); ok {
r1 = rf(proposal, curRank)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SignOwnProposal provides a mock function with given fields: unsignedProposal
func (_m *SafetyRules[StateT, VoteT]) SignOwnProposal(unsignedProposal *models.Proposal[StateT]) (*VoteT, error) {
ret := _m.Called(unsignedProposal)
if len(ret) == 0 {
panic("no return value specified for SignOwnProposal")
}
var r0 *VoteT
var r1 error
if rf, ok := ret.Get(0).(func(*models.Proposal[StateT]) (*VoteT, error)); ok {
return rf(unsignedProposal)
}
if rf, ok := ret.Get(0).(func(*models.Proposal[StateT]) *VoteT); ok {
r0 = rf(unsignedProposal)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*VoteT)
}
}
if rf, ok := ret.Get(1).(func(*models.Proposal[StateT]) error); ok {
r1 = rf(unsignedProposal)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewSafetyRules creates a new instance of SafetyRules. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewSafetyRules[StateT models.Unique, VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *SafetyRules[StateT, VoteT] {
mock := &SafetyRules[StateT, VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,93 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// SignatureAggregator is an autogenerated mock type for the SignatureAggregator type
type SignatureAggregator struct {
mock.Mock
}
// Aggregate provides a mock function with given fields: publicKeys, signatures
func (_m *SignatureAggregator) Aggregate(publicKeys [][]byte, signatures [][]byte) (models.AggregatedSignature, error) {
ret := _m.Called(publicKeys, signatures)
if len(ret) == 0 {
panic("no return value specified for Aggregate")
}
var r0 models.AggregatedSignature
var r1 error
if rf, ok := ret.Get(0).(func([][]byte, [][]byte) (models.AggregatedSignature, error)); ok {
return rf(publicKeys, signatures)
}
if rf, ok := ret.Get(0).(func([][]byte, [][]byte) models.AggregatedSignature); ok {
r0 = rf(publicKeys, signatures)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(models.AggregatedSignature)
}
}
if rf, ok := ret.Get(1).(func([][]byte, [][]byte) error); ok {
r1 = rf(publicKeys, signatures)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// VerifySignatureMultiMessage provides a mock function with given fields: publicKeys, signature, messages, context
func (_m *SignatureAggregator) VerifySignatureMultiMessage(publicKeys [][]byte, signature []byte, messages [][]byte, context []byte) bool {
ret := _m.Called(publicKeys, signature, messages, context)
if len(ret) == 0 {
panic("no return value specified for VerifySignatureMultiMessage")
}
var r0 bool
if rf, ok := ret.Get(0).(func([][]byte, []byte, [][]byte, []byte) bool); ok {
r0 = rf(publicKeys, signature, messages, context)
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// VerifySignatureRaw provides a mock function with given fields: publicKey, signature, message, context
func (_m *SignatureAggregator) VerifySignatureRaw(publicKey []byte, signature []byte, message []byte, context []byte) bool {
ret := _m.Called(publicKey, signature, message, context)
if len(ret) == 0 {
panic("no return value specified for VerifySignatureRaw")
}
var r0 bool
if rf, ok := ret.Get(0).(func([]byte, []byte, []byte, []byte) bool); ok {
r0 = rf(publicKey, signature, message, context)
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// NewSignatureAggregator creates a new instance of SignatureAggregator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewSignatureAggregator(t interface {
mock.TestingT
Cleanup(func())
}) *SignatureAggregator {
mock := &SignatureAggregator{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

87
consensus/mocks/signer.go Normal file
View File

@ -0,0 +1,87 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// Signer is an autogenerated mock type for the Signer type
type Signer[StateT models.Unique, VoteT models.Unique] struct {
mock.Mock
}
// CreateTimeout provides a mock function with given fields: curView, newestQC, previousRankTimeoutCert
func (_m *Signer[StateT, VoteT]) CreateTimeout(curView uint64, newestQC models.QuorumCertificate, previousRankTimeoutCert models.TimeoutCertificate) (*models.TimeoutState[VoteT], error) {
ret := _m.Called(curView, newestQC, previousRankTimeoutCert)
if len(ret) == 0 {
panic("no return value specified for CreateTimeout")
}
var r0 *models.TimeoutState[VoteT]
var r1 error
if rf, ok := ret.Get(0).(func(uint64, models.QuorumCertificate, models.TimeoutCertificate) (*models.TimeoutState[VoteT], error)); ok {
return rf(curView, newestQC, previousRankTimeoutCert)
}
if rf, ok := ret.Get(0).(func(uint64, models.QuorumCertificate, models.TimeoutCertificate) *models.TimeoutState[VoteT]); ok {
r0 = rf(curView, newestQC, previousRankTimeoutCert)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.TimeoutState[VoteT])
}
}
if rf, ok := ret.Get(1).(func(uint64, models.QuorumCertificate, models.TimeoutCertificate) error); ok {
r1 = rf(curView, newestQC, previousRankTimeoutCert)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateVote provides a mock function with given fields: state
func (_m *Signer[StateT, VoteT]) CreateVote(state *models.State[StateT]) (*VoteT, error) {
ret := _m.Called(state)
if len(ret) == 0 {
panic("no return value specified for CreateVote")
}
var r0 *VoteT
var r1 error
if rf, ok := ret.Get(0).(func(*models.State[StateT]) (*VoteT, error)); ok {
return rf(state)
}
if rf, ok := ret.Get(0).(func(*models.State[StateT]) *VoteT); ok {
r0 = rf(state)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*VoteT)
}
}
if rf, ok := ret.Get(1).(func(*models.State[StateT]) error); ok {
r1 = rf(state)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewSigner creates a new instance of Signer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewSigner[StateT models.Unique, VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *Signer[StateT, VoteT] {
mock := &Signer[StateT, VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,57 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// StateProducer is an autogenerated mock type for the StateProducer type
type StateProducer[StateT models.Unique, VoteT models.Unique] struct {
mock.Mock
}
// MakeStateProposal provides a mock function with given fields: rank, qc, lastRankTC
func (_m *StateProducer[StateT, VoteT]) MakeStateProposal(rank uint64, qc models.QuorumCertificate, lastRankTC models.TimeoutCertificate) (*models.SignedProposal[StateT, VoteT], error) {
ret := _m.Called(rank, qc, lastRankTC)
if len(ret) == 0 {
panic("no return value specified for MakeStateProposal")
}
var r0 *models.SignedProposal[StateT, VoteT]
var r1 error
if rf, ok := ret.Get(0).(func(uint64, models.QuorumCertificate, models.TimeoutCertificate) (*models.SignedProposal[StateT, VoteT], error)); ok {
return rf(rank, qc, lastRankTC)
}
if rf, ok := ret.Get(0).(func(uint64, models.QuorumCertificate, models.TimeoutCertificate) *models.SignedProposal[StateT, VoteT]); ok {
r0 = rf(rank, qc, lastRankTC)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.SignedProposal[StateT, VoteT])
}
}
if rf, ok := ret.Get(1).(func(uint64, models.QuorumCertificate, models.TimeoutCertificate) error); ok {
r1 = rf(rank, qc, lastRankTC)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewStateProducer creates a new instance of StateProducer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewStateProducer[StateT models.Unique, VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *StateProducer[StateT, VoteT] {
mock := &StateProducer[StateT, VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,57 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// StateSignerDecoder is an autogenerated mock type for the StateSignerDecoder type
type StateSignerDecoder[StateT models.Unique] struct {
mock.Mock
}
// DecodeSignerIDs provides a mock function with given fields: state
func (_m *StateSignerDecoder[StateT]) DecodeSignerIDs(state *models.State[StateT]) ([]models.WeightedIdentity, error) {
ret := _m.Called(state)
if len(ret) == 0 {
panic("no return value specified for DecodeSignerIDs")
}
var r0 []models.WeightedIdentity
var r1 error
if rf, ok := ret.Get(0).(func(*models.State[StateT]) ([]models.WeightedIdentity, error)); ok {
return rf(state)
}
if rf, ok := ret.Get(0).(func(*models.State[StateT]) []models.WeightedIdentity); ok {
r0 = rf(state)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]models.WeightedIdentity)
}
}
if rf, ok := ret.Get(1).(func(*models.State[StateT]) error); ok {
r1 = rf(state)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewStateSignerDecoder creates a new instance of StateSignerDecoder. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewStateSignerDecoder[StateT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *StateSignerDecoder[StateT] {
mock := &StateSignerDecoder[StateT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,61 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// SyncProvider is an autogenerated mock type for the SyncProvider type
type SyncProvider[StateT models.Unique] struct {
mock.Mock
}
// Synchronize provides a mock function with given fields: ctx, existing
func (_m *SyncProvider[StateT]) Synchronize(ctx context.Context, existing *StateT) (<-chan *StateT, <-chan error) {
ret := _m.Called(ctx, existing)
if len(ret) == 0 {
panic("no return value specified for Synchronize")
}
var r0 <-chan *StateT
var r1 <-chan error
if rf, ok := ret.Get(0).(func(context.Context, *StateT) (<-chan *StateT, <-chan error)); ok {
return rf(ctx, existing)
}
if rf, ok := ret.Get(0).(func(context.Context, *StateT) <-chan *StateT); ok {
r0 = rf(ctx, existing)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(<-chan *StateT)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *StateT) <-chan error); ok {
r1 = rf(ctx, existing)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(<-chan error)
}
}
return r0, r1
}
// NewSyncProvider creates a new instance of SyncProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewSyncProvider[StateT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *SyncProvider[StateT] {
mock := &SyncProvider[StateT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,62 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// TimeoutAggregationConsumer is an autogenerated mock type for the TimeoutAggregationConsumer type
type TimeoutAggregationConsumer[VoteT models.Unique] struct {
mock.Mock
}
// OnDoubleTimeoutDetected provides a mock function with given fields: _a0, _a1
func (_m *TimeoutAggregationConsumer[VoteT]) OnDoubleTimeoutDetected(_a0 *models.TimeoutState[VoteT], _a1 *models.TimeoutState[VoteT]) {
_m.Called(_a0, _a1)
}
// OnInvalidTimeoutDetected provides a mock function with given fields: err
func (_m *TimeoutAggregationConsumer[VoteT]) OnInvalidTimeoutDetected(err models.InvalidTimeoutError[VoteT]) {
_m.Called(err)
}
// OnNewQuorumCertificateDiscovered provides a mock function with given fields: certificate
func (_m *TimeoutAggregationConsumer[VoteT]) OnNewQuorumCertificateDiscovered(certificate models.QuorumCertificate) {
_m.Called(certificate)
}
// OnNewTimeoutCertificateDiscovered provides a mock function with given fields: certificate
func (_m *TimeoutAggregationConsumer[VoteT]) OnNewTimeoutCertificateDiscovered(certificate models.TimeoutCertificate) {
_m.Called(certificate)
}
// OnPartialTimeoutCertificateCreated provides a mock function with given fields: rank, newestQC, lastRankTC
func (_m *TimeoutAggregationConsumer[VoteT]) OnPartialTimeoutCertificateCreated(rank uint64, newestQC models.QuorumCertificate, lastRankTC models.TimeoutCertificate) {
_m.Called(rank, newestQC, lastRankTC)
}
// OnTimeoutCertificateConstructedFromTimeouts provides a mock function with given fields: certificate
func (_m *TimeoutAggregationConsumer[VoteT]) OnTimeoutCertificateConstructedFromTimeouts(certificate models.TimeoutCertificate) {
_m.Called(certificate)
}
// OnTimeoutProcessed provides a mock function with given fields: timeout
func (_m *TimeoutAggregationConsumer[VoteT]) OnTimeoutProcessed(timeout *models.TimeoutState[VoteT]) {
_m.Called(timeout)
}
// NewTimeoutAggregationConsumer creates a new instance of TimeoutAggregationConsumer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewTimeoutAggregationConsumer[VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *TimeoutAggregationConsumer[VoteT] {
mock := &TimeoutAggregationConsumer[VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,37 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// TimeoutAggregationViolationConsumer is an autogenerated mock type for the TimeoutAggregationViolationConsumer type
type TimeoutAggregationViolationConsumer[VoteT models.Unique] struct {
mock.Mock
}
// OnDoubleTimeoutDetected provides a mock function with given fields: _a0, _a1
func (_m *TimeoutAggregationViolationConsumer[VoteT]) OnDoubleTimeoutDetected(_a0 *models.TimeoutState[VoteT], _a1 *models.TimeoutState[VoteT]) {
_m.Called(_a0, _a1)
}
// OnInvalidTimeoutDetected provides a mock function with given fields: err
func (_m *TimeoutAggregationViolationConsumer[VoteT]) OnInvalidTimeoutDetected(err models.InvalidTimeoutError[VoteT]) {
_m.Called(err)
}
// NewTimeoutAggregationViolationConsumer creates a new instance of TimeoutAggregationViolationConsumer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewTimeoutAggregationViolationConsumer[VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *TimeoutAggregationViolationConsumer[VoteT] {
mock := &TimeoutAggregationViolationConsumer[VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,57 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// TimeoutAggregator is an autogenerated mock type for the TimeoutAggregator type
type TimeoutAggregator[VoteT models.Unique] struct {
mock.Mock
}
// AddTimeout provides a mock function with given fields: timeoutState
func (_m *TimeoutAggregator[VoteT]) AddTimeout(timeoutState *models.TimeoutState[VoteT]) {
_m.Called(timeoutState)
}
// PruneUpToRank provides a mock function with given fields: lowestRetainedRank
func (_m *TimeoutAggregator[VoteT]) PruneUpToRank(lowestRetainedRank uint64) {
_m.Called(lowestRetainedRank)
}
// Start provides a mock function with given fields: ctx
func (_m *TimeoutAggregator[VoteT]) Start(ctx context.Context) error {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for Start")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
r0 = rf(ctx)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewTimeoutAggregator creates a new instance of TimeoutAggregator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewTimeoutAggregator[VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *TimeoutAggregator[VoteT] {
mock := &TimeoutAggregator[VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,63 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// TimeoutCollector is an autogenerated mock type for the TimeoutCollector type
type TimeoutCollector[VoteT models.Unique] struct {
mock.Mock
}
// AddTimeout provides a mock function with given fields: timeoutState
func (_m *TimeoutCollector[VoteT]) AddTimeout(timeoutState *models.TimeoutState[VoteT]) error {
ret := _m.Called(timeoutState)
if len(ret) == 0 {
panic("no return value specified for AddTimeout")
}
var r0 error
if rf, ok := ret.Get(0).(func(*models.TimeoutState[VoteT]) error); ok {
r0 = rf(timeoutState)
} else {
r0 = ret.Error(0)
}
return r0
}
// Rank provides a mock function with no fields
func (_m *TimeoutCollector[VoteT]) Rank() uint64 {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Rank")
}
var r0 uint64
if rf, ok := ret.Get(0).(func() uint64); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(uint64)
}
return r0
}
// NewTimeoutCollector creates a new instance of TimeoutCollector. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewTimeoutCollector[VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *TimeoutCollector[VoteT] {
mock := &TimeoutCollector[VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,52 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// TimeoutCollectorConsumer is an autogenerated mock type for the TimeoutCollectorConsumer type
type TimeoutCollectorConsumer[VoteT models.Unique] struct {
mock.Mock
}
// OnNewQuorumCertificateDiscovered provides a mock function with given fields: certificate
func (_m *TimeoutCollectorConsumer[VoteT]) OnNewQuorumCertificateDiscovered(certificate models.QuorumCertificate) {
_m.Called(certificate)
}
// OnNewTimeoutCertificateDiscovered provides a mock function with given fields: certificate
func (_m *TimeoutCollectorConsumer[VoteT]) OnNewTimeoutCertificateDiscovered(certificate models.TimeoutCertificate) {
_m.Called(certificate)
}
// OnPartialTimeoutCertificateCreated provides a mock function with given fields: rank, newestQC, lastRankTC
func (_m *TimeoutCollectorConsumer[VoteT]) OnPartialTimeoutCertificateCreated(rank uint64, newestQC models.QuorumCertificate, lastRankTC models.TimeoutCertificate) {
_m.Called(rank, newestQC, lastRankTC)
}
// OnTimeoutCertificateConstructedFromTimeouts provides a mock function with given fields: certificate
func (_m *TimeoutCollectorConsumer[VoteT]) OnTimeoutCertificateConstructedFromTimeouts(certificate models.TimeoutCertificate) {
_m.Called(certificate)
}
// OnTimeoutProcessed provides a mock function with given fields: timeout
func (_m *TimeoutCollectorConsumer[VoteT]) OnTimeoutProcessed(timeout *models.TimeoutState[VoteT]) {
_m.Called(timeout)
}
// NewTimeoutCollectorConsumer creates a new instance of TimeoutCollectorConsumer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewTimeoutCollectorConsumer[VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *TimeoutCollectorConsumer[VoteT] {
mock := &TimeoutCollectorConsumer[VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,59 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
consensus "source.quilibrium.com/quilibrium/monorepo/consensus"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// TimeoutCollectorFactory is an autogenerated mock type for the TimeoutCollectorFactory type
type TimeoutCollectorFactory[VoteT models.Unique] struct {
mock.Mock
}
// Create provides a mock function with given fields: rank
func (_m *TimeoutCollectorFactory[VoteT]) Create(rank uint64) (consensus.TimeoutCollector[VoteT], error) {
ret := _m.Called(rank)
if len(ret) == 0 {
panic("no return value specified for Create")
}
var r0 consensus.TimeoutCollector[VoteT]
var r1 error
if rf, ok := ret.Get(0).(func(uint64) (consensus.TimeoutCollector[VoteT], error)); ok {
return rf(rank)
}
if rf, ok := ret.Get(0).(func(uint64) consensus.TimeoutCollector[VoteT]); ok {
r0 = rf(rank)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(consensus.TimeoutCollector[VoteT])
}
}
if rf, ok := ret.Get(1).(func(uint64) error); ok {
r1 = rf(rank)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewTimeoutCollectorFactory creates a new instance of TimeoutCollectorFactory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewTimeoutCollectorFactory[VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *TimeoutCollectorFactory[VoteT] {
mock := &TimeoutCollectorFactory[VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,71 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
consensus "source.quilibrium.com/quilibrium/monorepo/consensus"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// TimeoutCollectors is an autogenerated mock type for the TimeoutCollectors type
type TimeoutCollectors[VoteT models.Unique] struct {
mock.Mock
}
// GetOrCreateCollector provides a mock function with given fields: rank
func (_m *TimeoutCollectors[VoteT]) GetOrCreateCollector(rank uint64) (consensus.TimeoutCollector[VoteT], bool, error) {
ret := _m.Called(rank)
if len(ret) == 0 {
panic("no return value specified for GetOrCreateCollector")
}
var r0 consensus.TimeoutCollector[VoteT]
var r1 bool
var r2 error
if rf, ok := ret.Get(0).(func(uint64) (consensus.TimeoutCollector[VoteT], bool, error)); ok {
return rf(rank)
}
if rf, ok := ret.Get(0).(func(uint64) consensus.TimeoutCollector[VoteT]); ok {
r0 = rf(rank)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(consensus.TimeoutCollector[VoteT])
}
}
if rf, ok := ret.Get(1).(func(uint64) bool); ok {
r1 = rf(rank)
} else {
r1 = ret.Get(1).(bool)
}
if rf, ok := ret.Get(2).(func(uint64) error); ok {
r2 = rf(rank)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// PruneUpToRank provides a mock function with given fields: lowestRetainedRank
func (_m *TimeoutCollectors[VoteT]) PruneUpToRank(lowestRetainedRank uint64) {
_m.Called(lowestRetainedRank)
}
// NewTimeoutCollectors creates a new instance of TimeoutCollectors. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewTimeoutCollectors[VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *TimeoutCollectors[VoteT] {
mock := &TimeoutCollectors[VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,45 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// TimeoutProcessor is an autogenerated mock type for the TimeoutProcessor type
type TimeoutProcessor[VoteT models.Unique] struct {
mock.Mock
}
// Process provides a mock function with given fields: timeout
func (_m *TimeoutProcessor[VoteT]) Process(timeout *models.TimeoutState[VoteT]) error {
ret := _m.Called(timeout)
if len(ret) == 0 {
panic("no return value specified for Process")
}
var r0 error
if rf, ok := ret.Get(0).(func(*models.TimeoutState[VoteT]) error); ok {
r0 = rf(timeout)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewTimeoutProcessor creates a new instance of TimeoutProcessor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewTimeoutProcessor[VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *TimeoutProcessor[VoteT] {
mock := &TimeoutProcessor[VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,59 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
consensus "source.quilibrium.com/quilibrium/monorepo/consensus"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// TimeoutProcessorFactory is an autogenerated mock type for the TimeoutProcessorFactory type
type TimeoutProcessorFactory[VoteT models.Unique] struct {
mock.Mock
}
// Create provides a mock function with given fields: rank
func (_m *TimeoutProcessorFactory[VoteT]) Create(rank uint64) (consensus.TimeoutProcessor[VoteT], error) {
ret := _m.Called(rank)
if len(ret) == 0 {
panic("no return value specified for Create")
}
var r0 consensus.TimeoutProcessor[VoteT]
var r1 error
if rf, ok := ret.Get(0).(func(uint64) (consensus.TimeoutProcessor[VoteT], error)); ok {
return rf(rank)
}
if rf, ok := ret.Get(0).(func(uint64) consensus.TimeoutProcessor[VoteT]); ok {
r0 = rf(rank)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(consensus.TimeoutProcessor[VoteT])
}
}
if rf, ok := ret.Get(1).(func(uint64) error); ok {
r1 = rf(rank)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewTimeoutProcessorFactory creates a new instance of TimeoutProcessorFactory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewTimeoutProcessorFactory[VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *TimeoutProcessorFactory[VoteT] {
mock := &TimeoutProcessorFactory[VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,132 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
consensus "source.quilibrium.com/quilibrium/monorepo/consensus"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// TimeoutSignatureAggregator is an autogenerated mock type for the TimeoutSignatureAggregator type
type TimeoutSignatureAggregator struct {
mock.Mock
}
// Aggregate provides a mock function with no fields
func (_m *TimeoutSignatureAggregator) Aggregate() ([]consensus.TimeoutSignerInfo, models.AggregatedSignature, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Aggregate")
}
var r0 []consensus.TimeoutSignerInfo
var r1 models.AggregatedSignature
var r2 error
if rf, ok := ret.Get(0).(func() ([]consensus.TimeoutSignerInfo, models.AggregatedSignature, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() []consensus.TimeoutSignerInfo); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]consensus.TimeoutSignerInfo)
}
}
if rf, ok := ret.Get(1).(func() models.AggregatedSignature); ok {
r1 = rf()
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(models.AggregatedSignature)
}
}
if rf, ok := ret.Get(2).(func() error); ok {
r2 = rf()
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// Rank provides a mock function with no fields
func (_m *TimeoutSignatureAggregator) Rank() uint64 {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Rank")
}
var r0 uint64
if rf, ok := ret.Get(0).(func() uint64); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(uint64)
}
return r0
}
// TotalWeight provides a mock function with no fields
func (_m *TimeoutSignatureAggregator) TotalWeight() uint64 {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for TotalWeight")
}
var r0 uint64
if rf, ok := ret.Get(0).(func() uint64); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(uint64)
}
return r0
}
// VerifyAndAdd provides a mock function with given fields: signerID, sig, newestQCRank
func (_m *TimeoutSignatureAggregator) VerifyAndAdd(signerID models.Identity, sig []byte, newestQCRank uint64) (uint64, error) {
ret := _m.Called(signerID, sig, newestQCRank)
if len(ret) == 0 {
panic("no return value specified for VerifyAndAdd")
}
var r0 uint64
var r1 error
if rf, ok := ret.Get(0).(func(models.Identity, []byte, uint64) (uint64, error)); ok {
return rf(signerID, sig, newestQCRank)
}
if rf, ok := ret.Get(0).(func(models.Identity, []byte, uint64) uint64); ok {
r0 = rf(signerID, sig, newestQCRank)
} else {
r0 = ret.Get(0).(uint64)
}
if rf, ok := ret.Get(1).(func(models.Identity, []byte, uint64) error); ok {
r1 = rf(signerID, sig, newestQCRank)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewTimeoutSignatureAggregator creates a new instance of TimeoutSignatureAggregator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewTimeoutSignatureAggregator(t interface {
mock.TestingT
Cleanup(func())
}) *TimeoutSignatureAggregator {
mock := &TimeoutSignatureAggregator{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,34 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import mock "github.com/stretchr/testify/mock"
// TraceLogger is an autogenerated mock type for the TraceLogger type
type TraceLogger struct {
mock.Mock
}
// Error provides a mock function with given fields: message, err
func (_m *TraceLogger) Error(message string, err error) {
_m.Called(message, err)
}
// Trace provides a mock function with given fields: message
func (_m *TraceLogger) Trace(message string) {
_m.Called(message)
}
// NewTraceLogger creates a new instance of TraceLogger. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewTraceLogger(t interface {
mock.TestingT
Cleanup(func())
}) *TraceLogger {
mock := &TraceLogger{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,111 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// Validator is an autogenerated mock type for the Validator type
type Validator[StateT models.Unique, VoteT models.Unique] struct {
mock.Mock
}
// ValidateProposal provides a mock function with given fields: proposal
func (_m *Validator[StateT, VoteT]) ValidateProposal(proposal *models.SignedProposal[StateT, VoteT]) error {
ret := _m.Called(proposal)
if len(ret) == 0 {
panic("no return value specified for ValidateProposal")
}
var r0 error
if rf, ok := ret.Get(0).(func(*models.SignedProposal[StateT, VoteT]) error); ok {
r0 = rf(proposal)
} else {
r0 = ret.Error(0)
}
return r0
}
// ValidateQuorumCertificate provides a mock function with given fields: qc
func (_m *Validator[StateT, VoteT]) ValidateQuorumCertificate(qc models.QuorumCertificate) error {
ret := _m.Called(qc)
if len(ret) == 0 {
panic("no return value specified for ValidateQuorumCertificate")
}
var r0 error
if rf, ok := ret.Get(0).(func(models.QuorumCertificate) error); ok {
r0 = rf(qc)
} else {
r0 = ret.Error(0)
}
return r0
}
// ValidateTimeoutCertificate provides a mock function with given fields: tc
func (_m *Validator[StateT, VoteT]) ValidateTimeoutCertificate(tc models.TimeoutCertificate) error {
ret := _m.Called(tc)
if len(ret) == 0 {
panic("no return value specified for ValidateTimeoutCertificate")
}
var r0 error
if rf, ok := ret.Get(0).(func(models.TimeoutCertificate) error); ok {
r0 = rf(tc)
} else {
r0 = ret.Error(0)
}
return r0
}
// ValidateVote provides a mock function with given fields: vote
func (_m *Validator[StateT, VoteT]) ValidateVote(vote *VoteT) (*models.WeightedIdentity, error) {
ret := _m.Called(vote)
if len(ret) == 0 {
panic("no return value specified for ValidateVote")
}
var r0 *models.WeightedIdentity
var r1 error
if rf, ok := ret.Get(0).(func(*VoteT) (*models.WeightedIdentity, error)); ok {
return rf(vote)
}
if rf, ok := ret.Get(0).(func(*VoteT) *models.WeightedIdentity); ok {
r0 = rf(vote)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.WeightedIdentity)
}
}
if rf, ok := ret.Get(1).(func(*VoteT) error); ok {
r1 = rf(vote)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewValidator creates a new instance of Validator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewValidator[StateT models.Unique, VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *Validator[StateT, VoteT] {
mock := &Validator[StateT, VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,81 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// Verifier is an autogenerated mock type for the Verifier type
type Verifier[VoteT models.Unique] struct {
mock.Mock
}
// VerifyQuorumCertificate provides a mock function with given fields: quorumCertificate
func (_m *Verifier[VoteT]) VerifyQuorumCertificate(quorumCertificate models.QuorumCertificate) error {
ret := _m.Called(quorumCertificate)
if len(ret) == 0 {
panic("no return value specified for VerifyQuorumCertificate")
}
var r0 error
if rf, ok := ret.Get(0).(func(models.QuorumCertificate) error); ok {
r0 = rf(quorumCertificate)
} else {
r0 = ret.Error(0)
}
return r0
}
// VerifyTimeoutCertificate provides a mock function with given fields: timeoutCertificate
func (_m *Verifier[VoteT]) VerifyTimeoutCertificate(timeoutCertificate models.TimeoutCertificate) error {
ret := _m.Called(timeoutCertificate)
if len(ret) == 0 {
panic("no return value specified for VerifyTimeoutCertificate")
}
var r0 error
if rf, ok := ret.Get(0).(func(models.TimeoutCertificate) error); ok {
r0 = rf(timeoutCertificate)
} else {
r0 = ret.Error(0)
}
return r0
}
// VerifyVote provides a mock function with given fields: vote
func (_m *Verifier[VoteT]) VerifyVote(vote *VoteT) error {
ret := _m.Called(vote)
if len(ret) == 0 {
panic("no return value specified for VerifyVote")
}
var r0 error
if rf, ok := ret.Get(0).(func(*VoteT) error); ok {
r0 = rf(vote)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewVerifier creates a new instance of Verifier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewVerifier[VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *Verifier[VoteT] {
mock := &Verifier[VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,85 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
consensus "source.quilibrium.com/quilibrium/monorepo/consensus"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// VerifyingVoteProcessor is an autogenerated mock type for the VerifyingVoteProcessor type
type VerifyingVoteProcessor[StateT models.Unique, VoteT models.Unique] struct {
mock.Mock
}
// Process provides a mock function with given fields: vote
func (_m *VerifyingVoteProcessor[StateT, VoteT]) Process(vote *VoteT) error {
ret := _m.Called(vote)
if len(ret) == 0 {
panic("no return value specified for Process")
}
var r0 error
if rf, ok := ret.Get(0).(func(*VoteT) error); ok {
r0 = rf(vote)
} else {
r0 = ret.Error(0)
}
return r0
}
// State provides a mock function with no fields
func (_m *VerifyingVoteProcessor[StateT, VoteT]) State() *StateT {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for State")
}
var r0 *StateT
if rf, ok := ret.Get(0).(func() *StateT); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*StateT)
}
}
return r0
}
// Status provides a mock function with no fields
func (_m *VerifyingVoteProcessor[StateT, VoteT]) Status() consensus.VoteCollectorStatus {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Status")
}
var r0 consensus.VoteCollectorStatus
if rf, ok := ret.Get(0).(func() consensus.VoteCollectorStatus); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(consensus.VoteCollectorStatus)
}
return r0
}
// NewVerifyingVoteProcessor creates a new instance of VerifyingVoteProcessor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewVerifyingVoteProcessor[StateT models.Unique, VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *VerifyingVoteProcessor[StateT, VoteT] {
mock := &VerifyingVoteProcessor[StateT, VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,52 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// VoteAggregationConsumer is an autogenerated mock type for the VoteAggregationConsumer type
type VoteAggregationConsumer[StateT models.Unique, VoteT models.Unique] struct {
mock.Mock
}
// OnDoubleVotingDetected provides a mock function with given fields: _a0, _a1
func (_m *VoteAggregationConsumer[StateT, VoteT]) OnDoubleVotingDetected(_a0 *VoteT, _a1 *VoteT) {
_m.Called(_a0, _a1)
}
// OnInvalidVoteDetected provides a mock function with given fields: err
func (_m *VoteAggregationConsumer[StateT, VoteT]) OnInvalidVoteDetected(err models.InvalidVoteError[VoteT]) {
_m.Called(err)
}
// OnQuorumCertificateConstructedFromVotes provides a mock function with given fields: _a0
func (_m *VoteAggregationConsumer[StateT, VoteT]) OnQuorumCertificateConstructedFromVotes(_a0 models.QuorumCertificate) {
_m.Called(_a0)
}
// OnVoteForInvalidStateDetected provides a mock function with given fields: vote, invalidProposal
func (_m *VoteAggregationConsumer[StateT, VoteT]) OnVoteForInvalidStateDetected(vote *VoteT, invalidProposal *models.SignedProposal[StateT, VoteT]) {
_m.Called(vote, invalidProposal)
}
// OnVoteProcessed provides a mock function with given fields: vote
func (_m *VoteAggregationConsumer[StateT, VoteT]) OnVoteProcessed(vote *VoteT) {
_m.Called(vote)
}
// NewVoteAggregationConsumer creates a new instance of VoteAggregationConsumer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewVoteAggregationConsumer[StateT models.Unique, VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *VoteAggregationConsumer[StateT, VoteT] {
mock := &VoteAggregationConsumer[StateT, VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,42 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// VoteAggregationViolationConsumer is an autogenerated mock type for the VoteAggregationViolationConsumer type
type VoteAggregationViolationConsumer[StateT models.Unique, VoteT models.Unique] struct {
mock.Mock
}
// OnDoubleVotingDetected provides a mock function with given fields: _a0, _a1
func (_m *VoteAggregationViolationConsumer[StateT, VoteT]) OnDoubleVotingDetected(_a0 *VoteT, _a1 *VoteT) {
_m.Called(_a0, _a1)
}
// OnInvalidVoteDetected provides a mock function with given fields: err
func (_m *VoteAggregationViolationConsumer[StateT, VoteT]) OnInvalidVoteDetected(err models.InvalidVoteError[VoteT]) {
_m.Called(err)
}
// OnVoteForInvalidStateDetected provides a mock function with given fields: vote, invalidProposal
func (_m *VoteAggregationViolationConsumer[StateT, VoteT]) OnVoteForInvalidStateDetected(vote *VoteT, invalidProposal *models.SignedProposal[StateT, VoteT]) {
_m.Called(vote, invalidProposal)
}
// NewVoteAggregationViolationConsumer creates a new instance of VoteAggregationViolationConsumer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewVoteAggregationViolationConsumer[StateT models.Unique, VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *VoteAggregationViolationConsumer[StateT, VoteT] {
mock := &VoteAggregationViolationConsumer[StateT, VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,80 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// VoteAggregator is an autogenerated mock type for the VoteAggregator type
type VoteAggregator[StateT models.Unique, VoteT models.Unique] struct {
mock.Mock
}
// AddState provides a mock function with given fields: state
func (_m *VoteAggregator[StateT, VoteT]) AddState(state *models.SignedProposal[StateT, VoteT]) {
_m.Called(state)
}
// AddVote provides a mock function with given fields: vote
func (_m *VoteAggregator[StateT, VoteT]) AddVote(vote *VoteT) {
_m.Called(vote)
}
// InvalidState provides a mock function with given fields: state
func (_m *VoteAggregator[StateT, VoteT]) InvalidState(state *models.SignedProposal[StateT, VoteT]) error {
ret := _m.Called(state)
if len(ret) == 0 {
panic("no return value specified for InvalidState")
}
var r0 error
if rf, ok := ret.Get(0).(func(*models.SignedProposal[StateT, VoteT]) error); ok {
r0 = rf(state)
} else {
r0 = ret.Error(0)
}
return r0
}
// PruneUpToRank provides a mock function with given fields: rank
func (_m *VoteAggregator[StateT, VoteT]) PruneUpToRank(rank uint64) {
_m.Called(rank)
}
// Start provides a mock function with given fields: ctx
func (_m *VoteAggregator[StateT, VoteT]) Start(ctx context.Context) error {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for Start")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
r0 = rf(ctx)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewVoteAggregator creates a new instance of VoteAggregator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewVoteAggregator[StateT models.Unique, VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *VoteAggregator[StateT, VoteT] {
mock := &VoteAggregator[StateT, VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,106 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
consensus "source.quilibrium.com/quilibrium/monorepo/consensus"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// VoteCollector is an autogenerated mock type for the VoteCollector type
type VoteCollector[StateT models.Unique, VoteT models.Unique] struct {
mock.Mock
}
// AddVote provides a mock function with given fields: vote
func (_m *VoteCollector[StateT, VoteT]) AddVote(vote *VoteT) error {
ret := _m.Called(vote)
if len(ret) == 0 {
panic("no return value specified for AddVote")
}
var r0 error
if rf, ok := ret.Get(0).(func(*VoteT) error); ok {
r0 = rf(vote)
} else {
r0 = ret.Error(0)
}
return r0
}
// ProcessState provides a mock function with given fields: state
func (_m *VoteCollector[StateT, VoteT]) ProcessState(state *models.SignedProposal[StateT, VoteT]) error {
ret := _m.Called(state)
if len(ret) == 0 {
panic("no return value specified for ProcessState")
}
var r0 error
if rf, ok := ret.Get(0).(func(*models.SignedProposal[StateT, VoteT]) error); ok {
r0 = rf(state)
} else {
r0 = ret.Error(0)
}
return r0
}
// RegisterVoteConsumer provides a mock function with given fields: consumer
func (_m *VoteCollector[StateT, VoteT]) RegisterVoteConsumer(consumer consensus.VoteConsumer[VoteT]) {
_m.Called(consumer)
}
// Status provides a mock function with no fields
func (_m *VoteCollector[StateT, VoteT]) Status() consensus.VoteCollectorStatus {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Status")
}
var r0 consensus.VoteCollectorStatus
if rf, ok := ret.Get(0).(func() consensus.VoteCollectorStatus); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(consensus.VoteCollectorStatus)
}
return r0
}
// View provides a mock function with no fields
func (_m *VoteCollector[StateT, VoteT]) View() uint64 {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for View")
}
var r0 uint64
if rf, ok := ret.Get(0).(func() uint64); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(uint64)
}
return r0
}
// NewVoteCollector creates a new instance of VoteCollector. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewVoteCollector[StateT models.Unique, VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *VoteCollector[StateT, VoteT] {
mock := &VoteCollector[StateT, VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,37 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// VoteCollectorConsumer is an autogenerated mock type for the VoteCollectorConsumer type
type VoteCollectorConsumer[VoteT models.Unique] struct {
mock.Mock
}
// OnQuorumCertificateConstructedFromVotes provides a mock function with given fields: _a0
func (_m *VoteCollectorConsumer[VoteT]) OnQuorumCertificateConstructedFromVotes(_a0 models.QuorumCertificate) {
_m.Called(_a0)
}
// OnVoteProcessed provides a mock function with given fields: vote
func (_m *VoteCollectorConsumer[VoteT]) OnVoteProcessed(vote *VoteT) {
_m.Called(vote)
}
// NewVoteCollectorConsumer creates a new instance of VoteCollectorConsumer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewVoteCollectorConsumer[VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *VoteCollectorConsumer[VoteT] {
mock := &VoteCollectorConsumer[VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,92 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
context "context"
consensus "source.quilibrium.com/quilibrium/monorepo/consensus"
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// VoteCollectors is an autogenerated mock type for the VoteCollectors type
type VoteCollectors[StateT models.Unique, VoteT models.Unique] struct {
mock.Mock
}
// GetOrCreateCollector provides a mock function with given fields: rank
func (_m *VoteCollectors[StateT, VoteT]) GetOrCreateCollector(rank uint64) (consensus.VoteCollector[StateT, VoteT], bool, error) {
ret := _m.Called(rank)
if len(ret) == 0 {
panic("no return value specified for GetOrCreateCollector")
}
var r0 consensus.VoteCollector[StateT, VoteT]
var r1 bool
var r2 error
if rf, ok := ret.Get(0).(func(uint64) (consensus.VoteCollector[StateT, VoteT], bool, error)); ok {
return rf(rank)
}
if rf, ok := ret.Get(0).(func(uint64) consensus.VoteCollector[StateT, VoteT]); ok {
r0 = rf(rank)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(consensus.VoteCollector[StateT, VoteT])
}
}
if rf, ok := ret.Get(1).(func(uint64) bool); ok {
r1 = rf(rank)
} else {
r1 = ret.Get(1).(bool)
}
if rf, ok := ret.Get(2).(func(uint64) error); ok {
r2 = rf(rank)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// PruneUpToRank provides a mock function with given fields: lowestRetainedRank
func (_m *VoteCollectors[StateT, VoteT]) PruneUpToRank(lowestRetainedRank uint64) {
_m.Called(lowestRetainedRank)
}
// Start provides a mock function with given fields: ctx
func (_m *VoteCollectors[StateT, VoteT]) Start(ctx context.Context) error {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for Start")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
r0 = rf(ctx)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewVoteCollectors creates a new instance of VoteCollectors. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewVoteCollectors[StateT models.Unique, VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *VoteCollectors[StateT, VoteT] {
mock := &VoteCollectors[StateT, VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,65 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
consensus "source.quilibrium.com/quilibrium/monorepo/consensus"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// VoteProcessor is an autogenerated mock type for the VoteProcessor type
type VoteProcessor[VoteT models.Unique] struct {
mock.Mock
}
// Process provides a mock function with given fields: vote
func (_m *VoteProcessor[VoteT]) Process(vote *VoteT) error {
ret := _m.Called(vote)
if len(ret) == 0 {
panic("no return value specified for Process")
}
var r0 error
if rf, ok := ret.Get(0).(func(*VoteT) error); ok {
r0 = rf(vote)
} else {
r0 = ret.Error(0)
}
return r0
}
// Status provides a mock function with no fields
func (_m *VoteProcessor[VoteT]) Status() consensus.VoteCollectorStatus {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Status")
}
var r0 consensus.VoteCollectorStatus
if rf, ok := ret.Get(0).(func() consensus.VoteCollectorStatus); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(consensus.VoteCollectorStatus)
}
return r0
}
// NewVoteProcessor creates a new instance of VoteProcessor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewVoteProcessor[VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *VoteProcessor[VoteT] {
mock := &VoteProcessor[VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,59 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
consensus "source.quilibrium.com/quilibrium/monorepo/consensus"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// VoteProcessorFactory is an autogenerated mock type for the VoteProcessorFactory type
type VoteProcessorFactory[StateT models.Unique, VoteT models.Unique] struct {
mock.Mock
}
// Create provides a mock function with given fields: tracer, proposal
func (_m *VoteProcessorFactory[StateT, VoteT]) Create(tracer consensus.TraceLogger, proposal *models.SignedProposal[StateT, VoteT]) (consensus.VerifyingVoteProcessor[StateT, VoteT], error) {
ret := _m.Called(tracer, proposal)
if len(ret) == 0 {
panic("no return value specified for Create")
}
var r0 consensus.VerifyingVoteProcessor[StateT, VoteT]
var r1 error
if rf, ok := ret.Get(0).(func(consensus.TraceLogger, *models.SignedProposal[StateT, VoteT]) (consensus.VerifyingVoteProcessor[StateT, VoteT], error)); ok {
return rf(tracer, proposal)
}
if rf, ok := ret.Get(0).(func(consensus.TraceLogger, *models.SignedProposal[StateT, VoteT]) consensus.VerifyingVoteProcessor[StateT, VoteT]); ok {
r0 = rf(tracer, proposal)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(consensus.VerifyingVoteProcessor[StateT, VoteT])
}
}
if rf, ok := ret.Get(1).(func(consensus.TraceLogger, *models.SignedProposal[StateT, VoteT]) error); ok {
r1 = rf(tracer, proposal)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewVoteProcessorFactory creates a new instance of VoteProcessorFactory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewVoteProcessorFactory[StateT models.Unique, VoteT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *VoteProcessorFactory[StateT, VoteT] {
mock := &VoteProcessorFactory[StateT, VoteT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,252 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// VotingProvider is an autogenerated mock type for the VotingProvider type
type VotingProvider[StateT models.Unique, VoteT models.Unique, PeerIDT models.Unique] struct {
mock.Mock
}
// FinalizeTimeout provides a mock function with given fields: ctx, filter, rank, latestQuorumCertificateRanks, aggregatedSignature
func (_m *VotingProvider[StateT, VoteT, PeerIDT]) FinalizeTimeout(ctx context.Context, filter []byte, rank uint64, latestQuorumCertificateRanks []uint64, aggregatedSignature models.AggregatedSignature) (models.TimeoutCertificate, error) {
ret := _m.Called(ctx, filter, rank, latestQuorumCertificateRanks, aggregatedSignature)
if len(ret) == 0 {
panic("no return value specified for FinalizeTimeout")
}
var r0 models.TimeoutCertificate
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, []byte, uint64, []uint64, models.AggregatedSignature) (models.TimeoutCertificate, error)); ok {
return rf(ctx, filter, rank, latestQuorumCertificateRanks, aggregatedSignature)
}
if rf, ok := ret.Get(0).(func(context.Context, []byte, uint64, []uint64, models.AggregatedSignature) models.TimeoutCertificate); ok {
r0 = rf(ctx, filter, rank, latestQuorumCertificateRanks, aggregatedSignature)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(models.TimeoutCertificate)
}
}
if rf, ok := ret.Get(1).(func(context.Context, []byte, uint64, []uint64, models.AggregatedSignature) error); ok {
r1 = rf(ctx, filter, rank, latestQuorumCertificateRanks, aggregatedSignature)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FinalizeVotes provides a mock function with given fields: ctx, proposals, proposalVotes
func (_m *VotingProvider[StateT, VoteT, PeerIDT]) FinalizeVotes(ctx context.Context, proposals map[models.Identity]*StateT, proposalVotes map[models.Identity]*VoteT) (*StateT, PeerIDT, error) {
ret := _m.Called(ctx, proposals, proposalVotes)
if len(ret) == 0 {
panic("no return value specified for FinalizeVotes")
}
var r0 *StateT
var r1 PeerIDT
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, map[models.Identity]*StateT, map[models.Identity]*VoteT) (*StateT, PeerIDT, error)); ok {
return rf(ctx, proposals, proposalVotes)
}
if rf, ok := ret.Get(0).(func(context.Context, map[models.Identity]*StateT, map[models.Identity]*VoteT) *StateT); ok {
r0 = rf(ctx, proposals, proposalVotes)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*StateT)
}
}
if rf, ok := ret.Get(1).(func(context.Context, map[models.Identity]*StateT, map[models.Identity]*VoteT) PeerIDT); ok {
r1 = rf(ctx, proposals, proposalVotes)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(PeerIDT)
}
}
if rf, ok := ret.Get(2).(func(context.Context, map[models.Identity]*StateT, map[models.Identity]*VoteT) error); ok {
r2 = rf(ctx, proposals, proposalVotes)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// IsQuorum provides a mock function with given fields: ctx, proposalVotes
func (_m *VotingProvider[StateT, VoteT, PeerIDT]) IsQuorum(ctx context.Context, proposalVotes map[models.Identity]*VoteT) (bool, error) {
ret := _m.Called(ctx, proposalVotes)
if len(ret) == 0 {
panic("no return value specified for IsQuorum")
}
var r0 bool
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, map[models.Identity]*VoteT) (bool, error)); ok {
return rf(ctx, proposalVotes)
}
if rf, ok := ret.Get(0).(func(context.Context, map[models.Identity]*VoteT) bool); ok {
r0 = rf(ctx, proposalVotes)
} else {
r0 = ret.Get(0).(bool)
}
if rf, ok := ret.Get(1).(func(context.Context, map[models.Identity]*VoteT) error); ok {
r1 = rf(ctx, proposalVotes)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SendConfirmation provides a mock function with given fields: ctx, finalized
func (_m *VotingProvider[StateT, VoteT, PeerIDT]) SendConfirmation(ctx context.Context, finalized *StateT) error {
ret := _m.Called(ctx, finalized)
if len(ret) == 0 {
panic("no return value specified for SendConfirmation")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *StateT) error); ok {
r0 = rf(ctx, finalized)
} else {
r0 = ret.Error(0)
}
return r0
}
// SendProposal provides a mock function with given fields: ctx, proposal
func (_m *VotingProvider[StateT, VoteT, PeerIDT]) SendProposal(ctx context.Context, proposal *StateT) error {
ret := _m.Called(ctx, proposal)
if len(ret) == 0 {
panic("no return value specified for SendProposal")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *StateT) error); ok {
r0 = rf(ctx, proposal)
} else {
r0 = ret.Error(0)
}
return r0
}
// SendVote provides a mock function with given fields: ctx, vote
func (_m *VotingProvider[StateT, VoteT, PeerIDT]) SendVote(ctx context.Context, vote *VoteT) (PeerIDT, error) {
ret := _m.Called(ctx, vote)
if len(ret) == 0 {
panic("no return value specified for SendVote")
}
var r0 PeerIDT
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *VoteT) (PeerIDT, error)); ok {
return rf(ctx, vote)
}
if rf, ok := ret.Get(0).(func(context.Context, *VoteT) PeerIDT); ok {
r0 = rf(ctx, vote)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(PeerIDT)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *VoteT) error); ok {
r1 = rf(ctx, vote)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SignTimeoutVote provides a mock function with given fields: ctx, filter, currentRank, newestQuorumCertificateRank
func (_m *VotingProvider[StateT, VoteT, PeerIDT]) SignTimeoutVote(ctx context.Context, filter []byte, currentRank uint64, newestQuorumCertificateRank uint64) (*VoteT, error) {
ret := _m.Called(ctx, filter, currentRank, newestQuorumCertificateRank)
if len(ret) == 0 {
panic("no return value specified for SignTimeoutVote")
}
var r0 *VoteT
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, []byte, uint64, uint64) (*VoteT, error)); ok {
return rf(ctx, filter, currentRank, newestQuorumCertificateRank)
}
if rf, ok := ret.Get(0).(func(context.Context, []byte, uint64, uint64) *VoteT); ok {
r0 = rf(ctx, filter, currentRank, newestQuorumCertificateRank)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*VoteT)
}
}
if rf, ok := ret.Get(1).(func(context.Context, []byte, uint64, uint64) error); ok {
r1 = rf(ctx, filter, currentRank, newestQuorumCertificateRank)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SignVote provides a mock function with given fields: ctx, state
func (_m *VotingProvider[StateT, VoteT, PeerIDT]) SignVote(ctx context.Context, state *models.State[StateT]) (*VoteT, error) {
ret := _m.Called(ctx, state)
if len(ret) == 0 {
panic("no return value specified for SignVote")
}
var r0 *VoteT
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *models.State[StateT]) (*VoteT, error)); ok {
return rf(ctx, state)
}
if rf, ok := ret.Get(0).(func(context.Context, *models.State[StateT]) *VoteT); ok {
r0 = rf(ctx, state)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*VoteT)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *models.State[StateT]) error); ok {
r1 = rf(ctx, state)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewVotingProvider creates a new instance of VotingProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewVotingProvider[StateT models.Unique, VoteT models.Unique, PeerIDT models.Unique](t interface {
mock.TestingT
Cleanup(func())
}) *VotingProvider[StateT, VoteT, PeerIDT] {
mock := &VotingProvider[StateT, VoteT, PeerIDT]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,42 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import mock "github.com/stretchr/testify/mock"
// WeightProvider is an autogenerated mock type for the WeightProvider type
type WeightProvider struct {
mock.Mock
}
// GetWeightForBitmask provides a mock function with given fields: filter, bitmask
func (_m *WeightProvider) GetWeightForBitmask(filter []byte, bitmask []byte) uint64 {
ret := _m.Called(filter, bitmask)
if len(ret) == 0 {
panic("no return value specified for GetWeightForBitmask")
}
var r0 uint64
if rf, ok := ret.Get(0).(func([]byte, []byte) uint64); ok {
r0 = rf(filter, bitmask)
} else {
r0 = ret.Get(0).(uint64)
}
return r0
}
// NewWeightProvider creates a new instance of WeightProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewWeightProvider(t interface {
mock.TestingT
Cleanup(func())
}) *WeightProvider {
mock := &WeightProvider{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,130 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
models "source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// WeightedSignatureAggregator is an autogenerated mock type for the WeightedSignatureAggregator type
type WeightedSignatureAggregator struct {
mock.Mock
}
// Aggregate provides a mock function with no fields
func (_m *WeightedSignatureAggregator) Aggregate() ([]models.WeightedIdentity, models.AggregatedSignature, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Aggregate")
}
var r0 []models.WeightedIdentity
var r1 models.AggregatedSignature
var r2 error
if rf, ok := ret.Get(0).(func() ([]models.WeightedIdentity, models.AggregatedSignature, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() []models.WeightedIdentity); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]models.WeightedIdentity)
}
}
if rf, ok := ret.Get(1).(func() models.AggregatedSignature); ok {
r1 = rf()
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(models.AggregatedSignature)
}
}
if rf, ok := ret.Get(2).(func() error); ok {
r2 = rf()
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// TotalWeight provides a mock function with no fields
func (_m *WeightedSignatureAggregator) TotalWeight() uint64 {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for TotalWeight")
}
var r0 uint64
if rf, ok := ret.Get(0).(func() uint64); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(uint64)
}
return r0
}
// TrustedAdd provides a mock function with given fields: signerID, sig
func (_m *WeightedSignatureAggregator) TrustedAdd(signerID models.Identity, sig []byte) (uint64, error) {
ret := _m.Called(signerID, sig)
if len(ret) == 0 {
panic("no return value specified for TrustedAdd")
}
var r0 uint64
var r1 error
if rf, ok := ret.Get(0).(func(models.Identity, []byte) (uint64, error)); ok {
return rf(signerID, sig)
}
if rf, ok := ret.Get(0).(func(models.Identity, []byte) uint64); ok {
r0 = rf(signerID, sig)
} else {
r0 = ret.Get(0).(uint64)
}
if rf, ok := ret.Get(1).(func(models.Identity, []byte) error); ok {
r1 = rf(signerID, sig)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Verify provides a mock function with given fields: signerID, sig
func (_m *WeightedSignatureAggregator) Verify(signerID models.Identity, sig []byte) error {
ret := _m.Called(signerID, sig)
if len(ret) == 0 {
panic("no return value specified for Verify")
}
var r0 error
if rf, ok := ret.Get(0).(func(models.Identity, []byte) error); ok {
r0 = rf(signerID, sig)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewWeightedSignatureAggregator creates a new instance of WeightedSignatureAggregator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewWeightedSignatureAggregator(t interface {
mock.TestingT
Cleanup(func())
}) *WeightedSignatureAggregator {
mock := &WeightedSignatureAggregator{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,34 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import mock "github.com/stretchr/testify/mock"
// Workerpool is an autogenerated mock type for the Workerpool type
type Workerpool struct {
mock.Mock
}
// StopWait provides a mock function with no fields
func (_m *Workerpool) StopWait() {
_m.Called()
}
// Submit provides a mock function with given fields: task
func (_m *Workerpool) Submit(task func()) {
_m.Called(task)
}
// NewWorkerpool creates a new instance of Workerpool. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewWorkerpool(t interface {
mock.TestingT
Cleanup(func())
}) *Workerpool {
mock := &Workerpool{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,29 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import mock "github.com/stretchr/testify/mock"
// Workers is an autogenerated mock type for the Workers type
type Workers struct {
mock.Mock
}
// Submit provides a mock function with given fields: task
func (_m *Workers) Submit(task func()) {
_m.Called(task)
}
// NewWorkers creates a new instance of Workers. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewWorkers(t interface {
mock.TestingT
Cleanup(func())
}) *Workers {
mock := &Workers{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,48 @@
package models
// AggregatedSignature provides a generic interface over an aggregatable
// signature type
type AggregatedSignature interface {
// GetSignature returns the aggregated signature in raw canonical bytes
GetSignature() []byte
// GetPublicKey returns the public key in raw canonical bytes
GetPublicKey() []byte
// GetBitmask returns the bitmask of the signers in the signature, in matching
// order to the clique's prover set (in ascending ring order).
GetBitmask() []byte
}
// AggregatedSigner provides a generic interface over an aggregatable signature
// scheme. Embeds the validation-only methods.
type AggregatedSigner interface {
AggregatedSignatureValidator
// AggregateSignatures produces an AggregatedSignature object, expecting
// public keys and signatures to be in matching order, with nil slices for
// bitmask entries that are not present. The order should be aligned to the
// clique's prover set (in ascending ring order).
AggregateSignatures(
publicKeys [][]byte,
signatures [][]byte,
) (AggregatedSignature, error)
// SignWithContext produces an AggregatedSignature object, optionally taking
// an existing AggregatedSignature and builds on top of it.
SignWithContext(
aggregatedSignature AggregatedSignature,
bitmaskIndex int,
privateKey []byte,
message []byte,
context []byte,
) (AggregatedSignature, error)
}
// AggregatedSignatureValidator provides a generic interface over aggregated
// signature validation.
type AggregatedSignatureValidator interface {
// VerifySignature validates the AggregatedSignature, with a binary pass/fail
// result.
VerifySignature(
aggregatedSignature AggregatedSignature,
message []byte,
context []byte,
) bool
}

View File

@ -0,0 +1,15 @@
package models
// ConsensusState defines the core minimum data required to maintain consensus
// safety betwixt the core consensus state machine and the deriving users of the
// state machine, different from StateT (the object being built by the user).
type ConsensusState[VoteT Unique] struct {
// The filter scope of the consensus state.
Filter []byte
// The latest rank that has been finalized (e.g. cannot be forked below).
FinalizedRank uint64
// The latest rank voted on in a quorum certificate or timeout certificate.
LatestAcknowledgedRank uint64
// The latest timeout data produced by this instance.
LatestTimeout *TimeoutState[VoteT]
}

View File

@ -0,0 +1,13 @@
package models
import "time"
// NextRank is the control flow event for when the next rank should be entered.
type NextRank struct {
// Rank is the next rank value.
Rank uint64
// Start is the time the next rank was entered.
Start time.Time
// End is the time the next rank ends (i.e. times out).
End time.Time
}

588
consensus/models/errors.go Normal file
View File

@ -0,0 +1,588 @@
package models
import (
"errors"
"fmt"
)
var (
ErrUnverifiableState = errors.New("state proposal can't be verified")
ErrInvalidSignature = errors.New("invalid signature")
ErrRankUnknown = errors.New("rank is unknown")
)
type NoVoteError struct {
Err error
}
func (e NoVoteError) Error() string {
return fmt.Sprintf("not voting - %s", e.Err.Error())
}
func (e NoVoteError) Unwrap() error {
return e.Err
}
// IsNoVoteError returns whether an error is NoVoteError
func IsNoVoteError(err error) bool {
var e NoVoteError
return errors.As(err, &e)
}
func NewNoVoteErrorf(msg string, args ...interface{}) error {
return NoVoteError{Err: fmt.Errorf(msg, args...)}
}
type NoTimeoutError struct {
Err error
}
func (e NoTimeoutError) Error() string {
return fmt.Sprintf(
"conditions not satisfied to generate valid TimeoutState: %s",
e.Err.Error(),
)
}
func (e NoTimeoutError) Unwrap() error {
return e.Err
}
func IsNoTimeoutError(err error) bool {
var e NoTimeoutError
return errors.As(err, &e)
}
func NewNoTimeoutErrorf(msg string, args ...interface{}) error {
return NoTimeoutError{Err: fmt.Errorf(msg, args...)}
}
type InvalidFormatError struct {
err error
}
func NewInvalidFormatError(err error) error {
return InvalidFormatError{err}
}
func NewInvalidFormatErrorf(msg string, args ...interface{}) error {
return InvalidFormatError{fmt.Errorf(msg, args...)}
}
func (e InvalidFormatError) Error() string { return e.err.Error() }
func (e InvalidFormatError) Unwrap() error { return e.err }
func IsInvalidFormatError(err error) bool {
var e InvalidFormatError
return errors.As(err, &e)
}
type ConfigurationError struct {
err error
}
func NewConfigurationError(err error) error {
return ConfigurationError{err}
}
func NewConfigurationErrorf(msg string, args ...interface{}) error {
return ConfigurationError{fmt.Errorf(msg, args...)}
}
func (e ConfigurationError) Error() string { return e.err.Error() }
func (e ConfigurationError) Unwrap() error { return e.err }
func IsConfigurationError(err error) bool {
var e ConfigurationError
return errors.As(err, &e)
}
type MissingStateError struct {
Rank uint64
Identifier Identity
}
func (e MissingStateError) Error() string {
return fmt.Sprintf(
"missing state at rank %d with ID %v",
e.Rank,
e.Identifier,
)
}
func IsMissingStateError(err error) bool {
var e MissingStateError
return errors.As(err, &e)
}
type InvalidQuorumCertificateError struct {
Identifier Identity
Rank uint64
Err error
}
func (e InvalidQuorumCertificateError) Error() string {
return fmt.Sprintf(
"invalid QuorumCertificate for state %x at rank %d: %s",
e.Identifier,
e.Rank,
e.Err.Error(),
)
}
func IsInvalidQuorumCertificateError(err error) bool {
var e InvalidQuorumCertificateError
return errors.As(err, &e)
}
func (e InvalidQuorumCertificateError) Unwrap() error {
return e.Err
}
type InvalidTimeoutCertificateError struct {
Rank uint64
Err error
}
func (e InvalidTimeoutCertificateError) Error() string {
return fmt.Sprintf(
"invalid TimeoutCertificate at rank %d: %s",
e.Rank,
e.Err.Error(),
)
}
func IsInvalidTimeoutCertificateError(err error) bool {
var e InvalidTimeoutCertificateError
return errors.As(err, &e)
}
func (e InvalidTimeoutCertificateError) Unwrap() error {
return e.Err
}
type InvalidProposalError[StateT Unique, VoteT Unique] struct {
InvalidProposal *SignedProposal[StateT, VoteT]
Err error
}
func NewInvalidProposalErrorf[StateT Unique, VoteT Unique](
proposal *SignedProposal[StateT, VoteT],
msg string,
args ...interface{},
) error {
return InvalidProposalError[StateT, VoteT]{
InvalidProposal: proposal,
Err: fmt.Errorf(msg, args...),
}
}
func (e InvalidProposalError[StateT, VoteT]) Error() string {
return fmt.Sprintf(
"invalid proposal %x at rank %d: %s",
e.InvalidProposal.State.Identifier,
e.InvalidProposal.State.Rank,
e.Err.Error(),
)
}
func (e InvalidProposalError[StateT, VoteT]) Unwrap() error {
return e.Err
}
func IsInvalidProposalError[StateT Unique, VoteT Unique](err error) bool {
var e InvalidProposalError[StateT, VoteT]
return errors.As(err, &e)
}
func AsInvalidProposalError[StateT Unique, VoteT Unique](
err error,
) (*InvalidProposalError[StateT, VoteT], bool) {
var e InvalidProposalError[StateT, VoteT]
ok := errors.As(err, &e)
if ok {
return &e, true
}
return nil, false
}
type InvalidStateError[StateT Unique] struct {
InvalidState *State[StateT]
Err error
}
func NewInvalidStateErrorf[StateT Unique](
state *State[StateT],
msg string,
args ...interface{},
) error {
return InvalidStateError[StateT]{
InvalidState: state,
Err: fmt.Errorf(msg, args...),
}
}
func (e InvalidStateError[StateT]) Error() string {
return fmt.Sprintf(
"invalid state %x at rank %d: %s",
e.InvalidState.Identifier,
e.InvalidState.Rank,
e.Err.Error(),
)
}
func IsInvalidStateError[StateT Unique](err error) bool {
var e InvalidStateError[StateT]
return errors.As(err, &e)
}
func AsInvalidStateError[StateT Unique](err error) (
*InvalidStateError[StateT],
bool,
) {
var e InvalidStateError[StateT]
ok := errors.As(err, &e)
if ok {
return &e, true
}
return nil, false
}
func (e InvalidStateError[StateT]) Unwrap() error {
return e.Err
}
type InvalidVoteError[VoteT Unique] struct {
Vote *VoteT
Err error
}
func NewInvalidVoteErrorf[VoteT Unique](
vote *VoteT,
msg string,
args ...interface{},
) error {
return InvalidVoteError[VoteT]{
Vote: vote,
Err: fmt.Errorf(msg, args...),
}
}
func (e InvalidVoteError[VoteT]) Error() string {
return fmt.Sprintf(
"invalid vote at rank %d for state %x: %s",
(*e.Vote).GetRank(),
(*e.Vote).Identity(),
e.Err.Error(),
)
}
func IsInvalidVoteError[VoteT Unique](err error) bool {
var e InvalidVoteError[VoteT]
return errors.As(err, &e)
}
func AsInvalidVoteError[VoteT Unique](err error) (
*InvalidVoteError[VoteT],
bool,
) {
var e InvalidVoteError[VoteT]
ok := errors.As(err, &e)
if ok {
return &e, true
}
return nil, false
}
func (e InvalidVoteError[VoteT]) Unwrap() error {
return e.Err
}
type ByzantineThresholdExceededError struct {
Evidence string
}
func (e ByzantineThresholdExceededError) Error() string {
return e.Evidence
}
func IsByzantineThresholdExceededError(err error) bool {
var target ByzantineThresholdExceededError
return errors.As(err, &target)
}
type DoubleVoteError[VoteT Unique] struct {
FirstVote *VoteT
ConflictingVote *VoteT
err error
}
func (e DoubleVoteError[VoteT]) Error() string {
return e.err.Error()
}
func IsDoubleVoteError[VoteT Unique](err error) bool {
var e DoubleVoteError[VoteT]
return errors.As(err, &e)
}
func AsDoubleVoteError[VoteT Unique](err error) (
*DoubleVoteError[VoteT],
bool,
) {
var e DoubleVoteError[VoteT]
ok := errors.As(err, &e)
if ok {
return &e, true
}
return nil, false
}
func (e DoubleVoteError[VoteT]) Unwrap() error {
return e.err
}
func NewDoubleVoteErrorf[VoteT Unique](
firstVote, conflictingVote *VoteT,
msg string,
args ...interface{},
) error {
return DoubleVoteError[VoteT]{
FirstVote: firstVote,
ConflictingVote: conflictingVote,
err: fmt.Errorf(msg, args...),
}
}
type DuplicatedSignerError struct {
err error
}
func NewDuplicatedSignerError(err error) error {
return DuplicatedSignerError{err}
}
func NewDuplicatedSignerErrorf(msg string, args ...interface{}) error {
return DuplicatedSignerError{err: fmt.Errorf(msg, args...)}
}
func (e DuplicatedSignerError) Error() string { return e.err.Error() }
func (e DuplicatedSignerError) Unwrap() error { return e.err }
func IsDuplicatedSignerError(err error) bool {
var e DuplicatedSignerError
return errors.As(err, &e)
}
type InvalidSignatureIncludedError struct {
err error
}
func NewInvalidSignatureIncludedError(err error) error {
return InvalidSignatureIncludedError{err}
}
func NewInvalidSignatureIncludedErrorf(msg string, args ...interface{}) error {
return InvalidSignatureIncludedError{fmt.Errorf(msg, args...)}
}
func (e InvalidSignatureIncludedError) Error() string { return e.err.Error() }
func (e InvalidSignatureIncludedError) Unwrap() error { return e.err }
func IsInvalidSignatureIncludedError(err error) bool {
var e InvalidSignatureIncludedError
return errors.As(err, &e)
}
type InvalidAggregatedKeyError struct {
error
}
func NewInvalidAggregatedKeyError(err error) error {
return InvalidAggregatedKeyError{err}
}
func NewInvalidAggregatedKeyErrorf(msg string, args ...interface{}) error {
return InvalidAggregatedKeyError{fmt.Errorf(msg, args...)}
}
func (e InvalidAggregatedKeyError) Unwrap() error { return e.error }
func IsInvalidAggregatedKeyError(err error) bool {
var e InvalidAggregatedKeyError
return errors.As(err, &e)
}
type InsufficientSignaturesError struct {
err error
}
func NewInsufficientSignaturesError(err error) error {
return InsufficientSignaturesError{err}
}
func NewInsufficientSignaturesErrorf(msg string, args ...interface{}) error {
return InsufficientSignaturesError{fmt.Errorf(msg, args...)}
}
func (e InsufficientSignaturesError) Error() string { return e.err.Error() }
func (e InsufficientSignaturesError) Unwrap() error { return e.err }
func IsInsufficientSignaturesError(err error) bool {
var e InsufficientSignaturesError
return errors.As(err, &e)
}
type InvalidSignerError struct {
err error
}
func NewInvalidSignerError(err error) error {
return InvalidSignerError{err}
}
func NewInvalidSignerErrorf(msg string, args ...interface{}) error {
return InvalidSignerError{fmt.Errorf(msg, args...)}
}
func (e InvalidSignerError) Error() string { return e.err.Error() }
func (e InvalidSignerError) Unwrap() error { return e.err }
func IsInvalidSignerError(err error) bool {
var e InvalidSignerError
return errors.As(err, &e)
}
type DoubleTimeoutError[VoteT Unique] struct {
FirstTimeout *TimeoutState[VoteT]
ConflictingTimeout *TimeoutState[VoteT]
err error
}
func (e DoubleTimeoutError[VoteT]) Error() string {
return e.err.Error()
}
func IsDoubleTimeoutError[VoteT Unique](err error) bool {
var e DoubleTimeoutError[VoteT]
return errors.As(err, &e)
}
func AsDoubleTimeoutError[VoteT Unique](err error) (
*DoubleTimeoutError[VoteT],
bool,
) {
var e DoubleTimeoutError[VoteT]
ok := errors.As(err, &e)
if ok {
return &e, true
}
return nil, false
}
func (e DoubleTimeoutError[VoteT]) Unwrap() error {
return e.err
}
func NewDoubleTimeoutErrorf[VoteT Unique](
firstTimeout, conflictingTimeout *TimeoutState[VoteT],
msg string,
args ...interface{},
) error {
return DoubleTimeoutError[VoteT]{
FirstTimeout: firstTimeout,
ConflictingTimeout: conflictingTimeout,
err: fmt.Errorf(msg, args...),
}
}
type InvalidTimeoutError[VoteT Unique] struct {
Timeout *TimeoutState[VoteT]
Err error
}
func NewInvalidTimeoutErrorf[VoteT Unique](
timeout *TimeoutState[VoteT],
msg string,
args ...interface{},
) error {
return InvalidTimeoutError[VoteT]{
Timeout: timeout,
Err: fmt.Errorf(msg, args...),
}
}
func (e InvalidTimeoutError[VoteT]) Error() string {
return fmt.Sprintf("invalid timeout: %d: %s",
e.Timeout.Rank,
e.Err.Error(),
)
}
func IsInvalidTimeoutError[VoteT Unique](err error) bool {
var e InvalidTimeoutError[VoteT]
return errors.As(err, &e)
}
func AsInvalidTimeoutError[VoteT Unique](err error) (
*InvalidTimeoutError[VoteT],
bool,
) {
var e InvalidTimeoutError[VoteT]
ok := errors.As(err, &e)
if ok {
return &e, true
}
return nil, false
}
func (e InvalidTimeoutError[VoteT]) Unwrap() error {
return e.Err
}
// UnknownExecutionResultError indicates that the Execution Result is unknown
type UnknownExecutionResultError struct {
err error
}
func NewUnknownExecutionResultErrorf(msg string, args ...interface{}) error {
return UnknownExecutionResultError{
err: fmt.Errorf(msg, args...),
}
}
func (e UnknownExecutionResultError) Unwrap() error {
return e.err
}
func (e UnknownExecutionResultError) Error() string {
return e.err.Error()
}
func IsUnknownExecutionResultError(err error) bool {
var unknownExecutionResultError UnknownExecutionResultError
return errors.As(err, &unknownExecutionResultError)
}
type BelowPrunedThresholdError struct {
err error
}
func NewBelowPrunedThresholdErrorf(msg string, args ...interface{}) error {
return BelowPrunedThresholdError{
err: fmt.Errorf(msg, args...),
}
}
func (e BelowPrunedThresholdError) Unwrap() error {
return e.err
}
func (e BelowPrunedThresholdError) Error() string {
return e.err.Error()
}
func IsBelowPrunedThresholdError(err error) bool {
var newIsBelowPrunedThresholdError BelowPrunedThresholdError
return errors.As(err, &newIsBelowPrunedThresholdError)
}

View File

@ -0,0 +1,14 @@
package models
// LivenessState defines the core minimum data required to maintain liveness
// of the pacemaker of the consensus state machine.
type LivenessState struct {
// The filter scope of the consensus state.
Filter []byte
// The current rank of the pacemaker.
CurrentRank uint64
// The latest quorum certificate seen by the pacemaker.
LatestQuorumCertificate QuorumCertificate
// The previous rank's timeout certificate, if applicable.
PriorRankTimeoutCertificate TimeoutCertificate
}

View File

@ -0,0 +1,45 @@
package models
import (
"errors"
)
type Proposal[StateT Unique] struct {
State *State[StateT]
PreviousRankTimeoutCertificate TimeoutCertificate
}
func ProposalFrom[StateT Unique](
state *State[StateT],
prevTC TimeoutCertificate,
) *Proposal[StateT] {
return &Proposal[StateT]{
State: state,
PreviousRankTimeoutCertificate: prevTC,
}
}
type SignedProposal[StateT Unique, VoteT Unique] struct {
Proposal[StateT]
Vote *VoteT
}
func (p *SignedProposal[StateT, VoteT]) ProposerVote() (*VoteT, error) {
if p.Vote == nil {
return nil, errors.New("missing vote")
}
return p.Vote, nil
}
func SignedProposalFromState[StateT Unique, VoteT Unique](
p *Proposal[StateT],
v *VoteT,
) *SignedProposal[StateT, VoteT] {
return &SignedProposal[StateT, VoteT]{
Proposal: Proposal[StateT]{
State: p.State,
PreviousRankTimeoutCertificate: p.PreviousRankTimeoutCertificate,
},
Vote: v,
}
}

View File

@ -0,0 +1,20 @@
package models
// QuorumCertificate defines the minimum properties required of a consensus
// clique's validating set of data for a frame.
type QuorumCertificate interface {
// GetFilter returns the applicable filter for the consensus clique.
GetFilter() []byte
// GetRank returns the rank of the consensus loop.
GetRank() uint64
// GetFrameNumber returns the frame number applied to the round.
GetFrameNumber() uint64
// GetSelector returns the selector of the frame.
GetSelector() Identity
// GetTimestamp returns the timestamp of the certificate.
GetTimestamp() int64
// GetAggregatedSignature returns the set of signers who voted on the round.
GetAggregatedSignature() AggregatedSignature
// Equals compares inner equality with another quorum certificate.
Equals(other QuorumCertificate) bool
}

101
consensus/models/state.go Normal file
View File

@ -0,0 +1,101 @@
package models
import (
"fmt"
)
// State is the HotStuff algorithm's concept of a state, which - in the bigger
// picture - corresponds to the state header.
type State[StateT Unique] struct {
Rank uint64
Identifier Identity
ProposerID Identity
ParentQuorumCertificate QuorumCertificate
Timestamp uint64 // Unix milliseconds
State *StateT
}
// StateFrom combines external state with source parent quorum certificate.
func StateFrom[StateT Unique](
t *StateT,
parentCert QuorumCertificate,
) *State[StateT] {
state := State[StateT]{
Identifier: (*t).Identity(),
Rank: (*t).GetRank(),
ParentQuorumCertificate: parentCert,
ProposerID: (*t).Source(),
Timestamp: (*t).GetTimestamp(),
State: t,
}
return &state
}
// GenesisStateFrom returns a generic consensus model of genesis state.
func GenesisStateFrom[StateT Unique](internal *StateT) *State[StateT] {
genesis := &State[StateT]{
Identifier: (*internal).Identity(),
Rank: (*internal).GetRank(),
ProposerID: (*internal).Source(),
ParentQuorumCertificate: nil,
Timestamp: (*internal).GetTimestamp(),
State: internal,
}
return genesis
}
// CertifiedState holds a certified state, which is a state and a
// QuorumCertificate that is pointing to the state. A QuorumCertificate is the
// aggregated form of votes from a supermajority of HotStuff and
// therefore proves validity of the state. A certified state satisfies:
// State.Rank == QuorumCertificate.Rank and
// State.Identifier == QuorumCertificate.Identifier
type CertifiedState[StateT Unique] struct {
State *State[StateT]
CertifyingQuorumCertificate QuorumCertificate
}
// NewCertifiedState constructs a new certified state. It checks the consistency
// requirements and returns an exception otherwise:
//
// State.Rank == QuorumCertificate.Rank and State.Identifier ==
//
// QuorumCertificate.Identifier
func NewCertifiedState[StateT Unique](
state *State[StateT],
quorumCertificate QuorumCertificate,
) (CertifiedState[StateT], error) {
if state.Rank != quorumCertificate.GetRank() {
return CertifiedState[StateT]{},
fmt.Errorf(
"state's rank (%d) should equal the qc's rank (%d)",
state.Rank,
quorumCertificate.GetRank(),
)
}
if state.Identifier != quorumCertificate.GetSelector() {
return CertifiedState[StateT]{},
fmt.Errorf(
"state's ID (%x) should equal the state referenced by the qc (%x)",
state.Identifier,
quorumCertificate.GetSelector(),
)
}
return CertifiedState[StateT]{
State: state,
CertifyingQuorumCertificate: quorumCertificate,
}, nil
}
// Identifier returns a unique identifier for the state (the ID signed to
// produce a state vote). To avoid repeated computation, we use value from the
// QuorumCertificate.
func (b *CertifiedState[StateT]) Identifier() Identity {
return b.CertifyingQuorumCertificate.GetSelector()
}
// Rank returns rank where the state was proposed.
func (b *CertifiedState[StateT]) Rank() uint64 {
return b.State.Rank
}

View File

@ -0,0 +1,19 @@
package models
// TimeoutCertificate defines the minimum properties required of a consensus
// clique's invalidating set of data for a frame.
type TimeoutCertificate interface {
// GetFilter returns the applicable filter for the consensus clique.
GetFilter() []byte
// GetRank returns the rank of the consensus loop.
GetRank() uint64
// GetLatestRanks returns the latest ranks seen by members of clique, in
// matching order to the clique's prover set (in ascending ring order).
GetLatestRanks() []uint64
// GetLatestQuorumCert returns the latest quorum certificate accepted.
GetLatestQuorumCert() QuorumCertificate
// GetAggregatedSignature returns the set of signers who voted on the round.
GetAggregatedSignature() AggregatedSignature
// Equals compares inner equality with another timeout certificate.
Equals(other TimeoutCertificate) bool
}

View File

@ -0,0 +1,45 @@
package models
import "bytes"
// TimeoutState represents the stored state change step relevant to the point of
// rank of a given instance of the consensus state machine.
type TimeoutState[VoteT Unique] struct {
// The rank of the timeout data.
Rank uint64
// The latest quorum certificate seen by the pacemaker.
LatestQuorumCertificate QuorumCertificate
// The previous rank's timeout certificate, if applicable.
PriorRankTimeoutCertificate TimeoutCertificate
// The signed payload which will become part of the new timeout certificate.
Vote *VoteT
// TimeoutTick is the number of times the `timeout.Controller` has
// (re-)emitted the timeout for this rank. When the timer for the rank's
// original duration expires, a `TimeoutState` with `TimeoutTick = 0` is
// broadcast. Subsequently, `timeout.Controller` re-broadcasts the
// `TimeoutState` periodically based on some internal heuristic. Each time
// we attempt a re-broadcast, the `TimeoutTick` is incremented. Incrementing
// the field prevents de-duplicated within the network layer, which in turn
// guarantees quick delivery of the `TimeoutState` after GST and facilitates
// recovery.
TimeoutTick uint64
}
func (t *TimeoutState[VoteT]) Equals(other *TimeoutState[VoteT]) bool {
// Shortcut if `t` and `other` point to the same object; covers case where
// both are nil.
if t == other {
return true
}
if t == nil || other == nil {
// only one is nil, the other not (otherwise we would have returned above)
return false
}
// both are not nil, so we can compare the fields
return t.Rank == other.Rank &&
t.LatestQuorumCertificate.Equals(other.LatestQuorumCertificate) &&
t.PriorRankTimeoutCertificate.Equals(other.PriorRankTimeoutCertificate) &&
(*t.Vote).Source() == (*other.Vote).Source() &&
bytes.Equal((*t.Vote).GetSignature(), (*other.Vote).GetSignature())
}

View File

@ -0,0 +1,26 @@
package models
type Identity = string
// Unique defines important attributes for distinguishing relative basis of
// items.
type Unique interface {
// Identity provides the relevant identity of the given Unique.
Identity() Identity
// Clone should provide a shallow clone of the Unique.
Clone() Unique
// GetRank indicates the ordinal basis of comparison.
GetRank() uint64
// Source provides the relevant identity of who issued the given Unique.
Source() Identity
// GetTimestamp provides the relevant timestamp of the given Unique.
GetTimestamp() uint64
// GetSignature provides the signature of the given Unique (if present).
GetSignature() []byte
}
type WeightedIdentity interface {
PublicKey() []byte
Identity() Identity
Weight() uint64
}

View File

@ -0,0 +1,104 @@
package pubsub
import (
"sync"
"time"
"source.quilibrium.com/quilibrium/monorepo/consensus"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// CommunicatorDistributor ingests outbound consensus messages from HotStuff's
// core logic and distributes them to consumers. This logic only runs inside
// active consensus participants proposing state, voting, collecting +
// aggregating votes to QCs, and participating in the pacemaker (sending
// timeouts, collecting + aggregating timeouts to TCs).
// Concurrency safe.
type CommunicatorDistributor[StateT models.Unique, VoteT models.Unique] struct {
consumers []consensus.CommunicatorConsumer[StateT, VoteT]
lock sync.RWMutex
}
var _ consensus.CommunicatorConsumer[*nilUnique, *nilUnique] = (*CommunicatorDistributor[*nilUnique, *nilUnique])(nil)
func NewCommunicatorDistributor[
StateT models.Unique,
VoteT models.Unique,
]() *CommunicatorDistributor[StateT, VoteT] {
return &CommunicatorDistributor[StateT, VoteT]{}
}
func (d *CommunicatorDistributor[StateT, VoteT]) AddCommunicatorConsumer(
consumer consensus.CommunicatorConsumer[StateT, VoteT],
) {
d.lock.Lock()
defer d.lock.Unlock()
d.consumers = append(d.consumers, consumer)
}
func (d *CommunicatorDistributor[StateT, VoteT]) OnOwnVote(
vote *VoteT,
recipientID models.Identity,
) {
d.lock.RLock()
defer d.lock.RUnlock()
for _, s := range d.consumers {
s.OnOwnVote(vote, recipientID)
}
}
func (d *CommunicatorDistributor[StateT, VoteT]) OnOwnTimeout(
timeout *models.TimeoutState[VoteT],
) {
d.lock.RLock()
defer d.lock.RUnlock()
for _, s := range d.consumers {
s.OnOwnTimeout(timeout)
}
}
func (d *CommunicatorDistributor[StateT, VoteT]) OnOwnProposal(
proposal *models.SignedProposal[StateT, VoteT],
targetPublicationTime time.Time,
) {
d.lock.RLock()
defer d.lock.RUnlock()
for _, s := range d.consumers {
s.OnOwnProposal(proposal, targetPublicationTime)
}
}
// Type used to satisfy generic arguments in compiler time type assertion check
type nilUnique struct{}
// GetSignature implements models.Unique.
func (n *nilUnique) GetSignature() []byte {
panic("unimplemented")
}
// GetTimestamp implements models.Unique.
func (n *nilUnique) GetTimestamp() uint64 {
panic("unimplemented")
}
// Source implements models.Unique.
func (n *nilUnique) Source() models.Identity {
panic("unimplemented")
}
// Clone implements models.Unique.
func (n *nilUnique) Clone() models.Unique {
panic("unimplemented")
}
// GetRank implements models.Unique.
func (n *nilUnique) GetRank() uint64 {
panic("unimplemented")
}
// Identity implements models.Unique.
func (n *nilUnique) Identity() models.Identity {
panic("unimplemented")
}
var _ models.Unique = (*nilUnique)(nil)

View File

@ -0,0 +1,127 @@
package pubsub
import (
"source.quilibrium.com/quilibrium/monorepo/consensus"
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
)
// Distributor distributes notifications to a list of consumers (event
// consumers).
//
// It allows thread-safe subscription of multiple consumers to events.
type Distributor[StateT models.Unique, VoteT models.Unique] struct {
*FollowerDistributor[StateT, VoteT]
*CommunicatorDistributor[StateT, VoteT]
*ParticipantDistributor[StateT, VoteT]
}
var _ consensus.Consumer[*nilUnique, *nilUnique] = (*Distributor[*nilUnique, *nilUnique])(nil)
func NewDistributor[
StateT models.Unique,
VoteT models.Unique,
]() *Distributor[StateT, VoteT] {
return &Distributor[StateT, VoteT]{
FollowerDistributor: NewFollowerDistributor[StateT, VoteT](),
CommunicatorDistributor: NewCommunicatorDistributor[StateT, VoteT](),
ParticipantDistributor: NewParticipantDistributor[StateT, VoteT](),
}
}
// AddConsumer adds an event consumer to the Distributor
func (p *Distributor[StateT, VoteT]) AddConsumer(
consumer consensus.Consumer[StateT, VoteT],
) {
p.FollowerDistributor.AddFollowerConsumer(consumer)
p.CommunicatorDistributor.AddCommunicatorConsumer(consumer)
p.ParticipantDistributor.AddParticipantConsumer(consumer)
}
// FollowerDistributor ingests consensus follower events and distributes it to
// consumers. It allows thread-safe subscription of multiple consumers to
// events.
type FollowerDistributor[StateT models.Unique, VoteT models.Unique] struct {
*ProposalViolationDistributor[StateT, VoteT]
*FinalizationDistributor[StateT]
}
var _ consensus.FollowerConsumer[*nilUnique, *nilUnique] = (*FollowerDistributor[*nilUnique, *nilUnique])(nil)
func NewFollowerDistributor[
StateT models.Unique,
VoteT models.Unique,
]() *FollowerDistributor[StateT, VoteT] {
return &FollowerDistributor[StateT, VoteT]{
ProposalViolationDistributor: NewProposalViolationDistributor[StateT, VoteT](),
FinalizationDistributor: NewFinalizationDistributor[StateT](),
}
}
// AddFollowerConsumer registers the input `consumer` to be notified on
// `consensus.ConsensusFollowerConsumer` events.
func (d *FollowerDistributor[StateT, VoteT]) AddFollowerConsumer(
consumer consensus.FollowerConsumer[StateT, VoteT],
) {
d.FinalizationDistributor.AddFinalizationConsumer(consumer)
d.ProposalViolationDistributor.AddProposalViolationConsumer(consumer)
}
// TimeoutAggregationDistributor ingests timeout aggregation events and
// distributes it to consumers. It allows thread-safe subscription of multiple
// consumers to events.
type TimeoutAggregationDistributor[VoteT models.Unique] struct {
*TimeoutAggregationViolationDistributor[VoteT]
*TimeoutCollectorDistributor[VoteT]
}
var _ consensus.TimeoutAggregationConsumer[*nilUnique] = (*TimeoutAggregationDistributor[*nilUnique])(nil)
func NewTimeoutAggregationDistributor[
VoteT models.Unique,
]() *TimeoutAggregationDistributor[VoteT] {
return &TimeoutAggregationDistributor[VoteT]{
TimeoutAggregationViolationDistributor: NewTimeoutAggregationViolationDistributor[VoteT](),
TimeoutCollectorDistributor: NewTimeoutCollectorDistributor[VoteT](),
}
}
func (d *TimeoutAggregationDistributor[VoteT]) AddTimeoutAggregationConsumer(
consumer consensus.TimeoutAggregationConsumer[VoteT],
) {
d.TimeoutAggregationViolationDistributor.
AddTimeoutAggregationViolationConsumer(consumer)
d.TimeoutCollectorDistributor.AddTimeoutCollectorConsumer(consumer)
}
// VoteAggregationDistributor ingests vote aggregation events and distributes it
// to consumers. It allows thread-safe subscription of multiple consumers to
// events.
type VoteAggregationDistributor[
StateT models.Unique,
VoteT models.Unique,
] struct {
*VoteAggregationViolationDistributor[StateT, VoteT]
*VoteCollectorDistributor[VoteT]
}
var _ consensus.VoteAggregationConsumer[*nilUnique, *nilUnique] = (*VoteAggregationDistributor[*nilUnique, *nilUnique])(nil)
func NewVoteAggregationDistributor[
StateT models.Unique,
VoteT models.Unique,
]() *VoteAggregationDistributor[StateT, VoteT] {
return &VoteAggregationDistributor[StateT, VoteT]{
VoteAggregationViolationDistributor: NewVoteAggregationViolationDistributor[StateT, VoteT](),
VoteCollectorDistributor: NewQCCreatedDistributor[VoteT](),
}
}
func (
d *VoteAggregationDistributor[StateT, VoteT],
) AddVoteAggregationConsumer(
consumer consensus.VoteAggregationConsumer[StateT, VoteT],
) {
d.VoteAggregationViolationDistributor.
AddVoteAggregationViolationConsumer(consumer)
d.VoteCollectorDistributor.AddVoteCollectorConsumer(consumer)
}

Some files were not shown because too many files have changed in this diff Show More