feature: Check the previous block height in BlockVerifier
This is a temporary busy-waiting fix.
This commit is contained in:
parent
2b1e7162b1
commit
1e787aecb9
|
@ -7,22 +7,23 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.13"
|
chrono = "0.4.13"
|
||||||
futures = "0.3.5"
|
color-eyre = "0.5"
|
||||||
futures-util = "0.3.5"
|
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
redjubjub = "0.2"
|
redjubjub = "0.2"
|
||||||
|
|
||||||
|
metrics = "0.12"
|
||||||
|
futures = "0.3.5"
|
||||||
|
futures-util = "0.3.5"
|
||||||
tokio = { version = "0.2.22", features = ["time", "sync", "stream", "tracing"] }
|
tokio = { version = "0.2.22", features = ["time", "sync", "stream", "tracing"] }
|
||||||
tower = "0.3"
|
tower = "0.3"
|
||||||
tracing = "0.1.17"
|
tracing = "0.1.17"
|
||||||
tracing-futures = "0.2.4"
|
tracing-futures = "0.2.4"
|
||||||
metrics = "0.12"
|
|
||||||
|
|
||||||
tower-batch = { path = "../tower-batch/" }
|
tower-batch = { path = "../tower-batch/" }
|
||||||
zebra-chain = { path = "../zebra-chain" }
|
zebra-chain = { path = "../zebra-chain" }
|
||||||
zebra-state = { path = "../zebra-state" }
|
zebra-state = { path = "../zebra-state" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
color-eyre = "0.5"
|
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
spandoc = "0.2"
|
spandoc = "0.2"
|
||||||
tokio = { version = "0.2", features = ["full"] }
|
tokio = { version = "0.2", features = ["full"] }
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//! Verification occurs in multiple stages:
|
//! Verification occurs in multiple stages:
|
||||||
//! - getting blocks (disk- or network-bound)
|
//! - getting blocks (disk- or network-bound)
|
||||||
//! - context-free verification of signatures, proofs, and scripts (CPU-bound)
|
//! - context-free verification of signatures, proofs, and scripts (CPU-bound)
|
||||||
//! - context-dependent verification of the chain state (awaits a verified parent block)
|
//! - context-dependent verification of the chain state (depends on previous blocks)
|
||||||
//!
|
//!
|
||||||
//! Verification is provided via a `tower::Service`, to support backpressure and batch
|
//! Verification is provided via a `tower::Service`, to support backpressure and batch
|
||||||
//! verification.
|
//! verification.
|
||||||
|
@ -12,6 +12,7 @@
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
use color_eyre::eyre::{eyre, Report};
|
||||||
use futures_util::FutureExt;
|
use futures_util::FutureExt;
|
||||||
use std::{
|
use std::{
|
||||||
error,
|
error,
|
||||||
|
@ -19,10 +20,13 @@ use std::{
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
use tower::{buffer::Buffer, Service};
|
use tokio::time;
|
||||||
|
use tower::{buffer::Buffer, Service, ServiceExt};
|
||||||
|
|
||||||
use zebra_chain::block::{Block, BlockHeaderHash};
|
use zebra_chain::block::{Block, BlockHeaderHash};
|
||||||
|
use zebra_chain::types::BlockHeight;
|
||||||
|
|
||||||
struct BlockVerifier<S>
|
struct BlockVerifier<S>
|
||||||
where
|
where
|
||||||
|
@ -67,8 +71,9 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, block: Arc<Block>) -> Self::Future {
|
fn call(&mut self, block: Arc<Block>) -> Self::Future {
|
||||||
// TODO(jlusby): Error = Report, handle errors from state_service.
|
let mut state = self.state_service.clone();
|
||||||
|
|
||||||
|
// TODO(jlusby): Error = Report, handle errors from state_service.
|
||||||
async move {
|
async move {
|
||||||
// Since errors cause an early exit, try to do the
|
// Since errors cause an early exit, try to do the
|
||||||
// quick checks first.
|
// quick checks first.
|
||||||
|
@ -78,6 +83,38 @@ where
|
||||||
block.header.is_equihash_solution_valid()?;
|
block.header.is_equihash_solution_valid()?;
|
||||||
block.is_coinbase_first()?;
|
block.is_coinbase_first()?;
|
||||||
|
|
||||||
|
// These checks only apply to generated blocks. We check the block
|
||||||
|
// height for parsed blocks when we deserialize them.
|
||||||
|
let height = block
|
||||||
|
.coinbase_height()
|
||||||
|
.ok_or("Invalid block: missing block height")?;
|
||||||
|
if height > BlockHeight::MAX {
|
||||||
|
Err("Invalid block height: greater than the maximum height.")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// As a temporary solution for chain gaps, wait for the previous block,
|
||||||
|
// and check its height.
|
||||||
|
// TODO:
|
||||||
|
// - Add a previous block height and hash constraint to the AddBlock request,
|
||||||
|
// so that we can verify in parallel, then check constraints before committing
|
||||||
|
//
|
||||||
|
// Skip contextual checks for the genesis block
|
||||||
|
let previous_block_hash = block.header.previous_block_hash;
|
||||||
|
if previous_block_hash != crate::parameters::GENESIS_PREVIOUS_BLOCK_HASH {
|
||||||
|
tracing::debug!(?height, "Awaiting previous block from state");
|
||||||
|
let previous_block = BlockVerifier::await_block(
|
||||||
|
&mut state,
|
||||||
|
previous_block_hash,
|
||||||
|
BlockHeight(height.0 - 1),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let previous_height = previous_block.coinbase_height().unwrap();
|
||||||
|
if height.0 != previous_height.0 + 1 {
|
||||||
|
Err("Invalid block height: must be 1 more than the previous block height.")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// - header verification
|
// - header verification
|
||||||
// - contextual verification
|
// - contextual verification
|
||||||
|
@ -88,6 +125,62 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The BlockVerifier implementation.
|
||||||
|
///
|
||||||
|
/// The state service is only used for contextual verification.
|
||||||
|
/// (The `ChainVerifier` updates the state.)
|
||||||
|
impl<S> BlockVerifier<S>
|
||||||
|
where
|
||||||
|
S: Service<zebra_state::Request, Response = zebra_state::Response, Error = Error>
|
||||||
|
+ Send
|
||||||
|
+ Clone
|
||||||
|
+ 'static,
|
||||||
|
S::Future: Send + 'static,
|
||||||
|
{
|
||||||
|
/// Get the block for `hash`, using `state`.
|
||||||
|
///
|
||||||
|
/// If there is no block for that hash, returns `Ok(None)`.
|
||||||
|
/// Returns an error if `state.poll_ready` errors.
|
||||||
|
async fn get_block(state: &mut S, hash: BlockHeaderHash) -> Result<Option<Arc<Block>>, Report> {
|
||||||
|
let block = state
|
||||||
|
.ready_and()
|
||||||
|
.await
|
||||||
|
.map_err(|e| eyre!(e))?
|
||||||
|
.call(zebra_state::Request::GetBlock { hash })
|
||||||
|
.await
|
||||||
|
.map(|response| match response {
|
||||||
|
zebra_state::Response::Block { block } => block,
|
||||||
|
_ => unreachable!("GetBlock request can only result in Response::Block"),
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
Ok(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait until a block with `hash` is in `state`.
|
||||||
|
///
|
||||||
|
/// Returns an error if `state.poll_ready` errors.
|
||||||
|
async fn await_block(
|
||||||
|
state: &mut S,
|
||||||
|
hash: BlockHeaderHash,
|
||||||
|
height: BlockHeight,
|
||||||
|
) -> Result<Arc<Block>, Report> {
|
||||||
|
loop {
|
||||||
|
match BlockVerifier::get_block(state, hash).await? {
|
||||||
|
Some(block) => return Ok(block),
|
||||||
|
// Busy-waiting is only a temporary solution to waiting for blocks.
|
||||||
|
// TODO:
|
||||||
|
// - Get an AwaitBlock future from the state
|
||||||
|
// - Replace with AddBlock constraints
|
||||||
|
None => {
|
||||||
|
tracing::debug!(?height, ?hash, "Waiting for state to have block");
|
||||||
|
time::delay_for(Duration::from_secs(2)).await
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a block verification service, using the provided state service.
|
/// Return a block verification service, using the provided state service.
|
||||||
///
|
///
|
||||||
/// The block verifier holds a state service of type `S`, used as context for
|
/// The block verifier holds a state service of type `S`, used as context for
|
||||||
|
|
|
@ -2,11 +2,8 @@
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use chrono::{Duration, Utc};
|
use chrono::Utc;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::{eyre, Report};
|
||||||
use color_eyre::eyre::Report;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use tower::{util::ServiceExt, Service};
|
|
||||||
|
|
||||||
use zebra_chain::block::Block;
|
use zebra_chain::block::Block;
|
||||||
use zebra_chain::block::BlockHeader;
|
use zebra_chain::block::BlockHeader;
|
||||||
|
@ -23,7 +20,7 @@ async fn verify() -> Result<(), Report> {
|
||||||
zebra_test::init();
|
zebra_test::init();
|
||||||
|
|
||||||
let block =
|
let block =
|
||||||
Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_415000_BYTES[..])?;
|
Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])?;
|
||||||
let hash: BlockHeaderHash = block.as_ref().into();
|
let hash: BlockHeaderHash = block.as_ref().into();
|
||||||
|
|
||||||
let state_service = Box::new(zebra_state::in_memory::init());
|
let state_service = Box::new(zebra_state::in_memory::init());
|
||||||
|
@ -52,7 +49,7 @@ async fn verify_fail_future_time() -> Result<(), Report> {
|
||||||
zebra_test::init();
|
zebra_test::init();
|
||||||
|
|
||||||
let mut block =
|
let mut block =
|
||||||
<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_415000_BYTES[..])?;
|
<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])?;
|
||||||
|
|
||||||
let state_service = zebra_state::in_memory::init();
|
let state_service = zebra_state::in_memory::init();
|
||||||
let mut block_verifier = super::init(state_service.clone());
|
let mut block_verifier = super::init(state_service.clone());
|
||||||
|
@ -62,7 +59,7 @@ async fn verify_fail_future_time() -> Result<(), Report> {
|
||||||
// those checks should be performed later in validation, because they
|
// those checks should be performed later in validation, because they
|
||||||
// are more expensive.
|
// are more expensive.
|
||||||
let three_hours_in_the_future = Utc::now()
|
let three_hours_in_the_future = Utc::now()
|
||||||
.checked_add_signed(Duration::hours(3))
|
.checked_add_signed(chrono::Duration::hours(3))
|
||||||
.ok_or("overflow when calculating 3 hours in the future")
|
.ok_or("overflow when calculating 3 hours in the future")
|
||||||
.map_err(|e| eyre!(e))?;
|
.map_err(|e| eyre!(e))?;
|
||||||
block.header.time = three_hours_in_the_future;
|
block.header.time = three_hours_in_the_future;
|
||||||
|
@ -97,7 +94,7 @@ async fn header_solution() -> Result<(), Report> {
|
||||||
let ready_verifier_service = block_verifier.ready_and().await.map_err(|e| eyre!(e))?;
|
let ready_verifier_service = block_verifier.ready_and().await.map_err(|e| eyre!(e))?;
|
||||||
|
|
||||||
// Get a valid block
|
// Get a valid block
|
||||||
let mut block = Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_415000_BYTES[..])
|
let mut block = Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])
|
||||||
.expect("block test vector should deserialize");
|
.expect("block test vector should deserialize");
|
||||||
|
|
||||||
// This should be ok
|
// This should be ok
|
||||||
|
@ -166,7 +163,8 @@ async fn coinbase() -> Result<(), Report> {
|
||||||
let ready_verifier_service = block_verifier.ready_and().await.map_err(|e| eyre!(e))?;
|
let ready_verifier_service = block_verifier.ready_and().await.map_err(|e| eyre!(e))?;
|
||||||
|
|
||||||
// Test 3: Invalid coinbase position
|
// Test 3: Invalid coinbase position
|
||||||
let mut block = Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_415000_BYTES[..])?;
|
let mut block =
|
||||||
|
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])?;
|
||||||
assert_eq!(block.transactions.len(), 1);
|
assert_eq!(block.transactions.len(), 1);
|
||||||
|
|
||||||
// Extract the coinbase transaction from the block
|
// Extract the coinbase transaction from the block
|
||||||
|
|
|
@ -6,11 +6,11 @@ use crate::checkpoint::CheckpointList;
|
||||||
|
|
||||||
use color_eyre::eyre::Report;
|
use color_eyre::eyre::Report;
|
||||||
use color_eyre::eyre::{bail, eyre};
|
use color_eyre::eyre::{bail, eyre};
|
||||||
use futures::future::TryFutureExt;
|
use futures::{future::TryFutureExt, stream::FuturesUnordered};
|
||||||
use std::mem::drop;
|
use std::{collections::BTreeMap, mem::drop, sync::Arc, time::Duration};
|
||||||
use std::{collections::BTreeMap, sync::Arc, time::Duration};
|
use tokio::{stream::StreamExt, time::timeout};
|
||||||
use tokio::time::timeout;
|
use tower::{Service, ServiceExt};
|
||||||
use tower::{util::ServiceExt, Service};
|
use tracing_futures::Instrument;
|
||||||
|
|
||||||
use zebra_chain::block::{Block, BlockHeader};
|
use zebra_chain::block::{Block, BlockHeader};
|
||||||
use zebra_chain::serialization::ZcashDeserialize;
|
use zebra_chain::serialization::ZcashDeserialize;
|
||||||
|
@ -22,6 +22,9 @@ use zebra_chain::Network::{self, *};
|
||||||
/// If the verifier doesn't send a message on the channel, any tests that
|
/// If the verifier doesn't send a message on the channel, any tests that
|
||||||
/// await the channel future will hang.
|
/// await the channel future will hang.
|
||||||
///
|
///
|
||||||
|
/// The block verifier waits for the previous block to reach the state service.
|
||||||
|
/// If that never happens, the test can hang.
|
||||||
|
///
|
||||||
/// This value is set to a large value, to avoid spurious failures due to
|
/// This value is set to a large value, to avoid spurious failures due to
|
||||||
/// high system load.
|
/// high system load.
|
||||||
const VERIFY_TIMEOUT_SECONDS: u64 = 10;
|
const VERIFY_TIMEOUT_SECONDS: u64 = 10;
|
||||||
|
@ -126,17 +129,34 @@ async fn verify_block() -> Result<(), Report> {
|
||||||
|
|
||||||
let (mut chain_verifier, _) = verifiers_from_checkpoint_list(checkpoint_list);
|
let (mut chain_verifier, _) = verifiers_from_checkpoint_list(checkpoint_list);
|
||||||
|
|
||||||
|
/// SPANDOC: Make sure the verifier service is ready for block 0
|
||||||
|
let ready_verifier_service = chain_verifier.ready_and().await.map_err(|e| eyre!(e))?;
|
||||||
|
/// SPANDOC: Set up the future for block 0
|
||||||
|
let verify_future = timeout(
|
||||||
|
Duration::from_secs(VERIFY_TIMEOUT_SECONDS),
|
||||||
|
ready_verifier_service.call(block0.clone()),
|
||||||
|
);
|
||||||
|
/// SPANDOC: Verify block 0
|
||||||
|
// TODO(teor || jlusby): check error kind
|
||||||
|
let verify_response = verify_future
|
||||||
|
.map_err(|e| eyre!(e))
|
||||||
|
.await
|
||||||
|
.expect("timeout should not happen")
|
||||||
|
.expect("block should verify");
|
||||||
|
|
||||||
|
assert_eq!(verify_response, hash0);
|
||||||
|
|
||||||
let block1 = Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_1_BYTES[..])?;
|
let block1 = Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_1_BYTES[..])?;
|
||||||
let hash1: BlockHeaderHash = block1.as_ref().into();
|
let hash1: BlockHeaderHash = block1.as_ref().into();
|
||||||
|
|
||||||
/// SPANDOC: Make sure the verifier service is ready
|
/// SPANDOC: Make sure the verifier service is ready for block 1
|
||||||
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))?;
|
||||||
/// SPANDOC: Set up the future
|
/// SPANDOC: Set up the future for block 1
|
||||||
let verify_future = timeout(
|
let verify_future = timeout(
|
||||||
Duration::from_secs(VERIFY_TIMEOUT_SECONDS),
|
Duration::from_secs(VERIFY_TIMEOUT_SECONDS),
|
||||||
ready_verifier_service.call(block1.clone()),
|
ready_verifier_service.call(block1.clone()),
|
||||||
);
|
);
|
||||||
/// SPANDOC: Verify the block
|
/// SPANDOC: Verify block 1
|
||||||
// TODO(teor || jlusby): check error kind
|
// TODO(teor || jlusby): check error kind
|
||||||
let verify_response = verify_future
|
let verify_response = verify_future
|
||||||
.map_err(|e| eyre!(e))
|
.map_err(|e| eyre!(e))
|
||||||
|
@ -371,3 +391,68 @@ async fn verify_fail_add_block_checkpoint() -> Result<(), Report> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn continuous_blockchain_test() -> Result<(), Report> {
|
||||||
|
continuous_blockchain().await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test a continuous blockchain in the BlockVerifier
|
||||||
|
#[spandoc::spandoc]
|
||||||
|
async fn continuous_blockchain() -> Result<(), Report> {
|
||||||
|
zebra_test::init();
|
||||||
|
|
||||||
|
// A continuous blockchain
|
||||||
|
let mut blockchain = Vec::new();
|
||||||
|
for b in &[
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_1_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_2_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_3_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_4_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_5_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_6_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_7_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_8_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_9_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_10_BYTES[..],
|
||||||
|
] {
|
||||||
|
let block = Arc::<Block>::zcash_deserialize(*b)?;
|
||||||
|
let hash: BlockHeaderHash = block.as_ref().into();
|
||||||
|
blockchain.push((block.clone(), block.coinbase_height().unwrap(), hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a checkpoint list containing the genesis block
|
||||||
|
let checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> = blockchain
|
||||||
|
.iter()
|
||||||
|
.map(|(_, height, hash)| (*height, *hash))
|
||||||
|
.take(1)
|
||||||
|
.collect();
|
||||||
|
let checkpoint_list = CheckpointList::from_list(checkpoint_list).map_err(|e| eyre!(e))?;
|
||||||
|
|
||||||
|
let (mut chain_verifier, _) = verifiers_from_checkpoint_list(checkpoint_list);
|
||||||
|
|
||||||
|
let mut handles = FuturesUnordered::new();
|
||||||
|
|
||||||
|
// Now verify each block
|
||||||
|
for (block, height, _hash) in blockchain {
|
||||||
|
/// SPANDOC: Make sure the verifier service is ready for block {?height}
|
||||||
|
let ready_verifier_service = chain_verifier.ready_and().map_err(|e| eyre!(e)).await?;
|
||||||
|
|
||||||
|
/// SPANDOC: Set up the future for block {?height}
|
||||||
|
let verify_future = timeout(
|
||||||
|
std::time::Duration::from_secs(VERIFY_TIMEOUT_SECONDS),
|
||||||
|
ready_verifier_service.call(block.clone()),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// SPANDOC: spawn verification future in the background for block {?height}
|
||||||
|
let handle = tokio::spawn(verify_future.in_current_span());
|
||||||
|
handles.push(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(result) = handles.next().await {
|
||||||
|
result??.map_err(|e| eyre!(e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -305,7 +305,7 @@ async fn continuous_blockchain(restart_height: Option<BlockHeight>) -> Result<()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SPANDOC: Make sure the verifier service is ready
|
/// SPANDOC: Make sure the verifier service is ready for block {?height}
|
||||||
let ready_verifier_service = checkpoint_verifier
|
let ready_verifier_service = checkpoint_verifier
|
||||||
.ready_and()
|
.ready_and()
|
||||||
.map_err(|e| eyre!(e))
|
.map_err(|e| eyre!(e))
|
||||||
|
@ -317,7 +317,7 @@ async fn continuous_blockchain(restart_height: Option<BlockHeight>) -> Result<()
|
||||||
ready_verifier_service.call(block.clone()),
|
ready_verifier_service.call(block.clone()),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// SPANDOC: spawn verification future in the background
|
/// SPANDOC: spawn verification future in the background for block {?height}
|
||||||
let handle = tokio::spawn(verify_future.in_current_span());
|
let handle = tokio::spawn(verify_future.in_current_span());
|
||||||
handles.push(handle);
|
handles.push(handle);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue