Skip to content

Commit

Permalink
Revisit definition of "near head of chain" of the runtime service (#1658
Browse files Browse the repository at this point in the history
)

* Revisit definition of "near head of chain" of the runtime service

* PR link
  • Loading branch information
tomaka authored Feb 2, 2024
1 parent 32dceee commit dc151bb
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 42 deletions.
25 changes: 23 additions & 2 deletions lib/src/chain/async_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ pub struct AsyncTree<TNow, TBl, TAsync> {
/// [`AsyncOpState::Finished`].
input_finalized_index: Option<fork_tree::NodeIndex>,

/// Index within [`AsyncTree::non_finalized_blocks`] of the current "input" best block.
/// `None` if the input best block is the output finalized block.
input_best_block_index: Option<fork_tree::NodeIndex>,

/// Incremented by one and stored within [`Block::input_best_block_weight`].
input_best_block_next_weight: u32,

Expand All @@ -202,6 +206,7 @@ where
output_finalized_async_user_data: config.finalized_async_user_data,
non_finalized_blocks: fork_tree::ForkTree::with_capacity(config.blocks_capacity),
input_finalized_index: None,
input_best_block_index: None,
input_best_block_next_weight: 2,
output_finalized_block_weight: 1, // `0` is reserved for blocks who are never best.
next_async_op_id: AsyncOpId(0),
Expand Down Expand Up @@ -254,6 +259,7 @@ where
user_data: block.user_data,
}),
input_finalized_index: self.input_finalized_index,
input_best_block_index: self.input_best_block_index,
input_best_block_next_weight: self.input_best_block_next_weight,
output_finalized_block_weight: self.output_finalized_block_weight,
next_async_op_id: self.next_async_op_id,
Expand Down Expand Up @@ -360,6 +366,14 @@ where
self.non_finalized_blocks.children(node)
}

/// Returns the [`NodeIndex`] of the current "input" best block.
///
/// Returns `None` if there is no best block. In terms of logic, this means that the best block
/// is the output finalized block, which is out of scope of this data structure.
pub fn input_best_block_index(&self) -> Option<NodeIndex> {
self.input_best_block_index
}

/// Returns the list of all non-finalized blocks that have been inserted, both input and
/// output.
///
Expand Down Expand Up @@ -765,14 +779,18 @@ where
};

// Insert the new block.
self.non_finalized_blocks.insert(
let new_index = self.non_finalized_blocks.insert(
parent_index,
Block {
user_data: block,
async_op,
input_best_block_weight,
},
)
);

self.input_best_block_index = Some(new_index);

new_index
}

/// Updates the state machine to take into account that the best block of the input has been
Expand All @@ -797,6 +815,8 @@ where
(None, None) => true,
});

self.input_best_block_index = new_best_block;

// If necessary, update the weight of the block.
match new_best_block
.map(|new_best_block| {
Expand Down Expand Up @@ -845,6 +865,7 @@ where
.is_ancestor(node_to_finalize, new_best_block));

self.input_finalized_index = Some(node_to_finalize);
self.input_best_block_index = Some(new_best_block);

// If necessary, update the weight of the block.
match &mut self
Expand Down
121 changes: 81 additions & 40 deletions light-base/src/runtime_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,9 @@ struct Block {
/// that it makes sense to cache it.
hash: [u8; 32],

/// Height of the block.
height: u64,

/// Header of the block in question.
/// Guaranteed to always be valid for the output best and finalized blocks. Otherwise,
/// not guaranteed to be valid.
Expand Down Expand Up @@ -796,6 +799,7 @@ async fn run_background<TPlat: PlatformRef>(
hash: header::hash_from_scale_encoded_header(
&config.genesis_block_scale_encoded_header,
),
height: 0,
scale_encoded_header: config.genesis_block_scale_encoded_header,
},
None,
Expand All @@ -815,7 +819,6 @@ async fn run_background<TPlat: PlatformRef>(
to_background: Box::pin(to_background.clone()),
to_background_tx: to_background_tx.clone(),
next_subscription_id: 0,
best_near_head_of_chain: config.sync_service.is_near_head_of_chain_heuristic().await,
tree,
runtimes: slab::Slab::with_capacity(2),
pending_subscriptions: VecDeque::with_capacity(8),
Expand Down Expand Up @@ -1299,17 +1302,19 @@ async fn run_background<TPlat: PlatformRef>(
// Additionally, the situation where a subscription is killed but the finalized block
// didn't change should be extremely rare anyway.
{
// TODO: restore
/*guarded.best_near_head_of_chain =
is_near_head_of_chain_heuristic(&sync_service, &guarded).await;*/

background.runtimes = slab::Slab::with_capacity(2); // TODO: hardcoded capacity

// TODO: DRY below
if let Some(finalized_block_runtime) = subscription.finalized_block_runtime {
let finalized_block_hash = header::hash_from_scale_encoded_header(
&subscription.finalized_block_scale_encoded_header,
);
let finalized_block_height = header::decode(
&subscription.finalized_block_scale_encoded_header,
background.sync_service.block_number_bytes(),
)
.unwrap()
.number; // TODO: consider feeding the information from the sync service?

let storage_code_len = u64::try_from(
finalized_block_runtime
Expand Down Expand Up @@ -1366,6 +1371,7 @@ async fn run_background<TPlat: PlatformRef>(
pinned_blocks: BTreeMap::new(),
finalized_block: Block {
hash: finalized_block_hash,
height: finalized_block_height,
scale_encoded_header: subscription
.finalized_block_scale_encoded_header,
},
Expand Down Expand Up @@ -1399,6 +1405,12 @@ async fn run_background<TPlat: PlatformRef>(
hash: header::hash_from_scale_encoded_header(
&block.scale_encoded_header,
),
height: header::decode(
&block.scale_encoded_header,
background.sync_service.block_number_bytes(),
)
.unwrap()
.number, // TODO: consider feeding the information from the sync service?
scale_encoded_header: block.scale_encoded_header,
},
parent_index,
Expand All @@ -1423,6 +1435,12 @@ async fn run_background<TPlat: PlatformRef>(
hash: header::hash_from_scale_encoded_header(
&subscription.finalized_block_scale_encoded_header,
),
height: header::decode(
&subscription.finalized_block_scale_encoded_header,
background.sync_service.block_number_bytes(),
)
.unwrap()
.number, // TODO: consider feeding the information from the sync service?
scale_encoded_header: subscription
.finalized_block_scale_encoded_header,
},
Expand All @@ -1449,6 +1467,12 @@ async fn run_background<TPlat: PlatformRef>(
hash: header::hash_from_scale_encoded_header(
&block.scale_encoded_header,
),
height: header::decode(
&block.scale_encoded_header,
background.sync_service.block_number_bytes(),
)
.unwrap()
.number, // TODO: consider feeding the information from the sync service?
scale_encoded_header: block.scale_encoded_header,
},
Some(parent_index),
Expand Down Expand Up @@ -1753,13 +1777,28 @@ async fn run_background<TPlat: PlatformRef>(
"foreground-is-near-head-of-chain-heuristic"
);

// The runtime service adds a delay between the moment a best block is reported by
// the sync service and the moment it is reported by the runtime service.
// Because of this, any "far from head of chain" to "near head of chain" transition
// must take that delay into account. The other way around ("near" to "far") is
// unaffected.
// If we aren't subscribed to the sync service yet, we notify that we are not
// near the head of the chain.
if !background.blocks_stream.is_none() {
let _ = result_tx.send(false);
continue;
}

// If the sync service is far from the head, the runtime service is also far.
// Check whether any runtime has been downloaded yet. If not, we notify that
// we're not near the head of the chain.
let Tree::FinalizedBlockRuntimeKnown {
tree,
finalized_block,
..
} = &background.tree
else {
let _ = result_tx.send(false);
continue;
};

// The runtime service head might be close to the sync service head, but if the
// sync service is far away from the head of the chain, then the runtime service
// is necessarily also far away.
if !background
.sync_service
.is_near_head_of_chain_heuristic()
Expand All @@ -1769,11 +1808,25 @@ async fn run_background<TPlat: PlatformRef>(
continue;
}

// If the sync service is near, report the result of
// `is_near_head_of_chain_heuristic()` when called at the latest best block that
// the runtime service reported through its API, to make sure that we don't report
// "near" while having reported only blocks that were far.
let _ = result_tx.send(background.best_near_head_of_chain);
// If the input best block (i.e. what the sync service feeds us) is equal to
// output finalized block (i.e. what the runtime service has downloaded), we are
// at the very head of the chain.
let Some(input_best) = tree.input_best_block_index() else {
let _ = result_tx.send(true);
continue;
};

// We consider ourselves as being at the head of the chain if the
// distance between the output tree best (i.e. whose runtime has
// been downloaded) and the input tree best (i.e. what the sync service
// feeds us) is smaller than a certain number of blocks.
// Note that the input best can have a smaller block height than the
// output, for example in case of reorg.
let is_near = tree[input_best].height.saturating_sub(
tree.output_best_block_index()
.map_or(finalized_block.height, |(idx, _)| tree[idx].height),
) <= 12;
let _ = result_tx.send(is_near);
}

WakeUpReason::ToBackground(ToBackground::UnpinBlock {
Expand Down Expand Up @@ -2227,16 +2280,6 @@ async fn run_background<TPlat: PlatformRef>(
);
}

let near_head_of_chain = background
.sync_service
.is_near_head_of_chain_heuristic()
.await;

// TODO: note that this code is never reached for parachains
if new_block.is_new_best {
background.best_near_head_of_chain = near_head_of_chain;
}

match &mut background.tree {
Tree::FinalizedBlockRuntimeKnown {
tree,
Expand All @@ -2260,6 +2303,12 @@ async fn run_background<TPlat: PlatformRef>(
hash: header::hash_from_scale_encoded_header(
&new_block.scale_encoded_header,
),
height: header::decode(
&new_block.scale_encoded_header,
background.sync_service.block_number_bytes(),
)
.unwrap()
.number, // TODO: consider feeding the information from the sync service?
scale_encoded_header: new_block.scale_encoded_header,
},
parent_index,
Expand All @@ -2279,6 +2328,12 @@ async fn run_background<TPlat: PlatformRef>(
hash: header::hash_from_scale_encoded_header(
&new_block.scale_encoded_header,
),
height: header::decode(
&new_block.scale_encoded_header,
background.sync_service.block_number_bytes(),
)
.unwrap()
.number, // TODO: consider feeding the information from the sync service?
scale_encoded_header: new_block.scale_encoded_header,
},
Some(parent_index),
Expand Down Expand Up @@ -2352,13 +2407,6 @@ async fn run_background<TPlat: PlatformRef>(
block_hash = HashDisplay(&hash)
);

let near_head_of_chain = background
.sync_service
.is_near_head_of_chain_heuristic()
.await;

background.best_near_head_of_chain = near_head_of_chain;

match &mut background.tree {
Tree::FinalizedBlockRuntimeKnown {
finalized_block,
Expand Down Expand Up @@ -2410,9 +2458,6 @@ async fn run_background<TPlat: PlatformRef>(
.format_with(", ", |block, fmt| fmt(&HashDisplay(&block.hash)))
.to_string();

// TODO: the line below is a complete hack; the code that updates this value is never reached for parachains, and as such the line below is here to update this field
background.best_near_head_of_chain = true;

// Try to find an existing runtime identical to the one that has just been
// downloaded. This loop is `O(n)`, but given that we expect this list to very
// small (at most 1 or 2 elements), this is not a problem.
Expand Down Expand Up @@ -2603,10 +2648,6 @@ struct Background<TPlat: PlatformRef> {
/// [`Tree::FinalizedBlockRuntimeUnknown`].
next_subscription_id: u64,

/// Return value of calling [`sync_service::SyncService::is_near_head_of_chain_heuristic`]
/// after the latest best block update.
best_near_head_of_chain: bool,

/// List of runtimes referenced by the tree in [`Tree`] and by
/// [`Tree::FinalizedBlockRuntimeKnown::pinned_blocks`].
///
Expand Down
4 changes: 4 additions & 0 deletions wasm-node/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Changed

- Clarify the value of `isSyncing` returned by `system_health`. The value will be equal to `false` if no peer that smoldot is connected to is more than 10 blocks ahead, and that the highest block would runtime code has been downloaded is no more than 12 blocks ahead of the highest block of the local chain. ([#1658](https://github.com/smol-dot/smoldot/pull/1658))

### Fixed

- The warp syncing process no longer repeats itself every 32 blocks, which was causing unnecessary bandwidth and CPU usage. ([#1656](https://github.com/smol-dot/smoldot/pull/1656))
Expand Down

0 comments on commit dc151bb

Please sign in to comment.