Skip to main content

fedimint_hbbft/queueing_honey_badger/
mod.rs

1//! # Queueing Honey Badger
2//!
3//! This works exactly like Dynamic Honey Badger, but it has a transaction queue built in. Whenever
4//! an epoch is output, it will automatically select a list of pending transactions and propose it
5//! for the next one. The user can continuously add more pending transactions to the queue.
6//!
7//! If there are no pending transactions, no validators in the process of being added or
8//! removed and not enough other nodes have proposed yet, no automatic proposal will be made: The
9//! network then waits until at least _f + 1_ have any content for the next epoch.
10//!
11//! ## How it works
12//!
13//! Queueing Honey Badger runs a Dynamic Honey Badger internally, and automatically inputs a list
14//! of pending transactions as its contribution at the beginning of each epoch. These are selected
15//! by making a random choice of _B / N_ out of the first _B_ entries in the queue, where _B_ is the
16//! configurable `batch_size` parameter, and _N_ is the current number of validators.
17//!
18//! After each output, the transactions that made it into the new batch are removed from the queue.
19//!
20//! The random choice of transactions is made to reduce redundancy even if all validators have
21//! roughly the same entries in their queues. By selecting a random fraction of the first _B_
22//! entries, any two nodes will likely make almost disjoint contributions instead of proposing
23//! the same transaction multiple times.
24
25use std::marker::PhantomData;
26use std::{cmp, iter};
27
28use derivative::Derivative;
29use rand::distributions::{Distribution, Standard};
30use rand::Rng;
31use serde::{de::DeserializeOwned, Serialize};
32use thiserror::Error;
33
34use crate::crypto::{PublicKey, SecretKey};
35use crate::dynamic_honey_badger::{
36    self, Batch as DhbBatch, DynamicHoneyBadger, FaultKind, JoinPlan, Message, Step as DhbStep,
37};
38use crate::transaction_queue::TransactionQueue;
39use crate::{ConsensusProtocol, Contribution, NetworkInfo, NodeIdT};
40
41pub use crate::dynamic_honey_badger::{Change, ChangeState, Input};
42
43/// Queueing honey badger error variants.
44#[derive(Debug, Error)]
45pub enum Error {
46    /// Failed to handle input.
47    #[error("Input error: {0}")]
48    Input(dynamic_honey_badger::Error),
49    /// Failed to handle a message.
50    #[error("Handle message error: {0}")]
51    HandleMessage(dynamic_honey_badger::Error),
52    /// Failed to propose a contribution.
53    #[error("Propose error: {0}")]
54    Propose(dynamic_honey_badger::Error),
55    /// Failed to create a Dynamic Honey Badger instance according to a join plan.
56    #[error("New joining error: {0}")]
57    NewJoining(dynamic_honey_badger::Error),
58}
59
60/// The result of `QueueingHoneyBadger` handling an input or message.
61pub type Result<T> = ::std::result::Result<T, Error>;
62
63/// A Queueing Honey Badger builder, to configure the parameters and create new instances of
64/// `QueueingHoneyBadger`.
65pub struct QueueingHoneyBadgerBuilder<T, N, Q>
66where
67    T: Contribution + Serialize + DeserializeOwned + Clone,
68    N: NodeIdT + Serialize + DeserializeOwned,
69{
70    /// Shared network data.
71    dyn_hb: DynamicHoneyBadger<Vec<T>, N>,
72    /// The target number of transactions to be included in each batch.
73    batch_size: usize,
74    /// The queue of pending transactions that haven't been output in a batch yet.
75    queue: Q,
76    /// The initial step of the managed `DynamicHoneyBadger` instance.
77    step: Option<DhbStep<Vec<T>, N>>,
78    _phantom: PhantomData<T>,
79}
80
81type QueueingHoneyBadgerWithStep<T, N, Q> = (QueueingHoneyBadger<T, N, Q>, Step<T, N>);
82
83impl<T, N, Q> QueueingHoneyBadgerBuilder<T, N, Q>
84where
85    T: Contribution + Serialize + DeserializeOwned + Clone,
86    N: NodeIdT + Serialize + DeserializeOwned,
87    Q: TransactionQueue<T>,
88    Standard: Distribution<N>,
89{
90    /// Returns a new `QueueingHoneyBadgerBuilder` wrapping the given instance of
91    /// `DynamicHoneyBadger`.
92    pub fn new(dyn_hb: DynamicHoneyBadger<Vec<T>, N>) -> Self {
93        // TODO: Use the defaults from `HoneyBadgerBuilder`.
94        QueueingHoneyBadgerBuilder {
95            dyn_hb,
96            batch_size: 100,
97            queue: Default::default(),
98            step: None,
99            _phantom: PhantomData,
100        }
101    }
102
103    /// Sets the initial step of the `DynamicHoneyBadger` instance.
104    pub fn step(mut self, step: DhbStep<Vec<T>, N>) -> Self {
105        self.step = Some(step);
106        self
107    }
108
109    /// Sets the target number of transactions per batch.
110    pub fn batch_size(mut self, batch_size: usize) -> Self {
111        self.batch_size = batch_size;
112        self
113    }
114
115    /// Sets the transaction queue object.
116    pub fn queue(mut self, queue: Q) -> Self {
117        self.queue = queue;
118        self
119    }
120
121    /// Creates a new Queueing Honey Badger instance with an empty buffer.
122    pub fn build<R: Rng>(self, rng: &mut R) -> Result<QueueingHoneyBadgerWithStep<T, N, Q>> {
123        self.build_with_transactions(None, rng)
124    }
125
126    /// Returns a new Queueing Honey Badger instance that starts with the given transactions in its
127    /// buffer.
128    pub fn build_with_transactions<TI, R>(
129        mut self,
130        txs: TI,
131        rng: &mut R,
132    ) -> Result<QueueingHoneyBadgerWithStep<T, N, Q>>
133    where
134        TI: IntoIterator<Item = T>,
135        R: Rng,
136    {
137        self.queue.extend(txs);
138        let mut qhb = QueueingHoneyBadger {
139            dyn_hb: self.dyn_hb,
140            batch_size: self.batch_size,
141            queue: self.queue,
142        };
143        let mut step = qhb.propose(rng)?;
144        if let Some(dhb_step) = self.step {
145            step.extend(dhb_step);
146        }
147        Ok((qhb, step))
148    }
149}
150
151/// A Honey Badger instance that can handle adding and removing nodes and manages a transaction
152/// queue.
153#[derive(Derivative)]
154#[derivative(Debug)]
155pub struct QueueingHoneyBadger<T, N: Ord, Q> {
156    /// The target number of transactions to be included in each batch.
157    batch_size: usize,
158    /// The internal managed `DynamicHoneyBadger` instance.
159    dyn_hb: DynamicHoneyBadger<Vec<T>, N>,
160    /// The queue of pending transactions that haven't been output in a batch yet.
161    queue: Q,
162}
163
164/// A `QueueingHoneyBadger` step, possibly containing multiple outputs.
165pub type Step<T, N> = crate::Step<Message<N>, Batch<T, N>, N, FaultKind>;
166
167impl<T, N, Q> ConsensusProtocol for QueueingHoneyBadger<T, N, Q>
168where
169    T: Contribution + Serialize + DeserializeOwned + Clone,
170    N: NodeIdT + Serialize + DeserializeOwned,
171    Q: TransactionQueue<T>,
172    Standard: Distribution<N>,
173{
174    type NodeId = N;
175    type Input = Input<T, N>;
176    type Output = Batch<T, N>;
177    type Message = Message<N>;
178    type Error = Error;
179    type FaultKind = FaultKind;
180
181    fn handle_input<R: Rng>(&mut self, input: Self::Input, rng: &mut R) -> Result<Step<T, N>> {
182        // User transactions are forwarded to `HoneyBadger` right away. Internal messages are
183        // in addition signed and broadcast.
184
185        match input {
186            Input::User(tx) => self.push_transaction(tx, rng),
187            Input::Change(change) => self.vote_for(change, rng),
188        }
189    }
190
191    fn handle_message<R: Rng>(
192        &mut self,
193        sender_id: &N,
194        message: Self::Message,
195        rng: &mut R,
196    ) -> Result<Step<T, N>> {
197        self.handle_message(sender_id, message, rng)
198    }
199
200    fn terminated(&self) -> bool {
201        false
202    }
203
204    fn our_id(&self) -> &N {
205        self.dyn_hb.our_id()
206    }
207}
208
209impl<T, N, Q> QueueingHoneyBadger<T, N, Q>
210where
211    T: Contribution + Serialize + DeserializeOwned + Clone,
212    N: NodeIdT + Serialize + DeserializeOwned,
213    Q: TransactionQueue<T>,
214    Standard: Distribution<N>,
215{
216    /// Returns a new `QueueingHoneyBadgerBuilder` configured to use the node IDs and cryptographic
217    /// keys specified by `netinfo`.
218    pub fn builder(dyn_hb: DynamicHoneyBadger<Vec<T>, N>) -> QueueingHoneyBadgerBuilder<T, N, Q> {
219        QueueingHoneyBadgerBuilder::new(dyn_hb)
220    }
221
222    /// Creates a new `QueueingHoneyBadgerBuilder` for joining the network specified in the
223    /// `JoinPlan`.
224    ///
225    /// Returns a `QueueingHoneyBadgerBuilder` or an error if creation of the managed
226    /// `DynamicHoneyBadger` instance has failed.
227    pub fn builder_joining<R: Rng>(
228        our_id: N,
229        secret_key: SecretKey,
230        join_plan: JoinPlan<N>,
231        rng: &mut R,
232    ) -> Result<QueueingHoneyBadgerBuilder<T, N, Q>> {
233        let (dhb, step) = DynamicHoneyBadger::new_joining(our_id, secret_key, join_plan, rng)
234            .map_err(Error::NewJoining)?;
235        Ok(QueueingHoneyBadgerBuilder::new(dhb).step(step))
236    }
237
238    /// Adds a transaction to the queue.
239    ///
240    /// This can be called at any time to append to the transaction queue. The new transaction will
241    /// be proposed in some future epoch.
242    ///
243    /// If no proposal has yet been made for the current epoch, this may trigger one. In this case,
244    /// a nonempty step will returned, with the corresponding messages. (Or, if we are the only
245    /// validator, even with the completed batch as an output.)
246    pub fn push_transaction<R: Rng>(&mut self, tx: T, rng: &mut R) -> Result<Step<T, N>> {
247        self.queue.extend(iter::once(tx));
248        self.propose(rng)
249    }
250
251    /// Casts a vote to change the set of validators.
252    ///
253    /// This stores a pending vote for the change. It will be included in some future batch, and
254    /// once enough validators have been voted for the same change, it will take effect.
255    pub fn vote_for<R: Rng>(&mut self, change: Change<N>, rng: &mut R) -> Result<Step<T, N>> {
256        self.apply(|dyn_hb, _| dyn_hb.vote_for(change), rng)
257    }
258
259    /// Casts a vote to add a node as a validator.
260    ///
261    /// This stores a pending vote for the change. It will be included in some future batch, and
262    /// once enough validators have been voted for the same change, it will take effect.
263    pub fn vote_to_add<R: Rng>(
264        &mut self,
265        node_id: N,
266        pub_key: PublicKey,
267        rng: &mut R,
268    ) -> Result<Step<T, N>> {
269        self.apply(|dyn_hb, _| dyn_hb.vote_to_add(node_id, pub_key), rng)
270    }
271
272    /// Casts a vote to demote a validator to observer.
273    ///
274    /// This stores a pending vote for the change. It will be included in some future batch, and
275    /// once enough validators have been voted for the same change, it will take effect.
276    pub fn vote_to_remove<R: Rng>(&mut self, node_id: &N, rng: &mut R) -> Result<Step<T, N>> {
277        self.apply(|dyn_hb, _| dyn_hb.vote_to_remove(node_id), rng)
278    }
279
280    /// Handles a message received from `sender_id`.
281    ///
282    /// This must be called with every message we receive from another node.
283    pub fn handle_message<R: Rng>(
284        &mut self,
285        sender_id: &N,
286        message: Message<N>,
287        rng: &mut R,
288    ) -> Result<Step<T, N>> {
289        self.apply(
290            |dyn_hb, rng| dyn_hb.handle_message(sender_id, message, rng),
291            rng,
292        )
293    }
294
295    /// Returns a reference to the internal managed `DynamicHoneyBadger` instance.
296    pub fn dyn_hb(&self) -> &DynamicHoneyBadger<Vec<T>, N> {
297        &self.dyn_hb
298    }
299
300    /// Returns the information about the node IDs in the network, and the cryptographic keys.
301    pub fn netinfo(&self) -> &NetworkInfo<N> {
302        self.dyn_hb.netinfo()
303    }
304
305    /// Returns the current queue of the `QueueingHoneyBadger`.
306    pub fn queue(&self) -> &Q {
307        &self.queue
308    }
309
310    /// Applies a function `f` to the `DynamicHoneyBadger` instance and processes the step.
311    fn apply<R, F>(&mut self, f: F, rng: &mut R) -> Result<Step<T, N>>
312    where
313        F: FnOnce(
314            &mut DynamicHoneyBadger<Vec<T>, N>,
315            &mut R,
316        ) -> dynamic_honey_badger::Result<Step<T, N>>,
317        R: Rng,
318    {
319        let step = f(&mut self.dyn_hb, rng).map_err(Error::Input)?;
320        self.queue
321            .remove_multiple(step.output.iter().flat_map(Batch::iter));
322        Ok(step.join(self.propose(rng)?))
323    }
324
325    /// Returns the epoch of the next batch that will be output.
326    pub fn next_epoch(&self) -> u64 {
327        self.dyn_hb.next_epoch()
328    }
329
330    /// Returns `true` if we are ready to propose our contribution for the next epoch, i.e. if the
331    /// previous epoch has completed and we have either pending transactions or we are required to
332    /// make a proposal to avoid stalling the network.
333    fn can_propose(&self) -> bool {
334        if self.dyn_hb.has_input() {
335            return false; // Previous epoch is still in progress.
336        }
337        !self.queue.is_empty() || self.dyn_hb.should_propose()
338    }
339
340    /// Initiates the next epoch by proposing a batch from the queue.
341    fn propose<R: Rng>(&mut self, rng: &mut R) -> Result<Step<T, N>> {
342        let mut step = Step::default();
343        while self.can_propose() {
344            let amount = cmp::max(1, self.batch_size / self.dyn_hb.netinfo().num_nodes());
345            let proposal = self.queue.choose(rng, amount, self.batch_size);
346            let propose_step = self
347                .dyn_hb
348                .handle_input(Input::User(proposal), rng)
349                .map_err(Error::Propose)?;
350            self.queue
351                .remove_multiple(propose_step.output.iter().flat_map(Batch::iter));
352            step.extend(propose_step);
353        }
354        Ok(step)
355    }
356}
357
358/// A batch containing a list of transactions from at least two thirds of the validators.
359pub type Batch<T, N> = DhbBatch<Vec<T>, N>;