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 = 2f64 / 3f64;
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_super_majority_root(&self) -> Slot {
101 self.commitment_slots.highest_super_majority_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 pub fn slot_with_commitment(&self, commitment_level: CommitmentLevel) -> Slot {
115 match commitment_level {
116 CommitmentLevel::Processed => self.slot(),
117 CommitmentLevel::Confirmed => self.highest_gossip_confirmed_slot(),
118 CommitmentLevel::Finalized => self.highest_super_majority_root(),
119 }
120 }
121
122 fn highest_slot_with_confirmation_count(&self, confirmation_count: usize) -> Slot {
123 assert!(confirmation_count > 0 && confirmation_count <= MAX_LOCKOUT_HISTORY);
124 for slot in (self.root()..self.slot()).rev() {
125 if let Some(count) = self.get_confirmation_count(slot) {
126 if count >= confirmation_count {
127 return slot;
128 }
129 }
130 }
131 self.commitment_slots.root
132 }
133
134 pub fn calculate_highest_confirmed_slot(&self) -> Slot {
135 self.highest_slot_with_confirmation_count(1)
136 }
137
138 pub fn get_confirmation_count(&self, slot: Slot) -> Option<usize> {
139 self.get_lockout_count(slot, VOTE_THRESHOLD_SIZE)
140 }
141
142 fn get_lockout_count(&self, slot: Slot, minimum_stake_percentage: f64) -> Option<usize> {
145 self.get_block_commitment(slot).map(|block_commitment| {
146 let iterator = block_commitment.commitment.iter().enumerate().rev();
147 let mut sum = 0;
148 for (i, stake) in iterator {
149 sum += stake;
150 if (sum as f64 / self.total_stake as f64) > minimum_stake_percentage {
151 return i + 1;
152 }
153 }
154 0
155 })
156 }
157
158 pub fn new_for_tests() -> Self {
159 let mut block_commitment: HashMap<Slot, BlockCommitment> = HashMap::new();
160 block_commitment.insert(0, BlockCommitment::default());
161 Self {
162 block_commitment,
163 total_stake: 42,
164 ..Self::default()
165 }
166 }
167
168 pub fn new_for_tests_with_slots(slot: Slot, root: Slot) -> Self {
169 let mut block_commitment: HashMap<Slot, BlockCommitment> = HashMap::new();
170 block_commitment.insert(0, BlockCommitment::default());
171 Self {
172 block_commitment,
173 total_stake: 42,
174 commitment_slots: CommitmentSlots {
175 slot,
176 root,
177 highest_confirmed_slot: root,
178 highest_super_majority_root: root,
179 },
180 }
181 }
182
183 pub fn set_highest_confirmed_slot(&mut self, slot: Slot) {
184 self.commitment_slots.highest_confirmed_slot = slot;
185 }
186
187 pub fn set_highest_super_majority_root(&mut self, root: Slot) {
188 self.commitment_slots.highest_super_majority_root = root;
189 }
190
191 pub fn initialize_slots(&mut self, slot: Slot, root: Slot) {
192 self.commitment_slots.slot = slot;
193 self.commitment_slots.root = root;
194 }
195
196 pub fn set_all_slots(&mut self, slot: Slot, root: Slot) {
197 self.commitment_slots.slot = slot;
198 self.commitment_slots.highest_confirmed_slot = slot;
199 self.commitment_slots.root = root;
200 self.commitment_slots.highest_super_majority_root = root;
201 }
202}
203
204#[derive(Default, Clone, Copy)]
205pub struct CommitmentSlots {
206 pub slot: Slot,
208 pub root: Slot,
210 pub highest_confirmed_slot: Slot,
212 pub highest_super_majority_root: Slot,
214}
215
216impl CommitmentSlots {
217 pub fn new_from_slot(slot: Slot) -> Self {
218 Self {
219 slot,
220 ..Self::default()
221 }
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn test_block_commitment() {
231 let mut cache = BlockCommitment::default();
232 assert_eq!(cache.get_confirmation_stake(1), 0);
233 cache.increase_confirmation_stake(1, 10);
234 assert_eq!(cache.get_confirmation_stake(1), 10);
235 cache.increase_confirmation_stake(1, 20);
236 assert_eq!(cache.get_confirmation_stake(1), 30);
237 }
238
239 #[test]
240 fn test_get_confirmations() {
241 let mut cache0 = BlockCommitment::default();
243 cache0.increase_confirmation_stake(1, 5);
244 cache0.increase_confirmation_stake(2, 40);
245
246 let mut cache1 = BlockCommitment::default();
247 cache1.increase_confirmation_stake(1, 40);
248 cache1.increase_confirmation_stake(2, 5);
249
250 let mut cache2 = BlockCommitment::default();
251 cache2.increase_confirmation_stake(1, 20);
252 cache2.increase_confirmation_stake(2, 5);
253
254 let mut block_commitment = HashMap::new();
255 block_commitment.entry(0).or_insert(cache0);
256 block_commitment.entry(1).or_insert(cache1);
257 block_commitment.entry(2).or_insert(cache2);
258 let block_commitment_cache = BlockCommitmentCache {
259 block_commitment,
260 total_stake: 50,
261 ..BlockCommitmentCache::default()
262 };
263
264 assert_eq!(block_commitment_cache.get_confirmation_count(0), Some(2));
265 assert_eq!(block_commitment_cache.get_confirmation_count(1), Some(1));
266 assert_eq!(block_commitment_cache.get_confirmation_count(2), Some(0),);
267 assert_eq!(block_commitment_cache.get_confirmation_count(3), None,);
268 }
269
270 #[test]
271 fn test_highest_confirmed_slot() {
272 let bank_slot_5 = 5;
273 let total_stake = 50;
274
275 let mut cache0 = BlockCommitment::default();
277 cache0.increase_confirmation_stake(1, 5);
278 cache0.increase_confirmation_stake(2, 40);
279
280 let mut cache1 = BlockCommitment::default();
282 cache1.increase_confirmation_stake(1, 40);
283 cache1.increase_confirmation_stake(2, 5);
284
285 let mut cache2 = BlockCommitment::default();
287 cache2.increase_confirmation_stake(1, 20);
288 cache2.increase_confirmation_stake(2, 5);
289
290 let mut block_commitment = HashMap::new();
291 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);
295 let block_commitment_cache =
296 BlockCommitmentCache::new(block_commitment, total_stake, commitment_slots);
297
298 assert_eq!(block_commitment_cache.calculate_highest_confirmed_slot(), 2);
299
300 let mut block_commitment = HashMap::new();
302 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 =
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(3).or_insert(cache1); block_commitment.entry(5).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(), 3);
319
320 let mut block_commitment = HashMap::new();
322 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 =
326 BlockCommitmentCache::new(block_commitment, total_stake, commitment_slots);
327
328 assert_eq!(block_commitment_cache.calculate_highest_confirmed_slot(), 1);
329
330 let mut block_commitment = HashMap::new();
332 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 =
336 BlockCommitmentCache::new(block_commitment, total_stake, commitment_slots);
337
338 assert_eq!(block_commitment_cache.calculate_highest_confirmed_slot(), 0);
339 }
340}