diff --git a/network/src/legacy/gossip/attestation.rs b/network/src/legacy/gossip/attestation.rs index 7f6de6586e63..a47f75288bf4 100644 --- a/network/src/legacy/gossip/attestation.rs +++ b/network/src/legacy/gossip/attestation.rs @@ -35,7 +35,7 @@ use sc_network::ReputationChange; use polkadot_validation::GenericStatement; use polkadot_primitives::Hash; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use log::warn; @@ -44,22 +44,34 @@ use super::{ ChainContext, Known, MessageValidationData, GossipStatement, }; +/// Meta-data that we keep about a candidate in the `Knowledge`. +#[derive(Debug, Clone)] +pub(super) struct CandidateMeta { + /// The hash of the pov-block data. + pub(super) pov_block_hash: Hash, +} + // knowledge about attestations on a single parent-hash. #[derive(Default)] pub(super) struct Knowledge { - candidates: HashSet, + candidates: HashMap, } impl Knowledge { // whether the peer is aware of a candidate with given hash. fn is_aware_of(&self, candidate_hash: &Hash) -> bool { - self.candidates.contains(candidate_hash) + self.candidates.contains_key(candidate_hash) + } + + // Get candidate meta data for a candidate by hash. + fn candidate_meta(&self, candidate_hash: &Hash) -> Option<&CandidateMeta> { + self.candidates.get(candidate_hash) } // note that the peer is aware of a candidate with given hash. this should // be done after observing an incoming candidate message via gossip. - fn note_aware(&mut self, candidate_hash: Hash) { - self.candidates.insert(candidate_hash); + fn note_aware(&mut self, candidate_hash: Hash, candidate_meta: CandidateMeta) { + self.candidates.insert(candidate_hash, candidate_meta); } } @@ -84,9 +96,14 @@ impl PeerData { } #[cfg(test)] - pub(super) fn note_aware_under_leaf(&mut self, relay_chain_leaf: &Hash, candidate_hash: Hash) { + pub(super) fn note_aware_under_leaf( + &mut self, + relay_chain_leaf: &Hash, + candidate_hash: Hash, + meta: CandidateMeta, + ) { if let Some(knowledge) = self.live.get_mut(relay_chain_leaf) { - knowledge.note_aware(candidate_hash); + knowledge.note_aware(candidate_hash, meta); } } @@ -144,6 +161,7 @@ impl View { }, )); self.topics.insert(attestation_topic(relay_chain_leaf), relay_chain_leaf); + self.topics.insert(super::pov_block_topic(relay_chain_leaf), relay_chain_leaf); } /// Prune old leaf-work that fails the leaf predicate. @@ -164,6 +182,17 @@ impl View { self.topics.get(topic) } + #[cfg(test)] + pub(super) fn note_aware_under_leaf( + &mut self, + relay_chain_leaf: &Hash, + candidate_hash: Hash, + meta: CandidateMeta, + ) { + if let Some(view) = self.leaf_view_mut(relay_chain_leaf) { + view.knowledge.note_aware(candidate_hash, meta); + } + } /// Validate the signature on an attestation statement of some kind. Should be done before /// any repropagation of that statement. @@ -225,15 +254,59 @@ impl View { } } + /// Validate a pov-block message. + pub(super) fn validate_pov_block_message( + &mut self, + message: &super::GossipPoVBlock, + chain: &C, + ) + -> (GossipValidationResult, ReputationChange) + { + match self.leaf_view(&message.relay_chain_leaf) { + None => { + let cost = match chain.is_known(&message.relay_chain_leaf) { + Some(Known::Leaf) => { + warn!( + target: "network", + "Leaf block {} not considered live for attestation", + message.relay_chain_leaf, + ); + cost::NONE + } + Some(Known::Old) => cost::POV_BLOCK_UNWANTED, + _ => cost::FUTURE_MESSAGE, + }; + + (GossipValidationResult::Discard, cost) + } + Some(view) => { + // we only accept pov-blocks for candidates that we have + // and consider active. + match view.knowledge.candidate_meta(&message.candidate_hash) { + None => (GossipValidationResult::Discard, cost::POV_BLOCK_UNWANTED), + Some(meta) => { + // check that the pov-block hash is actually correct. + if meta.pov_block_hash == message.pov_block.hash() { + let topic = super::pov_block_topic(message.relay_chain_leaf); + (GossipValidationResult::ProcessAndKeep(topic), benefit::NEW_POV_BLOCK) + } else { + (GossipValidationResult::Discard, cost::POV_BLOCK_BAD_DATA) + } + } + } + } + } + } + /// whether it's allowed to send a statement to a peer with given knowledge /// about the relay parent the statement refers to. pub(super) fn statement_allowed( &mut self, statement: &GossipStatement, - relay_chain_leaf: &Hash, peer_knowledge: &mut Knowledge, ) -> bool { let signed = &statement.signed_statement; + let relay_chain_leaf = &statement.relay_chain_leaf; match signed.statement { GenericStatement::Valid(ref h) | GenericStatement::Invalid(ref h) => { @@ -245,9 +318,10 @@ impl View { // if we are sending a `Candidate` message we should make sure that // attestation_view and their_view reflects that we know about the candidate. let hash = c.hash(); - peer_knowledge.note_aware(hash); + let meta = CandidateMeta { pov_block_hash: c.pov_block_hash }; + peer_knowledge.note_aware(hash, meta.clone()); if let Some(attestation_view) = self.leaf_view_mut(&relay_chain_leaf) { - attestation_view.knowledge.note_aware(hash); + attestation_view.knowledge.note_aware(hash, meta); } // at this point, the peer hasn't seen the message or the candidate @@ -256,6 +330,15 @@ impl View { } } } + + /// whether it's allowed to send a pov-block to a peer. + pub(super) fn pov_block_allowed( + &mut self, + statement: &super::GossipPoVBlock, + peer_knowledge: &mut Knowledge, + ) -> bool { + peer_knowledge.is_aware_of(&statement.candidate_hash) + } } struct LeafView { diff --git a/network/src/legacy/gossip/mod.rs b/network/src/legacy/gossip/mod.rs index 561873c990a4..0afa604e2af8 100644 --- a/network/src/legacy/gossip/mod.rs +++ b/network/src/legacy/gossip/mod.rs @@ -60,7 +60,7 @@ use sc_network_gossip::{ use polkadot_validation::{SignedStatement}; use polkadot_primitives::{Block, Hash}; use polkadot_primitives::parachain::{ - ParachainHost, ValidatorId, ErasureChunk as PrimitiveChunk, SigningContext, + ParachainHost, ValidatorId, ErasureChunk as PrimitiveChunk, SigningContext, PoVBlock, }; use polkadot_erasure_coding::{self as erasure}; use codec::{Decode, Encode}; @@ -95,6 +95,8 @@ mod benefit { pub const NEW_CANDIDATE: Rep = Rep::new(100, "Polkadot: New candidate"); /// When a peer sends us a previously-unknown attestation. pub const NEW_ATTESTATION: Rep = Rep::new(50, "Polkadot: New attestation"); + /// When a peer sends us a previously-unknown pov-block + pub const NEW_POV_BLOCK: Rep = Rep::new(150, "Polkadot: New PoV block"); /// When a peer sends us a previously-unknown erasure chunk. pub const NEW_ERASURE_CHUNK: Rep = Rep::new(10, "Polkadot: New erasure chunk"); } @@ -105,6 +107,10 @@ mod cost { pub const NONE: Rep = Rep::new(0, ""); /// A peer sent us an attestation and we don't know the candidate. pub const ATTESTATION_NO_CANDIDATE: Rep = Rep::new(-100, "Polkadot: No candidate"); + /// A peer sent us a pov-block and we don't know the candidate or the leaf. + pub const POV_BLOCK_UNWANTED: Rep = Rep::new(-500, "Polkadot: No candidate"); + /// A peer sent us a pov-block message with wrong data. + pub const POV_BLOCK_BAD_DATA: Rep = Rep::new(-1000, "Polkadot: Bad PoV-block data"); /// A peer sent us a statement we consider in the future. pub const FUTURE_MESSAGE: Rep = Rep::new(-100, "Polkadot: Future message"); /// A peer sent us a statement from the past. @@ -135,6 +141,9 @@ pub enum GossipMessage { /// A packet containing one of the erasure-coding chunks of one candidate. #[codec(index = "3")] ErasureChunk(ErasureChunkMessage), + /// A PoV-block. + #[codec(index = "255")] + PoVBlock(GossipPoVBlock), } impl From for GossipMessage { @@ -149,6 +158,12 @@ impl From for GossipMessage { } } +impl From for GossipMessage { + fn from(pov: GossipPoVBlock) -> Self { + GossipMessage::PoVBlock(pov) + } +} + /// A gossip message containing a statement. #[derive(Encode, Decode, Clone, PartialEq)] pub struct GossipStatement { @@ -185,15 +200,18 @@ impl From for GossipMessage { } } -/// A packet of messages from one parachain to another. -/// -/// These are all the messages posted from one parachain to another during the -/// execution of a single parachain block. Since this parachain block may have been -/// included in many forks of the relay chain, there is no relay-chain leaf parameter. -#[derive(Encode, Decode, Clone, PartialEq)] -pub struct GossipParachainMessages { - /// The root of the message queue. - pub queue_root: Hash, +/// A pov-block being gossipped. Should only be sent to peers aware of the candidate +/// referenced. +#[derive(Encode, Decode, Clone, Debug, PartialEq)] +pub struct GossipPoVBlock { + /// The block hash of the relay chain being referred to. In context, this should + /// be a leaf. + pub relay_chain_leaf: Hash, + /// The hash of some candidate localized to the same relay-chain leaf, whose + /// pov-block is this block. + pub candidate_hash: Hash, + /// The pov-block itself. + pub pov_block: PoVBlock, } /// A versioned neighbor message. @@ -262,6 +280,14 @@ pub(crate) fn attestation_topic(parent_hash: Hash) -> Hash { BlakeTwo256::hash(&v[..]) } +/// Compute the gossip topic for PoV blocks based on the given parent hash. +pub(crate) fn pov_block_topic(parent_hash: Hash) -> Hash { + let mut v = parent_hash.as_ref().to_vec(); + v.extend(b"pov-blocks"); + + BlakeTwo256::hash(&v[..]) +} + /// Register a gossip validator on the network service. // NOTE: since RegisteredMessageValidator is meant to be a type-safe proof // that we've actually done the registration, this should be the only way @@ -511,8 +537,9 @@ impl Inner { let new_topics = if let Some(ref mut peer) = self.peers.get_mut(sender) { let new_leaves = peer.attestation.update_leaves(&chain_heads); let new_attestation_topics = new_leaves.iter().cloned().map(attestation_topic); + let new_pov_block_topics = new_leaves.iter().cloned().map(pov_block_topic); - new_attestation_topics.collect() + new_attestation_topics.chain(new_pov_block_topics).collect() } else { Vec::new() }; @@ -643,6 +670,19 @@ impl sc_network_gossip::Validator for MessageVa } (res, cb) } + Ok(GossipMessage::PoVBlock(pov_block)) => { + let (res, cb) = { + let mut inner = self.inner.write(); + let inner = &mut *inner; + inner.attestation_view.validate_pov_block_message(&pov_block, &inner.chain) + }; + + if let GossipValidationResult::ProcessAndKeep(ref topic) = res { + context.broadcast_message(topic.clone(), data.to_vec(), false); + } + + (res, cb) + } Ok(GossipMessage::ErasureChunk(chunk)) => { self.inner.write().validate_erasure_chunk_packet(chunk) } @@ -688,11 +728,24 @@ impl sc_network_gossip::Validator for MessageVa .and_then(|(p, r)| p.attestation.knowledge_at_mut(&r).map(|k| (k, r))); peer_knowledge.map_or(false, |(knowledge, attestation_head)| { - attestation_view.statement_allowed( - statement, - &attestation_head, - knowledge, - ) + statement.relay_chain_leaf == attestation_head + && attestation_view.statement_allowed( + statement, + knowledge, + ) + }) + } + Ok(GossipMessage::PoVBlock(ref pov_block)) => { + // to allow pov-blocks, we need peer knowledge. + let peer_knowledge = peer.and_then(move |p| attestation_head.map(|r| (p, r))) + .and_then(|(p, r)| p.attestation.knowledge_at_mut(&r).map(|k| (k, r))); + + peer_knowledge.map_or(false, |(knowledge, attestation_head)| { + pov_block.relay_chain_leaf == attestation_head + && attestation_view.pov_block_allowed( + pov_block, + knowledge, + ) }) } _ => false, @@ -707,7 +760,7 @@ mod tests { use sc_network_gossip::Validator as ValidatorT; use std::sync::mpsc; use parking_lot::Mutex; - use polkadot_primitives::parachain::AbridgedCandidateReceipt; + use polkadot_primitives::parachain::{AbridgedCandidateReceipt, BlockData}; use sp_core::sr25519::Signature as Sr25519Signature; use polkadot_validation::GenericStatement; @@ -768,7 +821,7 @@ mod tests { } #[test] - fn message_allowed() { + fn attestation_message_allowed() { let (tx, _rx) = mpsc::channel(); let tx = Mutex::new(tx); let report_handle = Box::new(move |peer: &PeerId, cb: ReputationChange| tx.lock().send((peer.clone(), cb)).unwrap()); @@ -806,6 +859,9 @@ mod tests { vec![ ContextEvent::SendTopic(peer_a.clone(), attestation_topic(hash_a), false), ContextEvent::SendTopic(peer_a.clone(), attestation_topic(hash_b), false), + + ContextEvent::SendTopic(peer_a.clone(), pov_block_topic(hash_a), false), + ContextEvent::SendTopic(peer_a.clone(), pov_block_topic(hash_b), false), ], ); @@ -908,38 +964,100 @@ mod tests { chain_heads: vec![hash_a, hash_b], }).encode(); - let res = validator.validate( - &mut validator_context, - &peer_a, - &message[..], - ); + { + let res = validator.validate( + &mut validator_context, + &peer_a, + &message[..], + ); - match res { - GossipValidationResult::Discard => {}, - _ => panic!("wrong result"), + match res { + GossipValidationResult::Discard => {}, + _ => panic!("wrong result"), + } + assert_eq!( + validator_context.events, + vec![ + ContextEvent::SendTopic(peer_a.clone(), attestation_topic(hash_a), false), + ContextEvent::SendTopic(peer_a.clone(), attestation_topic(hash_b), false), + + ContextEvent::SendTopic(peer_a.clone(), pov_block_topic(hash_a), false), + ContextEvent::SendTopic(peer_a.clone(), pov_block_topic(hash_b), false), + ], + ); + + validator_context.clear(); } - assert_eq!( - validator_context.events, - vec![ - ContextEvent::SendTopic(peer_a.clone(), attestation_topic(hash_a), false), - ContextEvent::SendTopic(peer_a.clone(), attestation_topic(hash_b), false), - ], + + let mut validation_data = MessageValidationData::default(); + validation_data.signing_context.parent_hash = hash_a; + validator.inner.write().attestation_view.new_local_leaf(validation_data); + } + + #[test] + fn pov_block_message_allowed() { + let (tx, _rx) = mpsc::channel(); + let tx = Mutex::new(tx); + let report_handle = Box::new(move |peer: &PeerId, cb: ReputationChange| tx.lock().send((peer.clone(), cb)).unwrap()); + let validator = MessageValidator::new_test( + TestChainContext::default(), + report_handle, ); + let peer_a = PeerId::random(); + + let mut validator_context = MockValidatorContext::default(); + validator.new_peer(&mut validator_context, &peer_a, Roles::FULL); + assert!(validator_context.events.is_empty()); validator_context.clear(); - let topic_a = attestation_topic(hash_a); + let hash_a = [1u8; 32].into(); + let hash_b = [2u8; 32].into(); + + let message = GossipMessage::from(NeighborPacket { + chain_heads: vec![hash_a, hash_b], + }).encode(); + + { + let res = validator.validate( + &mut validator_context, + &peer_a, + &message[..], + ); + + match res { + GossipValidationResult::Discard => {}, + _ => panic!("wrong result"), + } + assert_eq!( + validator_context.events, + vec![ + ContextEvent::SendTopic(peer_a.clone(), attestation_topic(hash_a), false), + ContextEvent::SendTopic(peer_a.clone(), attestation_topic(hash_b), false), + + ContextEvent::SendTopic(peer_a.clone(), pov_block_topic(hash_a), false), + ContextEvent::SendTopic(peer_a.clone(), pov_block_topic(hash_b), false), + ], + ); + + validator_context.clear(); + } + + let topic_a = pov_block_topic(hash_a); let c_hash = [99u8; 32].into(); - let statement = GossipMessage::Statement(GossipStatement { + let pov_block = PoVBlock { + block_data: BlockData(vec![1, 2, 3]), + }; + + let pov_block_hash = pov_block.hash(); + + let message = GossipMessage::PoVBlock(GossipPoVBlock { relay_chain_leaf: hash_a, - signed_statement: SignedStatement { - statement: GenericStatement::Valid(c_hash), - signature: Sr25519Signature([255u8; 64]).into(), - sender: 1, - } + candidate_hash: c_hash, + pov_block, }); - let encoded = statement.encode(); + let encoded = message.encode(); let mut validation_data = MessageValidationData::default(); validation_data.signing_context.parent_hash = hash_a; validator.inner.write().attestation_view.new_local_leaf(validation_data); @@ -956,10 +1074,181 @@ mod tests { .get_mut(&peer_a) .unwrap() .attestation - .note_aware_under_leaf(&hash_a, c_hash); + .note_aware_under_leaf( + &hash_a, + c_hash, + attestation::CandidateMeta { pov_block_hash }, + ); + { let mut message_allowed = validator.message_allowed(); assert!(message_allowed(&peer_a, MessageIntent::Broadcast, &topic_a, &encoded[..])); } } + + #[test] + fn validate_pov_block_message() { + let (tx, _rx) = mpsc::channel(); + let tx = Mutex::new(tx); + let report_handle = Box::new(move |peer: &PeerId, cb: ReputationChange| tx.lock().send((peer.clone(), cb)).unwrap()); + let validator = MessageValidator::new_test( + TestChainContext::default(), + report_handle, + ); + + let peer_a = PeerId::random(); + + let mut validator_context = MockValidatorContext::default(); + validator.new_peer(&mut validator_context, &peer_a, Roles::FULL); + assert!(validator_context.events.is_empty()); + validator_context.clear(); + + let hash_a = [1u8; 32].into(); + let hash_b = [2u8; 32].into(); + + let message = GossipMessage::from(NeighborPacket { + chain_heads: vec![hash_a, hash_b], + }).encode(); + + { + let res = validator.validate( + &mut validator_context, + &peer_a, + &message[..], + ); + + match res { + GossipValidationResult::Discard => {}, + _ => panic!("wrong result"), + } + assert_eq!( + validator_context.events, + vec![ + ContextEvent::SendTopic(peer_a.clone(), attestation_topic(hash_a), false), + ContextEvent::SendTopic(peer_a.clone(), attestation_topic(hash_b), false), + + ContextEvent::SendTopic(peer_a.clone(), pov_block_topic(hash_a), false), + ContextEvent::SendTopic(peer_a.clone(), pov_block_topic(hash_b), false), + ], + ); + + validator_context.clear(); + } + + let pov_topic = pov_block_topic(hash_a); + + let pov_block = PoVBlock { + block_data: BlockData(vec![1, 2, 3]), + }; + + let pov_block_hash = pov_block.hash(); + let c_hash = [99u8; 32].into(); + + let message = GossipMessage::PoVBlock(GossipPoVBlock { + relay_chain_leaf: hash_a, + candidate_hash: c_hash, + pov_block, + }); + + let bad_message = GossipMessage::PoVBlock(GossipPoVBlock { + relay_chain_leaf: hash_a, + candidate_hash: c_hash, + pov_block: PoVBlock { + block_data: BlockData(vec![4, 5, 6]), + }, + }); + + let encoded = message.encode(); + let bad_encoded = bad_message.encode(); + + let mut validation_data = MessageValidationData::default(); + validation_data.signing_context.parent_hash = hash_a; + validator.inner.write().attestation_view.new_local_leaf(validation_data); + + // before sending `Candidate` message, neither are allowed. + { + let res = validator.validate( + &mut validator_context, + &peer_a, + &encoded[..], + ); + + match res { + GossipValidationResult::Discard => {}, + _ => panic!("wrong result"), + } + assert_eq!( + validator_context.events, + Vec::new(), + ); + + validator_context.clear(); + } + + { + let res = validator.validate( + &mut validator_context, + &peer_a, + &bad_encoded[..], + ); + + match res { + GossipValidationResult::Discard => {}, + _ => panic!("wrong result"), + } + assert_eq!( + validator_context.events, + Vec::new(), + ); + + validator_context.clear(); + } + + validator.inner.write().attestation_view.note_aware_under_leaf( + &hash_a, + c_hash, + attestation::CandidateMeta { pov_block_hash }, + ); + + // now the good message passes and the others not. + { + let res = validator.validate( + &mut validator_context, + &peer_a, + &encoded[..], + ); + + match res { + GossipValidationResult::ProcessAndKeep(topic) => assert_eq!(topic,pov_topic), + _ => panic!("wrong result"), + } + assert_eq!( + validator_context.events, + vec![ + ContextEvent::BroadcastMessage(pov_topic, encoded.clone(), false), + ], + ); + + validator_context.clear(); + } + + { + let res = validator.validate( + &mut validator_context, + &peer_a, + &bad_encoded[..], + ); + + match res { + GossipValidationResult::Discard => {}, + _ => panic!("wrong result"), + } + assert_eq!( + validator_context.events, + Vec::new(), + ); + + validator_context.clear(); + } + } } diff --git a/network/src/protocol/mod.rs b/network/src/protocol/mod.rs index b63fa563867e..787429d88fe1 100644 --- a/network/src/protocol/mod.rs +++ b/network/src/protocol/mod.rs @@ -877,9 +877,17 @@ impl Worker where &self.gossip_handle, ); } - ServiceToWorkerMsg::FetchPoVBlock(_candidate, _sender) => { - // TODO https://github.com/paritytech/polkadot/issues/742: - // create a filter on gossip for it and send to sender. + ServiceToWorkerMsg::FetchPoVBlock(candidate, mut sender) => { + // The gossip system checks that the correct pov-block data is present + // before placing in the pool, so we can safely check by candidate hash. + let get_msg = fetch_pov_from_gossip(&candidate, &self.gossip_handle); + + let _ = self.executor.spawn(async move { + let res = future::select(get_msg, AwaitCanceled { inner: &mut sender }).await; + if let Either::Left((pov_block, _)) = res { + let _ = sender.send(pov_block); + } + }); } ServiceToWorkerMsg::FetchErasureChunk(candidate_hash, validator_index, mut sender) => { let topic = crate::erasure_coding_topic(&candidate_hash); @@ -1133,16 +1141,14 @@ async fn statement_import_loop( statements.insert(0, statement); let producers: Vec<_> = { - // TODO: fetch these from gossip. - // https://github.com/paritytech/polkadot/issues/742 - fn ignore_pov_fetch_requests(_: &AbridgedCandidateReceipt) - -> future::Pending> - { - future::pending() - } + let gossip_handle = &gossip_handle; + let fetch_pov = |candidate: &AbridgedCandidateReceipt| fetch_pov_from_gossip( + candidate, + gossip_handle, + ).map(Result::<_, std::io::Error>::Ok); table.import_remote_statements( - &ignore_pov_fetch_requests, + &fetch_pov, statements.iter().cloned(), ) }; @@ -1192,6 +1198,33 @@ async fn statement_import_loop( } } +fn fetch_pov_from_gossip( + candidate: &AbridgedCandidateReceipt, + gossip_handle: &impl GossipOps, +) -> impl Future + Send { + let candidate_hash = candidate.hash(); + let topic = crate::legacy::gossip::pov_block_topic(candidate.relay_parent); + + // The gossip system checks that the correct pov-block data is present + // before placing in the pool, so we can safely check by candidate hash. + gossip_handle.gossip_messages_for(topic) + .filter_map(move |(msg, _)| { + future::ready(match msg { + GossipMessage::PoVBlock(pov_block_message) => + if pov_block_message.candidate_hash == candidate_hash { + Some(pov_block_message.pov_block) + } else { + None + }, + _ => None, + }) + }) + .into_future() + .map(|(item, _)| item.expect( + "gossip message streams do not conclude early; qed" + )) +} + // distribute a "local collation": this is the collation gotten by a validator // from a collator. it needs to be distributed to other validators in the same // group. @@ -1206,19 +1239,37 @@ fn distribute_validated_collation( let hash = receipt.hash(); let validated = Validated::collated_local( receipt, - pov_block, + pov_block.clone(), ); - let statement = crate::legacy::gossip::GossipStatement::new( - instance.relay_parent, - match instance.statement_table.import_validated(validated) { - None => return, - Some(s) => s, - } - ); + // gossip the signed statement. + { + let statement = crate::legacy::gossip::GossipStatement::new( + instance.relay_parent, + match instance.statement_table.import_validated(validated) { + None => return, + Some(s) => s, + } + ); + + gossip_handle.gossip_message(instance.attestation_topic, statement.into()); + } + + // gossip the PoV block. + { + let pov_block_message = crate::legacy::gossip::GossipPoVBlock { + relay_chain_leaf: instance.relay_parent, + candidate_hash: hash, + pov_block, + }; - gossip_handle.gossip_message(instance.attestation_topic, statement.into()); + gossip_handle.gossip_message( + crate::legacy::gossip::pov_block_topic(instance.relay_parent), + pov_block_message.into(), + ); + } + // gossip erasure chunks. for chunk in chunks.1 { let message = crate::legacy::gossip::ErasureChunkMessage { chunk, diff --git a/network/src/protocol/tests.rs b/network/src/protocol/tests.rs index a114ccb37e32..24ec30ebbe42 100644 --- a/network/src/protocol/tests.rs +++ b/network/src/protocol/tests.rs @@ -14,6 +14,7 @@ //! Tests for the protocol. use super::*; +use crate::legacy::gossip::GossipPoVBlock; use parking_lot::Mutex; use polkadot_primitives::{Block, Header, BlockId}; @@ -21,8 +22,9 @@ use polkadot_primitives::parachain::{ Id as ParaId, Chain, DutyRoster, ParachainHost, ValidatorId, Retriable, CollatorId, AbridgedCandidateReceipt, GlobalValidationSchedule, LocalValidationData, ErasureChunk, SigningContext, + PoVBlock, BlockData, }; -use polkadot_validation::SharedTable; +use polkadot_validation::{SharedTable, TableRouter}; use av_store::{Store as AvailabilityStore, ErasureNetworking}; use sc_network_gossip::TopicNotification; @@ -402,7 +404,6 @@ fn worker_task_shuts_down_when_sender_dropped() { fn consensus_instances_cleaned_up() { let (mut service, _gossip, mut pool, worker_task) = test_setup(Config { collating_for: None }); let relay_parent = [0; 32].into(); - let authorities = Vec::new(); let signing_context = SigningContext { session_index: Default::default(), @@ -420,7 +421,7 @@ fn consensus_instances_cleaned_up() { pool.spawner().spawn_local(worker_task).unwrap(); let router = pool.run_until( - service.build_table_router(table, &authorities) + service.build_table_router(table, &[]) ).unwrap(); drop(router); @@ -590,3 +591,54 @@ fn erasure_fetch_drop_also_drops_gossip_sender() { pool.run_until(test_work); } + +#[test] +fn fetches_pov_block_from_gossip() { + let (service, gossip, mut pool, worker_task) = test_setup(Config { collating_for: None }); + let relay_parent = [255; 32].into(); + + let pov_block = PoVBlock { + block_data: BlockData(vec![1, 2, 3]), + }; + + let mut candidate = AbridgedCandidateReceipt::default(); + candidate.relay_parent = relay_parent; + candidate.pov_block_hash = pov_block.hash(); + let candidate_hash = candidate.hash(); + + let signing_context = SigningContext { + session_index: Default::default(), + parent_hash: relay_parent, + }; + + let table = Arc::new(SharedTable::new( + Vec::new(), + HashMap::new(), + None, + signing_context, + AvailabilityStore::new_in_memory(service.clone()), + None, + )); + + let spawner = pool.spawner(); + + spawner.spawn_local(worker_task).unwrap(); + let topic = crate::legacy::gossip::pov_block_topic(relay_parent); + let (mut gossip_tx, _gossip_taken_rx) = gossip.add_gossip_stream(topic); + + let test_work = async move { + let router = service.build_table_router(table, &[]).await.unwrap(); + let pov_block_listener = router.fetch_pov_block(&candidate); + + let message = GossipMessage::PoVBlock(GossipPoVBlock { + relay_chain_leaf: relay_parent, + candidate_hash, + pov_block, + }).encode(); + + gossip_tx.send(TopicNotification { message, sender: None }).await.unwrap(); + pov_block_listener.await + }; + + pool.run_until(test_work).unwrap(); +}