From 714def990e6259bc5a150d54ab4f5eed29961396 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Sun, 1 Nov 2020 10:49:34 -0800 Subject: [PATCH] make state service use both finalized and non-finalized state (#1239) * make service use both finalized and non-finalized state * Document new functions * add documentation to sled fns * cleanup tip fn now that errors are gone * rename height unwrap fn --- zebra-state/src/request.rs | 16 +++- zebra-state/src/service.rs | 86 ++++++++++++++++---- zebra-state/src/service/memory_state.rs | 56 ++++++++++++- zebra-state/src/sled_state.rs | 97 +++++++++-------------- zebra-state/src/sled_state/sled_format.rs | 49 +++++++----- 5 files changed, 203 insertions(+), 101 deletions(-) diff --git a/zebra-state/src/request.rs b/zebra-state/src/request.rs index e02b128fe..974210617 100644 --- a/zebra-state/src/request.rs +++ b/zebra-state/src/request.rs @@ -13,7 +13,7 @@ use crate::Response; /// /// This enum implements `From` for [`block::Hash`] and [`block::Height`], /// so it can be created using `hash.into()` or `height.into()`. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum HashOrHeight { /// A block identified by hash. Hash(block::Hash), @@ -21,6 +21,20 @@ pub enum HashOrHeight { Height(block::Height), } +impl HashOrHeight { + /// Unwrap the inner height or attempt to retrieve the height for a given + /// hash if one exists. + pub fn height_or_else(self, op: F) -> Option + where + F: FnOnce(block::Hash) -> Option, + { + match self { + HashOrHeight::Hash(hash) => op(hash), + HashOrHeight::Height(height) => Some(height), + } + } +} + impl From for HashOrHeight { fn from(hash: block::Hash) -> Self { Self::Hash(hash) diff --git a/zebra-state/src/service.rs b/zebra-state/src/service.rs index 599cdcd18..f2c6b0e7c 100644 --- a/zebra-state/src/service.rs +++ b/zebra-state/src/service.rs @@ -15,10 +15,14 @@ use tracing::instrument; use zebra_chain::{ block::{self, Block}, parameters::Network, + transaction, + transaction::Transaction, + transparent, }; use crate::{ - BoxError, CommitBlockError, Config, FinalizedState, Request, Response, ValidateContextError, + request::HashOrHeight, BoxError, CommitBlockError, Config, FinalizedState, Request, Response, + ValidateContextError, }; mod memory_state; @@ -151,7 +155,7 @@ impl StateService { .coinbase_height() .expect("coinbase heights should be valid"); - self.mem.any_chain_contains(&hash) || self.sled.get_hash(height) == Some(hash) + self.mem.any_chain_contains(&hash) || self.sled.hash(height) == Some(hash) } /// Attempt to validate and commit all queued blocks whose parents have @@ -193,6 +197,61 @@ impl StateService { // TODO: contextual validation design and implementation Ok(()) } + + /// Create a block locator for the current best chain. + fn block_locator(&self) -> Option> { + let tip_height = self.tip()?.0; + + let heights = crate::util::block_locator_heights(tip_height); + let mut hashes = Vec::with_capacity(heights.len()); + + for height in heights { + if let Some(hash) = self.hash(height) { + hashes.push(hash); + } + } + + Some(hashes) + } + + /// Return the tip of the current best chain. + pub fn tip(&self) -> Option<(block::Height, block::Hash)> { + self.mem.tip().or_else(|| self.sled.tip()) + } + + /// Return the depth of block `hash` in the current best chain. + pub fn depth(&self, hash: block::Hash) -> Option { + let tip = self.tip()?.0; + let height = self.mem.height(hash).or_else(|| self.sled.height(hash))?; + + Some(tip.0 - height.0) + } + + /// Return the block identified by either its `height` or `hash` if it exists + /// in the current best chain. + pub fn block(&self, hash_or_height: HashOrHeight) -> Option> { + self.mem + .block(hash_or_height) + .or_else(|| self.sled.block(hash_or_height)) + } + + /// Return the transaction identified by `hash` if it exists in the current + /// best chain. + pub fn transaction(&self, hash: transaction::Hash) -> Option> { + self.mem + .transaction(hash) + .or_else(|| self.sled.transaction(hash)) + } + + /// Return the hash for the block at `height` in the current best chain. + pub fn hash(&self, height: block::Height) -> Option { + self.mem.hash(height).or_else(|| self.sled.hash(height)) + } + + /// Return the utxo pointed to by `outpoint` if it exists in any chain. + pub fn utxo(&self, outpoint: &transparent::OutPoint) -> Option { + self.mem.utxo(outpoint).or_else(|| self.sled.utxo(outpoint)) + } } impl Service for StateService { @@ -244,33 +303,30 @@ impl Service for StateService { .boxed() } Request::Depth(hash) => { - // todo: handle in memory and sled - let rsp = self.sled.depth(hash).map(Response::Depth); + let rsp = Ok(self.depth(hash)).map(Response::Depth); async move { rsp }.boxed() } Request::Tip => { - // todo: handle in memory and sled - let rsp = self.sled.tip().map(Response::Tip); + let rsp = Ok(self.tip()).map(Response::Tip); async move { rsp }.boxed() } Request::BlockLocator => { - // todo: handle in memory and sled - let rsp = self.sled.block_locator().map(Response::BlockLocator); + let rsp = Ok(self.block_locator().unwrap_or_default()).map(Response::BlockLocator); + async move { rsp }.boxed() + } + Request::Transaction(hash) => { + let rsp = Ok(self.transaction(hash)).map(Response::Transaction); async move { rsp }.boxed() } - Request::Transaction(_) => unimplemented!(), Request::Block(hash_or_height) => { - //todo: handle in memory and sled - let rsp = self.sled.block(hash_or_height).map(Response::Block); + let rsp = Ok(self.block(hash_or_height)).map(Response::Block); async move { rsp }.boxed() } Request::AwaitUtxo(outpoint) => { let fut = self.pending_utxos.queue(outpoint); - if let Some(finalized_utxo) = self.sled.utxo(&outpoint).unwrap() { - self.pending_utxos.respond(outpoint, finalized_utxo); - } else if let Some(non_finalized_utxo) = self.mem.utxo(&outpoint) { - self.pending_utxos.respond(outpoint, non_finalized_utxo); + if let Some(utxo) = self.utxo(&outpoint) { + self.pending_utxos.respond(outpoint, utxo); } fut.boxed() diff --git a/zebra-state/src/service/memory_state.rs b/zebra-state/src/service/memory_state.rs index 0a40ea9d3..d22de653e 100644 --- a/zebra-state/src/service/memory_state.rs +++ b/zebra-state/src/service/memory_state.rs @@ -14,10 +14,13 @@ use tracing::{debug_span, instrument, trace}; use zebra_chain::{ block::{self, Block}, primitives::Groth16Proof, - sapling, sprout, transaction, transparent, + sapling, sprout, + transaction::{self, Transaction}, + transparent, work::difficulty::PartialCumulativeWork, }; +use crate::request::HashOrHeight; use crate::service::QueuedBlock; #[derive(Default, Clone)] @@ -492,9 +495,7 @@ impl NonFinalizedState { /// Returns the length of the non-finalized portion of the current best chain. pub fn best_chain_len(&self) -> block::Height { block::Height( - self.chain_set - .iter() - .next_back() + self.best_chain() .expect("only called after inserting a block") .blocks .len() as u32, @@ -547,6 +548,53 @@ impl NonFinalizedState { None } + + /// Returns the `block` at a given height or hash in the best chain. + pub fn block(&self, hash_or_height: HashOrHeight) -> Option> { + let best_chain = self.best_chain()?; + let height = + hash_or_height.height_or_else(|hash| best_chain.height_by_hash.get(&hash).cloned())?; + + best_chain.blocks.get(&height).cloned() + } + + /// Returns the hash for a given `block::Height` if it is present in the best chain. + pub fn hash(&self, height: block::Height) -> Option { + self.block(height.into()).map(|block| block.hash()) + } + + /// Returns the tip of the best chain. + pub fn tip(&self) -> Option<(block::Height, block::Hash)> { + let best_chain = self.best_chain()?; + let height = best_chain.non_finalized_tip_height(); + let hash = best_chain.non_finalized_tip_hash(); + + Some((height, hash)) + } + + /// Returns the depth of `hash` in the best chain. + pub fn height(&self, hash: block::Hash) -> Option { + let best_chain = self.best_chain()?; + let height = *best_chain.height_by_hash.get(&hash)?; + Some(height) + } + + /// Returns the given transaction if it exists in the best chain. + pub fn transaction(&self, hash: transaction::Hash) -> Option> { + let best_chain = self.best_chain()?; + best_chain.tx_by_hash.get(&hash).map(|(height, index)| { + let block = &best_chain.blocks[height]; + block.transactions[*index].clone() + }) + } + + /// Return the non-finalized portion of the current best chain + fn best_chain(&self) -> Option<&Chain> { + self.chain_set + .iter() + .next_back() + .map(|box_chain| box_chain.deref()) + } } /// A queue of blocks, awaiting the arrival of parent blocks. diff --git a/zebra-state/src/sled_state.rs b/zebra-state/src/sled_state.rs index c01541c61..097a7ec10 100644 --- a/zebra-state/src/sled_state.rs +++ b/zebra-state/src/sled_state.rs @@ -7,6 +7,7 @@ use zebra_chain::transparent; use zebra_chain::{ block::{self, Block}, parameters::{Network, GENESIS_PREVIOUS_BLOCK_HASH}, + transaction::{self, Transaction}, }; use crate::{BoxError, Config, HashOrHeight, QueuedBlock}; @@ -193,7 +194,6 @@ impl FinalizedState { /// Returns the hash of the current finalized tip block. pub fn finalized_tip_hash(&self) -> block::Hash { self.tip() - .expect("inability to look up tip is unrecoverable") .map(|(_, hash)| hash) // if the state is empty, return the genesis previous block hash .unwrap_or(GENESIS_PREVIOUS_BLOCK_HASH) @@ -201,9 +201,7 @@ impl FinalizedState { /// Returns the height of the current finalized tip block. pub fn finalized_tip_height(&self) -> Option { - self.tip() - .expect("inability to look up tip is unrecoverable") - .map(|(height, _)| height) + self.tip().map(|(height, _)| height) } /// Immediately commit `block` to the finalized state. @@ -316,74 +314,55 @@ impl FinalizedState { let _ = rsp_tx.send(result.map_err(Into::into)); } - // TODO: this impl works only during checkpointing, it needs to be rewritten - pub fn block_locator(&self) -> Result, BoxError> { - let (tip_height, _) = match self.tip()? { - Some(height) => height, - None => return Ok(Vec::new()), - }; + /// Returns the tip height and hash if there is one. + pub fn tip(&self) -> Option<(block::Height, block::Hash)> { + self.hash_by_height + .iter() + .rev() + .next() + .transpose() + .expect("expected that sled errors would not occur") + .map(|(height_bytes, hash_bytes)| { + let height = block::Height::from_ivec(height_bytes); + let hash = block::Hash::from_ivec(hash_bytes); - let heights = crate::util::block_locator_heights(tip_height); - let mut hashes = Vec::with_capacity(heights.len()); - - for height in heights { - if let Some(hash) = self.hash_by_height.zs_get(&height)? { - hashes.push(hash); - } - } - - Ok(hashes) + (height, hash) + }) } - pub fn tip(&self) -> Result, BoxError> { - if let Some((height_bytes, hash_bytes)) = - self.hash_by_height.iter().rev().next().transpose()? - { - let height = block::Height::from_ivec(height_bytes)?; - let hash = block::Hash::from_ivec(hash_bytes)?; - - Ok(Some((height, hash))) - } else { - Ok(None) - } + /// Returns the height of the given block if it exists. + pub fn height(&self, hash: block::Hash) -> Option { + self.height_by_hash.zs_get(&hash) } - pub fn depth(&self, hash: block::Hash) -> Result, BoxError> { - let height: block::Height = match self.height_by_hash.zs_get(&hash)? { - Some(height) => height, - None => return Ok(None), - }; + /// Returns the given block if it exists. + pub fn block(&self, hash_or_height: HashOrHeight) -> Option> { + let height = hash_or_height.height_or_else(|hash| self.height_by_hash.zs_get(&hash))?; - let (tip_height, _) = self.tip()?.expect("tip must exist"); - - Ok(Some(tip_height.0 - height.0)) - } - - pub fn block(&self, hash_or_height: HashOrHeight) -> Result>, BoxError> { - let height = match hash_or_height { - HashOrHeight::Height(height) => height, - HashOrHeight::Hash(hash) => match self.height_by_hash.zs_get(&hash)? { - Some(height) => height, - None => return Ok(None), - }, - }; - - Ok(self.block_by_height.zs_get(&height)?) + self.block_by_height.zs_get(&height) } /// Returns the `transparent::Output` pointed to by the given /// `transparent::OutPoint` if it is present. - pub fn utxo( - &self, - outpoint: &transparent::OutPoint, - ) -> Result, BoxError> { + pub fn utxo(&self, outpoint: &transparent::OutPoint) -> Option { self.utxo_by_outpoint.zs_get(outpoint) } /// Returns the finalized hash for a given `block::Height` if it is present. - pub fn get_hash(&self, height: block::Height) -> Option { - self.hash_by_height - .zs_get(&height) - .expect("expected that sled errors would not occur") + pub fn hash(&self, height: block::Height) -> Option { + self.hash_by_height.zs_get(&height) + } + + /// Returns the given transaction if it exists. + pub fn transaction(&self, hash: transaction::Hash) -> Option> { + self.tx_by_hash + .zs_get(&hash) + .map(|TransactionLocation { index, height }| { + let block = self + .block(height.into()) + .expect("block will exist if TransactionLocation does"); + + block.transactions[index as usize].clone() + }) } } diff --git a/zebra-state/src/sled_state/sled_format.rs b/zebra-state/src/sled_state/sled_format.rs index c861258fd..2a72e29a1 100644 --- a/zebra-state/src/sled_state/sled_format.rs +++ b/zebra-state/src/sled_state/sled_format.rs @@ -9,8 +9,6 @@ use zebra_chain::{ sprout, transaction, transparent, }; -use crate::BoxError; - pub struct TransactionLocation { pub height: block::Height, pub index: u32, @@ -31,10 +29,16 @@ pub trait IntoSled { fn into_ivec(self) -> sled::IVec; } -// Helper type for retrieving types from sled with the correct format +/// Helper type for retrieving types from sled with the correct format. +/// +/// The ivec should be correctly encoded by IntoSled. pub trait FromSled: Sized { - // function to convert the sled bytes back into the deserialized type - fn from_ivec(bytes: sled::IVec) -> Result; + /// Function to convert the sled bytes back into the deserialized type. + /// + /// # Panics + /// + /// - if the input data doesn't deserialize correctly + fn from_ivec(bytes: sled::IVec) -> Self; } impl IntoSled for &Block { @@ -51,9 +55,9 @@ impl IntoSled for &Block { } impl FromSled for Arc { - fn from_ivec(bytes: sled::IVec) -> Result { - let block = Arc::::zcash_deserialize(bytes.as_ref())?; - Ok(block) + fn from_ivec(bytes: sled::IVec) -> Self { + Arc::::zcash_deserialize(bytes.as_ref()) + .expect("deserialization format should match the serialization format used by IntoSled") } } @@ -78,7 +82,7 @@ impl IntoSled for TransactionLocation { } impl FromSled for TransactionLocation { - fn from_ivec(sled_bytes: sled::IVec) -> Result { + fn from_ivec(sled_bytes: sled::IVec) -> Self { let height = { let mut bytes = [0; 4]; bytes.copy_from_slice(&sled_bytes[0..4]); @@ -92,7 +96,7 @@ impl FromSled for TransactionLocation { u32::from_be_bytes(bytes) }; - Ok(TransactionLocation { height, index }) + TransactionLocation { height, index } } } @@ -156,9 +160,9 @@ impl IntoSled for () { } impl FromSled for block::Hash { - fn from_ivec(bytes: sled::IVec) -> Result { + fn from_ivec(bytes: sled::IVec) -> Self { let array = bytes.as_ref().try_into().unwrap(); - Ok(Self(array)) + Self(array) } } @@ -174,9 +178,9 @@ impl IntoSled for block::Height { } impl FromSled for block::Height { - fn from_ivec(bytes: sled::IVec) -> Result { + fn from_ivec(bytes: sled::IVec) -> Self { let array = bytes.as_ref().try_into().unwrap(); - Ok(block::Height(u32::from_be_bytes(array))) + block::Height(u32::from_be_bytes(array)) } } @@ -194,8 +198,9 @@ impl IntoSled for &transparent::Output { } impl FromSled for transparent::Output { - fn from_ivec(bytes: sled::IVec) -> Result { - Self::zcash_deserialize(&*bytes).map_err(Into::into) + fn from_ivec(bytes: sled::IVec) -> Self { + Self::zcash_deserialize(&*bytes) + .expect("deserialization format should match the serialization format used by IntoSled") } } @@ -248,24 +253,24 @@ impl SledSerialize for sled::transaction::TransactionalTree { pub trait SledDeserialize { /// Serialize the given key and use that to get and deserialize the /// corresponding value from a sled tree, if it is present. - fn zs_get(&self, key: &K) -> Result, BoxError> + fn zs_get(&self, key: &K) -> Option where K: IntoSled, V: FromSled; } impl SledDeserialize for sled::Tree { - fn zs_get(&self, key: &K) -> Result, BoxError> + fn zs_get(&self, key: &K) -> Option where K: IntoSled, V: FromSled, { let key_bytes = key.as_bytes(); - let value_bytes = self.get(key_bytes)?; + let value_bytes = self + .get(key_bytes) + .expect("expected that sled errors would not occur"); - let value = value_bytes.map(V::from_ivec).transpose()?; - - Ok(value) + value_bytes.map(V::from_ivec) } }