Skip to content

Commit fe799f8

Browse files
authored
[testnet] Simplify and generalize block_hashes. (#4897) (#4901)
Backport of #4897. ## Motivation After introducing sparse chains, we use `block_hashes` not just for ranges, but for sparse lists of blocks, too. ## Proposal Use `LogView::multi_get` (list of heights) instead of `read` (range). Make `block_hashes` accept a list of heights instead of a range. ## Test Plan CI should catch regressions; no new functionality was added. ## Release Plan - These changes should: - be released in the next SDK version, (low priority!) - be released in the next validator hotfix. (low priority!) ## Links - PR to main: #4897 - Related: #4880 - [reviewer checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
1 parent 30b8f86 commit fe799f8

File tree

5 files changed

+27
-97
lines changed

5 files changed

+27
-97
lines changed

linera-base/src/data_types.rs

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use std::{
1212
hash::Hash,
1313
io, iter,
1414
num::ParseIntError,
15-
ops::{Bound, RangeBounds},
1615
path::Path,
1716
str::FromStr,
1817
sync::Arc,
@@ -498,32 +497,6 @@ impl TryFrom<BlockHeight> for usize {
498497
}
499498
}
500499

501-
/// Allows converting [`BlockHeight`] ranges to inclusive tuples of bounds.
502-
pub trait BlockHeightRangeBounds {
503-
/// Returns the range as a tuple of inclusive bounds.
504-
/// If the range is empty, returns `None`.
505-
fn to_inclusive(&self) -> Option<(BlockHeight, BlockHeight)>;
506-
}
507-
508-
impl<T: RangeBounds<BlockHeight>> BlockHeightRangeBounds for T {
509-
fn to_inclusive(&self) -> Option<(BlockHeight, BlockHeight)> {
510-
let start = match self.start_bound() {
511-
Bound::Included(height) => *height,
512-
Bound::Excluded(height) => height.try_add_one().ok()?,
513-
Bound::Unbounded => BlockHeight(0),
514-
};
515-
let end = match self.end_bound() {
516-
Bound::Included(height) => *height,
517-
Bound::Excluded(height) => height.try_sub_one().ok()?,
518-
Bound::Unbounded => BlockHeight::MAX,
519-
};
520-
if start > end {
521-
return None;
522-
}
523-
Some((start, end))
524-
}
525-
}
526-
527500
impl_wrapped_number!(Amount, u128);
528501
impl_wrapped_number!(BlockHeight, u64);
529502
impl_wrapped_number!(TimeDelta, u64);

linera-chain/src/chain.rs

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33

44
use std::{
55
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
6-
ops::RangeBounds,
76
sync::Arc,
87
};
98

109
use linera_base::{
1110
crypto::{CryptoHash, ValidatorPublicKey},
1211
data_types::{
13-
ApplicationDescription, ApplicationPermissions, ArithmeticError, Blob, BlockHeight,
14-
BlockHeightRangeBounds as _, Epoch, OracleResponse, Timestamp,
12+
ApplicationDescription, ApplicationPermissions, ArithmeticError, Blob, BlockHeight, Epoch,
13+
OracleResponse, Timestamp,
1514
},
1615
ensure,
1716
identifiers::{AccountOwner, ApplicationId, BlobType, ChainId, StreamId},
@@ -1056,43 +1055,37 @@ where
10561055
}
10571056

10581057
/// Returns the hashes of all blocks we have in the given range.
1058+
///
1059+
/// If the input heights are in ascending order, the hashes will be in the same order.
1060+
/// Otherwise they may be unordered.
10591061
#[instrument(skip_all, fields(
10601062
chain_id = %self.chain_id(),
10611063
next_block_height = %self.tip_state.get().next_block_height,
1062-
start_height = ?range.start_bound(),
1063-
end_height = ?range.end_bound()
10641064
))]
10651065
pub async fn block_hashes(
10661066
&self,
1067-
range: impl RangeBounds<BlockHeight>,
1067+
heights: impl IntoIterator<Item = BlockHeight>,
10681068
) -> Result<Vec<CryptoHash>, ChainError> {
10691069
let next_height = self.tip_state.get().next_block_height;
1070-
// If the range is not empty, it can always be represented as start..=end.
1071-
let Some((start, end)) = range.to_inclusive() else {
1072-
return Ok(Vec::new());
1073-
};
10741070
// Everything up to (excluding) next_height is in confirmed_log.
1075-
let mut hashes = if let Ok(last_height) = next_height.try_sub_one() {
1076-
let usize_start = usize::try_from(start)?;
1077-
let usize_end = usize::try_from(end.min(last_height))?;
1078-
self.confirmed_log.read(usize_start..=usize_end).await?
1079-
} else {
1080-
Vec::new()
1081-
};
1071+
let (confirmed_heights, unconfirmed_heights) = heights
1072+
.into_iter()
1073+
.partition::<Vec<_>, _>(|height| *height < next_height);
1074+
let confirmed_indices = confirmed_heights
1075+
.into_iter()
1076+
.map(|height| usize::try_from(height.0).map_err(|_| ArithmeticError::Overflow))
1077+
.collect::<Result<_, _>>()?;
1078+
let confirmed_hashes = self.confirmed_log.multi_get(confirmed_indices).await?;
10821079
// Everything after (including) next_height in preprocessed_blocks if we have it.
1083-
let block_heights = (start.max(next_height).0..=end.0)
1084-
.map(BlockHeight)
1085-
.collect::<Vec<_>>();
1086-
for hash in self
1080+
let unconfirmed_hashes = self
10871081
.preprocessed_blocks
1088-
.multi_get(&block_heights)
1089-
.await?
1082+
.multi_get(&unconfirmed_heights)
1083+
.await?;
1084+
Ok(confirmed_hashes
10901085
.into_iter()
1086+
.chain(unconfirmed_hashes)
10911087
.flatten()
1092-
{
1093-
hashes.push(hash);
1094-
}
1095-
Ok(hashes)
1088+
.collect())
10961089
}
10971090

10981091
/// Resets the chain manager for the next block height.

linera-core/src/chain_worker/state.rs

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use std::{
77
borrow::Cow,
88
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
99
iter,
10-
ops::RangeInclusive,
1110
sync::{self, Arc},
1211
};
1312

@@ -1561,11 +1560,9 @@ where
15611560
bundles.sort_by_key(|b| b.bundle.timestamp);
15621561
info.requested_pending_message_bundles = bundles;
15631562
}
1564-
let mut hashes = Vec::new();
1565-
let height_ranges = into_ranges(query.request_sent_certificate_hashes_by_heights);
1566-
for height_range in height_ranges {
1567-
hashes.extend(chain.block_hashes(height_range).await?);
1568-
}
1563+
let hashes = chain
1564+
.block_hashes(query.request_sent_certificate_hashes_by_heights)
1565+
.await?;
15691566
info.requested_sent_certificate_hashes = hashes;
15701567
if let Some(start) = query.request_received_log_excluding_first_n {
15711568
let start = usize::try_from(start).map_err(|_| ArithmeticError::Overflow)?;
@@ -1644,21 +1641,6 @@ where
16441641
}
16451642
}
16461643

1647-
/// Returns an iterator of inclusive ranges that exactly cover the list of block heights.
1648-
fn into_ranges(
1649-
values: impl IntoIterator<Item = BlockHeight>,
1650-
) -> impl Iterator<Item = RangeInclusive<BlockHeight>> {
1651-
let mut values_iter = values.into_iter().peekable();
1652-
iter::from_fn(move || {
1653-
let start = values_iter.next()?;
1654-
let mut end = start;
1655-
while values_iter.peek() == end.try_add_one().ok().as_ref() {
1656-
end = values_iter.next()?;
1657-
}
1658-
Some(start..=end)
1659-
})
1660-
}
1661-
16621644
/// Returns the keys whose value is `None`.
16631645
fn missing_blob_ids(maybe_blobs: &BTreeMap<BlobId, Option<Blob>>) -> Vec<BlobId> {
16641646
maybe_blobs
@@ -1771,21 +1753,3 @@ impl<'a> CrossChainUpdateHelper<'a> {
17711753
Ok(bundles)
17721754
}
17731755
}
1774-
1775-
#[test]
1776-
fn test_into_ranges() {
1777-
assert_eq!(
1778-
into_ranges(vec![
1779-
BlockHeight(2),
1780-
BlockHeight(3),
1781-
BlockHeight(4),
1782-
BlockHeight(6)
1783-
],)
1784-
.collect::<Vec<_>>(),
1785-
vec![
1786-
BlockHeight(2)..=BlockHeight(4),
1787-
BlockHeight(6)..=BlockHeight(6)
1788-
]
1789-
);
1790-
assert_eq!(into_ranges(vec![]).collect::<Vec<_>>(), vec![]);
1791-
}

linera-core/src/updater.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,7 @@ where
522522
.local_node
523523
.chain_state_view(chain_id)
524524
.await?
525-
.block_hashes(height..=height)
525+
.block_hashes([height])
526526
.await?
527527
.into_iter()
528528
.next()
@@ -556,12 +556,12 @@ where
556556
Ok(info) => info,
557557
};
558558
// Obtain the missing blocks and the manager state from the local node.
559-
let range = info.next_block_height..target_block_height;
559+
let heights = (info.next_block_height.0..target_block_height.0).map(BlockHeight);
560560
let validator_missing_hashes = self
561561
.local_node
562562
.chain_state_view(chain_id)
563563
.await?
564-
.block_hashes(range)
564+
.block_hashes(heights)
565565
.await?;
566566
if !validator_missing_hashes.is_empty() {
567567
// Send the requested certificates in order.

linera-service/src/cli/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1830,7 +1830,7 @@ impl Runnable for Job {
18301830
.await
18311831
.context("Failed to load chain")?;
18321832
let block_hash = chain_state_view
1833-
.block_hashes(height..=height)
1833+
.block_hashes([height])
18341834
.await
18351835
.context("Failed to find a block hash for the given height")?[0];
18361836
let block = context

0 commit comments

Comments
 (0)