diff --git a/tools/summonerd/src/queue.rs b/tools/summonerd/src/queue.rs new file mode 100644 index 0000000000..c60385693d --- /dev/null +++ b/tools/summonerd/src/queue.rs @@ -0,0 +1,158 @@ +use anyhow::Result; +use std::{collections::HashSet, sync::Arc}; + +use penumbra_keys::Address; +use penumbra_num::Amount; +use tokio::sync::RwLock; + +use crate::participant::Participant; + +/// The inner struct for "normal" manipulation outside of the concurrent data structure +struct Inner { + // Invariant: this is always sorted by the amount. + // Invariant: no two participants have the same address + sorted: Vec<(Participant, Amount)>, + // Used to keep track of all the addresses in the sorted vec. + addresses: HashSet
, +} + +impl Inner { + fn new() -> Self { + Inner { + sorted: Vec::new(), + addresses: HashSet::new(), + } + } + + /// Get the number of participants in the queue + fn len(&self) -> usize { + self.sorted.len() + } + + /// Insert a participant and their bid into the queue. + /// + /// If the participant is already in the queue (as determined by having the same address), + /// then this may push them higher in the queue if the bid is higher. + /// + /// Their bid being lower should never happen in practice. + /// + /// We also don't expect the case of duplicate participants to happen either, but we should + /// handle it. + fn push(&mut self, participant: Participant, bid: Amount) { + let address = participant.address(); + // If the participant is already present in the queue, remove them. + if self.addresses.contains(&address) { + // Note, we don't remove them from the address list, because we'll be inserting soon. + self.sorted.retain(|(p, _)| p.address() != address); + } + let position = match self.sorted.binary_search_by(|(_, a)| a.cmp(&bid)) { + // If we find a participant with the same bid, insert before them. + Ok(pos) => pos, + // If no participant with the bid is found, this is the correct position. + Err(pos) => pos, + }; + self.sorted.insert(position, (participant, bid)); + self.addresses.insert(address); + } + + /// Remove all participants that aren't live + fn prune(&mut self) { + self.sorted.retain(|(participant, _)| { + let live = participant.is_live(); + // Make sure to remove from the addresses list as well. + if !live { + self.addresses.remove(&participant.address()); + } + live + }) + } + + /// Remove the participant with the highest bid from this queue. + /// + /// This will return None if the queue is empty. + fn pop(&mut self) -> Option<(Participant, Amount)> { + let result = self.sorted.pop(); + if let Some((ref participant, _)) = result { + self.addresses.remove(&participant.address()); + } + result + } + + /// Return the highest bid in the queue, if not empty. + /// + /// This will return None if the queue is empty. + fn top_bid(&self) -> Option