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:
parent
80597087b3
commit
2acfcf3a90
|
@ -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)]
|
||||||
|
|
|
@ -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<
|
||||||
|
|
|
@ -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))?;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue