Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
da6c8fb
[Collator Protocol] Add CandidateDescriptorV3 support to experimental…
AlexandruCihodaru Mar 6, 2026
02055b4
Update from github-actions[bot] running command 'prdoc --audience nod…
github-actions[bot] Mar 9, 2026
bd07bd7
[Collator Protocol] Move CandidateDescriptorV3 feature check to sessi…
AlexandruCihodaru Mar 9, 2026
0331aa2
Merge remote-tracking branch 'origin' into acihodaru/experimental_val…
AlexandruCihodaru Mar 9, 2026
ea2f122
Merge remote-tracking branch 'origin' into acihodaru/experimental_val…
AlexandruCihodaru Mar 11, 2026
2eda45f
collator-protocol: Validate V3 scheduling parent against last finishe…
AlexandruCihodaru Mar 12, 2026
4374b00
Merge remote-tracking branch 'origin' into acihodaru/experimental_val…
AlexandruCihodaru Mar 13, 2026
6ee0b01
Update from github-actions[bot] running command 'fmt'
github-actions[bot] Mar 13, 2026
44f9d51
collator-protocol: Deduplicate V3 scheduling parent validation logic
AlexandruCihodaru Mar 14, 2026
4d8b0bb
fix some typos
AlexandruCihodaru Mar 17, 2026
1ff933c
Merge remote-tracking branch 'origin' into acihodaru/experimental_val…
AlexandruCihodaru Mar 17, 2026
da1f1a6
test that the parent of the sp from the current slot is accepted
AlexandruCihodaru Mar 17, 2026
b1b02ae
Merge remote-tracking branch 'origin' into acihodaru/experimental_val…
AlexandruCihodaru Mar 20, 2026
c6e225e
Update from github-actions[bot] running command 'fmt'
github-actions[bot] Mar 20, 2026
5dc858e
address comments
AlexandruCihodaru Mar 20, 2026
3ed7d90
Merge remote-tracking branch 'origin' into acihodaru/experimental_val…
AlexandruCihodaru Mar 23, 2026
354ae36
address feedback
AlexandruCihodaru Mar 23, 2026
3c184d8
remove manual hash implementation for Advertisement
AlexandruCihodaru Mar 23, 2026
e25b50f
Merge branch 'master' into acihodaru/experimental_validator_candidate_v3
AlexandruCihodaru Mar 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 55 additions & 3 deletions polkadot/node/network/collator-protocol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,32 @@
#![recursion_limit = "256"]

use std::{
collections::HashSet,
collections::{HashMap, HashSet},
sync::Arc,
time::{Duration, Instant},
};

use futures::{
channel::oneshot,
stream::{FusedStream, StreamExt},
FutureExt, TryFutureExt,
};

use polkadot_node_subsystem::CollatorProtocolSenderTrait;
use polkadot_node_subsystem_util::{database::Database, reputation::ReputationAggregator};
use sp_consensus_babe::digests::CompatibleDigestItem;
use sp_core::H256;
use sp_keystore::KeystorePtr;

use polkadot_node_network_protocol::{
request_response::{v2 as protocol_v2, IncomingRequestReceiver},
PeerId, UnifiedReputationChange as Rep,
};
use polkadot_node_subsystem::{errors::SubsystemError, overseer, DummySubsystem, SpawnedSubsystem};
use polkadot_primitives::CollatorPair;
use polkadot_node_subsystem::{
errors::SubsystemError, messages::ChainApiMessage, overseer, DummySubsystem, SpawnedSubsystem,
};
use polkadot_primitives::{CollatorPair, Hash, RELAY_CHAIN_SLOT_DURATION_MILLIS};
use sp_consensus_slots::SlotDuration;
pub use validator_side_experimental::ReputationConfig;

mod collator_side;
Expand Down Expand Up @@ -206,3 +213,48 @@ fn tick_stream(period: Duration) -> impl FusedStream<Item = ()> {
})
.fuse()
}

/// Scheduling info tracked per active leaf, used for V3 scheduling parent validation.
/// Stores the leaf's BABE slot and parent hash so the validator can determine whether
/// the scheduling parent corresponds to the last finished relay chain slot.
struct LeafSchedulingInfo {
/// The parent hash of the leaf block.
parent_hash: Hash,
/// The BABE slot of the leaf block.
slot: sp_consensus_slots::Slot,
}

pub(crate) async fn extract_leaf_scheduling_info<Sender: CollatorProtocolSenderTrait>(
sender: &mut Sender,
leaf: H256,
) -> Option<LeafSchedulingInfo> {
// Fetch leaf header to extract BABE slot for V3 scheduling parent validation.
// Without this info, V3 advertisements referencing this leaf will be rejected.
let (tx, rx) = oneshot::channel();
sender.send_message(ChainApiMessage::BlockHeader(leaf, tx)).await;
let header = rx.await.ok().and_then(|r| r.ok().flatten());
header.and_then(|header| {
let slot = header.digest.logs().iter().find_map(|log| log.as_babe_pre_digest())?.slot();
Some(LeafSchedulingInfo { parent_hash: header.parent_hash, slot })
})
}

pub(crate) fn is_scheduling_parent_valid(
scheduling_parent: &Hash,
leaf_scheduling_info: &HashMap<Hash, LeafSchedulingInfo>,
) -> bool {
let slot_duration = SlotDuration::from_millis(RELAY_CHAIN_SLOT_DURATION_MILLIS);
let current_slot =
sp_consensus_slots::Slot::from_timestamp(sp_timestamp::Timestamp::current(), slot_duration);
if let Some(info) = leaf_scheduling_info.get(scheduling_parent) {
// scheduling_parent is a leaf. This is allowed only when the leaf's slot is
// the previous slot.
*current_slot == *info.slot + 1
} else {
// scheduling_parent is not a leaf. This is allowed only if the sp is the parent of
// any leaf whose slot is still in progress.
leaf_scheduling_info
.iter()
.any(|(_, info)| *current_slot == *info.slot && *scheduling_parent == info.parent_hash)
}
}
51 changes: 6 additions & 45 deletions polkadot/node/network/collator-protocol/src/validator_side/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ use tokio_util::sync::CancellationToken;

use sp_keystore::KeystorePtr;

use crate::{extract_leaf_scheduling_info, is_scheduling_parent_valid, LeafSchedulingInfo};
use polkadot_node_network_protocol::{
self as net_protocol,
peer_set::{CollationVersion, PeerSet, MAX_AUTHORITY_INCOMING_STREAMS},
Expand All @@ -156,9 +157,9 @@ use polkadot_node_network_protocol::{
use polkadot_node_primitives::{SignedFullStatement, Statement};
use polkadot_node_subsystem::{
messages::{
CanSecondRequest, CandidateBackingMessage, ChainApiMessage, CollatorProtocolMessage,
IfDisconnected, NetworkBridgeEvent, NetworkBridgeTxMessage, ParentHeadData,
ProspectiveParachainsMessage, ProspectiveValidationDataRequest,
CanSecondRequest, CandidateBackingMessage, CollatorProtocolMessage, IfDisconnected,
NetworkBridgeEvent, NetworkBridgeTxMessage, ParentHeadData, ProspectiveParachainsMessage,
ProspectiveValidationDataRequest,
},
overseer, CollatorProtocolSenderTrait, FromOrchestra, OverseerSignal, SubsystemError,
};
Expand All @@ -170,10 +171,7 @@ use polkadot_node_subsystem_util::{
use polkadot_primitives::{
CandidateDescriptorV2, CandidateDescriptorVersion, CandidateHash, CollatorId, CoreIndex, Hash,
HeadData, Id as ParaId, OccupiedCoreAssumption, PersistedValidationData, SessionIndex,
RELAY_CHAIN_SLOT_DURATION_MILLIS,
};
use sp_consensus_babe::digests::CompatibleDigestItem;
use sp_consensus_slots::SlotDuration;

use super::{modify_reputation, tick_stream, LOG_TARGET};

Expand Down Expand Up @@ -580,16 +578,6 @@ struct HeldOffAdvertisement {
advertised_descriptor_version: Option<CandidateDescriptorVersion>,
}

/// Scheduling info tracked per active leaf, used for V3 scheduling parent validation.
/// Stores the leaf's BABE slot and parent hash so the validator can determine whether
/// the scheduling parent corresponds to the last finished relay chain slot.
struct LeafSchedulingInfo {
/// The parent hash of the leaf block.
parent_hash: Hash,
/// The BABE slot of the leaf block.
slot: sp_consensus_slots::Slot,
}

/// All state relevant for the validator side of the protocol lives here.
#[derive(Default)]
struct State {
Expand Down Expand Up @@ -1752,26 +1740,7 @@ where
// 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 {
let slot_duration = SlotDuration::from_millis(RELAY_CHAIN_SLOT_DURATION_MILLIS);
let current_slot = sp_consensus_slots::Slot::from_timestamp(
sp_timestamp::Timestamp::current(),
slot_duration,
);

let scheduling_parent_valid =
if let Some(info) = state.leaf_scheduling_info.get(&scheduling_parent) {
// scheduling_parent is a leaf — valid only if the leaf's slot is exactly
// one behind the current slot (i.e., it just finished).
*current_slot == *info.slot + 1
} else {
// scheduling_parent is not a leaf — valid if it's the parent of any leaf
// whose slot is the current slot (still in progress).
state.leaf_scheduling_info.iter().any(|(_leaf_hash, info)| {
*current_slot == *info.slot && scheduling_parent == info.parent_hash
})
};

if !scheduling_parent_valid {
if !is_scheduling_parent_valid(&scheduling_parent, &state.leaf_scheduling_info) {
return Err(AdvertisementError::SchedulingParentNotValid);
}
}
Expand Down Expand Up @@ -2001,15 +1970,7 @@ where
state.per_scheduling_parent.insert(*leaf, per_scheduling_parent);
state.leaf_claim_queues.insert(*leaf, leaf_claim_queue);

// Fetch leaf header to extract BABE slot for V3 scheduling parent validation.
// Without this info, V3 advertisements referencing this leaf will be rejected.
let (tx, rx) = oneshot::channel();
sender.send_message(ChainApiMessage::BlockHeader(*leaf, tx)).await;
let header = rx.await.ok().and_then(|r| r.ok()).flatten();
match header.and_then(|h| {
let slot = h.digest.logs().iter().find_map(|log| log.as_babe_pre_digest())?.slot();
Some(LeafSchedulingInfo { parent_hash: h.parent_hash, slot })
}) {
match extract_leaf_scheduling_info(sender, *leaf).await {
Some(info) => {
state.leaf_scheduling_info.insert(*leaf, info);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use polkadot_node_subsystem::messages::ChainApiMessage;
use polkadot_primitives::{
BlockNumber, CandidateCommitments, CandidateDescriptorVersion,
CommittedCandidateReceiptV2 as CommittedCandidateReceipt, Header, MutateDescriptorV2,
SigningContext, ValidatorId,
SigningContext, ValidatorId, RELAY_CHAIN_SLOT_DURATION_MILLIS,
};
use polkadot_primitives_test_helpers::{
dummy_committed_candidate_receipt_v2, dummy_committed_candidate_receipt_v3,
Expand Down
Loading
Loading