use std::{collections::HashSet, sync::Arc, time::Duration};
use tokio::sync::{mpsc, watch};
use zebra_chain::{
amount::NonNegative,
block::{self, Block},
parameters::Network,
value_balance::ValueBalance,
};
use crate::{
arbitrary::Prepare, service::finalized_state::FinalizedState, tests::FakeChainHelper, Config,
NonFinalizedState, WatchReceiver,
};
use super::NonFinalizedBlocksListener;
fn fake_chain(network: &Network, len: usize) -> Vec<Arc<Block>> {
let mut blocks = vec![Arc::new(network.test_block(653599, 583999).unwrap())];
while blocks.len() < len {
let child = blocks.last().unwrap().make_fake_child().set_work(10);
blocks.push(child);
}
blocks
}
fn state_from_chain(network: &Network, blocks: &[Arc<Block>]) -> NonFinalizedState {
let finalized_state = FinalizedState::new(
&Config::ephemeral(),
network,
#[cfg(feature = "elasticsearch")]
false,
)
.expect("opening an ephemeral database should succeed");
finalized_state.set_finalized_value_pool(ValueBalance::<NonNegative>::fake_populated_pool());
let mut state = NonFinalizedState::new(network);
let (root, rest) = blocks.split_first().expect("chain must not be empty");
state
.commit_new_chain(root.clone().prepare(), &finalized_state)
.expect("committing the root block should succeed");
for block in rest {
state
.commit_block(block.clone().prepare(), &finalized_state)
.expect("committing a child block should succeed");
}
state
}
async fn recv_hash(rx: &mut mpsc::Receiver<(block::Hash, Arc<Block>)>) -> block::Hash {
tokio::time::timeout(Duration::from_secs(10), rx.recv())
.await
.expect("listener should send a block before timing out")
.expect("listener channel should stay open")
.0
}
async fn assert_idle(rx: &mut mpsc::Receiver<(block::Hash, Arc<Block>)>) {
assert!(
tokio::time::timeout(Duration::from_millis(200), rx.recv())
.await
.is_err(),
"listener should not send any more blocks",
);
}
#[tokio::test]
async fn sends_all_blocks_when_no_known_tips() {
let network = Network::Mainnet;
let blocks = fake_chain(&network, 3);
let hashes: Vec<_> = blocks.iter().map(|b| b.hash()).collect();
let (_tx, rx) = watch::channel(state_from_chain(&network, &blocks));
let listener = NonFinalizedBlocksListener::spawn(WatchReceiver::new(rx), HashSet::new());
let mut received = listener.unwrap();
assert_eq!(recv_hash(&mut received).await, hashes[0]);
assert_eq!(recv_hash(&mut received).await, hashes[1]);
assert_eq!(recv_hash(&mut received).await, hashes[2]);
assert_idle(&mut received).await;
}
#[tokio::test]
async fn skips_blocks_at_or_below_known_tip() {
let network = Network::Mainnet;
let blocks = fake_chain(&network, 3);
let hashes: Vec<_> = blocks.iter().map(|b| b.hash()).collect();
let known_tips = std::iter::once(hashes[1]).collect();
let (_tx, rx) = watch::channel(state_from_chain(&network, &blocks));
let listener = NonFinalizedBlocksListener::spawn(WatchReceiver::new(rx), known_tips);
let mut received = listener.unwrap();
assert_eq!(recv_hash(&mut received).await, hashes[2]);
assert_idle(&mut received).await;
}
#[tokio::test]
async fn sends_nothing_when_caller_has_the_tip() {
let network = Network::Mainnet;
let blocks = fake_chain(&network, 3);
let tip = blocks.last().unwrap().hash();
let (_tx, rx) = watch::channel(state_from_chain(&network, &blocks));
let listener =
NonFinalizedBlocksListener::spawn(WatchReceiver::new(rx), std::iter::once(tip).collect());
let mut received = listener.unwrap();
assert_idle(&mut received).await;
}
#[tokio::test]
async fn sends_only_new_blocks_on_update() {
let network = Network::Mainnet;
let blocks = fake_chain(&network, 4);
let hashes: Vec<_> = blocks.iter().map(|b| b.hash()).collect();
let initial = state_from_chain(&network, &blocks[..3]);
let extended = state_from_chain(&network, &blocks);
let (tx, rx) = watch::channel(initial);
let listener = NonFinalizedBlocksListener::spawn(WatchReceiver::new(rx), HashSet::new());
let mut received = listener.unwrap();
for expected in &hashes[..3] {
assert_eq!(recv_hash(&mut received).await, *expected);
}
tx.send(extended).expect("listener should still be running");
assert_eq!(recv_hash(&mut received).await, hashes[3]);
assert_idle(&mut received).await;
}