# hotstuff-rs v. 0.4 changelog
The changes can be grouped as follows:
1. Consensus for validator-set-updating blocks,
2. Validator set speculation,
3. View synchronization and block sync trigger,
4. Related refactoring and restructuring for the sake of integrating the above-mentioned features,
5. Bug fixes:
- pending validator sets,
- commit rule,
- locking precommit and commit QCs
6. Enhancements:
- allow replicas to vote for a known block
- wrapper types to replace type aliasing, e.g., with `CryptoHash` or `ViewNumber` (optional)
# Changes to the `BlockTree` fields
New fields:
1. `resigning_validator_set`, deserialized into `ValidatorSet` type,
2. `locked_validator_set_updating_block`, deserialized into `CryptoHash` type,
3. `locked_validator_set_updating_view`, deserialized into `ViewNumber` type,
4. `highest_block_justify`, deserialized into `QuorumCertificate` type,
5. `highest_tc`, deserialized into `TimeoutCertificate` type.
Modified fields:
1. Replace `pending_validator_set_updates` with `validator_set_updates_info` (or `attached_validator_set_updates`), deserialized into `ValidatorSetUpdatesInfo` type
# Consensus for validator-set-updating blocks
## New `Validators` module
This module regulates what validators can do in the speculation vs. "normal" periods, for instance when they can act as leaders, when they can vote and what they can vote for. The predicates contained in this module implement the validator set speculation protocol for the speculation period, and for the "normal" period they generalize predicates that are already found in the code of the `Algorithm` module.
### `ValidatorSetState` struct
```rust
enum ValidatorSetState = {
StableValidatorSet { validator_set: ValidatorSet},
ValidatorSetUpdate { new_validator_set: ValidatorSet,
old_validator_set: ValidatorSet,
block: CryptoHash,
}
}
```
**Methods:**
#### `i_am_voter`
This method generalizes the predicate from [this line](https://github.com/parallelchain-io/hotstuff_rs/blob/1f4352b9941ffa0deaa5192e7388361b39575017/src/algorithm.rs#L356) and contains an additional check that implements the validator set speculation protocol.
- For `StableValidatorSet{vs}`, a validator from `vs` is always a voter (unless the vote is for the "decide" phase in which case the validator set state should have been updated to `ValidatorSetUpdate{vs', vs, b}`).
- For `ValidatorSetUpdate{vs', vs, b}` a validator can be a voter for a given nudge or proposal depending on its membership in `vs` or `vs'` and the `justify` QC of the proposal or the nudge.
```rust
fn i_am_voter(self, cur_view: ViewNumber, justify: QuorumCertificate) -> bool {
match self {
StableValidatorSet(_) => true,
ValidatorSetUpdate{vs', vs, b} => {
if (vs.contains(me) && !vs'.contains(me) {
justify.block == b &&
justify.phase <= Commit
} else if (vs'.contains(me) && !vs.contains(me) {
justify.block == b &&
qc.phase >= Commit
} else {
vs.contains(me) && vs'.contains(me)
}
}
}
}
```
#### `i_am_leader`
This method generalizes the predicate from [this line](https://github.com/parallelchain-io/hotstuff_rs/blob/1f4352b9941ffa0deaa5192e7388361b39575017/src/algorithm.rs#L142) and contains an additional check that implements the validator set speculation protocol.
For `StableValidatorSet{vs}`, a validator is a leader of the current view if it is the leader of the current view for`vs`.
For `ValidatorSetUpdate{vs', vs, b}` a validator is a leader of the current view if and only if:
1. The validator is a leader of the current view for `vs` or `vs'`, and
2. The validator has a `highest_qc` on top of which it can propose or nudge given its membership in the committed or resigning validator set.
```rust
fn i_am_leader(self, cur_view: ViewNumber, highest_qc: QuorumCertificate, pacemaker: impl Pacemaker) -> (bool, Option<Justify>) {
match self {
StableValidatorSet(vs) => {
pacemaker.view_leader(cur_view, &vs) == me.public()
},
ValidatorSetUpdate{vs', vs, b} => {
let i_am_leader_of_vs = pacemaker.view_leader(cur_view, &vs) == me.public();
let i_am_leader_of_vs' = pacemaker.view_leader(cur_view, &vs') == me.public();
if i_am_leader_of_vs {
highest_qc.block == b &&
highest_qc.phase = Commit)
} else if i_am_leader_of_vs' {
highest_qc.block == b &&
highest_qc.phase >= Commit
} else { false }
}
}
```
#### `next_leader`
Generalizes [this line](https://github.com/parallelchain-io/hotstuff_rs/blob/1f4352b9941ffa0deaa5192e7388361b39575017/src/algorithm.rs#L357).
By `next_leader` we mean the intended recipient of a given `Vote` message.
For `StableValidatorSet{vs}`, the next leader is simply the leader of the next view for `vs`.
For `ValidatorSetUpdate{vs', vs, b}` the next leader, to whom the vote is addressed, depends on the phase of the vote.
```rust
fn next_leader(self, cur_view, vote: &Vote, pacemaker: impl Pacemaker) -> VerifyingKey {
match self {
StableValidatorSet(vs) => {
pacemaker.view_leader(cur_view+1, &vs)
},
ValidatorSetUpdate{vs', vs, b} => {
if vote.block == b {
match vote.phase {
Prepare | Precommit(_) | Commit(_) => pacemaker.view_leader(cur_view+1, &vs),
Decide(_) => pacemaker.view_leader(cur_view+1, &vs')
}
} else {
panic!("During the Validator Set Update phase a validator can only vote for the validator-set-updating block!")
}
}
}
```
#### Optional: `i_am_next_leader`
Generalizes [this predicate](https://github.com/parallelchain-io/hotstuff_rs/blob/1f4352b9941ffa0deaa5192e7388361b39575017/src/algorithm.rs#L411) used to determine whether a replica is the next leader.
```rust
fn i_am_next_leader(self, cur_view) -> bool {
match self {
StableValidatorSet(vs) => {
me.public() == pacemaker.view_leader(cur_view + 1, vs)
},
ValidatorSetUpdate{vs', vs, b} => {
me.public() == pacemaker.view_leader(cur_view + 1, vs) ||
me.public() == pacemaker.view_leader(cur_view + 1, vs')
}
}
```
## Safety-related changes in `Types` and `State`
With the validator set speculation and the new consensus protocol for validator-set-updating blocks, as well as some of the bug fixes, we have plenty of changes to the safety predicates, which calls for neat and type-safe abstractions.
### `Justify` enum
This struct is a wrapper for the `QuorumCertificate` type that introduces more type safety. The idea is that only some `QuorumCertificate`s, i.e., the ones from `Generic` or `Decide` phase, can serve as the `justify` of a block, and only some i.e, the ones from `Prepare`, `Precommit` or `Commit` phase, can serve as the `justify` of a `Nudge`.
```rust
pub(crate) enum Justify {
BlockJustify(QuorumCertificate),
NudgeJustify(QuorumCertificate)
```
This is merely for the sake of code readibility and in order to avoid listing all acceptable phases of a given QC, like we do in `safe_qc`. Instead, we will:
- Define `From` and `Into` instances to convert between the `Justify` and `QuorumCertificate` types,
- Change the type of the `justify` fields of a `Block` and a `Nudge` to the `Justify` type,
- In `safe_block` and on receiving a `Nudge`, check if its `justify` is a `BlockJustify` and a `NudgeJustify` respectively,
- in `safe_qc` pattern match on the `Justify::from(qc)`
### `safe_qc`
New requirements:
1. If a validator set updating block is locked, then the `NudgeJustify` must be for that block, and(! or have higher view than the lock)
2. Consecutive views requirement: `prepareQC.view + 1 = precommitQC.view` and `precommitQC.view + 1 = commitQC.view`.
```rust
pub fn safe_qc(&self, qc: &QuorumCertificate, chain_id: ChainID) -> bool {
/* 1 */
(qc.chain_id == chain_id || qc.is_genesis_qc()) &&
/* 2 */
(self.contains(&qc.block) || block_justify.qc.is_genesis_qc()) &&
/* 3 */ qc.view >= self.locked_view() &&
match Justify::from(qc) {
BlockJustify(qc) => {
/* 4 */ (qc.phase.is_generic() && !self.contains_validator_set_updates(&qc.block) ||
qc.phase.is_decide() && self.contains_validator_set_updates(&qc.block))
},
NudgeJustify(qc) => {
/* 4 */ self.contains_validator_set_updates(&qc.block) &&
/* 5 */ (self.locked_validator_set_updating_block().is_none() || qc.block == self.locked_validator_set_updating_block().unwrap() || qc.view > self.locked_validator_set_updating_view().unwrap())
/* 6 */ match qc.phase {
Precommit(prepare_qc_view) => qc.view == prepare_qc_view + 1,
Commit(precommit_qc_view) => qc.view == precommit_qc_view + 1,
_ => true
}
}
}
}
```
**Note:** on receiving any nudge or proposal, a replica should check if it should unlock `self.locked_validator_set_updating_block()` (because a QC with a view higher than the `locked_validator_set_updating_block_view` was seen) before checking `safe_qc`!
**Note:** `safe_qc` (along with `correct_qc` and `safe_block`) may not be sufficient cobdition for a replica to vote for a given nudge or proposal. For prepare and precommit QCs, a replica should check if `cur_view = qc.view + 1` before voting.
**Also note** that after checking `safe_qc` on receiving a nudge, we should also check if the lock on a validator-set-updating block should be released and/or another block should be locked. This is different from the order as specified in the protocol spec, where the lock should be checked and possibly released before checking `safe_qc`. This is why the line `qc.view > self.locked_validator_set_updating_view().unwrap()` is included in condition 4.
### New `correct_qc` method of the `BlockTree`
This method serves to check the cryptographic correctness of a `BlockJustify` against an appropriate validator set, depending on where the replica is in the protocol, i.e., depending on the state of the `BlockTree`.
The assumption behind this implementation is that a replica may need to verify the correctness of a QC for a parent block more than once. Likewise, it may need to validate a parent QC, for example after seeing a `decideQC`, a replica may need to validate a commitQC since other replicas have not received the `decideQC` and are nuding commitQC. In general, a replica may need to go one step back in validating QCs - since seeing a QC is generally an evidence that a quorum is at most one step behind.
Hence, how a QC for a validator-set-updating block should be validated depends on whether the validator set updates have already been applied or are still pending.
```rust
pub fn correct_qc(&self, qc: &QuorumCertificate) -> bool {
match qc.phase {
Generic => qc.is_correct(self.committed_validator_set()),
Prepare | Precommit(_) | Commit(_) => {
match self.validator_set_updates(qc.block) {
Pending(vs_updates) => qc.is_correct(self.committed_validator_set()),
Committed(vs_updates) => {
let prev_vs = self.committed_validator_set().apply(vs_updates.inverse());
qc.is_correct(prev_vs)
},
_ => false // The block must have associated validator set updates!
}
},
Decide(_) => {
match self.validator_set_updates(qc.block) {
Pending(vs_updates) => {
let vs = self.committed_validator_set();
let vs' = self.committed_validator_set().apply(vs_updates);
let (vs'_qc, vs_qc) = self.qc.split(vs', vs);
vs'_qc.is_correct(vs') && vs_qc.is_correct(vs)
},
Committed(vs_updates) => {
let vs' = self.committed_validator_set();
let vs = self.committed_validator_set().apply(vs_updates.inverse());
let (vs'_qc, vs_qc) = self.qc.split(vs', vs);
vs'_qc.is_correct(vs') && vs_qc.is_correct(vs)
}
}
}
}
}
```
### Modified `safe_block`
Two changes:
1. New requirement: If a block is locked, then to be `safe_block` either the block must be the locked block, i.e., have the same hash, or its `justify.view` must be greater than the view of the locked block
2. Instead of `(block.justify.phase.is_generic() || block.justify.phase.is_commit())`, check if the `justify` is of the `BlockJustify` type
4. Removed requirement: `!self.contains(&block.hash)`.
**Note** that this requirement is not entirely removed, rather **we will check if the block is already in the `BlockTree` before inserting it to the `BlockTree`**. However, removing the requirement from `safe_block` allows us to vote for a block that is already in the block tree - this is essential fro the liveness of the consensus protocol for validator set updating blocks.
**Also note** that after checking `safe_block` on receiving a proposal, we should also check if the lock on a validator-set-updating block should be released and/or another block should be locked. This is different from the order as specified in the protocol spec, where the lock should be checked and possibly released before checking `safe_block`. This is why the line `qc.view > self.locked_validator_set_updating_view().unwrap()` is included in condition 4.
```rust
pub fn safe_block(&self, block: &Block, chain_id: ChainID) -> bool {
/* 1 */
self.safe_qc(&block.justify, chain_id) &&
/* 2 */ (self.locked_validator_set_updating_block.is_none() ||
block.hash == self.locked_validator_set_updating_block().unwrap() ||
block.justify.view > self.locked_validator_set_updating_view().unwrap() )
}
```
### Commit rule
A method of the `BlockTree`.
We check if the justify of a block is a valid commitQC for the block's great-grandparent, i.e., if it satisfies the commit rule. This is meant to be applied only to a genericQC.
```rust
let parent_justify = self.block_justify(block_justify.qc.block);
let grandparent_justify = self.block_justify(parent_justify.qc.block);
block_justify.qc.view == parent_justify.qc.view + 1 &&
parent_justify.qc.view == granparent_justify.qc.view + 1
```
This check shall be integrated into [these lines](https://github.com/parallelchain-io/hotstuff_rs/blob/1f4352b9941ffa0deaa5192e7388361b39575017/src/state.rs#L192), and divided into two short-circuiting checks.
### `insert_block` and `commit_block`
TODO: make sure `insert_block` is self-sufficient for committing and deciding blocks, as it is the only function that writes to the `BlockTree` when in sync.
#### `insert_block`
Outline of changes:
1. Check if the lock on a validator-set-updating block should be removed or updated,
2. Replace the `Commit` phase justify, with a `Decide` phase justify,
3. For a `Decide` justify, still call `commit_block` (in case the block has not been committed yet, for instance if `insert_block` is called from `sync`),
4. For a `Decide` justify, set `resigning_validator_set = None`, i.e., forget about the old validator set.
5. For a `Decide(v)` justify, set `locked_view = v` (`v` is the view of the `commitQC`).
#### `commit_block`
Outline of changes:
1. For a validator-set-updating block, set the newly committed and resigning validator sets accordingly.
<!-- 2. For a validatir-set-updating block, update `locked_view = commitQC.view`. -->
## Algorithm
1. `on_receive_nudge` and `on_receive_proposal` must incorporate changes regarding the locking behaviour, extra collectors, and forgetting the resigning validator set.
2. `on_receive_proposal` should also check if the block exists before inserting.
3. `on_receive_nudge` should call `commit_block` on receiving a commitQC.
4. `on_receive_nudge` should not update `locked_view`, unless it is a commitQC, in which case update `locked_view = commitQC.view`.
## Types module (updates)
The changes in the module concern adding a new `decideQC`, they mostly apply to the `QuorumCertificate` struct and its methods, as well as the `VoteCollector`.
### `ValidatorSetUpdatesInfo`
Define a new `ValidatorSetUpdatesInfo` enum, which can be used to map blocks to their associated validator set updates (if any), containing information on whether the updates have been applied or not. Introducing this enum is part of the solution to [issue 28](https://github.com/parallelchain-io/hotstuff_rs/issues/28).
TODO: Find a more better name for this enum.
```rust
enum ValidatorSetUpdatesInfo {
NoUpdates,
Pending(ValidatorSetUpdates),
Committed(ValidatorSetUpdates)
}
```
Then the BlockTree should store a mapping from a block (hash) to its ValidatorSetUpdatesInfo, where:
- a non-validator-set-updating block maps to `NoUpdates`,
- a uncommitted validator-set-updating block maps to `Pending(vs_updates)`,
- a committed validator-set-updating block maps to `Committed(vs_updates)`.
We also define a `BlockTree` method called `contains_validator_set_updates` which takes a block as an argument and returns true if and only if the block maps to `Pending(vs_updates)` or `Committed(vs_updates)`.
### Extra "decide" phase:
```rust
pub enum Phase {
// ↓↓↓ For pipelined flow ↓↓↓ //
Generic,
// ↓↓↓ For phased flow ↓↓↓ //
Prepare,
// The inner view number is the view number of the *prepare* qc contained in the nudge which triggered the
// vote containing this phase.
Precommit(ViewNumber),
// The inner view number is the view number of the *precommit* qc contained in the nudge which triggered the
// vote containing this phase.
Commit(ViewNumber),
// The inner view number is the view number of the *commit* qc contained in the nudge which triggered the
// vote containing this phase.
Decide(ViewNumber),
}
```
We also define the `Ord` trait for `Phase`.
### New methods of `QuorumCertificate`
The idea is that a decideQC contains a vector of signatures that can be split into two vectors of signatures, from the new and resigning validator sets respectively. Likewise, two QCs from `Decide` phase can be merged into a `decideQC`.
New methods of the `QuorumCertificate`:
```rust
fn merge(self, qc: QuorumCertificate) -> QuorumCertificate {
}
```
```rust
fn split(self, index: u64) -> (QuorumCertificate, QuorumCertificate) {
}
```
# View and Block Sync
## `Algorithm` module
### Modified flow of a `view`
The following changes concern the body of the `start_algorithm` loop:
1. Before setting `cur_view`, we check which view (`>= cur_view`) is the minimal view that has not timed out yet, and we set cur_view to that view. We also check if we have just entered a new epoch, and if so reset the timers.
- if we entered the first view of a new epoch, we allocate `Pacemaker.view_length` to each view starting from the first view,
- if we entered another view of the epoch, we allocate `Pacemaker.view_length` to each view starting from the view we have entered (TODO: think of a better mechanism).
2. After setting `cur_view`, we check if we have just entered an epoch view, and if so we sync with a random peer.
3. The return type of `execute_view` will be `Result<(), EpochEnd>`
- `ShouldSync` error is not needed anymore, since block sync should be called from `execute_view`, thus counting towards view execution time,
- New `EpochEnd` error which signals that the epoch view has timed out, and the replica should enter `epoch_sync`.
4. If the `execute_view` returns `Err(EpochEnd)`, then call `epoch_sync`, as part of which `view_deadline` is called and the timer for the view is reset.
### New `epoch_sync` method
When called, `epoch_sync` will:
1. Set `ready_to_advance = false`,
2. Reset the timer for the current epoch view to `now()` + $\Delta_{view}$ (via `view_deadline` call), and
3. Broadcast a `NewEpoch` message, and
4. Enter a loop, bounded by the timeout of the current epoch view, where:
- `NewView`, `NewEpoch`, `AdvanceView`, `AdvanceEpoch` messages are processed, including collecting a TC,
- On seeing a QC or TC for `>= cur_view` (received via `ShouldAdvanceView` or `ShouldAdvanceEpoch` error) we process the QC/TC as usual - if safe and correct, set `highest_qc`/`highest_tc`, `ready_to_advance = true` and exit the loop, if for unknown block then sync, else ignore.
5. If `ready_to_advance = false`, then go back to step 2.
The purpose of resetting the timer and periodically sending `NewEpoch` until a TC is received, is to nudge any lagging replicas in case there is no quorum in the epoch view, so that eventually a quorum is formed.
### Conditions on advancing to a new view
For non-epoch v:
1. On getting `Err(ShouldAdvanceView{qc, sender})` from `pm_stub.recv`, with a correct and safe qc.
For an epoch view v:
1. On seeing `Err(ShouldAdvanceEpoch{c, sender})` from `pm_stub.recv`.
### `on_receive_advance_view`
This method shall be called after `Err(ShouldAdvanceView{qc, sender})` is seen. In reality, this doesn't have to be abstracted into a new method.
When `on_receive_advance_view` is called:
1. Check if the `qc` is `is_correct` and `safe_qc`, also if it is from the leader of `qc.view` and if it is for a non-epoch view, if yes then terminate this view and advance to a new view.
2. If the `qc` is not `safe_qc`, but is `is_correct`, and satisfies the two other conditions above, then sync with the `sender`, and afterwards re-evaluate the `qc`. **Note:** This can happen only once, no recursion!
3. Else, ignore the error.
### `on_receive_advance_epoch`
This method shall be called after `Err(ShouldAdvanceEpoch{qc, sender})` is seen. Again, in reality, this doesn't have to be abstracted into a new method.
When `on_receive_advance_epoch` is called on a `qc`:
1. Check if the qc is `is_correct` and `safe_qc`, also if it is for an epoch view, if yes then:
- broadcast a `AdvanceEpoch{qc}` message,
- terminate this view and advance to new view.
3. If the `qc` is not `safe_qc`, but is `is_correct`, and is from the corrrect sender, then sync with the `sender`, and re-evaluate the qc.
4. Else, ignore the error.
When `on_receive_advance_epoch` is called on a `tc`:
1. Check if the `tc` is `is_correct` and if it is for an epoch view, if yes then:
- broadcast a `AdvanceEpoch{tc}` message,
- terminate this view and advance to a new view.
2. Else, ignore the error.
### `on_receive_new_epoch`
1. Verify the signature and collect with the `NewEpochCollector`,
2. If a TC collected, then broadcast it.
### New `TimeoutCertificate` type
```rust
#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
pub struct TimeoutCertificate {
pub chain_id: ChainID,
pub view: ViewNumber,
pub signatures: SignatureSet,
}
```
Just like for `QuorumCertificate`, there will be an `is_correct` method for `TimeoutCertificate` to check if the certificate is cryptographically correct i.e, correctly signed by:
- validators from the `committed_validator_set`
- or validator from the `resigning_validator_set` (if `resigning_validator_set.is_some()`)
with total power greater or equal to the quorum threshold of $2f+1$.
### New `highest_tc` field for the `BlockTree`
```rust
mod paths {
\\ All other fields...
pub(super) const HIGHEST_TC: [u8; 1] = [12];
}
```
### New `ProgressMessage` instances
```rust
enum ProgressMessage {
Proposal(Proposal),
Nudge(Nudge),
Vote(Vote),
NewView(NewView),
NewEpoch(NewEpoch), // new: signal the desire to advance to a new epoch
AdvanceView(QuorumCertificate), // new: inform others about non-epoch-view QC
AdvanceEpoch(Certificate), // new: inform others about the epoch-view TC or QC
}
```
where
```rust
struct NewEpoch {
chain_id: ChainID
view: ViewNumber
signature: Signature
highest_c: Certificate
}
```
```rust
enum Certificate {
QuorumCertificate{QuorumCertificate},
TimeoutCertificate{TimeoutCertificate},
}
```
and `AdvanceEpoch` simply wraps around a `Certificate` type, but has the same APIs as other `ProgressMessage`s:
* The `view()` of a `AdvanceEpoch(tc)` message is the view of the `tc` or `qc` respectively,
* The `chain_id()` of a `AdvanceEpoch(tc)` message is the chain id of the `tc` or `qc` respectively.
Alternatively, define:
```rust
struct AdvanceEpoch {
chain_id: ChainID
view: ViewNumber
certificate: Certificate
}
```
and
```rust
struct AdvanceView {
chain_id: ChainID
view: ViewNumber
qc: QuorumCertificate
}
```
### `ProgressMessageBuffer` updates
```rust
enum ProgressMessageReceiveError {
Timeout,
ReceivedQCFromTheFuture, // TODO: delete
ShouldAdvanceView{qc: QuorumCertificate, sender: VerifyingKey}, // new
ShouldAdvanceEpoch{c: Certificate, sender: VerifyingKey}, // new
}
```
The high-level idea is that:
- `ShouldAdvanceView` is thrown when the message type suggests that the quorum has advanced from or is ready to advance from a non-epoch view, even if it is a non-epoch view of another epoch (and hence requires advancing an epoch) and the replica can simply advance to the view where the quorum is.
- `ShouldAdvanceEpoch` is thrown when the message type suggests that the quorum is ready to advance or in the process of advancing to a new epoch, and there is evidence that replicas can advance, and in addition to advancing to a new epoch the replica should re-broadcast this evidence to everyone.
As for the `recv` method:
1. On seeing a `AdvanceView(qc)` message (picked up from the poller or cache) with `qc.view >= cur_view` and `qc.view` not epoch view **or** `NewEpoch` or `NewView` message with a certificate for (epoch or non-epoch) `view >= cur_view`:
- the message is cached,
- `Err(ShouldAdvanceView{c, sender})` is thrown,
2. On seeing a `AdvanceEpoch(c)` message (picked up from the poller or cache) with a QC or TC for `view >= cur_view`:
- the message is cached,
- `Err(ShouldAdvanceEpoch{c, sender})` is thrown.
The idea is that TC or QC for current or future view are immediately returned in the corresponding error instances. Upon getting one of these errors the algorithm thread will check if the QC or TC is `correct` (and if it is `safe_qc` for a QC), and if so it will update its `highest_tc` or `highest_qc` field and return in `execute_view`. In case of an epoch view QC or TC, the algorithm thread will also re-broadcast it.
### Keeping track of whether a replica has voted
It is essential for the safety of the protocol that a replica votes only once per view.
This can be put into question as we introduce the validator set speculation period, but the validator set speculation period is designed in such a way that the invariant that a replica votes at most once per view is preserved.
Hence, we store an `i_have_voted` variable, initiated as false on entering each view, and once it becomes true a replica can't vote anymore.
**Exception:** we do not set the variable to equal to `true` after voting "decide", and we do not check if the variable is `false` before voting "decide". This is to let the replica vote again in the exceptional scenario when the leader of `vs` broadcasts a `Nudge(commitQC)`, not knowing about the newly collected `decideQC`, before the leader of `vs'` broadcasts a proposal supported by a `decideQC`. This is safe because the "decide" vote is merely a finalizing vote for a validator-set-updating block that has already been committed.
### Modular `Pacemaker` implementation
New methods for the trait:
```rust
pub trait Pacemaker: Send {
fn view_timeout(
&mut self,
cur_view: ViewNumber,
highest_qc_view_number: ViewNumber, // TODO: delete, no longer needed.
) -> Duration;
fn view_leader(&mut self, cur_view: ViewNumber, validator_set: &ValidatorSet)
-> VerifyingKey;
}
```
`DefaultPacemaker` implements the trait by storing, reading from, and writing to a `timeout` vector:
```rust
pub struct DefaultPacemaker {
view_time: Duration,
epoch_length: u64,
cur_epoch: u64,
timeout: BTreeMap<ViewNumber, Instant>,
}
impl DefaultPacemaker {
/// # Safety
/// `view_timeout` must not be larger than [u32::MAX] seconds for reasons explained in [Pacemaker].
pub fn new(view_timeout: Duration) -> DefaultPacemaker {
Self {
view_timeout,
0,
// timeout = [..., now()]
}
}
// new provided method.
fn reset_epoch_view_timeout(
&mut self,
epoch_number: EpochNumber
) -> ()
// new provided method.
// sets the timers for a new epoch.
fn set_epoch_timers(
&mut self,
epoch_number: EpochNumber,
start_view: ViewNumber,
) -> ()
}
impl Pacemaker for DefaultPacemaker {
// ...
}
```
To keep the `Pacemaker` API simple and consistent with the Pacemaker module API specification from the HotStuff paper, the `DefaultPacemaker` will implement `view_timeout` such that if needed the timers are reset on calling `view_timeout`.
For the implementation of the `view_leader` method, check [this design document](https://hackmd.io/FS7UmWUGT-qDdrxetGVB2w).
### `NewEpochCollector`
Now stores signatures, so that a TC can be returned from the `collect` method.
```rust
struct NewEpochCollector {
chain_id: ChainID,
view: ViewNumber,
validator_set: ValidatorSet,
validator_set_total_power: TotalPower,
signature_set: SignatureSet,
}
```