#![allow(clippy::unwrap_in_result)]
use std::sync::{
atomic::{AtomicU8, Ordering},
Arc,
};
use futures::future;
use tokio::time::{timeout, Duration};
use zebra_chain::{
block::Height,
parameters::{Network, POST_BLOSSOM_POW_TARGET_SPACING},
};
use zebra_network::constants::{
DEFAULT_CRAWL_NEW_PEER_INTERVAL, HANDSHAKE_TIMEOUT, INVENTORY_ROTATION_INTERVAL,
};
use zebra_state::ChainTipSender;
use crate::{
components::sync::{
ChainSync, BLOCK_DOWNLOAD_RETRY_LIMIT, BLOCK_DOWNLOAD_TIMEOUT, BLOCK_VERIFY_TIMEOUT,
GENESIS_TIMEOUT_RETRY, SYNC_RESTART_DELAY,
},
config::ZebradConfig,
};
#[test]
fn ensure_timeouts_consistent() {
let _init_guard = zebra_test::init();
assert!(
SYNC_RESTART_DELAY.as_secs() > 2 * BLOCK_DOWNLOAD_TIMEOUT.as_secs(),
"Sync restart should allow for pending and buffered requests to complete"
);
const BLOCK_DOWNLOAD_HEDGE_TIMEOUT: u64 =
2 * BLOCK_DOWNLOAD_RETRY_LIMIT as u64 * BLOCK_DOWNLOAD_TIMEOUT.as_secs();
assert!(
BLOCK_VERIFY_TIMEOUT.as_secs()
> SYNC_RESTART_DELAY.as_secs()
+ BLOCK_DOWNLOAD_HEDGE_TIMEOUT
+ BLOCK_DOWNLOAD_TIMEOUT.as_secs(),
"Block verify should allow for a block timeout, a sync restart, and some block fetches"
);
const MIN_NETWORK_SPEED_BYTES_PER_SEC: u64 = 10 * 1024 * 1024 / 8;
assert!(
BLOCK_VERIFY_TIMEOUT.as_secs() > SYNC_RESTART_DELAY.as_secs() + 2 * zebra_consensus::MAX_CHECKPOINT_BYTE_COUNT / MIN_NETWORK_SPEED_BYTES_PER_SEC,
"Block verify should allow for a full checkpoint download, a sync restart, then a full checkpoint re-download"
);
assert!(
BLOCK_VERIFY_TIMEOUT.as_secs()
> 2 * zebra_chain::parameters::NetworkUpgrade::Blossom
.target_spacing()
.num_seconds() as u64,
"Block verify should allow for at least one new block to be generated and distributed"
);
assert!(
GENESIS_TIMEOUT_RETRY.as_secs() > HANDSHAKE_TIMEOUT.as_secs()
&& GENESIS_TIMEOUT_RETRY.as_secs() < BLOCK_DOWNLOAD_TIMEOUT.as_secs(),
"Genesis retries should wait for new peers, but they shouldn't wait too long"
);
assert!(
SYNC_RESTART_DELAY.as_secs() < POST_BLOSSOM_POW_TARGET_SPACING.into(),
"a syncer tip crawl should complete before most new blocks"
);
assert!(
INVENTORY_ROTATION_INTERVAL < SYNC_RESTART_DELAY,
"we should expire some inventory every time the syncer resets"
);
assert!(
SYNC_RESTART_DELAY < 2 * INVENTORY_ROTATION_INTERVAL,
"we should give the syncer at least one retry attempt, \
before we expire all inventory"
);
assert!(
DEFAULT_CRAWL_NEW_PEER_INTERVAL.as_secs() + HANDSHAKE_TIMEOUT.as_secs()
< SYNC_RESTART_DELAY.as_secs(),
"an address crawl and peer connections should complete before most syncer tips crawls"
);
}
#[test]
fn request_genesis_is_rate_limited() {
let (runtime, _init_guard) = zebra_test::init_async();
let _guard = runtime.enter();
const RETRIES_TO_RUN: u8 = 3;
let peer_requests_counter = Arc::new(AtomicU8::new(0));
let peer_requests_counter_in_service = Arc::clone(&peer_requests_counter);
let state_requests_counter = Arc::new(AtomicU8::new(0));
let state_requests_counter_in_service = Arc::clone(&state_requests_counter);
let peer_service = tower::service_fn(move |request| {
match request {
zebra_network::Request::BlocksByHash(_) => {
peer_requests_counter_in_service.fetch_add(1, Ordering::SeqCst);
future::err("block not found".into())
}
_ => unreachable!("no other request is allowed"),
}
});
let state_service = tower::service_fn(move |request| {
match request {
zebra_state::Request::KnownBlock(_) => {
state_requests_counter_in_service.fetch_add(1, Ordering::SeqCst);
future::ok(zebra_state::Response::KnownBlock(None))
}
_ => unreachable!("no other request is allowed"),
}
});
let (_sender, latest_chain_tip, _change) = ChainTipSender::new(None, &Network::Mainnet);
let verifier_service =
tower::service_fn(
move |_| async move { unreachable!("no request to this service is allowed") },
);
let (misbehavior_tx, _misbehavior_rx) = tokio::sync::mpsc::channel(1);
let (mut chain_sync, _) = ChainSync::new(
&ZebradConfig::default(),
Height(0),
peer_service,
verifier_service,
state_service,
latest_chain_tip,
misbehavior_tx,
);
runtime.block_on(async move {
let retries_timeout = (RETRIES_TO_RUN - 1) as u64 * GENESIS_TIMEOUT_RETRY.as_secs()
+ GENESIS_TIMEOUT_RETRY.as_secs() / 2;
let _ = timeout(
Duration::from_secs(retries_timeout),
chain_sync.request_genesis(),
)
.await;
});
let peer_requests_counter = peer_requests_counter.load(Ordering::SeqCst);
assert!(peer_requests_counter >= RETRIES_TO_RUN);
assert!(peer_requests_counter <= RETRIES_TO_RUN * (BLOCK_DOWNLOAD_RETRY_LIMIT as u8) * 2);
assert_eq!(
state_requests_counter.load(Ordering::SeqCst),
RETRIES_TO_RUN
);
}