Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions crates/node/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,12 @@ where

let drained = self.storage.drain_above(target).await?;

// Immediately cap block tags to the common ancestor so that
// `latest` never references a block that no longer exists in
// storage. This must happen before the reorg notification so
// that RPC consumers see consistent tags.
self.chain.tags().rewind_to(target);

// The early return above guards against no-op reverts, so drained
// should always contain at least one block. Guard defensively.
debug_assert!(!drained.is_empty(), "drain_above returned empty after host revert");
Expand Down
59 changes: 59 additions & 0 deletions crates/rpc/src/config/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,33 @@ impl BlockTags {
self.latest.store(latest, Ordering::Release);
}

/// Cap all three tags to at most `ancestor`.
///
/// Used during reorgs to ensure tags never reference blocks that
/// have been removed from storage. Stores are ordered
/// finalized → safe → latest (the same as [`update_all`]) so
/// that readers never observe `latest < finalized` while the
/// values are being decreased.
///
/// [`update_all`]: Self::update_all
///
/// # Example
///
/// ```
/// use signet_rpc::BlockTags;
///
/// let tags = BlockTags::new(100, 95, 90);
/// tags.rewind_to(92);
/// assert_eq!(tags.latest(), 92);
/// assert_eq!(tags.safe(), 92);
/// assert_eq!(tags.finalized(), 90); // already below ancestor
/// ```
pub fn rewind_to(&self, ancestor: u64) {
self.finalized.fetch_min(ancestor, Ordering::Release);
self.safe.fetch_min(ancestor, Ordering::Release);
self.latest.fetch_min(ancestor, Ordering::Release);
}

/// Returns `true` if the node is currently syncing.
pub fn is_syncing(&self) -> bool {
self.sync_status.read().expect("sync status lock poisoned").is_some()
Expand All @@ -130,6 +157,38 @@ impl BlockTags {
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn rewind_to_caps_all_tags() {
let tags = BlockTags::new(100, 95, 90);
tags.rewind_to(92);
assert_eq!(tags.latest(), 92);
assert_eq!(tags.safe(), 92);
assert_eq!(tags.finalized(), 90);
}

#[test]
fn rewind_to_caps_all_above_ancestor() {
let tags = BlockTags::new(100, 95, 90);
tags.rewind_to(50);
assert_eq!(tags.latest(), 50);
assert_eq!(tags.safe(), 50);
assert_eq!(tags.finalized(), 50);
}

#[test]
fn rewind_to_noop_when_all_below() {
let tags = BlockTags::new(100, 95, 90);
tags.rewind_to(200);
assert_eq!(tags.latest(), 100);
assert_eq!(tags.safe(), 95);
assert_eq!(tags.finalized(), 90);
}
}

/// Error resolving a block identifier.
#[derive(Debug, thiserror::Error)]
pub enum ResolveError {
Expand Down