hotstuff_rs/
app.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
/*
    Copyright © 2023, ParallelChain Lab
    Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
*/

//! Pluggable, replicable applications.
//!
//! # Applications
//!
//! The basic interaction between HotStuff-rs and the library user is this:
//! 1. Library user specifies a state machine.
//! 2. Library user provides the state machine to HotStuff-rs
//! 3. HotStuff-rs replicates the state machine.
//!
//! We refer to the state machine in this basic interaction as the **"application"**. In short, an
//! application is a state machine that mutates
//! [two states](#two-app-mutable-states-app-state-and-validator-set), namely the *App State* and the
//! *Validator Set*, in response to receiving a payload called a [`Block`]. In other words, an
//! application can be thought of as a function (a "state transition function") with the following type
//! signature: ```(AppState, ValidatorSet, Block) -> (AppState, ValidatorSet)```.
//!
//! To specify an application, the library user must implement the [`App`] trait. This entails
//! implementing a state transition function and exposing this function to HotStuff-rs through three
//! different [required methods](App#required-methods), namely `produce_block`, `validate_block`, and
//! `validate_block_for_sync`.
//!
//! Each of the above three methods correspond to a distinct context in which HotStuff-rs will call the
//! application's state transition function. For example, HotStuff-rs will call `produce_block` when the
//! local replica is a leader and needs to produce a new block to propose it. The context in which each
//! function is called is documented in their respective Rustdocs.
//!
//! # Two app-mutable states: App State and Validator Set
//!
//! The two states that an application can mutate in response to a block are the App State, and the
//! Validator Set. Both states are stored in the block tree, which itself is stored in the pluggable
//! [`KVStore`](crate::block_tree::pluggables::KVStore) provided to HotStuff-rs by the library user.
//!
//! The **App State** is a set of key-value mappings, in which the keys and values are be arbitrary byte
//! strings. The library user is completely free to decide the structure and contents of this mapping
//! in whichever way supports their use-case. HotStuff-rs itself does not assume anything about the app
//! state, nor does it insert anything into the app state when a replica is
//! [`initialize`](crate::replica::Replica::initialize)d besides what the library user configures as the
//! `initial_app_state`.
//!
//! For more information about the **Validator Set**, read the rustdocs in the
//! [`validator_set`](crate::types::validator_set) module.
//!
//! `App` code tells HotStuff-rs to make changes to the app state and the validator set by listing the
//! changes that should be made in an [`AppStateUpdates`] or [`ValidatorSetUpdates`] (respectively) and
//! returning these changes from calls to its three methods.

use crate::{
    block_tree::{accessors::app::AppBlockTreeView, pluggables::KVStore},
    types::{
        block::Block,
        data_types::{CryptoHash, Data, ViewNumber},
        update_sets::{AppStateUpdates, ValidatorSetUpdates},
    },
};

/// Trait implemented by applications that are to be replicated by HotStuff-rs.
///
/// # Determinism requirements
///
/// In order for the state machine to be correctly replicated across all replicas, types that implement
/// `App` must ensure that their implementations of `produce_block`, `validate_block`, and
/// `validate_block_for_sync` are deterministic. That is, for any particular request (e.g.,
/// [`ProduceBlockRequest`]), a function must always produce the same response
/// ([`ProduceBlockResponse`]), regardless of whether the machine executing the call has an Intel CPU
/// or an AMD CPU, whether a pseudorandom number generator spits out 0 or 1, or whether it is currently
/// raining outside, etc.
///
/// The key to making sure that these methods are deterministic is ensuring that their respective
/// method bodies depend on and **only on** the information that is available through the public methods
/// of their request type. In particular, this means not reading the block tree through a
/// `BlockTreeSnapshot`, but reading it only through [`AppBlockTreeView`].

/// # Timing requirements
///
/// HotStuff-rs calls `produce_block` when a replica has to produce a block, and calls `validate_block`
/// when a replica has to validate a block.
///
/// These calls are **blocking** (synchronous) calls, so a replica will not continue making progress
/// until they complete. Because of this, library users should implement `produce_block` and
/// `validate_block` satisfy a specific timing constraint so that replicas do not spend too long in
/// these functions and cause views to timeout and progress to stall. This timing constraint is
/// exactly:
///
/// `4 * EWNL + produce_block_duration + validate_block_duration < max_view_time`,
///
/// where:
/// - `EWNL`: "Expected Worst-case Network Latency". That is, the maximum duration of time needed to send
///   a message from a validator to another validator under "normal" network conditions.
/// - `produce_block_duration`: how long `produce_block` takes to execute.
/// - `validate_block_duration`: how long `validate_block` takes to execute.
/// - `max_view_time`: the provided
///   [`Configuration::max_view_time`](crate::replica::Configuration::max_view_time).
///
/// ## Rationale behind timing requirements
///
/// Let's say a view begins at time T for the proposer of the view, and assume that:
/// - Messages take `EWNL` to deliver between validators,
/// - The time taken to process messages is negligible besides `produce_block_duration` and
///   `validate_block_duration`, and
/// - Every step begins after the previous step has ended (this is a simplifying assumption. In reality,
///   for example, the Proposer does not have to wait for replicas to receive `AdvanceView` in order to
///   start producing a block, and therefore, step 3 should really happen at
///   `T+diff(EWNL, produce_block_duration)` instead of at `T+EWNL+produce_block_duration` as below).
///
/// Then the view will proceed as follows:
///
/// |Step No.|Time after T (cumulative)|Events|
/// |---|---|---|
/// |1|`+0`|<ul><li>Proposer enters view.</li><li>Proposer broadcasts `AdvanceView`.</li><li>Proposer broadcasts `Proposal`</li></ul>|
/// |2|`+EWNL`|<ul><li>Replicas receive `AdvanceView`.</li><li>Replicas enter view.</li></ul>|
/// |3|`+produce_block_duration`|<ul><li>Proposer enters view.</li></ul>|
/// |4|`+EWNL`|<ul><li>Replicas receive `Proposal`.</li></ul>|
/// |5|`+validate_block_duration`|<ul><li>Replicas send `PhaseVote`s.</li></ul>|
/// |6|`+EWNL`|<ul><li>Next Leader collects `PhaseCertificate`.</li><li>Next Leader leaves view.</li><li>Next Leader broadcasts `AdvanceView`.</li><li>Next Leader broadcasts `Proposal`</li></ul>|
/// |7|`+EWNL`|<ul><li>Replicas receive `AdvanceView`.</li><li>Replicas leave view.</li></ul>|
///
/// In the view above (and indeed in any view), there are two possible cases about the identities of
/// Proposer and Next Leader:
/// 1. If Proposer == Next Leader, then:
///     1. The Proposer/Next Leader enters the view at step no. 1 and leaves at step no. 6, spending `3EWNL +
///       produce_block_duration + validate_block_duration` in the view.
///     2. Other replicas enter the view at step no. 2 and leave at step no. 7, spending `3EWNL +
///       produce_block_duration + validate_block_duration` in the view.
/// 2. If Proposer != Next Leader, then:
///     1. Proposer enters the view at step no.1 and leaves at step no. 7, spending `4EWNL +
///       produce_block_duration + validate_block_duration` in the view.
///     2. Next Leader enters the view at step no. 2 and leaves at step no. 6, spending `2EWNL +
///       produce_block_duration + validate_block_duration` in the view.
///     3. Other replicas enter the view at step no. 2 and leave at step no. 7, spending `3EWNL +
///       produce_block_duration + validate_block_duration` in the view.
///
/// This shows that the most time a replica will spend in a view under normal network conditions is
/// `4EWNL + produce_block_duration + validate_block_duration` (case 2.1), which is exactly the
/// left-hand-side of the timing constraint inequality.
pub trait App<K: KVStore>: Send {
    /// Called by HotStuff-rs when the replica becomes a leader and has to produce a new `Block` to be
    /// inserted into the block tree and proposed to other validators.
    fn produce_block(&mut self, request: ProduceBlockRequest<K>) -> ProduceBlockResponse;

    /// Called by HotStuff-rs when the replica receives a `Proposal` and has to validate the `Block` inside
    /// it to decide whether or not it should insert it into the block tree and vote for it.
    fn validate_block(&mut self, request: ValidateBlockRequest<K>) -> ValidateBlockResponse;

    /// Called when the replica is syncing and receives a `BlockSyncResponse` and has to validate the
    /// `Block` inside it to decide whether or not it should insert it into the block tree and vote for it.
    ///
    /// # Purpose
    ///
    /// There are several reasons why implementations of `App` may want calls to `validate_block` to take
    /// *at least* a certain amount of time.
    ///
    /// For example, implementors may want to limit the rate at which views proceed (e.g., to prevent the
    /// block tree from growing too quickly and exhausting disk space), and may choose to do so by making
    /// their implementations of `produce_block` and `validate_block` cause a thread sleep until a minimum
    /// amount of time is reached.
    ///
    /// Such a thread sleep solution works to slow down consensus decisions by, among other things, causing
    /// replicas to block for the amount of time when receiving a block through a `Proposal`, delaying
    /// the sending of a `PhaseVote` to the next leader.
    ///
    /// However, such a solution will not only slow down consensus decisions, it will *also* slow down
    /// Block Sync. In general, implementors will want `validate_block` to take at least a minimum amount of
    /// time when it is called within a view, but complete as quickly as possible when called during block
    /// sync.
    ///
    /// This is where `validate_block_for_sync` comes in. With `validate_block_for_sync`, implementers can
    /// rely on the fact that `validate_block` will *only* be called within a view, adding a thread sleep
    /// or other mechanism in it will not slow down sync as long as the mechanism is not also added to
    /// `validate_block_for_sync`.
    fn validate_block_for_sync(
        &mut self,
        request: ValidateBlockRequest<K>,
    ) -> ValidateBlockResponse;
}

/// Request for an `App` to produce a new block extending a specific `parent_block`.
pub struct ProduceBlockRequest<'a, K: KVStore> {
    cur_view: ViewNumber,
    parent_block: Option<CryptoHash>,
    block_tree_view: AppBlockTreeView<'a, K>,
}

impl<'a, K: KVStore> ProduceBlockRequest<'a, K> {
    /// Create a new `ProduceBlockRequest`.
    pub(crate) fn new(
        cur_view: ViewNumber,
        parent_block: Option<CryptoHash>,
        block_tree_view: AppBlockTreeView<'a, K>,
    ) -> Self {
        Self {
            cur_view,
            parent_block,
            block_tree_view,
        }
    }

    /// Get the current view of the replica.
    pub fn cur_view(&self) -> ViewNumber {
        self.cur_view
    }

    /// Get the parent block of the block that this `produce_block` call should extend.
    pub fn parent_block(&self) -> Option<CryptoHash> {
        self.parent_block
    }

    /// Get a current view of the block tree that this `produce_block` call can safely read from without
    /// risking [non-determinism](Self#determinism-requirements).
    pub fn block_tree(&self) -> &AppBlockTreeView<'a, K> {
        &self.block_tree_view
    }
}

/// Response from an `App` to a [`ProduceBlockRequest`].
pub struct ProduceBlockResponse {
    /// The `data_hash` field of the produced block.
    pub data_hash: CryptoHash,

    /// The `data` field of the produced block.
    pub data: Data,

    /// The `AppStateUpdates` that the produced block will cause when it is committed.
    pub app_state_updates: Option<AppStateUpdates>,

    /// The `ValidatorSetUpdates` that the produced block will cause when it is committed.
    pub validator_set_updates: Option<ValidatorSetUpdates>,
}

/// Request for the app to validate a proposed block. Contains information about the proposed
/// [block](crate::types::block::Block), and the relevant
/// [state of the Block Tree](crate::block_tree::accessors::app::AppBlockTreeView).
pub struct ValidateBlockRequest<'a, 'b, K: KVStore> {
    proposed_block: &'a Block,
    block_tree_view: AppBlockTreeView<'b, K>,
}

impl<'a, 'b, K: KVStore> ValidateBlockRequest<'a, 'b, K> {
    /// Create a new `ValidateBlockRequest`.
    pub(crate) fn new(proposed_block: &'a Block, block_tree_view: AppBlockTreeView<'b, K>) -> Self {
        Self {
            proposed_block,
            block_tree_view,
        }
    }

    /// Get the block that this `validate_block` call should validate.
    pub fn proposed_block(&self) -> &Block {
        self.proposed_block
    }

    /// Get a current view of the block tree that this `validate_block` call can safely read from without
    /// risking [non-determinism](Self#determinism-requirements).
    pub fn block_tree(&self) -> &AppBlockTreeView<K> {
        &self.block_tree_view
    }
}

/// Response from an `App` upon receiving a [`ValidateBlockRequest`].
pub enum ValidateBlockResponse {
    /// Indicates that [`ValidateBlockRequest::proposed_block`] is valid according to the `App`'s semantics.
    Valid {
        /// The `AppStateUpdates` that the proposed block will cause when it is committed.
        app_state_updates: Option<AppStateUpdates>,

        /// The `ValidatorSetUpdates` that the proposed block will cause when it is committed.
        validator_set_updates: Option<ValidatorSetUpdates>,
    },

    /// Indicates either that [`ValidateBlockRequest::proposed_block`] is invalid according to the
    /// `App`'s semantics, or that the execution of `validate_block` for the proposed block will exceed the
    /// [timing requirements](App#timing-requirements).
    Invalid,
}