Make the CheckpointVerifier handle partial restarts (#736)

Also put generic bounds on the BlockVerifier struct,
so we get better compilation errors.
This commit is contained in:
teor 2020-07-24 11:47:48 +10:00 committed by GitHub
parent 80597087b3
commit 2acfcf3a90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 271 additions and 110 deletions

View File

@ -24,7 +24,14 @@ use tower::{buffer::Buffer, Service};
use zebra_chain::block::{Block, BlockHeaderHash}; use zebra_chain::block::{Block, BlockHeaderHash};
struct BlockVerifier<S> { struct BlockVerifier<S>
where
S: Service<zebra_state::Request, Response = zebra_state::Response, Error = Error>
+ Send
+ Clone
+ 'static,
S::Future: Send + 'static,
{
/// The underlying `ZebraState`, possibly wrapped in other services. /// The underlying `ZebraState`, possibly wrapped in other services.
// TODO: contextual verification // TODO: contextual verification
#[allow(dead_code)] #[allow(dead_code)]

View File

@ -160,9 +160,10 @@ where
} }
} }
/// Return a chain verification service, using `network` and the provided state /// Return a chain verification service, using `network` and `state_service`.
/// service. The network is used to create a block verifier and checkpoint ///
/// verifier. /// Gets the initial tip from the state service, and uses it to create a block
/// verifier and checkpoint verifier.
/// ///
/// This function should only be called once for a particular state service. If /// This function should only be called once for a particular state service. If
/// you need shared block or checkpoint verfiers, create them yourself, and pass /// you need shared block or checkpoint verfiers, create them yourself, and pass
@ -171,7 +172,7 @@ where
// TODO: revise this interface when we generate our own blocks, or validate // TODO: revise this interface when we generate our own blocks, or validate
// mempool transactions. We might want to share the BlockVerifier, and we // mempool transactions. We might want to share the BlockVerifier, and we
// might not want to add generated blocks to the state. // might not want to add generated blocks to the state.
pub fn init<S>( pub async fn init<S>(
network: Network, network: Network,
state_service: S, state_service: S,
) -> impl Service< ) -> impl Service<
@ -189,10 +190,20 @@ where
+ 'static, + 'static,
S::Future: Send + 'static, S::Future: Send + 'static,
{ {
tracing::debug!(?network, "initialising ChainVerifier from network"); let initial_tip = zebra_state::initial_tip(state_service.clone())
.await
.expect("State service poll_ready is Ok");
let height = initial_tip.clone().map(|b| b.coinbase_height()).flatten();
let hash = initial_tip.clone().map(|b| b.hash());
tracing::debug!(
?network,
?height,
?hash,
"initialising ChainVerifier with network and initial tip"
);
let block_verifier = crate::block::init(state_service.clone()); let block_verifier = crate::block::init(state_service.clone());
let checkpoint_verifier = CheckpointVerifier::new(network); let checkpoint_verifier = CheckpointVerifier::new(network, initial_tip);
init_from_verifiers(block_verifier, checkpoint_verifier, state_service) init_from_verifiers(block_verifier, checkpoint_verifier, state_service)
} }
@ -214,7 +225,6 @@ where
/// bugs. /// bugs.
pub fn init_from_verifiers<BV, S>( pub fn init_from_verifiers<BV, S>(
block_verifier: BV, block_verifier: BV,
// We use an explcit type, so callers can't accidentally swap the verifiers
checkpoint_verifier: CheckpointVerifier, checkpoint_verifier: CheckpointVerifier,
state_service: S, state_service: S,
) -> impl Service< ) -> impl Service<

View File

@ -63,7 +63,7 @@ fn verifiers_from_checkpoint_list(
let state_service = zebra_state::in_memory::init(); let state_service = zebra_state::in_memory::init();
let block_verifier = crate::block::init(state_service.clone()); let block_verifier = crate::block::init(state_service.clone());
let checkpoint_verifier = let checkpoint_verifier =
crate::checkpoint::CheckpointVerifier::from_checkpoint_list(checkpoint_list); crate::checkpoint::CheckpointVerifier::from_checkpoint_list(checkpoint_list, None);
let chain_verifier = let chain_verifier =
super::init_from_verifiers(block_verifier, checkpoint_verifier, state_service.clone()); super::init_from_verifiers(block_verifier, checkpoint_verifier, state_service.clone());
@ -167,7 +167,7 @@ async fn verify_checkpoint() -> Result<(), Report> {
// Test that the chain::init function works. Most of the other tests use // Test that the chain::init function works. Most of the other tests use
// init_from_verifiers. // init_from_verifiers.
let mut chain_verifier = super::init(Mainnet, zebra_state::in_memory::init()); let mut chain_verifier = super::init(Mainnet, zebra_state::in_memory::init()).await;
/// SPANDOC: Make sure the verifier service is ready /// SPANDOC: Make sure the verifier service is ready
let ready_verifier_service = chain_verifier.ready_and().await.map_err(|e| eyre!(e))?; let ready_verifier_service = chain_verifier.ready_and().await.map_err(|e| eyre!(e))?;

View File

@ -88,6 +88,9 @@ pub struct CheckpointVerifier {
/// The checkpoint list for this verifier. /// The checkpoint list for this verifier.
checkpoint_list: CheckpointList, checkpoint_list: CheckpointList,
/// The hash of the initial tip, if any.
initial_tip_hash: Option<BlockHeaderHash>,
// Queued Blocks // Queued Blocks
// //
/// A queue of unverified blocks. /// A queue of unverified blocks.
@ -111,20 +114,28 @@ pub struct CheckpointVerifier {
/// Contains non-service utility functions for CheckpointVerifiers. /// Contains non-service utility functions for CheckpointVerifiers.
impl CheckpointVerifier { impl CheckpointVerifier {
/// Return a checkpoint verification service for `network`, using the /// Return a checkpoint verification service for `network`, using the
/// hard-coded checkpoint list. If `initial_tip` is Some(_), the
/// verifier starts at that initial tip, which does not have to be in the
/// hard-coded checkpoint list. /// hard-coded checkpoint list.
/// ///
/// This function should be called only once for a particular network, rather /// This function should be called only once for a particular network, rather
/// than constructing multiple verification services for the same network. To /// than constructing multiple verification services for the same network. To
/// Clone a CheckpointVerifier, you might need to wrap it in a /// clone a CheckpointVerifier, you might need to wrap it in a
/// `tower::Buffer` service. /// `tower::Buffer` service.
pub fn new(network: Network) -> Self { pub fn new(network: Network, initial_tip: Option<Arc<Block>>) -> Self {
let checkpoint_list = CheckpointList::new(network); let checkpoint_list = CheckpointList::new(network);
let max_height = checkpoint_list.max_height(); let max_height = checkpoint_list.max_height();
tracing::info!(?max_height, ?network, "initialising CheckpointVerifier"); let initial_height = initial_tip.clone().map(|b| b.coinbase_height()).flatten();
Self::from_checkpoint_list(checkpoint_list) tracing::info!(
?max_height,
?network,
?initial_height,
"initialising CheckpointVerifier"
);
Self::from_checkpoint_list(checkpoint_list, initial_tip)
} }
/// Return a checkpoint verification service using `list`. /// Return a checkpoint verification service using `list` and `initial_tip`.
/// ///
/// Assumes that the provided genesis checkpoint is correct. /// Assumes that the provided genesis checkpoint is correct.
/// ///
@ -136,23 +147,48 @@ impl CheckpointVerifier {
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn from_list( pub(crate) fn from_list(
list: impl IntoIterator<Item = (BlockHeight, BlockHeaderHash)>, list: impl IntoIterator<Item = (BlockHeight, BlockHeaderHash)>,
initial_tip: Option<Arc<Block>>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
Ok(Self::from_checkpoint_list(CheckpointList::from_list(list)?)) Ok(Self::from_checkpoint_list(
CheckpointList::from_list(list)?,
initial_tip,
))
} }
/// Return a checkpoint verification service using `checkpoint_list`. /// Return a checkpoint verification service using `checkpoint_list` and
/// `initial_tip`.
/// ///
/// Callers should prefer `CheckpointVerifier::new`, which uses the /// Callers should prefer `CheckpointVerifier::new`, which uses the
/// hard-coded checkpoint lists. See `CheckpointVerifier::new` and /// hard-coded checkpoint lists. See `CheckpointVerifier::new` and
/// `CheckpointList::from_list` for more details. /// `CheckpointList::from_list` for more details.
pub(crate) fn from_checkpoint_list(checkpoint_list: CheckpointList) -> Self { pub(crate) fn from_checkpoint_list(
checkpoint_list: CheckpointList,
initial_tip: Option<Arc<Block>>,
) -> Self {
// All the initialisers should call this function, so we only have to // All the initialisers should call this function, so we only have to
// change fields or default values in one place. // change fields or default values in one place.
let (initial_tip_hash, verifier_progress) = match initial_tip {
Some(initial_tip) => {
let initial_height = initial_tip
.coinbase_height()
.expect("Bad initial tip: must have coinbase height");
if initial_height >= checkpoint_list.max_height() {
(None, Progress::FinalCheckpoint)
} else {
(
Some(initial_tip.hash()),
Progress::InitialTip(initial_height),
)
}
}
// We start by verifying the genesis block, by itself
None => (None, Progress::BeforeGenesis),
};
CheckpointVerifier { CheckpointVerifier {
checkpoint_list, checkpoint_list,
initial_tip_hash,
queued: BTreeMap::new(), queued: BTreeMap::new(),
// We start by verifying the genesis block, by itself verifier_progress,
verifier_progress: Progress::BeforeGenesis,
} }
} }
@ -162,7 +198,8 @@ impl CheckpointVerifier {
/// Return the current verifier's progress. /// Return the current verifier's progress.
/// ///
/// If verification has not started yet, returns `BeforeGenesis`. /// If verification has not started yet, returns `BeforeGenesis`,
/// or `InitialTip(height)` if there were cached verified blocks.
/// ///
/// If verification is ongoing, returns `PreviousCheckpoint(height)`. /// If verification is ongoing, returns `PreviousCheckpoint(height)`.
/// `height` increases as checkpoints are verified. /// `height` increases as checkpoints are verified.
@ -178,7 +215,7 @@ impl CheckpointVerifier {
fn current_start_bound(&self) -> Option<Bound<BlockHeight>> { fn current_start_bound(&self) -> Option<Bound<BlockHeight>> {
match self.previous_checkpoint_height() { match self.previous_checkpoint_height() {
BeforeGenesis => Some(Unbounded), BeforeGenesis => Some(Unbounded),
PreviousCheckpoint(height) => Some(Excluded(height)), InitialTip(height) | PreviousCheckpoint(height) => Some(Excluded(height)),
FinalCheckpoint => None, FinalCheckpoint => None,
} }
} }
@ -204,7 +241,7 @@ impl CheckpointVerifier {
return WaitingForBlocks; return WaitingForBlocks;
} }
BeforeGenesis => BlockHeight(0), BeforeGenesis => BlockHeight(0),
PreviousCheckpoint(height) => height, InitialTip(height) | PreviousCheckpoint(height) => height,
FinalCheckpoint => return FinishedVerifying, FinalCheckpoint => return FinishedVerifying,
}; };
@ -256,6 +293,10 @@ impl CheckpointVerifier {
fn previous_checkpoint_hash(&self) -> Progress<BlockHeaderHash> { fn previous_checkpoint_hash(&self) -> Progress<BlockHeaderHash> {
match self.previous_checkpoint_height() { match self.previous_checkpoint_height() {
BeforeGenesis => BeforeGenesis, BeforeGenesis => BeforeGenesis,
InitialTip(_) => self
.initial_tip_hash
.map(InitialTip)
.expect("initial tip height must have an initial tip hash"),
PreviousCheckpoint(height) => self PreviousCheckpoint(height) => self
.checkpoint_list .checkpoint_list
.hash(height) .hash(height)
@ -282,10 +323,12 @@ impl CheckpointVerifier {
// Any height is valid // Any height is valid
BeforeGenesis => {} BeforeGenesis => {}
// Greater heights are valid // Greater heights are valid
PreviousCheckpoint(previous_height) if (height <= previous_height) => { InitialTip(previous_height) | PreviousCheckpoint(previous_height)
if (height <= previous_height) =>
{
Err("block height has already been verified")? Err("block height has already been verified")?
} }
PreviousCheckpoint(_) => {} InitialTip(_) | PreviousCheckpoint(_) => {}
// We're finished, so no checkpoint height is valid // We're finished, so no checkpoint height is valid
FinalCheckpoint => Err("verification has finished")?, FinalCheckpoint => Err("verification has finished")?,
}; };
@ -311,6 +354,8 @@ impl CheckpointVerifier {
self.verifier_progress = FinalCheckpoint; self.verifier_progress = FinalCheckpoint;
} else if self.checkpoint_list.contains(verified_height) { } else if self.checkpoint_list.contains(verified_height) {
self.verifier_progress = PreviousCheckpoint(verified_height); self.verifier_progress = PreviousCheckpoint(verified_height);
// We're done with the initial tip hash now
self.initial_tip_hash = None;
} }
} }
@ -473,7 +518,7 @@ impl CheckpointVerifier {
// like other blocks, the genesis parent hash is set by the // like other blocks, the genesis parent hash is set by the
// consensus parameters. // consensus parameters.
BeforeGenesis => parameters::GENESIS_PREVIOUS_BLOCK_HASH, BeforeGenesis => parameters::GENESIS_PREVIOUS_BLOCK_HASH,
PreviousCheckpoint(hash) => hash, InitialTip(hash) | PreviousCheckpoint(hash) => hash,
FinalCheckpoint => return, FinalCheckpoint => return,
}; };
// Return early if we're still waiting for more blocks // Return early if we're still waiting for more blocks

View File

@ -45,7 +45,7 @@ async fn single_item_checkpoint_list() -> Result<(), Report> {
.collect(); .collect();
let mut checkpoint_verifier = let mut checkpoint_verifier =
CheckpointVerifier::from_list(genesis_checkpoint_list).map_err(|e| eyre!(e))?; CheckpointVerifier::from_list(genesis_checkpoint_list, None).map_err(|e| eyre!(e))?;
assert_eq!( assert_eq!(
checkpoint_verifier.previous_checkpoint_height(), checkpoint_verifier.previous_checkpoint_height(),
@ -125,7 +125,7 @@ async fn multi_item_checkpoint_list() -> Result<(), Report> {
.collect(); .collect();
let mut checkpoint_verifier = let mut checkpoint_verifier =
CheckpointVerifier::from_list(checkpoint_list).map_err(|e| eyre!(e))?; CheckpointVerifier::from_list(checkpoint_list, None).map_err(|e| eyre!(e))?;
assert_eq!( assert_eq!(
checkpoint_verifier.previous_checkpoint_height(), checkpoint_verifier.previous_checkpoint_height(),
@ -206,11 +206,16 @@ async fn multi_item_checkpoint_list() -> Result<(), Report> {
#[tokio::test] #[tokio::test]
async fn continuous_blockchain_test() -> Result<(), Report> { async fn continuous_blockchain_test() -> Result<(), Report> {
continuous_blockchain().await continuous_blockchain(None).await?;
for height in 0..=10 {
continuous_blockchain(Some(BlockHeight(height))).await?;
}
Ok(())
} }
/// Test a continuous blockchain, restarting verification at `restart_height`.
#[spandoc::spandoc] #[spandoc::spandoc]
async fn continuous_blockchain() -> Result<(), Report> { async fn continuous_blockchain(restart_height: Option<BlockHeight>) -> Result<(), Report> {
zebra_test::init(); zebra_test::init();
// A continuous blockchain // A continuous blockchain
@ -238,63 +243,32 @@ async fn continuous_blockchain() -> Result<(), Report> {
for b in &[ for b in &[
&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..], &zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..],
&zebra_test::vectors::BLOCK_MAINNET_5_BYTES[..], &zebra_test::vectors::BLOCK_MAINNET_5_BYTES[..],
&zebra_test::vectors::BLOCK_MAINNET_10_BYTES[..], &zebra_test::vectors::BLOCK_MAINNET_9_BYTES[..],
] { ] {
let block = Arc::<Block>::zcash_deserialize(*b)?; let block = Arc::<Block>::zcash_deserialize(*b)?;
let hash: BlockHeaderHash = block.as_ref().into(); let hash: BlockHeaderHash = block.as_ref().into();
checkpoints.push((block.clone(), block.coinbase_height().unwrap(), hash)); checkpoints.push((block.clone(), block.coinbase_height().unwrap(), hash));
} }
// The checkpoint list will contain only block 0, 5 and 10 // The checkpoint list will contain only block 0, 5 and 9
let checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> = checkpoints let checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> = checkpoints
.iter() .iter()
.map(|(_block, height, hash)| (*height, *hash)) .map(|(_block, height, hash)| (*height, *hash))
.collect(); .collect();
let mut checkpoint_verifier = /// SPANDOC: Verify blocks, restarting at {?restart_height}
CheckpointVerifier::from_list(checkpoint_list).map_err(|e| eyre!(e))?; {
let initial_tip = restart_height
.map(|BlockHeight(height)| &blockchain[height as usize].0)
.cloned();
let mut checkpoint_verifier =
CheckpointVerifier::from_list(checkpoint_list, initial_tip).map_err(|e| eyre!(e))?;
// Setup checks // Setup checks
assert_eq!( if restart_height
checkpoint_verifier.previous_checkpoint_height(), .map(|h| h >= checkpoint_verifier.checkpoint_list.max_height())
BeforeGenesis .unwrap_or(false)
); {
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
WaitingForBlocks
);
assert_eq!(
checkpoint_verifier.checkpoint_list.max_height(),
BlockHeight(10)
);
let mut handles = FuturesUnordered::new();
// Now verify each block
for (block, height, _hash) in blockchain {
/// SPANDOC: Make sure the verifier service is ready
let ready_verifier_service = checkpoint_verifier
.ready_and()
.map_err(|e| eyre!(e))
.await?;
/// SPANDOC: Set up the future for block {?height}
let verify_future = timeout(
Duration::from_secs(VERIFY_TIMEOUT_SECONDS),
ready_verifier_service.call(block.clone()),
);
/// SPANDOC: spawn verification future in the background
let handle = tokio::spawn(verify_future.in_current_span());
handles.push(handle);
// Execution checks
if height < checkpoint_verifier.checkpoint_list.max_height() {
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
WaitingForBlocks
);
} else {
assert_eq!( assert_eq!(
checkpoint_verifier.previous_checkpoint_height(), checkpoint_verifier.previous_checkpoint_height(),
FinalCheckpoint FinalCheckpoint
@ -303,26 +277,86 @@ async fn continuous_blockchain() -> Result<(), Report> {
checkpoint_verifier.target_checkpoint_height(), checkpoint_verifier.target_checkpoint_height(),
FinishedVerifying FinishedVerifying
); );
} else {
assert_eq!(
checkpoint_verifier.previous_checkpoint_height(),
restart_height.map(InitialTip).unwrap_or(BeforeGenesis)
);
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
WaitingForBlocks
);
} }
} assert_eq!(
checkpoint_verifier.checkpoint_list.max_height(),
BlockHeight(9)
);
while let Some(result) = handles.next().await { let mut handles = FuturesUnordered::new();
result??.map_err(|e| eyre!(e))?;
}
// Final checks // Now verify each block
assert_eq!( for (block, height, _hash) in blockchain {
checkpoint_verifier.previous_checkpoint_height(), if let Some(restart_height) = restart_height {
FinalCheckpoint if height <= restart_height {
); continue;
assert_eq!( }
checkpoint_verifier.target_checkpoint_height(), }
FinishedVerifying if height > checkpoint_verifier.checkpoint_list.max_height() {
); break;
assert_eq!( }
checkpoint_verifier.checkpoint_list.max_height(),
BlockHeight(10) /// SPANDOC: Make sure the verifier service is ready
); let ready_verifier_service = checkpoint_verifier
.ready_and()
.map_err(|e| eyre!(e))
.await?;
/// SPANDOC: Set up the future for block {?height}
let verify_future = timeout(
Duration::from_secs(VERIFY_TIMEOUT_SECONDS),
ready_verifier_service.call(block.clone()),
);
/// SPANDOC: spawn verification future in the background
let handle = tokio::spawn(verify_future.in_current_span());
handles.push(handle);
// Execution checks
if height < checkpoint_verifier.checkpoint_list.max_height() {
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
WaitingForBlocks
);
} else {
assert_eq!(
checkpoint_verifier.previous_checkpoint_height(),
FinalCheckpoint
);
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
FinishedVerifying
);
}
}
while let Some(result) = handles.next().await {
result??.map_err(|e| eyre!(e))?;
}
// Final checks
assert_eq!(
checkpoint_verifier.previous_checkpoint_height(),
FinalCheckpoint
);
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
FinishedVerifying
);
assert_eq!(
checkpoint_verifier.checkpoint_list.max_height(),
BlockHeight(9)
);
}
Ok(()) Ok(())
} }
@ -349,7 +383,7 @@ async fn block_higher_than_max_checkpoint_fail() -> Result<(), Report> {
.collect(); .collect();
let mut checkpoint_verifier = let mut checkpoint_verifier =
CheckpointVerifier::from_list(genesis_checkpoint_list).map_err(|e| eyre!(e))?; CheckpointVerifier::from_list(genesis_checkpoint_list, None).map_err(|e| eyre!(e))?;
assert_eq!( assert_eq!(
checkpoint_verifier.previous_checkpoint_height(), checkpoint_verifier.previous_checkpoint_height(),
@ -424,7 +458,7 @@ async fn wrong_checkpoint_hash_fail() -> Result<(), Report> {
.collect(); .collect();
let mut checkpoint_verifier = let mut checkpoint_verifier =
CheckpointVerifier::from_list(genesis_checkpoint_list).map_err(|e| eyre!(e))?; CheckpointVerifier::from_list(genesis_checkpoint_list, None).map_err(|e| eyre!(e))?;
assert_eq!( assert_eq!(
checkpoint_verifier.previous_checkpoint_height(), checkpoint_verifier.previous_checkpoint_height(),
@ -604,7 +638,7 @@ async fn checkpoint_drop_cancel() -> Result<(), Report> {
.collect(); .collect();
let mut checkpoint_verifier = let mut checkpoint_verifier =
CheckpointVerifier::from_list(checkpoint_list).map_err(|e| eyre!(e))?; CheckpointVerifier::from_list(checkpoint_list, None).map_err(|e| eyre!(e))?;
assert_eq!( assert_eq!(
checkpoint_verifier.previous_checkpoint_height(), checkpoint_verifier.previous_checkpoint_height(),
@ -688,7 +722,7 @@ async fn hard_coded_mainnet() -> Result<(), Report> {
let hash0: BlockHeaderHash = block0.as_ref().into(); let hash0: BlockHeaderHash = block0.as_ref().into();
// Use the hard-coded checkpoint list // Use the hard-coded checkpoint list
let mut checkpoint_verifier = CheckpointVerifier::new(Network::Mainnet); let mut checkpoint_verifier = CheckpointVerifier::new(Network::Mainnet, None);
assert_eq!( assert_eq!(
checkpoint_verifier.previous_checkpoint_height(), checkpoint_verifier.previous_checkpoint_height(),

View File

@ -12,8 +12,18 @@ use Target::*;
pub enum Progress<HeightOrHash> { pub enum Progress<HeightOrHash> {
/// We have not verified any blocks yet. /// We have not verified any blocks yet.
BeforeGenesis, BeforeGenesis,
/// We have verified up to and including this initial tip.
///
/// Initial tips might not be one of our hard-coded checkpoints, because we
/// might have:
/// - changed the checkpoint spacing, or
/// - added new checkpoints above the initial tip.
InitialTip(HeightOrHash),
/// We have verified up to and including this checkpoint. /// We have verified up to and including this checkpoint.
PreviousCheckpoint(HeightOrHash), PreviousCheckpoint(HeightOrHash),
/// We have finished verifying. /// We have finished verifying.
/// ///
/// The final checkpoint is not included in this variant. The verifier has /// The final checkpoint is not included in this variant. The verifier has
@ -33,7 +43,10 @@ impl Ord for Progress<BlockHeight> {
(_, BeforeGenesis) => Ordering::Greater, (_, BeforeGenesis) => Ordering::Greater,
(FinalCheckpoint, _) => Ordering::Greater, (FinalCheckpoint, _) => Ordering::Greater,
(_, FinalCheckpoint) => Ordering::Less, (_, FinalCheckpoint) => Ordering::Less,
(PreviousCheckpoint(self_height), PreviousCheckpoint(other_height)) => { (InitialTip(self_height), InitialTip(other_height))
| (InitialTip(self_height), PreviousCheckpoint(other_height))
| (PreviousCheckpoint(self_height), InitialTip(other_height))
| (PreviousCheckpoint(self_height), PreviousCheckpoint(other_height)) => {
self_height.cmp(other_height) self_height.cmp(other_height)
} }
} }

View File

@ -9,20 +9,23 @@ edition = "2018"
[dependencies] [dependencies]
zebra-chain = { path = "../zebra-chain" } zebra-chain = { path = "../zebra-chain" }
tower = "0.3.1"
futures = "0.3.5" color-eyre = "0.5"
lazy_static = "1.4.0"
hex = "0.4.2"
sled = "0.34.0"
serde = { version = "1", features = ["serde_derive"] }
dirs = "3.0.1" dirs = "3.0.1"
hex = "0.4.2"
lazy_static = "1.4.0"
serde = { version = "1", features = ["serde_derive"] }
sled = "0.34.0"
futures = "0.3.5"
tower = "0.3.1"
tracing = "0.1" tracing = "0.1"
tracing-futures = "0.2" tracing-futures = "0.2"
[dev-dependencies] [dev-dependencies]
tokio = { version = "0.2.22", features = ["full"] }
zebra-test = { path = "../zebra-test/" } zebra-test = { path = "../zebra-test/" }
once_cell = "1.4"
spandoc = "0.2" spandoc = "0.2"
tempdir = "0.3.7" tempdir = "0.3.7"
color-eyre = "0.5" tokio = { version = "0.2.22", features = ["full"] }
once_cell = "1.4"

View File

@ -14,9 +14,12 @@
#![doc(html_root_url = "https://doc.zebra.zfnd.org/zebra_state")] #![doc(html_root_url = "https://doc.zebra.zfnd.org/zebra_state")]
#![warn(missing_docs)] #![warn(missing_docs)]
#![allow(clippy::try_err)] #![allow(clippy::try_err)]
use color_eyre::eyre::{eyre, Report};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::PathBuf; use std::path::PathBuf;
use std::{iter, sync::Arc}; use std::{error, iter, sync::Arc};
use tower::{Service, ServiceExt};
use zebra_chain::{ use zebra_chain::{
block::{Block, BlockHeaderHash}, block::{Block, BlockHeaderHash},
types::BlockHeight, types::BlockHeight,
@ -133,6 +136,51 @@ fn block_locator_heights(tip_height: BlockHeight) -> impl Iterator<Item = BlockH
.chain(iter::once(BlockHeight(0))) .chain(iter::once(BlockHeight(0)))
} }
/// The error type for the State Service.
// TODO(jlusby): Error = Report ?
type Error = Box<dyn error::Error + Send + Sync + 'static>;
/// Get the tip block, using `state`.
///
/// If there is no tip, returns `Ok(None)`.
/// Returns an error if `state.poll_ready` errors.
pub async fn initial_tip<S>(state: S) -> Result<Option<Arc<Block>>, Report>
where
S: Service<Request, Response = Response, Error = Error> + Send + Clone + 'static,
S::Future: Send + 'static,
{
let initial_tip_hash = state
.clone()
.ready_and()
.await
.map_err(|e| eyre!(e))?
.call(Request::GetTip)
.await
.map(|response| match response {
Response::Tip { hash } => hash,
_ => unreachable!("GetTip request can only result in Response::Tip"),
})
.ok();
let initial_tip_block = match initial_tip_hash {
Some(hash) => state
.clone()
.ready_and()
.await
.map_err(|e| eyre!(e))?
.call(Request::GetBlock { hash })
.await
.map(|response| match response {
Response::Block { block } => block,
_ => unreachable!("GetBlock request can only result in Response::Block"),
})
.ok(),
None => None,
};
Ok(initial_tip_block)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -40,6 +40,10 @@ impl StartCmd {
async fn start(&self) -> Result<(), Report> { async fn start(&self) -> Result<(), Report> {
info!(?self, "starting to connect to the network"); info!(?self, "starting to connect to the network");
let config = app_config();
let state = zebra_state::on_disk::init(config.state.clone());
let verifier = zebra_consensus::chain::init(config.network.network, state.clone()).await;
// The service that our node uses to respond to requests by peers // The service that our node uses to respond to requests by peers
let node = Buffer::new( let node = Buffer::new(
service_fn(|req| async move { service_fn(|req| async move {
@ -48,10 +52,7 @@ impl StartCmd {
}), }),
1, 1,
); );
let config = app_config();
let state = zebra_state::on_disk::init(config.state.clone());
let (peer_set, _address_book) = zebra_network::init(config.network.clone(), node).await; let (peer_set, _address_book) = zebra_network::init(config.network.clone(), node).await;
let verifier = zebra_consensus::chain::init(config.network.network, state.clone());
let mut syncer = sync::Syncer::new(config.network.network, peer_set, state, verifier); let mut syncer = sync::Syncer::new(config.network.network, peer_set, state, verifier);