Skip to content

Commit c5cbe51

Browse files
authored
fast-sync: add tests (#410)
fast sync tests
1 parent e84f5c1 commit c5cbe51

File tree

3 files changed

+170
-14
lines changed

3 files changed

+170
-14
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

consensus/fast-sync/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ tokio = { workspace = true, features = ["full"] }
2424
tower = { workspace = true }
2525

2626
[dev-dependencies]
27+
proptest = { workspace = true }
28+
tokio-test = { workspace = true }
29+
tempfile = { workspace = true }
2730

2831
[lints]
2932
workspace = true

consensus/fast-sync/src/fast_sync.rs

Lines changed: 164 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,24 @@ pub async fn validate_entries<N: NetworkZone>(
9898

9999
let mut hashes_stop_diff_last_height = last_height - hashes_stop_height;
100100

101+
// get the hashes we are missing to create the first fast-sync hash.
102+
let BlockchainResponse::BlockHashInRange(starting_hashes) = blockchain_read_handle
103+
.ready()
104+
.await?
105+
.call(BlockchainReadRequest::BlockHashInRange(
106+
hashes_start_height..start_height,
107+
Chain::Main,
108+
))
109+
.await?
110+
else {
111+
unreachable!()
112+
};
113+
114+
// If we don't have enough hashes to make up a batch we can't validate any.
115+
if amount_of_hashes + starting_hashes.len() < FAST_SYNC_BATCH_LEN {
116+
return Ok((VecDeque::new(), entries));
117+
}
118+
101119
let mut unknown = VecDeque::new();
102120

103121
// start moving from the back of the batches taking enough hashes out so we are only left with hashes
@@ -125,23 +143,10 @@ pub async fn validate_entries<N: NetworkZone>(
125143
unknown.push_front(back);
126144
}
127145

128-
// get the hashes we are missing to create the first fast-sync hash.
129-
let BlockchainResponse::BlockHashInRange(hashes) = blockchain_read_handle
130-
.ready()
131-
.await?
132-
.call(BlockchainReadRequest::BlockHashInRange(
133-
hashes_start_height..start_height,
134-
Chain::Main,
135-
))
136-
.await?
137-
else {
138-
unreachable!()
139-
};
140-
141146
// Start verifying the hashes.
142147
let mut hasher = Hasher::default();
143148
let mut last_i = 1;
144-
for (i, hash) in hashes
149+
for (i, hash) in starting_hashes
145150
.iter()
146151
.chain(entries.iter().flat_map(|e| e.ids.iter()))
147152
.enumerate()
@@ -245,3 +250,148 @@ pub fn block_to_verified_block_information(
245250
block,
246251
}
247252
}
253+
254+
#[cfg(test)]
255+
mod tests {
256+
use std::{collections::VecDeque, slice, sync::LazyLock};
257+
258+
use proptest::proptest;
259+
260+
use cuprate_p2p::block_downloader::ChainEntry;
261+
use cuprate_p2p_core::{client::InternalPeerID, handles::HandleBuilder, ClearNet};
262+
263+
use crate::{
264+
fast_sync_stop_height, set_fast_sync_hashes, validate_entries, FAST_SYNC_BATCH_LEN,
265+
};
266+
267+
static HASHES: LazyLock<&[[u8; 32]]> = LazyLock::new(|| {
268+
let hashes = (0..FAST_SYNC_BATCH_LEN * 2000)
269+
.map(|i| {
270+
let mut ret = [0; 32];
271+
ret[..8].copy_from_slice(&i.to_le_bytes());
272+
ret
273+
})
274+
.collect::<Vec<_>>();
275+
276+
let hashes = hashes.leak();
277+
278+
let fast_sync_hashes = hashes
279+
.chunks(FAST_SYNC_BATCH_LEN)
280+
.map(|chunk| {
281+
let len = chunk.len() * 32;
282+
let bytes = chunk.as_ptr().cast::<u8>();
283+
284+
// SAFETY:
285+
// We are casting a valid [[u8; 32]] to a [u8], no alignment requirements and we are using it
286+
// within the [[u8; 32]]'s lifetime.
287+
unsafe { blake3::hash(slice::from_raw_parts(bytes, len)).into() }
288+
})
289+
.collect::<Vec<_>>();
290+
291+
set_fast_sync_hashes(fast_sync_hashes.leak());
292+
293+
hashes
294+
});
295+
296+
proptest! {
297+
#[test]
298+
fn valid_entry(len in 0_usize..1_500_000) {
299+
let mut ids = HASHES.to_vec();
300+
ids.resize(len, [0_u8; 32]);
301+
302+
let handle = HandleBuilder::new().build();
303+
304+
let entry = ChainEntry {
305+
ids,
306+
peer: InternalPeerID::Unknown(1),
307+
handle: handle.1
308+
};
309+
310+
let data_dir = tempfile::tempdir().unwrap();
311+
312+
tokio_test::block_on(async move {
313+
let blockchain_config = cuprate_blockchain::config::ConfigBuilder::new()
314+
.data_directory(data_dir.path().to_path_buf())
315+
.build();
316+
317+
let (mut blockchain_read_handle, _, _) =
318+
cuprate_blockchain::service::init(blockchain_config).unwrap();
319+
320+
321+
let ret = validate_entries::<ClearNet>(VecDeque::from([entry]), 0, &mut blockchain_read_handle).await.unwrap();
322+
323+
let len_left = ret.0.iter().map(|e| e.ids.len()).sum::<usize>();
324+
let len_right = ret.1.iter().map(|e| e.ids.len()).sum::<usize>();
325+
326+
assert_eq!(len_left + len_right, len);
327+
assert!(len_left <= fast_sync_stop_height());
328+
assert!(len_right < FAST_SYNC_BATCH_LEN || len > fast_sync_stop_height());
329+
});
330+
}
331+
332+
#[test]
333+
fn single_hash_entries(len in 0_usize..1_500_000) {
334+
let handle = HandleBuilder::new().build();
335+
let entries = (0..len).map(|i| {
336+
ChainEntry {
337+
ids: vec![HASHES.get(i).copied().unwrap_or_default()],
338+
peer: InternalPeerID::Unknown(1),
339+
handle: handle.1.clone()
340+
}
341+
}).collect();
342+
343+
let data_dir = tempfile::tempdir().unwrap();
344+
345+
tokio_test::block_on(async move {
346+
let blockchain_config = cuprate_blockchain::config::ConfigBuilder::new()
347+
.data_directory(data_dir.path().to_path_buf())
348+
.build();
349+
350+
let (mut blockchain_read_handle, _, _) =
351+
cuprate_blockchain::service::init(blockchain_config).unwrap();
352+
353+
354+
let ret = validate_entries::<ClearNet>(entries, 0, &mut blockchain_read_handle).await.unwrap();
355+
356+
let len_left = ret.0.iter().map(|e| e.ids.len()).sum::<usize>();
357+
let len_right = ret.1.iter().map(|e| e.ids.len()).sum::<usize>();
358+
359+
assert_eq!(len_left + len_right, len);
360+
assert!(len_left <= fast_sync_stop_height());
361+
assert!(len_right < FAST_SYNC_BATCH_LEN || len > fast_sync_stop_height());
362+
});
363+
}
364+
365+
#[test]
366+
fn not_enough_hashes(len in 0_usize..FAST_SYNC_BATCH_LEN) {
367+
let hashes_start_height = FAST_SYNC_BATCH_LEN * 1234;
368+
369+
let handle = HandleBuilder::new().build();
370+
let entry = ChainEntry {
371+
ids: HASHES[hashes_start_height..(hashes_start_height + len)].to_vec(),
372+
peer: InternalPeerID::Unknown(1),
373+
handle: handle.1
374+
};
375+
376+
let data_dir = tempfile::tempdir().unwrap();
377+
378+
tokio_test::block_on(async move {
379+
let blockchain_config = cuprate_blockchain::config::ConfigBuilder::new()
380+
.data_directory(data_dir.path().to_path_buf())
381+
.build();
382+
383+
let (mut blockchain_read_handle, _, _) =
384+
cuprate_blockchain::service::init(blockchain_config).unwrap();
385+
386+
387+
let ret = validate_entries::<ClearNet>(VecDeque::from([entry]), 0, &mut blockchain_read_handle).await.unwrap();
388+
389+
let len_left = ret.0.iter().map(|e| e.ids.len()).sum::<usize>();
390+
let len_right = ret.1.iter().map(|e| e.ids.len()).sum::<usize>();
391+
392+
assert_eq!(len_right, len);
393+
assert_eq!(len_left, 0);
394+
});
395+
}
396+
}
397+
}

0 commit comments

Comments
 (0)