1use {
2 solana_clock::Slot, solana_commitment_config::CommitmentLevel,
3 solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY, std::collections::HashMap,
4};
5
6pub const VOTE_THRESHOLD_SIZE: f64 = 2f64 / 3f64;
7
8pub type BlockCommitmentArray = [u64; MAX_LOCKOUT_HISTORY + 1];
9
10#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
11pub struct BlockCommitment {
12 pub commitment: BlockCommitmentArray,
13}
14
15impl BlockCommitment {
16 pub fn increase_confirmation_stake(&mut self, confirmation_count: usize, stake: u64) {
17 assert!(confirmation_count > 0 && confirmation_count <= MAX_LOCKOUT_HISTORY);
18 self.commitment[confirmation_count - 1] += stake;
19 }
20
21 pub fn get_confirmation_stake(&mut self, confirmation_count: usize) -> u64 {
22 assert!(confirmation_count > 0 && confirmation_count <= MAX_LOCKOUT_HISTORY);
23 self.commitment[confirmation_count - 1]
24 }
25
26 pub fn increase_rooted_stake(&mut self, stake: u64) {
27 self.commitment[MAX_LOCKOUT_HISTORY] += stake;
28 }
29
30 pub fn get_rooted_stake(&self) -> u64 {
31 self.commitment[MAX_LOCKOUT_HISTORY]
32 }
33
34 pub fn new(commitment: BlockCommitmentArray) -> Self {
35 Self { commitment }
36 }
37}
38
39#[derive(Default)]
41pub struct BlockCommitmentCache {
42 block_commitment: HashMap<Slot, BlockCommitment>,
45 commitment_slots: CommitmentSlots,
48 total_stake: u64,
50}
51
52impl std::fmt::Debug for BlockCommitmentCache {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 f.debug_struct("BlockCommitmentCache")
55 .field("block_commitment", &self.block_commitment)
56 .field("total_stake", &self.total_stake)
57 .field(
58 "bank",
59 &format_args!("Bank({{current_slot: {:?}}})", self.commitment_slots.slot),
60 )
61 .field("root", &self.commitment_slots.root)
62 .finish()
63 }
64}
65
66impl BlockCommitmentCache {
67 pub fn new(
68 block_commitment: HashMap<Slot, BlockCommitment>,
69 total_stake: u64,
70 commitment_slots: CommitmentSlots,
71 ) -> Self {
72 Self {
73 block_commitment,
74 commitment_slots,
75 total_stake,
76 }
77 }
78
79 pub fn get_block_commitment(&self, slot: Slot) -> Option<&BlockCommitment> {
80 self.block_commitment.get(&slot)
81 }
82
83 pub fn total_stake(&self) -> u64 {
84 self.total_stake
85 }
86
87 pub fn slot(&self) -> Slot {
88 self.commitment_slots.slot
89 }
90
91 pub fn root(&self) -> Slot {
92 self.commitment_slots.root
93 }
94
95 pub fn highest_confirmed_slot(&self) -> Slot {
96 self.commitment_slots.highest_confirmed_slot
97 }
98
99 pub fn highest_super_majority_root(&self) -> Slot {
100 self.commitment_slots.highest_super_majority_root
101 }
102
103 pub fn commitment_slots(&self) -> CommitmentSlots {
104 self.commitment_slots
105 }
106
107 pub fn highest_gossip_confirmed_slot(&self) -> Slot {
108 self.highest_confirmed_slot()
111 }
112
113 pub fn slot_with_commitment(&self, commitment_level: CommitmentLevel) -> Slot {
114 match commitment_level {
115 CommitmentLevel::Processed => self.slot(),
116 CommitmentLevel::Confirmed => self.highest_gossip_confirmed_slot(),
117 CommitmentLevel::Finalized => self.highest_super_majority_root(),
118 }
119 }
120
121 fn highest_slot_with_confirmation_count(&self, confirmation_count: usize) -> Slot {
122 assert!(confirmation_count > 0 && confirmation_count <= MAX_LOCKOUT_HISTORY);
123 for slot in (self.root()..self.slot()).rev() {
124 if let Some(count) = self.get_confirmation_count(slot) {
125 if count >= confirmation_count {
126 return slot;
127 }
128 }
129 }
130 self.commitment_slots.root
131 }
132
133 pub fn calculate_highest_confirmed_slot(&self) -> Slot {
134 self.highest_slot_with_confirmation_count(1)
135 }
136
137 pub fn get_confirmation_count(&self, slot: Slot) -> Option<usize> {
138 self.get_lockout_count(slot, VOTE_THRESHOLD_SIZE)
139 }
140
141 fn get_lockout_count(&self, slot: Slot, minimum_stake_percentage: f64) -> Option<usize> {
144 self.get_block_commitment(slot).map(|block_commitment| {
145 let iterator = block_commitment.commitment.iter().enumerate().rev();
146 let mut sum = 0;
147 for (i, stake) in iterator {
148 sum += stake;
149 if (sum as f64 / self.total_stake as f64) > minimum_stake_percentage {
150 return i + 1;
151 }
152 }
153 0
154 })
155 }
156
157 pub fn new_for_tests() -> Self {
158 let mut block_commitment: HashMap<Slot, BlockCommitment> = HashMap::new();
159 block_commitment.insert(0, BlockCommitment::default());
160 Self {
161 block_commitment,
162 total_stake: 42,
163 ..Self::default()
164 }
165 }
166
167 pub fn new_for_tests_with_slots(slot: Slot, root: Slot) -> Self {
168 let mut block_commitment: HashMap<Slot, BlockCommitment> = HashMap::new();
169 block_commitment.insert(0, BlockCommitment::default());
170 Self {
171 block_commitment,
172 total_stake: 42,
173 commitment_slots: CommitmentSlots {
174 slot,
175 root,
176 highest_confirmed_slot: root,
177 highest_super_majority_root: root,
178 },
179 }
180 }
181
182 pub fn set_highest_confirmed_slot(&mut self, slot: Slot) {
183 self.commitment_slots.highest_confirmed_slot = slot;
184 }
185
186 pub fn set_highest_super_majority_root(&mut self, root: Slot) {
187 self.commitment_slots.highest_super_majority_root = root;
188 }
189
190 pub fn initialize_slots(&mut self, slot: Slot, root: Slot) {
191 self.commitment_slots.slot = slot;
192 self.commitment_slots.root = root;
193 }
194
195 pub fn set_all_slots(&mut self, slot: Slot, root: Slot) {
196 self.commitment_slots.slot = slot;
197 self.commitment_slots.highest_confirmed_slot = slot;
198 self.commitment_slots.root = root;
199 self.commitment_slots.highest_super_majority_root = root;
200 }
201}
202
203#[derive(Default, Clone, Copy)]
204pub struct CommitmentSlots {
205 pub slot: Slot,
207 pub root: Slot,
209 pub highest_confirmed_slot: Slot,
211 pub highest_super_majority_root: Slot,
213}
214
215impl CommitmentSlots {
216 pub fn new_from_slot(slot: Slot) -> Self {
217 Self {
218 slot,
219 ..Self::default()
220 }
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 #[test]
229 fn test_block_commitment() {
230 let mut cache = BlockCommitment::default();
231 assert_eq!(cache.get_confirmation_stake(1), 0);
232 cache.increase_confirmation_stake(1, 10);
233 assert_eq!(cache.get_confirmation_stake(1), 10);
234 cache.increase_confirmation_stake(1, 20);
235 assert_eq!(cache.get_confirmation_stake(1), 30);
236 }
237
238 #[test]
239 fn test_get_confirmations() {
240 let mut cache0 = BlockCommitment::default();
242 cache0.increase_confirmation_stake(1, 5);
243 cache0.increase_confirmation_stake(2, 40);
244
245 let mut cache1 = BlockCommitment::default();
246 cache1.increase_confirmation_stake(1, 40);
247 cache1.increase_confirmation_stake(2, 5);
248
249 let mut cache2 = BlockCommitment::default();
250 cache2.increase_confirmation_stake(1, 20);
251 cache2.increase_confirmation_stake(2, 5);
252
253 let mut block_commitment = HashMap::new();
254 block_commitment.entry(0).or_insert(cache0);
255 block_commitment.entry(1).or_insert(cache1);
256 block_commitment.entry(2).or_insert(cache2);
257 let block_commitment_cache = BlockCommitmentCache {
258 block_commitment,
259 total_stake: 50,
260 ..BlockCommitmentCache::default()
261 };
262
263 assert_eq!(block_commitment_cache.get_confirmation_count(0), Some(2));
264 assert_eq!(block_commitment_cache.get_confirmation_count(1), Some(1));
265 assert_eq!(block_commitment_cache.get_confirmation_count(2), Some(0),);
266 assert_eq!(block_commitment_cache.get_confirmation_count(3), None,);
267 }
268
269 #[test]
270 fn test_highest_confirmed_slot() {
271 let bank_slot_5 = 5;
272 let total_stake = 50;
273
274 let mut cache0 = BlockCommitment::default();
276 cache0.increase_confirmation_stake(1, 5);
277 cache0.increase_confirmation_stake(2, 40);
278
279 let mut cache1 = BlockCommitment::default();
281 cache1.increase_confirmation_stake(1, 40);
282 cache1.increase_confirmation_stake(2, 5);
283
284 let mut cache2 = BlockCommitment::default();
286 cache2.increase_confirmation_stake(1, 20);
287 cache2.increase_confirmation_stake(2, 5);
288
289 let mut block_commitment = HashMap::new();
290 block_commitment.entry(1).or_insert_with(|| cache0.clone()); block_commitment.entry(2).or_insert_with(|| cache1.clone()); block_commitment.entry(3).or_insert_with(|| cache2.clone()); let commitment_slots = CommitmentSlots::new_from_slot(bank_slot_5);
294 let block_commitment_cache =
295 BlockCommitmentCache::new(block_commitment, total_stake, commitment_slots);
296
297 assert_eq!(block_commitment_cache.calculate_highest_confirmed_slot(), 2);
298
299 let mut block_commitment = HashMap::new();
301 block_commitment.entry(1).or_insert_with(|| cache1.clone()); block_commitment.entry(2).or_insert_with(|| cache1.clone()); block_commitment.entry(3).or_insert_with(|| cache2.clone()); let block_commitment_cache =
305 BlockCommitmentCache::new(block_commitment, total_stake, commitment_slots);
306
307 assert_eq!(block_commitment_cache.calculate_highest_confirmed_slot(), 2);
308
309 let mut block_commitment = HashMap::new();
311 block_commitment.entry(1).or_insert_with(|| cache1.clone()); block_commitment.entry(3).or_insert(cache1); block_commitment.entry(5).or_insert_with(|| cache2.clone()); let block_commitment_cache =
315 BlockCommitmentCache::new(block_commitment, total_stake, commitment_slots);
316
317 assert_eq!(block_commitment_cache.calculate_highest_confirmed_slot(), 3);
318
319 let mut block_commitment = HashMap::new();
321 block_commitment.entry(1).or_insert(cache0); block_commitment.entry(2).or_insert_with(|| cache2.clone()); block_commitment.entry(3).or_insert_with(|| cache2.clone()); let block_commitment_cache =
325 BlockCommitmentCache::new(block_commitment, total_stake, commitment_slots);
326
327 assert_eq!(block_commitment_cache.calculate_highest_confirmed_slot(), 1);
328
329 let mut block_commitment = HashMap::new();
331 block_commitment.entry(1).or_insert_with(|| cache2.clone()); block_commitment.entry(2).or_insert_with(|| cache2.clone()); block_commitment.entry(3).or_insert(cache2); let block_commitment_cache =
335 BlockCommitmentCache::new(block_commitment, total_stake, commitment_slots);
336
337 assert_eq!(block_commitment_cache.calculate_highest_confirmed_slot(), 0);
338 }
339}