implement initial persistent state backend based on `sled` (#473)

This commit is contained in:
Jane Lusby 2020-06-15 14:41:26 -07:00 committed by GitHub
parent 5afea58fe2
commit bb0553fab6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 454 additions and 44 deletions

158
Cargo.lock generated
View File

@ -310,6 +310,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "crc32fast"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-epoch"
version = "0.8.2"
@ -354,7 +363,7 @@ checksum = "26778518a7f6cffa1d25a44b602b62b979bd88adb9e99ffec546998cf3404839"
dependencies = [
"byteorder",
"digest",
"rand_core",
"rand_core 0.5.1",
"subtle",
"zeroize",
]
@ -416,7 +425,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb6195ea92e78e243aef73daa954f7afa0f018cd5bad78c39b7f141244eb78b3"
dependencies = [
"curve25519-dalek",
"rand_core",
"rand_core 0.5.1",
"serde",
"sha2",
"thiserror",
@ -443,6 +452,22 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
[[package]]
name = "fs2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
dependencies = [
"libc",
"winapi 0.3.8",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "fuchsia-zircon"
version = "0.3.3"
@ -554,6 +579,15 @@ dependencies = [
"slab",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]]
name = "generational-arena"
version = "0.2.7"
@ -791,9 +825,9 @@ checksum = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
[[package]]
name = "lock_api"
version = "0.3.3"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b"
checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
dependencies = [
"scopeguard",
]
@ -932,7 +966,7 @@ dependencies = [
"metrics-observer-prometheus",
"metrics-observer-yaml",
"metrics-util",
"parking_lot",
"parking_lot 0.9.0",
"quanta",
]
@ -1075,10 +1109,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252"
dependencies = [
"lock_api",
"parking_lot_core",
"parking_lot_core 0.6.2",
"rustc_version",
]
[[package]]
name = "parking_lot"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
dependencies = [
"lock_api",
"parking_lot_core 0.7.2",
]
[[package]]
name = "parking_lot_core"
version = "0.6.2"
@ -1094,6 +1138,20 @@ dependencies = [
"winapi 0.3.8",
]
[[package]]
name = "parking_lot_core"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
dependencies = [
"cfg-if",
"cloudabi",
"libc",
"redox_syscall",
"smallvec 1.4.0",
"winapi 0.3.8",
]
[[package]]
name = "pin-project"
version = "0.4.22"
@ -1174,7 +1232,7 @@ dependencies = [
"lazy_static",
"num-traits",
"quick-error",
"rand",
"rand 0.7.3",
"rand_chacha",
"rand_xorshift",
"regex-syntax",
@ -1227,6 +1285,19 @@ dependencies = [
"proc-macro2 1.0.9",
]
[[package]]
name = "rand"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
dependencies = [
"fuchsia-cprng",
"libc",
"rand_core 0.3.1",
"rdrand",
"winapi 0.3.8",
]
[[package]]
name = "rand"
version = "0.7.3"
@ -1236,7 +1307,7 @@ dependencies = [
"getrandom",
"libc",
"rand_chacha",
"rand_core",
"rand_core 0.5.1",
"rand_hc",
]
@ -1247,9 +1318,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core",
"rand_core 0.5.1",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
version = "0.5.1"
@ -1265,7 +1351,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core",
"rand_core 0.5.1",
]
[[package]]
@ -1274,7 +1360,16 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8"
dependencies = [
"rand_core",
"rand_core 0.5.1",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
@ -1285,7 +1380,7 @@ checksum = "05af26cd84133b56adf15179b564320ace234fa926b7a8c5fa8e391ffb65359f"
dependencies = [
"blake2b_simd",
"jubjub",
"rand_core",
"rand_core 0.5.1",
"serde",
"thiserror",
]
@ -1536,6 +1631,22 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
[[package]]
name = "sled"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fb6824dde66ad33bf20c6e8476f5b82b871bc8bc3c129a10ea2f7dae5060fa3"
dependencies = [
"crc32fast",
"crossbeam-epoch",
"crossbeam-utils 0.7.2",
"fs2",
"fxhash",
"libc",
"log",
"parking_lot 0.10.2",
]
[[package]]
name = "smallvec"
version = "0.6.13"
@ -1622,6 +1733,16 @@ dependencies = [
"unicode-xid 0.2.0",
]
[[package]]
name = "tempdir"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
dependencies = [
"rand 0.4.6",
"remove_dir_all",
]
[[package]]
name = "tempfile"
version = "3.1.0"
@ -1630,7 +1751,7 @@ checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
dependencies = [
"cfg-if",
"libc",
"rand",
"rand 0.7.3",
"redox_syscall",
"remove_dir_all",
"winapi 0.3.8",
@ -2076,7 +2197,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "637ff90c9540fa3073bb577e65033069e4bae7c79d49d74aa3ffdf5342a53217"
dependencies = [
"curve25519-dalek",
"rand_core",
"rand_core 0.5.1",
"zeroize",
]
@ -2106,7 +2227,7 @@ dependencies = [
"lazy_static",
"proptest",
"proptest-derive",
"rand_core",
"rand_core 0.5.1",
"redjubjub",
"ripemd160",
"secp256k1",
@ -2154,7 +2275,7 @@ dependencies = [
"pin-project",
"proptest",
"proptest-derive",
"rand",
"rand 0.7.3",
"serde",
"thiserror",
"tokio",
@ -2183,7 +2304,10 @@ dependencies = [
"futures",
"hex",
"lazy_static",
"serde",
"sled",
"spandoc",
"tempdir",
"tokio",
"tower",
"tracing",
@ -2216,7 +2340,7 @@ dependencies = [
"metrics",
"metrics-runtime",
"once_cell",
"rand",
"rand 0.7.3",
"serde",
"thiserror",
"tokio",

View File

@ -14,6 +14,8 @@ eyre = "0.4.2"
futures = "0.3.5"
lazy_static = "1.4.0"
hex = "0.4.2"
sled = "0.31.0"
serde = { version = "1", features = ["serde_derive"] }
[dev-dependencies]
color-eyre = "0.3.4"
@ -25,3 +27,4 @@ tracing = "0.1.15"
tracing-futures = "0.2.4"
tracing-error = "0.1.2"
tracing-subscriber = "0.2.5"
tempdir = "0.3.7"

View File

@ -1,3 +1,8 @@
//! A basic implementation of the zebra-state service entirely in memory
//!
//! This service is provided as an independent implementation of the
//! zebra-state service to use in verifying the correctness of `on_disk`'s
//! `Service` implementation.
use super::{Request, Response};
use futures::prelude::*;
use std::{
@ -11,13 +16,13 @@ use tower::{buffer::Buffer, Service};
mod block_index;
#[derive(Default)]
struct ZebraState {
struct InMemoryState {
index: block_index::BlockIndex,
}
type Error = Box<dyn error::Error + Send + Sync + 'static>;
impl Service<Request> for ZebraState {
impl Service<Request> for InMemoryState {
type Response = Response;
type Error = Error;
type Future =
@ -59,6 +64,8 @@ impl Service<Request> for ZebraState {
}
}
/// Return's a type that implement's the `zebra_state::Service` entirely in
/// memory using `HashMaps`
pub fn init() -> impl Service<
Request,
Response = Response,
@ -67,5 +74,5 @@ pub fn init() -> impl Service<
> + Send
+ Clone
+ 'static {
Buffer::new(ZebraState::default(), 1)
Buffer::new(InMemoryState::default(), 1)
}

View File

@ -1,24 +1,84 @@
//! 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.
#![doc(html_logo_url = "https://www.zfnd.org/images/zebra-icon.png")]
#![doc(html_root_url = "https://doc.zebra.zfnd.org/zebra_state")]
#![warn(missing_docs)]
#![allow(clippy::try_err)]
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::sync::Arc;
use zebra_chain::block::{Block, BlockHeaderHash};
pub mod in_memory;
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"),
}
}
}
#[derive(Debug, PartialEq)]
/// A state request, used to manipulate the zebra-state on disk or in memory
pub enum Request {
// TODO(jlusby): deprecate in the future based on our validation story
AddBlock { block: Arc<Block> },
GetBlock { hash: BlockHeaderHash },
/// 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
GetTip,
}
#[derive(Debug, PartialEq)]
/// A state response
pub enum Response {
Added { hash: BlockHeaderHash },
Block { block: Arc<Block> },
Tip { hash: BlockHeaderHash },
/// 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,
},
}
#[cfg(test)]
@ -26,34 +86,58 @@ mod tests {
use super::*;
use color_eyre::Report;
use eyre::{bail, ensure, eyre};
use std::sync::Once;
use tower::Service;
use tracing_error::ErrorLayer;
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};
use zebra_chain::serialization::ZcashDeserialize;
static LOGGER_INIT: Once = Once::new();
fn install_tracing() {
use tracing_error::ErrorLayer;
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};
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();
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();
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.with(ErrorLayer::default())
.init();
})
}
#[tokio::test]
async fn round_trip() -> Result<(), Report> {
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,
>,
{
let block: Arc<_> =
Block::zcash_deserialize(&zebra_test_vectors::BLOCK_MAINNET_415000_BYTES[..])?.into();
let hash = block.as_ref().into();
let mut service = in_memory::init();
let response = service
.call(Request::AddBlock {
block: block.clone(),
@ -83,8 +167,30 @@ mod tests {
}
#[tokio::test]
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(())
}
#[spandoc::spandoc]
async fn get_tip() -> Result<(), Report> {
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,
>,
{
install_tracing();
let block0: Arc<_> =
@ -96,8 +202,6 @@ mod tests {
let block1_hash: BlockHeaderHash = block1.as_ref().into();
let expected_hash: BlockHeaderHash = block1_hash;
let mut service = in_memory::init();
/// insert the higher block first
let response = service
.call(Request::AddBlock { block: block1 })

172
zebra-state/src/on_disk.rs Normal file
View File

@ -0,0 +1,172 @@
//! The primary implementation of the `zebra_state::Service` built upon sled
use super::{Request, Response};
use crate::Config;
use futures::prelude::*;
use std::sync::Arc;
use std::{
error,
future::Future,
pin::Pin,
task::{Context, Poll},
};
use tower::{buffer::Buffer, Service};
use zebra_chain::serialization::{ZcashDeserialize, ZcashSerialize};
use zebra_chain::{
block::{Block, BlockHeaderHash},
types::BlockHeight,
};
#[derive(Clone)]
struct SledState {
storage: sled::Db,
}
impl SledState {
pub(crate) fn new(config: &Config) -> Self {
let config = config.sled_config();
Self {
storage: config.open().unwrap(),
}
}
pub(super) fn insert(
&mut self,
block: impl Into<Arc<Block>>,
) -> Result<BlockHeaderHash, Error> {
let block = block.into();
let hash: BlockHeaderHash = block.as_ref().into();
let height = block.coinbase_height().unwrap();
let by_height = self.storage.open_tree(b"by_height")?;
let by_hash = self.storage.open_tree(b"by_hash")?;
let mut bytes = Vec::new();
block.zcash_serialize(&mut bytes)?;
// TODO(jlusby): make this transactional
by_height.insert(&height.0.to_be_bytes(), bytes.as_slice())?;
by_hash.insert(&hash.0, bytes)?;
Ok(hash)
}
pub(super) fn get(&self, query: impl Into<BlockQuery>) -> Result<Option<Arc<Block>>, Error> {
let query = query.into();
let value = match query {
BlockQuery::ByHash(hash) => {
let by_hash = self.storage.open_tree(b"by_hash")?;
let key = &hash.0;
by_hash.get(key)?
}
BlockQuery::ByHeight(height) => {
let by_height = self.storage.open_tree(b"by_height")?;
let key = height.0.to_be_bytes();
by_height.get(key)?
}
};
if let Some(bytes) = value {
let bytes = bytes.as_ref();
let block = ZcashDeserialize::zcash_deserialize(bytes)?;
Ok(Some(block))
} else {
Ok(None)
}
}
pub(super) fn get_tip(&self) -> Result<Option<BlockHeaderHash>, Error> {
let tree = self.storage.open_tree(b"by_height")?;
let last_entry = tree.iter().values().next_back();
match last_entry {
Some(Ok(bytes)) => {
let block = Arc::<Block>::zcash_deserialize(bytes.as_ref())?;
Ok(Some(block.as_ref().into()))
}
Some(Err(e)) => Err(e)?,
None => Ok(None),
}
}
}
impl Default for SledState {
fn default() -> Self {
let config = crate::Config::default();
Self::new(&config)
}
}
impl Service<Request> for SledState {
type Response = Response;
type Error = Error;
type Future =
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request) -> Self::Future {
match req {
Request::AddBlock { block } => {
let mut storage = self.clone();
async move { storage.insert(block).map(|hash| Response::Added { hash }) }.boxed()
}
Request::GetBlock { hash } => {
let storage = self.clone();
async move {
storage
.get(hash)?
.map(|block| Response::Block { block })
.ok_or_else(|| "block could not be found".into())
}
.boxed()
}
Request::GetTip => {
let storage = self.clone();
async move {
storage
.get_tip()?
.map(|hash| Response::Tip { hash })
.ok_or_else(|| "zebra-state contains no blocks".into())
}
.boxed()
}
}
}
}
pub(super) enum BlockQuery {
ByHash(BlockHeaderHash),
ByHeight(BlockHeight),
}
impl From<BlockHeaderHash> for BlockQuery {
fn from(hash: BlockHeaderHash) -> Self {
Self::ByHash(hash)
}
}
impl From<BlockHeight> for BlockQuery {
fn from(height: BlockHeight) -> Self {
Self::ByHeight(height)
}
}
/// Return's a type that implement's the `zebra_state::Service` using `sled`
pub fn init(
config: Config,
) -> impl Service<
Request,
Response = Response,
Error = Error,
Future = impl Future<Output = Result<Response, Error>>,
> + Send
+ Clone
+ 'static {
Buffer::new(SledState::new(&config), 1)
}
type Error = Box<dyn error::Error + Send + Sync + 'static>;