Enable collation via RPC relay chain node (#1585)

* Add minimal overseer gen with dummy subsystems

* Fix dependencies

* no-compile: only client transaction pool missing

* Remove unused imports

* Continue to hack towards PoC

* Continue

* Make mini node compile

* Compiling version with blockchainevents trait

* Continue

* Check in lockfile

* Block with tokio

* update patches

* Update polkadot patches

* Use polkadot-primitives v2

* Fix build problems

* First working version

* Adjust cargo.lock

* Add integration test

* Make integration test work

* Allow startinc collator without relay-chain args

* Make OverseerRuntimeClient async

* Create separate integration test

* Remove unused ChainSelection code

* Remove unused parameters on new-mini

* Connect collator node in test to relay chain nodes

* Make BlockChainRPCClient obsolete

* Clean up

* Clean up

* Reimplement blockchain-rpc-events

* Revert "Allow startinc collator without relay-chain args"

This reverts commit f22c70e16521f375fe125df5616d48ceea926b1a.

* Add `strict_record_validation` to AuthorityDiscovery

* Move network to cumulus

* Remove BlockchainRPCEvents

* Remove `BlockIdTo` and `BlockchainEvents`

* Make AuthorityDiscovery async

* Use hash in OverseerRuntime

* Adjust naming of runtime client trait

* Implement more rpc-client methods

* Improve error handling for `ApiError`

* Extract authority-discovery creationand cleanup

* RPC -> Rpc

* Extract bitswap

* Adjust to changes on master

* Implement `hash` method

* Introduce DummyChainSync, remove ProofProvider and BlockBackend

* Remove `HeaderMetadata` from blockchain-rpc-client

* Make ChainSync work

* Implement NetworkHeaderBackend

* Cleanup

* Adjustments after master merge

* Remove ImportQueue from network parameters

* Remove cargo patches

* Eliminate warnings

* Revert to HeaderBackend

* Add zombienet test

* Implement `status()` method

* Add more comments, improve readability

* Remove patches from Cargo.toml

* Remove integration test in favor of zombienet

* Remove unused dependencies, rename minimal node crate

* Adjust to latest master changes

* fmt

* Execute zombienet test on gitlab ci

* Reuse network metrics

* Chainsync metrics

* fmt

* Feed RPC node as boot node to the relay chain minimal node

* fmt

* Add bootnodes to zombienet collators

* Allow specification of relay chain args

* Apply review suggestions

* Remove unnecessary casts

* Enable PoV recovery for rpc full nodes

* Revert unwanted changes

* Make overseerHandle non-optional

* Add availability-store subsystem

* Add AuxStore and ChainApiSubsystem

* Add availability distribution subsystem

* Improve pov-recovery logging and add RPC nodes to tests

* fmt

* Make availability config const

* lock

* Enable debug logs for pov-recovery in zombienet

* Add log filters to test binary

* Allow wss

* Address review comments

* Apply reviewer comments

* Adjust to master changes

* Apply reviewer suggestions

* Bump polkadot

* Add builder method for minimal node

* Bump substrate and polkadot

* Clean up overseer building

* Add bootnode to two in pov_recovery test

* Fix missing quote in pov recovery zombienet test

* Improve zombienet pov test

* More debug logs for pov-recovery

* Remove reserved nodes like on original test

* Revert zombienet test to master
This commit is contained in:
Sebastian Kunert
2022-10-10 09:06:26 +02:00
committed by GitHub
parent 59d0f06156
commit cdcf5ddd9b
33 changed files with 1948 additions and 104 deletions
@@ -0,0 +1,49 @@
[package]
authors = ["Parity Technologies <admin@parity.io>"]
name = "cumulus-relay-chain-minimal-node"
version = "0.1.0"
edition = "2021"
[dependencies]
# polkadot deps
polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "master" }
polkadot-core-primitives = { git = "https://github.com/paritytech/polkadot", branch = "master" }
polkadot-overseer = { git = "https://github.com/paritytech/polkadot", branch = "master" }
polkadot-service = { git = "https://github.com/paritytech/polkadot", branch = "master" }
polkadot-node-subsystem-util = { git = "https://github.com/paritytech/polkadot", branch = "master" }
polkadot-node-network-protocol = { git = "https://github.com/paritytech/polkadot", branch = "master" }
polkadot-network-bridge = { git = "https://github.com/paritytech/polkadot", branch = "master" }
polkadot-node-core-av-store = { git = "https://github.com/paritytech/polkadot", branch = "master" }
polkadot-availability-distribution = { git = "https://github.com/paritytech/polkadot", branch = "master" }
# substrate deps
sc-authority-discovery = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-keystore = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-network-sync = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-network-common = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-network-light = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-service = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-transaction-pool-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-tracing = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-telemetry = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-consensus-babe = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
# cumulus deps
cumulus-relay-chain-interface = { path = "../relay-chain-interface" }
cumulus-relay-chain-rpc-interface = { path = "../relay-chain-rpc-interface" }
cumulus-primitives-core = { path = "../../primitives/core" }
lru = "0.8"
tracing = "0.1.25"
async-trait = "0.1.52"
futures = "0.3.24"
url = "2.2.2"
tokio = { version = "1.17.0", features = ["macros"] }
@@ -0,0 +1,463 @@
// Copyright 2022 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// Cumulus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Cumulus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Cumulus. If not, see <http://www.gnu.org/licenses/>.
use std::{pin::Pin, str::FromStr};
use cumulus_relay_chain_interface::{RelayChainError, RelayChainResult};
use cumulus_relay_chain_rpc_interface::RelayChainRpcClient;
use futures::{Future, Stream, StreamExt};
use polkadot_core_primitives::{Block, BlockId, Hash, Header};
use polkadot_overseer::RuntimeApiSubsystemClient;
use polkadot_service::{AuxStore, HeaderBackend};
use sc_authority_discovery::AuthorityDiscovery;
use sc_network_common::config::MultiaddrWithPeerId;
use sp_api::{ApiError, RuntimeApiInfo};
use sp_blockchain::Info;
const LOG_TARGET: &str = "blockchain-rpc-client";
#[derive(Clone)]
pub struct BlockChainRpcClient {
rpc_client: RelayChainRpcClient,
}
impl BlockChainRpcClient {
pub fn new(rpc_client: RelayChainRpcClient) -> Self {
Self { rpc_client }
}
pub async fn chain_get_header(
&self,
hash: Option<Hash>,
) -> Result<Option<Header>, RelayChainError> {
self.rpc_client.chain_get_header(hash).await
}
pub async fn block_get_hash(
&self,
number: Option<polkadot_service::BlockNumber>,
) -> Result<Option<Hash>, RelayChainError> {
self.rpc_client.chain_get_block_hash(number).await
}
}
// Implementation required by Availability-Distribution subsystem
// but never called in our case.
impl AuxStore for BlockChainRpcClient {
fn insert_aux<
'a,
'b: 'a,
'c: 'a,
I: IntoIterator<Item = &'a (&'c [u8], &'c [u8])>,
D: IntoIterator<Item = &'a &'b [u8]>,
>(
&self,
_insert: I,
_delete: D,
) -> sp_blockchain::Result<()> {
unimplemented!("Not supported on the RPC collator")
}
fn get_aux(&self, _key: &[u8]) -> sp_blockchain::Result<Option<Vec<u8>>> {
unimplemented!("Not supported on the RPC collator")
}
}
#[async_trait::async_trait]
impl RuntimeApiSubsystemClient for BlockChainRpcClient {
async fn validators(
&self,
at: Hash,
) -> Result<Vec<polkadot_primitives::v2::ValidatorId>, sp_api::ApiError> {
Ok(self.rpc_client.parachain_host_validators(at).await?)
}
async fn validator_groups(
&self,
at: Hash,
) -> Result<
(
Vec<Vec<polkadot_primitives::v2::ValidatorIndex>>,
polkadot_primitives::v2::GroupRotationInfo<polkadot_core_primitives::BlockNumber>,
),
sp_api::ApiError,
> {
Ok(self.rpc_client.parachain_host_validator_groups(at).await?)
}
async fn availability_cores(
&self,
at: Hash,
) -> Result<
Vec<polkadot_primitives::v2::CoreState<Hash, polkadot_core_primitives::BlockNumber>>,
sp_api::ApiError,
> {
Ok(self.rpc_client.parachain_host_availability_cores(at).await?)
}
async fn persisted_validation_data(
&self,
at: Hash,
para_id: cumulus_primitives_core::ParaId,
assumption: polkadot_primitives::v2::OccupiedCoreAssumption,
) -> Result<
Option<
cumulus_primitives_core::PersistedValidationData<
Hash,
polkadot_core_primitives::BlockNumber,
>,
>,
sp_api::ApiError,
> {
Ok(self
.rpc_client
.parachain_host_persisted_validation_data(at, para_id, assumption)
.await?)
}
async fn assumed_validation_data(
&self,
at: Hash,
para_id: cumulus_primitives_core::ParaId,
expected_persisted_validation_data_hash: Hash,
) -> Result<
Option<(
cumulus_primitives_core::PersistedValidationData<
Hash,
polkadot_core_primitives::BlockNumber,
>,
polkadot_primitives::v2::ValidationCodeHash,
)>,
sp_api::ApiError,
> {
Ok(self
.rpc_client
.parachain_host_assumed_validation_data(
at,
para_id,
expected_persisted_validation_data_hash,
)
.await?)
}
async fn check_validation_outputs(
&self,
at: Hash,
para_id: cumulus_primitives_core::ParaId,
outputs: polkadot_primitives::v2::CandidateCommitments,
) -> Result<bool, sp_api::ApiError> {
Ok(self
.rpc_client
.parachain_host_check_validation_outputs(at, para_id, outputs)
.await?)
}
async fn session_index_for_child(
&self,
at: Hash,
) -> Result<polkadot_primitives::v2::SessionIndex, sp_api::ApiError> {
Ok(self.rpc_client.parachain_host_session_index_for_child(at).await?)
}
async fn validation_code(
&self,
at: Hash,
para_id: cumulus_primitives_core::ParaId,
assumption: polkadot_primitives::v2::OccupiedCoreAssumption,
) -> Result<Option<polkadot_primitives::v2::ValidationCode>, sp_api::ApiError> {
Ok(self.rpc_client.parachain_host_validation_code(at, para_id, assumption).await?)
}
async fn candidate_pending_availability(
&self,
at: Hash,
para_id: cumulus_primitives_core::ParaId,
) -> Result<Option<polkadot_primitives::v2::CommittedCandidateReceipt<Hash>>, sp_api::ApiError>
{
Ok(self
.rpc_client
.parachain_host_candidate_pending_availability(at, para_id)
.await?)
}
async fn candidate_events(
&self,
at: Hash,
) -> Result<Vec<polkadot_primitives::v2::CandidateEvent<Hash>>, sp_api::ApiError> {
Ok(self.rpc_client.parachain_host_candidate_events(at).await?)
}
async fn dmq_contents(
&self,
at: Hash,
recipient: cumulus_primitives_core::ParaId,
) -> Result<
Vec<cumulus_primitives_core::InboundDownwardMessage<polkadot_core_primitives::BlockNumber>>,
sp_api::ApiError,
> {
Ok(self.rpc_client.parachain_host_dmq_contents(recipient, at).await?)
}
async fn inbound_hrmp_channels_contents(
&self,
at: Hash,
recipient: cumulus_primitives_core::ParaId,
) -> Result<
std::collections::BTreeMap<
cumulus_primitives_core::ParaId,
Vec<
polkadot_core_primitives::InboundHrmpMessage<polkadot_core_primitives::BlockNumber>,
>,
>,
sp_api::ApiError,
> {
Ok(self
.rpc_client
.parachain_host_inbound_hrmp_channels_contents(recipient, at)
.await?)
}
async fn validation_code_by_hash(
&self,
at: Hash,
validation_code_hash: polkadot_primitives::v2::ValidationCodeHash,
) -> Result<Option<polkadot_primitives::v2::ValidationCode>, sp_api::ApiError> {
Ok(self
.rpc_client
.parachain_host_validation_code_by_hash(at, validation_code_hash)
.await?)
}
async fn on_chain_votes(
&self,
at: Hash,
) -> Result<Option<polkadot_primitives::v2::ScrapedOnChainVotes<Hash>>, sp_api::ApiError> {
Ok(self.rpc_client.parachain_host_on_chain_votes(at).await?)
}
async fn session_info(
&self,
at: Hash,
index: polkadot_primitives::v2::SessionIndex,
) -> Result<Option<polkadot_primitives::v2::SessionInfo>, sp_api::ApiError> {
Ok(self.rpc_client.parachain_host_session_info(at, index).await?)
}
async fn session_info_before_version_2(
&self,
at: Hash,
index: polkadot_primitives::v2::SessionIndex,
) -> Result<Option<polkadot_primitives::v2::OldV1SessionInfo>, sp_api::ApiError> {
Ok(self.rpc_client.parachain_host_session_info_before_version_2(at, index).await?)
}
async fn submit_pvf_check_statement(
&self,
at: Hash,
stmt: polkadot_primitives::v2::PvfCheckStatement,
signature: polkadot_primitives::v2::ValidatorSignature,
) -> Result<(), sp_api::ApiError> {
Ok(self
.rpc_client
.parachain_host_submit_pvf_check_statement(at, stmt, signature)
.await?)
}
async fn pvfs_require_precheck(
&self,
at: Hash,
) -> Result<Vec<polkadot_primitives::v2::ValidationCodeHash>, sp_api::ApiError> {
Ok(self.rpc_client.parachain_host_pvfs_require_precheck(at).await?)
}
async fn validation_code_hash(
&self,
at: Hash,
para_id: cumulus_primitives_core::ParaId,
assumption: polkadot_primitives::v2::OccupiedCoreAssumption,
) -> Result<Option<polkadot_primitives::v2::ValidationCodeHash>, sp_api::ApiError> {
Ok(self
.rpc_client
.parachain_host_validation_code_hash(at, para_id, assumption)
.await?)
}
async fn current_epoch(&self, at: Hash) -> Result<sp_consensus_babe::Epoch, sp_api::ApiError> {
Ok(self.rpc_client.babe_api_current_epoch(at).await?)
}
async fn authorities(
&self,
at: Hash,
) -> std::result::Result<Vec<polkadot_primitives::v2::AuthorityDiscoveryId>, sp_api::ApiError> {
Ok(self.rpc_client.authority_discovery_authorities(at).await?)
}
async fn api_version_parachain_host(&self, at: Hash) -> Result<Option<u32>, sp_api::ApiError> {
let api_id = <dyn polkadot_primitives::runtime_api::ParachainHost<Block>>::ID;
Ok(self.rpc_client.runtime_version(at).await.map(|v| v.api_version(&api_id))?)
}
async fn disputes(
&self,
at: Hash,
) -> Result<
Vec<(
polkadot_primitives::v2::SessionIndex,
polkadot_primitives::v2::CandidateHash,
polkadot_primitives::v2::DisputeState<polkadot_primitives::v2::BlockNumber>,
)>,
ApiError,
> {
Ok(self.rpc_client.parachain_host_staging_get_disputes(at).await?)
}
}
#[async_trait::async_trait]
impl AuthorityDiscovery<Block> for BlockChainRpcClient {
async fn authorities(
&self,
at: Hash,
) -> std::result::Result<Vec<polkadot_primitives::v2::AuthorityDiscoveryId>, sp_api::ApiError> {
let result = self.rpc_client.authority_discovery_authorities(at).await?;
Ok(result)
}
}
impl BlockChainRpcClient {
pub async fn local_listen_addresses(
&self,
) -> Result<Vec<MultiaddrWithPeerId>, RelayChainError> {
let addresses = self.rpc_client.system_local_listen_addresses().await?;
tracing::debug!(target: LOG_TARGET, ?addresses, "Fetched listen address from RPC node.");
let mut result_vec = Vec::new();
for address in addresses {
match MultiaddrWithPeerId::from_str(&address) {
Ok(addr) => result_vec.push(addr),
Err(err) =>
return Err(RelayChainError::GenericError(format!(
"Failed to parse a local listen addresses from the RPC node: {}",
err
))),
}
}
Ok(result_vec)
}
pub async fn import_notification_stream(
&self,
) -> RelayChainResult<Pin<Box<dyn Stream<Item = Header> + Send>>> {
Ok(self.rpc_client.get_imported_heads_stream().await?.boxed())
}
pub async fn finality_notification_stream(
&self,
) -> RelayChainResult<Pin<Box<dyn Stream<Item = Header> + Send>>> {
Ok(self.rpc_client.get_finalized_heads_stream().await?.boxed())
}
}
fn block_local<T>(fut: impl Future<Output = T>) -> T {
let tokio_handle = tokio::runtime::Handle::current();
tokio::task::block_in_place(|| tokio_handle.block_on(fut))
}
impl HeaderBackend<Block> for BlockChainRpcClient {
fn header(
&self,
id: BlockId,
) -> sp_blockchain::Result<Option<<Block as polkadot_service::BlockT>::Header>> {
let fetch_header = |hash| block_local(self.rpc_client.chain_get_header(Some(hash)));
match id {
BlockId::Hash(hash) => Ok(fetch_header(hash)?),
BlockId::Number(number) => {
if let Some(hash) = HeaderBackend::<Block>::hash(self, number)? {
Ok(fetch_header(hash)?)
} else {
Ok(None)
}
},
}
}
fn info(&self) -> Info<Block> {
let best_header = block_local(self.rpc_client.chain_get_header(None))
.expect("Unable to get header from relay chain.")
.unwrap();
let genesis_hash = block_local(self.rpc_client.chain_get_head(Some(0)))
.expect("Unable to get header from relay chain.");
let finalized_head = block_local(self.rpc_client.chain_get_finalized_head())
.expect("Unable to get finalized head from relay chain.");
let finalized_header = block_local(self.rpc_client.chain_get_header(Some(finalized_head)))
.expect("Unable to get finalized header from relay chain.")
.unwrap();
Info {
best_hash: best_header.hash(),
best_number: best_header.number,
genesis_hash,
finalized_hash: finalized_head,
finalized_number: finalized_header.number,
finalized_state: None,
number_leaves: 1,
block_gap: None,
}
}
fn status(
&self,
id: sp_api::BlockId<Block>,
) -> sp_blockchain::Result<sp_blockchain::BlockStatus> {
let exists = match id {
BlockId::Hash(_) => self.header(id)?.is_some(),
BlockId::Number(n) => {
let best_header = block_local(self.rpc_client.chain_get_header(None))?;
if let Some(best) = best_header {
n < best.number
} else {
false
}
},
};
if exists {
Ok(sc_client_api::blockchain::BlockStatus::InChain)
} else {
Ok(sc_client_api::blockchain::BlockStatus::Unknown)
}
}
fn number(
&self,
hash: <Block as polkadot_service::BlockT>::Hash,
) -> sp_blockchain::Result<
Option<<<Block as polkadot_service::BlockT>::Header as polkadot_service::HeaderT>::Number>,
> {
let result = block_local(self.rpc_client.chain_get_header(Some(hash)))?
.map(|maybe_header| maybe_header.number);
Ok(result)
}
fn hash(
&self,
number: polkadot_service::NumberFor<Block>,
) -> sp_blockchain::Result<Option<<Block as polkadot_service::BlockT>::Hash>> {
Ok(block_local(self.rpc_client.chain_get_block_hash(number.into()))?)
}
}
@@ -0,0 +1,274 @@
// Copyright 2022 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use cumulus_relay_chain_interface::RelayChainError;
use lru::LruCache;
use polkadot_availability_distribution::{
AvailabilityDistributionSubsystem, IncomingRequestReceivers,
};
use polkadot_node_core_av_store::Config;
use polkadot_node_network_protocol::{
peer_set::PeerSetProtocolNames,
request_response::{
v1::{
AvailableDataFetchingRequest, ChunkFetchingRequest, CollationFetchingRequest,
PoVFetchingRequest,
},
IncomingRequestReceiver, ReqProtocolNames,
},
};
use polkadot_node_subsystem_util::metrics::{prometheus::Registry, Metrics};
use polkadot_overseer::{
BlockInfo, DummySubsystem, MetricsTrait, Overseer, OverseerHandle, OverseerMetrics, SpawnGlue,
KNOWN_LEAVES_CACHE_SIZE,
};
use polkadot_primitives::v2::CollatorPair;
use polkadot_service::{
overseer::{
AvailabilityRecoverySubsystem, AvailabilityStoreSubsystem, ChainApiSubsystem,
CollationGenerationSubsystem, CollatorProtocolSubsystem, NetworkBridgeMetrics,
NetworkBridgeRxSubsystem, NetworkBridgeTxSubsystem, ProtocolSide, RuntimeApiSubsystem,
},
Error, OverseerConnector,
};
use sc_authority_discovery::Service as AuthorityDiscoveryService;
use sc_keystore::LocalKeystore;
use sc_network::NetworkStateInfo;
use std::sync::Arc;
use cumulus_primitives_core::relay_chain::{Block, Hash as PHash};
use polkadot_service::{Handle, TaskManager};
use crate::BlockChainRpcClient;
use futures::{select, StreamExt};
use sp_runtime::traits::Block as BlockT;
/// Arguments passed for overseer construction.
pub(crate) struct CollatorOverseerGenArgs<'a> {
/// Runtime client generic, providing the `ProvieRuntimeApi` trait besides others.
pub runtime_client: Arc<BlockChainRpcClient>,
/// Underlying network service implementation.
pub network_service: Arc<sc_network::NetworkService<Block, PHash>>,
/// Underlying authority discovery service.
pub authority_discovery_service: AuthorityDiscoveryService,
// Receiver for collation request protocol
pub collation_req_receiver: IncomingRequestReceiver<CollationFetchingRequest>,
// Receiver for PoV request protocol
pub pov_req_receiver: IncomingRequestReceiver<PoVFetchingRequest>,
// Receiver for chunk request protocol
pub chunk_req_receiver: IncomingRequestReceiver<ChunkFetchingRequest>,
// Receiver for availability request protocol
pub available_data_req_receiver: IncomingRequestReceiver<AvailableDataFetchingRequest>,
/// Prometheus registry, commonly used for production systems, less so for test.
pub registry: Option<&'a Registry>,
/// Task spawner to be used throughout the overseer and the APIs it provides.
pub spawner: sc_service::SpawnTaskHandle,
/// Determines the behavior of the collator.
pub collator_pair: CollatorPair,
/// Request response protocols
pub req_protocol_names: ReqProtocolNames,
/// Peerset protocols name mapping
pub peer_set_protocol_names: PeerSetProtocolNames,
/// Config for the availability store
pub availability_config: Config,
/// The underlying key value store for the parachains.
pub parachains_db: Arc<dyn polkadot_node_subsystem_util::database::Database>,
}
fn build_overseer<'a>(
connector: OverseerConnector,
CollatorOverseerGenArgs {
runtime_client,
network_service,
authority_discovery_service,
collation_req_receiver,
available_data_req_receiver,
availability_config,
registry,
spawner,
collator_pair,
req_protocol_names,
peer_set_protocol_names,
parachains_db,
pov_req_receiver,
chunk_req_receiver,
}: CollatorOverseerGenArgs<'a>,
) -> Result<
(Overseer<SpawnGlue<sc_service::SpawnTaskHandle>, Arc<BlockChainRpcClient>>, OverseerHandle),
Error,
> {
let leaves = Vec::new();
let metrics = <OverseerMetrics as MetricsTrait>::register(registry)?;
let keystore = Arc::new(LocalKeystore::in_memory());
let spawner = SpawnGlue(spawner);
let network_bridge_metrics: NetworkBridgeMetrics = Metrics::register(registry)?;
let builder = Overseer::builder()
.availability_distribution(AvailabilityDistributionSubsystem::new(
keystore.clone(),
IncomingRequestReceivers { pov_req_receiver, chunk_req_receiver },
Metrics::register(registry)?,
))
.availability_recovery(AvailabilityRecoverySubsystem::with_chunks_only(
available_data_req_receiver,
Metrics::register(registry)?,
))
.availability_store(AvailabilityStoreSubsystem::new(
parachains_db.clone(),
availability_config,
Metrics::register(registry)?,
))
.bitfield_distribution(DummySubsystem)
.bitfield_signing(DummySubsystem)
.candidate_backing(DummySubsystem)
.candidate_validation(DummySubsystem)
.pvf_checker(DummySubsystem)
.chain_api(ChainApiSubsystem::new(runtime_client.clone(), Metrics::register(registry)?))
.collation_generation(CollationGenerationSubsystem::new(Metrics::register(registry)?))
.collator_protocol({
let side = ProtocolSide::Collator(
network_service.local_peer_id().clone(),
collator_pair,
collation_req_receiver,
Metrics::register(registry)?,
);
CollatorProtocolSubsystem::new(side)
})
.network_bridge_rx(NetworkBridgeRxSubsystem::new(
network_service.clone(),
authority_discovery_service.clone(),
Box::new(network_service.clone()),
network_bridge_metrics.clone(),
peer_set_protocol_names.clone(),
))
.network_bridge_tx(NetworkBridgeTxSubsystem::new(
network_service.clone(),
authority_discovery_service.clone(),
network_bridge_metrics,
req_protocol_names,
peer_set_protocol_names,
))
.provisioner(DummySubsystem)
.runtime_api(RuntimeApiSubsystem::new(
runtime_client.clone(),
Metrics::register(registry)?,
spawner.clone(),
))
.statement_distribution(DummySubsystem)
.approval_distribution(DummySubsystem)
.approval_voting(DummySubsystem)
.gossip_support(DummySubsystem)
.dispute_coordinator(DummySubsystem)
.dispute_distribution(DummySubsystem)
.chain_selection(DummySubsystem)
.leaves(Vec::from_iter(
leaves
.into_iter()
.map(|BlockInfo { hash, parent_hash: _, number }| (hash, number)),
))
.activation_external_listeners(Default::default())
.span_per_active_leaf(Default::default())
.active_leaves(Default::default())
.supports_parachains(runtime_client)
.known_leaves(LruCache::new(KNOWN_LEAVES_CACHE_SIZE))
.metrics(metrics)
.spawner(spawner);
builder.build_with_connector(connector).map_err(|e| e.into())
}
pub(crate) fn spawn_overseer(
overseer_args: CollatorOverseerGenArgs,
task_manager: &TaskManager,
relay_chain_rpc_client: Arc<BlockChainRpcClient>,
) -> Result<polkadot_overseer::Handle, polkadot_service::Error> {
let (overseer, overseer_handle) = build_overseer(OverseerConnector::default(), overseer_args)
.map_err(|e| {
tracing::error!("Failed to initialize overseer: {}", e);
e
})?;
let overseer_handle = Handle::new(overseer_handle.clone());
{
let handle = overseer_handle.clone();
task_manager.spawn_essential_handle().spawn_blocking(
"overseer",
None,
Box::pin(async move {
use futures::{pin_mut, FutureExt};
let forward = forward_collator_events(relay_chain_rpc_client, handle).fuse();
let overseer_fut = overseer.run().fuse();
pin_mut!(overseer_fut);
pin_mut!(forward);
select! {
_ = forward => (),
_ = overseer_fut => (),
}
}),
);
}
Ok(overseer_handle)
}
/// Minimal relay chain node representation
pub struct NewMinimalNode {
/// Task manager running all tasks for the minimal node
pub task_manager: TaskManager,
/// Overseer handle to interact with subsystems
pub overseer_handle: Handle,
/// Network service
pub network: Arc<sc_network::NetworkService<Block, <Block as BlockT>::Hash>>,
}
/// Glues together the [`Overseer`] and `BlockchainEvents` by forwarding
/// import and finality notifications into the [`OverseerHandle`].
async fn forward_collator_events(
client: Arc<BlockChainRpcClient>,
mut handle: Handle,
) -> Result<(), RelayChainError> {
let mut finality = client.finality_notification_stream().await?.fuse();
let mut imports = client.import_notification_stream().await?.fuse();
loop {
select! {
f = finality.next() => {
match f {
Some(header) => {
tracing::info!(target: "minimal-polkadot-node", "Received finalized block via RPC: #{} ({})", header.number, header.hash());
let block_info = BlockInfo { hash: header.hash(), parent_hash: header.parent_hash, number: header.number };
handle.block_finalized(block_info).await;
}
None => return Err(RelayChainError::GenericError("Relay chain finality stream ended.".to_string())),
}
},
i = imports.next() => {
match i {
Some(header) => {
tracing::info!(target: "minimal-polkadot-node", "Received imported block via RPC: #{} ({})", header.number, header.hash());
let block_info = BlockInfo { hash: header.hash(), parent_hash: header.parent_hash, number: header.number };
handle.block_imported(block_info).await;
}
None => return Err(RelayChainError::GenericError("Relay chain import stream ended.".to_string())),
}
}
}
}
}
@@ -0,0 +1,223 @@
// Copyright 2017-2022 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use collator_overseer::{CollatorOverseerGenArgs, NewMinimalNode};
use cumulus_relay_chain_interface::{RelayChainError, RelayChainInterface, RelayChainResult};
use cumulus_relay_chain_rpc_interface::{RelayChainRpcInterface, Url};
use polkadot_network_bridge::{peer_sets_info, IsAuthority};
use polkadot_node_network_protocol::{
peer_set::PeerSetProtocolNames,
request_response::{self, IncomingRequest, ReqProtocolNames},
};
use polkadot_node_subsystem_util::metrics::prometheus::Registry;
use polkadot_primitives::v2::CollatorPair;
use sc_authority_discovery::Service as AuthorityDiscoveryService;
use sc_network::{Event, NetworkService};
use sc_network_common::service::NetworkEventStream;
use std::sync::Arc;
use polkadot_service::{open_database, Configuration, TaskManager};
use futures::StreamExt;
use sp_runtime::{app_crypto::Pair, traits::Block as BlockT};
mod collator_overseer;
mod network;
mod blockchain_rpc_client;
pub use blockchain_rpc_client::BlockChainRpcClient;
fn build_authority_discovery_service<Block: BlockT>(
task_manager: &TaskManager,
client: Arc<BlockChainRpcClient>,
config: &Configuration,
network: Arc<NetworkService<Block, <Block as BlockT>::Hash>>,
prometheus_registry: Option<Registry>,
) -> AuthorityDiscoveryService {
let auth_disc_publish_non_global_ips = config.network.allow_non_globals_in_dht;
let authority_discovery_role = sc_authority_discovery::Role::Discover;
let dht_event_stream = network.event_stream("authority-discovery").filter_map(|e| async move {
match e {
Event::Dht(e) => Some(e),
_ => None,
}
});
let (worker, service) = sc_authority_discovery::new_worker_and_service_with_config(
sc_authority_discovery::WorkerConfig {
publish_non_global_ips: auth_disc_publish_non_global_ips,
// Require that authority discovery records are signed.
strict_record_validation: true,
..Default::default()
},
client,
network.clone(),
Box::pin(dht_event_stream),
authority_discovery_role,
prometheus_registry.clone(),
);
task_manager.spawn_handle().spawn(
"authority-discovery-worker",
Some("authority-discovery"),
worker.run(),
);
service
}
pub async fn build_minimal_relay_chain_node(
polkadot_config: Configuration,
task_manager: &mut TaskManager,
relay_chain_url: Url,
) -> RelayChainResult<(Arc<(dyn RelayChainInterface + 'static)>, Option<CollatorPair>)> {
let client = cumulus_relay_chain_rpc_interface::create_client_and_start_worker(
relay_chain_url,
task_manager,
)
.await?;
let collator_pair = CollatorPair::generate().0;
let collator_node = new_minimal_relay_chain(
polkadot_config,
collator_pair.clone(),
Arc::new(BlockChainRpcClient::new(client.clone())),
)
.await?;
task_manager.add_child(collator_node.task_manager);
Ok((
Arc::new(RelayChainRpcInterface::new(client, collator_node.overseer_handle)),
Some(collator_pair),
))
}
/// Builds a minimal relay chain node. Chain data is fetched
/// via [`BlockChainRpcClient`] and fed into the overseer and its subsystems.
///
/// Instead of spawning all subsystems, this minimal node will only spawn subsystems
/// required to collate:
/// - AvailabilityRecovery
/// - CollationGeneration
/// - CollatorProtocol
/// - NetworkBridgeRx
/// - NetworkBridgeTx
/// - RuntimeApi
/// - ChainApi
/// - AvailabilityDistribution
#[sc_tracing::logging::prefix_logs_with("Relaychain")]
async fn new_minimal_relay_chain(
mut config: Configuration,
collator_pair: CollatorPair,
relay_chain_rpc_client: Arc<BlockChainRpcClient>,
) -> Result<NewMinimalNode, RelayChainError> {
let role = config.role.clone();
// Use the given RPC node as bootnode, since we do not have a chain spec with valid boot nodes
let mut boot_node_address = relay_chain_rpc_client.local_listen_addresses().await?;
config.network.boot_nodes.append(&mut boot_node_address);
let task_manager = {
let registry = config.prometheus_config.as_ref().map(|cfg| &cfg.registry);
TaskManager::new(config.tokio_handle.clone(), registry)?
};
let prometheus_registry = config.prometheus_registry().cloned();
let genesis_hash = relay_chain_rpc_client
.block_get_hash(Some(0))
.await
.expect("Genesis block hash is always available; qed")
.unwrap_or_default();
let peer_set_protocol_names =
PeerSetProtocolNames::new(genesis_hash, config.chain_spec.fork_id());
let is_authority = if role.is_authority() { IsAuthority::Yes } else { IsAuthority::No };
config
.network
.extra_sets
.extend(peer_sets_info(is_authority, &peer_set_protocol_names));
let request_protocol_names = ReqProtocolNames::new(genesis_hash, config.chain_spec.fork_id());
let (collation_req_receiver, available_data_req_receiver, pov_req_receiver, chunk_req_receiver) =
build_request_response_protocol_receivers(&request_protocol_names, &mut config);
let (network, network_starter) =
network::build_collator_network(network::BuildCollatorNetworkParams {
config: &config,
client: relay_chain_rpc_client.clone(),
spawn_handle: task_manager.spawn_handle(),
genesis_hash,
})?;
let authority_discovery_service = build_authority_discovery_service(
&task_manager,
relay_chain_rpc_client.clone(),
&config,
network.clone(),
prometheus_registry.clone(),
);
let parachains_db = open_database(&config.database)?;
let overseer_args = CollatorOverseerGenArgs {
runtime_client: relay_chain_rpc_client.clone(),
network_service: network.clone(),
authority_discovery_service,
collation_req_receiver,
available_data_req_receiver,
registry: prometheus_registry.as_ref(),
spawner: task_manager.spawn_handle(),
collator_pair,
req_protocol_names: request_protocol_names,
peer_set_protocol_names,
parachains_db,
availability_config: polkadot_service::AVAILABILITY_CONFIG,
pov_req_receiver,
chunk_req_receiver,
};
let overseer_handle = collator_overseer::spawn_overseer(
overseer_args,
&task_manager,
relay_chain_rpc_client.clone(),
)?;
network_starter.start_network();
Ok(NewMinimalNode { task_manager, overseer_handle, network })
}
fn build_request_response_protocol_receivers(
request_protocol_names: &ReqProtocolNames,
config: &mut Configuration,
) -> (
request_response::IncomingRequestReceiver<request_response::v1::CollationFetchingRequest>,
request_response::IncomingRequestReceiver<request_response::v1::AvailableDataFetchingRequest>,
request_response::IncomingRequestReceiver<request_response::v1::PoVFetchingRequest>,
request_response::IncomingRequestReceiver<request_response::v1::ChunkFetchingRequest>,
) {
let (collation_req_receiver, cfg) =
IncomingRequest::get_config_receiver(request_protocol_names);
config.network.request_response_protocols.push(cfg);
let (available_data_req_receiver, cfg) =
IncomingRequest::get_config_receiver(request_protocol_names);
config.network.request_response_protocols.push(cfg);
let (pov_req_receiver, cfg) = IncomingRequest::get_config_receiver(request_protocol_names);
config.network.request_response_protocols.push(cfg);
let (chunk_req_receiver, cfg) = IncomingRequest::get_config_receiver(request_protocol_names);
config.network.request_response_protocols.push(cfg);
(collation_req_receiver, available_data_req_receiver, pov_req_receiver, chunk_req_receiver)
}
@@ -0,0 +1,384 @@
// Copyright 2022 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// Cumulus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Cumulus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Cumulus. If not, see <http://www.gnu.org/licenses/>.
use polkadot_core_primitives::{Block, Hash};
use polkadot_service::{BlockT, NumberFor};
use polkadot_node_network_protocol::PeerId;
use sc_network::{NetworkService, SyncState};
use sc_network_common::sync::{Metrics, SyncStatus};
use sc_network_light::light_client_requests;
use sc_network_sync::{block_request_handler, state_request_handler};
use sc_service::{error::Error, Configuration, NetworkStarter, SpawnTaskHandle};
use sp_consensus::BlockOrigin;
use sp_runtime::Justifications;
use std::sync::Arc;
use crate::BlockChainRpcClient;
pub(crate) struct BuildCollatorNetworkParams<'a> {
/// The service configuration.
pub config: &'a Configuration,
/// A shared client returned by `new_full_parts`.
pub client: Arc<BlockChainRpcClient>,
/// A handle for spawning tasks.
pub spawn_handle: SpawnTaskHandle,
/// Genesis hash
pub genesis_hash: Hash,
}
/// Build the network service, the network status sinks and an RPC sender.
pub(crate) fn build_collator_network(
params: BuildCollatorNetworkParams,
) -> Result<(Arc<NetworkService<Block, Hash>>, NetworkStarter), Error> {
let BuildCollatorNetworkParams { config, client, spawn_handle, genesis_hash } = params;
let protocol_id = config.protocol_id();
let block_request_protocol_config =
block_request_handler::generate_protocol_config(&protocol_id, genesis_hash, None);
let state_request_protocol_config =
state_request_handler::generate_protocol_config(&protocol_id, genesis_hash, None);
let light_client_request_protocol_config =
light_client_requests::generate_protocol_config(&protocol_id, genesis_hash, None);
let network_params = sc_network::config::Params {
role: config.role.clone(),
executor: {
let spawn_handle = Clone::clone(&spawn_handle);
Some(Box::new(move |fut| {
spawn_handle.spawn("libp2p-node", Some("networking"), fut);
}))
},
fork_id: None,
chain_sync: Box::new(DummyChainSync),
network_config: config.network.clone(),
chain: client.clone(),
import_queue: Box::new(DummyImportQueue),
protocol_id,
metrics_registry: config.prometheus_config.as_ref().map(|config| config.registry.clone()),
block_request_protocol_config,
state_request_protocol_config,
warp_sync_protocol_config: None,
light_client_request_protocol_config,
request_response_protocol_configs: Vec::new(),
};
let network_worker = sc_network::NetworkWorker::new(network_params)?;
let network_service = network_worker.service().clone();
let (network_start_tx, network_start_rx) = futures::channel::oneshot::channel();
// The network worker is responsible for gathering all network messages and processing
// them. This is quite a heavy task, and at the time of the writing of this comment it
// frequently happens that this future takes several seconds or in some situations
// even more than a minute until it has processed its entire queue. This is clearly an
// issue, and ideally we would like to fix the network future to take as little time as
// possible, but we also take the extra harm-prevention measure to execute the networking
// future using `spawn_blocking`.
spawn_handle.spawn_blocking("network-worker", Some("networking"), async move {
if network_start_rx.await.is_err() {
tracing::warn!(
"The NetworkStart returned as part of `build_network` has been silently dropped"
);
// This `return` might seem unnecessary, but we don't want to make it look like
// everything is working as normal even though the user is clearly misusing the API.
return
}
network_worker.await
});
let network_starter = NetworkStarter::new(network_start_tx);
Ok((network_service, network_starter))
}
/// Empty ChainSync shell. Syncing code is not necessary for
/// the minimal node, but network currently requires it. So
/// we provide a noop implementation.
struct DummyChainSync;
impl<B: BlockT> sc_network_common::sync::ChainSync<B> for DummyChainSync {
fn peer_info(&self, _who: &PeerId) -> Option<sc_network_common::sync::PeerInfo<B>> {
None
}
fn status(&self) -> sc_network_common::sync::SyncStatus<B> {
SyncStatus {
state: SyncState::Idle,
best_seen_block: None,
num_peers: 0,
queued_blocks: 0,
state_sync: None,
warp_sync: None,
}
}
fn num_sync_requests(&self) -> usize {
0
}
fn num_downloaded_blocks(&self) -> usize {
0
}
fn num_peers(&self) -> usize {
0
}
fn new_peer(
&mut self,
_who: PeerId,
_best_hash: <B as BlockT>::Hash,
_best_number: polkadot_service::NumberFor<B>,
) -> Result<
Option<sc_network_common::sync::message::BlockRequest<B>>,
sc_network_common::sync::BadPeer,
> {
Ok(None)
}
fn update_chain_info(
&mut self,
_best_hash: &<B as BlockT>::Hash,
_best_number: polkadot_service::NumberFor<B>,
) {
}
fn request_justification(
&mut self,
_hash: &<B as BlockT>::Hash,
_number: polkadot_service::NumberFor<B>,
) {
}
fn clear_justification_requests(&mut self) {}
fn set_sync_fork_request(
&mut self,
_peers: Vec<PeerId>,
_hash: &<B as BlockT>::Hash,
_number: polkadot_service::NumberFor<B>,
) {
}
fn justification_requests(
&mut self,
) -> Box<dyn Iterator<Item = (PeerId, sc_network_common::sync::message::BlockRequest<B>)> + '_>
{
Box::new(std::iter::empty())
}
fn block_requests(
&mut self,
) -> Box<dyn Iterator<Item = (&PeerId, sc_network_common::sync::message::BlockRequest<B>)> + '_>
{
Box::new(std::iter::empty())
}
fn state_request(&mut self) -> Option<(PeerId, sc_network_common::sync::OpaqueStateRequest)> {
None
}
fn warp_sync_request(
&mut self,
) -> Option<(PeerId, sc_network_common::sync::warp::WarpProofRequest<B>)> {
None
}
fn on_block_data(
&mut self,
_who: &PeerId,
_request: Option<sc_network_common::sync::message::BlockRequest<B>>,
_response: sc_network_common::sync::message::BlockResponse<B>,
) -> Result<sc_network_common::sync::OnBlockData<B>, sc_network_common::sync::BadPeer> {
unimplemented!("Not supported on the RPC collator")
}
fn on_state_data(
&mut self,
_who: &PeerId,
_response: sc_network_common::sync::OpaqueStateResponse,
) -> Result<sc_network_common::sync::OnStateData<B>, sc_network_common::sync::BadPeer> {
unimplemented!("Not supported on the RPC collator")
}
fn on_warp_sync_data(
&mut self,
_who: &PeerId,
_response: sc_network_common::sync::warp::EncodedProof,
) -> Result<(), sc_network_common::sync::BadPeer> {
unimplemented!("Not supported on the RPC collator")
}
fn on_block_justification(
&mut self,
_who: PeerId,
_response: sc_network_common::sync::message::BlockResponse<B>,
) -> Result<sc_network_common::sync::OnBlockJustification<B>, sc_network_common::sync::BadPeer>
{
unimplemented!("Not supported on the RPC collator")
}
fn on_blocks_processed(
&mut self,
_imported: usize,
_count: usize,
_results: Vec<(
Result<
sc_consensus::BlockImportStatus<polkadot_service::NumberFor<B>>,
sc_consensus::BlockImportError,
>,
<B as BlockT>::Hash,
)>,
) -> Box<
dyn Iterator<
Item = Result<
(PeerId, sc_network_common::sync::message::BlockRequest<B>),
sc_network_common::sync::BadPeer,
>,
>,
> {
Box::new(std::iter::empty())
}
fn on_justification_import(
&mut self,
_hash: <B as BlockT>::Hash,
_number: polkadot_service::NumberFor<B>,
_success: bool,
) {
}
fn on_block_finalized(
&mut self,
_hash: &<B as BlockT>::Hash,
_number: polkadot_service::NumberFor<B>,
) {
}
fn push_block_announce_validation(
&mut self,
_who: PeerId,
_hash: <B as BlockT>::Hash,
_announce: sc_network_common::sync::message::BlockAnnounce<<B as BlockT>::Header>,
_is_best: bool,
) {
}
fn poll_block_announce_validation(
&mut self,
_cx: &mut std::task::Context,
) -> std::task::Poll<sc_network_common::sync::PollBlockAnnounceValidation<<B as BlockT>::Header>>
{
std::task::Poll::Pending
}
fn peer_disconnected(
&mut self,
_who: &PeerId,
) -> Option<sc_network_common::sync::OnBlockData<B>> {
None
}
fn metrics(&self) -> sc_network_common::sync::Metrics {
Metrics {
queued_blocks: 0,
fork_targets: 0,
justifications: sc_network_common::sync::metrics::Metrics {
pending_requests: 0,
active_requests: 0,
importing_requests: 0,
failed_requests: 0,
},
}
}
fn create_opaque_block_request(
&self,
_request: &sc_network_common::sync::message::BlockRequest<B>,
) -> sc_network_common::sync::OpaqueBlockRequest {
unimplemented!("Not supported on the RPC collator")
}
fn encode_block_request(
&self,
_request: &sc_network_common::sync::OpaqueBlockRequest,
) -> Result<Vec<u8>, String> {
unimplemented!("Not supported on the RPC collator")
}
fn decode_block_response(
&self,
_response: &[u8],
) -> Result<sc_network_common::sync::OpaqueBlockResponse, String> {
unimplemented!("Not supported on the RPC collator")
}
fn block_response_into_blocks(
&self,
_request: &sc_network_common::sync::message::BlockRequest<B>,
_response: sc_network_common::sync::OpaqueBlockResponse,
) -> Result<Vec<sc_network_common::sync::message::BlockData<B>>, String> {
unimplemented!("Not supported on the RPC collator")
}
fn encode_state_request(
&self,
_request: &sc_network_common::sync::OpaqueStateRequest,
) -> Result<Vec<u8>, String> {
unimplemented!("Not supported on the RPC collator")
}
fn decode_state_response(
&self,
_response: &[u8],
) -> Result<sc_network_common::sync::OpaqueStateResponse, String> {
unimplemented!("Not supported on the RPC collator")
}
}
struct DummyImportQueue;
impl sc_service::ImportQueue<Block> for DummyImportQueue {
fn import_blocks(
&mut self,
_origin: BlockOrigin,
_blocks: Vec<sc_consensus::IncomingBlock<Block>>,
) {
}
fn import_justifications(
&mut self,
_who: PeerId,
_hash: Hash,
_number: NumberFor<Block>,
_justifications: Justifications,
) {
}
fn poll_actions(
&mut self,
_cx: &mut futures::task::Context,
_link: &mut dyn sc_consensus::import_queue::Link<Block>,
) {
}
}