Skip to content

Commit 5fb2269

Browse files
committed
blockstore: compute and populate DoubleMerkleRootMeta
1 parent e09d73d commit 5fb2269

File tree

3 files changed

+237
-4
lines changed

3 files changed

+237
-4
lines changed

ledger/src/blockstore.rs

Lines changed: 231 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ use {
1818
leader_schedule_cache::LeaderScheduleCache,
1919
next_slots_iterator::NextSlotsIterator,
2020
shred::{
21-
self, ErasureSetId, ProcessShredsStats, ReedSolomonCache, Shred, ShredData, ShredFlags,
21+
self,
22+
merkle_tree::{
23+
get_proof_size, make_merkle_proof, make_merkle_tree, SIZE_OF_MERKLE_PROOF_ENTRY,
24+
},
25+
ErasureSetId, ProcessShredsStats, ReedSolomonCache, Shred, ShredData, ShredFlags,
2226
ShredId, ShredType, Shredder, DATA_SHREDS_PER_FEC_BLOCK,
2327
},
2428
slot_stats::{ShredSource, SlotsStats},
@@ -49,6 +53,7 @@ use {
4953
solana_metrics::datapoint_error,
5054
solana_pubkey::Pubkey,
5155
solana_runtime::bank::Bank,
56+
solana_sha256_hasher::hashv,
5257
solana_signature::Signature,
5358
solana_signer::Signer,
5459
solana_storage_proto::{StoredExtendedRewards, StoredTransactionStatusMeta},
@@ -869,6 +874,108 @@ impl Blockstore {
869874
}
870875
}
871876

877+
/// Fetches (and populates if needed) the DoubleMerkleMeta for the given block.
878+
/// Returns the double_merkle_root
879+
///
880+
/// Should only be used on full blocks.
881+
pub fn get_or_compute_double_merkle_root(
882+
&self,
883+
slot: Slot,
884+
block_location: BlockLocation,
885+
) -> Result<Hash> {
886+
if let Some(double_merkle_meta) = self.double_merkle_meta_cf.get((slot, block_location))? {
887+
return Ok(double_merkle_meta.double_merkle_root);
888+
}
889+
890+
// Compute double merkle - slot must be full at this point
891+
let Some(slot_meta) = self.meta_cf.get(slot)? else {
892+
return Err(BlockstoreError::SlotUnavailable);
893+
};
894+
895+
if !slot_meta.is_full() {
896+
return Err(BlockstoreError::SlotNotFull(slot, block_location));
897+
}
898+
899+
let Some(last_index) = slot_meta.last_index else {
900+
return Err(BlockstoreError::UnknownLastIndex(slot));
901+
};
902+
903+
// This function is only used post Alpenglow, so implicitely gated by SIMD-0317 as that is a prereq
904+
let fec_set_count = (last_index / (DATA_SHREDS_PER_FEC_BLOCK as u64) + 1) as usize;
905+
906+
let Some(parent_meta) = self.parent_meta_cf.get((slot, block_location))? else {
907+
return Err(BlockstoreError::SlotUnavailable);
908+
};
909+
910+
// Collect merkle roots for each FEC set
911+
let mut merkle_tree_leaves = Vec::with_capacity(fec_set_count + 1);
912+
913+
for i in 0..fec_set_count {
914+
let fec_set_index = (i * DATA_SHREDS_PER_FEC_BLOCK) as u32;
915+
let erasure_set_id = ErasureSetId::new(slot, fec_set_index);
916+
917+
let Some(merkle_root) = self
918+
.merkle_root_meta_from_location(erasure_set_id, block_location)?
919+
.and_then(|mrm| mrm.merkle_root())
920+
else {
921+
return Err(BlockstoreError::MissingMerkleRoot(
922+
slot,
923+
fec_set_index as u64,
924+
));
925+
};
926+
merkle_tree_leaves.push(Ok(merkle_root));
927+
}
928+
929+
// Add parent info as the last leaf
930+
let parent_info_hash = hashv(&[
931+
&parent_meta.parent_slot.to_le_bytes(),
932+
parent_meta.parent_block_id.as_ref(),
933+
]);
934+
merkle_tree_leaves.push(Ok(parent_info_hash));
935+
936+
// Build the merkle tree
937+
let merkle_tree = make_merkle_tree(merkle_tree_leaves).map_err(|_| {
938+
BlockstoreError::FailedDoubleMerkleRootConstruction(slot, block_location)
939+
})?;
940+
let double_merkle_root = *merkle_tree
941+
.last()
942+
.expect("Merkle tree cannot be empty as fec_set_count is > 0");
943+
944+
// Build proofs
945+
let tree_size = fec_set_count + 1;
946+
let mut proofs = Vec::with_capacity(tree_size);
947+
948+
for leaf_index in 0..tree_size {
949+
let proof_iter = make_merkle_proof(leaf_index, tree_size, &merkle_tree);
950+
let proof: Vec<u8> = proof_iter
951+
.map(|proof| proof.map(|p| p.as_slice()))
952+
.collect::<std::result::Result<Vec<_>, _>>()
953+
.map_err(|_| {
954+
BlockstoreError::FailedDoubleMerkleRootConstruction(slot, block_location)
955+
})?
956+
.into_iter()
957+
.flatten()
958+
.copied()
959+
.collect();
960+
debug_assert!(
961+
proof.len() == get_proof_size(tree_size) as usize * SIZE_OF_MERKLE_PROOF_ENTRY
962+
);
963+
proofs.push(proof);
964+
}
965+
966+
// Create and store DoubleMerkleMeta
967+
let double_merkle_meta = DoubleMerkleMeta {
968+
double_merkle_root,
969+
fec_set_count,
970+
proofs,
971+
};
972+
973+
self.double_merkle_meta_cf
974+
.put((slot, block_location), &double_merkle_meta)?;
975+
976+
Ok(double_merkle_root)
977+
}
978+
872979
/// Check whether the specified slot is an orphan slot which does not
873980
/// have a parent slot.
874981
///
@@ -6109,7 +6216,11 @@ pub mod tests {
61096216
crate::{
61106217
genesis_utils::{create_genesis_config, GenesisConfigInfo},
61116218
leader_schedule::{FixedSchedule, IdentityKeyedLeaderSchedule},
6112-
shred::{max_ticks_per_n_shreds, MAX_DATA_SHREDS_PER_SLOT},
6219+
shred::{
6220+
max_ticks_per_n_shreds,
6221+
merkle_tree::{get_merkle_root, MerkleProofEntry},
6222+
MAX_DATA_SHREDS_PER_SLOT,
6223+
},
61136224
},
61146225
assert_matches::assert_matches,
61156226
bincode::{serialize, Options},
@@ -12786,4 +12897,122 @@ pub mod tests {
1278612897
Err(TransactionError::InsufficientFundsForFee)
1278712898
);
1278812899
}
12900+
12901+
#[test]
12902+
fn test_get_or_compute_double_merkle_root() {
12903+
let ledger_path = get_tmp_ledger_path_auto_delete!();
12904+
let blockstore = Blockstore::open(ledger_path.path()).unwrap();
12905+
12906+
let parent_slot = 990;
12907+
let slot = 1000;
12908+
let num_entries = 200;
12909+
12910+
// Create a set of shreds for a complete block
12911+
let (data_shreds, coding_shreds, leader_schedule) =
12912+
setup_erasure_shreds(slot, parent_slot, num_entries);
12913+
12914+
// Create ParentMeta
12915+
let parent_meta = ParentMeta {
12916+
parent_slot,
12917+
parent_block_id: Hash::default(),
12918+
replay_fec_set_index: 0,
12919+
};
12920+
blockstore
12921+
.parent_meta_cf
12922+
.put((slot, BlockLocation::Original), &parent_meta)
12923+
.unwrap();
12924+
12925+
// Insert shreds into blockstore
12926+
let mut fec_set_roots = [Hash::default(); 3];
12927+
for shred in data_shreds.iter().chain(coding_shreds.iter()) {
12928+
if shred.is_data() && shred.index() % (DATA_SHREDS_PER_FEC_BLOCK as u32) == 0 {
12929+
// store fec set merkle roots for later
12930+
fec_set_roots[(shred.index() as usize) / DATA_SHREDS_PER_FEC_BLOCK] =
12931+
shred.merkle_root().unwrap();
12932+
}
12933+
let duplicates =
12934+
blockstore.insert_shred_return_duplicate(shred.clone(), &leader_schedule);
12935+
assert!(duplicates.is_empty());
12936+
}
12937+
12938+
let slot_meta = blockstore.meta(slot).unwrap().unwrap();
12939+
assert!(slot_meta.is_full());
12940+
12941+
// Test getting the double merkle root
12942+
let block_location = BlockLocation::Original;
12943+
let double_merkle_root = blockstore
12944+
.get_or_compute_double_merkle_root(slot, block_location)
12945+
.unwrap();
12946+
12947+
let double_merkle_meta = blockstore
12948+
.double_merkle_meta_cf
12949+
.get((slot, block_location))
12950+
.unwrap()
12951+
.unwrap();
12952+
12953+
// Verify meta
12954+
assert_eq!(double_merkle_meta.double_merkle_root, double_merkle_root);
12955+
assert_eq!(double_merkle_meta.fec_set_count, 3); // With 200 entries, we should have 3 FEC sets
12956+
assert_eq!(double_merkle_meta.proofs.len(), 4); // 3 FEC set, 1 parent info
12957+
12958+
// Verify the proofs
12959+
let proof_size = get_proof_size(double_merkle_meta.fec_set_count + 1) as usize;
12960+
12961+
// Fec sets
12962+
for (fec_set, root) in fec_set_roots.iter().enumerate() {
12963+
let proof = &double_merkle_meta.proofs[fec_set];
12964+
let proof = proof
12965+
.chunks(SIZE_OF_MERKLE_PROOF_ENTRY)
12966+
.map(<&MerkleProofEntry>::try_from)
12967+
.map(std::result::Result::unwrap);
12968+
assert_eq!(proof_size, proof.clone().count());
12969+
12970+
let double_merkle_root = get_merkle_root(fec_set, *root, proof).unwrap();
12971+
assert_eq!(double_merkle_meta.double_merkle_root, double_merkle_root);
12972+
}
12973+
12974+
// Parent info - final proof
12975+
let parent_info_hash = hashv(&[
12976+
&parent_slot.to_le_bytes(),
12977+
parent_meta.parent_block_id.as_ref(),
12978+
]);
12979+
let parent_info_proof = &double_merkle_meta.proofs[double_merkle_meta.fec_set_count];
12980+
let proof = parent_info_proof
12981+
.chunks(SIZE_OF_MERKLE_PROOF_ENTRY)
12982+
.map(<&MerkleProofEntry>::try_from)
12983+
.map(std::result::Result::unwrap);
12984+
assert_eq!(proof_size, proof.clone().count());
12985+
12986+
let double_merkle_root =
12987+
get_merkle_root(double_merkle_meta.fec_set_count, parent_info_hash, proof).unwrap();
12988+
assert_eq!(double_merkle_meta.double_merkle_root, double_merkle_root);
12989+
12990+
// Slot not full should fail
12991+
let incomplete_slot = 1001; // Make it a child of slot 1000
12992+
let (partial_shreds, _, leader_schedule) =
12993+
setup_erasure_shreds_with_index_and_chained_merkle_and_last_in_slot(
12994+
incomplete_slot,
12995+
slot, // parent is 1000
12996+
5,
12997+
0,
12998+
Some(Hash::new_from_array(rand::thread_rng().gen())),
12999+
false, // not last in slot
13000+
);
13001+
13002+
for shred in partial_shreds.iter().take(3) {
13003+
let duplicates =
13004+
blockstore.insert_shred_return_duplicate(shred.clone(), &leader_schedule);
13005+
assert!(duplicates.is_empty());
13006+
}
13007+
13008+
let result = blockstore.get_or_compute_double_merkle_root(incomplete_slot, block_location);
13009+
match result {
13010+
Err(BlockstoreError::SlotNotFull(slot, loc)) => {
13011+
assert_eq!(slot, incomplete_slot);
13012+
assert_eq!(loc, block_location);
13013+
} // This is the expected error
13014+
Err(e) => panic!("Unexpected error: {e:?}"),
13015+
Ok(_) => panic!("Expected error but got Ok"),
13016+
}
13017+
}
1278913018
}

ledger/src/blockstore/error.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
//! The error that can be produced from Blockstore operations.
22
33
use {
4-
log::*, solana_accounts_db::hardened_unpack::UnpackError, solana_clock::Slot, thiserror::Error,
4+
crate::blockstore_meta::BlockLocation, log::*,
5+
solana_accounts_db::hardened_unpack::UnpackError, solana_clock::Slot, thiserror::Error,
56
};
67

78
#[derive(Error, Debug)]
@@ -66,5 +67,9 @@ pub enum BlockstoreError {
6667
UnexpectedBlockComponent,
6768
#[error("Block component mismatch slot {0}")]
6869
BlockComponentMismatch(Slot),
70+
#[error("Slot {0} at location {1} not full")]
71+
SlotNotFull(Slot, BlockLocation),
72+
#[error("Double merkle root construction failure for slot {0} at location {1}")]
73+
FailedDoubleMerkleRootConstruction(Slot, BlockLocation),
6974
}
7075
pub type Result<T> = std::result::Result<T, BlockstoreError>;

ledger/src/shred/merkle_tree.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ pub fn get_merkle_tree_size(num_shreds: usize) -> usize {
108108
}
109109

110110
// Maps number of (code + data) shreds to merkle_proof.len().
111-
#[cfg(test)]
112111
pub(crate) const fn get_proof_size(num_shreds: usize) -> u8 {
113112
let bits = usize::BITS - num_shreds.leading_zeros();
114113
let proof_size = if num_shreds.is_power_of_two() {

0 commit comments

Comments
 (0)