Skip to content

Commit

Permalink
allow negative heights in rpc methods that parse strings to `HashOrHe…
Browse files Browse the repository at this point in the history
…ight`
  • Loading branch information
oxarbitrage committed Feb 22, 2025
1 parent 1e9f021 commit 30cedf4
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 3 deletions.
15 changes: 15 additions & 0 deletions zebra-rpc/src/methods/tests/vectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,21 @@ async fn rpc_getblock() {
assert_eq!(get_block, expected_result);
}

// Test negative heights: -1 should return block 10, -2 block 9, etc.
for neg_height in (-10..=-1).rev() {
// Convert negative height to corresponding index
let index = (neg_height + (blocks.len() as i32)) as usize;

let expected_result = GetBlock::Raw(blocks[index].clone().into());

let get_block = rpc
.get_block(neg_height.to_string(), Some(0u8))
.await
.expect("We should have a GetBlock struct");

assert_eq!(get_block, expected_result);
}

// Create empty note commitment tree information.
let sapling = SaplingTrees { size: 0 };
let orchard = OrchardTrees { size: 0 };
Expand Down
16 changes: 16 additions & 0 deletions zebra-state/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ pub enum HashOrHeight {
Hash(block::Hash),
/// A block identified by height.
Height(block::Height),
/// A block identified by a negative height.
NegativeHeight(i64),
}

impl HashOrHeight {
Expand All @@ -96,6 +98,7 @@ impl HashOrHeight {
match self {
HashOrHeight::Hash(hash) => op(hash),
HashOrHeight::Height(height) => Some(height),
HashOrHeight::NegativeHeight(_) => None,
}
}

Expand All @@ -113,6 +116,7 @@ impl HashOrHeight {
match self {
HashOrHeight::Hash(hash) => Some(hash),
HashOrHeight::Height(height) => op(height),
HashOrHeight::NegativeHeight(_) => None,
}
}

Expand All @@ -133,13 +137,24 @@ impl HashOrHeight {
None
}
}

/// Returns the height if this is a [`HashOrHeight::NegativeHeight`] and no overflow occurs.
pub fn height_from_negative_height(&self, tip: block::Height) -> Option<block::Height> {
if let HashOrHeight::NegativeHeight(index) = self {
let new_height = i64::from(tip.0).checked_add(index + 1)?;
u32::try_from(new_height).ok().map(block::Height)
} else {
None
}
}
}

impl std::fmt::Display for HashOrHeight {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HashOrHeight::Hash(hash) => write!(f, "{hash}"),
HashOrHeight::Height(height) => write!(f, "{}", height.0),
HashOrHeight::NegativeHeight(index) => write!(f, "{}", index),
}
}
}
Expand Down Expand Up @@ -176,6 +191,7 @@ impl std::str::FromStr for HashOrHeight {
s.parse()
.map(Self::Hash)
.or_else(|_| s.parse().map(Self::Height))
.or_else(|_| s.parse().map(|index: i64| Self::NegativeHeight(index)))
.map_err(|_| {
SerializationError::Parse("could not convert the input string to a hash or height")
})
Expand Down
9 changes: 8 additions & 1 deletion zebra-state/src/service/finalized_state/zebra_db/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,14 @@ impl ZebraDb {
#[allow(clippy::unwrap_in_result)]
pub fn block(&self, hash_or_height: HashOrHeight) -> Option<Arc<Block>> {
// Block
let height = hash_or_height.height_or_else(|hash| self.height(hash))?;
let height = match hash_or_height {
HashOrHeight::Hash(hash) => self.height(hash),
HashOrHeight::Height(height) => Some(height),
HashOrHeight::NegativeHeight(_) => {
hash_or_height.height_from_negative_height(self.tip()?.0)
}
}?;

let header = self.block_header(height.into())?;

// Transactions
Expand Down
10 changes: 8 additions & 2 deletions zebra-state/src/service/non_finalized_state/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,8 +415,13 @@ impl Chain {
/// Returns the [`ContextuallyVerifiedBlock`] with [`block::Hash`] or
/// [`Height`], if it exists in this chain.
pub fn block(&self, hash_or_height: HashOrHeight) -> Option<&ContextuallyVerifiedBlock> {
let height =
hash_or_height.height_or_else(|hash| self.height_by_hash.get(&hash).cloned())?;
let height = match hash_or_height {
HashOrHeight::Hash(hash) => self.height_by_hash.get(&hash).cloned(),
HashOrHeight::Height(height) => Some(height),
HashOrHeight::NegativeHeight(_) => {
hash_or_height.height_from_negative_height(self.non_finalized_tip_height())
}
}?;

self.blocks.get(&height)
}
Expand Down Expand Up @@ -507,6 +512,7 @@ impl Chain {
match hash_or_height {
Hash(hash) => self.contains_block_hash(hash),
Height(height) => self.contains_block_height(height),
NegativeHeight(_) => false,
}
}

Expand Down

0 comments on commit 30cedf4

Please sign in to comment.