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