ledger-tool: Manually walk optimistic slots ancestors (#32362)
If a slot is marked as optimistically confirmed, it is probable but not guaranteed that its' ancestors will also be marked as optimistically confirmed in the Blockstore. Given the importance of examining optimistically confirmed slots around cluster restarts, manually walk an AncestorIterator to avoid the chance of a slot improperly being ignored in cluster restart scenarios.
This commit is contained in:
parent
52fe7eb1cf
commit
5feebd2dc8
|
@ -64,7 +64,7 @@ use {
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
account::{AccountSharedData, ReadableAccount, WritableAccount},
|
account::{AccountSharedData, ReadableAccount, WritableAccount},
|
||||||
account_utils::StateMut,
|
account_utils::StateMut,
|
||||||
clock::{Epoch, Slot},
|
clock::{Epoch, Slot, UnixTimestamp},
|
||||||
feature::{self, Feature},
|
feature::{self, Feature},
|
||||||
feature_set::{self, FeatureSet},
|
feature_set::{self, FeatureSet},
|
||||||
genesis_config::ClusterType,
|
genesis_config::ClusterType,
|
||||||
|
@ -954,6 +954,72 @@ fn compute_slot_cost(blockstore: &Blockstore, slot: Slot) -> Result<(), String>
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the supplied slot contains any nonvote transactions
|
||||||
|
fn slot_contains_nonvote_tx(blockstore: &Blockstore, slot: Slot) -> bool {
|
||||||
|
let (entries, _, _) = blockstore
|
||||||
|
.get_slot_entries_with_shred_info(slot, 0, false)
|
||||||
|
.expect("Failed to get slot entries");
|
||||||
|
let contains_nonvote = entries
|
||||||
|
.iter()
|
||||||
|
.flat_map(|entry| entry.transactions.iter())
|
||||||
|
.flat_map(get_program_ids)
|
||||||
|
.any(|program_id| *program_id != solana_vote_program::id());
|
||||||
|
contains_nonvote
|
||||||
|
}
|
||||||
|
|
||||||
|
type OptimisticSlotInfo = (Slot, Option<(Hash, UnixTimestamp)>, bool);
|
||||||
|
|
||||||
|
/// Return the latest `num_slots` optimistically confirmed slots, including
|
||||||
|
/// ancestors of optimistically confirmed slots that may not have been marked
|
||||||
|
/// as optimistically confirmed themselves.
|
||||||
|
fn get_latest_optimistic_slots(
|
||||||
|
blockstore: &Blockstore,
|
||||||
|
num_slots: usize,
|
||||||
|
exclude_vote_only_slots: bool,
|
||||||
|
) -> Vec<OptimisticSlotInfo> {
|
||||||
|
// Consider a chain X -> Y -> Z where X and Z have been optimistically
|
||||||
|
// confirmed. Given that Y is an ancestor of Z, Y can implicitly be
|
||||||
|
// considered as optimistically confirmed. However, there isn't an explicit
|
||||||
|
// guarantee that Y will be marked as optimistically confirmed in the
|
||||||
|
// blockstore.
|
||||||
|
//
|
||||||
|
// Because retrieving optimistically confirmed slots is an important part
|
||||||
|
// of cluster restarts, exercise caution in this function and manually walk
|
||||||
|
// the ancestors of the latest optimistically confirmed slot instead of
|
||||||
|
// solely relying on the contents of the optimistically confirmed column.
|
||||||
|
let Some(latest_slot) = blockstore
|
||||||
|
.get_latest_optimistic_slots(1)
|
||||||
|
.expect("get_latest_optimistic_slots() failed")
|
||||||
|
.pop() else {
|
||||||
|
eprintln!("Blockstore does not contain any optimistically confirmed slots");
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
let latest_slot = latest_slot.0;
|
||||||
|
|
||||||
|
let slot_iter = AncestorIterator::new_inclusive(latest_slot, blockstore).map(|slot| {
|
||||||
|
let contains_nonvote_tx = slot_contains_nonvote_tx(blockstore, slot);
|
||||||
|
let hash_and_timestamp_opt = blockstore
|
||||||
|
.get_optimistic_slot(slot)
|
||||||
|
.expect("get_optimistic_slot() failed");
|
||||||
|
if hash_and_timestamp_opt.is_none() {
|
||||||
|
warn!(
|
||||||
|
"Slot {slot} is an ancestor of latest optimistically confirmed slot \
|
||||||
|
{latest_slot}, but was not marked as optimistically confirmed in blockstore."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
(slot, hash_and_timestamp_opt, contains_nonvote_tx)
|
||||||
|
});
|
||||||
|
|
||||||
|
if exclude_vote_only_slots {
|
||||||
|
slot_iter
|
||||||
|
.filter(|(_, _, contains_nonvote)| *contains_nonvote)
|
||||||
|
.take(num_slots)
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
slot_iter.take(num_slots).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Finds the accounts needed to replay slots `snapshot_slot` to `ending_slot`.
|
/// Finds the accounts needed to replay slots `snapshot_slot` to `ending_slot`.
|
||||||
/// Removes all other accounts from accounts_db, and updates the accounts hash
|
/// Removes all other accounts from accounts_db, and updates the accounts hash
|
||||||
/// and capitalization. This is used by the --minimize option in create-snapshot
|
/// and capitalization. This is used by the --minimize option in create-snapshot
|
||||||
|
@ -3902,44 +3968,26 @@ fn main() {
|
||||||
);
|
);
|
||||||
let num_slots = value_t_or_exit!(arg_matches, "num_slots", usize);
|
let num_slots = value_t_or_exit!(arg_matches, "num_slots", usize);
|
||||||
let exclude_vote_only_slots = arg_matches.is_present("exclude_vote_only_slots");
|
let exclude_vote_only_slots = arg_matches.is_present("exclude_vote_only_slots");
|
||||||
|
let slots =
|
||||||
let slots_iter = blockstore
|
get_latest_optimistic_slots(&blockstore, num_slots, exclude_vote_only_slots);
|
||||||
.reversed_optimistic_slots_iterator()
|
|
||||||
.expect("Failed to get reversed optimistic slots iterator")
|
|
||||||
.map(|(slot, hash, timestamp)| {
|
|
||||||
let (entries, _, _) = blockstore
|
|
||||||
.get_slot_entries_with_shred_info(slot, 0, false)
|
|
||||||
.expect("Failed to get slot entries");
|
|
||||||
let contains_nonvote = entries
|
|
||||||
.iter()
|
|
||||||
.flat_map(|entry| entry.transactions.iter())
|
|
||||||
.flat_map(get_program_ids)
|
|
||||||
.any(|program_id| *program_id != solana_vote_program::id());
|
|
||||||
(slot, hash, timestamp, contains_nonvote)
|
|
||||||
});
|
|
||||||
|
|
||||||
let slots: Vec<_> = if exclude_vote_only_slots {
|
|
||||||
slots_iter
|
|
||||||
.filter(|(_, _, _, contains_nonvote)| *contains_nonvote)
|
|
||||||
.take(num_slots)
|
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
slots_iter.take(num_slots).collect()
|
|
||||||
};
|
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"{:>20} {:>44} {:>32} {:>13}",
|
"{:>20} {:>44} {:>32} {:>13}",
|
||||||
"Slot", "Hash", "Timestamp", "Vote Only?"
|
"Slot", "Hash", "Timestamp", "Vote Only?"
|
||||||
);
|
);
|
||||||
for (slot, hash, timestamp, contains_nonvote) in slots.iter() {
|
for (slot, hash_and_timestamp_opt, contains_nonvote) in slots.iter() {
|
||||||
let time_str = {
|
let (time_str, hash_str) =
|
||||||
let secs: u64 = (timestamp / 1_000) as u64;
|
if let Some((hash, timestamp)) = hash_and_timestamp_opt {
|
||||||
let nanos: u32 = ((timestamp % 1_000) * 1_000_000) as u32;
|
let secs: u64 = (timestamp / 1_000) as u64;
|
||||||
let t = UNIX_EPOCH + Duration::new(secs, nanos);
|
let nanos: u32 = ((timestamp % 1_000) * 1_000_000) as u32;
|
||||||
let datetime: DateTime<Utc> = t.into();
|
let t = UNIX_EPOCH + Duration::new(secs, nanos);
|
||||||
datetime.to_rfc3339()
|
let datetime: DateTime<Utc> = t.into();
|
||||||
};
|
|
||||||
let hash_str = format!("{hash}");
|
(datetime.to_rfc3339(), format!("{hash}"))
|
||||||
|
} else {
|
||||||
|
let unknown = "Unknown";
|
||||||
|
(String::from(unknown), String::from(unknown))
|
||||||
|
};
|
||||||
println!(
|
println!(
|
||||||
"{:>20} {:>44} {:>32} {:>13}",
|
"{:>20} {:>44} {:>32} {:>13}",
|
||||||
slot, &hash_str, &time_str, !contains_nonvote
|
slot, &hash_str, &time_str, !contains_nonvote
|
||||||
|
@ -4113,3 +4161,43 @@ fn main() {
|
||||||
info!("{}", measure_total_execution_time);
|
info!("{}", measure_total_execution_time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use {
|
||||||
|
super::*,
|
||||||
|
solana_ledger::{blockstore::make_many_slot_entries, get_tmp_ledger_path_auto_delete},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_latest_optimistic_ancestors() {
|
||||||
|
let ledger_path = get_tmp_ledger_path_auto_delete!();
|
||||||
|
let blockstore = Blockstore::open(ledger_path.path()).unwrap();
|
||||||
|
|
||||||
|
// Insert 5 slots into blockstore
|
||||||
|
let start_slot = 0;
|
||||||
|
let num_slots = 5;
|
||||||
|
let entries_per_shred = 5;
|
||||||
|
let (shreds, _) = make_many_slot_entries(start_slot, num_slots, entries_per_shred);
|
||||||
|
blockstore.insert_shreds(shreds, None, false).unwrap();
|
||||||
|
|
||||||
|
// Mark even shreds as optimistically confirmed
|
||||||
|
(0..num_slots).step_by(2).for_each(|slot| {
|
||||||
|
blockstore
|
||||||
|
.insert_optimistic_slot(slot, &Hash::default(), UnixTimestamp::default())
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
let exclude_vote_only_slots = false;
|
||||||
|
let optimistic_slots: Vec<_> =
|
||||||
|
get_latest_optimistic_slots(&blockstore, num_slots as usize, exclude_vote_only_slots)
|
||||||
|
.iter()
|
||||||
|
.map(|(slot, _, _)| *slot)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Should see all slots here since they're all chained, despite only evens getting marked
|
||||||
|
// get_latest_optimistic_slots() returns slots in descending order so use .rev()
|
||||||
|
let expected: Vec<_> = (start_slot..num_slots).rev().collect();
|
||||||
|
assert_eq!(optimistic_slots, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3103,6 +3103,15 @@ impl Blockstore {
|
||||||
self.optimistic_slots_cf.put(slot, &slot_data)
|
self.optimistic_slots_cf.put(slot, &slot_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns information about a single optimistically confirmed slot
|
||||||
|
pub fn get_optimistic_slot(&self, slot: Slot) -> Result<Option<(Hash, UnixTimestamp)>> {
|
||||||
|
Ok(self
|
||||||
|
.optimistic_slots_cf
|
||||||
|
.get(slot)?
|
||||||
|
.map(|meta| (meta.hash(), meta.timestamp())))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns information about the `num` latest optimistically confirmed slot
|
||||||
pub fn get_latest_optimistic_slots(
|
pub fn get_latest_optimistic_slots(
|
||||||
&self,
|
&self,
|
||||||
num: usize,
|
num: usize,
|
||||||
|
|
Loading…
Reference in New Issue