mirror of
https://github.com/QuilibriumNetwork/ceremonyclient.git
synced 2026-03-06 00:37:42 +08:00
wip: conversion of hotstuff from flow into Q-oriented model
This commit is contained in:
parent
4df761de20
commit
d71b0538f2
18
consensus/.mockery.yaml
Normal file
18
consensus/.mockery.yaml
Normal 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"
|
||||
@ -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.
|
||||
|
||||
77
consensus/backoff_timer.go
Normal file
77
consensus/backoff_timer.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
154
consensus/consensus_committee.go
Normal file
154
consensus/consensus_committee.go
Normal 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)
|
||||
}
|
||||
453
consensus/consensus_consumer.go
Normal file
453
consensus/consensus_consumer.go
Normal 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]
|
||||
}
|
||||
82
consensus/consensus_events.go
Normal file
82
consensus/consensus_events.go
Normal 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])
|
||||
}
|
||||
23
consensus/consensus_finalizer.go
Normal file
23
consensus/consensus_finalizer.go
Normal 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
|
||||
}
|
||||
106
consensus/consensus_forks.go
Normal file
106
consensus/consensus_forks.go
Normal 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
|
||||
}
|
||||
29
consensus/consensus_leader.go
Normal file
29
consensus/consensus_leader.go
Normal 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)
|
||||
}
|
||||
25
consensus/consensus_liveness.go
Normal file
25
consensus/consensus_liveness.go
Normal 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
|
||||
}
|
||||
65
consensus/consensus_pacemaker.go
Normal file
65
consensus/consensus_pacemaker.go
Normal 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
|
||||
}
|
||||
25
consensus/consensus_producer.go
Normal file
25
consensus/consensus_producer.go
Normal 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)
|
||||
}
|
||||
73
consensus/consensus_safety_rules.go
Normal file
73
consensus/consensus_safety_rules.go
Normal 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)
|
||||
}
|
||||
161
consensus/consensus_signature.go
Normal file
161
consensus/consensus_signature.go
Normal 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,
|
||||
)
|
||||
}
|
||||
39
consensus/consensus_signer.go
Normal file
39
consensus/consensus_signer.go
Normal 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)
|
||||
}
|
||||
18
consensus/consensus_store.go
Normal file
18
consensus/consensus_store.go
Normal 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)
|
||||
}
|
||||
20
consensus/consensus_sync.go
Normal file
20
consensus/consensus_sync.go
Normal 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)
|
||||
}
|
||||
128
consensus/consensus_timeout.go
Normal file
128
consensus/consensus_timeout.go
Normal 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)
|
||||
}
|
||||
12
consensus/consensus_tracing.go
Normal file
12
consensus/consensus_tracing.go
Normal 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) {}
|
||||
32
consensus/consensus_validator.go
Normal file
32
consensus/consensus_validator.go
Normal 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)
|
||||
}
|
||||
45
consensus/consensus_verifier.go
Normal file
45
consensus/consensus_verifier.go
Normal 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
|
||||
}
|
||||
63
consensus/consensus_voting.go
Normal file
63
consensus/consensus_voting.go
Normal 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
|
||||
}
|
||||
10
consensus/consensus_weight.go
Normal file
10
consensus/consensus_weight.go
Normal 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
|
||||
}
|
||||
688
consensus/event_handler/event_handler.go
Normal file
688
consensus/event_handler/event_handler.go
Normal 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)
|
||||
346
consensus/event_loop/event_loop.go
Normal file
346
consensus/event_loop/event_loop.go
Normal 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)
|
||||
394
consensus/forest/leveled_forest.go
Normal file
394
consensus/forest/leveled_forest.go
Normal 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
103
consensus/forest/vertex.go
Normal 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
688
consensus/forks/forks.go
Normal 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)
|
||||
|
||||
77
consensus/forks/state_container.go
Normal file
77
consensus/forks/state_container.go
Normal 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)
|
||||
@ -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
|
||||
)
|
||||
@ -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=
|
||||
|
||||
120
consensus/helper/quorum_certificate.go
Normal file
120
consensus/helper/quorum_certificate.go
Normal 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
112
consensus/helper/state.go
Normal 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
|
||||
}
|
||||
}
|
||||
153
consensus/helper/timeout_certificate.go
Normal file
153
consensus/helper/timeout_certificate.go
Normal 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
|
||||
}
|
||||
}
|
||||
44
consensus/mocks/communicator_consumer.go
Normal file
44
consensus/mocks/communicator_consumer.go
Normal 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
|
||||
}
|
||||
123
consensus/mocks/consensus_store.go
Normal file
123
consensus/mocks/consensus_store.go
Normal 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
126
consensus/mocks/consumer.go
Normal 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
|
||||
}
|
||||
249
consensus/mocks/dynamic_committee.go
Normal file
249
consensus/mocks/dynamic_committee.go
Normal 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
|
||||
}
|
||||
162
consensus/mocks/event_handler.go
Normal file
162
consensus/mocks/event_handler.go
Normal 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
|
||||
}
|
||||
67
consensus/mocks/event_loop.go
Normal file
67
consensus/mocks/event_loop.go
Normal 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
|
||||
}
|
||||
37
consensus/mocks/finalization_consumer.go
Normal file
37
consensus/mocks/finalization_consumer.go
Normal 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
|
||||
}
|
||||
45
consensus/mocks/finalizer.go
Normal file
45
consensus/mocks/finalizer.go
Normal 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
|
||||
}
|
||||
47
consensus/mocks/follower_consumer.go
Normal file
47
consensus/mocks/follower_consumer.go
Normal 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
|
||||
}
|
||||
32
consensus/mocks/follower_loop.go
Normal file
32
consensus/mocks/follower_loop.go
Normal 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
183
consensus/mocks/forks.go
Normal 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
|
||||
}
|
||||
89
consensus/mocks/leader_provider.go
Normal file
89
consensus/mocks/leader_provider.go
Normal 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
|
||||
}
|
||||
77
consensus/mocks/liveness_provider.go
Normal file
77
consensus/mocks/liveness_provider.go
Normal 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
|
||||
}
|
||||
205
consensus/mocks/pacemaker.go
Normal file
205
consensus/mocks/pacemaker.go
Normal 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
98
consensus/mocks/packer.go
Normal 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
|
||||
}
|
||||
91
consensus/mocks/participant_consumer.go
Normal file
91
consensus/mocks/participant_consumer.go
Normal 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
|
||||
}
|
||||
47
consensus/mocks/proposal_duration_provider.go
Normal file
47
consensus/mocks/proposal_duration_provider.go
Normal 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
|
||||
}
|
||||
37
consensus/mocks/proposal_violation_consumer.go
Normal file
37
consensus/mocks/proposal_violation_consumer.go
Normal 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
|
||||
}
|
||||
87
consensus/mocks/read_only_consensus_store.go
Normal file
87
consensus/mocks/read_only_consensus_store.go
Normal 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
189
consensus/mocks/replicas.go
Normal 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
|
||||
}
|
||||
117
consensus/mocks/safety_rules.go
Normal file
117
consensus/mocks/safety_rules.go
Normal 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
|
||||
}
|
||||
93
consensus/mocks/signature_aggregator.go
Normal file
93
consensus/mocks/signature_aggregator.go
Normal 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
87
consensus/mocks/signer.go
Normal 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
|
||||
}
|
||||
57
consensus/mocks/state_producer.go
Normal file
57
consensus/mocks/state_producer.go
Normal 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
|
||||
}
|
||||
57
consensus/mocks/state_signer_decoder.go
Normal file
57
consensus/mocks/state_signer_decoder.go
Normal 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
|
||||
}
|
||||
61
consensus/mocks/sync_provider.go
Normal file
61
consensus/mocks/sync_provider.go
Normal 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
|
||||
}
|
||||
62
consensus/mocks/timeout_aggregation_consumer.go
Normal file
62
consensus/mocks/timeout_aggregation_consumer.go
Normal 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
|
||||
}
|
||||
37
consensus/mocks/timeout_aggregation_violation_consumer.go
Normal file
37
consensus/mocks/timeout_aggregation_violation_consumer.go
Normal 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
|
||||
}
|
||||
57
consensus/mocks/timeout_aggregator.go
Normal file
57
consensus/mocks/timeout_aggregator.go
Normal 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
|
||||
}
|
||||
63
consensus/mocks/timeout_collector.go
Normal file
63
consensus/mocks/timeout_collector.go
Normal 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
|
||||
}
|
||||
52
consensus/mocks/timeout_collector_consumer.go
Normal file
52
consensus/mocks/timeout_collector_consumer.go
Normal 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
|
||||
}
|
||||
59
consensus/mocks/timeout_collector_factory.go
Normal file
59
consensus/mocks/timeout_collector_factory.go
Normal 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
|
||||
}
|
||||
71
consensus/mocks/timeout_collectors.go
Normal file
71
consensus/mocks/timeout_collectors.go
Normal 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
|
||||
}
|
||||
45
consensus/mocks/timeout_processor.go
Normal file
45
consensus/mocks/timeout_processor.go
Normal 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
|
||||
}
|
||||
59
consensus/mocks/timeout_processor_factory.go
Normal file
59
consensus/mocks/timeout_processor_factory.go
Normal 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
|
||||
}
|
||||
132
consensus/mocks/timeout_signature_aggregator.go
Normal file
132
consensus/mocks/timeout_signature_aggregator.go
Normal 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
|
||||
}
|
||||
34
consensus/mocks/trace_logger.go
Normal file
34
consensus/mocks/trace_logger.go
Normal 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
|
||||
}
|
||||
111
consensus/mocks/validator.go
Normal file
111
consensus/mocks/validator.go
Normal 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
|
||||
}
|
||||
81
consensus/mocks/verifier.go
Normal file
81
consensus/mocks/verifier.go
Normal 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
|
||||
}
|
||||
85
consensus/mocks/verifying_vote_processor.go
Normal file
85
consensus/mocks/verifying_vote_processor.go
Normal 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
|
||||
}
|
||||
52
consensus/mocks/vote_aggregation_consumer.go
Normal file
52
consensus/mocks/vote_aggregation_consumer.go
Normal 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
|
||||
}
|
||||
42
consensus/mocks/vote_aggregation_violation_consumer.go
Normal file
42
consensus/mocks/vote_aggregation_violation_consumer.go
Normal 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
|
||||
}
|
||||
80
consensus/mocks/vote_aggregator.go
Normal file
80
consensus/mocks/vote_aggregator.go
Normal 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
|
||||
}
|
||||
106
consensus/mocks/vote_collector.go
Normal file
106
consensus/mocks/vote_collector.go
Normal 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
|
||||
}
|
||||
37
consensus/mocks/vote_collector_consumer.go
Normal file
37
consensus/mocks/vote_collector_consumer.go
Normal 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
|
||||
}
|
||||
92
consensus/mocks/vote_collectors.go
Normal file
92
consensus/mocks/vote_collectors.go
Normal 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
|
||||
}
|
||||
65
consensus/mocks/vote_processor.go
Normal file
65
consensus/mocks/vote_processor.go
Normal 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
|
||||
}
|
||||
59
consensus/mocks/vote_processor_factory.go
Normal file
59
consensus/mocks/vote_processor_factory.go
Normal 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
|
||||
}
|
||||
252
consensus/mocks/voting_provider.go
Normal file
252
consensus/mocks/voting_provider.go
Normal 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
|
||||
}
|
||||
42
consensus/mocks/weight_provider.go
Normal file
42
consensus/mocks/weight_provider.go
Normal 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
|
||||
}
|
||||
130
consensus/mocks/weighted_signature_aggregator.go
Normal file
130
consensus/mocks/weighted_signature_aggregator.go
Normal 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
|
||||
}
|
||||
34
consensus/mocks/workerpool.go
Normal file
34
consensus/mocks/workerpool.go
Normal 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
|
||||
}
|
||||
29
consensus/mocks/workers.go
Normal file
29
consensus/mocks/workers.go
Normal 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
|
||||
}
|
||||
48
consensus/models/aggregated_signature.go
Normal file
48
consensus/models/aggregated_signature.go
Normal 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
|
||||
}
|
||||
15
consensus/models/consensus_state.go
Normal file
15
consensus/models/consensus_state.go
Normal 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]
|
||||
}
|
||||
13
consensus/models/control_flows.go
Normal file
13
consensus/models/control_flows.go
Normal 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
588
consensus/models/errors.go
Normal 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)
|
||||
}
|
||||
14
consensus/models/liveness_state.go
Normal file
14
consensus/models/liveness_state.go
Normal 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
|
||||
}
|
||||
45
consensus/models/proposal.go
Normal file
45
consensus/models/proposal.go
Normal 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,
|
||||
}
|
||||
}
|
||||
20
consensus/models/quorum_certificate.go
Normal file
20
consensus/models/quorum_certificate.go
Normal 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
101
consensus/models/state.go
Normal 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
|
||||
}
|
||||
19
consensus/models/timeout_certificate.go
Normal file
19
consensus/models/timeout_certificate.go
Normal 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
|
||||
}
|
||||
45
consensus/models/timeout_state.go
Normal file
45
consensus/models/timeout_state.go
Normal 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())
|
||||
}
|
||||
26
consensus/models/unique.go
Normal file
26
consensus/models/unique.go
Normal 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
|
||||
}
|
||||
104
consensus/notifications/pubsub/communicator_distributor.go
Normal file
104
consensus/notifications/pubsub/communicator_distributor.go
Normal 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)
|
||||
127
consensus/notifications/pubsub/distributor.go
Normal file
127
consensus/notifications/pubsub/distributor.go
Normal 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
Loading…
Reference in New Issue
Block a user