Skip to content

Commit 6c34aa1

Browse files
authored
[stable2412] Partial backport of #6825 and #7205 (#7298)
This is a partial backport of #6825 and #7205. Only the node-side minor changes were ported. However, this creates forward compatibility of the omni-node from 2412 with runtimes built after #6825.
1 parent f5d1d17 commit 6c34aa1

9 files changed

Lines changed: 189 additions & 21 deletions

File tree

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cumulus/client/consensus/aura/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ polkadot-node-subsystem-util.default-features = true
8282
polkadot-overseer.workspace = true
8383
polkadot-overseer.default-features = true
8484

85+
[dev-dependencies]
86+
cumulus-test-client = { workspace = true }
87+
cumulus-test-relay-sproof-builder = { workspace = true }
88+
sp-keyring = { workspace = true }
89+
8590
[features]
8691
# Allows collator to use full PoV size for block building
8792
full-pov-size = []

cumulus/client/consensus/aura/src/collators/lookahead.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ where
336336
);
337337
Some(super::can_build_upon::<_, _, P>(
338338
slot_now,
339+
relay_slot,
339340
timestamp,
340341
block_hash,
341342
included_block,

cumulus/client/consensus/aura/src/collators/mod.rs

Lines changed: 136 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use polkadot_primitives::{
3434
ValidationCodeHash,
3535
};
3636
use sc_consensus_aura::{standalone as aura_internal, AuraApi};
37-
use sp_api::ProvideRuntimeApi;
37+
use sp_api::{ApiExt, ProvideRuntimeApi};
3838
use sp_core::Pair;
3939
use sp_keystore::KeystorePtr;
4040
use sp_timestamp::Timestamp;
@@ -160,7 +160,8 @@ async fn cores_scheduled_for_para(
160160
// Checks if we own the slot at the given block and whether there
161161
// is space in the unincluded segment.
162162
async fn can_build_upon<Block: BlockT, Client, P>(
163-
slot: Slot,
163+
para_slot: Slot,
164+
relay_slot: Slot,
164165
timestamp: Timestamp,
165166
parent_hash: Block::Hash,
166167
included_block: Block::Hash,
@@ -169,25 +170,35 @@ async fn can_build_upon<Block: BlockT, Client, P>(
169170
) -> Option<SlotClaim<P::Public>>
170171
where
171172
Client: ProvideRuntimeApi<Block>,
172-
Client::Api: AuraApi<Block, P::Public> + AuraUnincludedSegmentApi<Block>,
173+
Client::Api: AuraApi<Block, P::Public> + AuraUnincludedSegmentApi<Block> + ApiExt<Block>,
173174
P: Pair,
174175
P::Public: Codec,
175176
P::Signature: Codec,
176177
{
177178
let runtime_api = client.runtime_api();
178179
let authorities = runtime_api.authorities(parent_hash).ok()?;
179-
let author_pub = aura_internal::claim_slot::<P>(slot, &authorities, keystore).await?;
180+
let author_pub = aura_internal::claim_slot::<P>(para_slot, &authorities, keystore).await?;
180181

181-
// Here we lean on the property that building on an empty unincluded segment must always
182-
// be legal. Skipping the runtime API query here allows us to seamlessly run this
183-
// collator against chains which have not yet upgraded their runtime.
184-
if parent_hash != included_block &&
185-
!runtime_api.can_build_upon(parent_hash, included_block, slot).ok()?
186-
{
187-
return None
182+
// This function is typically called when we want to build block N. At that point, the
183+
// unincluded segment in the runtime is unaware of the hash of block N-1. If the unincluded
184+
// segment in the runtime is full, but block N-1 is the included block, the unincluded segment
185+
// should have length 0 and we can build. Since the hash is not available to the runtime
186+
// however, we need this extra check here.
187+
if parent_hash == included_block {
188+
return Some(SlotClaim::unchecked::<P>(author_pub, para_slot, timestamp));
188189
}
189190

190-
Some(SlotClaim::unchecked::<P>(author_pub, slot, timestamp))
191+
let api_version = runtime_api
192+
.api_version::<dyn AuraUnincludedSegmentApi<Block>>(parent_hash)
193+
.ok()
194+
.flatten()?;
195+
196+
let slot = if api_version > 1 { relay_slot } else { para_slot };
197+
198+
runtime_api
199+
.can_build_upon(parent_hash, included_block, slot)
200+
.ok()?
201+
.then(|| SlotClaim::unchecked::<P>(author_pub, para_slot, timestamp))
191202
}
192203

193204
/// Use [`cumulus_client_consensus_common::find_potential_parents`] to find parachain blocks that
@@ -239,3 +250,116 @@ where
239250
.max_by_key(|a| a.depth)
240251
.map(|parent| (included_block, parent))
241252
}
253+
254+
#[cfg(test)]
255+
mod tests {
256+
use crate::collators::can_build_upon;
257+
use codec::Encode;
258+
use cumulus_primitives_aura::Slot;
259+
use cumulus_primitives_core::BlockT;
260+
use cumulus_relay_chain_interface::PHash;
261+
use cumulus_test_client::{
262+
runtime::{Block, Hash},
263+
Client, DefaultTestClientBuilderExt, InitBlockBuilder, TestClientBuilder,
264+
TestClientBuilderExt,
265+
};
266+
use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
267+
use polkadot_primitives::HeadData;
268+
use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy};
269+
use sp_consensus::BlockOrigin;
270+
use sp_keystore::{Keystore, KeystorePtr};
271+
use sp_timestamp::Timestamp;
272+
use std::sync::Arc;
273+
274+
async fn import_block<I: BlockImport<Block>>(
275+
importer: &I,
276+
block: Block,
277+
origin: BlockOrigin,
278+
import_as_best: bool,
279+
) {
280+
let (header, body) = block.deconstruct();
281+
282+
let mut block_import_params = BlockImportParams::new(origin, header);
283+
block_import_params.fork_choice = Some(ForkChoiceStrategy::Custom(import_as_best));
284+
block_import_params.body = Some(body);
285+
importer.import_block(block_import_params).await.unwrap();
286+
}
287+
288+
fn sproof_with_parent_by_hash(client: &Client, hash: PHash) -> RelayStateSproofBuilder {
289+
let header = client.header(hash).ok().flatten().expect("No header for parent block");
290+
let included = HeadData(header.encode());
291+
let mut builder = RelayStateSproofBuilder::default();
292+
builder.para_id = cumulus_test_client::runtime::PARACHAIN_ID.into();
293+
builder.included_para_head = Some(included);
294+
295+
builder
296+
}
297+
async fn build_and_import_block(client: &Client, included: Hash) -> Block {
298+
let sproof = sproof_with_parent_by_hash(client, included);
299+
300+
let block_builder = client.init_block_builder(None, sproof).block_builder;
301+
302+
let block = block_builder.build().unwrap().block;
303+
304+
let origin = BlockOrigin::NetworkInitialSync;
305+
import_block(client, block.clone(), origin, true).await;
306+
block
307+
}
308+
309+
fn set_up_components() -> (Arc<Client>, KeystorePtr) {
310+
let keystore = Arc::new(sp_keystore::testing::MemoryKeystore::new()) as Arc<_>;
311+
for key in sp_keyring::Sr25519Keyring::iter() {
312+
Keystore::sr25519_generate_new(
313+
&*keystore,
314+
sp_application_crypto::key_types::AURA,
315+
Some(&key.to_seed()),
316+
)
317+
.expect("Can insert key into MemoryKeyStore");
318+
}
319+
(Arc::new(TestClientBuilder::new().build()), keystore)
320+
}
321+
322+
/// This tests a special scenario where the unincluded segment in the runtime
323+
/// is full. We are calling `can_build_upon`, passing the last built block as the
324+
/// included one. In the runtime we will not find the hash of the included block in the
325+
/// unincluded segment. The `can_build_upon` runtime API would therefore return `false`, but
326+
/// we are ensuring on the node side that we are are always able to build on the included block.
327+
#[tokio::test]
328+
async fn test_can_build_upon() {
329+
let (client, keystore) = set_up_components();
330+
331+
let genesis_hash = client.chain_info().genesis_hash;
332+
let mut last_hash = genesis_hash;
333+
334+
// Fill up the unincluded segment tracker in the runtime.
335+
while can_build_upon::<_, _, sp_consensus_aura::sr25519::AuthorityPair>(
336+
Slot::from(u64::MAX),
337+
Slot::from(u64::MAX),
338+
Timestamp::default(),
339+
last_hash,
340+
genesis_hash,
341+
&*client,
342+
&keystore,
343+
)
344+
.await
345+
.is_some()
346+
{
347+
let block = build_and_import_block(&client, genesis_hash).await;
348+
last_hash = block.header().hash();
349+
}
350+
351+
// Blocks were built with the genesis hash set as included block.
352+
// We call `can_build_upon` with the last built block as the included block.
353+
let result = can_build_upon::<_, _, sp_consensus_aura::sr25519::AuthorityPair>(
354+
Slot::from(u64::MAX),
355+
Slot::from(u64::MAX),
356+
Timestamp::default(),
357+
last_hash,
358+
last_hash,
359+
&*client,
360+
&keystore,
361+
)
362+
.await;
363+
assert!(result.is_some());
364+
}
365+
}

cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ use cumulus_relay_chain_interface::RelayChainInterface;
2525

2626
use polkadot_primitives::{
2727
vstaging::{ClaimQueueOffset, CoreSelector, DEFAULT_CLAIM_QUEUE_OFFSET},
28-
BlockId, CoreIndex, Hash as RelayHash, Header as RelayHeader, Id as ParaId,
29-
OccupiedCoreAssumption,
28+
Block as RelayBlock, BlockId, CoreIndex, Hash as RelayHash, Header as RelayHeader,
29+
Id as ParaId, OccupiedCoreAssumption,
3030
};
3131

3232
use futures::prelude::*;
@@ -300,8 +300,17 @@ where
300300
// on-chain data.
301301
collator.collator_service().check_block_status(parent_hash, &parent_header);
302302

303+
let Ok(relay_slot) =
304+
sc_consensus_babe::find_pre_digest::<RelayBlock>(relay_parent_header)
305+
.map(|babe_pre_digest| babe_pre_digest.slot())
306+
else {
307+
tracing::error!(target: crate::LOG_TARGET, "Relay chain does not contain babe slot. This should never happen.");
308+
continue;
309+
};
310+
303311
let slot_claim = match crate::collators::can_build_upon::<_, _, P>(
304312
para_slot.slot,
313+
relay_slot,
305314
para_slot.timestamp,
306315
parent_hash,
307316
included_block,

cumulus/client/parachain-inherent/src/mock.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
use crate::{ParachainInherentData, INHERENT_IDENTIFIER};
1818
use codec::Decode;
1919
use cumulus_primitives_core::{
20-
relay_chain, relay_chain::UpgradeGoAhead, InboundDownwardMessage, InboundHrmpMessage, ParaId,
21-
PersistedValidationData,
20+
relay_chain,
21+
relay_chain::{Slot, UpgradeGoAhead},
22+
InboundDownwardMessage, InboundHrmpMessage, ParaId, PersistedValidationData,
2223
};
2324
use cumulus_primitives_parachain_inherent::MessageQueueChain;
2425
use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
@@ -28,9 +29,6 @@ use sp_inherents::{InherentData, InherentDataProvider};
2829
use sp_runtime::traits::Block;
2930
use std::collections::BTreeMap;
3031

31-
/// Relay chain slot duration, in milliseconds.
32-
pub const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000;
33-
3432
/// Inherent data provider that supplies mocked validation data.
3533
///
3634
/// This is useful when running a node that is not actually backed by any relay chain.
@@ -175,8 +173,7 @@ impl<R: Send + Sync + GenerateRandomness<u64>> InherentDataProvider
175173
// Calculate the mocked relay block based on the current para block
176174
let relay_parent_number =
177175
self.relay_offset + self.relay_blocks_per_para_block * self.current_para_block;
178-
sproof_builder.current_slot =
179-
((relay_parent_number / RELAY_CHAIN_SLOT_DURATION_MILLIS) as u64).into();
176+
sproof_builder.current_slot = Slot::from(relay_parent_number as u64);
180177

181178
sproof_builder.upgrade_go_ahead = self.upgrade_go_ahead;
182179
// Process the downward messages and set up the correct head

cumulus/xcm/xcm-emulator/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,6 +1118,7 @@ macro_rules! decl_test_networks {
11181118
) -> $crate::ParachainInherentData {
11191119
let mut sproof = $crate::RelayStateSproofBuilder::default();
11201120
sproof.para_id = para_id.into();
1121+
sproof.current_slot = $crate::polkadot_primitives::Slot::from(relay_parent_number as u64);
11211122

11221123
// egress channel
11231124
let e_index = sproof.hrmp_egress_channel_index.get_or_insert_with(Vec::new);

prdoc/pr_6825.prdoc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
2+
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
3+
4+
title: Use relay chain slot for velocity measurement on parachains
5+
6+
doc:
7+
- audience: Runtime Dev
8+
description: |
9+
This is a partial backport of #6825. It makes the node side of omni-node forward compatible with runtimes that
10+
are build with the changes from #6825.
11+
12+
crates:
13+
- name: cumulus-client-parachain-inherent
14+
bump: patch
15+
- name: cumulus-client-consensus-aura
16+
bump: patch
17+
- name: xcm-emulator
18+
bump: patch

prdoc/pr_7205.prdoc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
title: 'Collator: Fix `can_build_upon` by always allowing to build on included block'
2+
doc:
3+
- audience: Node Dev
4+
description: |-
5+
Fixes a bug introduced in #6825.
6+
We should always allow building on the included block of parachains. In situations where the unincluded segment
7+
is full, but the included block moved to the most recent block, building was wrongly disallowed.
8+
crates:
9+
- name: cumulus-client-consensus-aura
10+
bump: patch

0 commit comments

Comments
 (0)