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:
Marek 2023-09-21 07:58:04 +02:00 committed by GitHub
parent 09c1f994f0
commit daee5e5fcd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 52 additions and 12 deletions

View File

@ -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.

View File

@ -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;

View File

@ -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.