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: \
77 slot={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, solana_leader_schedule::SlotLeader,
142 solana_ledger::get_tmp_ledger_path_auto_delete, solana_runtime::bank::Bank,
143 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!(
171 optimistic_confirmation_verifier
172 .unchecked_slots
173 .contains(&(snapshot_start_slot + 1, bank_hash))
174 );
175 }
176
177 #[test]
178 fn test_get_unrooted_optimistic_slots_same_slot_different_hash() {
179 let snapshot_start_slot = 0;
180 let mut optimistic_confirmation_verifier =
181 OptimisticConfirmationVerifier::new(snapshot_start_slot);
182 let bad_bank_hash = Hash::new_from_array([42u8; 32]);
183 let blockstore_path = get_tmp_ledger_path_auto_delete!();
184 let blockstore = Blockstore::open(blockstore_path.path()).unwrap();
185 let optimistic_slots = vec![(1, bad_bank_hash), (3, Hash::default())];
186 optimistic_confirmation_verifier
187 .add_new_optimistic_confirmed_slots(optimistic_slots, &blockstore);
188 assert_eq!(blockstore.get_latest_optimistic_slots(10).unwrap().len(), 2);
189 let vote_simulator = setup_forks();
190 let bank1 = vote_simulator.bank_forks.read().unwrap().get(1).unwrap();
191 assert_eq!(
192 optimistic_confirmation_verifier
193 .verify_for_unrooted_optimistic_slots(&bank1, &blockstore),
194 vec![(1, bad_bank_hash)]
195 );
196 assert_eq!(optimistic_confirmation_verifier.unchecked_slots.len(), 1);
197 assert!(
198 optimistic_confirmation_verifier
199 .unchecked_slots
200 .contains(&(3, Hash::default()))
201 );
202 }
203
204 #[test]
205 fn test_get_unrooted_optimistic_slots() {
206 let snapshot_start_slot = 0;
207 let mut optimistic_confirmation_verifier =
208 OptimisticConfirmationVerifier::new(snapshot_start_slot);
209 let blockstore_path = get_tmp_ledger_path_auto_delete!();
210 let blockstore = Blockstore::open(blockstore_path.path()).unwrap();
211 let mut vote_simulator = setup_forks();
212 let optimistic_slots: Vec<_> = vec![1, 3, 5]
213 .into_iter()
214 .map(|s| {
215 (
216 s,
217 vote_simulator
218 .bank_forks
219 .read()
220 .unwrap()
221 .get(s)
222 .unwrap()
223 .hash(),
224 )
225 })
226 .collect();
227
228 optimistic_confirmation_verifier
230 .add_new_optimistic_confirmed_slots(optimistic_slots.clone(), &blockstore);
231 assert_eq!(blockstore.get_latest_optimistic_slots(10).unwrap().len(), 3);
232 let bank5 = vote_simulator.bank_forks.read().unwrap().get(5).unwrap();
233 assert!(
234 optimistic_confirmation_verifier
235 .verify_for_unrooted_optimistic_slots(&bank5, &blockstore)
236 .is_empty()
237 );
238 assert!(optimistic_confirmation_verifier.unchecked_slots.is_empty());
240
241 optimistic_confirmation_verifier
243 .add_new_optimistic_confirmed_slots(optimistic_slots.clone(), &blockstore);
244 let bank3 = vote_simulator.bank_forks.read().unwrap().get(3).unwrap();
245 assert!(
246 optimistic_confirmation_verifier
247 .verify_for_unrooted_optimistic_slots(&bank3, &blockstore)
248 .is_empty()
249 );
250 assert_eq!(optimistic_confirmation_verifier.unchecked_slots.len(), 1);
252 assert!(
253 optimistic_confirmation_verifier
254 .unchecked_slots
255 .contains(&optimistic_slots[2])
256 );
257
258 optimistic_confirmation_verifier
261 .add_new_optimistic_confirmed_slots(optimistic_slots.clone(), &blockstore);
262 let bank4 = vote_simulator.bank_forks.read().unwrap().get(4).unwrap();
263 assert_eq!(
264 optimistic_confirmation_verifier
265 .verify_for_unrooted_optimistic_slots(&bank4, &blockstore),
266 vec![optimistic_slots[1]]
267 );
268 assert_eq!(optimistic_confirmation_verifier.unchecked_slots.len(), 1);
270 assert!(
271 optimistic_confirmation_verifier
272 .unchecked_slots
273 .contains(&optimistic_slots[2])
274 );
275
276 vote_simulator.set_root(5);
278
279 let bank6 = vote_simulator.bank_forks.read().unwrap().get(6).unwrap();
281 let bank7_new = Bank::new_from_parent(bank6, SlotLeader::default(), 7);
282 vote_simulator.bank_forks.write().unwrap().insert(bank7_new);
283 let bank7 = vote_simulator.bank_forks.read().unwrap().get(7).unwrap();
284 assert!(!bank7.ancestors.contains_key(&3));
285
286 optimistic_confirmation_verifier
289 .add_new_optimistic_confirmed_slots(optimistic_slots.clone(), &blockstore);
290 assert_eq!(
291 optimistic_confirmation_verifier
292 .verify_for_unrooted_optimistic_slots(&bank7, &blockstore),
293 optimistic_slots[0..=1].to_vec()
294 );
295 assert!(optimistic_confirmation_verifier.unchecked_slots.is_empty());
296
297 blockstore.set_roots([1, 3].iter()).unwrap();
299 optimistic_confirmation_verifier
300 .add_new_optimistic_confirmed_slots(optimistic_slots, &blockstore);
301 assert!(
302 optimistic_confirmation_verifier
303 .verify_for_unrooted_optimistic_slots(&bank7, &blockstore)
304 .is_empty()
305 );
306 assert!(optimistic_confirmation_verifier.unchecked_slots.is_empty());
307 assert_eq!(blockstore.get_latest_optimistic_slots(10).unwrap().len(), 3);
308 }
309
310 fn setup_forks() -> VoteSimulator {
311 let forks = tr(0) / (tr(1) / (tr(2) / (tr(4))) / (tr(3) / (tr(5) / (tr(6)))));
325
326 let mut vote_simulator = VoteSimulator::new(1);
327 vote_simulator.fill_bank_forks(forks, &HashMap::new(), true);
328 vote_simulator
329 }
330}