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