1use {
2 crate::cluster_info_vote_listener::VoteTracker,
3 solana_clock::Slot,
4 solana_hash::Hash,
5 solana_ledger::blockstore::Blockstore,
6 solana_runtime::bank::Bank,
7 solana_time_utils::timestamp,
8 std::{collections::BTreeSet, time::Instant},
9};
10
11pub struct OptimisticConfirmationVerifier {
12 snapshot_start_slot: Slot,
13 unchecked_slots: BTreeSet<(Slot, Hash)>,
14 last_optimistic_slot_ts: Instant,
15}
16
17impl OptimisticConfirmationVerifier {
18 pub fn new(snapshot_start_slot: Slot) -> Self {
19 Self {
20 snapshot_start_slot,
21 unchecked_slots: BTreeSet::default(),
22 last_optimistic_slot_ts: Instant::now(),
23 }
24 }
25
26 pub fn verify_for_unrooted_optimistic_slots(
28 &mut self,
29 root_bank: &Bank,
30 blockstore: &Blockstore,
31 ) -> Vec<(Slot, Hash)> {
32 let root = root_bank.slot();
33 let root_ancestors = &root_bank.ancestors;
34 let slots_after_root = self
35 .unchecked_slots
36 .split_off(&((root + 1), Hash::default()));
37 let slots_before_root = std::mem::replace(&mut self.unchecked_slots, slots_after_root);
39 slots_before_root
40 .into_iter()
41 .filter(|(optimistic_slot, optimistic_hash)| {
42 (*optimistic_slot == root && *optimistic_hash != root_bank.hash())
43 || (!root_ancestors.contains_key(optimistic_slot) &&
44 !blockstore.is_root(*optimistic_slot))
53 })
54 .collect()
55 }
56
57 pub fn add_new_optimistic_confirmed_slots(
58 &mut self,
59 new_optimistic_slots: Vec<(Slot, Hash)>,
60 blockstore: &Blockstore,
61 ) {
62 if new_optimistic_slots.is_empty() {
63 return;
64 }
65
66 for (new_optimistic_slot, hash) in new_optimistic_slots {
69 if new_optimistic_slot > self.snapshot_start_slot {
70 if let Err(e) = blockstore.insert_optimistic_slot(
71 new_optimistic_slot,
72 &hash,
73 timestamp().try_into().unwrap(),
74 ) {
75 error!(
76 "failed to record optimistic slot in blockstore: slot={}: {:?}",
77 new_optimistic_slot, &e
78 );
79 }
80 datapoint_info!("optimistic_slot", ("slot", new_optimistic_slot, i64),);
81 self.unchecked_slots.insert((new_optimistic_slot, hash));
82 }
83 }
84
85 self.last_optimistic_slot_ts = Instant::now();
86 }
87
88 pub fn format_optimistic_confirmed_slot_violation_log(slot: Slot) -> String {
89 format!("Optimistically confirmed slot {slot} was not rooted")
90 }
91
92 pub fn log_unrooted_optimistic_slots(
93 root_bank: &Bank,
94 vote_tracker: &VoteTracker,
95 unrooted_optimistic_slots: &[(Slot, Hash)],
96 ) {
97 let root = root_bank.slot();
98 for (optimistic_slot, hash) in unrooted_optimistic_slots.iter() {
99 let epoch = root_bank.epoch_schedule().get_epoch(*optimistic_slot);
100 let epoch_stakes = root_bank.epoch_stakes(epoch);
101 let total_epoch_stake = epoch_stakes.map(|e| e.total_stake()).unwrap_or(0);
102 let voted_stake = {
103 let slot_tracker = vote_tracker.get_slot_vote_tracker(*optimistic_slot);
104 let r_slot_tracker = slot_tracker.as_ref().map(|s| s.read().unwrap());
105 let voted_stake = r_slot_tracker
106 .as_ref()
107 .and_then(|s| s.optimistic_votes_tracker(hash))
108 .map(|s| s.stake())
109 .unwrap_or(0);
110
111 error!(
112 "{}, hash: {hash}, epoch: {epoch}, voted keys: {:?}, root: {root}, root bank \
113 hash: {}, voted stake: {voted_stake}, total epoch stake: \
114 {total_epoch_stake}, pct: {}",
115 Self::format_optimistic_confirmed_slot_violation_log(*optimistic_slot),
116 r_slot_tracker
117 .as_ref()
118 .and_then(|s| s.optimistic_votes_tracker(hash))
119 .map(|s| s.voted()),
120 root_bank.hash(),
121 voted_stake as f64 / total_epoch_stake as f64,
122 );
123 voted_stake
124 };
125
126 datapoint_warn!(
127 "optimistic_slot_not_rooted",
128 ("slot", *optimistic_slot, i64),
129 ("epoch", epoch, i64),
130 ("root", root, i64),
131 ("voted_stake", voted_stake, i64),
132 ("total_epoch_stake", total_epoch_stake, i64),
133 );
134 }
135 }
136}
137
138#[cfg(test)]
139mod test {
140 use {
141 super::*, crate::vote_simulator::VoteSimulator,
142 solana_ledger::get_tmp_ledger_path_auto_delete, solana_pubkey::Pubkey,
143 solana_runtime::bank::Bank, std::collections::HashMap, trees::tr,
144 };
145
146 #[test]
147 fn test_add_new_optimistic_confirmed_slots() {
148 let snapshot_start_slot = 10;
149 let bank_hash = Hash::default();
150 let mut optimistic_confirmation_verifier =
151 OptimisticConfirmationVerifier::new(snapshot_start_slot);
152 let blockstore_path = get_tmp_ledger_path_auto_delete!();
153 let blockstore = Blockstore::open(blockstore_path.path()).unwrap();
154 optimistic_confirmation_verifier.add_new_optimistic_confirmed_slots(
155 vec![(snapshot_start_slot - 1, bank_hash)],
156 &blockstore,
157 );
158 assert_eq!(blockstore.get_latest_optimistic_slots(10).unwrap().len(), 0);
159 optimistic_confirmation_verifier.add_new_optimistic_confirmed_slots(
160 vec![(snapshot_start_slot, bank_hash)],
161 &blockstore,
162 );
163 assert_eq!(blockstore.get_latest_optimistic_slots(10).unwrap().len(), 0);
164 optimistic_confirmation_verifier.add_new_optimistic_confirmed_slots(
165 vec![(snapshot_start_slot + 1, bank_hash)],
166 &blockstore,
167 );
168 assert_eq!(blockstore.get_latest_optimistic_slots(10).unwrap().len(), 1);
169 assert_eq!(optimistic_confirmation_verifier.unchecked_slots.len(), 1);
170 assert!(optimistic_confirmation_verifier
171 .unchecked_slots
172 .contains(&(snapshot_start_slot + 1, bank_hash)));
173 }
174
175 #[test]
176 fn test_get_unrooted_optimistic_slots_same_slot_different_hash() {
177 let snapshot_start_slot = 0;
178 let mut optimistic_confirmation_verifier =
179 OptimisticConfirmationVerifier::new(snapshot_start_slot);
180 let bad_bank_hash = Hash::new_from_array([42u8; 32]);
181 let blockstore_path = get_tmp_ledger_path_auto_delete!();
182 let blockstore = Blockstore::open(blockstore_path.path()).unwrap();
183 let optimistic_slots = vec![(1, bad_bank_hash), (3, Hash::default())];
184 optimistic_confirmation_verifier
185 .add_new_optimistic_confirmed_slots(optimistic_slots, &blockstore);
186 assert_eq!(blockstore.get_latest_optimistic_slots(10).unwrap().len(), 2);
187 let vote_simulator = setup_forks();
188 let bank1 = vote_simulator.bank_forks.read().unwrap().get(1).unwrap();
189 assert_eq!(
190 optimistic_confirmation_verifier
191 .verify_for_unrooted_optimistic_slots(&bank1, &blockstore),
192 vec![(1, bad_bank_hash)]
193 );
194 assert_eq!(optimistic_confirmation_verifier.unchecked_slots.len(), 1);
195 assert!(optimistic_confirmation_verifier
196 .unchecked_slots
197 .contains(&(3, Hash::default())));
198 }
199
200 #[test]
201 fn test_get_unrooted_optimistic_slots() {
202 let snapshot_start_slot = 0;
203 let mut optimistic_confirmation_verifier =
204 OptimisticConfirmationVerifier::new(snapshot_start_slot);
205 let blockstore_path = get_tmp_ledger_path_auto_delete!();
206 let blockstore = Blockstore::open(blockstore_path.path()).unwrap();
207 let mut vote_simulator = setup_forks();
208 let optimistic_slots: Vec<_> = vec![1, 3, 5]
209 .into_iter()
210 .map(|s| {
211 (
212 s,
213 vote_simulator
214 .bank_forks
215 .read()
216 .unwrap()
217 .get(s)
218 .unwrap()
219 .hash(),
220 )
221 })
222 .collect();
223
224 optimistic_confirmation_verifier
226 .add_new_optimistic_confirmed_slots(optimistic_slots.clone(), &blockstore);
227 assert_eq!(blockstore.get_latest_optimistic_slots(10).unwrap().len(), 3);
228 let bank5 = vote_simulator.bank_forks.read().unwrap().get(5).unwrap();
229 assert!(optimistic_confirmation_verifier
230 .verify_for_unrooted_optimistic_slots(&bank5, &blockstore)
231 .is_empty());
232 assert!(optimistic_confirmation_verifier.unchecked_slots.is_empty());
234
235 optimistic_confirmation_verifier
237 .add_new_optimistic_confirmed_slots(optimistic_slots.clone(), &blockstore);
238 let bank3 = vote_simulator.bank_forks.read().unwrap().get(3).unwrap();
239 assert!(optimistic_confirmation_verifier
240 .verify_for_unrooted_optimistic_slots(&bank3, &blockstore)
241 .is_empty());
242 assert_eq!(optimistic_confirmation_verifier.unchecked_slots.len(), 1);
244 assert!(optimistic_confirmation_verifier
245 .unchecked_slots
246 .contains(&optimistic_slots[2]));
247
248 optimistic_confirmation_verifier
251 .add_new_optimistic_confirmed_slots(optimistic_slots.clone(), &blockstore);
252 let bank4 = vote_simulator.bank_forks.read().unwrap().get(4).unwrap();
253 assert_eq!(
254 optimistic_confirmation_verifier
255 .verify_for_unrooted_optimistic_slots(&bank4, &blockstore),
256 vec![optimistic_slots[1]]
257 );
258 assert_eq!(optimistic_confirmation_verifier.unchecked_slots.len(), 1);
260 assert!(optimistic_confirmation_verifier
261 .unchecked_slots
262 .contains(&optimistic_slots[2]));
263
264 vote_simulator.set_root(5);
266
267 let bank6 = vote_simulator.bank_forks.read().unwrap().get(6).unwrap();
269 vote_simulator
270 .bank_forks
271 .write()
272 .unwrap()
273 .insert(Bank::new_from_parent(bank6, &Pubkey::default(), 7));
274 let bank7 = vote_simulator.bank_forks.read().unwrap().get(7).unwrap();
275 assert!(!bank7.ancestors.contains_key(&3));
276
277 optimistic_confirmation_verifier
280 .add_new_optimistic_confirmed_slots(optimistic_slots.clone(), &blockstore);
281 assert_eq!(
282 optimistic_confirmation_verifier
283 .verify_for_unrooted_optimistic_slots(&bank7, &blockstore),
284 optimistic_slots[0..=1].to_vec()
285 );
286 assert!(optimistic_confirmation_verifier.unchecked_slots.is_empty());
287
288 blockstore.set_roots([1, 3].iter()).unwrap();
290 optimistic_confirmation_verifier
291 .add_new_optimistic_confirmed_slots(optimistic_slots, &blockstore);
292 assert!(optimistic_confirmation_verifier
293 .verify_for_unrooted_optimistic_slots(&bank7, &blockstore)
294 .is_empty());
295 assert!(optimistic_confirmation_verifier.unchecked_slots.is_empty());
296 assert_eq!(blockstore.get_latest_optimistic_slots(10).unwrap().len(), 3);
297 }
298
299 fn setup_forks() -> VoteSimulator {
300 let forks = tr(0) / (tr(1) / (tr(2) / (tr(4))) / (tr(3) / (tr(5) / (tr(6)))));
314
315 let mut vote_simulator = VoteSimulator::new(1);
316 vote_simulator.fill_bank_forks(forks, &HashMap::new(), true);
317 vote_simulator
318 }
319}