Skip to content

Commit 46d2d6d

Browse files
authored
Simplify and generalize block_hashes. (#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 could be backported to `testnet_conway`, then - be released in a new SDK, (low priority!) - be released in a validator hotfix. (low priority!) ## Links - Related: #4880 - [reviewer checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
1 parent 16cc14b commit 46d2d6d

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},
@@ -1066,43 +1065,37 @@ where
10661065
}
10671066

10681067
/// Returns the hashes of all blocks we have in the given range.
1068+
///
1069+
/// If the input heights are in ascending order, the hashes will be in the same order.
1070+
/// Otherwise they may be unordered.
10691071
#[instrument(skip_all, fields(
10701072
chain_id = %self.chain_id(),
10711073
next_block_height = %self.tip_state.get().next_block_height,
1072-
start_height = ?range.start_bound(),
1073-
end_height = ?range.end_bound()
10741074
))]
10751075
pub async fn block_hashes(
10761076
&self,
1077-
range: impl RangeBounds<BlockHeight>,
1077+
heights: impl IntoIterator<Item = BlockHeight>,
10781078
) -> Result<Vec<CryptoHash>, ChainError> {
10791079
let next_height = self.tip_state.get().next_block_height;
1080-
// If the range is not empty, it can always be represented as start..=end.
1081-
let Some((start, end)) = range.to_inclusive() else {
1082-
return Ok(Vec::new());
1083-
};
10841080
// Everything up to (excluding) next_height is in confirmed_log.
1085-
let mut hashes = if let Ok(last_height) = next_height.try_sub_one() {
1086-
let usize_start = usize::try_from(start)?;
1087-
let usize_end = usize::try_from(end.min(last_height))?;
1088-
self.confirmed_log.read(usize_start..=usize_end).await?
1089-
} else {
1090-
Vec::new()
1091-
};
1081+
let (confirmed_heights, unconfirmed_heights) = heights
1082+
.into_iter()
1083+
.partition::<Vec<_>, _>(|height| *height < next_height);
1084+
let confirmed_indices = confirmed_heights
1085+
.into_iter()
1086+
.map(|height| usize::try_from(height.0).map_err(|_| ArithmeticError::Overflow))
1087+
.collect::<Result<_, _>>()?;
1088+
let confirmed_hashes = self.confirmed_log.multi_get(confirmed_indices).await?;
10921089
// Everything after (including) next_height in preprocessed_blocks if we have it.
1093-
let block_heights = (start.max(next_height).0..=end.0)
1094-
.map(BlockHeight)
1095-
.collect::<Vec<_>>();
1096-
for hash in self
1090+
let unconfirmed_hashes = self
10971091
.preprocessed_blocks
1098-
.multi_get(&block_heights)
1099-
.await?
1092+
.multi_get(&unconfirmed_heights)
1093+
.await?;
1094+
Ok(confirmed_hashes
11001095
.into_iter()
1096+
.chain(unconfirmed_hashes)
11011097
.flatten()
1102-
{
1103-
hashes.push(hash);
1104-
}
1105-
Ok(hashes)
1098+
.collect())
11061099
}
11071100

11081101
/// 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

@@ -1570,11 +1569,9 @@ where
15701569
bundles.sort_by_key(|b| b.bundle.timestamp);
15711570
info.requested_pending_message_bundles = bundles;
15721571
}
1573-
let mut hashes = Vec::new();
1574-
let height_ranges = into_ranges(query.request_sent_certificate_hashes_by_heights);
1575-
for height_range in height_ranges {
1576-
hashes.extend(chain.block_hashes(height_range).await?);
1577-
}
1572+
let hashes = chain
1573+
.block_hashes(query.request_sent_certificate_hashes_by_heights)
1574+
.await?;
15781575
info.requested_sent_certificate_hashes = hashes;
15791576
if let Some(start) = query.request_received_log_excluding_first_n {
15801577
let start = usize::try_from(start).map_err(|_| ArithmeticError::Overflow)?;
@@ -1653,21 +1650,6 @@ where
16531650
}
16541651
}
16551652

1656-
/// Returns an iterator of inclusive ranges that exactly cover the list of block heights.
1657-
fn into_ranges(
1658-
values: impl IntoIterator<Item = BlockHeight>,
1659-
) -> impl Iterator<Item = RangeInclusive<BlockHeight>> {
1660-
let mut values_iter = values.into_iter().peekable();
1661-
iter::from_fn(move || {
1662-
let start = values_iter.next()?;
1663-
let mut end = start;
1664-
while values_iter.peek() == end.try_add_one().ok().as_ref() {
1665-
end = values_iter.next()?;
1666-
}
1667-
Some(start..=end)
1668-
})
1669-
}
1670-
16711653
/// Returns the keys whose value is `None`.
16721654
fn missing_blob_ids(maybe_blobs: &BTreeMap<BlobId, Option<Blob>>) -> Vec<BlobId> {
16731655
maybe_blobs
@@ -1780,21 +1762,3 @@ impl<'a> CrossChainUpdateHelper<'a> {
17801762
Ok(bundles)
17811763
}
17821764
}
1783-
1784-
#[test]
1785-
fn test_into_ranges() {
1786-
assert_eq!(
1787-
into_ranges(vec![
1788-
BlockHeight(2),
1789-
BlockHeight(3),
1790-
BlockHeight(4),
1791-
BlockHeight(6)
1792-
],)
1793-
.collect::<Vec<_>>(),
1794-
vec![
1795-
BlockHeight(2)..=BlockHeight(4),
1796-
BlockHeight(6)..=BlockHeight(6)
1797-
]
1798-
);
1799-
assert_eq!(into_ranges(vec![]).collect::<Vec<_>>(), vec![]);
1800-
}

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
@@ -1776,7 +1776,7 @@ impl Runnable for Job {
17761776
.await
17771777
.context("Failed to load chain")?;
17781778
let block_hash = chain_state_view
1779-
.block_hashes(height..=height)
1779+
.block_hashes([height])
17801780
.await
17811781
.context("Failed to find a block hash for the given height")?[0];
17821782
let block = context

0 commit comments

Comments
 (0)