Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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