fix(chain): Return errors instead of panicking in methods for `Height`s (#7591)
* Return errors instead of panicking * Apply suggestions from code review Co-authored-by: teor <teor@riseup.net> * Turn `unwrap`s into `expect`s * Refactor the error messages --------- Co-authored-by: teor <teor@riseup.net>
This commit is contained in:
parent
09c1f994f0
commit
daee5e5fcd
|
|
@ -1,6 +1,7 @@
|
||||||
//! Block height.
|
//! Block height.
|
||||||
|
|
||||||
use std::ops::{Add, Sub};
|
use std::ops::{Add, Sub};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{serialization::SerializationError, BoxError};
|
use crate::{serialization::SerializationError, BoxError};
|
||||||
|
|
||||||
|
|
@ -24,6 +25,14 @@ pub mod json_conversion;
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
pub struct Height(pub u32);
|
pub struct Height(pub u32);
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum HeightError {
|
||||||
|
#[error("The resulting height would overflow Height::MAX.")]
|
||||||
|
Overflow,
|
||||||
|
#[error("The resulting height would underflow Height::MIN.")]
|
||||||
|
Underflow,
|
||||||
|
}
|
||||||
|
|
||||||
impl std::str::FromStr for Height {
|
impl std::str::FromStr for Height {
|
||||||
type Err = SerializationError;
|
type Err = SerializationError;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
|
@ -72,8 +81,8 @@ impl Height {
|
||||||
///
|
///
|
||||||
/// - If the current height is at its maximum.
|
/// - If the current height is at its maximum.
|
||||||
// TODO Return an error instead of panicking #7263.
|
// TODO Return an error instead of panicking #7263.
|
||||||
pub fn next(self) -> Self {
|
pub fn next(self) -> Result<Self, HeightError> {
|
||||||
(self + 1).expect("Height should not be at its maximum.")
|
(self + 1).ok_or(HeightError::Overflow)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the previous [`Height`].
|
/// Returns the previous [`Height`].
|
||||||
|
|
@ -82,8 +91,8 @@ impl Height {
|
||||||
///
|
///
|
||||||
/// - If the current height is at its minimum.
|
/// - If the current height is at its minimum.
|
||||||
// TODO Return an error instead of panicking #7263.
|
// TODO Return an error instead of panicking #7263.
|
||||||
pub fn previous(self) -> Self {
|
pub fn previous(self) -> Result<Self, HeightError> {
|
||||||
(self - 1).expect("Height should not be at its minimum.")
|
(self - 1).ok_or(HeightError::Underflow)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the [`Height`] is at its minimum.
|
/// Returns `true` if the [`Height`] is at its minimum.
|
||||||
|
|
|
||||||
|
|
@ -365,7 +365,12 @@ fn check_sapling_subtrees(db: &ZebraDb) -> Result<(), &'static str> {
|
||||||
}
|
}
|
||||||
// Check that the final note has a greater subtree index if it didn't complete a subtree.
|
// Check that the final note has a greater subtree index if it didn't complete a subtree.
|
||||||
else {
|
else {
|
||||||
let Some(prev_tree) = db.sapling_tree_by_height(&subtree.end.previous()) else {
|
let prev_height = subtree
|
||||||
|
.end
|
||||||
|
.previous()
|
||||||
|
.expect("Note commitment subtrees should not end at the minimal height.");
|
||||||
|
|
||||||
|
let Some(prev_tree) = db.sapling_tree_by_height(&prev_height) else {
|
||||||
result = Err("missing note commitment tree below subtree completion height");
|
result = Err("missing note commitment tree below subtree completion height");
|
||||||
error!(?result, ?subtree.end);
|
error!(?result, ?subtree.end);
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -477,7 +482,12 @@ fn check_orchard_subtrees(db: &ZebraDb) -> Result<(), &'static str> {
|
||||||
}
|
}
|
||||||
// Check that the final note has a greater subtree index if it didn't complete a subtree.
|
// Check that the final note has a greater subtree index if it didn't complete a subtree.
|
||||||
else {
|
else {
|
||||||
let Some(prev_tree) = db.orchard_tree_by_height(&subtree.end.previous()) else {
|
let prev_height = subtree
|
||||||
|
.end
|
||||||
|
.previous()
|
||||||
|
.expect("Note commitment subtrees should not end at the minimal height.");
|
||||||
|
|
||||||
|
let Some(prev_tree) = db.orchard_tree_by_height(&prev_height) else {
|
||||||
result = Err("missing note commitment tree below subtree completion height");
|
result = Err("missing note commitment tree below subtree completion height");
|
||||||
error!(?result, ?subtree.end);
|
error!(?result, ?subtree.end);
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -556,7 +556,12 @@ impl Chain {
|
||||||
// Don't add a new tree unless it differs from the previous one or there's no previous tree.
|
// Don't add a new tree unless it differs from the previous one or there's no previous tree.
|
||||||
if height.is_min()
|
if height.is_min()
|
||||||
|| self
|
|| self
|
||||||
.sprout_tree(height.previous().into())
|
.sprout_tree(
|
||||||
|
height
|
||||||
|
.previous()
|
||||||
|
.expect("Already checked for underflow.")
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
.map_or(true, |prev_tree| prev_tree != tree)
|
.map_or(true, |prev_tree| prev_tree != tree)
|
||||||
{
|
{
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -640,7 +645,9 @@ impl Chain {
|
||||||
// it will always violate the invariant. We restore the invariant by storing the highest
|
// it will always violate the invariant. We restore the invariant by storing the highest
|
||||||
// (rightmost) removed tree just above `height` if there is no tree at that height.
|
// (rightmost) removed tree just above `height` if there is no tree at that height.
|
||||||
if !self.is_empty() && height < self.non_finalized_tip_height() {
|
if !self.is_empty() && height < self.non_finalized_tip_height() {
|
||||||
let next_height = height.next();
|
let next_height = height
|
||||||
|
.next()
|
||||||
|
.expect("Zebra should never reach the max height in normal operation.");
|
||||||
|
|
||||||
if self.sprout_trees_by_height.get(&next_height).is_none() {
|
if self.sprout_trees_by_height.get(&next_height).is_none() {
|
||||||
// TODO: Use `try_insert` once it stabilises.
|
// TODO: Use `try_insert` once it stabilises.
|
||||||
|
|
@ -754,7 +761,12 @@ impl Chain {
|
||||||
// Don't add a new tree unless it differs from the previous one or there's no previous tree.
|
// Don't add a new tree unless it differs from the previous one or there's no previous tree.
|
||||||
if height.is_min()
|
if height.is_min()
|
||||||
|| self
|
|| self
|
||||||
.sapling_tree(height.previous().into())
|
.sapling_tree(
|
||||||
|
height
|
||||||
|
.previous()
|
||||||
|
.expect("Already checked for underflow.")
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
.map_or(true, |prev_tree| prev_tree != tree)
|
.map_or(true, |prev_tree| prev_tree != tree)
|
||||||
{
|
{
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -834,7 +846,9 @@ impl Chain {
|
||||||
// it will always violate the invariant. We restore the invariant by storing the highest
|
// it will always violate the invariant. We restore the invariant by storing the highest
|
||||||
// (rightmost) removed tree just above `height` if there is no tree at that height.
|
// (rightmost) removed tree just above `height` if there is no tree at that height.
|
||||||
if !self.is_empty() && height < self.non_finalized_tip_height() {
|
if !self.is_empty() && height < self.non_finalized_tip_height() {
|
||||||
let next_height = height.next();
|
let next_height = height
|
||||||
|
.next()
|
||||||
|
.expect("Zebra should never reach the max height in normal operation.");
|
||||||
|
|
||||||
if self.sapling_trees_by_height.get(&next_height).is_none() {
|
if self.sapling_trees_by_height.get(&next_height).is_none() {
|
||||||
// TODO: Use `try_insert` once it stabilises.
|
// TODO: Use `try_insert` once it stabilises.
|
||||||
|
|
@ -954,7 +968,12 @@ impl Chain {
|
||||||
// Don't add a new tree unless it differs from the previous one or there's no previous tree.
|
// Don't add a new tree unless it differs from the previous one or there's no previous tree.
|
||||||
if height.is_min()
|
if height.is_min()
|
||||||
|| self
|
|| self
|
||||||
.orchard_tree(height.previous().into())
|
.orchard_tree(
|
||||||
|
height
|
||||||
|
.previous()
|
||||||
|
.expect("Already checked for underflow.")
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
.map_or(true, |prev_tree| prev_tree != tree)
|
.map_or(true, |prev_tree| prev_tree != tree)
|
||||||
{
|
{
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -1034,7 +1053,9 @@ impl Chain {
|
||||||
// it will always violate the invariant. We restore the invariant by storing the highest
|
// it will always violate the invariant. We restore the invariant by storing the highest
|
||||||
// (rightmost) removed tree just above `height` if there is no tree at that height.
|
// (rightmost) removed tree just above `height` if there is no tree at that height.
|
||||||
if !self.is_empty() && height < self.non_finalized_tip_height() {
|
if !self.is_empty() && height < self.non_finalized_tip_height() {
|
||||||
let next_height = height.next();
|
let next_height = height
|
||||||
|
.next()
|
||||||
|
.expect("Zebra should never reach the max height in normal operation.");
|
||||||
|
|
||||||
if self.orchard_trees_by_height.get(&next_height).is_none() {
|
if self.orchard_trees_by_height.get(&next_height).is_none() {
|
||||||
// TODO: Use `try_insert` once it stabilises.
|
// TODO: Use `try_insert` once it stabilises.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue