diff --git a/cumulus/client/consensus/aura/src/collators/lookahead.rs b/cumulus/client/consensus/aura/src/collators/lookahead.rs
index 68c243b57ad25..a7a9a72325d4c 100644
--- a/cumulus/client/consensus/aura/src/collators/lookahead.rs
+++ b/cumulus/client/consensus/aura/src/collators/lookahead.rs
@@ -317,6 +317,23 @@ where
},
};
+ let session_index = match params
+ .relay_client
+ .session_index_for_child(relay_parent)
+ .await
+ {
+ Ok(session_index) => session_index,
+ Err(err) => {
+ tracing::error!(
+ target: crate::LOG_TARGET,
+ ?err,
+ ?relay_parent,
+ "Failed to fetch session index."
+ );
+ continue;
+ },
+ };
+
let parent_search_result = match crate::collators::find_parent(
relay_parent,
params.para_id,
@@ -501,11 +518,12 @@ where
SubmitCollationParams {
relay_parent,
collation,
- parent_head: parent_header.encode().into(),
validation_code_hash,
result_sender: None,
core_index,
scheduling_parent: None,
+ session_index,
+ validation_data,
},
),
"SubmitCollation",
diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs b/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs
index 7cfde76a7921e..98885c84350c6 100644
--- a/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs
+++ b/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs
@@ -474,7 +474,7 @@ where
parachain_candidate: candidate.into(),
validation_code_hash,
core_index: core.core_index(),
- max_pov_size: validation_data.max_pov_size,
+ validation_data,
}) {
tracing::error!(target: crate::LOG_TARGET, ?err, "Unable to send block to collation task.");
return;
diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/collation_task.rs b/cumulus/client/consensus/aura/src/collators/slot_based/collation_task.rs
index b1008ac283111..c0fa0846de001 100644
--- a/cumulus/client/consensus/aura/src/collators/slot_based/collation_task.rs
+++ b/cumulus/client/consensus/aura/src/collators/slot_based/collation_task.rs
@@ -15,7 +15,6 @@
// You should have received a copy of the GNU General Public License
// along with Cumulus. If not, see .
-use codec::Encode;
use std::path::PathBuf;
use cumulus_client_collator::service::ServiceInterface as CollatorServiceInterface;
@@ -126,7 +125,7 @@ async fn handle_collation_message session_index,
+ Err(err) => {
+ tracing::error!(
+ target: LOG_TARGET,
+ ?err,
+ ?relay_parent,
+ "Failed to fetch session index."
+ );
+ return;
+ },
+ };
+
tracing::debug!(target: LOG_TARGET, ?core_index, ?hash, %number, "Submitting collation for core.");
overseer_handle
@@ -178,11 +190,12 @@ async fn handle_collation_message {
pub validation_code_hash: ValidationCodeHash,
/// Core index that this block should be submitted on
pub core_index: CoreIndex,
- /// Maximum pov size. Currently needed only for exporting PoV.
- pub max_pov_size: u32,
+ /// The persisted validation data for this collation.
+ pub validation_data: PersistedValidationData,
}
diff --git a/polkadot/node/collation-generation/src/lib.rs b/polkadot/node/collation-generation/src/lib.rs
index 6a9aa2c361e33..b523babed1848 100644
--- a/polkadot/node/collation-generation/src/lib.rs
+++ b/polkadot/node/collation-generation/src/lib.rs
@@ -229,44 +229,28 @@ impl CollationGenerationSubsystem {
let SubmitCollationParams {
relay_parent,
collation,
- parent_head,
validation_code_hash,
result_sender,
core_index,
scheduling_parent,
+ session_index,
+ validation_data,
} = params;
- let mut validation_data = match request_persisted_validation_data(
- relay_parent,
- config.para_id,
- OccupiedCoreAssumption::TimedOut,
- ctx.sender(),
- )
- .await
- .await??
- {
- Some(v) => v,
- None => {
- gum::debug!(
- target: LOG_TARGET,
- relay_parent = ?relay_parent,
- our_para = %config.para_id,
- "No validation data for para - does it exist at this relay-parent?",
- );
- return Ok(());
- },
- };
-
- // We need to swap the parent-head data, but all other fields here will be correct.
- validation_data.parent_head = parent_head;
-
- let claim_queue = request_claim_queue(relay_parent, ctx.sender()).await.await??;
+ // For V2 descriptors, scheduling_parent is None and relay_parent serves both roles.
+ let scheduling_parent_or_relay = scheduling_parent.unwrap_or(relay_parent);
+ let claim_queue =
+ request_claim_queue(scheduling_parent_or_relay, ctx.sender()).await.await??;
- let session_index =
- request_session_index_for_child(relay_parent, ctx.sender()).await.await??;
+ let scheduling_session =
+ request_session_index_for_child(scheduling_parent_or_relay, ctx.sender())
+ .await
+ .await??;
- let session_info =
- self.session_info_cache.get(relay_parent, session_index, ctx.sender()).await?;
+ let session_info = self
+ .session_info_cache
+ .get(scheduling_parent_or_relay, scheduling_session, ctx.sender())
+ .await?;
let collation = PreparedCollation {
collation,
relay_parent,
@@ -276,6 +260,7 @@ impl CollationGenerationSubsystem {
n_validators: session_info.n_validators,
core_index,
session_index,
+ scheduling_session,
};
construct_and_distribute_receipt(
@@ -300,7 +285,7 @@ impl CollationGenerationSubsystem {
return Ok(());
};
- let Some(relay_parent) = maybe_activated else { return Ok(()) };
+ let Some(activated) = maybe_activated else { return Ok(()) };
// If there is no collation function provided, bail out early.
// Important: Lookahead collator and slot based collator do not use `CollatorFn`.
@@ -313,14 +298,14 @@ impl CollationGenerationSubsystem {
let _timer = self.metrics.time_new_activation();
let session_index =
- request_session_index_for_child(relay_parent, ctx.sender()).await.await??;
+ request_session_index_for_child(activated, ctx.sender()).await.await??;
let session_info =
- self.session_info_cache.get(relay_parent, session_index, ctx.sender()).await?;
+ self.session_info_cache.get(activated, session_index, ctx.sender()).await?;
let n_validators = session_info.n_validators;
let claim_queue =
- ClaimQueueSnapshot::from(request_claim_queue(relay_parent, ctx.sender()).await.await??);
+ ClaimQueueSnapshot::from(request_claim_queue(activated, ctx.sender()).await.await??);
let assigned_cores = claim_queue
.iter_all_claims()
@@ -338,7 +323,7 @@ impl CollationGenerationSubsystem {
// for some more blocks, or even time out. We assume all cores are being freed.
let mut validation_data = match request_persisted_validation_data(
- relay_parent,
+ activated,
para_id,
// Just use included assumption always. If there are no pending candidates it's a
// no-op.
@@ -352,7 +337,7 @@ impl CollationGenerationSubsystem {
None => {
gum::debug!(
target: LOG_TARGET,
- relay_parent = ?relay_parent,
+ relay_parent = ?activated,
our_para = %para_id,
"validation data is not available",
);
@@ -361,7 +346,7 @@ impl CollationGenerationSubsystem {
};
let validation_code_hash = match request_validation_code_hash(
- relay_parent,
+ activated,
para_id,
// Just use included assumption always. If there are no pending candidates it's a
// no-op.
@@ -375,7 +360,7 @@ impl CollationGenerationSubsystem {
None => {
gum::debug!(
target: LOG_TARGET,
- relay_parent = ?relay_parent,
+ relay_parent = ?activated,
our_para = %para_id,
"validation code hash is not found.",
);
@@ -403,7 +388,7 @@ impl CollationGenerationSubsystem {
};
let (collation, result_sender) =
- match collator_fn(relay_parent, &validation_data).await {
+ match collator_fn(activated, &validation_data).await {
Some(collation) => collation.into_inner(),
None => {
gum::debug!(
@@ -486,12 +471,14 @@ impl CollationGenerationSubsystem {
PreparedCollation {
collation,
para_id,
- relay_parent,
+ relay_parent: activated,
validation_data: validation_data.clone(),
validation_code_hash,
n_validators,
core_index: descriptor_core_index,
session_index,
+ // V2 only: relay_parent == scheduling_parent, same session.
+ scheduling_session: session_index,
},
&mut task_sender,
result_sender,
@@ -572,7 +559,10 @@ struct PreparedCollation {
validation_code_hash: ValidationCodeHash,
n_validators: usize,
core_index: CoreIndex,
+ /// The relay parent's session index.
session_index: SessionIndex,
+ /// The scheduling parent's session index.
+ scheduling_session: SessionIndex,
}
/// Takes a prepared collation, along with its context, and produces a candidate receipt
@@ -594,6 +584,7 @@ async fn construct_and_distribute_receipt(
n_validators,
core_index,
session_index,
+ scheduling_session,
} = collation;
let persisted_validation_data_hash = validation_data.hash();
@@ -641,6 +632,7 @@ async fn construct_and_distribute_receipt(
relay_parent,
core_index,
session_index,
+ scheduling_session,
persisted_validation_data_hash,
pov_hash,
erasure_root,
diff --git a/polkadot/node/collation-generation/src/tests.rs b/polkadot/node/collation-generation/src/tests.rs
index a306b3b9d7c01..7807f917fe294 100644
--- a/polkadot/node/collation-generation/src/tests.rs
+++ b/polkadot/node/collation-generation/src/tests.rs
@@ -178,10 +178,16 @@ fn submit_collation_is_no_op_before_initialization() {
relay_parent: Hash::repeat_byte(0),
scheduling_parent: Some(Hash::repeat_byte(0)),
collation: test_collation(),
- parent_head: vec![1, 2, 3].into(),
validation_code_hash: Hash::repeat_byte(1).into(),
result_sender: None,
core_index: CoreIndex(0),
+ session_index: 1,
+ validation_data: PersistedValidationData {
+ parent_head: vec![1, 2, 3].into(),
+ relay_parent_number: 10,
+ relay_parent_storage_root: Hash::repeat_byte(1),
+ max_pov_size: 1024,
+ },
}),
})
.await;
@@ -223,10 +229,11 @@ fn submit_collation_leads_to_distribution() {
relay_parent,
scheduling_parent: Some(relay_parent),
collation,
- parent_head: dummy_head_data(),
validation_code_hash,
result_sender: None,
core_index: CoreIndex(0),
+ session_index: 1,
+ validation_data: expected_pvd.clone(),
}),
})
.await;
@@ -234,8 +241,6 @@ fn submit_collation_leads_to_distribution() {
helpers::handle_runtime_calls_on_submit_collation(
&mut virtual_overseer,
relay_parent,
- para_id,
- expected_pvd.clone(),
[(CoreIndex(0), VecDeque::from([para_id]))].into(),
)
.await;
@@ -259,6 +264,78 @@ fn submit_collation_leads_to_distribution() {
});
}
+#[test]
+fn submit_collation_v3_runtime_calls_use_scheduling_parent() {
+ let relay_parent = Hash::repeat_byte(0xAA);
+ let scheduling_parent = Hash::repeat_byte(0xBB);
+ let validation_code_hash = ValidationCodeHash::from(Hash::repeat_byte(42));
+ let parent_head = dummy_head_data();
+ let para_id = ParaId::from(5);
+ let expected_pvd = PersistedValidationData {
+ parent_head: parent_head.clone(),
+ relay_parent_number: 10,
+ relay_parent_storage_root: Hash::repeat_byte(1),
+ max_pov_size: 1024,
+ };
+
+ test_harness(|mut virtual_overseer| async move {
+ virtual_overseer
+ .send(FromOrchestra::Communication {
+ msg: CollationGenerationMessage::Initialize(test_config_no_collator(para_id)),
+ })
+ .await;
+
+ let mut collation = test_collation();
+ collation.upward_messages.force_push(UMP_SEPARATOR);
+ collation
+ .upward_messages
+ .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(0)).encode());
+
+ virtual_overseer
+ .send(FromOrchestra::Communication {
+ msg: CollationGenerationMessage::SubmitCollation(SubmitCollationParams {
+ relay_parent,
+ scheduling_parent: Some(scheduling_parent),
+ collation,
+ validation_code_hash,
+ result_sender: None,
+ core_index: CoreIndex(0),
+ session_index: 1,
+ validation_data: expected_pvd.clone(),
+ }),
+ })
+ .await;
+
+ // All runtime API calls must be against the scheduling_parent, not relay_parent.
+ helpers::handle_runtime_calls_on_submit_collation(
+ &mut virtual_overseer,
+ scheduling_parent,
+ [(CoreIndex(0), VecDeque::from([para_id]))].into(),
+ )
+ .await;
+
+ assert_matches!(
+ overseer_recv(&mut virtual_overseer).await,
+ AllMessages::CollatorProtocol(CollatorProtocolMessage::DistributeCollation {
+ candidate_receipt,
+ parent_head_data_hash,
+ ..
+ }) => {
+ let CandidateReceiptV2 { descriptor, .. } = candidate_receipt;
+ assert_eq!(parent_head_data_hash, parent_head.hash());
+ assert_eq!(descriptor.persisted_validation_data_hash(), expected_pvd.hash());
+ // relay_parent in the descriptor is the execution context
+ assert_eq!(descriptor.relay_parent(), relay_parent);
+ // scheduling_parent in the descriptor is the scheduling context
+ assert_eq!(descriptor.scheduling_parent(), scheduling_parent);
+ assert_eq!(descriptor.version(), CandidateDescriptorVersion::V3);
+ }
+ );
+
+ virtual_overseer
+ });
+}
+
#[test]
fn distribute_collation_only_for_assigned_para_id_at_offset_0() {
let activated_hash: Hash = [1; 32].into();
@@ -469,10 +546,11 @@ fn v2_receipts_failed_core_index_check() {
relay_parent,
scheduling_parent: Some(relay_parent),
collation: test_collation(),
- parent_head: dummy_head_data(),
validation_code_hash,
result_sender: None,
core_index: CoreIndex(0),
+ session_index: 1,
+ validation_data: expected_pvd.clone(),
}),
})
.await;
@@ -480,8 +558,6 @@ fn v2_receipts_failed_core_index_check() {
helpers::handle_runtime_calls_on_submit_collation(
&mut virtual_overseer,
relay_parent,
- para_id,
- expected_pvd.clone(),
// Core index commitment is on core 0 but don't add any assignment for core 0.
[(CoreIndex(1), [para_id].into_iter().collect())].into_iter().collect(),
)
@@ -527,10 +603,11 @@ fn approved_peer_signal() {
relay_parent,
scheduling_parent: Some(relay_parent),
collation,
- parent_head: dummy_head_data(),
validation_code_hash,
result_sender: None,
core_index: CoreIndex(0),
+ session_index: 1,
+ validation_data: expected_pvd.clone(),
}),
})
.await;
@@ -538,8 +615,6 @@ fn approved_peer_signal() {
helpers::handle_runtime_calls_on_submit_collation(
&mut virtual_overseer,
relay_parent,
- para_id,
- expected_pvd.clone(),
[(CoreIndex(0), [para_id].into_iter().collect())].into_iter().collect(),
)
.await;
@@ -706,32 +781,20 @@ mod helpers {
}
}
- // Handles all runtime requests performed in `handle_submit_collation`
+ // Handles all runtime requests performed in `handle_submit_collation`.
+ // All requests are made against the scheduling parent (or relay_parent for V2).
pub async fn handle_runtime_calls_on_submit_collation(
virtual_overseer: &mut VirtualOverseer,
- relay_parent: Hash,
- para_id: ParaId,
- expected_pvd: PersistedValidationData,
+ scheduling_parent: Hash,
claim_queue: BTreeMap>,
) {
- assert_matches!(
- overseer_recv(virtual_overseer).await,
- AllMessages::RuntimeApi(RuntimeApiMessage::Request(rp, RuntimeApiRequest::PersistedValidationData(id, a, tx))) => {
- assert_eq!(rp, relay_parent);
- assert_eq!(id, para_id);
- assert_eq!(a, OccupiedCoreAssumption::TimedOut);
-
- tx.send(Ok(Some(expected_pvd))).unwrap();
- }
- );
-
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
rp,
RuntimeApiRequest::ClaimQueue(tx),
)) => {
- assert_eq!(rp, relay_parent);
+ assert_eq!(rp, scheduling_parent);
tx.send(Ok(claim_queue)).unwrap();
}
);
@@ -739,7 +802,7 @@ mod helpers {
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(rp, RuntimeApiRequest::SessionIndexForChild(tx))) => {
- assert_eq!(rp, relay_parent);
+ assert_eq!(rp, scheduling_parent);
tx.send(Ok(1)).unwrap();
}
);
@@ -747,7 +810,7 @@ mod helpers {
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(rp, RuntimeApiRequest::Validators(tx))) => {
- assert_eq!(rp, relay_parent);
+ assert_eq!(rp, scheduling_parent);
tx.send(Ok(vec![
Sr25519Keyring::Alice.public().into(),
Sr25519Keyring::Bob.public().into(),
diff --git a/polkadot/node/core/backing/src/tests/mod.rs b/polkadot/node/core/backing/src/tests/mod.rs
index a9a5d3e9920ef..c5a79b5700e50 100644
--- a/polkadot/node/core/backing/src/tests/mod.rs
+++ b/polkadot/node/core/backing/src/tests/mod.rs
@@ -4416,7 +4416,8 @@ fn version_acceptance_before_and_after_v3_activation_on_second() {
test_state.chain_ids[0],
test_state.relay_parent,
CoreIndex(0),
- 1,
+ 1, // session_index
+ 1, // scheduling_session_index
pvd.hash(),
pov_hash,
make_erasure_root(&test_state, pov.clone(), pvd.clone()),
@@ -4521,7 +4522,8 @@ fn version_acceptance_before_and_after_v3_activation_on_statement() {
test_state.chain_ids[0],
test_state.relay_parent,
CoreIndex(0),
- 1,
+ 1, // session_index
+ 1, // scheduling_session_index
pvd.hash(),
pov_hash,
make_erasure_root(&test_state, pov.clone(), pvd.clone()),
diff --git a/polkadot/node/core/candidate-validation/src/tests.rs b/polkadot/node/core/candidate-validation/src/tests.rs
index 8ce2eb57bdb0e..f6f8e54cc80d8 100644
--- a/polkadot/node/core/candidate-validation/src/tests.rs
+++ b/polkadot/node/core/candidate-validation/src/tests.rs
@@ -921,6 +921,7 @@ fn v3_ump_signal_enforcement() {
relay_parent,
CoreIndex(0),
1,
+ 1,
validation_data.hash(),
pov.hash(),
validation_code.hash(),
@@ -2681,6 +2682,7 @@ fn pre_validation_v3_scheduling_offset_mismatch() {
dummy_hash(), // relay_parent
CoreIndex(0),
1, // session_index
+ 1, // scheduling_session_index
dummy_hash(),
pov.hash(),
validation_code.hash(),
@@ -3057,6 +3059,7 @@ fn pre_validation_relay_parent_session_check_v3_ancestor_query() {
relay_parent,
CoreIndex(1),
1,
+ 1,
dummy_hash(),
pov.hash(),
validation_code.hash(),
diff --git a/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs b/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs
index c46770f617667..68de7ee0a3aba 100644
--- a/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs
+++ b/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs
@@ -131,6 +131,7 @@ impl CandidateBuilder {
self.relay_parent,
CoreIndex(0),
1,
+ 1,
persisted_validation_data.hash(),
Hash::repeat_byte(1),
Hash::repeat_byte(42),
diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs
index 384cacf575e52..465ad05b5f9e3 100644
--- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs
+++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs
@@ -4190,6 +4190,7 @@ fn v3_sanity_check_uses_scheduling_session_not_relay_parent_session() {
relay_parent,
core,
relay_parent_session,
+ relay_parent_session,
Hash::zero(),
Hash::zero(),
Hash::zero(),
diff --git a/polkadot/node/primitives/src/lib.rs b/polkadot/node/primitives/src/lib.rs
index fbda18b2317d5..50b94bad536e1 100644
--- a/polkadot/node/primitives/src/lib.rs
+++ b/polkadot/node/primitives/src/lib.rs
@@ -529,8 +529,6 @@ pub struct SubmitCollationParams {
pub relay_parent: Hash,
/// The collation itself (PoV and commitments)
pub collation: Collation,
- /// The parent block's head-data.
- pub parent_head: HeadData,
/// The hash of the validation code the collation was created against.
pub validation_code_hash: ValidationCodeHash,
/// An optional result sender that should be informed about a successfully seconded collation.
@@ -547,6 +545,12 @@ pub struct SubmitCollationParams {
///
/// WARNING: Should only be set if the `CandidateReceiptV3` node feature is set.
pub scheduling_parent: Option,
+ /// The session index of the relay parent. Goes into the candidate descriptor.
+ /// Must be provided by the caller because the relay parent's state may be pruned.
+ pub session_index: SessionIndex,
+ /// The persisted validation data for this collation. The `parent_head` field must be set
+ /// to the correct parent head-data for the parablock being submitted.
+ pub validation_data: PersistedValidationData,
}
/// This is the data we keep available for each candidate included in the relay chain.
diff --git a/polkadot/primitives/src/v9/mod.rs b/polkadot/primitives/src/v9/mod.rs
index 0d177214ed04d..79a94f58b348e 100644
--- a/polkadot/primitives/src/v9/mod.rs
+++ b/polkadot/primitives/src/v9/mod.rs
@@ -2387,6 +2387,7 @@ impl> CandidateDescriptorV2 {
relay_parent: H,
core_index: CoreIndex,
session_index: SessionIndex,
+ scheduling_session_index: SessionIndex,
persisted_validation_data_hash: Hash,
pov_hash: Hash,
erasure_root: Hash,
@@ -2400,7 +2401,10 @@ impl> CandidateDescriptorV2 {
version: 1,
core_index: core_index.0 as u16,
session_index,
- scheduling_session_offset: 0,
+ scheduling_session_offset: scheduling_session_index
+ .saturating_sub(session_index)
+ .try_into()
+ .expect("scheduling session offset should fit in u8"),
reserved1: [0; 24],
persisted_validation_data_hash,
pov_hash,
@@ -3309,7 +3313,8 @@ pub mod tests {
Id::from(1u32),
Hash::repeat_byte(1),
CoreIndex(0),
- 1,
+ 1, // session_index
+ 1, // scheduling_session_index
Hash::repeat_byte(2),
Hash::repeat_byte(3),
Hash::repeat_byte(4),
diff --git a/polkadot/primitives/test-helpers/src/lib.rs b/polkadot/primitives/test-helpers/src/lib.rs
index ff08c4ddaa313..f0de469d3e9f4 100644
--- a/polkadot/primitives/test-helpers/src/lib.rs
+++ b/polkadot/primitives/test-helpers/src/lib.rs
@@ -452,6 +452,7 @@ pub fn dummy_candidate_descriptor_v3 + Copy + Default>(
relay_parent,
CoreIndex(1),
1,
+ 1,
invalid,
invalid,
invalid,
@@ -650,6 +651,7 @@ pub fn make_valid_candidate_descriptor_v3 + Copy + Default>(
relay_parent: H,
core_index: CoreIndex,
session_index: SessionIndex,
+ scheduling_session_index: SessionIndex,
persisted_validation_data_hash: Hash,
pov_hash: Hash,
validation_code_hash: impl Into,
@@ -664,6 +666,7 @@ pub fn make_valid_candidate_descriptor_v3 + Copy + Default>(
relay_parent,
core_index,
session_index,
+ scheduling_session_index,
persisted_validation_data_hash,
pov_hash,
erasure_root,
diff --git a/polkadot/runtime/parachains/src/builder.rs b/polkadot/runtime/parachains/src/builder.rs
index 9527abcfb5048..9b298fe1460a7 100644
--- a/polkadot/runtime/parachains/src/builder.rs
+++ b/polkadot/runtime/parachains/src/builder.rs
@@ -691,6 +691,7 @@ impl BenchBuilder {
relay_parent,
core_idx,
self.target_session,
+ self.target_session, // scheduling_session_index
persisted_validation_data_hash,
pov_hash,
Default::default(),
diff --git a/polkadot/runtime/parachains/src/inclusion/tests.rs b/polkadot/runtime/parachains/src/inclusion/tests.rs
index 901fc2ceaa465..b4ae7bc30a615 100644
--- a/polkadot/runtime/parachains/src/inclusion/tests.rs
+++ b/polkadot/runtime/parachains/src/inclusion/tests.rs
@@ -322,6 +322,7 @@ impl TestCandidateBuilder {
self.relay_parent,
core_index,
self.descriptor_session_index.unwrap_or(0),
+ self.descriptor_session_index.unwrap_or(0), // scheduling_session_index
self.persisted_validation_data_hash,
self.pov_hash,
Default::default(),
diff --git a/prdoc/pr_11456.prdoc b/prdoc/pr_11456.prdoc
new file mode 100644
index 0000000000000..76dac5e972074
--- /dev/null
+++ b/prdoc/pr_11456.prdoc
@@ -0,0 +1,46 @@
+# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
+# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
+
+title: 'Collation generation: pass validation data from caller, fetch session from scheduling parent'
+
+doc:
+- audience: Node Dev
+ description: |
+ Makes `handle_submit_collation` robust for V3 candidate descriptors where
+ the relay parent can be old/ancient (pruned state). Instead of fetching
+ `PersistedValidationData` and `session_index` from the relay parent's
+ runtime state (which may be unavailable), the caller now provides
+ `PersistedValidationData` directly in `SubmitCollationParams`, and the
+ session index is now fetched correctly from the scheduling parent (which
+ always has available state).
+
+ The `parent_head` field is removed from `SubmitCollationParams` — callers should set
+ `validation_data.parent_head` directly instead.
+
+ All runtime API calls in the submit collation path now correctly target the scheduling
+ parent rather than the relay parent.
+
+ `CandidateDescriptorV2::new_v3` now takes an explicit `scheduling_session_index`
+ parameter instead of hardcoding the offset to 0.
+
+crates:
+- name: polkadot-primitives
+ bump: major
+- name: polkadot-primitives-test-helpers
+ bump: major
+- name: polkadot-node-primitives
+ bump: major
+- name: polkadot-node-collation-generation
+ bump: patch
+- name: polkadot-node-core-backing
+ bump: patch
+- name: polkadot-node-core-candidate-validation
+ bump: patch
+- name: polkadot-node-core-prospective-parachains
+ bump: patch
+- name: polkadot-collator-protocol
+ bump: patch
+- name: polkadot-runtime-parachains
+ bump: patch
+- name: cumulus-client-consensus-aura
+ bump: patch