use super::{events::BlockApplyToken, reorder::*, state::*, *};
#[derive(Clone, Debug)]
pub(super) struct ApplyingBlock {
pub(super) token: BlockApplyToken,
pub(super) hash: block::Hash,
pub(super) previous_block_hash: block::Hash,
pub(super) body: BufferedBlockBody,
pub(super) bytes: u64,
pub(super) submitted: bool,
pub(super) source_peer: ZakuraPeerId,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(super) enum AcceptOutcome {
Buffered { covered: block::Height },
Redundant { release_bytes: u64 },
}
#[derive(Clone, Debug)]
pub(super) struct SubmitItem {
pub(super) height: block::Height,
pub(super) hash: block::Hash,
pub(super) token: BlockApplyToken,
pub(super) block: Arc<block::Block>,
}
#[derive(Copy, Clone, Debug)]
struct InFlightSubmission {
height: block::Height,
hash: block::Hash,
bytes: u64,
decoded_attributed_memory_bytes: u64,
detached: bool,
}
#[derive(Copy, Clone, Debug)]
pub(super) struct AdvanceOutcome {
#[cfg_attr(not(test), allow(dead_code))] pub(super) release_bytes: u64,
pub(super) changed: bool,
}
#[derive(Clone, Debug)]
pub(super) struct Sequencer {
reorder: ReorderBuffer,
applying: BTreeMap<block::Height, ApplyingBlock>,
submitted_applies: BTreeMap<block::Height, Vec<(block::Hash, usize)>>,
in_flight_submissions: BTreeMap<BlockApplyToken, InFlightSubmission>,
next_apply_token: BlockApplyToken,
applying_buffered_bytes: u64,
applying_decoded_attributed_memory_bytes: u64,
detached_submission_decoded_attributed_memory_bytes: u64,
attached_submission_count: usize,
in_flight_submission_count: usize,
in_flight_submission_bytes: u64,
body_download_floor: block::Height,
verified_block_tip: block::Height,
submitted_apply_limit: usize,
}
impl Sequencer {
pub(super) fn new(verified_block_tip: block::Height, submitted_apply_limit: usize) -> Self {
Self {
reorder: ReorderBuffer::new(),
applying: BTreeMap::new(),
submitted_applies: BTreeMap::new(),
in_flight_submissions: BTreeMap::new(),
next_apply_token: 1,
applying_buffered_bytes: 0,
applying_decoded_attributed_memory_bytes: 0,
detached_submission_decoded_attributed_memory_bytes: 0,
attached_submission_count: 0,
in_flight_submission_count: 0,
in_flight_submission_bytes: 0,
body_download_floor: verified_block_tip,
verified_block_tip,
submitted_apply_limit,
}
}
pub(super) fn floor(&self) -> block::Height {
self.body_download_floor
}
pub(super) fn verified_tip(&self) -> block::Height {
self.verified_block_tip
}
#[cfg(test)]
pub(super) fn reorder_contains(&self, height: block::Height) -> bool {
self.reorder.contains(height)
}
#[cfg(test)]
pub(super) fn applying_contains(&self, height: block::Height) -> bool {
self.applying.contains_key(&height)
}
#[cfg(test)]
pub(super) fn submitted_contains(&self, height: block::Height) -> bool {
self.submitted_applies.contains_key(&height)
}
pub(super) fn reorder_len(&self) -> usize {
self.reorder.len()
}
pub(super) fn applying_len(&self) -> usize {
self.applying.len()
}
pub(super) fn applying_buffered_bytes(&self) -> u64 {
self.applying_buffered_bytes
}
pub(super) fn applying_decoded_attributed_memory_bytes(&self) -> u64 {
self.applying_decoded_attributed_memory_bytes
.saturating_add(self.detached_submission_decoded_attributed_memory_bytes)
}
#[cfg(test)]
pub(super) fn applying_decoded_attributed_memory_bytes_scanned(&self) -> u64 {
let attached = self
.applying
.values()
.map(|applying| applying.body.decoded_attributed_memory_size_bytes())
.fold(0u64, u64::saturating_add);
let detached = self
.in_flight_submissions
.values()
.filter(|submission| submission.detached)
.map(|submission| submission.decoded_attributed_memory_bytes)
.fold(0u64, u64::saturating_add);
attached.saturating_add(detached)
}
#[cfg(test)]
pub(super) fn decoded_applying_count(&self) -> usize {
self.applying
.values()
.filter(|applying| applying.body.is_decoded())
.count()
}
#[cfg(test)]
pub(super) fn applying_buffered_bytes_scanned(&self) -> u64 {
self.applying
.values()
.map(|applying| applying.bytes)
.fold(0u64, u64::saturating_add)
}
pub(super) fn reorder_buffered_bytes(&self) -> u64 {
self.reorder.buffered_bytes()
}
pub(super) fn reorder_decoded_attributed_memory_bytes(&self) -> u64 {
self.reorder.decoded_attributed_memory_bytes()
}
#[cfg(test)]
pub(super) fn reorder_decoded_attributed_memory_bytes_scanned(&self) -> u64 {
self.reorder.decoded_attributed_memory_bytes_scanned()
}
pub(super) fn unsubmitted_applying_count(&self) -> usize {
self.applying
.len()
.saturating_sub(self.attached_submission_count)
}
pub(super) fn in_flight_submission_bytes(&self) -> u64 {
self.in_flight_submission_bytes
}
pub(super) fn in_flight_submission_count(&self) -> usize {
self.in_flight_submission_count
}
#[cfg(test)]
pub(super) fn in_flight_submission_count_scanned(&self) -> usize {
self.in_flight_submissions.len()
}
#[cfg(test)]
pub(super) fn in_flight_submission_bytes_scanned(&self) -> u64 {
self.in_flight_submissions
.values()
.map(|submission| submission.bytes)
.fold(0u64, u64::saturating_add)
}
#[cfg(test)]
pub(super) fn attached_submission_count_scanned(&self) -> usize {
self.applying
.values()
.filter(|applying| applying.submitted)
.count()
}
pub(super) fn has_submitted_apply(&self, height: block::Height, hash: block::Hash) -> bool {
self.submitted_applies
.get(&height)
.is_some_and(|entries| entries.iter().any(|(entry_hash, _)| *entry_hash == hash))
}
pub(super) fn has_buffered_at_or_above(&self, height: block::Height) -> bool {
self.reorder.contains_at_or_above(height)
|| self.applying.range(height..).next().is_some()
|| self.submitted_applies.range(height..).next().is_some()
}
pub(super) fn applying_previous_block_hash(
&self,
height: block::Height,
) -> Option<block::Hash> {
self.applying
.get(&height)
.map(|applying| applying.previous_block_hash)
}
pub(super) fn reorder_hash(&self, height: block::Height) -> Option<block::Hash> {
self.reorder.hash(height)
}
pub(super) fn applying_hash(&self, height: block::Height) -> Option<block::Hash> {
self.applying.get(&height).map(|applying| applying.hash)
}
pub(super) fn submitted_has_only_other_hashes(
&self,
height: block::Height,
hash: block::Hash,
) -> bool {
self.submitted_applies
.get(&height)
.is_some_and(|entries| entries.iter().all(|(entry_hash, _)| *entry_hash != hash))
}
#[cfg(test)]
pub(super) fn accept_body(
&mut self,
height: block::Height,
hash: block::Hash,
block: Arc<block::Block>,
bytes: u64,
source_peer: ZakuraPeerId,
) -> AcceptOutcome {
let previous_block_hash = block.header.previous_block_hash;
self.accept_buffered_body(
height,
hash,
previous_block_hash,
BufferedBlockBody::from_decoded_block(block, None),
bytes,
source_peer,
)
}
pub(super) fn accept_buffered_body(
&mut self,
height: block::Height,
hash: block::Hash,
previous_block_hash: block::Hash,
body: BufferedBlockBody,
bytes: u64,
source_peer: ZakuraPeerId,
) -> AcceptOutcome {
if height <= self.body_download_floor
|| self.reorder.contains(height)
|| self.applying.contains_key(&height)
|| self.has_submitted_apply(height, hash)
{
return AcceptOutcome::Redundant {
release_bytes: bytes,
};
}
let body = if next_height(self.body_download_floor) == Some(height) {
body
} else {
body.retain_for_backlog()
};
match self
.reorder
.insert_body(height, hash, previous_block_hash, body, bytes, source_peer)
{
ReorderInsertResult::Inserted => AcceptOutcome::Buffered { covered: height },
ReorderInsertResult::Duplicate => AcceptOutcome::Redundant {
release_bytes: bytes,
},
}
}
pub(super) fn drain_ready_into_applying(&mut self) -> Vec<block::Height> {
let released = self
.reorder
.drain_contiguous_prefix(self.body_download_floor);
let mut free_submit_slots = self
.submitted_apply_limit
.saturating_sub(self.applying.len());
let mut covered = Vec::with_capacity(released.len());
for drained in released {
let mut body = drained.body;
if free_submit_slots > 0 {
free_submit_slots -= 1;
} else {
body.retain_for_backlog_in_place();
}
self.body_download_floor = drained.height;
covered.push(drained.height);
let decoded_attributed_memory_size_bytes = body.decoded_attributed_memory_size_bytes();
self.applying.insert(
drained.height,
ApplyingBlock {
token: 0,
hash: drained.hash,
previous_block_hash: drained.previous_block_hash,
body,
bytes: drained.bytes,
submitted: false,
source_peer: drained.source_peer,
},
);
self.applying_buffered_bytes =
self.applying_buffered_bytes.saturating_add(drained.bytes);
self.applying_decoded_attributed_memory_bytes = self
.applying_decoded_attributed_memory_bytes
.saturating_add(decoded_attributed_memory_size_bytes);
}
covered
}
pub(super) fn submittable_heights(&self) -> Vec<block::Height> {
let available = self
.submitted_apply_limit
.saturating_sub(self.in_flight_submission_count());
if available == 0 {
return Vec::new();
}
self.applying
.iter()
.filter_map(|(height, applying)| (!applying.submitted).then_some(*height))
.take(available)
.collect()
}
pub(super) fn prepare_submit(&mut self, height: block::Height) -> Option<SubmitItem> {
if self
.applying
.get(&height)
.is_none_or(|applying| applying.submitted)
{
return None;
}
let token = self.next_apply_token();
let (
hash,
bytes,
block,
decoded_attributed_memory_size_bytes,
newly_decoded_attributed_memory_bytes,
) = {
let applying = self.applying.get_mut(&height)?;
let decoded_before = applying.body.decoded_attributed_memory_size_bytes();
let block = applying.body.decoded_block();
let decoded_after = applying.body.decoded_attributed_memory_size_bytes();
applying.token = token;
applying.submitted = true;
(
applying.hash,
applying.bytes,
block,
decoded_after,
decoded_after.saturating_sub(decoded_before),
)
};
self.applying_decoded_attributed_memory_bytes = self
.applying_decoded_attributed_memory_bytes
.saturating_add(newly_decoded_attributed_memory_bytes);
self.attached_submission_count = self.attached_submission_count.saturating_add(1);
self.in_flight_submissions.insert(
token,
InFlightSubmission {
height,
hash,
bytes,
decoded_attributed_memory_bytes: decoded_attributed_memory_size_bytes,
detached: false,
},
);
self.in_flight_submission_count = self.in_flight_submission_count.saturating_add(1);
self.in_flight_submission_bytes = self.in_flight_submission_bytes.saturating_add(bytes);
Some(SubmitItem {
height,
hash,
token,
block,
})
}
pub(super) fn unsubmit(&mut self, height: block::Height, token: BlockApplyToken) {
let unsubmitted = {
let Some(applying) = self.applying.get_mut(&height) else {
return;
};
if applying.token != token {
return;
}
let was_submitted = applying.submitted;
applying.token = 0;
applying.submitted = false;
let decoded_before = applying.body.decoded_attributed_memory_size_bytes();
applying.body.retain_for_backlog_in_place();
let decoded_after = applying.body.decoded_attributed_memory_size_bytes();
was_submitted.then_some((
applying.hash,
applying.bytes,
decoded_before.saturating_sub(decoded_after),
))
};
if let Some((hash, bytes, decoded_attributed_memory_size_bytes)) = unsubmitted {
self.attached_submission_count = self.attached_submission_count.saturating_sub(1);
self.applying_decoded_attributed_memory_bytes = self
.applying_decoded_attributed_memory_bytes
.saturating_sub(decoded_attributed_memory_size_bytes);
self.release_in_flight_submission(token, height, hash, bytes);
}
}
fn next_apply_token(&mut self) -> BlockApplyToken {
let token = self.next_apply_token;
self.next_apply_token = self.next_apply_token.checked_add(1).unwrap_or(1);
token
}
pub(super) fn record_submitted_apply(&mut self, height: block::Height, hash: block::Hash) {
let entries = self.submitted_applies.entry(height).or_default();
if let Some((_, count)) = entries
.iter_mut()
.find(|(entry_hash, _)| *entry_hash == hash)
{
*count = count.saturating_add(1);
} else {
entries.push((hash, 1));
}
}
fn decrement_submitted_apply(&mut self, height: block::Height, hash: block::Hash) {
let Some(entries) = self.submitted_applies.get_mut(&height) else {
return;
};
if let Some(index) = entries
.iter()
.position(|(entry_hash, _)| *entry_hash == hash)
{
let (_, count) = &mut entries[index];
*count = count.saturating_sub(1);
if *count == 0 {
entries.remove(index);
}
}
if entries.is_empty() {
self.submitted_applies.remove(&height);
}
}
pub(super) fn finish_submission(
&mut self,
token: BlockApplyToken,
height: block::Height,
hash: block::Hash,
) -> bool {
let Some(submission) = self.in_flight_submissions.get(&token) else {
return false;
};
if submission.height != height || submission.hash != hash {
return false;
}
let bytes = submission.bytes;
let decoded_attributed_memory_bytes = submission.decoded_attributed_memory_bytes;
let detached = submission.detached;
self.in_flight_submissions.remove(&token);
self.in_flight_submission_count = self.in_flight_submission_count.saturating_sub(1);
self.in_flight_submission_bytes = self.in_flight_submission_bytes.saturating_sub(bytes);
if detached {
self.detached_submission_decoded_attributed_memory_bytes = self
.detached_submission_decoded_attributed_memory_bytes
.saturating_sub(decoded_attributed_memory_bytes);
}
self.decrement_submitted_apply(height, hash);
true
}
pub(super) fn finish_attached_submission(
&mut self,
token: BlockApplyToken,
height: block::Height,
hash: block::Hash,
) -> bool {
let submission_matches = self
.in_flight_submissions
.get(&token)
.is_some_and(|submission| submission.height == height && submission.hash == hash);
if !submission_matches {
return false;
}
let Some(applying) = self.applying.get_mut(&height) else {
return false;
};
if applying.token != token || applying.hash != hash || !applying.submitted {
return false;
}
let decoded_before = applying.body.decoded_attributed_memory_size_bytes();
applying.body.retain_for_backlog_in_place();
let decoded_after = applying.body.decoded_attributed_memory_size_bytes();
self.applying_decoded_attributed_memory_bytes = self
.applying_decoded_attributed_memory_bytes
.saturating_sub(decoded_before.saturating_sub(decoded_after));
self.finish_submission(token, height, hash)
}
fn release_in_flight_submission(
&mut self,
token: BlockApplyToken,
height: block::Height,
hash: block::Hash,
bytes: u64,
) {
let matches = self
.in_flight_submissions
.get(&token)
.is_some_and(|submission| {
submission.height == height && submission.hash == hash && submission.bytes == bytes
});
if matches {
let submission = self
.in_flight_submissions
.remove(&token)
.expect("submission exists because it matched above");
self.in_flight_submission_count = self.in_flight_submission_count.saturating_sub(1);
self.in_flight_submission_bytes = self.in_flight_submission_bytes.saturating_sub(bytes);
if submission.detached {
self.detached_submission_decoded_attributed_memory_bytes = self
.detached_submission_decoded_attributed_memory_bytes
.saturating_sub(submission.decoded_attributed_memory_bytes);
}
}
}
fn clear_submitted_applies_from(&mut self, from: block::Height) {
let heights: Vec<_> = self
.submitted_applies
.range(from..)
.map(|(height, _)| *height)
.collect();
for height in heights {
self.submitted_applies.remove(&height);
}
}
fn clear_submitted_applies_through(&mut self, tip: block::Height) {
let heights: Vec<_> = self
.submitted_applies
.range(..=tip)
.map(|(height, _)| *height)
.collect();
for height in heights {
self.submitted_applies.remove(&height);
}
}
pub(super) fn applying_token_hash(
&self,
height: block::Height,
) -> Option<(BlockApplyToken, block::Hash)> {
self.applying
.get(&height)
.map(|applying| (applying.token, applying.hash))
}
pub(super) fn remove_applying(&mut self, height: block::Height) -> Option<ApplyingBlock> {
let removed = self.applying.remove(&height)?;
let decoded_attributed_memory_bytes = removed.body.decoded_attributed_memory_size_bytes();
self.applying_buffered_bytes = self.applying_buffered_bytes.saturating_sub(removed.bytes);
self.applying_decoded_attributed_memory_bytes = self
.applying_decoded_attributed_memory_bytes
.saturating_sub(decoded_attributed_memory_bytes);
if removed.submitted {
self.attached_submission_count = self.attached_submission_count.saturating_sub(1);
if let Some(submission) = self.in_flight_submissions.get_mut(&removed.token) {
if !submission.detached {
submission.detached = true;
self.detached_submission_decoded_attributed_memory_bytes = self
.detached_submission_decoded_attributed_memory_bytes
.saturating_add(submission.decoded_attributed_memory_bytes);
}
}
}
Some(removed)
}
pub(super) fn reset_floor_below(&mut self, height: block::Height) {
self.body_download_floor = previous_height(height)
.unwrap_or(block::Height::MIN)
.max(self.verified_block_tip);
}
pub(super) fn drop_reorder_from(&mut self, from: block::Height) -> u64 {
self.reorder.drop_from(from)
}
pub(super) fn release_applying_blocks_from(&mut self, from: block::Height) -> u64 {
let heights: Vec<_> = self
.applying
.range(from..)
.map(|(height, _)| *height)
.collect();
let mut released = 0u64;
for height in heights {
if let Some(applying) = self.remove_applying(height) {
released = released.saturating_add(applying.bytes);
}
}
self.clear_submitted_applies_from(from);
released
}
pub(super) fn release_applied_through(&mut self, tip: block::Height) -> u64 {
let applied: Vec<_> = self
.applying
.range(..=tip)
.map(|(height, _)| *height)
.collect();
self.clear_submitted_applies_through(tip);
let mut released = 0u64;
for height in applied {
if let Some(applying) = self.remove_applying(height) {
released = released.saturating_add(applying.bytes);
}
}
released
}
pub(super) fn advance_verified_tip(
&mut self,
new_tip: block::Height,
release_applied: bool,
) -> AdvanceOutcome {
self.body_download_floor = self.body_download_floor.max(new_tip);
if new_tip == self.verified_block_tip {
return AdvanceOutcome {
release_bytes: 0,
changed: false,
};
}
let mut released = self.reorder.drop_through(new_tip);
if release_applied {
released = released.saturating_add(self.release_applied_through(new_tip));
}
self.verified_block_tip = new_tip;
AdvanceOutcome {
release_bytes: released,
changed: true,
}
}
pub(super) fn reset_to(&mut self, new_tip: block::Height, keep_submitted_applies: bool) -> u64 {
self.verified_block_tip = new_tip;
self.body_download_floor = new_tip;
let mut released = self.reorder.clear();
released =
released.saturating_add(self.release_all_applying_for_reset(keep_submitted_applies));
released
}
fn release_all_applying_for_reset(&mut self, keep_submitted_applies: bool) -> u64 {
let released = self.applying.values().map(|applying| applying.bytes).sum();
for applying in self.applying.values().filter(|applying| applying.submitted) {
if let Some(submission) = self.in_flight_submissions.get_mut(&applying.token) {
if !submission.detached {
submission.detached = true;
self.detached_submission_decoded_attributed_memory_bytes = self
.detached_submission_decoded_attributed_memory_bytes
.saturating_add(submission.decoded_attributed_memory_bytes);
}
}
}
if !keep_submitted_applies {
self.submitted_applies.clear();
}
self.applying.clear();
self.applying_buffered_bytes = 0;
self.applying_decoded_attributed_memory_bytes = 0;
self.attached_submission_count = 0;
released
}
}