From f8e5b1672d45c4b150e5d71c96e454ffae5617e8 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Mon, 13 Jan 2025 06:04:07 -0800 Subject: [PATCH] Blockstore: Migrate ShredIndex type to more efficient data structure (#3900) --- Cargo.lock | 1 + ledger/Cargo.toml | 1 + .../proptest-regressions/blockstore_meta.txt | 7 + ledger/src/blockstore_db.rs | 45 +- ledger/src/blockstore_meta.rs | 471 +++++++++++++++++- programs/sbf/Cargo.lock | 78 +++ svm/examples/Cargo.lock | 78 +++ 7 files changed, 669 insertions(+), 12 deletions(-) create mode 100644 ledger/proptest-regressions/blockstore_meta.txt diff --git a/Cargo.lock b/Cargo.lock index a7b643f06c62d0..628dccf12631a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7671,6 +7671,7 @@ dependencies = [ "mockall", "num_cpus", "num_enum", + "proptest", "prost", "qualifier_attr", "rand 0.8.5", diff --git a/ledger/Cargo.toml b/ledger/Cargo.toml index a4d00aefa4e848..6d4ce7d5ab5cad 100644 --- a/ledger/Cargo.toml +++ b/ledger/Cargo.toml @@ -31,6 +31,7 @@ lru = { workspace = true } mockall = { workspace = true } num_cpus = { workspace = true } num_enum = { workspace = true } +proptest = { workspace = true } prost = { workspace = true } qualifier_attr = { workspace = true } rand = { workspace = true } diff --git a/ledger/proptest-regressions/blockstore_meta.txt b/ledger/proptest-regressions/blockstore_meta.txt new file mode 100644 index 00000000000000..ba4028c0da445d --- /dev/null +++ b/ledger/proptest-regressions/blockstore_meta.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc d28b14f167a3950cfc2a5b82dff1e15c65e9ac23a5c249f812e69af96c3489ed # shrinks to coding_indices = 0..0, data_indices = 2984..15152, slot = 0 diff --git a/ledger/src/blockstore_db.rs b/ledger/src/blockstore_db.rs index 200a480f3d1279..f6bf40ba3b7abf 100644 --- a/ledger/src/blockstore_db.rs +++ b/ledger/src/blockstore_db.rs @@ -1,8 +1,7 @@ pub use rocksdb::Direction as IteratorDirection; use { crate::{ - blockstore_meta, - blockstore_meta::MerkleRootMeta, + blockstore_meta::{self, MerkleRootMeta}, blockstore_metrics::{ maybe_enable_rocksdb_perf, report_rocksdb_read_perf, report_rocksdb_write_perf, BlockstoreRocksDbColumnFamilyMetrics, PerfSamplingStatus, PERF_METRIC_OP_NAME_GET, @@ -11,7 +10,7 @@ use { }, blockstore_options::{AccessType, BlockstoreOptions, LedgerColumnOptions}, }, - bincode::{deserialize, serialize}, + bincode::{deserialize, Options as BincodeOptions}, byteorder::{BigEndian, ByteOrder}, log::*, prost::Message, @@ -796,6 +795,14 @@ pub trait ColumnName { pub trait TypedColumn: Column { type Type: Serialize + DeserializeOwned; + + fn deserialize(data: &[u8]) -> Result { + Ok(bincode::deserialize(data)?) + } + + fn serialize(data: &Self::Type) -> Result> { + Ok(bincode::serialize(data)?) + } } impl TypedColumn for columns::AddressSignatures { @@ -1210,6 +1217,28 @@ impl ColumnName for columns::Index { } impl TypedColumn for columns::Index { type Type = blockstore_meta::Index; + + fn deserialize(data: &[u8]) -> Result { + let config = bincode::DefaultOptions::new() + // `bincode::serialize` uses fixint encoding by default, so we need to use the same here + .with_fixint_encoding() + .reject_trailing_bytes(); + + // Migration strategy for new column format: + // 1. Release 1: Add ability to read new format as fallback, keep writing old format + // 2. Release 2: Switch to writing new format, keep reading old format as fallback + // 3. Release 3: Remove old format support once stable + // This allows safe downgrade to Release 1 since it can read both formats + // https://github.com/anza-xyz/agave/issues/3570 + let index: bincode::Result = config.deserialize(data); + match index { + Ok(index) => Ok(index), + Err(_) => { + let index: blockstore_meta::IndexV2 = config.deserialize(data)?; + Ok(index.into()) + } + } + } } impl SlotColumn for columns::DeadSlots {} @@ -1662,7 +1691,7 @@ where let result = self .backend .multi_get_cf(self.handle(), keys) - .map(|out| Ok(out?.as_deref().map(deserialize).transpose()?)); + .map(|out| out?.as_deref().map(C::deserialize).transpose()); if let Some(op_start_instant) = is_perf_enabled { // use multi-get instead @@ -1689,7 +1718,7 @@ where &self.read_perf_status, ); if let Some(pinnable_slice) = self.backend.get_pinned_cf(self.handle(), key)? { - let value = deserialize(pinnable_slice.as_ref())?; + let value = C::deserialize(pinnable_slice.as_ref())?; result = Ok(Some(value)) } @@ -1709,7 +1738,7 @@ where self.column_options.rocks_perf_sample_interval, &self.write_perf_status, ); - let serialized_value = serialize(value)?; + let serialized_value = C::serialize(value)?; let key = Self::key_from_index(index); let result = self.backend.put_cf(self.handle(), &key, &serialized_value); @@ -1732,7 +1761,7 @@ where value: &C::Type, ) -> Result<()> { let key = Self::key_from_index(index); - let serialized_value = serialize(value)?; + let serialized_value = C::serialize(value)?; batch.put_cf(self.handle(), &key, &serialized_value) } } @@ -2254,7 +2283,7 @@ pub mod tests { C: ColumnIndexDeprecation + TypedColumn + ColumnName, { pub fn put_deprecated(&self, index: C::DeprecatedIndex, value: &C::Type) -> Result<()> { - let serialized_value = serialize(value)?; + let serialized_value = C::serialize(value)?; self.backend .put_cf(self.handle(), &C::deprecated_key(index), &serialized_value) } diff --git a/ledger/src/blockstore_meta.rs b/ledger/src/blockstore_meta.rs index 173088f376bbfc..60d156b1642c00 100644 --- a/ledger/src/blockstore_meta.rs +++ b/ledger/src/blockstore_meta.rs @@ -1,5 +1,8 @@ use { - crate::shred::{Shred, ShredType}, + crate::{ + blockstore::MAX_DATA_SHREDS_PER_SLOT, + shred::{Shred, ShredType}, + }, bitflags::bitflags, serde::{Deserialize, Deserializer, Serialize, Serializer}, solana_sdk::{ @@ -8,7 +11,7 @@ use { }, std::{ collections::BTreeSet, - ops::{Range, RangeBounds}, + ops::{Bound, Range, RangeBounds}, }, }; @@ -112,6 +115,33 @@ pub struct Index { coding: ShredIndex, } +#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] +pub struct IndexV2 { + pub slot: Slot, + data: ShredIndexV2, + coding: ShredIndexV2, +} + +impl From for Index { + fn from(index: IndexV2) -> Self { + Index { + slot: index.slot, + data: index.data.into(), + coding: index.coding.into(), + } + } +} + +impl From for IndexV2 { + fn from(index: Index) -> Self { + IndexV2 { + slot: index.slot, + data: index.data.into(), + coding: index.coding.into(), + } + } +} + #[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] pub struct ShredIndex { /// Map representing presence/absence of shreds @@ -243,6 +273,10 @@ impl Index { } } +/// Superseded by [`ShredIndexV2`]. +/// +/// TODO: Remove this once new [`ShredIndexV2`] is fully rolled out +/// and no longer relies on it for fallback. impl ShredIndex { pub fn num_shreds(&self) -> usize { self.index.len() @@ -262,6 +296,228 @@ impl ShredIndex { pub(crate) fn insert(&mut self, index: u64) { self.index.insert(index); } + + #[cfg(test)] + fn remove(&mut self, index: u64) { + self.index.remove(&index); + } +} + +/// A bitvec (`Vec`) of shred indices, where each u8 represents 8 shred indices. +/// +/// The current implementation of [`ShredIndex`] utilizes a [`BTreeSet`] to store +/// shred indices. While [`BTreeSet`] remains efficient as operations are amortized +/// over time, the overhead of the B-tree structure becomes significant when frequently +/// serialized and deserialized. In particular: +/// - **Tree Traversal**: Serialization requires walking the non-contiguous tree structure. +/// - **Reconstruction**: Deserialization involves rebuilding the tree in bulk, +/// including dynamic memory allocations and re-balancing nodes. +/// +/// In contrast, our bit vec implementation provides: +/// - **Contiguous Memory**: All bits are stored in a contiguous array of u64 words, +/// allowing direct indexing and efficient memory access patterns. +/// - **Direct Range Access**: Can load only the specific words that overlap with a +/// requested range, avoiding unnecessary traversal. +/// - **Simplified Serialization**: The contiguous memory layout allows for efficient +/// serialization/deserialization without tree reconstruction. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct ShredIndexV2 { + #[serde(with = "serde_bytes")] + index: Vec, + num_shreds: usize, +} + +impl Default for ShredIndexV2 { + fn default() -> Self { + Self { + index: vec![0; Self::MAX_WORDS_PER_SLOT], + num_shreds: 0, + } + } +} + +type ShredIndexV2Word = u8; +impl ShredIndexV2 { + const SIZE_OF_WORD: usize = std::mem::size_of::(); + const BITS_PER_WORD: usize = Self::SIZE_OF_WORD * 8; + const MAX_WORDS_PER_SLOT: usize = MAX_DATA_SHREDS_PER_SLOT.div_ceil(Self::BITS_PER_WORD); + + pub fn num_shreds(&self) -> usize { + self.num_shreds + } + + fn index_and_mask(index: u64) -> (usize, ShredIndexV2Word) { + let word_idx = index as usize / Self::BITS_PER_WORD; + let bit_idx = index as usize % Self::BITS_PER_WORD; + let mask = 1 << bit_idx; + (word_idx, mask as ShredIndexV2Word) + } + + #[cfg(test)] + fn remove(&mut self, index: u64) { + assert!( + index < MAX_DATA_SHREDS_PER_SLOT as u64, + "index out of bounds. {index} >= {MAX_DATA_SHREDS_PER_SLOT}" + ); + + let (word_idx, mask) = Self::index_and_mask(index); + + if self.index[word_idx] & mask != 0 { + self.index[word_idx] ^= mask; + self.num_shreds -= 1; + } + } + + #[allow(unused)] + pub(crate) fn contains(&self, idx: u64) -> bool { + if idx >= MAX_DATA_SHREDS_PER_SLOT as u64 { + return false; + } + let (word_idx, mask) = Self::index_and_mask(idx); + (self.index[word_idx] & mask) != 0 + } + + pub(crate) fn insert(&mut self, idx: u64) { + if idx >= MAX_DATA_SHREDS_PER_SLOT as u64 { + return; + } + let (word_idx, mask) = Self::index_and_mask(idx); + if self.index[word_idx] & mask == 0 { + self.index[word_idx] |= mask; + self.num_shreds += 1; + } + } + + /// Provides an iterator over the set shred indices within a specified range. + /// + /// # Algorithm + /// 1. Divide the specified range into 8-bit words (u8). + /// 2. For each word:HH + /// - Calculate the base index (position of the word * 8). + /// - Process all set bits in the word. + /// - For words overlapping the range boundaries: + /// - Determine the relevant bit range using boundaries. + /// - Mask out bits outside the range. + /// - Use bit manipulation to iterate over set bits efficiently. + /// + /// ## Explanation + /// Given range `[75..205]`: + /// + /// Word layout (each word is 8 bits), where each X represents a bit candidate: + /// ```text + /// Word 9 (72-79): [..XXXXXX] ← Partial word (start) + /// Word 10 (80-87): [XXXXXXXX] ← Full word (entirely in range) + /// ... + /// Word 25 (200-207): [XXXXXX..] ← Partial word (end) + /// ``` + /// + /// Partial Word 9 (contains start boundary 75): + /// - Base index = 72 + /// - Lower boundary = 75 - 72 = 3 + /// - Lower mask = `11111000` (right-shift) + /// + /// Partial Word 25 (contains end boundary 205): + /// - Base index = 200 + /// - Upper boundary = 205 - 200 = 5 + /// - Upper mask = `00111111` (left-shift) + /// + /// Final mask = `word & lower_mask & upper_mask` + /// + /// Bit iteration: + /// 1. Apply masks to restrict the bits to the range. + /// 2. While bits remain in the masked word: + /// a. Find the lowest set bit (`trailing_zeros`). + /// b. Add the bit's position to the base index. + /// c. Clear the lowest set bit (`n & (n - 1)`). + /// ``` + pub(crate) fn range(&self, bounds: R) -> impl Iterator + '_ + where + R: RangeBounds, + { + let start = match bounds.start_bound() { + Bound::Included(&n) => n as usize, + Bound::Excluded(&n) => n as usize + 1, + Bound::Unbounded => 0, + }; + let end = match bounds.end_bound() { + Bound::Included(&n) => n as usize + 1, + Bound::Excluded(&n) => n as usize, + Bound::Unbounded => MAX_DATA_SHREDS_PER_SLOT, + }; + + let end_word = end + .div_ceil(Self::BITS_PER_WORD) + .min(Self::MAX_WORDS_PER_SLOT); + let start_word = (start / Self::BITS_PER_WORD).min(end_word); + + self.index[start_word..end_word] + .iter() + .enumerate() + .flat_map(move |(word_offset, &word)| { + let base_idx = (start_word + word_offset) * Self::BITS_PER_WORD; + + let lower_bound = start.saturating_sub(base_idx); + let upper_bound = if base_idx + Self::BITS_PER_WORD > end { + end - base_idx + } else { + Self::BITS_PER_WORD + }; + + let lower_mask = !0 << lower_bound; + let upper_mask = !0 >> (Self::BITS_PER_WORD - upper_bound); + let mask = word & lower_mask & upper_mask; + + std::iter::from_fn({ + let mut remaining = mask; + move || { + if remaining == 0 { + None + } else { + let bit_idx = remaining.trailing_zeros(); + // Clear the lowest set bit + remaining &= remaining - 1; + Some(base_idx as u64 + bit_idx as u64) + } + } + }) + }) + } + + fn iter(&self) -> impl Iterator + '_ { + self.range(0..MAX_DATA_SHREDS_PER_SLOT as u64) + } +} + +impl FromIterator for ShredIndexV2 { + fn from_iter>(iter: T) -> Self { + let mut index = ShredIndexV2::default(); + for idx in iter { + index.insert(idx); + } + index + } +} + +impl FromIterator for ShredIndex { + fn from_iter>(iter: T) -> Self { + ShredIndex { + index: iter.into_iter().collect(), + } + } +} + +impl From for ShredIndexV2 { + fn from(value: ShredIndex) -> Self { + value.index.into_iter().collect() + } +} + +impl From for ShredIndex { + fn from(value: ShredIndexV2) -> Self { + ShredIndex { + index: value.iter().collect(), + } + } } impl SlotMeta { @@ -574,10 +830,13 @@ impl OptimisticSlotMetaVersioned { } } } + #[cfg(test)] mod test { use { super::*, + bincode::Options, + proptest::prelude::*, rand::{seq::SliceRandom, thread_rng}, }; @@ -624,7 +883,7 @@ mod test { .collect::>() .choose_multiple(&mut rng, erasure_config.num_data) { - index.data_mut().index.remove(&idx); + index.data_mut().remove(idx); assert!(e_meta.should_recover_shreds(&index)); } @@ -637,12 +896,216 @@ mod test { .collect::>() .choose_multiple(&mut rng, erasure_config.num_coding) { - index.coding_mut().index.remove(&idx); + index.coding_mut().remove(idx); assert!(!e_meta.should_recover_shreds(&index)); } } + /// Generate a random Range. + fn rand_range(range: Range) -> impl Strategy> { + (range.clone(), range).prop_map( + // Avoid descending (empty) ranges + |(start, end)| { + if start > end { + end..start + } else { + start..end + } + }, + ) + } + + proptest! { + #[test] + fn shred_index_legacy_compat( + shreds in rand_range(0..MAX_DATA_SHREDS_PER_SLOT as u64), + range in rand_range(0..MAX_DATA_SHREDS_PER_SLOT as u64) + ) { + let mut legacy = ShredIndex::default(); + let mut v2 = ShredIndexV2::default(); + + for i in shreds { + v2.insert(i); + legacy.insert(i); + } + + for &i in legacy.index.iter() { + assert!(v2.contains(i)); + } + + assert_eq!(v2.num_shreds(), legacy.num_shreds()); + + assert_eq!( + v2.range(range.clone()).sum::(), + legacy.range(range).sum::() + ); + + assert_eq!(ShredIndexV2::from(legacy.clone()), v2.clone()); + assert_eq!(ShredIndex::from(v2), legacy); + } + + /// Property: [`Index`] cannot be deserialized from [`IndexV2`]. + /// + /// # Failure cases + /// 1. Empty [`IndexV2`] + /// - [`ShredIndex`] deserialization should fail due to trailing bytes of `num_shreds`. + /// 2. Non-empty [`IndexV2`] + /// - Encoded length of [`ShredIndexV2::index`] (`Vec`) will be relative to a sequence of `u8`, + /// resulting in not enough bytes when deserialized into sequence of `u64`. + #[test] + fn test_legacy_collision( + coding_indices in rand_range(0..MAX_DATA_SHREDS_PER_SLOT as u64), + data_indices in rand_range(0..MAX_DATA_SHREDS_PER_SLOT as u64), + slot in 0..u64::MAX + ) { + let index = IndexV2 { + coding: coding_indices.into_iter().collect(), + data: data_indices.into_iter().collect(), + slot, + }; + let config = bincode::DefaultOptions::new().with_fixint_encoding().reject_trailing_bytes(); + let legacy = config.deserialize::(&config.serialize(&index).unwrap()); + prop_assert!(legacy.is_err()); + } + + /// Property: [`IndexV2`] cannot be deserialized from [`Index`]. + /// + /// # Failure cases + /// 1. Empty [`Index`] + /// - [`ShredIndexV2`] deserialization should fail due to missing `num_shreds` (not enough bytes). + /// 2. Non-empty [`Index`] + /// - Encoded length of [`ShredIndex::index`] (`BTreeSet`) will be relative to a sequence of `u64`, + /// resulting in trailing bytes when deserialized into sequence of `u8`. + #[test] + fn test_legacy_collision_inverse( + coding_indices in rand_range(0..MAX_DATA_SHREDS_PER_SLOT as u64), + data_indices in rand_range(0..MAX_DATA_SHREDS_PER_SLOT as u64), + slot in 0..u64::MAX + ) { + let index = Index { + coding: coding_indices.into_iter().collect(), + data: data_indices.into_iter().collect(), + slot, + }; + let config = bincode::DefaultOptions::new() + .with_fixint_encoding() + .reject_trailing_bytes(); + let v2 = config.deserialize::(&config.serialize(&index).unwrap()); + prop_assert!(v2.is_err()); + } + + // Property: range queries should return correct indices + #[test] + fn range_query_correctness( + indices in rand_range(0..MAX_DATA_SHREDS_PER_SLOT as u64), + ) { + let mut index = ShredIndexV2::default(); + + for idx in indices.clone() { + index.insert(idx); + } + + assert_eq!( + index.range(indices.clone()).collect::>(), + indices.into_iter().collect::>() + ); + } + } + + #[test] + fn test_shred_index_v2_range_bounds() { + let mut index = ShredIndexV2::default(); + + index.insert(10); + index.insert(20); + index.insert(30); + index.insert(40); + + use std::ops::Bound::*; + + // Test all combinations of bounds + let test_cases = [ + // (start_bound, end_bound, expected_result) + (Included(10), Included(30), vec![10, 20, 30]), + (Included(10), Excluded(30), vec![10, 20]), + (Excluded(10), Included(30), vec![20, 30]), + (Excluded(10), Excluded(30), vec![20]), + // Unbounded start + (Unbounded, Included(20), vec![10, 20]), + (Unbounded, Excluded(20), vec![10]), + // Unbounded end + (Included(30), Unbounded, vec![30, 40]), + (Excluded(30), Unbounded, vec![40]), + // Both Unbounded + (Unbounded, Unbounded, vec![10, 20, 30, 40]), + ]; + + for (start_bound, end_bound, expected) in test_cases { + let result: Vec<_> = index.range((start_bound, end_bound)).collect(); + assert_eq!( + result, expected, + "Failed for bounds: start={:?}, end={:?}", + start_bound, end_bound + ); + } + } + + #[test] + fn test_shred_index_v2_boundary_conditions() { + let mut index = ShredIndexV2::default(); + + // First possible index + index.insert(0); + // Last index in first word (bits 0-7) + index.insert(7); + // First index in second word (bits 8-15) + index.insert(8); + // Last index in second word + index.insert(15); + // Last valid index + index.insert(MAX_DATA_SHREDS_PER_SLOT as u64 - 1); + // Should be ignored (too large) + index.insert(MAX_DATA_SHREDS_PER_SLOT as u64); + + // Verify contents + assert!(index.contains(0)); + assert!(index.contains(7)); + assert!(index.contains(8)); + assert!(index.contains(15)); + assert!(index.contains(MAX_DATA_SHREDS_PER_SLOT as u64 - 1)); + assert!(!index.contains(MAX_DATA_SHREDS_PER_SLOT as u64)); + + // Cross-word boundary + assert_eq!(index.range(6..10).collect::>(), vec![7, 8]); + // Full first word + assert_eq!(index.range(0..8).collect::>(), vec![0, 7]); + // Full second word + assert_eq!(index.range(8..16).collect::>(), vec![8, 15]); + + // Empty ranges + assert_eq!(index.range(0..0).count(), 0); + assert_eq!(index.range(1..1).count(), 0); + + // Test range that exceeds max + let oversized_range = index.range(0..MAX_DATA_SHREDS_PER_SLOT as u64 + 1); + assert_eq!(oversized_range.count(), 5); + assert_eq!(index.num_shreds(), 5); + + index.remove(0); + assert!(!index.contains(0)); + index.remove(7); + assert!(!index.contains(7)); + index.remove(8); + assert!(!index.contains(8)); + index.remove(15); + assert!(!index.contains(15)); + index.remove(MAX_DATA_SHREDS_PER_SLOT as u64 - 1); + assert!(!index.contains(MAX_DATA_SHREDS_PER_SLOT as u64 - 1)); + + assert_eq!(index.num_shreds(), 0); + } + #[test] fn test_connected_flags_compatibility() { // Define a couple structs with bool and ConnectedFlags to illustrate diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 5c01911b5f9c6c..fdc51a15b3e438 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -683,6 +683,21 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -3884,6 +3899,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.7.0", + "lazy_static", + "num-traits", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "prost" version = "0.11.9" @@ -3982,6 +4017,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quinn" version = "0.11.6" @@ -4114,6 +4155,15 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rand_xoshiro" version = "0.6.0" @@ -4531,6 +4581,18 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.4" @@ -6077,6 +6139,7 @@ dependencies = [ "mockall", "num_cpus", "num_enum", + "proptest", "prost", "qualifier_attr", "rand 0.8.5", @@ -9660,6 +9723,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicase" version = "2.6.0" @@ -9831,6 +9900,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.3.1" diff --git a/svm/examples/Cargo.lock b/svm/examples/Cargo.lock index ae54e8a5a0e58d..2c10899753944b 100644 --- a/svm/examples/Cargo.lock +++ b/svm/examples/Cargo.lock @@ -603,6 +603,21 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -3775,6 +3790,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.7.0", + "lazy_static", + "num-traits", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "prost" version = "0.11.9" @@ -3873,6 +3908,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quinn" version = "0.11.6" @@ -4006,6 +4047,15 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rand_xoshiro" version = "0.6.0" @@ -4403,6 +4453,18 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.18" @@ -5907,6 +5969,7 @@ dependencies = [ "mockall", "num_cpus", "num_enum", + "proptest", "prost", "qualifier_attr", "rand 0.8.5", @@ -8978,6 +9041,12 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicase" version = "2.8.0" @@ -9140,6 +9209,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.5.0"