2020-06-15 14:41:26 -07:00
|
|
|
//! State storage code for Zebra. 🦓
|
|
|
|
//!
|
|
|
|
//! ## Organizational Structure
|
|
|
|
//!
|
|
|
|
//! zebra-state tracks `Blocks` using two key-value trees
|
|
|
|
//!
|
|
|
|
//! * BlockHeaderHash -> Block
|
|
|
|
//! * BlockHeight -> Block
|
|
|
|
//!
|
|
|
|
//! Inserting a block into the service will create a mapping in each tree for that block.
|
2020-06-02 16:16:17 -07:00
|
|
|
#![doc(html_logo_url = "https://www.zfnd.org/images/zebra-icon.png")]
|
|
|
|
#![doc(html_root_url = "https://doc.zebra.zfnd.org/zebra_state")]
|
2020-06-15 14:41:26 -07:00
|
|
|
#![warn(missing_docs)]
|
2020-06-04 10:17:49 -07:00
|
|
|
#![allow(clippy::try_err)]
|
2020-06-15 14:41:26 -07:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use std::path::PathBuf;
|
2020-06-04 10:17:49 -07:00
|
|
|
use std::sync::Arc;
|
2020-06-02 16:16:17 -07:00
|
|
|
use zebra_chain::block::{Block, BlockHeaderHash};
|
|
|
|
|
2020-06-04 10:17:49 -07:00
|
|
|
pub mod in_memory;
|
2020-06-15 14:41:26 -07:00
|
|
|
pub mod on_disk;
|
|
|
|
|
|
|
|
/// Configuration for networking code.
|
|
|
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct Config {
|
|
|
|
/// The root directory for the state storage
|
|
|
|
pub path: PathBuf,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Config {
|
|
|
|
pub(crate) fn sled_config(&self) -> sled::Config {
|
|
|
|
sled::Config::default().path(&self.path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Config {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
path: PathBuf::from("./.zebra-state"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-06-04 10:17:49 -07:00
|
|
|
|
2020-06-11 21:52:27 -07:00
|
|
|
#[derive(Debug, PartialEq)]
|
2020-06-15 14:41:26 -07:00
|
|
|
/// A state request, used to manipulate the zebra-state on disk or in memory
|
2020-06-02 16:16:17 -07:00
|
|
|
pub enum Request {
|
|
|
|
// TODO(jlusby): deprecate in the future based on our validation story
|
2020-06-15 14:41:26 -07:00
|
|
|
/// Add a block to the zebra-state
|
|
|
|
AddBlock {
|
|
|
|
/// The block to be added to the state
|
|
|
|
block: Arc<Block>,
|
|
|
|
},
|
|
|
|
/// Get a block from the zebra-state
|
|
|
|
GetBlock {
|
|
|
|
/// The hash used to identify the block
|
|
|
|
hash: BlockHeaderHash,
|
|
|
|
},
|
|
|
|
/// Get the block that is the tip of the current chain
|
2020-06-04 10:17:49 -07:00
|
|
|
GetTip,
|
2020-06-02 16:16:17 -07:00
|
|
|
}
|
|
|
|
|
2020-06-11 21:52:27 -07:00
|
|
|
#[derive(Debug, PartialEq)]
|
2020-06-15 14:41:26 -07:00
|
|
|
/// A state response
|
2020-06-02 16:16:17 -07:00
|
|
|
pub enum Response {
|
2020-06-15 14:41:26 -07:00
|
|
|
/// A response to a `AddBlock` request indicating a block was successfully
|
|
|
|
/// added to the state
|
|
|
|
Added {
|
|
|
|
/// The hash of the block that was added
|
|
|
|
hash: BlockHeaderHash,
|
|
|
|
},
|
|
|
|
/// The response to a `GetBlock` request by hash
|
|
|
|
Block {
|
|
|
|
/// The block that was requested
|
|
|
|
block: Arc<Block>,
|
|
|
|
},
|
|
|
|
/// The response to a `GetTip` request
|
|
|
|
Tip {
|
|
|
|
/// The hash of the block at the tip of the current chain
|
|
|
|
hash: BlockHeaderHash,
|
|
|
|
},
|
2020-06-02 16:16:17 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use color_eyre::Report;
|
|
|
|
use eyre::{bail, ensure, eyre};
|
2020-06-15 14:41:26 -07:00
|
|
|
use std::sync::Once;
|
2020-06-04 10:17:49 -07:00
|
|
|
use tower::Service;
|
2020-06-15 14:41:26 -07:00
|
|
|
use tracing_error::ErrorLayer;
|
|
|
|
use tracing_subscriber::prelude::*;
|
|
|
|
use tracing_subscriber::{fmt, EnvFilter};
|
2020-06-02 16:16:17 -07:00
|
|
|
use zebra_chain::serialization::ZcashDeserialize;
|
|
|
|
|
2020-06-15 14:41:26 -07:00
|
|
|
static LOGGER_INIT: Once = Once::new();
|
|
|
|
|
2020-06-04 10:17:49 -07:00
|
|
|
fn install_tracing() {
|
2020-06-15 14:41:26 -07:00
|
|
|
LOGGER_INIT.call_once(|| {
|
|
|
|
let fmt_layer = fmt::layer().with_target(false);
|
|
|
|
let filter_layer = EnvFilter::try_from_default_env()
|
|
|
|
.or_else(|_| EnvFilter::try_new("info"))
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
tracing_subscriber::registry()
|
|
|
|
.with(filter_layer)
|
|
|
|
.with(fmt_layer)
|
|
|
|
.with(ErrorLayer::default())
|
|
|
|
.init();
|
|
|
|
})
|
2020-06-04 10:17:49 -07:00
|
|
|
}
|
|
|
|
|
2020-06-02 16:16:17 -07:00
|
|
|
#[tokio::test]
|
2020-06-15 14:41:26 -07:00
|
|
|
async fn test_round_trip() -> Result<(), Report> {
|
|
|
|
install_tracing();
|
|
|
|
|
|
|
|
let service = in_memory::init();
|
|
|
|
round_trip(service).await?;
|
|
|
|
|
|
|
|
let mut config = crate::Config::default();
|
|
|
|
let tmp_dir = tempdir::TempDir::new("round_trip")?;
|
|
|
|
config.path = tmp_dir.path().to_owned();
|
|
|
|
let service = on_disk::init(config);
|
|
|
|
get_tip(service).await?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn round_trip<S>(mut service: S) -> Result<(), Report>
|
|
|
|
where
|
|
|
|
S: Service<
|
|
|
|
Request,
|
|
|
|
Error = Box<dyn std::error::Error + Send + Sync + 'static>,
|
|
|
|
Response = Response,
|
|
|
|
>,
|
|
|
|
{
|
2020-06-02 16:16:17 -07:00
|
|
|
let block: Arc<_> =
|
|
|
|
Block::zcash_deserialize(&zebra_test_vectors::BLOCK_MAINNET_415000_BYTES[..])?.into();
|
|
|
|
let hash = block.as_ref().into();
|
|
|
|
|
|
|
|
let response = service
|
|
|
|
.call(Request::AddBlock {
|
|
|
|
block: block.clone(),
|
|
|
|
})
|
|
|
|
.await
|
|
|
|
.map_err(|e| eyre!(e))?;
|
|
|
|
|
|
|
|
ensure!(
|
2020-06-11 21:52:27 -07:00
|
|
|
response == Response::Added { hash },
|
2020-06-02 16:16:17 -07:00
|
|
|
"unexpected response kind: {:?}",
|
|
|
|
response
|
|
|
|
);
|
|
|
|
|
|
|
|
let block_response = service
|
|
|
|
.call(Request::GetBlock { hash })
|
|
|
|
.await
|
|
|
|
.map_err(|e| eyre!(e))?;
|
|
|
|
|
|
|
|
match block_response {
|
|
|
|
Response::Block {
|
|
|
|
block: returned_block,
|
|
|
|
} => assert_eq!(block, returned_block),
|
|
|
|
_ => bail!("unexpected response kind: {:?}", block_response),
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-06-04 10:17:49 -07:00
|
|
|
|
|
|
|
#[tokio::test]
|
2020-06-15 14:41:26 -07:00
|
|
|
async fn test_get_tip() -> Result<(), Report> {
|
|
|
|
install_tracing();
|
|
|
|
|
|
|
|
let service = in_memory::init();
|
|
|
|
get_tip(service).await?;
|
|
|
|
|
|
|
|
let mut config = crate::Config::default();
|
|
|
|
let tmp_dir = tempdir::TempDir::new("get_tip")?;
|
|
|
|
config.path = tmp_dir.path().to_owned();
|
|
|
|
let service = on_disk::init(config);
|
|
|
|
get_tip(service).await?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-06-04 10:17:49 -07:00
|
|
|
#[spandoc::spandoc]
|
2020-06-15 14:41:26 -07:00
|
|
|
async fn get_tip<S>(mut service: S) -> Result<(), Report>
|
|
|
|
where
|
|
|
|
S: Service<
|
|
|
|
Request,
|
|
|
|
Error = Box<dyn std::error::Error + Send + Sync + 'static>,
|
|
|
|
Response = Response,
|
|
|
|
>,
|
|
|
|
{
|
2020-06-04 10:17:49 -07:00
|
|
|
install_tracing();
|
|
|
|
|
|
|
|
let block0: Arc<_> =
|
|
|
|
Block::zcash_deserialize(&zebra_test_vectors::BLOCK_MAINNET_GENESIS_BYTES[..])?.into();
|
|
|
|
let block1: Arc<_> =
|
|
|
|
Block::zcash_deserialize(&zebra_test_vectors::BLOCK_MAINNET_1_BYTES[..])?.into();
|
|
|
|
|
2020-06-11 21:52:27 -07:00
|
|
|
let block0_hash: BlockHeaderHash = block0.as_ref().into();
|
|
|
|
let block1_hash: BlockHeaderHash = block1.as_ref().into();
|
|
|
|
let expected_hash: BlockHeaderHash = block1_hash;
|
2020-06-04 10:17:49 -07:00
|
|
|
|
|
|
|
/// insert the higher block first
|
|
|
|
let response = service
|
|
|
|
.call(Request::AddBlock { block: block1 })
|
|
|
|
.await
|
|
|
|
.map_err(|e| eyre!(e))?;
|
|
|
|
|
|
|
|
ensure!(
|
2020-06-11 21:52:27 -07:00
|
|
|
response == Response::Added { hash: block1_hash },
|
2020-06-04 10:17:49 -07:00
|
|
|
"unexpected response kind: {:?}",
|
|
|
|
response
|
|
|
|
);
|
|
|
|
|
|
|
|
/// genesis block second
|
|
|
|
let response = service
|
|
|
|
.call(Request::AddBlock {
|
|
|
|
block: block0.clone(),
|
|
|
|
})
|
|
|
|
.await
|
|
|
|
.map_err(|e| eyre!(e))?;
|
|
|
|
|
|
|
|
ensure!(
|
2020-06-11 21:52:27 -07:00
|
|
|
response == Response::Added { hash: block0_hash },
|
2020-06-04 10:17:49 -07:00
|
|
|
"unexpected response kind: {:?}",
|
|
|
|
response
|
|
|
|
);
|
|
|
|
|
|
|
|
let block_response = service.call(Request::GetTip).await.map_err(|e| eyre!(e))?;
|
|
|
|
|
|
|
|
/// assert that the higher block is returned as the tip even tho it was least recently inserted
|
|
|
|
match block_response {
|
|
|
|
Response::Tip { hash } => assert_eq!(expected_hash, hash),
|
|
|
|
_ => bail!("unexpected response kind: {:?}", block_response),
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-06-02 16:16:17 -07:00
|
|
|
}
|