|
18 | 18 | leader_schedule_cache::LeaderScheduleCache, |
19 | 19 | next_slots_iterator::NextSlotsIterator, |
20 | 20 | 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, |
22 | 26 | ShredId, ShredType, Shredder, DATA_SHREDS_PER_FEC_BLOCK, |
23 | 27 | }, |
24 | 28 | slot_stats::{ShredSource, SlotsStats}, |
|
49 | 53 | solana_metrics::datapoint_error, |
50 | 54 | solana_pubkey::Pubkey, |
51 | 55 | solana_runtime::bank::Bank, |
| 56 | + solana_sha256_hasher::hashv, |
52 | 57 | solana_signature::Signature, |
53 | 58 | solana_signer::Signer, |
54 | 59 | solana_storage_proto::{StoredExtendedRewards, StoredTransactionStatusMeta}, |
@@ -869,6 +874,108 @@ impl Blockstore { |
869 | 874 | } |
870 | 875 | } |
871 | 876 |
|
| 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 | + |
872 | 979 | /// Check whether the specified slot is an orphan slot which does not |
873 | 980 | /// have a parent slot. |
874 | 981 | /// |
@@ -6109,7 +6216,11 @@ pub mod tests { |
6109 | 6216 | crate::{ |
6110 | 6217 | genesis_utils::{create_genesis_config, GenesisConfigInfo}, |
6111 | 6218 | 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 | + }, |
6113 | 6224 | }, |
6114 | 6225 | assert_matches::assert_matches, |
6115 | 6226 | bincode::{serialize, Options}, |
@@ -12786,4 +12897,122 @@ pub mod tests { |
12786 | 12897 | Err(TransactionError::InsufficientFundsForFee) |
12787 | 12898 | ); |
12788 | 12899 | } |
| 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 | + } |
12789 | 13018 | } |
0 commit comments