mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 11:07:56 +00:00
Notify collators about seconded collation (#2430)
* Notify collators about seconded collation This pr adds functionality to inform a collator that its collation was seconded by a parachain validator. Before this signed statement was only gossiped over the validation substream. Now, we explicitly send the seconded statement to the collator after it was validated successfully. Besides that it changes the `CollatorFn` to return an optional result sender that is informed when the build collation was seconded by a parachain validator. * Add test * Make sure we only send `Seconded` statements * Make sure we only receive valid statements * Review feedback
This commit is contained in:
@@ -18,10 +18,10 @@ use std::collections::{HashMap, HashSet};
|
||||
|
||||
use super::{LOG_TARGET, Result};
|
||||
|
||||
use futures::{select, FutureExt};
|
||||
use futures::{select, FutureExt, channel::oneshot};
|
||||
|
||||
use polkadot_primitives::v1::{
|
||||
CollatorId, CoreIndex, CoreState, Hash, Id as ParaId, CandidateReceipt, PoV, ValidatorId,
|
||||
CollatorId, CoreIndex, CoreState, Hash, Id as ParaId, CandidateReceipt, PoV, ValidatorId, CandidateHash,
|
||||
};
|
||||
use polkadot_subsystem::{
|
||||
jaeger, PerLeafSpan,
|
||||
@@ -38,6 +38,7 @@ use polkadot_node_subsystem_util::{
|
||||
request_availability_cores_ctx,
|
||||
metrics::{self, prometheus},
|
||||
};
|
||||
use polkadot_node_primitives::{SignedFullStatement, Statement};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Metrics(Option<MetricsInner>);
|
||||
@@ -195,6 +196,9 @@ struct State {
|
||||
/// We will keep up to one local collation per relay-parent.
|
||||
collations: HashMap<Hash, (CandidateReceipt, PoV)>,
|
||||
|
||||
/// The result senders per collation.
|
||||
collation_result_senders: HashMap<CandidateHash, oneshot::Sender<SignedFullStatement>>,
|
||||
|
||||
/// Our validator groups per active leaf.
|
||||
our_validators_groups: HashMap<Hash, ValidatorGroup>,
|
||||
|
||||
@@ -230,6 +234,7 @@ async fn distribute_collation(
|
||||
id: ParaId,
|
||||
receipt: CandidateReceipt,
|
||||
pov: PoV,
|
||||
result_sender: Option<oneshot::Sender<SignedFullStatement>>,
|
||||
) -> Result<()> {
|
||||
let relay_parent = receipt.descriptor.relay_parent;
|
||||
|
||||
@@ -289,6 +294,10 @@ async fn distribute_collation(
|
||||
|
||||
state.our_validators_groups.insert(relay_parent, current_validators.into());
|
||||
|
||||
if let Some(result_sender) = result_sender {
|
||||
state.collation_result_senders.insert(receipt.hash(), result_sender);
|
||||
}
|
||||
|
||||
state.collations.insert(relay_parent, (receipt, pov));
|
||||
|
||||
Ok(())
|
||||
@@ -438,7 +447,7 @@ async fn process_msg(
|
||||
CollateOn(id) => {
|
||||
state.collating_on = Some(id);
|
||||
}
|
||||
DistributeCollation(receipt, pov) => {
|
||||
DistributeCollation(receipt, pov, result_sender) => {
|
||||
let _span1 = state.span_per_relay_parent
|
||||
.get(&receipt.descriptor.relay_parent).map(|s| s.child("distributing-collation"));
|
||||
let _span2 = jaeger::pov_span(&pov, "distributing-collation");
|
||||
@@ -454,7 +463,7 @@ async fn process_msg(
|
||||
);
|
||||
}
|
||||
Some(id) => {
|
||||
distribute_collation(ctx, state, id, receipt, pov).await?;
|
||||
distribute_collation(ctx, state, id, receipt, pov, result_sender).await?;
|
||||
}
|
||||
None => {
|
||||
tracing::warn!(
|
||||
@@ -483,6 +492,12 @@ async fn process_msg(
|
||||
"NoteGoodCollation message is not expected on the collator side of the protocol",
|
||||
);
|
||||
}
|
||||
NotifyCollationSeconded(_, _) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"NotifyCollationSeconded message is not expected on the collator side of the protocol",
|
||||
);
|
||||
}
|
||||
NetworkBridgeUpdateV1(event) => {
|
||||
if let Err(e) = handle_network_msg(
|
||||
ctx,
|
||||
@@ -591,6 +606,17 @@ async fn handle_incoming_peer_message(
|
||||
"Collation message is not expected on the collator side of the protocol",
|
||||
);
|
||||
}
|
||||
CollationSeconded(statement) => {
|
||||
if !matches!(statement.payload(), Statement::Seconded(_)) {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
statement = ?statement,
|
||||
"Collation seconded message received with none-seconded statement.",
|
||||
);
|
||||
} else if let Some(sender) = state.collation_result_senders.remove(&statement.payload().candidate_hash()) {
|
||||
let _ = sender.send(statement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -685,7 +711,9 @@ async fn handle_our_view_change(
|
||||
view: OurView,
|
||||
) -> Result<()> {
|
||||
for removed in state.view.difference(&view) {
|
||||
state.collations.remove(removed);
|
||||
if let Some((receipt, _)) = state.collations.remove(removed) {
|
||||
state.collation_result_senders.remove(&receipt.hash());
|
||||
}
|
||||
state.our_validators_groups.remove(removed);
|
||||
state.connection_requests.remove_all(removed);
|
||||
state.span_per_relay_parent.remove(removed);
|
||||
@@ -1054,7 +1082,7 @@ mod tests {
|
||||
|
||||
overseer_send(
|
||||
virtual_overseer,
|
||||
CollatorProtocolMessage::DistributeCollation(candidate.clone(), pov_block.clone()),
|
||||
CollatorProtocolMessage::DistributeCollation(candidate.clone(), pov_block.clone(), None),
|
||||
).await;
|
||||
|
||||
// obtain the availability cores.
|
||||
|
||||
@@ -39,6 +39,7 @@ use polkadot_node_network_protocol::{
|
||||
v1 as protocol_v1, View, OurView, PeerId, ReputationChange as Rep, RequestId,
|
||||
};
|
||||
use polkadot_node_subsystem_util::{TimeoutExt as _, metrics::{self, prometheus}};
|
||||
use polkadot_node_primitives::{Statement, SignedFullStatement};
|
||||
|
||||
use super::{modify_reputation, LOG_TARGET, Result};
|
||||
|
||||
@@ -200,9 +201,6 @@ struct State {
|
||||
/// Delay after which a collation request would time out.
|
||||
request_timeout: Duration,
|
||||
|
||||
/// Possessed collations.
|
||||
collations: HashMap<(Hash, ParaId), Vec<(CollatorId, CandidateReceipt, PoV)>>,
|
||||
|
||||
/// Leaves have recently moved out of scope.
|
||||
/// These are looked into when we receive previously requested collations that we
|
||||
/// are no longer interested in.
|
||||
@@ -228,35 +226,13 @@ async fn fetch_collation<Context>(
|
||||
where
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>
|
||||
{
|
||||
// First take a look if we have already stored some of the relevant collations.
|
||||
if let Some(collations) = state.collations.get(&(relay_parent, para_id)) {
|
||||
for collation in collations.iter() {
|
||||
if collation.0 == collator_id {
|
||||
if let Err(e) = tx.send((collation.1.clone(), collation.2.clone())) {
|
||||
// We do not want this to be fatal because the receving subsystem
|
||||
// may have closed the results channel for some reason.
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
err = ?e,
|
||||
"Failed to send collation",
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
let relevant_advertiser = state.advertisements.iter().find_map(|(k, v)| {
|
||||
if v.contains(&(para_id, relay_parent)) && state.known_collators.get(k) == Some(&collator_id) {
|
||||
Some(k.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Dodge multiple references to `state`.
|
||||
let mut relevant_advertiser = None;
|
||||
|
||||
// Has the collator in question advertised a relevant collation?
|
||||
for (k, v) in state.advertisements.iter() {
|
||||
if v.contains(&(para_id, relay_parent)) {
|
||||
if state.known_collators.get(k) == Some(&collator_id) {
|
||||
relevant_advertiser = Some(k.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Request the collation.
|
||||
// Assume it is `request_collation`'s job to check and ignore duplicate requests.
|
||||
@@ -278,10 +254,8 @@ where
|
||||
// Since we have a one way map of PeerId -> CollatorId we have to
|
||||
// iterate here. Since a huge amount of peers is not expected this
|
||||
// is a tolerable thing to do.
|
||||
for (k, v) in state.known_collators.iter() {
|
||||
if *v == id {
|
||||
modify_reputation(ctx, k.clone(), COST_REPORT_BAD).await;
|
||||
}
|
||||
for (k, _) in state.known_collators.iter().filter(|d| *d.1 == id) {
|
||||
modify_reputation(ctx, k.clone(), COST_REPORT_BAD).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,10 +269,41 @@ async fn note_good_collation<Context>(
|
||||
where
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>
|
||||
{
|
||||
for (peer_id, collator_id) in state.known_collators.iter() {
|
||||
if id == *collator_id {
|
||||
modify_reputation(ctx, peer_id.clone(), BENEFIT_NOTIFY_GOOD).await;
|
||||
}
|
||||
for (peer_id, _) in state.known_collators.iter().filter(|d| *d.1 == id) {
|
||||
modify_reputation(ctx, peer_id.clone(), BENEFIT_NOTIFY_GOOD).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Notify a collator that its collation got seconded.
|
||||
#[tracing::instrument(level = "trace", skip(ctx, state), fields(subsystem = LOG_TARGET))]
|
||||
async fn notify_collation_seconded(
|
||||
ctx: &mut impl SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
state: &mut State,
|
||||
id: CollatorId,
|
||||
statement: SignedFullStatement,
|
||||
) {
|
||||
if !matches!(statement.payload(), Statement::Seconded(_)) {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
statement = ?statement,
|
||||
"Notify collation seconded called with a wrong statement.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let peer_ids = state.known_collators.iter()
|
||||
.filter_map(|(p, c)| if *c == id { Some(p.clone()) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !peer_ids.is_empty() {
|
||||
let wire_message = protocol_v1::CollatorProtocolMessage::CollationSeconded(statement);
|
||||
|
||||
ctx.send_message(AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::SendCollationMessage(
|
||||
peer_ids,
|
||||
protocol_v1::CollationProtocol::CollatorProtocol(wire_message),
|
||||
)
|
||||
)).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,7 +373,7 @@ where
|
||||
if id == request_id {
|
||||
if let Some(per_request) = state.requests_info.remove(&id) {
|
||||
let _ = per_request.received.send(());
|
||||
if let Some(collator_id) = state.known_collators.get(&origin) {
|
||||
if state.known_collators.get(&origin).is_some() {
|
||||
let pov = match pov.decompress() {
|
||||
Ok(pov) => pov,
|
||||
Err(error) => {
|
||||
@@ -395,11 +400,6 @@ where
|
||||
|
||||
let _ = per_request.result.send((receipt.clone(), pov.clone()));
|
||||
state.metrics.on_request(Ok(()));
|
||||
|
||||
state.collations
|
||||
.entry((relay_parent, para_id))
|
||||
.or_default()
|
||||
.push((collator_id.clone(), receipt, pov));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -558,6 +558,9 @@ where
|
||||
.map(|s| s.child("received-collation"));
|
||||
received_collation(ctx, state, origin, request_id, receipt, pov).await;
|
||||
}
|
||||
CollationSeconded(_) => {
|
||||
modify_reputation(ctx, origin, COST_UNEXPECTED_MESSAGE).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,8 +587,6 @@ async fn remove_relay_parent(
|
||||
}
|
||||
}
|
||||
|
||||
state.collations.retain(|k, _| k.0 != relay_parent);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -705,7 +706,7 @@ where
|
||||
"CollateOn message is not expected on the validator side of the protocol",
|
||||
);
|
||||
}
|
||||
DistributeCollation(_, _) => {
|
||||
DistributeCollation(_, _, _) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"DistributeCollation message is not expected on the validator side of the protocol",
|
||||
@@ -721,6 +722,9 @@ where
|
||||
NoteGoodCollation(id) => {
|
||||
note_good_collation(ctx, state, id).await;
|
||||
}
|
||||
NotifyCollationSeconded(id, statement) => {
|
||||
notify_collation_seconded(ctx, state, id, statement).await;
|
||||
}
|
||||
NetworkBridgeUpdateV1(event) => {
|
||||
if let Err(e) = handle_network_msg(
|
||||
ctx,
|
||||
|
||||
Reference in New Issue
Block a user