From b38e56f42ead2462615f4a984a24691a7ee89102 Mon Sep 17 00:00:00 2001 From: Marek Date: Fri, 7 Mar 2025 14:13:44 +0100 Subject: [PATCH] Support negative heights in `HashOrHeight` --- zebra-rpc/src/methods.rs | 10 +++++----- zebra-state/src/request.rs | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/zebra-rpc/src/methods.rs b/zebra-rpc/src/methods.rs index 0a5e6e351f5..e487f01188f 100644 --- a/zebra-rpc/src/methods.rs +++ b/zebra-rpc/src/methods.rs @@ -839,11 +839,11 @@ where None }; - let hash_or_height: HashOrHeight = hash_or_height - .parse() - // Reference for the legacy error code: - // - .map_error(server::error::LegacyCode::InvalidParameter)?; + let hash_or_height = + HashOrHeight::new(&hash_or_height, self.latest_chain_tip.best_tip_height()) + // Reference for the legacy error code: + // + .map_error(server::error::LegacyCode::InvalidParameter)?; if verbosity == 0 { let request = zebra_state::ReadRequest::Block(hash_or_height); diff --git a/zebra-state/src/request.rs b/zebra-state/src/request.rs index 1535fa81c1f..7348041f43f 100644 --- a/zebra-state/src/request.rs +++ b/zebra-state/src/request.rs @@ -2,13 +2,13 @@ use std::{ collections::{HashMap, HashSet}, - ops::{Deref, DerefMut, RangeInclusive}, + ops::{Add, Deref, DerefMut, RangeInclusive}, sync::Arc, }; use zebra_chain::{ amount::{Amount, NegativeAllowed, NonNegative}, - block::{self, Block}, + block::{self, Block, HeightDiff}, history_tree::HistoryTree, orchard, parallel::tree::NoteCommitmentTrees, @@ -133,6 +133,38 @@ impl HashOrHeight { None } } + + /// Constructs a new [`HashOrHeight`] from a string containing a hash or a positive or negative + /// height. + /// + /// When the provided `hash_or_height` contains a negative height, the `tip_height` parameter + /// needs to be `Some` since height `-1` points to the tip. + pub fn new(hash_or_height: &str, tip_height: Option) -> Result { + hash_or_height + .parse() + .map(Self::Hash) + .or_else(|_| hash_or_height.parse().map(Self::Height)) + .or_else(|_| { + hash_or_height + .parse() + .map_err(|_| "could not parse negative height") + .and_then(|d: HeightDiff| { + d.is_negative() + .then(|| { + Ok(HashOrHeight::Height( + tip_height + .ok_or("missing tip height")? + .add(d) + .ok_or("underflow when adding negative height to tip")? + .next() + .map_err(|_| "height -1 needs to point to tip")?, + )) + }) + .unwrap_or(Err("height was not negative")) + }) + }) + .map_err(|e| format!("could not convert the input string to a hash or height: {e}")) + } } impl std::fmt::Display for HashOrHeight {