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:
parent
0ad648fb6a
commit
714def990e
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue