Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make service use both finalized and non-finalized state #1239

Merged
merged 5 commits into from
Nov 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion zebra-state/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,28 @@ 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),
/// A block identified by height.
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)
Expand Down
86 changes: 71 additions & 15 deletions zebra-state/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
Expand Down
56 changes: 52 additions & 4 deletions zebra-state/src/service/memory_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down
97 changes: 38 additions & 59 deletions zebra-state/src/sled_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -193,17 +194,14 @@ 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)
}

/// 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.
Expand Down Expand Up @@ -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()),
};

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);
}
}
/// 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);

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()
})
}
}
Loading