From 3f37f6623202b7da720cffd0f9f41c6b8f7b83fb Mon Sep 17 00:00:00 2001 From: alindima Date: Tue, 17 Mar 2026 10:58:11 +0200 Subject: [PATCH 1/5] add relay parent to advertisement v3 --- .../src/collator_side/mod.rs | 25 +++++++---- .../src/validator_side/collation.rs | 30 ++++++------- .../src/validator_side/mod.rs | 43 ++++++++++++------- .../src/validator_side/tests/mod.rs | 14 +++--- .../tests/prospective_parachains.rs | 26 +++++------ .../src/validator_side_experimental/mod.rs | 16 +++++-- polkadot/node/network/protocol/src/lib.rs | 16 +++---- 7 files changed, 100 insertions(+), 70 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs index a0f0bc83f022c..679138b22b805 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs @@ -55,9 +55,9 @@ use polkadot_node_subsystem_util::{ TimeoutExt, }; use polkadot_primitives::{ - node_features, AuthorityDiscoveryId, BlockNumber, CandidateEvent, CandidateHash, - CandidateReceiptV2 as CandidateReceipt, CollatorPair, CoreIndex, Hash, HeadData, Id as ParaId, - SessionIndex, + node_features, AuthorityDiscoveryId, BlockNumber, CandidateDescriptorVersion, CandidateEvent, + CandidateHash, CandidateReceiptV2 as CandidateReceipt, CollatorPair, CoreIndex, Hash, + HeadData, Id as ParaId, SessionIndex, }; use crate::{modify_reputation, LOG_TARGET, LOG_TARGET_STATS}; @@ -937,16 +937,26 @@ async fn advertise_collation( }, } - // Get the candidate descriptor version from the receipt + let relay_parent = collation.receipt.descriptor.relay_parent(); let candidate_descriptor_version = collation.receipt.descriptor.version(per_scheduling_parent.v3_enabled); + // For V3 descriptors, send `Some(scheduling_parent)` so the validator knows this + // is a V3 candidate and can apply the scheduling parent validation rules. + // For V1/V2 descriptors, send `None` — the scheduling parent equals the relay + // parent. + let optional_scheduling_parent = + if candidate_descriptor_version == CandidateDescriptorVersion::V3 { + Some(scheduling_parent) + } else { + None + }; gum::debug!( target: LOG_TARGET, ?scheduling_parent, + ?relay_parent, ?candidate_hash, peer_id = %peer, - ?candidate_descriptor_version, "Advertising collation.", ); @@ -954,13 +964,12 @@ async fn advertise_collation( let message = match peer_version { CollationVersion::V3 => { - // Send V3 protocol message with the actual descriptor version CollationProtocols::V3(protocol_v3::CollationProtocol::CollatorProtocol( protocol_v3::CollatorProtocolMessage::AdvertiseCollation { - scheduling_parent, + relay_parent, candidate_hash: *candidate_hash, parent_head_data_hash: collation.parent_head_data.hash(), - candidate_descriptor_version, + scheduling_parent: optional_scheduling_parent, }, )) }, diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 82298ca95fab1..2c1b6e982c0cc 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -112,9 +112,10 @@ pub struct PendingCollation { pub prospective_candidate: Option, /// Hash of the candidate's commitments. pub commitments_hash: Option, - /// Advertised candidate descriptor version (for V3 protocol). - /// None for V1/V2 protocols. - pub advertised_descriptor_version: Option, + /// Whether the advertisement indicated a V3 descriptor (scheduling_parent was set + /// in the V3 protocol message). `false` for V1/V2 protocols or V3 advertisements + /// without an explicit scheduling parent. + pub is_v3_advertisement: bool, } // Manual Hash implementation for use in collation_requests_cancel_handles. @@ -128,9 +129,6 @@ pub struct PendingCollation { // Note: This does NOT prevent fetching the same candidate from different peers sequentially. // Multiple peers can advertise the same candidate, and we may fetch from each peer in turn // if earlier fetches fail. This is acceptable for redundancy but could be optimized in future. -// -// Fields excluded from hash: -// - advertised_descriptor_version: Protocol metadata, not part of request identity impl std::hash::Hash for PendingCollation { fn hash(&self, state: &mut H) { self.scheduling_parent.hash(state); @@ -138,21 +136,18 @@ impl std::hash::Hash for PendingCollation { self.peer_id.hash(state); self.prospective_candidate.hash(state); self.commitments_hash.hash(state); - // Explicitly exclude advertised_descriptor_version - it's protocol metadata + // Explicitly exclude is_v3_advertisement - it's protocol metadata } } impl PendingCollation { /// Constructor for PendingCollation. - /// - /// For V1/V2 protocol advertisements, pass `None` for `advertised_descriptor_version`. - /// For V3 protocol advertisements, pass `Some(version)` to track the advertised version. pub fn new( scheduling_parent: Hash, para_id: ParaId, peer_id: &PeerId, prospective_candidate: Option, - advertised_descriptor_version: Option, + is_v3_advertisement: bool, ) -> Self { Self { scheduling_parent, @@ -160,7 +155,7 @@ impl PendingCollation { peer_id: *peer_id, prospective_candidate, commitments_hash: None, - advertised_descriptor_version, + is_v3_advertisement, } } } @@ -203,13 +198,14 @@ pub fn fetched_collation_sanity_check( return Err(SecondingError::ParentHeadDataMismatch); } - // For V3 protocol advertisements, verify the fetched descriptor version matches the advertised - // one. - if let Some(advertised_version) = &advertised.advertised_descriptor_version { + // For V3 protocol advertisements, verify the fetched descriptor version matches. + // The advertised version is inferred from whether scheduling_parent was set in the + // protocol message. + if advertised.is_v3_advertisement { let fetched_version = fetched.descriptor.version(v3_enabled); - if advertised_version != &fetched_version { + if fetched_version != CandidateDescriptorVersion::V3 { return Err(SecondingError::DescriptorVersionMismatch( - *advertised_version, + CandidateDescriptorVersion::V3, fetched_version, )); } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 7ab8b92c3ab66..b8f444243bed9 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -574,6 +574,10 @@ struct HeldOffAdvertisement { /// The prospective candidate hash and its relay parent, if available. Will be none if collator /// protocol v1 is used. prospective_candidate: Option<(CandidateHash, Hash)>, + /// Whether the advertisement indicated a V3 descriptor (scheduling_parent was set + /// in the V3 protocol message). `false` for V1/V2 protocols or V3 advertisements + /// without an explicit scheduling parent. + is_v3_advertisement: bool, } /// Scheduling info tracked per active leaf, used for V3 scheduling parent validation. @@ -1228,28 +1232,33 @@ async fn process_incoming_peer_message( } }, CollationProtocols::V3(protocol_v3::CollatorProtocolMessage::AdvertiseCollation { - scheduling_parent, + relay_parent, candidate_hash, parent_head_data_hash, - candidate_descriptor_version, + scheduling_parent, }) => { + // Derive the effective scheduling parent: if explicitly set, the candidate + // uses a V3 descriptor; otherwise, the scheduling parent equals the relay + // parent. + let effective_scheduling_parent = scheduling_parent.unwrap_or(relay_parent); + if let Err(err) = handle_advertisement_v3( ctx.sender(), state, - scheduling_parent, + effective_scheduling_parent, origin, candidate_hash, parent_head_data_hash, - candidate_descriptor_version, + scheduling_parent.is_some(), ) .await { gum::debug!( target: LOG_TARGET, peer_id = ?origin, + ?relay_parent, ?scheduling_parent, ?candidate_hash, - ?candidate_descriptor_version, error = ?err, "Rejected v3 advertisement", ); @@ -1282,6 +1291,7 @@ fn hold_off_asset_hub_collation_if_needed( collator_id: &CollatorId, scheduling_parent: Hash, prospective_candidate: Option<(CandidateHash, Hash)>, + is_v3_advertisement: bool, ) -> bool { // If we don't know the peer we should reject the advertisement but to avoid verbosity and // copy-pasted logic we'll just return `false` and let the caller handle it. @@ -1326,6 +1336,7 @@ fn hold_off_asset_hub_collation_if_needed( peer_id, collator_id, prospective_candidate, + is_v3_advertisement, }); match hold_off_outcome { @@ -1694,6 +1705,7 @@ where &collator_id, scheduling_parent, prospective_candidate, + false, ) { return Ok(()); } @@ -1706,7 +1718,7 @@ where peer_id, collator_id, prospective_candidate, - None, // V1/V2 don't have advertised descriptor version + false, ) .await } @@ -1718,7 +1730,7 @@ async fn handle_advertisement_v3( peer_id: PeerId, candidate_hash: CandidateHash, parent_head_data_hash: Hash, - candidate_descriptor_version: CandidateDescriptorVersion, + is_v3_descriptor: bool, ) -> std::result::Result<(), AdvertisementError> where Sender: CollatorProtocolSenderTrait, @@ -1734,7 +1746,7 @@ where // V3 candidate descriptors require the scheduling_parent to be the block from the last // finished relay chain slot. We compare slot numbers rather than timestamps to keep // the logic simple and aligned with how BABE/Aura reason about slots. - if candidate_descriptor_version == CandidateDescriptorVersion::V3 { + if is_v3_descriptor { let slot_duration = SlotDuration::from_millis(RELAY_CHAIN_SLOT_DURATION_MILLIS); let current_slot = sp_consensus_slots::Slot::from_timestamp( sp_timestamp::Timestamp::current(), @@ -1779,6 +1791,7 @@ where &collator_id, scheduling_parent, Some((candidate_hash, parent_head_data_hash)), + is_v3_descriptor, ) { return Ok(()); } @@ -1791,7 +1804,7 @@ where peer_id, collator_id, Some((candidate_hash, parent_head_data_hash)), - Some(candidate_descriptor_version), + is_v3_descriptor, ) .await } @@ -1804,7 +1817,7 @@ async fn process_advertisement( peer_id: PeerId, collator_id: CollatorId, prospective_candidate: Option<(CandidateHash, Hash)>, - advertised_descriptor_version: Option, + is_v3_advertisement: bool, ) -> std::result::Result<(), AdvertisementError> where Sender: CollatorProtocolSenderTrait, @@ -1835,7 +1848,7 @@ where peer_id, collator_id, prospective_candidate, - advertised_descriptor_version, + is_v3_advertisement, ) .await; @@ -1863,7 +1876,7 @@ async fn enqueue_collation( peer_id: PeerId, collator_id: CollatorId, prospective_candidate: Option<(CandidateHash, Hash)>, - advertised_descriptor_version: Option, + is_v3_advertisement: bool, ) -> std::result::Result<(), FetchError> where Sender: CollatorProtocolSenderTrait, @@ -1902,7 +1915,7 @@ where para_id, &peer_id, prospective_candidate, - advertised_descriptor_version, + is_v3_advertisement, ); gum::debug!( @@ -2544,7 +2557,7 @@ async fn run_inner( }; for held_off_advertisement in held_off_advertisements { - let HeldOffAdvertisement{scheduling_parent, peer_id, collator_id, prospective_candidate} = held_off_advertisement; + let HeldOffAdvertisement{scheduling_parent, peer_id, collator_id, prospective_candidate, is_v3_advertisement} = held_off_advertisement; gum::debug!( target: LOG_TARGET, ?scheduling_parent, @@ -2561,7 +2574,7 @@ async fn run_inner( peer_id, collator_id, prospective_candidate, - None, // V1/V2 advertisement, no descriptor version + is_v3_advertisement, ) .await { gum::debug!( diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 11df88cbb43d3..20727fbf437fa 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -471,21 +471,25 @@ async fn advertise_collation( .await; } -/// Advertise a collation using the V3 protocol, which includes the candidate descriptor version. +/// Advertise a collation using the V3 protocol. +/// +/// `relay_parent` is the relay parent of the candidate. +/// `scheduling_parent` is `Some(hash)` if the candidate uses a V3 descriptor with a different +/// scheduling parent, or `None` if the scheduling parent equals the relay parent. async fn advertise_collation_v3( virtual_overseer: &mut VirtualOverseer, peer: PeerId, - scheduling_parent: Hash, + relay_parent: Hash, candidate_hash: CandidateHash, parent_head_data_hash: Hash, - candidate_descriptor_version: CandidateDescriptorVersion, + scheduling_parent: Option, ) { let wire_message = CollationProtocols::V3(protocol_v3::CollatorProtocolMessage::AdvertiseCollation { - scheduling_parent, + relay_parent, candidate_hash, parent_head_data_hash, - candidate_descriptor_version, + scheduling_parent, }); overseer_send( virtual_overseer, 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 3cf9444e2f5e2..6c34de02c052f 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 @@ -20,8 +20,8 @@ use super::*; use polkadot_node_subsystem::messages::ChainApiMessage; use polkadot_primitives::{ - BlockNumber, CandidateCommitments, CandidateDescriptorVersion, - CommittedCandidateReceiptV2 as CommittedCandidateReceipt, Header, MutateDescriptorV2, + BlockNumber, CandidateCommitments, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, + Header, MutateDescriptorV2, SigningContext, ValidatorId, }; use polkadot_primitives_test_helpers::{ @@ -2108,7 +2108,7 @@ fn v3_scheduling_parent_rejected_on_stalled_relay_chain() { head_b, candidate_hash, parent_head_data_hash, - CandidateDescriptorVersion::V3, + Some(head_b), ) .await; @@ -2201,10 +2201,10 @@ fn v3_scheduling_parent_in_progress_slot_accepts_leaf_parent() { advertise_collation_v3( &mut virtual_overseer, peer_a, - head_b_parent, + head_b_grandparent, candidate_hash, parent_head_data_hash, - CandidateDescriptorVersion::V3, + Some(head_b_parent), ) .await; @@ -2332,10 +2332,10 @@ fn v3_scheduling_parent_finished_slot_accepts_leaf() { advertise_collation_v3( &mut virtual_overseer, peer_a, - head_b, + head_b_parent, candidate_hash, parent_head_data_hash, - CandidateDescriptorVersion::V3, + Some(head_b), ) .await; @@ -2453,10 +2453,10 @@ fn v3_scheduling_parent_in_progress_slot_rejects_leaf() { advertise_collation_v3( &mut virtual_overseer, peer_a, - head_b, + head_b_parent, candidate_hash, parent_head_data_hash, - CandidateDescriptorVersion::V3, + Some(head_b), ) .await; @@ -2549,10 +2549,10 @@ fn v3_scheduling_parent_finished_slot_rejects_parent() { advertise_collation_v3( &mut virtual_overseer, peer_a, - head_b_parent, + head_b_grandparent, candidate_hash, parent_head_data_hash, - CandidateDescriptorVersion::V3, + Some(head_b_parent), ) .await; @@ -2624,10 +2624,10 @@ fn v3_scheduling_parent_outside_allowed_ancestry_rejected() { advertise_collation_v3( &mut virtual_overseer, peer_a, - unknown_scheduling_parent, + head_b, candidate_hash, parent_head_data_hash, - CandidateDescriptorVersion::V3, + Some(unknown_scheduling_parent), ) .await; diff --git a/polkadot/node/network/collator-protocol/src/validator_side_experimental/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side_experimental/mod.rs index 5a6d158571a9b..0d05e104f526e 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side_experimental/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side_experimental/mod.rs @@ -432,13 +432,23 @@ async fn process_incoming_peer_message( candidate_hash, parent_head_data_hash, .. - }) | + }) => { + state + .handle_advertisement( + sender, + origin, + scheduling_parent, + Some(ProspectiveCandidate { candidate_hash, parent_head_data_hash }), + ) + .await; + }, CollationProtocols::V3(V3::AdvertiseCollation { - scheduling_parent, + relay_parent, candidate_hash, parent_head_data_hash, - .. + scheduling_parent, }) => { + let scheduling_parent = scheduling_parent.unwrap_or(relay_parent); state .handle_advertisement( sender, diff --git a/polkadot/node/network/protocol/src/lib.rs b/polkadot/node/network/protocol/src/lib.rs index ffe10188479df..a4d1942e827ab 100644 --- a/polkadot/node/network/protocol/src/lib.rs +++ b/polkadot/node/network/protocol/src/lib.rs @@ -578,10 +578,7 @@ pub mod v2 { pub mod v3_collation { use codec::{Decode, Encode}; - use polkadot_primitives::{ - CandidateDescriptorVersion, CandidateHash, CollatorId, CollatorSignature, Hash, - Id as ParaId, - }; + use polkadot_primitives::{CandidateHash, CollatorId, CollatorSignature, Hash, Id as ParaId}; use polkadot_node_primitives::UncheckedSignedFullStatement; @@ -599,15 +596,16 @@ pub mod v3_collation { /// declared that they are a collator with given ID. #[codec(index = 1)] AdvertiseCollation { - /// Hash of the scheduling parent - used for validator assignment. - /// For V3 candidate descriptors, this must be an active leaf. - scheduling_parent: Hash, + /// The relay parent of the candidate. + relay_parent: Hash, /// Candidate hash. candidate_hash: CandidateHash, /// Parachain head data hash before candidate execution. parent_head_data_hash: Hash, - /// The version of the candidate descriptor. - candidate_descriptor_version: CandidateDescriptorVersion, + /// Optional scheduling parent, if the candidate is a v3 descriptor. + /// When `None`, the scheduling parent equals the relay parent + /// (V1/V2 descriptor). + scheduling_parent: Option, }, /// A collation sent to a validator was seconded. #[codec(index = 4)] From 9efc73fedc8a36409aaa488d44cff63e0d46585e Mon Sep 17 00:00:00 2001 From: alindima Date: Tue, 17 Mar 2026 11:12:38 +0200 Subject: [PATCH 2/5] fmt --- .../node/network/collator-protocol/src/collator_side/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs index 679138b22b805..2608e6825f06b 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs @@ -56,8 +56,8 @@ use polkadot_node_subsystem_util::{ }; use polkadot_primitives::{ node_features, AuthorityDiscoveryId, BlockNumber, CandidateDescriptorVersion, CandidateEvent, - CandidateHash, CandidateReceiptV2 as CandidateReceipt, CollatorPair, CoreIndex, Hash, - HeadData, Id as ParaId, SessionIndex, + CandidateHash, CandidateReceiptV2 as CandidateReceipt, CollatorPair, CoreIndex, Hash, HeadData, + Id as ParaId, SessionIndex, }; use crate::{modify_reputation, LOG_TARGET, LOG_TARGET_STATS}; From 72f64d7560555457a643f187bfefa03833f5b7f0 Mon Sep 17 00:00:00 2001 From: alindima Date: Tue, 17 Mar 2026 15:23:48 +0200 Subject: [PATCH 3/5] Revert V3 collation protocol changes Reverts commits 3f37f662320 and 9efc73fedc8 to restore the original V3 AdvertiseCollation protocol definition before re-implementing with the correct design. --- .../src/collator_side/mod.rs | 25 ++++------- .../src/validator_side/collation.rs | 30 +++++++------ .../src/validator_side/mod.rs | 43 +++++++------------ .../src/validator_side/tests/mod.rs | 14 +++--- .../tests/prospective_parachains.rs | 26 +++++------ .../src/validator_side_experimental/mod.rs | 16 ++----- polkadot/node/network/protocol/src/lib.rs | 16 ++++--- 7 files changed, 70 insertions(+), 100 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs index 2608e6825f06b..a0f0bc83f022c 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs @@ -55,9 +55,9 @@ use polkadot_node_subsystem_util::{ TimeoutExt, }; use polkadot_primitives::{ - node_features, AuthorityDiscoveryId, BlockNumber, CandidateDescriptorVersion, CandidateEvent, - CandidateHash, CandidateReceiptV2 as CandidateReceipt, CollatorPair, CoreIndex, Hash, HeadData, - Id as ParaId, SessionIndex, + node_features, AuthorityDiscoveryId, BlockNumber, CandidateEvent, CandidateHash, + CandidateReceiptV2 as CandidateReceipt, CollatorPair, CoreIndex, Hash, HeadData, Id as ParaId, + SessionIndex, }; use crate::{modify_reputation, LOG_TARGET, LOG_TARGET_STATS}; @@ -937,26 +937,16 @@ async fn advertise_collation( }, } - let relay_parent = collation.receipt.descriptor.relay_parent(); + // Get the candidate descriptor version from the receipt let candidate_descriptor_version = collation.receipt.descriptor.version(per_scheduling_parent.v3_enabled); - // For V3 descriptors, send `Some(scheduling_parent)` so the validator knows this - // is a V3 candidate and can apply the scheduling parent validation rules. - // For V1/V2 descriptors, send `None` — the scheduling parent equals the relay - // parent. - let optional_scheduling_parent = - if candidate_descriptor_version == CandidateDescriptorVersion::V3 { - Some(scheduling_parent) - } else { - None - }; gum::debug!( target: LOG_TARGET, ?scheduling_parent, - ?relay_parent, ?candidate_hash, peer_id = %peer, + ?candidate_descriptor_version, "Advertising collation.", ); @@ -964,12 +954,13 @@ async fn advertise_collation( let message = match peer_version { CollationVersion::V3 => { + // Send V3 protocol message with the actual descriptor version CollationProtocols::V3(protocol_v3::CollationProtocol::CollatorProtocol( protocol_v3::CollatorProtocolMessage::AdvertiseCollation { - relay_parent, + scheduling_parent, candidate_hash: *candidate_hash, parent_head_data_hash: collation.parent_head_data.hash(), - scheduling_parent: optional_scheduling_parent, + candidate_descriptor_version, }, )) }, diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 2c1b6e982c0cc..82298ca95fab1 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -112,10 +112,9 @@ pub struct PendingCollation { pub prospective_candidate: Option, /// Hash of the candidate's commitments. pub commitments_hash: Option, - /// Whether the advertisement indicated a V3 descriptor (scheduling_parent was set - /// in the V3 protocol message). `false` for V1/V2 protocols or V3 advertisements - /// without an explicit scheduling parent. - pub is_v3_advertisement: bool, + /// Advertised candidate descriptor version (for V3 protocol). + /// None for V1/V2 protocols. + pub advertised_descriptor_version: Option, } // Manual Hash implementation for use in collation_requests_cancel_handles. @@ -129,6 +128,9 @@ pub struct PendingCollation { // Note: This does NOT prevent fetching the same candidate from different peers sequentially. // Multiple peers can advertise the same candidate, and we may fetch from each peer in turn // if earlier fetches fail. This is acceptable for redundancy but could be optimized in future. +// +// Fields excluded from hash: +// - advertised_descriptor_version: Protocol metadata, not part of request identity impl std::hash::Hash for PendingCollation { fn hash(&self, state: &mut H) { self.scheduling_parent.hash(state); @@ -136,18 +138,21 @@ impl std::hash::Hash for PendingCollation { self.peer_id.hash(state); self.prospective_candidate.hash(state); self.commitments_hash.hash(state); - // Explicitly exclude is_v3_advertisement - it's protocol metadata + // Explicitly exclude advertised_descriptor_version - it's protocol metadata } } impl PendingCollation { /// Constructor for PendingCollation. + /// + /// For V1/V2 protocol advertisements, pass `None` for `advertised_descriptor_version`. + /// For V3 protocol advertisements, pass `Some(version)` to track the advertised version. pub fn new( scheduling_parent: Hash, para_id: ParaId, peer_id: &PeerId, prospective_candidate: Option, - is_v3_advertisement: bool, + advertised_descriptor_version: Option, ) -> Self { Self { scheduling_parent, @@ -155,7 +160,7 @@ impl PendingCollation { peer_id: *peer_id, prospective_candidate, commitments_hash: None, - is_v3_advertisement, + advertised_descriptor_version, } } } @@ -198,14 +203,13 @@ pub fn fetched_collation_sanity_check( return Err(SecondingError::ParentHeadDataMismatch); } - // For V3 protocol advertisements, verify the fetched descriptor version matches. - // The advertised version is inferred from whether scheduling_parent was set in the - // protocol message. - if advertised.is_v3_advertisement { + // For V3 protocol advertisements, verify the fetched descriptor version matches the advertised + // one. + if let Some(advertised_version) = &advertised.advertised_descriptor_version { let fetched_version = fetched.descriptor.version(v3_enabled); - if fetched_version != CandidateDescriptorVersion::V3 { + if advertised_version != &fetched_version { return Err(SecondingError::DescriptorVersionMismatch( - CandidateDescriptorVersion::V3, + *advertised_version, fetched_version, )); } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index b8f444243bed9..7ab8b92c3ab66 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -574,10 +574,6 @@ struct HeldOffAdvertisement { /// The prospective candidate hash and its relay parent, if available. Will be none if collator /// protocol v1 is used. prospective_candidate: Option<(CandidateHash, Hash)>, - /// Whether the advertisement indicated a V3 descriptor (scheduling_parent was set - /// in the V3 protocol message). `false` for V1/V2 protocols or V3 advertisements - /// without an explicit scheduling parent. - is_v3_advertisement: bool, } /// Scheduling info tracked per active leaf, used for V3 scheduling parent validation. @@ -1232,33 +1228,28 @@ async fn process_incoming_peer_message( } }, CollationProtocols::V3(protocol_v3::CollatorProtocolMessage::AdvertiseCollation { - relay_parent, + scheduling_parent, candidate_hash, parent_head_data_hash, - scheduling_parent, + candidate_descriptor_version, }) => { - // Derive the effective scheduling parent: if explicitly set, the candidate - // uses a V3 descriptor; otherwise, the scheduling parent equals the relay - // parent. - let effective_scheduling_parent = scheduling_parent.unwrap_or(relay_parent); - if let Err(err) = handle_advertisement_v3( ctx.sender(), state, - effective_scheduling_parent, + scheduling_parent, origin, candidate_hash, parent_head_data_hash, - scheduling_parent.is_some(), + candidate_descriptor_version, ) .await { gum::debug!( target: LOG_TARGET, peer_id = ?origin, - ?relay_parent, ?scheduling_parent, ?candidate_hash, + ?candidate_descriptor_version, error = ?err, "Rejected v3 advertisement", ); @@ -1291,7 +1282,6 @@ fn hold_off_asset_hub_collation_if_needed( collator_id: &CollatorId, scheduling_parent: Hash, prospective_candidate: Option<(CandidateHash, Hash)>, - is_v3_advertisement: bool, ) -> bool { // If we don't know the peer we should reject the advertisement but to avoid verbosity and // copy-pasted logic we'll just return `false` and let the caller handle it. @@ -1336,7 +1326,6 @@ fn hold_off_asset_hub_collation_if_needed( peer_id, collator_id, prospective_candidate, - is_v3_advertisement, }); match hold_off_outcome { @@ -1705,7 +1694,6 @@ where &collator_id, scheduling_parent, prospective_candidate, - false, ) { return Ok(()); } @@ -1718,7 +1706,7 @@ where peer_id, collator_id, prospective_candidate, - false, + None, // V1/V2 don't have advertised descriptor version ) .await } @@ -1730,7 +1718,7 @@ async fn handle_advertisement_v3( peer_id: PeerId, candidate_hash: CandidateHash, parent_head_data_hash: Hash, - is_v3_descriptor: bool, + candidate_descriptor_version: CandidateDescriptorVersion, ) -> std::result::Result<(), AdvertisementError> where Sender: CollatorProtocolSenderTrait, @@ -1746,7 +1734,7 @@ where // V3 candidate descriptors require the scheduling_parent to be the block from the last // finished relay chain slot. We compare slot numbers rather than timestamps to keep // the logic simple and aligned with how BABE/Aura reason about slots. - if is_v3_descriptor { + if candidate_descriptor_version == CandidateDescriptorVersion::V3 { let slot_duration = SlotDuration::from_millis(RELAY_CHAIN_SLOT_DURATION_MILLIS); let current_slot = sp_consensus_slots::Slot::from_timestamp( sp_timestamp::Timestamp::current(), @@ -1791,7 +1779,6 @@ where &collator_id, scheduling_parent, Some((candidate_hash, parent_head_data_hash)), - is_v3_descriptor, ) { return Ok(()); } @@ -1804,7 +1791,7 @@ where peer_id, collator_id, Some((candidate_hash, parent_head_data_hash)), - is_v3_descriptor, + Some(candidate_descriptor_version), ) .await } @@ -1817,7 +1804,7 @@ async fn process_advertisement( peer_id: PeerId, collator_id: CollatorId, prospective_candidate: Option<(CandidateHash, Hash)>, - is_v3_advertisement: bool, + advertised_descriptor_version: Option, ) -> std::result::Result<(), AdvertisementError> where Sender: CollatorProtocolSenderTrait, @@ -1848,7 +1835,7 @@ where peer_id, collator_id, prospective_candidate, - is_v3_advertisement, + advertised_descriptor_version, ) .await; @@ -1876,7 +1863,7 @@ async fn enqueue_collation( peer_id: PeerId, collator_id: CollatorId, prospective_candidate: Option<(CandidateHash, Hash)>, - is_v3_advertisement: bool, + advertised_descriptor_version: Option, ) -> std::result::Result<(), FetchError> where Sender: CollatorProtocolSenderTrait, @@ -1915,7 +1902,7 @@ where para_id, &peer_id, prospective_candidate, - is_v3_advertisement, + advertised_descriptor_version, ); gum::debug!( @@ -2557,7 +2544,7 @@ async fn run_inner( }; for held_off_advertisement in held_off_advertisements { - let HeldOffAdvertisement{scheduling_parent, peer_id, collator_id, prospective_candidate, is_v3_advertisement} = held_off_advertisement; + let HeldOffAdvertisement{scheduling_parent, peer_id, collator_id, prospective_candidate} = held_off_advertisement; gum::debug!( target: LOG_TARGET, ?scheduling_parent, @@ -2574,7 +2561,7 @@ async fn run_inner( peer_id, collator_id, prospective_candidate, - is_v3_advertisement, + None, // V1/V2 advertisement, no descriptor version ) .await { gum::debug!( diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 20727fbf437fa..11df88cbb43d3 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -471,25 +471,21 @@ async fn advertise_collation( .await; } -/// Advertise a collation using the V3 protocol. -/// -/// `relay_parent` is the relay parent of the candidate. -/// `scheduling_parent` is `Some(hash)` if the candidate uses a V3 descriptor with a different -/// scheduling parent, or `None` if the scheduling parent equals the relay parent. +/// Advertise a collation using the V3 protocol, which includes the candidate descriptor version. async fn advertise_collation_v3( virtual_overseer: &mut VirtualOverseer, peer: PeerId, - relay_parent: Hash, + scheduling_parent: Hash, candidate_hash: CandidateHash, parent_head_data_hash: Hash, - scheduling_parent: Option, + candidate_descriptor_version: CandidateDescriptorVersion, ) { let wire_message = CollationProtocols::V3(protocol_v3::CollatorProtocolMessage::AdvertiseCollation { - relay_parent, + scheduling_parent, candidate_hash, parent_head_data_hash, - scheduling_parent, + candidate_descriptor_version, }); overseer_send( virtual_overseer, 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 6c34de02c052f..3cf9444e2f5e2 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 @@ -20,8 +20,8 @@ use super::*; use polkadot_node_subsystem::messages::ChainApiMessage; use polkadot_primitives::{ - BlockNumber, CandidateCommitments, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, - Header, MutateDescriptorV2, + BlockNumber, CandidateCommitments, CandidateDescriptorVersion, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, Header, MutateDescriptorV2, SigningContext, ValidatorId, }; use polkadot_primitives_test_helpers::{ @@ -2108,7 +2108,7 @@ fn v3_scheduling_parent_rejected_on_stalled_relay_chain() { head_b, candidate_hash, parent_head_data_hash, - Some(head_b), + CandidateDescriptorVersion::V3, ) .await; @@ -2201,10 +2201,10 @@ fn v3_scheduling_parent_in_progress_slot_accepts_leaf_parent() { advertise_collation_v3( &mut virtual_overseer, peer_a, - head_b_grandparent, + head_b_parent, candidate_hash, parent_head_data_hash, - Some(head_b_parent), + CandidateDescriptorVersion::V3, ) .await; @@ -2332,10 +2332,10 @@ fn v3_scheduling_parent_finished_slot_accepts_leaf() { advertise_collation_v3( &mut virtual_overseer, peer_a, - head_b_parent, + head_b, candidate_hash, parent_head_data_hash, - Some(head_b), + CandidateDescriptorVersion::V3, ) .await; @@ -2453,10 +2453,10 @@ fn v3_scheduling_parent_in_progress_slot_rejects_leaf() { advertise_collation_v3( &mut virtual_overseer, peer_a, - head_b_parent, + head_b, candidate_hash, parent_head_data_hash, - Some(head_b), + CandidateDescriptorVersion::V3, ) .await; @@ -2549,10 +2549,10 @@ fn v3_scheduling_parent_finished_slot_rejects_parent() { advertise_collation_v3( &mut virtual_overseer, peer_a, - head_b_grandparent, + head_b_parent, candidate_hash, parent_head_data_hash, - Some(head_b_parent), + CandidateDescriptorVersion::V3, ) .await; @@ -2624,10 +2624,10 @@ fn v3_scheduling_parent_outside_allowed_ancestry_rejected() { advertise_collation_v3( &mut virtual_overseer, peer_a, - head_b, + unknown_scheduling_parent, candidate_hash, parent_head_data_hash, - Some(unknown_scheduling_parent), + CandidateDescriptorVersion::V3, ) .await; diff --git a/polkadot/node/network/collator-protocol/src/validator_side_experimental/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side_experimental/mod.rs index 0d05e104f526e..5a6d158571a9b 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side_experimental/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side_experimental/mod.rs @@ -432,23 +432,13 @@ async fn process_incoming_peer_message( candidate_hash, parent_head_data_hash, .. - }) => { - state - .handle_advertisement( - sender, - origin, - scheduling_parent, - Some(ProspectiveCandidate { candidate_hash, parent_head_data_hash }), - ) - .await; - }, + }) | CollationProtocols::V3(V3::AdvertiseCollation { - relay_parent, + scheduling_parent, candidate_hash, parent_head_data_hash, - scheduling_parent, + .. }) => { - let scheduling_parent = scheduling_parent.unwrap_or(relay_parent); state .handle_advertisement( sender, diff --git a/polkadot/node/network/protocol/src/lib.rs b/polkadot/node/network/protocol/src/lib.rs index a4d1942e827ab..ffe10188479df 100644 --- a/polkadot/node/network/protocol/src/lib.rs +++ b/polkadot/node/network/protocol/src/lib.rs @@ -578,7 +578,10 @@ pub mod v2 { pub mod v3_collation { use codec::{Decode, Encode}; - use polkadot_primitives::{CandidateHash, CollatorId, CollatorSignature, Hash, Id as ParaId}; + use polkadot_primitives::{ + CandidateDescriptorVersion, CandidateHash, CollatorId, CollatorSignature, Hash, + Id as ParaId, + }; use polkadot_node_primitives::UncheckedSignedFullStatement; @@ -596,16 +599,15 @@ pub mod v3_collation { /// declared that they are a collator with given ID. #[codec(index = 1)] AdvertiseCollation { - /// The relay parent of the candidate. - relay_parent: Hash, + /// Hash of the scheduling parent - used for validator assignment. + /// For V3 candidate descriptors, this must be an active leaf. + scheduling_parent: Hash, /// Candidate hash. candidate_hash: CandidateHash, /// Parachain head data hash before candidate execution. parent_head_data_hash: Hash, - /// Optional scheduling parent, if the candidate is a v3 descriptor. - /// When `None`, the scheduling parent equals the relay parent - /// (V1/V2 descriptor). - scheduling_parent: Option, + /// The version of the candidate descriptor. + candidate_descriptor_version: CandidateDescriptorVersion, }, /// A collation sent to a validator was seconded. #[codec(index = 4)] From b359f193969d75af736e4cced7e74f4d8ed48416 Mon Sep 17 00:00:00 2001 From: alindima Date: Wed, 18 Mar 2026 12:43:40 +0200 Subject: [PATCH 4/5] add the new impl --- .../src/collator_side/mod.rs | 1 + .../src/validator_side/collation.rs | 8 ++++ .../src/validator_side/error.rs | 4 ++ .../src/validator_side/mod.rs | 39 +++++++++++++++++-- .../src/validator_side/tests/mod.rs | 2 + .../tests/prospective_parachains.rs | 6 +++ polkadot/node/network/protocol/src/lib.rs | 4 +- 7 files changed, 60 insertions(+), 4 deletions(-) diff --git a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs index a0f0bc83f022c..21bf2137e40b9 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs @@ -961,6 +961,7 @@ async fn advertise_collation( candidate_hash: *candidate_hash, parent_head_data_hash: collation.parent_head_data.hash(), candidate_descriptor_version, + relay_parent: collation.receipt.descriptor.relay_parent(), }, )) }, diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 82298ca95fab1..da2983cc1a909 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -103,6 +103,8 @@ impl FetchedCollation { pub struct PendingCollation { /// Candidate's scheduling parent pub scheduling_parent: Hash, + /// Candidate's relay parent + pub relay_parent: Hash, /// Parachain id. pub para_id: ParaId, /// Peer that advertised this collation. @@ -149,6 +151,7 @@ impl PendingCollation { /// For V3 protocol advertisements, pass `Some(version)` to track the advertised version. pub fn new( scheduling_parent: Hash, + relay_parent: Hash, para_id: ParaId, peer_id: &PeerId, prospective_candidate: Option, @@ -156,6 +159,7 @@ impl PendingCollation { ) -> Self { Self { scheduling_parent, + relay_parent, para_id, peer_id: *peer_id, prospective_candidate, @@ -199,6 +203,10 @@ pub fn fetched_collation_sanity_check( return Err(SecondingError::SchedulingParentMismatch); } + if advertised.relay_parent != fetched.descriptor.relay_parent() { + return Err(SecondingError::RelayParentMismatch); + } + if maybe_parent_head_and_hash.map_or(false, |(head, hash)| head.hash() != hash) { return Err(SecondingError::ParentHeadDataMismatch); } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/error.rs b/polkadot/node/network/collator-protocol/src/validator_side/error.rs index 90fe762edd579..79cad44825d7a 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/error.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/error.rs @@ -76,6 +76,9 @@ pub enum SecondingError { #[error("Scheduling parent hash doesn't match the advertisement")] SchedulingParentMismatch, + #[error("Relay parent hash doesn't match the advertisement")] + RelayParentMismatch, + #[error("Received duplicate collation from the peer")] Duplicate, @@ -109,6 +112,7 @@ impl SecondingError { PersistedValidationDataMismatch | CandidateHashMismatch | SchedulingParentMismatch | + RelayParentMismatch | ParentHeadDataMismatch | InvalidCoreIndex(_, _) | InvalidSessionIndex(_, _) | diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 7ab8b92c3ab66..bf1b136220039 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -565,6 +565,8 @@ struct PerSchedulingParent { /// Information about a held off advertisement struct HeldOffAdvertisement { + /// The relay parent of the candidate. + relay_parent: Hash, /// The scheduling parent it's based on. scheduling_parent: Hash, /// The peer id of the collator that has sent the advertisement. @@ -574,6 +576,9 @@ struct HeldOffAdvertisement { /// The prospective candidate hash and its relay parent, if available. Will be none if collator /// protocol v1 is used. prospective_candidate: Option<(CandidateHash, Hash)>, + /// Advertised candidate descriptor version (for V3 protocol). + /// None for V1/V2 protocols. + advertised_descriptor_version: Option, } /// Scheduling info tracked per active leaf, used for V3 scheduling parent validation. @@ -1232,6 +1237,7 @@ async fn process_incoming_peer_message( candidate_hash, parent_head_data_hash, candidate_descriptor_version, + relay_parent, }) => { if let Err(err) = handle_advertisement_v3( ctx.sender(), @@ -1241,12 +1247,14 @@ async fn process_incoming_peer_message( candidate_hash, parent_head_data_hash, candidate_descriptor_version, + relay_parent, ) .await { gum::debug!( target: LOG_TARGET, peer_id = ?origin, + ?relay_parent, ?scheduling_parent, ?candidate_hash, ?candidate_descriptor_version, @@ -1282,6 +1290,8 @@ fn hold_off_asset_hub_collation_if_needed( collator_id: &CollatorId, scheduling_parent: Hash, prospective_candidate: Option<(CandidateHash, Hash)>, + relay_parent: Hash, + advertised_descriptor_version: Option, ) -> bool { // If we don't know the peer we should reject the advertisement but to avoid verbosity and // copy-pasted logic we'll just return `false` and let the caller handle it. @@ -1322,10 +1332,12 @@ fn hold_off_asset_hub_collation_if_needed( let hold_off_outcome = rp_state.ah_held_off_advertisements.hold_off_if_necessary(HeldOffAdvertisement { + relay_parent, scheduling_parent, peer_id, collator_id, prospective_candidate, + advertised_descriptor_version, }); match hold_off_outcome { @@ -1388,6 +1400,8 @@ enum AdvertisementError { Invalid(InsertAdvertisementError), /// Seconding not allowed by backing subsystem BlockedByBacking, + /// For non-V3 descriptors, the relay parent must equal the scheduling parent. + RelayParentMismatch, } impl AdvertisementError { @@ -1398,7 +1412,7 @@ impl AdvertisementError { SchedulingParentUnknown | UndeclaredCollator | Invalid(_) => { Some(COST_UNEXPECTED_MESSAGE) }, - SchedulingParentNotValid => Some(COST_INVALID_SCHEDULING_PARENT), + SchedulingParentNotValid | RelayParentMismatch => Some(COST_INVALID_SCHEDULING_PARENT), UnknownPeer | SecondedLimitReached | BlockedByBacking => None, } } @@ -1694,6 +1708,8 @@ where &collator_id, scheduling_parent, prospective_candidate, + scheduling_parent, // For V1/V2, the relay parent is the same as the scheduling parent + None, // V1/V2 don't have advertised descriptor version ) { return Ok(()); } @@ -1702,6 +1718,7 @@ where sender, state, scheduling_parent, + scheduling_parent, para_id, peer_id, collator_id, @@ -1719,12 +1736,20 @@ async fn handle_advertisement_v3( candidate_hash: CandidateHash, parent_head_data_hash: Hash, candidate_descriptor_version: CandidateDescriptorVersion, + relay_parent: Hash, ) -> std::result::Result<(), AdvertisementError> where Sender: CollatorProtocolSenderTrait, { let peer_data = state.peer_data.get_mut(&peer_id).ok_or(AdvertisementError::UnknownPeer)?; + // For non-V3 descriptors, the relay parent must equal the scheduling parent. + if candidate_descriptor_version != CandidateDescriptorVersion::V3 && + relay_parent != scheduling_parent + { + return Err(AdvertisementError::RelayParentMismatch); + } + // Fail fast if the scheduling parent is completely unknown. let per_scheduling_parent = state .per_scheduling_parent @@ -1779,6 +1804,8 @@ where &collator_id, scheduling_parent, Some((candidate_hash, parent_head_data_hash)), + relay_parent, + Some(candidate_descriptor_version), ) { return Ok(()); } @@ -1786,6 +1813,7 @@ where process_advertisement( sender, state, + relay_parent, scheduling_parent, para_id, peer_id, @@ -1799,6 +1827,7 @@ where async fn process_advertisement( sender: &mut Sender, state: &mut State, + relay_parent: Hash, scheduling_parent: Hash, para_id: ParaId, peer_id: PeerId, @@ -1830,6 +1859,7 @@ where let result = enqueue_collation( sender, state, + relay_parent, scheduling_parent, para_id, peer_id, @@ -1858,6 +1888,7 @@ where async fn enqueue_collation( sender: &mut Sender, state: &mut State, + relay_parent: Hash, scheduling_parent: Hash, para_id: ParaId, peer_id: PeerId, @@ -1899,6 +1930,7 @@ where let collations = &mut per_scheduling_parent.collations; let pending_collation = PendingCollation::new( scheduling_parent, + relay_parent, para_id, &peer_id, prospective_candidate, @@ -2544,7 +2576,7 @@ async fn run_inner( }; for held_off_advertisement in held_off_advertisements { - let HeldOffAdvertisement{scheduling_parent, peer_id, collator_id, prospective_candidate} = held_off_advertisement; + let HeldOffAdvertisement{relay_parent, scheduling_parent, peer_id, collator_id, prospective_candidate, advertised_descriptor_version} = held_off_advertisement; gum::debug!( target: LOG_TARGET, ?scheduling_parent, @@ -2556,12 +2588,13 @@ async fn run_inner( if let Err(err) = process_advertisement( ctx.sender(), &mut state, + relay_parent, scheduling_parent, ASSET_HUB_PARA_ID, peer_id, collator_id, prospective_candidate, - None, // V1/V2 advertisement, no descriptor version + advertised_descriptor_version ) .await { gum::debug!( diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 11df88cbb43d3..d41e9c53a7f8c 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -479,6 +479,7 @@ async fn advertise_collation_v3( candidate_hash: CandidateHash, parent_head_data_hash: Hash, candidate_descriptor_version: CandidateDescriptorVersion, + relay_parent: Hash, ) { let wire_message = CollationProtocols::V3(protocol_v3::CollatorProtocolMessage::AdvertiseCollation { @@ -486,6 +487,7 @@ async fn advertise_collation_v3( candidate_hash, parent_head_data_hash, candidate_descriptor_version, + relay_parent, }); overseer_send( virtual_overseer, 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 3cf9444e2f5e2..2a3c3f54c65dc 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 @@ -2109,6 +2109,7 @@ fn v3_scheduling_parent_rejected_on_stalled_relay_chain() { candidate_hash, parent_head_data_hash, CandidateDescriptorVersion::V3, + head_b, ) .await; @@ -2205,6 +2206,7 @@ fn v3_scheduling_parent_in_progress_slot_accepts_leaf_parent() { candidate_hash, parent_head_data_hash, CandidateDescriptorVersion::V3, + head_b_grandparent, ) .await; @@ -2336,6 +2338,7 @@ fn v3_scheduling_parent_finished_slot_accepts_leaf() { candidate_hash, parent_head_data_hash, CandidateDescriptorVersion::V3, + head_b_parent, ) .await; @@ -2457,6 +2460,7 @@ fn v3_scheduling_parent_in_progress_slot_rejects_leaf() { candidate_hash, parent_head_data_hash, CandidateDescriptorVersion::V3, + head_b_parent, ) .await; @@ -2553,6 +2557,7 @@ fn v3_scheduling_parent_finished_slot_rejects_parent() { candidate_hash, parent_head_data_hash, CandidateDescriptorVersion::V3, + head_b_grandparent, ) .await; @@ -2628,6 +2633,7 @@ fn v3_scheduling_parent_outside_allowed_ancestry_rejected() { candidate_hash, parent_head_data_hash, CandidateDescriptorVersion::V3, + head_b, ) .await; diff --git a/polkadot/node/network/protocol/src/lib.rs b/polkadot/node/network/protocol/src/lib.rs index ffe10188479df..f4973757bb07f 100644 --- a/polkadot/node/network/protocol/src/lib.rs +++ b/polkadot/node/network/protocol/src/lib.rs @@ -600,7 +600,7 @@ pub mod v3_collation { #[codec(index = 1)] AdvertiseCollation { /// Hash of the scheduling parent - used for validator assignment. - /// For V3 candidate descriptors, this must be an active leaf. + /// For non-v3 descriptors, this must be equal to the relay parent. scheduling_parent: Hash, /// Candidate hash. candidate_hash: CandidateHash, @@ -608,6 +608,8 @@ pub mod v3_collation { parent_head_data_hash: Hash, /// The version of the candidate descriptor. candidate_descriptor_version: CandidateDescriptorVersion, + /// The relay parent of the candidate. + relay_parent: Hash, }, /// A collation sent to a validator was seconded. #[codec(index = 4)] From 9885d4d79cf8dd5d059099f9e8dfcfe76d94b88b Mon Sep 17 00:00:00 2001 From: alindima Date: Wed, 18 Mar 2026 13:22:25 +0200 Subject: [PATCH 5/5] prdoc --- prdoc/pr_11393.prdoc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 prdoc/pr_11393.prdoc diff --git a/prdoc/pr_11393.prdoc b/prdoc/pr_11393.prdoc new file mode 100644 index 0000000000000..a84007508822b --- /dev/null +++ b/prdoc/pr_11393.prdoc @@ -0,0 +1,14 @@ +title: 'Add relay parent to V3 collation protocol advertisement' +doc: +- audience: Node Dev + description: | + Adds a `relay_parent` field to the V3 collation protocol `AdvertiseCollation` message. + + Also stores the relay parent and advertised descriptor version in pending collations + and held-off AssetHub advertisements, so that all sanity checks (including descriptor + version mismatch and relay parent mismatch) are correctly applied after fetching. +crates: +- name: polkadot-node-network-protocol + bump: major +- name: polkadot-collator-protocol + bump: major