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