near_primitives/stateless_validation/
chunk_endorsements_bitmap.rs

1use bitvec::prelude::*;
2use borsh::{BorshDeserialize, BorshSerialize};
3use near_schema_checker_lib::ProtocolSchema;
4
5/// Represents a collection of bitmaps, one per shard, to store whether the endorsements from the chunk validators has been received.
6///
7/// For each shard, the endorsements are encoded as a sequence of bits: 1 means endorsement received and 0 means not received.
8/// While the number of chunk validator seats is fixed, the number of chunk-validator assignments may be smaller and may change,
9/// since the seats are assigned to validators weighted by their stake. Thus, we represent the bits as a vector of bytes.
10/// The number of assignments may be less or equal to the number of total bytes. This representation allows increasing
11/// the chunk validator seats in the future (which will be represented by a vector of greater length).
12#[derive(
13    BorshSerialize,
14    BorshDeserialize,
15    serde::Serialize,
16    serde::Deserialize,
17    Debug,
18    Clone,
19    Eq,
20    PartialEq,
21    Default,
22    ProtocolSchema,
23)]
24pub struct ChunkEndorsementsBitmap {
25    inner: Vec<Vec<u8>>,
26}
27
28/// Type of BitVec used in the internal implementation.
29type BitVecType = BitVec<u8, Lsb0>;
30
31impl ChunkEndorsementsBitmap {
32    /// Creates an empty endorsement bitmap for each shard.
33    pub fn new(num_shards: usize) -> Self {
34        Self { inner: vec![Default::default(); num_shards] }
35    }
36
37    /// Returns the bitmap specifically for the genesis block.
38    /// This matches the chunk endorsement signatures in the genesis block body,
39    /// which is an empty vector (no shards).
40    pub fn genesis() -> Self {
41        Self::new(0)
42    }
43
44    /// Creates an endorsement bitmap from the provided bytes.
45    pub fn from_bytes(bytes: Vec<Vec<u8>>) -> Self {
46        Self { inner: bytes }
47    }
48
49    /// Returns the endorsements bits as a vector of bytes.
50    /// The struct can be reconstructed by providing the output of this function to `new_from_bits`.
51    pub fn bytes(&self) -> Vec<Vec<u8>> {
52        self.inner.clone()
53    }
54
55    // Creates an endorsement bitmap for all the shards.
56    pub fn from_endorsements(shards_to_endorsements: Vec<Vec<bool>>) -> Self {
57        let mut bitmap = ChunkEndorsementsBitmap::new(shards_to_endorsements.len());
58        for (shard_index, endorsements) in shards_to_endorsements.into_iter().enumerate() {
59            bitmap.add_endorsements(shard_index, endorsements);
60        }
61        bitmap
62    }
63
64    /// Adds the provided endorsements to the bitmap for the specified shard.
65    pub fn add_endorsements(&mut self, shard_index: usize, endorsements: Vec<bool>) {
66        let bitvec: BitVecType = endorsements.iter().collect();
67        self.inner[shard_index] = bitvec.into();
68    }
69
70    /// Returns an iterator over the endorsements (yields true if the endorsement for the respective position was received).
71    pub fn iter(&self, shard_index: usize) -> Box<dyn Iterator<Item = bool>> {
72        let bitvec = BitVecType::from_vec(self.inner[shard_index].clone());
73        Box::new(bitvec.into_iter())
74    }
75
76    /// Returns the number of shards in the endorsements.
77    pub fn num_shards(&self) -> usize {
78        self.inner.len()
79    }
80
81    /// Returns the full length of the bitmap for a given shard.
82    /// Note that the size may be greater than the number of validator assignments.
83    pub fn len(&self, shard_index: usize) -> Option<usize> {
84        self.inner.get(shard_index).map(|v| v.len() * 8)
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::ChunkEndorsementsBitmap;
91    use itertools::Itertools;
92    use rand::Rng;
93
94    const NUM_SHARDS: usize = 4;
95
96    /// Asserts that the bitmap encodes the expected endorsements and zeros for the other bits.
97    fn assert_bitmap(
98        bitmap: &ChunkEndorsementsBitmap,
99        num_assignments: usize,
100        expected_endorsements: &Vec<Vec<bool>>,
101    ) {
102        // Endorsements from the bitmap iterator must match the endorsements given previously.
103        for (shard_index, endorsements) in expected_endorsements.iter().enumerate() {
104            let num_bits = bitmap.len(shard_index).unwrap();
105            let bits = bitmap.iter(shard_index).collect_vec();
106            // Number of bits must be equal to the size of the bit iterator for the corresponding shard.
107            assert_eq!(num_bits, bits.len());
108            // Bitmap must contain the minimal number of bits to represent the endorsements.
109            assert_eq!(num_bits, num_assignments.div_ceil(8) * 8);
110            // Endorsements from the bitmap iterator must match the endorsements given previously.
111            let mut expected_bits = endorsements.clone();
112            // All remaining bits after the endorsement bits must be false.
113            expected_bits.extend(std::iter::repeat(false).take(num_bits - num_assignments));
114            assert_eq!(bits, expected_bits);
115        }
116    }
117
118    fn run_bitmap_test(num_assignments: usize, num_produced: usize) {
119        let mut rng = rand::thread_rng();
120        let mut bitmap = ChunkEndorsementsBitmap::new(NUM_SHARDS);
121        let mut expected_endorsements = vec![];
122        for shard_index in 0..NUM_SHARDS {
123            let mut endorsements = vec![false; num_assignments];
124            for _ in 0..num_produced {
125                endorsements[rng.gen_range(0..num_assignments)] = true;
126            }
127            expected_endorsements.push(endorsements.clone());
128            bitmap.add_endorsements(shard_index, endorsements);
129        }
130        // Check before serialization.
131        assert_bitmap(&bitmap, num_assignments, &expected_endorsements);
132
133        // Check after serialization.
134        let serialized = borsh::to_vec(&bitmap).unwrap();
135        let bitmap = borsh::from_slice::<ChunkEndorsementsBitmap>(&serialized).unwrap();
136        assert_bitmap(&bitmap, num_assignments, &expected_endorsements);
137    }
138
139    #[test]
140    fn test_chunk_endorsements_bitmap() {
141        run_bitmap_test(0, 0);
142        run_bitmap_test(60, 20);
143        run_bitmap_test(68, 20);
144        run_bitmap_test(125, 30);
145        run_bitmap_test(130, 50);
146        run_bitmap_test(300, 100);
147    }
148}