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
This commit is contained in:
Jane Lusby 2020-11-01 10:49:34 -08:00 committed by GitHub
parent 0ad648fb6a
commit 714def990e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 203 additions and 101 deletions

View File

@ -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<F>(self, op: F) -> Option<block::Height>
where
F: FnOnce(block::Hash) -> Option<block::Height>,
{
match self {
HashOrHeight::Hash(hash) => op(hash),
HashOrHeight::Height(height) => Some(height),
}
}
}
impl From<block::Hash> for HashOrHeight {
fn from(hash: block::Hash) -> Self {
Self::Hash(hash)

View File

@ -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<Vec<block::Hash>> {
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<u32> {
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<Arc<Block>> {
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<Arc<Transaction>> {
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<block::Hash> {
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<transparent::Output> {
self.mem.utxo(outpoint).or_else(|| self.sled.utxo(outpoint))
}
}
impl Service<Request> for StateService {
@ -244,33 +303,30 @@ impl Service<Request> 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()

View File

@ -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<Arc<Block>> {
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<block::Hash> {
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<block::Height> {
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<Arc<Transaction>> {
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.

View File

@ -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<block::Height> {
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<Vec<block::Hash>, 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<Option<(block::Height, block::Hash)>, 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<block::Height> {
self.height_by_hash.zs_get(&hash)
}
pub fn depth(&self, hash: block::Hash) -> Result<Option<u32>, 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<Arc<Block>> {
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<Option<Arc<Block>>, 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<Option<transparent::Output>, BoxError> {
pub fn utxo(&self, outpoint: &transparent::OutPoint) -> Option<transparent::Output> {
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<block::Hash> {
self.hash_by_height
.zs_get(&height)
.expect("expected that sled errors would not occur")
pub fn hash(&self, height: block::Height) -> Option<block::Hash> {
self.hash_by_height.zs_get(&height)
}
/// Returns the given transaction if it exists.
pub fn transaction(&self, hash: transaction::Hash) -> Option<Arc<Transaction>> {
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()
})
}
}

View File

@ -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<Self, BoxError>;
/// 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<Block> {
fn from_ivec(bytes: sled::IVec) -> Result<Self, BoxError> {
let block = Arc::<Block>::zcash_deserialize(bytes.as_ref())?;
Ok(block)
fn from_ivec(bytes: sled::IVec) -> Self {
Arc::<Block>::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<Self, BoxError> {
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<Self, BoxError> {
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<Self, BoxError> {
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, BoxError> {
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<K, V>(&self, key: &K) -> Result<Option<V>, BoxError>
fn zs_get<K, V>(&self, key: &K) -> Option<V>
where
K: IntoSled,
V: FromSled;
}
impl SledDeserialize for sled::Tree {
fn zs_get<K, V>(&self, key: &K) -> Result<Option<V>, BoxError>
fn zs_get<K, V>(&self, key: &K) -> Option<V>
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)
}
}