From b6a18e0e87f66b0a62cc31c591f9c470ae86b7bc Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Thu, 28 Apr 2022 02:36:19 -0400 Subject: [PATCH] Fix get_first_available_block for genesis; also make get_blocks(_with_limit) consistent (#24760) * Handle genesis more appropriately in get_first_available_block * Add unit test * Make get_blocks starting-slots consistent with other methods --- ledger/src/blockstore.rs | 54 ++++++++++++++++++++++++++++++++++++---- rpc/src/rpc.rs | 19 +++++++++----- 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/ledger/src/blockstore.rs b/ledger/src/blockstore.rs index 2bda1480d..1f2dd0083 100644 --- a/ledger/src/blockstore.rs +++ b/ledger/src/blockstore.rs @@ -1930,11 +1930,17 @@ impl Blockstore { /// The first complete block that is available in the Blockstore ledger pub fn get_first_available_block(&self) -> Result { - let mut root_iterator = self.rooted_slot_iterator(self.lowest_slot())?; - // The block at root-index 0 cannot be complete, because it is missing its parent - // blockhash. A parent blockhash must be calculated from the entries of the previous block. - // Therefore, the first available complete block is that at root-index 1. - Ok(root_iterator.nth(1).unwrap_or_default()) + let mut root_iterator = self.rooted_slot_iterator(self.lowest_slot_with_genesis())?; + let first_root = root_iterator.next().unwrap_or_default(); + // If the first root is slot 0, it is genesis. Genesis is always complete, so it is correct + // to return it as first-available. + if first_root == 0 { + return Ok(first_root); + } + // Otherwise, the block at root-index 0 cannot ever be complete, because it is missing its + // parent blockhash. A parent blockhash must be calculated from the entries of the previous + // block. Therefore, the first available complete block is that at root-index 1. + Ok(root_iterator.next().unwrap_or_default()) } pub fn get_rooted_block( @@ -3160,6 +3166,19 @@ impl Blockstore { self.last_root() } + fn lowest_slot_with_genesis(&self) -> Slot { + for (slot, meta) in self + .slot_meta_iterator(0) + .expect("unable to iterate over meta") + { + if meta.received > 0 { + return slot; + } + } + // This means blockstore is empty, should never get here aside from right at boot. + self.last_root() + } + pub fn lowest_cleanup_slot(&self) -> Slot { *self.lowest_cleanup_slot.read().unwrap() } @@ -6309,6 +6328,31 @@ pub mod tests { assert!(blockstore.get_data_shred(1, 0).unwrap().is_some()); } + #[test] + fn test_get_first_available_block() { + let mint_total = 1_000_000_000_000; + let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(mint_total); + let (ledger_path, _blockhash) = create_new_tmp_ledger_auto_delete!(&genesis_config); + let blockstore = Blockstore::open(ledger_path.path()).unwrap(); + assert_eq!(blockstore.get_first_available_block().unwrap(), 0); + assert_eq!(blockstore.lowest_slot_with_genesis(), 0); + assert_eq!(blockstore.lowest_slot(), 0); + for slot in 1..4 { + let entries = make_slot_entries_with_transactions(100); + let shreds = entries_to_test_shreds(&entries, slot, slot - 1, true, 0); + blockstore.insert_shreds(shreds, None, false).unwrap(); + blockstore.set_roots(vec![slot].iter()).unwrap(); + } + assert_eq!(blockstore.get_first_available_block().unwrap(), 0); + assert_eq!(blockstore.lowest_slot_with_genesis(), 0); + assert_eq!(blockstore.lowest_slot(), 1); + + blockstore.purge_slots(0, 1, PurgeType::CompactionFilter); + assert_eq!(blockstore.get_first_available_block().unwrap(), 3); + assert_eq!(blockstore.lowest_slot_with_genesis(), 2); + assert_eq!(blockstore.lowest_slot(), 2); + } + #[test] fn test_get_rooted_block() { let slot = 10; diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 790f9c50e..1689f761c 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -1124,7 +1124,10 @@ impl JsonRpcRequestProcessor { ))); } - let lowest_blockstore_slot = self.blockstore.lowest_slot(); + let lowest_blockstore_slot = self + .blockstore + .get_first_available_block() + .unwrap_or_default(); if start_slot < lowest_blockstore_slot { // If the starting slot is lower than what's available in blockstore assume the entire // [start_slot..end_slot] can be fetched from BigTable. This range should not ever run @@ -1188,7 +1191,10 @@ impl JsonRpcRequestProcessor { ))); } - let lowest_blockstore_slot = self.blockstore.lowest_slot(); + let lowest_blockstore_slot = self + .blockstore + .get_first_available_block() + .unwrap_or_default(); if start_slot < lowest_blockstore_slot { // If the starting slot is lower than what's available in blockstore assume the entire @@ -6677,6 +6683,7 @@ pub mod tests { #[test] fn test_get_blocks() { let rpc = RpcHandler::start(); + let _ = rpc.create_test_transactions_and_populate_blockstore(); rpc.add_roots_to_blockstore(vec![0, 1, 3, 4, 8]); rpc.block_commitment_cache .write() @@ -6685,7 +6692,7 @@ pub mod tests { let request = create_test_request("getBlocks", Some(json!([0u64]))); let result: Vec = parse_success_result(rpc.handle_request_sync(request)); - assert_eq!(result, vec![1, 3, 4, 8]); + assert_eq!(result, vec![0, 1, 3, 4, 8]); let request = create_test_request("getBlocks", Some(json!([2u64]))); let result: Vec = parse_success_result(rpc.handle_request_sync(request)); @@ -6693,11 +6700,11 @@ pub mod tests { let request = create_test_request("getBlocks", Some(json!([0u64, 4u64]))); let result: Vec = parse_success_result(rpc.handle_request_sync(request)); - assert_eq!(result, vec![1, 3, 4]); + assert_eq!(result, vec![0, 1, 3, 4]); let request = create_test_request("getBlocks", Some(json!([0u64, 7u64]))); let result: Vec = parse_success_result(rpc.handle_request_sync(request)); - assert_eq!(result, vec![1, 3, 4]); + assert_eq!(result, vec![0, 1, 3, 4]); let request = create_test_request("getBlocks", Some(json!([9u64, 11u64]))); let result: Vec = parse_success_result(rpc.handle_request_sync(request)); @@ -6713,7 +6720,7 @@ pub mod tests { Some(json!([0u64, MAX_GET_CONFIRMED_BLOCKS_RANGE])), ); let result: Vec = parse_success_result(rpc.handle_request_sync(request)); - assert_eq!(result, vec![1, 3, 4, 8]); + assert_eq!(result, vec![0, 1, 3, 4, 8]); let request = create_test_request( "getBlocks",